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

Last change on this file since 13661 was 13647, checked in by Don-vip, 6 years ago

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

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