source: josm/trunk/src/org/openstreetmap/josm/gui/widgets/TextContextualPopupMenu.java@ 8000

Last change on this file since 8000 was 8000, checked in by Don-vip, 9 years ago

fix #11046 - bind Undo action to Ctrl-Z in JosmTextField

  • Property svn:eol-style set to native
File size: 6.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.widgets;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Toolkit;
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.beans.PropertyChangeEvent;
10import java.beans.PropertyChangeListener;
11
12import javax.swing.AbstractAction;
13import javax.swing.Action;
14import javax.swing.ImageIcon;
15import javax.swing.JMenuItem;
16import javax.swing.JPopupMenu;
17import javax.swing.KeyStroke;
18import javax.swing.event.UndoableEditEvent;
19import javax.swing.event.UndoableEditListener;
20import javax.swing.text.DefaultEditorKit;
21import javax.swing.text.JTextComponent;
22import javax.swing.undo.CannotUndoException;
23import javax.swing.undo.UndoManager;
24
25import org.openstreetmap.josm.Main;
26import org.openstreetmap.josm.tools.ImageProvider;
27
28/**
29 * A popup menu designed for text components. It displays the following actions:
30 * <ul>
31 * <li>Undo</li>
32 * <li>Cut</li>
33 * <li>Copy</li>
34 * <li>Paste</li>
35 * <li>Delete</li>
36 * <li>Select All</li>
37 * </ul>
38 * @since 5886
39 */
40public class TextContextualPopupMenu extends JPopupMenu {
41
42 private static final String EDITABLE = "editable";
43
44 protected JTextComponent component = null;
45 protected UndoAction undoAction = null;
46
47 protected final PropertyChangeListener propertyChangeListener = new PropertyChangeListener() {
48 @Override public void propertyChange(PropertyChangeEvent evt) {
49 if (EDITABLE.equals(evt.getPropertyName())) {
50 removeAll();
51 addMenuEntries();
52 }
53 }
54 };
55
56 /**
57 * Creates a new {@link TextContextualPopupMenu}.
58 */
59 protected TextContextualPopupMenu() {
60 }
61
62 /**
63 * Attaches this contextual menu to the given text component.
64 * A menu can only be attached to a single component.
65 * @param component The text component that will display the menu and handle its actions.
66 * @return {@code this}
67 * @see #detach()
68 */
69 protected TextContextualPopupMenu attach(JTextComponent component) {
70 if (component != null && !isAttached()) {
71 this.component = component;
72 if (component.isEditable()) {
73 undoAction = new UndoAction();
74 component.getDocument().addUndoableEditListener(undoAction);
75 component.getInputMap().put(
76 KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), undoAction);
77 }
78 addMenuEntries();
79 component.addPropertyChangeListener(EDITABLE, propertyChangeListener);
80 }
81 return this;
82 }
83
84 private void addMenuEntries() {
85 if (component.isEditable()) {
86 add(new JMenuItem(undoAction));
87 addSeparator();
88 addMenuEntry(component, tr("Cut"), DefaultEditorKit.cutAction, null);
89 }
90 addMenuEntry(component, tr("Copy"), DefaultEditorKit.copyAction, "copy");
91 if (component.isEditable()) {
92 addMenuEntry(component, tr("Paste"), DefaultEditorKit.pasteAction, "paste");
93 addMenuEntry(component, tr("Delete"), DefaultEditorKit.deleteNextCharAction, null);
94 }
95 addSeparator();
96 addMenuEntry(component, tr("Select All"), DefaultEditorKit.selectAllAction, null);
97 }
98
99 /**
100 * Detaches this contextual menu from its text component.
101 * @return {@code this}
102 * @see #attach(JTextComponent)
103 */
104 protected TextContextualPopupMenu detach() {
105 if (isAttached()) {
106 component.removePropertyChangeListener(EDITABLE, propertyChangeListener);
107 removeAll();
108 if (undoAction != null) {
109 component.getDocument().removeUndoableEditListener(undoAction);
110 undoAction = null;
111 }
112 this.component = null;
113 }
114 return this;
115 }
116
117 /**
118 * Creates a new {@link TextContextualPopupMenu} and enables it for the given text component.
119 * @param component The component that will display the menu and handle its actions.
120 * @return The {@link PopupMenuLauncher} responsible of displaying the popup menu.
121 * Call {@link #disableMenuFor} with this object if you want to disable the menu later.
122 * @see #disableMenuFor(JTextComponent, PopupMenuLauncher)
123 */
124 public static PopupMenuLauncher enableMenuFor(JTextComponent component) {
125 PopupMenuLauncher launcher = new PopupMenuLauncher(new TextContextualPopupMenu().attach(component), true);
126 component.addMouseListener(launcher);
127 return launcher;
128 }
129
130 /**
131 * Disables the {@link TextContextualPopupMenu} attached to the given popup menu launcher and text component.
132 * @param component The component that currently displays the menu and handles its actions.
133 * @param launcher The {@link PopupMenuLauncher} obtained via {@link #enableMenuFor}.
134 * @see #enableMenuFor(JTextComponent)
135 */
136 public static void disableMenuFor(JTextComponent component, PopupMenuLauncher launcher) {
137 if (launcher.getMenu() instanceof TextContextualPopupMenu) {
138 ((TextContextualPopupMenu) launcher.getMenu()).detach();
139 component.removeMouseListener(launcher);
140 }
141 }
142
143 /**
144 * Determines if this popup is currently attached to a component.
145 * @return {@code true} if this popup is currently attached to a component, {@code false} otherwise.
146 */
147 public final boolean isAttached() {
148 return component != null;
149 }
150
151 protected void addMenuEntry(JTextComponent component, String label, String actionName, String iconName) {
152 Action action = component.getActionMap().get(actionName);
153 if (action != null) {
154 JMenuItem mi = new JMenuItem(action);
155 mi.setText(label);
156 if (iconName != null && Main.pref.getBoolean("text.popupmenu.useicons", true)) {
157 ImageIcon icon = new ImageProvider(iconName).setWidth(16).get();
158 if (icon != null) {
159 mi.setIcon(icon);
160 }
161 }
162 add(mi);
163 }
164 }
165
166 protected static class UndoAction extends AbstractAction implements UndoableEditListener {
167
168 private final UndoManager undoManager = new UndoManager();
169
170 /**
171 * Constructs a new {@code UndoAction}.
172 */
173 public UndoAction() {
174 super(tr("Undo"));
175 setEnabled(false);
176 }
177
178 @Override
179 public void undoableEditHappened(UndoableEditEvent e) {
180 undoManager.addEdit(e.getEdit());
181 setEnabled(undoManager.canUndo());
182 }
183
184 @Override
185 public void actionPerformed(ActionEvent e) {
186 try {
187 undoManager.undo();
188 } catch (CannotUndoException ex) {
189 if (Main.isTraceEnabled()) {
190 Main.trace(ex.getMessage());
191 }
192 } finally {
193 setEnabled(undoManager.canUndo());
194 }
195 }
196 }
197}
Note: See TracBrowser for help on using the repository browser.