01a23937b0383cfba0f57fa83024ad91191e35a2
[scilab.git] / scilab / modules / console / src / java / org / scilab / modules / console / SciOutputView.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.Container;
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.Transferable;
21 import java.awt.datatransfer.UnsupportedFlavorException;
22 import java.awt.dnd.DnDConstants;
23 import java.awt.dnd.DropTarget;
24 import java.awt.event.FocusAdapter;
25 import java.awt.event.FocusEvent;
26 import java.awt.event.MouseEvent;
27 import java.io.IOException;
28 import java.io.Writer;
29 import java.util.LinkedList;
30 import java.util.concurrent.ArrayBlockingQueue;
31 import java.util.concurrent.BlockingQueue;
32
33 import javax.swing.BorderFactory;
34 import javax.swing.JEditorPane;
35 import javax.swing.JPanel;
36 import javax.swing.JScrollPane;
37 import javax.swing.JTextPane;
38 import javax.swing.SwingUtilities;
39 import javax.swing.text.BadLocationException;
40 import javax.swing.text.DefaultEditorKit;
41 import javax.swing.text.Element;
42 import javax.swing.text.JTextComponent;
43 import javax.swing.text.PlainDocument;
44 import javax.swing.text.StyleContext;
45 import javax.swing.text.StyledDocument;
46 import javax.swing.text.PlainView;
47 import javax.swing.text.View;
48 import javax.swing.text.ViewFactory;
49
50 import org.scilab.modules.commons.gui.ScilabCaret;
51
52 import com.artenum.rosetta.interfaces.ui.OutputView;
53 import com.artenum.rosetta.util.BufferedWriter;
54 import com.artenum.rosetta.util.StringConstants;
55
56 /**
57  * Scilab Console UI which contains the previous commands and their outputs
58  *
59  * @author Vincent COUVERT
60  */
61 public class SciOutputView extends JEditorPane implements OutputView, ViewFactory {
62     private static final long serialVersionUID = 1L;
63
64     private static final int TOP_BORDER = 0;
65
66     private static final int BOTTOM_BORDER = 0;
67
68     private static final int LEFT_BORDER = 0;
69
70     private static final int RIGHT_BORDER = 0;
71
72     private static final int BUFFER_SIZE = 10;
73
74     private String activeStyle;
75
76     private String lastAppendedStyle;
77
78     private BlockingQueue<StringBuffer> bufferQueue;
79
80     private LinkedList<String> styleQueue;
81
82     private StringBuffer currentWorkingBuffer;
83
84     private SciConsole console;
85
86     private Thread thread;
87
88     private int insertPosition;
89
90     private int maxNumberOfLines;
91
92     private boolean lastEOL;
93
94     /**
95      * Constructor
96      */
97     public SciOutputView() {
98         super();
99
100         setEditorKit(new DefaultEditorKit() {
101                 public ViewFactory getViewFactory() {
102                     return SciOutputView.this;
103                 }
104             });
105
106         /* A PlainDocument contains only "box" for lines not for all characters (as in a StyledDocument)
107            so there are less boxes to explore in a PlainDocument... */
108         setDocument(new PlainDocument());
109         setMaxSize(10000);
110         setBorder(BorderFactory.createEmptyBorder(TOP_BORDER, LEFT_BORDER, BOTTOM_BORDER, RIGHT_BORDER));
111
112         // Enabled Drag&Drop with this component
113         this.setDragEnabled(true);
114         this.setDoubleBuffered(true);
115
116         activeStyle = StyleContext.DEFAULT_STYLE;
117         bufferQueue = new ArrayBlockingQueue<StringBuffer>(BUFFER_SIZE);
118         styleQueue = new LinkedList<String>();
119
120         /**
121          * Default caret for output view (to handle paste actions using middle button)
122          * @author Vincent COUVERT
123          */
124         final class FixedCaret extends ScilabCaret {
125
126             private static final long serialVersionUID = 8230195712653828841L;
127
128             /**
129              * Constructor
130              */
131             private FixedCaret() {
132                 super(SciOutputView.this);
133             }
134
135             /**
136              * Manages mouse clicks
137              * @param e the event
138              * @see javax.swing.text.DefaultCaret#mouseClicked(java.awt.event.MouseEvent)
139              */
140             public void mouseClicked(MouseEvent e) {
141                 if (SwingUtilities.isMiddleMouseButton(e) && e.getClickCount() == 1) {
142                     /*** PASTE USING MIDDLE BUTTON ***/
143                     JTextComponent c = (JTextComponent) e.getSource();
144                     if (c != null) {
145                         Toolkit tk = c.getToolkit();
146                         Clipboard buffer = tk.getSystemSelection();
147                         if (buffer != null) {
148                             Transferable trans = buffer.getContents(null);
149                             if (trans.isDataFlavorSupported(DataFlavor.stringFlavor)) {
150                                 try {
151                                     String pastedText = (String) trans.getTransferData(DataFlavor.stringFlavor);
152                                     ((JTextPane) getConsole().getConfiguration().getInputCommandView()).replaceSelection(pastedText);
153                                 } catch (UnsupportedFlavorException e1) {
154                                     e1.printStackTrace();
155                                 } catch (IOException e1) {
156                                     e1.printStackTrace();
157                                 }
158                             }
159                         }
160                     }
161                 } else if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) {
162                     /*** SEND THE FOCUS TO THE INPUT COMMAND VIEW ***/
163                     ((JTextPane) getConsole().getConfiguration().getInputCommandView()).requestFocus();
164                     ((JTextPane) getConsole().getConfiguration().getInputCommandView()).getCaret().setVisible(true);
165                 } else {
166                     /*** DELEGATE TO THE SYSTEM ***/
167                     super.mouseClicked(e);
168                 }
169             }
170
171             public void mousePressed(MouseEvent e) {
172                 ((SciInputCommandView) console.getConfiguration().getInputCommandView()).removeSelection();
173                 super.mousePressed(e);
174             }
175         }
176         // Set the caret
177         setCaret(new FixedCaret());
178         // Selection is forced to be visible because the component is not editable
179         getCaret().setSelectionVisible(true);
180
181         addFocusListener(new FocusAdapter() {
182                 public void focusGained(FocusEvent e) {
183                     ((JTextPane) getConsole().getConfiguration().getInputCommandView()).requestFocus();
184                 }
185             });
186     }
187
188     /**
189      * Unselect text if selected one exists
190      */
191     public void removeSelection() {
192         if (getSelectionStart() != getSelectionEnd()) {
193             setSelectionEnd(getSelectionStart());
194         }
195     }
196
197     /**
198      * @param styledDocument
199      */
200     public void setStyledDocument(StyledDocument styledDocument) { }
201
202     public void resetLastEOL() {
203         lastEOL = false;
204     }
205
206     /**
207      * Display a buffer entry in the console
208      * @param buff the string  to write
209      * @param style the style to use to format the string
210      */
211     private void displayLineBuffer(String buff, String style) {
212         int sDocLength = getDocument().getLength();
213
214         if (buff.equals("\r")) {
215             /* If \r sent by mprintf then display nothing but prepare next display */
216             /* Insertion will be done just after last NEW_LINE */
217             try {
218                 String outputTxt = getDocument().getText(0, sDocLength);
219                 insertPosition = outputTxt.lastIndexOf(StringConstants.NEW_LINE) + 1;
220             } catch (BadLocationException e) {
221                 e.printStackTrace();
222             }
223             return;
224         } else {
225             /* Change position for insertion if a previous \r still influence display */
226             if ((insertPosition != 0) && (insertPosition < sDocLength)) {
227                 sDocLength = insertPosition;
228                 try {
229                     /* Remove chars to be replaced */
230                     if (insertPosition + buff.length() <= getDocument().getLength()) {
231                         getDocument().remove(insertPosition, buff.length());
232                     } else {
233                         /* Remove end of line */
234                         getDocument().remove(insertPosition, getDocument().getLength() - insertPosition);
235                     }
236                 } catch (BadLocationException e) {
237                     e.printStackTrace();
238                 }
239             } else {
240                 /* Reinit insertPosition: 0 is equivalent to insertPosition value ignored */
241                 insertPosition = 0;
242             }
243         }
244
245         boolean slastEOL = lastEOL;
246         lastEOL = !buff.isEmpty() && buff.charAt(buff.length() - 1) == '\n';
247         String str = buff;
248         if (lastEOL) {
249             str = buff.substring(0, buff.length() - 1);
250         }
251
252         if (slastEOL) {
253             str = "\n" + str;
254         }
255
256         try {
257             getDocument().insertString(sDocLength, str, null);
258
259             /* Move insertPosition to the end of last inserted data */
260             if (insertPosition != 0) {
261                 insertPosition += str.length();
262             }
263         } catch (BadLocationException e) {
264             // TODO Auto-generated catch block
265             e.printStackTrace();
266         }
267
268         int count = getDocument().getDefaultRootElement().getElementCount();
269         if (count > 1.5 * maxNumberOfLines) {
270             /* A removal is costly: array copy and with a gap buffer that leads to two array copies (when remove is followed by an insert).
271                So the idea is to minimize the number of removal: a removal only when 0.5*maxNumberOfLines useless lines are entered.
272             */
273             try {
274                 getDocument().remove(0, getDocument().getDefaultRootElement().getElement(count - maxNumberOfLines - 1).getEndOffset());
275             } catch (BadLocationException e) {
276                 e.printStackTrace();
277             }
278         }
279     }
280
281     /**
282      * Adds text to the output view and change the size of others components if
283      * necessary
284      *
285      * @param content
286      *            text to add
287      */
288     public void append(String content) {
289         //append(content, activeStyle);
290         displayLineBuffer(content, activeStyle);
291     }
292
293     /**
294      * Adds text to the output view and change the size of others components if
295      * necessary
296      *
297      * @param content
298      *            text to add
299      * @param styleName
300      *            style to set for content
301      */
302     public void append(String content, String styleName) {
303         if (styleName.equals(lastAppendedStyle) && bufferQueue.size() > 1) {
304             currentWorkingBuffer.append(content);
305         } else {
306             lastAppendedStyle = styleName;
307             styleQueue.add(lastAppendedStyle);
308             try {
309                 currentWorkingBuffer = new StringBuffer(content);
310                 bufferQueue.put(currentWorkingBuffer);
311             } catch (InterruptedException e) {
312                 e.printStackTrace();
313             }
314         }
315         if (!thread.isAlive()) {
316             thread.run();
317         }
318     }
319
320     /**
321      * Gets the error writer
322      *
323      * @return the error writer
324      * @see com.artenum.rosetta.interfaces.ui.OutputView#getErrorWriter()
325      */
326     public Writer getErrorWriter() {
327         return new BufferedWriter(StyleContext.DEFAULT_STYLE, bufferQueue, styleQueue);
328     }
329
330     /**
331      * Gets the writer
332      *
333      * @return the writer
334      * @see com.artenum.rosetta.interfaces.ui.OutputView#getWriter()
335      */
336     public Writer getWriter() {
337         return new BufferedWriter(StyleContext.DEFAULT_STYLE, bufferQueue, styleQueue);
338     }
339
340     /**
341      * Resets the output view (remove text)
342      *
343      * @see com.artenum.rosetta.interfaces.ui.OutputView#reset()
344      */
345     public void reset() {
346         setText("");
347         setCaretPosition(0);
348         lastEOL = false;
349     }
350
351     /**
352      * Move the caret to the beginning of the styled document
353      *
354      * @see com.artenum.rosetta.interfaces.ui.OutputView#setCaretPositionToBeginning()
355      */
356     public void setCaretPositionToBeginning() {
357         insertPosition = 0;
358         setCaretPosition(0);
359     }
360
361     /**
362      * Move the caret to the end of the styled document
363      *
364      * @see com.artenum.rosetta.interfaces.ui.OutputView#setCaretPositionToEnd()
365      */
366     public void setCaretPositionToEnd() {
367         insertPosition = 0;
368         setCaretPosition(getDocument().getLength());
369     }
370
371     /**
372      * Set the style for current text
373      *
374      * @param styleName
375      *            the style to set
376      * @see com.artenum.rosetta.interfaces.ui.OutputView#setStyleName(java.lang.String)
377      */
378     public void setStyleName(String styleName) {
379         activeStyle = styleName;
380     }
381
382     /**
383      * Sets the console object containing this output view
384      *
385      * @param c
386      *            the console associated
387      */
388     public void setConsole(SciConsole c) {
389         console = c;
390
391         // Drag n' Drop handling
392         this.setDropTarget(new DropTarget(this,
393                                           DnDConstants.ACTION_COPY_OR_MOVE, new SciDropTargetListener(console)));
394
395         // Commented because now done by the caret class
396         //FocusMouseListener focusGrabber = new FocusMouseListener(console);
397         //this.addMouseListener(focusGrabber);
398     }
399
400     /**
401      * Gets the console object containing this output view
402      *
403      * @return the console associated
404      */
405     public SciConsole getConsole() {
406         return console;
407     }
408
409     /**
410      * Get the current thread used to display
411      * @return the thread
412      */
413     public Thread getThread() {
414         return thread;
415     }
416
417     /**
418      * Set the maximum number of lines to keep before deleting the older one
419      * @param number the maximum
420      */
421     public void setMaxSize(int number) {
422         maxNumberOfLines = Math.max(1, number);
423     }
424
425     public View create(Element e) {
426         return new PlainView(e) {
427             public Container getContainer() {
428                 return SciOutputView.this;
429             }
430         };
431     }
432 }