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