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    }