b9f57eafb4cf073e7fb6b61067fda955caf3b782
[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(cutMenu);
183         menu.add(copyMenu);
184         menu.add(pasteMenu);
185
186         menu.addSeparator();
187
188         menu.add(clearHistoryMenu);
189         menu.add(clearMenu);
190
191         menu.addSeparator();
192
193         menu.add(selectMenu);
194         menu.addSeparator();
195
196         menu.add(evalWithEchoMenu);
197         menu.add(evalWithNoEchoMenu);
198         menu.addSeparator();
199
200         menu.add(helpMenu);
201
202         ((JEditorPane) getConfiguration().getOutputView()).setComponentPopupMenu(menu);
203         ((JTextPane) getConfiguration().getInputCommandView()).setComponentPopupMenu(menu);
204         ((JPanel) getConfiguration().getPromptView()).setComponentPopupMenu(menu);
205
206         ((JTextPane) getConfiguration().getInputCommandView()).requestFocus();
207
208         addFocusListener(new FocusListener() {
209             public void focusGained(FocusEvent e) {
210                 ((JTextPane) getConfiguration().getInputCommandView()).requestFocus();
211             }
212
213             public void focusLost(FocusEvent e) { }
214         });
215     }
216
217     /**
218      * Displays data in the console
219      * @param dataToDisplay the data to be displayed
220      * @see fr.scilab.console.HelpBrowser#display(java.lang.String)
221      */
222     public void display(String dataToDisplay) {
223         this.getConfiguration().getOutputView().append(dataToDisplay);
224     }
225
226     /**
227      * This method is used to display the prompt
228      */
229     public void displayPrompt() {
230         SwingUtilities.invokeLater(new Runnable() {
231             public void run() {
232                 InputCommandView inputCmdView = SwingScilabConsole.this.getConfiguration().getInputCommandView();
233
234                 // Show the prompt
235                 SwingScilabConsole.this.getConfiguration().getPromptView().setVisible(true);
236
237                 // Show the input command view and its hidden components
238                 inputCmdView.setEditable(true);
239                 JTextPane tp = (JTextPane) inputCmdView;
240                 if (tp.isFocusOwner()) {
241                     tp.getCaret().setVisible(true);
242                 }
243                 setToHome();
244             }
245         });
246
247         ((SciOutputView) this.getConfiguration().getOutputView()).resetLastEOL();
248
249         updateScrollPosition();
250     }
251
252     /**
253      * Unblock the console if needed
254      */
255     public void unblock() {
256         if (getCanReadUserInputValue().availablePermits() == 0) {
257             setUserInputValue((int) 'n');
258         }
259
260         // interrupt any mscanf call (input, halt and so on)
261         ((SciInputCommandView) this.getConfiguration().getInputCommandView()).interrupt();
262     }
263
264     /**
265      * Reads one user input char
266      * @return the data entered by the user
267      * @see fr.scilab.console.HelpBrowser#getCharWithoutOutput()
268      */
269     public int getCharWithoutOutput() {
270         int retChar;
271
272         updateScrollPosition();
273
274         // Avoids reading of an empty buffer
275         try {
276             ((SciConsole) this).getCanReadUserInputValue().acquire();
277         } catch (InterruptedException e) {
278             e.printStackTrace();
279         }
280
281         this.getConfiguration().getPromptView().setVisible(false);
282         this.getConfiguration().getInputCommandView().setEditable(false);
283
284         // Add a keylistener which will set the returned char
285         OneCharKeyEventListener keyListener = new OneCharKeyEventListener(this);
286         ((JTextPane) this.getConfiguration().getInputCommandView()).addKeyListener(keyListener);
287         ((JEditorPane) this.getConfiguration().getOutputView()).addKeyListener(keyListener);
288
289         // Reads the buffer
290         retChar = this.getUserInputValue();
291         ((SciConsole) this).getCanReadUserInputValue().release();
292
293         // Remove the "more" message and replace it by an empty line
294         this.clear(-1);
295         this.display(StringConstants.NEW_LINE);
296
297         // Remove the key listener
298         ((JTextPane) this.getConfiguration().getInputCommandView()).removeKeyListener(keyListener);
299         ((JEditorPane) this.getConfiguration().getOutputView()).removeKeyListener(keyListener);
300
301         this.getConfiguration().getPromptView().setVisible(true);
302         this.getConfiguration().getInputCommandView().setEditable(true);
303
304         // Send back the focus of the input view
305         this.getConfiguration().getInputCommandView().reset();
306         this.getConfiguration().getInputCommandView().requestFocus();
307
308         final JTextPane cmdView = (JTextPane) this.getConfiguration().getInputCommandView();
309         SwingUtilities.invokeLater(new Runnable() {
310             public void run() {
311                 cmdView.getCaret().setVisible(true);
312             }
313         });
314
315         return retChar;
316     }
317
318     /**
319      * Draw a console
320      */
321     public void draw() {
322         super.setVisible(true);
323         super.doLayout();
324     }
325
326     /**
327      * Gets the dimensions (width and height) of a Scilab console
328      * @return the size of the console
329      */
330     public Size getDims() {
331         return new Size(super.getWidth(), super.getHeight());
332     }
333
334     /**
335      * Gets the position (X-coordinate and Y-coordinates) of a Scilab console
336      * @return the position of the console
337      */
338     public Position getPosition() {
339         return new Position(this.getX(), this.getY());
340     }
341
342     /**
343      * Gets the visibility status of a console
344      * @return the visibility status of the console (true if the console is visible, false if not)
345      */
346     public boolean isVisible() {
347         return super.isVisible();
348     }
349
350     /**
351      * Sets the dimensions (width and height) of a Scilab console
352      * @param newSize the size we want to set to the console
353      */
354     public void setDims(Size newSize) {
355         this.setPreferredSize(new Dimension(newSize.getWidth(), newSize.getHeight()));
356     }
357
358     /**
359      * Sets the position (X-coordinate and Y-coordinate) of a Scilab console
360      * @param newPosition the position we want to set to the console
361      */
362     public void setPosition(Position newPosition) {
363         this.setLocation(newPosition.getX(), newPosition.getY());
364     }
365
366     /**
367      * Sets the visibility status of a Scilab console
368      * @param newVisibleState the visibility status we want to set to the console
369      */
370     public void setVisible(boolean newVisibleState) {
371         super.setVisible(newVisibleState);
372     }
373
374     /**
375      * Clears the Console
376      */
377     public void clear() {
378         CheckClearConfirmation ccc = XConfiguration.get(CheckClearConfirmation.class, XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH)[0];
379         if (ccc.checked) {
380             final Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
381             final boolean[] checked = new boolean[1];
382             final Action action = new AbstractAction() {
383                 public void actionPerformed(ActionEvent e) {
384                     checked[0] = ((JCheckBox) e.getSource()).isSelected();
385                 }
386             };
387
388             boolean isNo = ScilabModalDialog.show(this, new String[] { CLEAR_CONFIRM }, CLEAR, IconType.WARNING_ICON, ButtonType.YES_NO, DONT_SHOW, action) == AnswerOption.NO_OPTION;
389             if (focused != null) {
390                 SwingUtilities.invokeLater(new Runnable() {
391                     public void run() {
392                         focused.requestFocus();
393                     }
394                 });
395             }
396
397             if (isNo) {
398                 if (checked[0]) {
399                     XConfiguration.set(XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH + "/@state", "unchecked");
400                 }
401                 return;
402             }
403
404             if (checked[0]) {
405                 XConfiguration.set(XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH + "/@state", "unchecked");
406             }
407         }
408
409         super.clear();
410     }
411
412     /**
413      * Sets the prompt displayed in the console
414      * @param prompt the prompt to be displayed in the console
415      */
416     public void setPrompt(String prompt) {
417         this.getConfiguration().getPromptView().setDefaultPrompt(prompt);
418     }
419
420     /**
421      * Clear the commands history
422      */
423     public void clearHistory() {
424         ((SciHistoryManager) this.getConfiguration().getHistoryManager()).reset();
425     }
426
427     /**
428      * Paste clipboard contents in Console input line
429      */
430     public void pasteClipboard() {
431         // Gets the contents of the clipboard
432         Toolkit toolkit = Toolkit.getDefaultToolkit();
433         Clipboard systemClipboard = toolkit.getSystemClipboard();
434
435         // Verify that clibpboard data is of text type
436         boolean dataAvailable;
437         try {
438             dataAvailable = systemClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor);
439         } catch (IllegalStateException exception) {
440             return;
441         }
442
443         // Exit if text data not available
444         if (!dataAvailable) {
445             return;
446         }
447
448         // Read data
449         String clipboardContents = null;
450         try {
451             clipboardContents = (String) systemClipboard.getData(DataFlavor.stringFlavor);
452         } catch (UnsupportedFlavorException e1) {
453             // Should never be here
454             e1.printStackTrace();
455         } catch (IOException e1) {
456             // Should never be here
457             e1.printStackTrace();
458         }
459
460         JTextPane input = ((JTextPane) this.getConfiguration().getInputCommandView());
461         StyledDocument doc = input.getStyledDocument();
462
463         // If some text selected then it is replaced
464         if (input.getSelectedText() != null) {
465             try {
466                 doc.remove(input.getSelectionStart(), input.getSelectionEnd() - input.getSelectionStart());
467             } catch (BadLocationException e) {
468                 e.printStackTrace();
469             }
470         }
471         // Insert clipboard contents
472         try {
473             doc.insertString(((JTextPane) this.getConfiguration().getInputCommandView()).getCaretPosition(),
474                              clipboardContents, doc.getStyle(StyleContext.DEFAULT_STYLE));
475         } catch (BadLocationException e) {
476             e.printStackTrace();
477         }
478     }
479
480     /**
481      * Select all the console contents
482      */
483     public void selectAll() {
484         JEditorPane output = (JEditorPane) this.getConfiguration().getOutputView();
485         output.setSelectionStart(0);
486         output.setSelectionEnd(output.getText().length());
487         // TODO should also select the prompt and the input
488     }
489
490     /**
491      * Return the selected text in the console
492      * @return The selected text in the console
493      */
494     private String getSelectedText() {
495         JEditorPane output = (JEditorPane) this.getConfiguration().getOutputView();
496         JTextPane input = (JTextPane) this.getConfiguration().getInputCommandView();
497
498         String selection = "";
499         if (output.getSelectedText() != null) {
500             selection += output.getSelectedText();
501         }
502         // TODO should also copy the prompt
503         if (input.getSelectedText() != null) {
504             selection += input.getSelectedText();
505         }
506         return selection;
507
508     }
509
510     /**
511      * Launch the help on the selected text into the Console
512      */
513     public void helpOnTheKeyword() {
514         String keyword = getSelectedText();
515         /* Double the quote/double quote in order to avoid
516          * and error with the call of help()
517          */
518         keyword = keyword.replaceAll("'", "''");
519         keyword = keyword.replaceAll("\"", "\"\"");
520
521         /* @TODO: Check if it is possible to call directly
522          * from the Java engine the help
523          * Last time I check, we needed some information
524          * provided by the Scilab native engine
525          */
526         InterpreterManagement.requestScilabExec("help('" + keyword + "')");
527     }
528
529     /**
530      * Evaluate the selection with echo
531      */
532     public void evaluateSelectionWithEcho() {
533         String selection = getSelectedText();
534         if (selection.compareTo("") != 0) {
535             StringTokenizer tokens = new StringTokenizer(selection, "\n");
536             String[] lines = new String[tokens.countTokens()];
537             int i = 0;
538             while (tokens.hasMoreTokens()) {
539                 lines[i++] = tokens.nextToken();
540             }
541             HistoryManagement.appendLinesToScilabHistory(lines, lines.length);
542             sendCommandsToScilab(selection, true, false);
543         }
544     }
545
546     /**
547      * Evaluate the selection with no echo
548      */
549     public void evaluateSelectionWithNoEcho() {
550         sendCommandsToScilab(getSelectedText(), false, false);
551     }
552
553     /**
554      * Put the console selected text in the clipboard
555      */
556     public void copyToClipboard() {
557         String selection = getSelectedText();
558         Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(selection), null);
559     }
560
561     /**
562      * Cut selected text in the Console input line
563      */
564     public void cutSelection() {
565         JTextPane input = (JTextPane) this.getConfiguration().getInputCommandView();
566         StyledDocument doc = input.getStyledDocument();
567
568         // If some text selected then it is replaced
569         if (input.getSelectedText() != null) {
570             try {
571                 /* Put the selection in the clipboard */
572                 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(input.getSelectedText()), null);
573                 /* Remove selected text */
574                 doc.remove(input.getSelectionStart(), input.getSelectionEnd() - input.getSelectionStart());
575             } catch (BadLocationException e) {
576                 e.printStackTrace();
577             }
578         }
579     }
580
581     /**
582      * Set the maximum number of lines stored in the Output
583      * @param nbLines the number of lines
584      */
585     public void setMaxOutputSize(int nbLines) {
586         ((SciOutputView) this.getConfiguration().getOutputView()).setMaxSize(nbLines);
587     }
588
589     @XConfAttribute
590     private static class CheckClearConfirmation {
591
592         public boolean checked;
593
594         private CheckClearConfirmation() { }
595
596         @XConfAttribute(attributes = {"state"})
597         private void set(String checked) {
598             this.checked = checked.equals("checked");
599         }
600     }
601 }