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

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

fix potential NPEs and Sonar issues related to serialization

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