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 }