001/*
002 * $Id: DefaultMenuWrangler.java,v 1.10 2011/03/24 16:06:33 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
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
031package edu.wisc.ssec.mcidasv.jython;
032
033import static edu.wisc.ssec.mcidasv.util.Contract.notNull;
034
035import java.awt.Toolkit;
036import java.awt.datatransfer.Clipboard;
037import java.awt.datatransfer.DataFlavor;
038import java.awt.datatransfer.Transferable;
039import java.awt.event.ActionEvent;
040import java.awt.event.ActionListener;
041import java.util.Map;
042
043import javax.swing.JMenu;
044import javax.swing.JMenuItem;
045import javax.swing.JPopupMenu;
046import javax.swing.border.BevelBorder;
047
048import org.python.core.PyObject;
049
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053// TODO(jon): this will need to be reconsidered, but it's fine for the current
054// console.
055public 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}