84ec8275a55ddcc0732eac1864849cda7ead8980
[scilab.git] / scilab / modules / console / src / java / org / scilab / modules / console / SciConsole.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-en.txt
10  *
11  */
12
13 package org.scilab.modules.console;
14
15 import java.awt.BorderLayout;
16 import java.awt.Color;
17 import java.awt.Cursor;
18 import java.awt.Dimension;
19 import java.awt.Font;
20 import java.awt.Point;
21 import java.awt.event.ComponentAdapter;
22 import java.awt.event.ComponentEvent;
23 import java.io.IOException;
24 import java.util.concurrent.Semaphore;
25
26 import javax.swing.BoundedRangeModel;
27 import javax.swing.DefaultBoundedRangeModel;
28 import javax.swing.JLabel;
29 import javax.swing.JPanel;
30 import javax.swing.JScrollPane;
31 import javax.swing.JEditorPane;
32 import javax.swing.JTextPane;
33 import javax.swing.JViewport;
34 import javax.swing.SwingUtilities;
35 import javax.swing.text.BadLocationException;
36 import javax.swing.text.Document;
37 import javax.swing.KeyStroke;
38 import javax.xml.parsers.ParserConfigurationException;
39 import org.scilab.modules.localization.Messages;
40
41 import org.xml.sax.SAXException;
42
43 import com.artenum.rosetta.interfaces.core.ConsoleConfiguration;
44 import com.artenum.rosetta.interfaces.core.InputParsingManager;
45 import com.artenum.rosetta.interfaces.ui.InputCommandView;
46 import com.artenum.rosetta.interfaces.ui.OutputView;
47 import com.artenum.rosetta.interfaces.ui.PromptView;
48 import com.artenum.rosetta.ui.Console;
49 import com.artenum.rosetta.util.ConfigurationBuilder;
50 import com.artenum.rosetta.util.ConsoleBuilder;
51 import com.artenum.rosetta.util.StringConstants;
52
53 /**
54  * Main class for Scilab Console based on Generic Console from Artenum
55  * @author Vincent COUVERT
56  */
57 public abstract class SciConsole extends JPanel {
58
59     private static final long serialVersionUID = 1L;
60
61     private static final int LINE_NUMBER_IN_PROMPT = 2;
62
63     private static final String BACKSLASH_R = "\r";
64
65     /**
66      * Maximum length of a command send to Scilab
67      */
68     private static final int MAX_CMD_LENGTH = 512;
69
70     /**
71      * Configuration associated to the console oject
72      */
73     private ConsoleConfiguration config;
74
75     /**
76      * Scroll Pane used in Scilab Console
77      */
78     private JScrollPane jSP;
79
80     /**
81      * Generic console object
82      */
83     private Console sciConsole;
84
85     /**
86      * Value used to get one char from user input (when using [more y or n ?])
87      */
88     private int userInputValue;
89
90     /**
91      * Protection for userInputValue variable R/W
92      */
93     private Semaphore canReadUserInputValue = new Semaphore(1);
94
95     /**
96      * Boolean flag used to store the state of Scilab (true is all works done)
97      */
98     private boolean workDone;
99
100     private boolean atBottom;
101
102     private boolean isToHome;
103
104     /**
105      * Constructor
106      * @param configFilePath the configuration file to use
107      */
108     public SciConsole(String configFilePath) {
109         super(new BorderLayout());
110
111         try {
112             config = ConfigurationBuilder.buildConfiguration(configFilePath);
113             config.setActiveProfile("scilab");
114             if (System.getProperty("os.name").toLowerCase().indexOf("mac") != -1)
115             {
116                 ConsoleConfiguration configMac = ConfigurationBuilder.buildConfiguration(configFilePath);;
117                 configMac.setActiveProfile("macosx");
118                 for (KeyStroke key : config.getKeyMapping().keys()){
119                     config.getKeyMapping().put(key,"");
120                 }
121                 for (KeyStroke key : configMac.getKeyMapping().keys()){
122                     config.getKeyMapping().put(key, configMac.getKeyMapping().get(key));
123                 }
124             }
125         } catch (IllegalArgumentException e) {
126             // TODO Auto-generated catch block
127             e.printStackTrace();
128         } catch (SAXException e) {
129             // TODO Auto-generated catch block
130             e.printStackTrace();
131         } catch (IOException e) {
132             // TODO Auto-generated catch block
133             e.printStackTrace();
134         } catch (ParserConfigurationException e) {
135             // TODO Auto-generated catch block
136             e.printStackTrace();
137         }
138
139         sciConsole = ConsoleBuilder.buildConsole(config, this);
140         jSP = new JScrollPane(sciConsole);
141
142         BoundedRangeModel model = jSP.getVerticalScrollBar().getModel();
143         jSP.getVerticalScrollBar().setModel(new DefaultBoundedRangeModel(model.getValue(), model.getExtent(), model.getMinimum(), model.getMaximum()) {
144                 public void setRangeProperties(int newValue, int newExtent, int newMin, int newMax, boolean adjusting) {
145                     // This method is overriden to keep the knob at the bottom during viewport resize
146                     // and to keep the knob at an other place if the user decided it.
147                     if (newMax != getMaximum()) {
148                         if (!adjusting) {
149                             if (atBottom) {
150                                 super.setRangeProperties(newMax - newExtent, newExtent, newMin, newMax, false);
151                             } else {
152                                 super.setRangeProperties(newValue, newExtent, newMin, newMax, false);
153                             }
154                         } else {
155                             double percent = (double) Math.abs(newMax - newValue - newExtent) / (double) newMax;
156                             if (atBottom && percent <= 0.03) {
157                                 super.setRangeProperties(newMax - newExtent, newExtent, newMin, newMax, true);
158                             } else {
159                                 super.setRangeProperties(newValue, newExtent, newMin, newMax, true);
160                                 atBottom = percent <= 0.01;
161                             }
162                         }
163                     } else {
164                         super.setRangeProperties(newValue, newExtent, newMin, newMax, adjusting);
165                     }
166                 }
167             });
168
169         this.add(jSP, BorderLayout.CENTER);
170
171         // The console is given to the outputView so that updateScrollPosition is is accessible
172         ((SciOutputView) config.getOutputView()).setConsole(this);
173
174         // The console is given to the outputView so that Drag&Drop can work
175         ((SciInputCommandView) config.getInputCommandView()).setConsole(this);
176
177         // The console is given to the CompletionWindow
178         ((SciCompletionWindow) config.getCompletionWindow()).setConsole(this);
179         ((SciCompletionWindow) config.getCompletionWindow()).setGraphicalContext(this);
180
181         // The promptview is given to the Parsing Manager
182         // Used to get the position of the CompletionWindow
183         ((SciInputParsingManager) config.getInputParsingManager()).setPromptView(this.getConfiguration().getPromptView());
184
185         // Reset history settings - bug 3612
186         ((SciHistoryManager)config.getHistoryManager()).setInHistory(false);
187
188         // Bug 8055 : update the lines/columns only when the console is resized
189         addComponentListener(new ComponentAdapter() {
190                 public void componentResized(ComponentEvent evt) {
191                     SwingUtilities.invokeLater(new Runnable() {
192                             public void run() {
193                                 scilabLinesUpdate();
194                                 jSP.getVerticalScrollBar().setBlockIncrement(jSP.getViewport().getExtentSize().height);
195                                 jSP.getHorizontalScrollBar().setBlockIncrement(jSP.getViewport().getExtentSize().width);
196                             }
197                         });
198                 }
199             });
200
201         sciConsole.invalidate();
202         sciConsole.doLayout();
203     }
204
205     /**
206      * Gets the configuration associated to the console
207      * @return the configuration
208      */
209     public ConsoleConfiguration getConfiguration() {
210         return config;
211     }
212
213     /**
214      * Sets the configuration associated to the console
215      * @param newConfig the new config to set
216      */
217     public void setConfiguration(ConsoleConfiguration newConfig) {
218         config = newConfig;
219     }
220
221     /**
222      * Updates Scilab internal variables containing the size of the console
223      * These variables are used to format data before displaying it
224      */
225     public void scilabLinesUpdate() {
226         // Size of the console
227         int outputViewWidth = jSP.getViewport().getExtentSize().width;
228
229         // Size of a char
230         OutputView outputView = this.getConfiguration().getOutputView();
231         int[] charsWidth = ((JEditorPane) outputView).getFontMetrics(((JEditorPane) outputView).getFont()).getWidths();
232
233         // This loop is not needed for monospaced fonts !
234         int maxCharWidth = charsWidth[33];
235         // The range 33--126 corresponds to the usual characters in ASCII
236         for (int i = 34; i < 126; i++) {
237             if (charsWidth[i] > maxCharWidth) {
238                 maxCharWidth = charsWidth[i];
239             }
240         }
241
242         int numberOfLines = Math.max(1, getNumberOfLines());
243         int promptWidth = ((JPanel) this.getConfiguration().getPromptView()).getPreferredSize().width;
244
245         int numberOfColumns = (outputViewWidth - promptWidth) / maxCharWidth - 1;
246         /* -1 because of the margin between text prompt and command line text */
247
248         GuiManagement.setScilabLines(numberOfLines, numberOfColumns);
249     }
250
251     /**
252      * Get the number of lines that can be displayed in the visible part of the console
253      * @return the number of lines
254      */
255     public int getNumberOfLines() {
256         // Size of the console
257         int outputViewHeight = jSP.getViewport().getExtentSize().height;
258
259         // Size of a char
260         OutputView outputView = this.getConfiguration().getOutputView();
261         int charHeight = ((JEditorPane) outputView).getFontMetrics(((JEditorPane) outputView).getFont()).getHeight();
262         int[] charsWidth = ((JEditorPane) outputView).getFontMetrics(((JEditorPane) outputView).getFont()).getWidths();
263
264         // This loop is not needed for monospaced fonts !
265         int maxCharWidth = charsWidth[0];
266         for (int i = 1; i < charsWidth.length; i++) {
267             if (charsWidth[i] > maxCharWidth) {
268                 maxCharWidth = charsWidth[i];
269             }
270         }
271
272         return outputViewHeight / charHeight - 1; /* -1 because of the size of the InputCommandLine */
273     }
274
275     /**
276      * Updates the scroll bars according to the contents
277      */
278     public void updateScrollPosition() {
279         // Update the scrollbar properties
280         jSP.getVerticalScrollBar().setBlockIncrement(jSP.getViewport().getExtentSize().height);
281         jSP.getHorizontalScrollBar().setBlockIncrement(jSP.getViewport().getExtentSize().width);
282         SwingUtilities.invokeLater(new Runnable() {
283                 public void run() {
284                     jSP.getVerticalScrollBar().getModel().setValue(jSP.getVerticalScrollBar().getModel().getMaximum() - jSP.getVerticalScrollBar().getModel().getExtent());
285                 }
286             });
287     }
288
289     /**
290      * Clears the console and the output view
291      */
292     public void clear() {
293         if (sciConsole.isPreferredSizeSet()) {
294             sciConsole.setPreferredSize(null);
295             sciConsole.invalidate();
296             sciConsole.doLayout();
297         }
298
299         try {
300             config.getInputCommandViewStyledDocument().remove(0, config.getInputCommandViewStyledDocument().getLength());
301         } catch (BadLocationException e) {
302             e.printStackTrace();
303         }
304         config.getOutputView().reset();
305         /* Bug 4014 */
306         /* We add a space to add a line */
307         /* clc , F2 and menus have same position */
308         config.getOutputView().append(" ");
309     }
310
311     /**
312      * Clears lines from the end of the output view
313      * If nbLines == -1 ==> Called from SwingScilabConsole.getCharWithoutOutput() ([more y or n ?])
314      * If nbLines == 0 ==> Clear the InputCommandLine
315      * @param nbLines the number of lines to be deleted
316      */
317     public void clear(int nbLines) {
318         if (sciConsole.isPreferredSizeSet()) {
319             sciConsole.setPreferredSize(null);
320             sciConsole.invalidate();
321             sciConsole.doLayout();
322         }
323
324         if (nbLines == 0) {
325             // Clear the prompt
326             config.getInputCommandView().reset();
327         } else {
328             // Clear lines in output command view
329             try {
330                 // We have to remove the command entered by the user
331                 int totalNumberOfLines = nbLines + LINE_NUMBER_IN_PROMPT;
332
333                 Document outputDoc = ((JEditorPane) config.getOutputView()).getDocument();
334                 String outputTxt =  outputDoc.getText(0, outputDoc.getLength());
335
336                 // Are there enough lines in the output view ?
337                 String[] allLines = outputTxt.split(StringConstants.NEW_LINE);
338                 if (allLines.length < totalNumberOfLines) {
339                     // Delete lines
340                     config.getOutputView().reset();
341                     config.getOutputView().append(Messages.gettext("Out of Screen"));
342                 } else {
343                     // Delete lines
344                     int lastEOL;
345                     for (int i = 0; i < totalNumberOfLines; i++) {
346                         outputTxt = outputDoc.getText(0, outputDoc.getLength());
347                         lastEOL = outputTxt.lastIndexOf(StringConstants.NEW_LINE);
348                         outputDoc.remove(lastEOL, outputDoc.getLength() - lastEOL);
349                     }
350                 }
351             } catch (BadLocationException e) {
352                 // TODO Auto-generated catch block
353                 e.printStackTrace();
354             }
355         }
356     }
357
358     /**
359      * Puts the prompt in the top left corner of the console
360      */
361     public void toHome() {
362         isToHome = true;
363     }
364
365     public void setToHome() {
366         if (isToHome) {
367             Dimension jSPExtSize = jSP.getViewport().getExtentSize();
368             int caretH = ((SciInputCommandView) config.getInputCommandView()).getCaretHeight();
369             int height = jSPExtSize.height + ((SciPromptView) config.getPromptView()).getParent().getBounds().y - caretH;
370             Dimension newDim = new Dimension(sciConsole.getSize().width, height);
371             sciConsole.setPreferredSize(newDim);
372             sciConsole.invalidate();
373             sciConsole.doLayout();
374             ((SciOutputView) config.getOutputView()).addComponentListener(new ComponentAdapter() {
375                     public void componentResized(ComponentEvent evt) {
376                         if (evt.getComponent().getSize().height >= sciConsole.getSize().height) {
377                             evt.getComponent().removeComponentListener(this);
378                             sciConsole.setPreferredSize(null);
379                             sciConsole.invalidate();
380                             sciConsole.doLayout();
381                         }
382                     }
383                 });
384
385             isToHome = false;
386             jSP.getVerticalScrollBar().getModel().setValue(jSP.getVerticalScrollBar().getModel().getMaximum() - jSP.getVerticalScrollBar().getModel().getExtent());
387         }
388     }
389
390     /**
391      * Gets the user input value
392      * @return the value entered by the used
393      */
394     public int getUserInputValue() {
395         try {
396             canReadUserInputValue.acquire();
397         } catch (InterruptedException e) {
398             e.printStackTrace();
399         }
400         return userInputValue;
401     }
402
403     /**
404      * Sets the value entered by the user
405      * @param userInputValue new value
406      */
407     public void setUserInputValue(int userInputValue) {
408         this.userInputValue = userInputValue;
409         canReadUserInputValue.release();
410     }
411
412     /**
413      * Gets the semaphore protection so that it can be acquired
414      * @return the semaphore
415      */
416     public Semaphore getCanReadUserInputValue() {
417         return canReadUserInputValue;
418     }
419
420     /**
421      * Send commands to be executed by Scilab (after a copy/paste or drag&drop...)
422      * @param textToExec all text lines to executed
423      * @param displayCmdInOutput flag indicating if the input command has to be displayed in the output view
424      * @param storeInHistory flag indicating if the input command has to be stored in the history
425      */
426     public void sendCommandsToScilab(String textToExec, boolean displayCmdInOutput, boolean storeInHistory) {
427         String[] linesToExec = textToExec.split(StringConstants.NEW_LINE);
428         int nbStatements = 0;
429
430         atBottom = true;
431         this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
432
433         while (nbStatements < linesToExec.length) {
434             // This loop contains code very similar to the code of ValidationAction.java
435             InputParsingManager inputParsingManager = config.getInputParsingManager();
436             PromptView promptView = config.getPromptView();
437
438             // Reset command line
439             inputParsingManager.reset();
440
441             // Reset history settings
442             config.getHistoryManager().setInHistory(false);
443
444             // Hide the prompt and command line
445             config.getInputCommandView().setEditable(false);
446             ((SciInputCommandView) config.getInputCommandView()).getCaret().setVisible(false);
447
448             config.getPromptView().setVisible(false);
449
450             // Remove the prompt if present at the beginning of the text to execute
451             // Bug 3002 fix: this "functionality" has been removed because:
452             // - Remove the --> even if not from paste action
453             // - Does not remove pause prompts
454
455             // Store the command in the buffer so that Scilab can read it
456             if (linesToExec[nbStatements].length() > MAX_CMD_LENGTH) {
457                 config.getOutputView().append("Command is too long (more than " + MAX_CMD_LENGTH
458                                               + " characters long): could not send it to Scilab\n");
459                 ((SciInputCommandView) config.getInputCommandView()).setCmdBuffer("", false);
460                 return;
461             }
462
463             ((SciInputCommandView) config.getInputCommandView())
464                 .setCmdBuffer(linesToExec[nbStatements].replace(BACKSLASH_R, ""), displayCmdInOutput);
465             if (storeInHistory) {
466                 ((SciHistoryManager) config.getHistoryManager()).addEntry(linesToExec[nbStatements].replace(BACKSLASH_R, ""));
467             }
468             nbStatements++;
469         }
470
471     }
472
473     /**
474      * Get the JScrollPane associated to the console
475      * @return the JScrollPane associated to the console
476      */
477     public JScrollPane getJScrollPane() {
478         return jSP;
479     }
480
481     /**
482      * Get the Console object associated to the console
483      * @return the Console object associated to the console
484      */
485     public Console getSciConsole() {
486         return sciConsole;
487     }
488
489     /**
490      * Get the current status of the console
491      * If the prompt view is visible, Scilab is waiting for commands
492      * @return true is Scilab is waiting for commands
493      */
494     public boolean isWaitingForInput() {
495         return ((JTextPane) config.getInputCommandView()).isEditable();
496     }
497
498     /**
499      * This methods is used by Scilab to get a new command to execute
500      * @return the command to execute
501      */
502     public String readLine() {
503
504         InputCommandView inputCmdView = this.getConfiguration().getInputCommandView();
505
506         getConfiguration().getOutputView().setCaretPositionToEnd();
507
508         displayPrompt();
509
510         // Display Cursor to show Scilab is available.
511         this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
512
513         // Reads the buffer
514         return ((SciInputCommandView) inputCmdView).getCmdBuffer();
515     }
516
517     /**
518      * This method is used to display the prompt
519      */
520     public abstract void displayPrompt();
521
522     /**
523      * Does Scilab have finished its work ?
524      * @return true if Scilab is waiting for new commands
525      */
526     public boolean isWorkDone() {
527         return workDone;
528     }
529
530     /**
531      * Set the font of the Console
532      * @param font the font to set
533      */
534     public void setFont(Font font) {
535         if (sciConsole != null) {
536             sciConsole.setFont(font);
537
538             /* Have to update the output view contents with new font */
539             String txt;
540             try {
541                 Document outputDoc = ((JEditorPane) config.getOutputView()).getDocument();
542                 txt = outputDoc.getText(0, outputDoc.getLength());
543                 outputDoc.remove(0, outputDoc.getLength());
544                 config.getOutputView().append(txt);
545             } catch (BadLocationException e) {
546                 System.out.println(Messages.gettext("Could not change the Console Font."));
547                 return;
548             }
549
550             /* Update the prompt */
551             ((JLabel) ((SciPromptView) config.getPromptView()).getPromptUI()).setFont(font);
552             config.getPromptView().updatePrompt();
553             scilabLinesUpdate();
554         }
555     }
556
557     /**
558      * Get the font of the Console
559      * @return the font
560      */
561     public Font getFont() {
562         if (sciConsole != null) {
563             return ((JLabel) ((SciPromptView) config.getPromptView()).getPromptUI()).getFont();
564         } else {
565             return null;
566         }
567     }
568
569     /**
570      * Get the Foreground Color of the Console
571      * @return the Foreground Color
572      */
573     public Color getForeground() {
574         if (sciConsole != null) {
575             return sciConsole.getForeground();
576         } else {
577             return null;
578         }
579     }
580
581     /**
582      * Get the Background Color of the Console
583      * @return the Background Color
584      */
585     public Color getBackground() {
586         if (sciConsole != null) {
587             return sciConsole.getBackground();
588         } else {
589             return null;
590         }
591     }
592
593     /**
594      * Set the Foreground Color of the Console
595      * @param color the Foreground Color
596      */
597     public void setForeground(Color color) {
598         if (sciConsole != null) {
599             sciConsole.setForeground(color);
600
601             /* Have to update the output view contents with new Foreground */
602             String txt;
603             try {
604                 Document outputDoc = ((JEditorPane) config.getOutputView()).getDocument();
605                 txt = outputDoc.getText(0, outputDoc.getLength());
606                 outputDoc.remove(0, outputDoc.getLength());
607                 config.getOutputView().append(txt);
608             } catch (BadLocationException e) {
609                 System.out.println(Messages.gettext("Could not change the Console Foreground."));
610                 return;
611             }
612
613             /* Update the prompt */
614             ((JLabel) ((SciPromptView) config.getPromptView()).getPromptUI()).setForeground(color);
615             config.getPromptView().updatePrompt();
616         }
617     }
618
619     /**
620      * Set the Background Color of the Console
621      * @param color the Background Color
622      */
623     public void setBackground(Color color) {
624         if (sciConsole != null) {
625             sciConsole.setBackground(color);
626         }
627     }
628 }