* Bug #13343 fixed - In console popup menu, 'help about a selected text' moved from...
[scilab.git] / scilab / modules / gui / src / java / org / scilab / modules / gui / bridge / console / SwingScilabConsole.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2007-2008 - INRIA - Vincent Couvert
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
10  *
11  */
12
13 package org.scilab.modules.gui.bridge.console;
14
15 import java.awt.Component;
16 import java.awt.Dimension;
17 import java.awt.Toolkit;
18 import java.awt.datatransfer.Clipboard;
19 import java.awt.datatransfer.DataFlavor;
20 import java.awt.datatransfer.StringSelection;
21 import java.awt.datatransfer.UnsupportedFlavorException;
22 import java.awt.event.ActionEvent;
23 import java.awt.event.FocusEvent;
24 import java.awt.event.FocusListener;
25 import java.awt.KeyboardFocusManager;
26 import java.beans.PropertyChangeEvent;
27 import java.beans.PropertyChangeListener;
28 import java.io.IOException;
29 import java.util.StringTokenizer;
30
31 import javax.swing.AbstractAction;
32 import javax.swing.Action;
33 import javax.swing.JCheckBox;
34 import javax.swing.JEditorPane;
35 import javax.swing.JPanel;
36 import javax.swing.JTextPane;
37 import javax.swing.SwingUtilities;
38 import javax.swing.text.BadLocationException;
39 import javax.swing.text.StyleContext;
40 import javax.swing.text.StyledDocument;
41
42 import org.scilab.modules.action_binding.InterpreterManagement;
43 import org.scilab.modules.console.OneCharKeyEventListener;
44 import org.scilab.modules.console.SciConsole;
45 import org.scilab.modules.console.SciHistoryManager;
46 import org.scilab.modules.console.SciInputCommandView;
47 import org.scilab.modules.console.SciOutputView;
48 import org.scilab.modules.graphic_objects.graphicObject.CallBack;
49 import org.scilab.modules.gui.bridge.contextmenu.SwingScilabContextMenu;
50 import org.scilab.modules.gui.bridge.menuitem.SwingScilabMenuItem;
51 import org.scilab.modules.gui.console.SimpleConsole;
52 import org.scilab.modules.gui.events.callback.ScilabCallBack;
53 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
54 import org.scilab.modules.gui.messagebox.ScilabModalDialog.AnswerOption;
55 import org.scilab.modules.gui.messagebox.ScilabModalDialog.ButtonType;
56 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
57 import org.scilab.modules.gui.utils.ConfigManager;
58 import org.scilab.modules.gui.utils.Position;
59 import org.scilab.modules.gui.utils.Size;
60 import org.scilab.modules.history_manager.HistoryManagement;
61 import org.scilab.modules.localization.Messages;
62 import org.scilab.modules.commons.xml.XConfiguration;
63
64 import static org.scilab.modules.commons.xml.XConfiguration.XConfAttribute;
65
66 import com.artenum.rosetta.interfaces.ui.InputCommandView;
67 import com.artenum.rosetta.util.StringConstants;
68
69 /**
70  * Swing implementation for Scilab Console in GUIs
71  * @author Vincent COUVERT
72  */
73 @SuppressWarnings(value = { "serial" })
74 public class SwingScilabConsole extends SciConsole implements SimpleConsole {
75
76     private static final long serialVersionUID = 1L;
77
78     private static final String CONFIRMATION_PATH = "//general/confirmation-dialogs/body/tools/tool[@id='console-clear']";
79     private static final String CLEAR = Messages.gettext("Clear Console");
80     private static final String CLEAR_CONFIRM = Messages.gettext("Are you sure you want to clear the console ?");
81     private static final String DONT_SHOW = Messages.gettext("Do not show this message again");
82
83     /**
84      * Constructor
85      */
86     public SwingScilabConsole() {
87         super(ConfigManager.getUserConfigFile());
88
89         SwingScilabContextMenu menu = new SwingScilabContextMenu();
90
91         SwingScilabMenuItem cutMenu = new SwingScilabMenuItem();
92         cutMenu.setText(Messages.gettext("Cut"));
93         cutMenu.setCallback(ScilabCallBack.createCallback(
94                                 "org.scilab.modules.gui.bridge.CallScilabBridge.cutConsoleSelection",
95                                 CallBack.JAVA));
96         cutMenu.setMnemonic('U');
97
98         SwingScilabMenuItem copyMenu = new SwingScilabMenuItem();
99         copyMenu.setText(Messages.gettext("Copy"));
100         copyMenu.setCallback(ScilabCallBack.createCallback(
101                                  "org.scilab.modules.gui.bridge.CallScilabBridge.copyConsoleSelection",
102                                  CallBack.JAVA));
103         copyMenu.setMnemonic('C');
104
105         SwingScilabMenuItem pasteMenu = new SwingScilabMenuItem();
106         pasteMenu.setText(Messages.gettext("Paste"));
107         pasteMenu.setCallback(ScilabCallBack.createCallback(
108                                   "org.scilab.modules.gui.bridge.CallScilabBridge.pasteClipboardIntoConsole",
109                                   CallBack.JAVA));
110         pasteMenu.setMnemonic('P');
111
112         SwingScilabMenuItem clearHistoryMenu = new SwingScilabMenuItem();
113         clearHistoryMenu.setText(Messages.gettext("Clear History"));
114         clearHistoryMenu.setCallback(ScilabCallBack.createCallback(
115                                          "org.scilab.modules.gui.bridge.CallScilabBridge.clearHistory",
116                                          CallBack.JAVA));
117         clearHistoryMenu.setMnemonic('H');
118
119         SwingScilabMenuItem clearMenu = new SwingScilabMenuItem();
120         clearMenu.setText(Messages.gettext("Clear Console"));
121         clearMenu.setCallback(ScilabCallBack.createCallback(
122                                   "org.scilab.modules.gui.bridge.CallScilabBridge.clear",
123                                   CallBack.JAVA));
124         clearMenu.setMnemonic('O');
125
126         SwingScilabMenuItem selectMenu = new SwingScilabMenuItem();
127         selectMenu.setText(Messages.gettext("Select All"));
128         selectMenu.setCallback(ScilabCallBack.createCallback(
129                                    "org.scilab.modules.gui.bridge.CallScilabBridge.selectAllConsoleContents",
130                                    CallBack.JAVA));
131         selectMenu.setMnemonic('S');
132
133
134         final SwingScilabMenuItem helpMenu = new SwingScilabMenuItem();
135         helpMenu.setText(Messages.gettext("Help on a selected keyword"));
136         helpMenu.setCallback(ScilabCallBack.createCallback(
137                                  "org.scilab.modules.gui.bridge.CallScilabBridge.helpOnTheKeyword",
138                                  CallBack.JAVA));
139         helpMenu.setMnemonic('M');
140         PropertyChangeListener listener = new PropertyChangeListener() {
141             public void propertyChange(PropertyChangeEvent arg0) {
142                 String keyword = getSelectedText();
143                 if (keyword == null || keyword.length() == 0) {
144                     helpMenu.setText(Messages.gettext("Help about a selected text"));
145                 } else {
146                     int nbOfDisplayedOnlyXChar = 10;
147                     if (keyword.length() > nbOfDisplayedOnlyXChar) {
148                         keyword = keyword.substring(0, nbOfDisplayedOnlyXChar) + "...";
149                     }
150                     helpMenu.setText(Messages.gettext("Help about '") + keyword + "'");
151                 }
152             }
153         };
154         helpMenu.addPropertyChangeListener(listener);
155
156         final SwingScilabMenuItem evalWithEchoMenu = new SwingScilabMenuItem();
157         evalWithEchoMenu.setText(Messages.gettext("Evaluate selection with echo"));
158         evalWithEchoMenu.setCallback(ScilabCallBack.createCallback("org.scilab.modules.gui.bridge.CallScilabBridge.evaluateSelectionWithEcho",
159                                      CallBack.JAVA));
160         evalWithEchoMenu.setMnemonic('E');
161         listener = new PropertyChangeListener() {
162             public void propertyChange(PropertyChangeEvent arg0) {
163                 String str = getSelectedText();
164                 evalWithEchoMenu.setEnabled(str != null && !str.isEmpty());
165             }
166         };
167         evalWithEchoMenu.addPropertyChangeListener(listener);
168
169         final SwingScilabMenuItem evalWithNoEchoMenu = new SwingScilabMenuItem();
170         evalWithNoEchoMenu.setText(Messages.gettext("Evaluate selection with no echo"));
171         evalWithNoEchoMenu.setCallback(ScilabCallBack.createCallback("org.scilab.modules.gui.bridge.CallScilabBridge.evaluateSelectionWithNoEcho",
172                                        CallBack.JAVA));
173         evalWithNoEchoMenu.setMnemonic('N');
174         listener = new PropertyChangeListener() {
175             public void propertyChange(PropertyChangeEvent arg0) {
176                 String str = getSelectedText();
177                 evalWithNoEchoMenu.setEnabled(str != null && !str.isEmpty());
178             }
179         };
180         evalWithEchoMenu.addPropertyChangeListener(listener);
181
182         menu.add(helpMenu);
183
184         menu.addSeparator();
185
186         menu.add(cutMenu);
187         menu.add(copyMenu);
188         menu.add(pasteMenu);
189
190         menu.addSeparator();
191
192         menu.add(clearHistoryMenu);
193         menu.add(clearMenu);
194
195         menu.addSeparator();
196
197         menu.add(selectMenu);
198         menu.addSeparator();
199
200         menu.add(evalWithEchoMenu);
201         menu.add(evalWithNoEchoMenu);
202
203         ((JEditorPane) getConfiguration().getOutputView()).setComponentPopupMenu(menu);
204         ((JTextPane) getConfiguration().getInputCommandView()).setComponentPopupMenu(menu);
205         ((JPanel) getConfiguration().getPromptView()).setComponentPopupMenu(menu);
206
207         ((JTextPane) getConfiguration().getInputCommandView()).requestFocus();
208
209         addFocusListener(new FocusListener() {
210             public void focusGained(FocusEvent e) {
211                 ((JTextPane) getConfiguration().getInputCommandView()).requestFocus();
212             }
213
214             public void focusLost(FocusEvent e) { }
215         });
216     }
217
218     /**
219      * Displays data in the console
220      * @param dataToDisplay the data to be displayed
221      * @see fr.scilab.console.HelpBrowser#display(java.lang.String)
222      */
223     public void display(String dataToDisplay) {
224         this.getConfiguration().getOutputView().append(dataToDisplay);
225     }
226
227     /**
228      * This method is used to display the prompt
229      */
230     public void displayPrompt() {
231         SwingUtilities.invokeLater(new Runnable() {
232             public void run() {
233                 InputCommandView inputCmdView = SwingScilabConsole.this.getConfiguration().getInputCommandView();
234
235                 // Show the prompt
236                 SwingScilabConsole.this.getConfiguration().getPromptView().setVisible(true);
237
238                 // Show the input command view and its hidden components
239                 inputCmdView.setEditable(true);
240                 JTextPane tp = (JTextPane) inputCmdView;
241                 if (tp.isFocusOwner()) {
242                     tp.getCaret().setVisible(true);
243                 }
244                 setToHome();
245             }
246         });
247
248         ((SciOutputView) this.getConfiguration().getOutputView()).resetLastEOL();
249
250         updateScrollPosition();
251     }
252
253     /**
254      * Unblock the console if needed
255      */
256     public void unblock() {
257         if (getCanReadUserInputValue().availablePermits() == 0) {
258             setUserInputValue((int) 'n');
259         }
260
261         // interrupt any mscanf call (input, halt and so on)
262         ((SciInputCommandView) this.getConfiguration().getInputCommandView()).interrupt();
263     }
264
265     /**
266      * Reads one user input char
267      * @return the data entered by the user
268      * @see fr.scilab.console.HelpBrowser#getCharWithoutOutput()
269      */
270     public int getCharWithoutOutput() {
271         int retChar;
272
273         updateScrollPosition();
274
275         // Avoids reading of an empty buffer
276         try {
277             ((SciConsole) this).getCanReadUserInputValue().acquire();
278         } catch (InterruptedException e) {
279             e.printStackTrace();
280         }
281
282         this.getConfiguration().getPromptView().setVisible(false);
283         this.getConfiguration().getInputCommandView().setEditable(false);
284
285         // Add a keylistener which will set the returned char
286         OneCharKeyEventListener keyListener = new OneCharKeyEventListener(this);
287         ((JTextPane) this.getConfiguration().getInputCommandView()).addKeyListener(keyListener);
288         ((JEditorPane) this.getConfiguration().getOutputView()).addKeyListener(keyListener);
289
290         // Reads the buffer
291         retChar = this.getUserInputValue();
292         ((SciConsole) this).getCanReadUserInputValue().release();
293
294         // Remove the "more" message and replace it by an empty line
295         this.clear(-1);
296         this.display(StringConstants.NEW_LINE);
297
298         // Remove the key listener
299         ((JTextPane) this.getConfiguration().getInputCommandView()).removeKeyListener(keyListener);
300         ((JEditorPane) this.getConfiguration().getOutputView()).removeKeyListener(keyListener);
301
302         this.getConfiguration().getPromptView().setVisible(true);
303         this.getConfiguration().getInputCommandView().setEditable(true);
304
305         // Send back the focus of the input view
306         this.getConfiguration().getInputCommandView().reset();
307         this.getConfiguration().getInputCommandView().requestFocus();
308
309         final JTextPane cmdView = (JTextPane) this.getConfiguration().getInputCommandView();
310         SwingUtilities.invokeLater(new Runnable() {
311             public void run() {
312                 cmdView.getCaret().setVisible(true);
313             }
314         });
315
316         return retChar;
317     }
318
319     /**
320      * Draw a console
321      */
322     public void draw() {
323         super.setVisible(true);
324         super.doLayout();
325     }
326
327     /**
328      * Gets the dimensions (width and height) of a Scilab console
329      * @return the size of the console
330      */
331     public Size getDims() {
332         return new Size(super.getWidth(), super.getHeight());
333     }
334
335     /**
336      * Gets the position (X-coordinate and Y-coordinates) of a Scilab console
337      * @return the position of the console
338      */
339     public Position getPosition() {
340         return new Position(this.getX(), this.getY());
341     }
342
343     /**
344      * Gets the visibility status of a console
345      * @return the visibility status of the console (true if the console is visible, false if not)
346      */
347     public boolean isVisible() {
348         return super.isVisible();
349     }
350
351     /**
352      * Sets the dimensions (width and height) of a Scilab console
353      * @param newSize the size we want to set to the console
354      */
355     public void setDims(Size newSize) {
356         this.setPreferredSize(new Dimension(newSize.getWidth(), newSize.getHeight()));
357     }
358
359     /**
360      * Sets the position (X-coordinate and Y-coordinate) of a Scilab console
361      * @param newPosition the position we want to set to the console
362      */
363     public void setPosition(Position newPosition) {
364         this.setLocation(newPosition.getX(), newPosition.getY());
365     }
366
367     /**
368      * Sets the visibility status of a Scilab console
369      * @param newVisibleState the visibility status we want to set to the console
370      */
371     public void setVisible(boolean newVisibleState) {
372         super.setVisible(newVisibleState);
373     }
374
375     /**
376      * Clears the Console
377      */
378     public void clear() {
379         CheckClearConfirmation ccc = XConfiguration.get(CheckClearConfirmation.class, XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH)[0];
380         if (ccc.checked) {
381             final Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
382             final boolean[] checked = new boolean[1];
383             final Action action = new AbstractAction() {
384                 public void actionPerformed(ActionEvent e) {
385                     checked[0] = ((JCheckBox) e.getSource()).isSelected();
386                 }
387             };
388
389             boolean isNo = ScilabModalDialog.show(this, new String[] { CLEAR_CONFIRM }, CLEAR, IconType.WARNING_ICON, ButtonType.YES_NO, DONT_SHOW, action) == AnswerOption.NO_OPTION;
390             if (focused != null) {
391                 SwingUtilities.invokeLater(new Runnable() {
392                     public void run() {
393                         focused.requestFocus();
394                     }
395                 });
396             }
397
398             if (isNo) {
399                 if (checked[0]) {
400                     XConfiguration.set(XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH + "/@state", "unchecked");
401                 }
402                 return;
403             }
404
405             if (checked[0]) {
406                 XConfiguration.set(XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH + "/@state", "unchecked");
407             }
408         }
409
410         super.clear();
411     }
412
413     /**
414      * Sets the prompt displayed in the console
415      * @param prompt the prompt to be displayed in the console
416      */
417     public void setPrompt(String prompt) {
418         this.getConfiguration().getPromptView().setDefaultPrompt(prompt);
419     }
420
421     /**
422      * Clear the commands history
423      */
424     public void clearHistory() {
425         ((SciHistoryManager) this.getConfiguration().getHistoryManager()).reset();
426     }
427
428     /**
429      * Paste clipboard contents in Console input line
430      */
431     public void pasteClipboard() {
432         // Gets the contents of the clipboard
433         Toolkit toolkit = Toolkit.getDefaultToolkit();
434         Clipboard systemClipboard = toolkit.getSystemClipboard();
435
436         // Verify that clibpboard data is of text type
437         boolean dataAvailable;
438         try {
439             dataAvailable = systemClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor);
440         } catch (IllegalStateException exception) {
441             return;
442         }
443
444         // Exit if text data not available
445         if (!dataAvailable) {
446             return;
447         }
448
449         // Read data
450         String clipboardContents = null;
451         try {
452             clipboardContents = (String) systemClipboard.getData(DataFlavor.stringFlavor);
453         } catch (UnsupportedFlavorException e1) {
454             // Should never be here
455             e1.printStackTrace();
456         } catch (IOException e1) {
457             // Should never be here
458             e1.printStackTrace();
459         }
460
461         JTextPane input = ((JTextPane) this.getConfiguration().getInputCommandView());
462         StyledDocument doc = input.getStyledDocument();
463
464         // If some text selected then it is replaced
465         if (input.getSelectedText() != null) {
466             try {
467                 doc.remove(input.getSelectionStart(), input.getSelectionEnd() - input.getSelectionStart());
468             } catch (BadLocationException e) {
469                 e.printStackTrace();
470             }
471         }
472         // Insert clipboard contents
473         try {
474             doc.insertString(((JTextPane) this.getConfiguration().getInputCommandView()).getCaretPosition(),
475                              clipboardContents, doc.getStyle(StyleContext.DEFAULT_STYLE));
476         } catch (BadLocationException e) {
477             e.printStackTrace();
478         }
479     }
480
481     /**
482      * Select all the console contents
483      */
484     public void selectAll() {
485         JEditorPane output = (JEditorPane) this.getConfiguration().getOutputView();
486         output.setSelectionStart(0);
487         output.setSelectionEnd(output.getText().length());
488         // TODO should also select the prompt and the input
489     }
490
491     /**
492      * Return the selected text in the console
493      * @return The selected text in the console
494      */
495     private String getSelectedText() {
496         JEditorPane output = (JEditorPane) this.getConfiguration().getOutputView();
497         JTextPane input = (JTextPane) this.getConfiguration().getInputCommandView();
498
499         String selection = "";
500         if (output.getSelectedText() != null) {
501             selection += output.getSelectedText();
502         }
503         // TODO should also copy the prompt
504         if (input.getSelectedText() != null) {
505             selection += input.getSelectedText();
506         }
507         return selection;
508
509     }
510
511     /**
512      * Launch the help on the selected text into the Console
513      */
514     public void helpOnTheKeyword() {
515         String keyword = getSelectedText();
516         /* Double the quote/double quote in order to avoid
517          * and error with the call of help()
518          */
519         keyword = keyword.replaceAll("'", "''");
520         keyword = keyword.replaceAll("\"", "\"\"");
521
522         /* @TODO: Check if it is possible to call directly
523          * from the Java engine the help
524          * Last time I check, we needed some information
525          * provided by the Scilab native engine
526          */
527         InterpreterManagement.requestScilabExec("help('" + keyword + "')");
528     }
529
530     /**
531      * Evaluate the selection with echo
532      */
533     public void evaluateSelectionWithEcho() {
534         String selection = getSelectedText();
535         if (selection.compareTo("") != 0) {
536             StringTokenizer tokens = new StringTokenizer(selection, "\n");
537             String[] lines = new String[tokens.countTokens()];
538             int i = 0;
539             while (tokens.hasMoreTokens()) {
540                 lines[i++] = tokens.nextToken();
541             }
542             HistoryManagement.appendLinesToScilabHistory(lines, lines.length);
543             sendCommandsToScilab(selection, true, false);
544         }
545     }
546
547     /**
548      * Evaluate the selection with no echo
549      */
550     public void evaluateSelectionWithNoEcho() {
551         sendCommandsToScilab(getSelectedText(), false, false);
552     }
553
554     /**
555      * Put the console selected text in the clipboard
556      */
557     public void copyToClipboard() {
558         String selection = getSelectedText();
559         Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(selection), null);
560     }
561
562     /**
563      * Cut selected text in the Console input line
564      */
565     public void cutSelection() {
566         JTextPane input = (JTextPane) this.getConfiguration().getInputCommandView();
567         StyledDocument doc = input.getStyledDocument();
568
569         // If some text selected then it is replaced
570         if (input.getSelectedText() != null) {
571             try {
572                 /* Put the selection in the clipboard */
573                 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(input.getSelectedText()), null);
574                 /* Remove selected text */
575                 doc.remove(input.getSelectionStart(), input.getSelectionEnd() - input.getSelectionStart());
576             } catch (BadLocationException e) {
577                 e.printStackTrace();
578             }
579         }
580     }
581
582     /**
583      * Set the maximum number of lines stored in the Output
584      * @param nbLines the number of lines
585      */
586     public void setMaxOutputSize(int nbLines) {
587         ((SciOutputView) this.getConfiguration().getOutputView()).setMaxSize(nbLines);
588     }
589
590     @XConfAttribute
591     private static class CheckClearConfirmation {
592
593         public boolean checked;
594
595         private CheckClearConfirmation() { }
596
597         @XConfAttribute(attributes = {"state"})
598         private void set(String checked) {
599             this.checked = checked.equals("checked");
600         }
601     }
602 }