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