001 /*
002 * $Id: DefaultMenuWrangler.java,v 1.11 2012/02/19 17:35:47 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2012
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 *
012 * All Rights Reserved
013 *
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.
016 *
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 *
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
025 * GNU Lesser Public License for more details.
026 *
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program. If not, see http://www.gnu.org/licenses.
029 */
030
031 package edu.wisc.ssec.mcidasv.jython;
032
033 import static edu.wisc.ssec.mcidasv.util.Contract.notNull;
034
035 import java.awt.Toolkit;
036 import java.awt.datatransfer.Clipboard;
037 import java.awt.datatransfer.DataFlavor;
038 import java.awt.datatransfer.Transferable;
039 import java.awt.event.ActionEvent;
040 import java.awt.event.ActionListener;
041 import java.util.Map;
042
043 import javax.swing.JMenu;
044 import javax.swing.JMenuItem;
045 import javax.swing.JPopupMenu;
046 import javax.swing.border.BevelBorder;
047
048 import org.python.core.PyObject;
049
050 import org.slf4j.Logger;
051 import org.slf4j.LoggerFactory;
052
053 // TODO(jon): this will need to be reconsidered, but it's fine for the current
054 // console.
055 public class DefaultMenuWrangler implements MenuWrangler {
056
057 private static final Logger logger = LoggerFactory.getLogger(DefaultMenuWrangler.class);
058
059 /** The {@link Console} whose menus we're {@literal "wrangling"}. */
060 private final Console console;
061
062 /** Handles {@literal "cut"} requests that originate from the menu. */
063 private final CutTextAction cutAction;
064
065 /** Handles {@literal "copy"} requests that originate from the menu. */
066 private final CopyTextAction copyAction;
067
068 /** Handles {@literal "paste"} requests that originate from the menu. */
069 private final PasteTextAction pasteAction;
070
071 /** Allows the user to clear out the buffer via the menu. */
072 private final ClearBufferAction clearAction;
073
074 /** Allows the user to select the buffer's contents. */
075 private final SelectBufferAction selectAction;
076
077 public DefaultMenuWrangler(final Console console) {
078 this.console = notNull(console, "Cannot provide a null console");
079
080 cutAction = new CutTextAction(console);
081 copyAction = new CopyTextAction(console);
082 pasteAction = new PasteTextAction(console);
083
084 clearAction = new ClearBufferAction(console);
085 selectAction = new SelectBufferAction(console);
086 }
087
088 public JPopupMenu buildMenu() {
089 JPopupMenu menu = new JPopupMenu();
090 menu.add(makeLocalsMenu());
091 menu.addSeparator();
092 menu.add(cutAction.getMenuItem());
093 menu.add(copyAction.getMenuItem());
094 menu.add(pasteAction.getMenuItem());
095 menu.addSeparator();
096 menu.add(clearAction.getMenuItem());
097 menu.add(selectAction.getMenuItem());
098 menu.setBorder(new BevelBorder(BevelBorder.RAISED));
099 return menu;
100 }
101
102 /**
103 * Don't need to handle this just yet.
104 */
105 public void stateChanged() {
106 logger.trace("noop!");
107 }
108
109 /**
110 * Returns the contents of Jython's local namespace as a {@link JMenu} that
111 * allows for (limited) introspection.
112 *
113 * @return {@code JMenu} containing the local namespace.
114 */
115 private JMenu makeLocalsMenu() {
116 JMenu menu = new JMenu("Local Namespace");
117
118 ActionListener menuClickHandler = new ActionListener() {
119 public void actionPerformed(final ActionEvent e) {
120 String varName = e.getActionCommand();
121 // TODO(jon): IDLE doesn't appear to allow inserts on anything
122 // except for the last line. is this what we want?
123 console.insertAtCaret(Console.TXT_NORMAL, varName);
124 }
125 };
126
127 // TODO(jon): it would be really cool to allow customizable submenu
128 // stuff. [ working on it! ]
129 Map<String, PyObject> locals = console.getLocalNamespace();
130 for (Map.Entry<String, PyObject> entry : locals.entrySet()) {
131
132 String key = entry.getKey();
133 PyObject value = entry.getValue();
134
135 Class<?> c = value.getClass();
136 // if (value instanceof PyJavaInstance)
137 // c = value.__tojava__(Object.class).getClass();
138
139 String itemName = key + ": " + c.getSimpleName();
140
141 JMenuItem item = new JMenuItem(itemName);
142 item.setActionCommand(key);
143 item.addActionListener(menuClickHandler);
144 menu.add(item);
145 }
146 return menu;
147 }
148
149 /**
150 * Generalized representation of a {@literal "context popup menu"}. Handles
151 * the more trivial things that the menu items need to handle.
152 */
153 private static abstract class MenuAction {
154
155 protected final Console console;
156
157 protected final String label;
158
159 protected final JMenuItem item;
160
161 protected MenuAction(final Console console, final String label) {
162 this.console = console;
163 this.label = label;
164 item = buildMenuItem();
165 }
166
167 public ActionListener getActionListener() {
168 return new ActionListener() {
169 public void actionPerformed(final ActionEvent e) {
170 doAction();
171 }
172 };
173 }
174
175 public JMenuItem getMenuItem() {
176 item.setEnabled(validConsoleState());
177 return item;
178 }
179
180 public JMenuItem buildMenuItem() {
181 JMenuItem item = new JMenuItem(label);
182 item.setEnabled(validConsoleState());
183 item.addActionListener(getActionListener());
184 return item;
185 }
186
187 abstract public boolean validConsoleState();
188
189 abstract public void doAction();
190 }
191
192 /**
193 * Allows the user to trigger a {@literal "cut"} operation. There must be
194 * some text that is currently selected in order for this to be enabled.
195 *
196 * @see javax.swing.text.JTextComponent#cut()
197 */
198 private static class CutTextAction extends MenuAction {
199
200 public CutTextAction(final Console console) {
201 super(console, "Cut");
202 }
203
204 @Override public boolean validConsoleState() {
205 if (console == null || console.getTextPane() == null) {
206 return false;
207 }
208
209 String selection = console.getTextPane().getSelectedText();
210 if (selection != null && selection.length() > 0) {
211 return true;
212 }
213 return false;
214 }
215
216 @Override public void doAction() {
217 console.getTextPane().cut();
218 }
219 }
220
221 /**
222 * Basic {@literal "copy"} operation. Requires that there is some selected
223 * text in the console's {@code JTextPane}.
224 *
225 * @see javax.swing.text.JTextComponent#copy()
226 */
227 private static class CopyTextAction extends MenuAction {
228
229 public CopyTextAction(final Console console) {
230 super(console, "Copy");
231 }
232
233 @Override public boolean validConsoleState() {
234 if (console == null || console.getTextPane() == null) {
235 return false;
236 }
237
238 String selection = console.getTextPane().getSelectedText();
239 if (selection != null && selection.length() > 0) {
240 return true;
241 }
242
243 return false;
244 }
245
246 @Override public void doAction() {
247 console.getTextPane().copy();
248 }
249 }
250
251 /**
252 * Allows the user to (attempt) to paste the contents of the <i>system</i>
253 * clipboard. Clipboard must contain some kind of {@literal "text"} for
254 * this to work.
255 *
256 * @see javax.swing.text.JTextComponent#paste()
257 */
258 private static class PasteTextAction extends MenuAction {
259
260 public PasteTextAction(final Console console) {
261 super(console, "Paste");
262 }
263
264 @Override public boolean validConsoleState() {
265 if (console == null || console.getTextPane() == null) {
266 return false;
267 }
268
269 Clipboard clippy =
270 Toolkit.getDefaultToolkit().getSystemClipboard();
271 Transferable contents = clippy.getContents(null);
272 if (contents != null) {
273 if (contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
274 return true;
275 }
276 }
277 return false;
278 }
279
280 @Override public void doAction() {
281 console.getTextPane().paste();
282 }
283 }
284
285 /**
286 * Clears out the console's {@code JTextPane}, though a fresh jython
287 * prompt is shown afterwards.
288 */
289 private static class ClearBufferAction extends MenuAction {
290
291 public ClearBufferAction(final Console console) {
292 super(console, "Clear Buffer");
293 }
294
295 @Override public boolean validConsoleState() {
296 if (console == null || console.getTextPane() == null) {
297 return false;
298 }
299 return true;
300 }
301
302 @Override public void doAction() {
303 console.getTextPane().selectAll();
304 console.getTextPane().replaceSelection("");
305 console.prompt();
306 }
307 }
308
309 /**
310 * Selects everything contained in the console's {@code JTextPane}.
311 *
312 * @see javax.swing.text.JTextComponent#selectAll()
313 */
314 private static class SelectBufferAction extends MenuAction {
315
316 public SelectBufferAction(final Console console) {
317 super(console, "Select All");
318 }
319
320 @Override public boolean validConsoleState() {
321 if (console == null || console.getTextPane() == null) {
322 return false;
323 }
324 return true;
325 }
326
327 @Override public void doAction() {
328 console.getTextPane().selectAll();
329 }
330 }
331 }