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

Last change on this file since 16420 was 16420, checked in by simon04, 4 years ago

see #19237 - Add /*ICON*/ markers

  • Property svn:eol-style set to native
File size: 9.8 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.GraphicsEnvironment;
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.beans.PropertyChangeListener;
10
11import javax.swing.AbstractAction;
12import javax.swing.Action;
13import javax.swing.ImageIcon;
14import javax.swing.JMenuItem;
15import javax.swing.JPopupMenu;
16import javax.swing.KeyStroke;
17import javax.swing.event.UndoableEditListener;
18import javax.swing.text.DefaultEditorKit;
19import javax.swing.text.JTextComponent;
20import javax.swing.undo.CannotRedoException;
21import javax.swing.undo.CannotUndoException;
22import javax.swing.undo.UndoManager;
23
24import org.openstreetmap.josm.spi.preferences.Config;
25import org.openstreetmap.josm.tools.ImageProvider;
26import org.openstreetmap.josm.tools.Logging;
27import org.openstreetmap.josm.tools.PlatformManager;
28
29/**
30 * A popup menu designed for text components. It displays the following actions:
31 * <ul>
32 * <li>Undo</li>
33 * <li>Redo</li>
34 * <li>Cut</li>
35 * <li>Copy</li>
36 * <li>Paste</li>
37 * <li>Delete</li>
38 * <li>Select All</li>
39 * </ul>
40 * @since 5886
41 */
42public class TextContextualPopupMenu extends JPopupMenu {
43
44 private static final String EDITABLE = "editable";
45
46 protected JTextComponent component;
47 protected boolean undoRedo;
48 protected final UndoAction undoAction = new UndoAction();
49 protected final RedoAction redoAction = new RedoAction();
50 protected final UndoManager undo = new UndoManager();
51
52 protected final transient UndoableEditListener undoEditListener = e -> {
53 undo.addEdit(e.getEdit());
54 updateUndoRedoState();
55 };
56
57 protected final transient PropertyChangeListener propertyChangeListener = evt -> {
58 if (EDITABLE.equals(evt.getPropertyName())) {
59 removeAll();
60 addMenuEntries();
61 }
62 };
63
64 /**
65 * Creates a new {@link TextContextualPopupMenu}.
66 */
67 protected TextContextualPopupMenu() {
68 // Restricts visibility
69 }
70
71 private void updateUndoRedoState() {
72 undoAction.updateUndoState();
73 redoAction.updateRedoState();
74 }
75
76 /**
77 * Attaches this contextual menu to the given text component.
78 * A menu can only be attached to a single component.
79 * @param component The text component that will display the menu and handle its actions.
80 * @param undoRedo {@code true} if undo/redo must be supported
81 * @return {@code this}
82 * @see #detach()
83 */
84 protected TextContextualPopupMenu attach(JTextComponent component, boolean undoRedo) {
85 if (component != null && !isAttached()) {
86 this.component = component;
87 if (undoRedo && component.isEditable()) {
88 enableUndoRedo();
89 }
90 addMenuEntries();
91 component.addPropertyChangeListener(EDITABLE, propertyChangeListener);
92 }
93 return this;
94 }
95
96 private void enableUndoRedo() {
97 if (!undoRedo) {
98 component.getDocument().addUndoableEditListener(undoEditListener);
99 if (!GraphicsEnvironment.isHeadless()) {
100 component.getInputMap().put(
101 KeyStroke.getKeyStroke(KeyEvent.VK_Z, PlatformManager.getPlatform().getMenuShortcutKeyMaskEx()), undoAction);
102 component.getInputMap().put(
103 KeyStroke.getKeyStroke(KeyEvent.VK_Y, PlatformManager.getPlatform().getMenuShortcutKeyMaskEx()), redoAction);
104 }
105 undoRedo = true;
106 }
107 }
108
109 private void disableUndoRedo() {
110 if (undoRedo) {
111 if (!GraphicsEnvironment.isHeadless()) {
112 component.getInputMap().remove(
113 KeyStroke.getKeyStroke(KeyEvent.VK_Z, PlatformManager.getPlatform().getMenuShortcutKeyMaskEx()));
114 component.getInputMap().remove(
115 KeyStroke.getKeyStroke(KeyEvent.VK_Y, PlatformManager.getPlatform().getMenuShortcutKeyMaskEx()));
116 }
117 component.getDocument().removeUndoableEditListener(undoEditListener);
118 undoRedo = false;
119 }
120 }
121
122 private void addMenuEntries() {
123 if (component.isEditable()) {
124 if (undoRedo) {
125 addMenuEntry(new JMenuItem(undoAction), "undo");
126 addMenuEntry(new JMenuItem(redoAction), "redo");
127 addSeparator();
128 }
129 addMenuEntry(component, tr("Cut"), DefaultEditorKit.cutAction, /* ICON */ "cut");
130 }
131 addMenuEntry(component, tr("Copy"), DefaultEditorKit.copyAction, /* ICON */ "copy");
132 if (component.isEditable()) {
133 addMenuEntry(component, tr("Paste"), DefaultEditorKit.pasteAction, /* ICON */ "paste");
134 addMenuEntry(component, tr("Delete"), DefaultEditorKit.deleteNextCharAction, /* ICON */ "dialogs/delete");
135 }
136 addSeparator();
137 addMenuEntry(component, tr("Select All"), DefaultEditorKit.selectAllAction, /* ICON */ "dialogs/select");
138 }
139
140 /**
141 * Detaches this contextual menu from its text component.
142 * @return {@code this}
143 * @see #attach(JTextComponent, boolean)
144 */
145 protected TextContextualPopupMenu detach() {
146 if (isAttached()) {
147 component.removePropertyChangeListener(EDITABLE, propertyChangeListener);
148 removeAll();
149 if (undoRedo) {
150 disableUndoRedo();
151 }
152 component = null;
153 }
154 return this;
155 }
156
157 /**
158 * Creates a new {@link TextContextualPopupMenu} and enables it for the given text component.
159 * @param component The component that will display the menu and handle its actions.
160 * @param undoRedo Enables or not Undo/Redo feature. Not recommended for table cell editors, unless each cell provides its own editor
161 * @return The {@link PopupMenuLauncher} responsible of displaying the popup menu.
162 * Call {@link #disableMenuFor} with this object if you want to disable the menu later.
163 * @see #disableMenuFor
164 */
165 public static PopupMenuLauncher enableMenuFor(JTextComponent component, boolean undoRedo) {
166 PopupMenuLauncher launcher = new PopupMenuLauncher(new TextContextualPopupMenu().attach(component, undoRedo), true);
167 component.addMouseListener(launcher);
168 return launcher;
169 }
170
171 /**
172 * Disables the {@link TextContextualPopupMenu} attached to the given popup menu launcher and text component.
173 * @param component The component that currently displays the menu and handles its actions.
174 * @param launcher The {@link PopupMenuLauncher} obtained via {@link #enableMenuFor}.
175 * @see #enableMenuFor
176 */
177 public static void disableMenuFor(JTextComponent component, PopupMenuLauncher launcher) {
178 if (launcher.getMenu() instanceof TextContextualPopupMenu) {
179 ((TextContextualPopupMenu) launcher.getMenu()).detach();
180 component.removeMouseListener(launcher);
181 }
182 }
183
184 /**
185 * Empties the internal undo manager.
186 * @since 14977
187 */
188 public void discardAllUndoableEdits() {
189 undo.discardAllEdits();
190 updateUndoRedoState();
191 }
192
193 /**
194 * Determines if this popup is currently attached to a component.
195 * @return {@code true} if this popup is currently attached to a component, {@code false} otherwise.
196 */
197 public final boolean isAttached() {
198 return component != null;
199 }
200
201 protected void addMenuEntry(JTextComponent component, String label, String actionName, String iconName) {
202 Action action = component.getActionMap().get(actionName);
203 if (action != null) {
204 JMenuItem mi = new JMenuItem(action);
205 mi.setText(label);
206 addMenuEntry(mi, iconName);
207 }
208 }
209
210 protected void addMenuEntry(JMenuItem mi, String iconName) {
211 if (iconName != null && Config.getPref().getBoolean("text.popupmenu.useicons", true)) {
212 ImageIcon icon = new ImageProvider(iconName).setSize(ImageProvider.ImageSizes.SMALLICON).get();
213 mi.setIcon(icon);
214 }
215 add(mi);
216 }
217
218 protected class UndoAction extends AbstractAction {
219
220 /**
221 * Constructs a new {@code UndoAction}.
222 */
223 public UndoAction() {
224 super(tr("Undo"));
225 setEnabled(false);
226 }
227
228 @Override
229 public void actionPerformed(ActionEvent e) {
230 try {
231 undo.undo();
232 } catch (CannotUndoException ex) {
233 Logging.trace(ex);
234 } finally {
235 updateUndoState();
236 redoAction.updateRedoState();
237 }
238 }
239
240 public void updateUndoState() {
241 if (undo.canUndo()) {
242 setEnabled(true);
243 putValue(Action.NAME, undo.getUndoPresentationName());
244 } else {
245 setEnabled(false);
246 putValue(Action.NAME, tr("Undo"));
247 }
248 }
249 }
250
251 protected class RedoAction extends AbstractAction {
252
253 /**
254 * Constructs a new {@code RedoAction}.
255 */
256 public RedoAction() {
257 super(tr("Redo"));
258 new ImageProvider("redo").getResource().attachImageIcon(this);
259 setEnabled(false);
260 }
261
262 @Override
263 public void actionPerformed(ActionEvent e) {
264 try {
265 undo.redo();
266 } catch (CannotRedoException ex) {
267 Logging.trace(ex);
268 } finally {
269 updateRedoState();
270 undoAction.updateUndoState();
271 }
272 }
273
274 public void updateRedoState() {
275 if (undo.canRedo()) {
276 setEnabled(true);
277 putValue(Action.NAME, undo.getRedoPresentationName());
278 } else {
279 setEnabled(false);
280 putValue(Action.NAME, tr("Redo"));
281 }
282 }
283 }
284}
Note: See TracBrowser for help on using the repository browser.