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

Last change on this file since 13146 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • 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.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, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), undoAction);
89 component.getInputMap().put(
90 KeyStroke.getKeyStroke(KeyEvent.VK_Y, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), 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 = ImageProvider.get(iconName, ImageProvider.ImageSizes.SMALLICON);
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.