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    }