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

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

see #11390 - sonar - squid:S1604 - Java 8: Anonymous inner classes containing only one method should become lambdas

  • Property svn:eol-style set to native
File size: 8.7 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.Toolkit;
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
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.UndoableEditListener;
19import javax.swing.text.DefaultEditorKit;
20import javax.swing.text.JTextComponent;
21import javax.swing.undo.CannotRedoException;
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>Redo</li>
33 * <li>Cut</li>
34 * <li>Copy</li>
35 * <li>Paste</li>
36 * <li>Delete</li>
37 * <li>Select All</li>
38 * </ul>
39 * @since 5886
40 */
41public class TextContextualPopupMenu extends JPopupMenu {
42
43 private static final String EDITABLE = "editable";
44
45 protected JTextComponent component;
46 protected boolean undoRedo;
47 protected final UndoAction undoAction = new UndoAction();
48 protected final RedoAction redoAction = new RedoAction();
49 protected final UndoManager undo = new UndoManager();
50
51 protected final transient UndoableEditListener undoEditListener = e -> {
52 undo.addEdit(e.getEdit());
53 undoAction.updateUndoState();
54 redoAction.updateRedoState();
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 /**
72 * Attaches this contextual menu to the given text component.
73 * A menu can only be attached to a single component.
74 * @param component The text component that will display the menu and handle its actions.
75 * @param undoRedo {@code true} if undo/redo must be supported
76 * @return {@code this}
77 * @see #detach()
78 */
79 protected TextContextualPopupMenu attach(JTextComponent component, boolean undoRedo) {
80 if (component != null && !isAttached()) {
81 this.component = component;
82 this.undoRedo = undoRedo;
83 if (undoRedo && component.isEditable()) {
84 component.getDocument().addUndoableEditListener(undoEditListener);
85 if (!GraphicsEnvironment.isHeadless()) {
86 component.getInputMap().put(
87 KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), undoAction);
88 component.getInputMap().put(
89 KeyStroke.getKeyStroke(KeyEvent.VK_Y, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), redoAction);
90 }
91 }
92 addMenuEntries();
93 component.addPropertyChangeListener(EDITABLE, propertyChangeListener);
94 }
95 return this;
96 }
97
98 private void addMenuEntries() {
99 if (component.isEditable()) {
100 if (undoRedo) {
101 add(new JMenuItem(undoAction));
102 add(new JMenuItem(redoAction));
103 addSeparator();
104 }
105 addMenuEntry(component, tr("Cut"), DefaultEditorKit.cutAction, null);
106 }
107 addMenuEntry(component, tr("Copy"), DefaultEditorKit.copyAction, "copy");
108 if (component.isEditable()) {
109 addMenuEntry(component, tr("Paste"), DefaultEditorKit.pasteAction, "paste");
110 addMenuEntry(component, tr("Delete"), DefaultEditorKit.deleteNextCharAction, null);
111 }
112 addSeparator();
113 addMenuEntry(component, tr("Select All"), DefaultEditorKit.selectAllAction, null);
114 }
115
116 /**
117 * Detaches this contextual menu from its text component.
118 * @return {@code this}
119 * @see #attach(JTextComponent, boolean)
120 */
121 protected TextContextualPopupMenu detach() {
122 if (isAttached()) {
123 component.removePropertyChangeListener(EDITABLE, propertyChangeListener);
124 removeAll();
125 if (undoRedo) {
126 component.getDocument().removeUndoableEditListener(undoEditListener);
127 }
128 component = null;
129 }
130 return this;
131 }
132
133 /**
134 * Creates a new {@link TextContextualPopupMenu} and enables it for the given text component.
135 * @param component The component that will display the menu and handle its actions.
136 * @param undoRedo Enables or not Undo/Redo feature. Not recommended for table cell editors, unless each cell provides its own editor
137 * @return The {@link PopupMenuLauncher} responsible of displaying the popup menu.
138 * Call {@link #disableMenuFor} with this object if you want to disable the menu later.
139 * @see #disableMenuFor
140 */
141 public static PopupMenuLauncher enableMenuFor(JTextComponent component, boolean undoRedo) {
142 PopupMenuLauncher launcher = new PopupMenuLauncher(new TextContextualPopupMenu().attach(component, undoRedo), true);
143 component.addMouseListener(launcher);
144 return launcher;
145 }
146
147 /**
148 * Disables the {@link TextContextualPopupMenu} attached to the given popup menu launcher and text component.
149 * @param component The component that currently displays the menu and handles its actions.
150 * @param launcher The {@link PopupMenuLauncher} obtained via {@link #enableMenuFor}.
151 * @see #enableMenuFor
152 */
153 public static void disableMenuFor(JTextComponent component, PopupMenuLauncher launcher) {
154 if (launcher.getMenu() instanceof TextContextualPopupMenu) {
155 ((TextContextualPopupMenu) launcher.getMenu()).detach();
156 component.removeMouseListener(launcher);
157 }
158 }
159
160 /**
161 * Determines if this popup is currently attached to a component.
162 * @return {@code true} if this popup is currently attached to a component, {@code false} otherwise.
163 */
164 public final boolean isAttached() {
165 return component != null;
166 }
167
168 protected void addMenuEntry(JTextComponent component, String label, String actionName, String iconName) {
169 Action action = component.getActionMap().get(actionName);
170 if (action != null) {
171 JMenuItem mi = new JMenuItem(action);
172 mi.setText(label);
173 if (iconName != null && Main.pref.getBoolean("text.popupmenu.useicons", true)) {
174 ImageIcon icon = ImageProvider.get(iconName, ImageProvider.ImageSizes.SMALLICON);
175 if (icon != null) {
176 mi.setIcon(icon);
177 }
178 }
179 add(mi);
180 }
181 }
182
183 protected class UndoAction extends AbstractAction {
184
185 /**
186 * Constructs a new {@code UndoAction}.
187 */
188 public UndoAction() {
189 super(tr("Undo"));
190 setEnabled(false);
191 }
192
193 @Override
194 public void actionPerformed(ActionEvent e) {
195 try {
196 undo.undo();
197 } catch (CannotUndoException ex) {
198 if (Main.isTraceEnabled()) {
199 Main.trace(ex.getMessage());
200 }
201 } finally {
202 updateUndoState();
203 redoAction.updateRedoState();
204 }
205 }
206
207 public void updateUndoState() {
208 if (undo.canUndo()) {
209 setEnabled(true);
210 putValue(Action.NAME, undo.getUndoPresentationName());
211 } else {
212 setEnabled(false);
213 putValue(Action.NAME, tr("Undo"));
214 }
215 }
216 }
217
218 protected class RedoAction extends AbstractAction {
219
220 /**
221 * Constructs a new {@code RedoAction}.
222 */
223 public RedoAction() {
224 super(tr("Redo"));
225 setEnabled(false);
226 }
227
228 @Override
229 public void actionPerformed(ActionEvent e) {
230 try {
231 undo.redo();
232 } catch (CannotRedoException ex) {
233 if (Main.isTraceEnabled()) {
234 Main.trace(ex.getMessage());
235 }
236 } finally {
237 updateRedoState();
238 undoAction.updateUndoState();
239 }
240 }
241
242 public void updateRedoState() {
243 if (undo.canRedo()) {
244 setEnabled(true);
245 putValue(Action.NAME, undo.getRedoPresentationName());
246 } else {
247 setEnabled(false);
248 putValue(Action.NAME, tr("Redo"));
249 }
250 }
251 }
252}
Note: See TracBrowser for help on using the repository browser.