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 }