8fa053baae4e0dd685dab62b7262fe0f594353cd
[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.Color;
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.FocusEvent;
25 import java.awt.event.FocusListener;
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.JPanel;
35 import javax.swing.JScrollPane;
36 import javax.swing.JTextPane;
37 import javax.swing.SwingUtilities;
38 import javax.swing.UIManager;
39 import javax.swing.text.BadLocationException;
40 import javax.swing.text.DefaultCaret;
41 import javax.swing.text.JTextComponent;
42 import javax.swing.text.StyleContext;
43
44 import org.scilab.modules.commons.gui.ScilabCaret;
45
46 import com.artenum.rosetta.interfaces.ui.OutputView;
47 import com.artenum.rosetta.util.BufferedWriter;
48 import com.artenum.rosetta.util.StringConstants;
49
50 /**
51  * Scilab Console UI which contains the previous commands and their outputs
52  *
53  * @author Vincent COUVERT
54  */
55 public class SciOutputView extends JTextPane implements OutputView {
56         private static final long serialVersionUID = 1L;
57
58         private static final int TOP_BORDER = 0;
59
60         private static final int BOTTOM_BORDER = 0;
61
62         private static final int LEFT_BORDER = 0;
63
64         private static final int RIGHT_BORDER = 0;
65
66         private static final int BUFFER_SIZE = 10;
67
68         private String activeStyle;
69
70         private String lastAppendedStyle;
71
72         private BlockingQueue<StringBuffer> bufferQueue;
73
74         private LinkedList<String> styleQueue;
75
76         private StringBuffer currentWorkingBuffer;
77
78         private SciConsole console;
79
80         private Thread thread;
81         
82         private int insertPosition;
83         
84         private int maxNumberOfLines;
85         private int numberOfLines;
86         
87         /**
88          * Constructor
89          */
90         public SciOutputView() {
91                 setBorder(BorderFactory.createEmptyBorder(TOP_BORDER, LEFT_BORDER,
92                                 BOTTOM_BORDER, RIGHT_BORDER));
93
94                 // Enabled Drag&Drop with this component
95                 this.setDragEnabled(true);
96                 this.setDoubleBuffered(true);
97
98                 activeStyle = StyleContext.DEFAULT_STYLE;
99                 bufferQueue = new ArrayBlockingQueue<StringBuffer>(BUFFER_SIZE);
100                 styleQueue = new LinkedList<String>();
101                 setFocusable(false);
102
103                 /**
104                  * Default caret for output view (to handle paste actions using middle button)
105                  * @author Vincent COUVERT
106                  */
107                 final class FixedCaret extends ScilabCaret {
108
109                         /**
110                          * Constructor
111                          */
112                         private FixedCaret() {
113                                 super(SciOutputView.this);
114                         }
115
116                         /**
117                          * Manages mouse clicks
118                          * @param e the event
119                          * @see javax.swing.text.DefaultCaret#mouseClicked(java.awt.event.MouseEvent)
120                          */
121                         public void mouseClicked(MouseEvent e) {
122                                 if (SwingUtilities.isMiddleMouseButton(e) && e.getClickCount() == 1) {
123                                         /*** PASTE USING MIDDLE BUTTON ***/
124                                         JTextComponent c = (JTextComponent) e.getSource();
125                                         if (c != null) {
126                                                 Toolkit tk = c.getToolkit();
127                                                 Clipboard buffer = tk.getSystemSelection();
128                                                 if (buffer != null) {
129                                                         Transferable trans = buffer.getContents(null);
130                                                         if (trans.isDataFlavorSupported(DataFlavor.stringFlavor)) {
131                                                                 try {
132                                                                         String pastedText = (String) trans.getTransferData(DataFlavor.stringFlavor);
133                                                                         ((JTextPane) getConsole()
134                                                                                         .getConfiguration()
135                                                                                         .getInputCommandView())
136                                                                                         .replaceSelection(pastedText);
137                                                                 } catch (UnsupportedFlavorException e1) {
138                                                                         e1.printStackTrace();
139                                                                 } catch (IOException e1) {
140                                                                         e1.printStackTrace();
141                                                                 }
142                                                         }
143                                                 }
144                                         }
145                                 } else if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) {
146                                         /*** SEND THE FOCUS TO THE INPUT COMMAND VIEW ***/
147                                         ((JTextPane) getConsole().getConfiguration().getInputCommandView()).requestFocus();
148                                         ((JTextPane) getConsole().getConfiguration().getInputCommandView()).getCaret().setVisible(true);
149                                 } else {
150                                         /*** DELEGATE TO THE SYSTEM ***/
151                                         super.mouseClicked(e);
152                                 }
153                         }
154                 }
155
156                 // Set the caret
157                 setCaret(new FixedCaret());
158                 // Selection is forced to be visible because the component is not editable
159                 getCaret().setSelectionVisible(true);
160         }
161
162         /**
163          * Display a buffer entry in the console
164          * @param buff the string  to write
165          * @param style the style to use to format the string
166          */
167         private void displayLineBuffer(String buff, String style) {
168
169                 int sDocLength = getStyledDocument().getLength();
170                 
171                 if (buff.equals("\r")) {
172                         /* If \r sent by mprintf then display nothing but prepare next display */
173                         /* Insertion will be done just after last NEW_LINE */
174                         try {
175                                 sDocLength = getStyledDocument().getLength();
176                                 String outputTxt = getStyledDocument().getText(0, sDocLength);
177                                 insertPosition = outputTxt.lastIndexOf(StringConstants.NEW_LINE) + 1;
178                         } catch (BadLocationException e) {
179                                 e.printStackTrace();
180                         }
181                         return;
182                 } else {
183                         /* Change position for insertion if a previous \r still influence display */
184                         if ((insertPosition != 0) && (insertPosition < sDocLength)) {
185                                 sDocLength = insertPosition;
186                                 try {
187                                         /* Remove chars to be replaced */
188                                         if (insertPosition + buff.length() <= getStyledDocument().getLength()) {
189                                                 getStyledDocument().remove(insertPosition, buff.length());
190                                         } else {
191                                                 /* Remove end of line */
192                                                 getStyledDocument().remove(insertPosition, getStyledDocument().getLength() - insertPosition);
193                                         }
194                                 } catch (BadLocationException e) {
195                                         e.printStackTrace();
196                                 }
197                         } else {
198                                 /* Reinit insertPosition: 0 is equivalent to insertPosition value ignored */
199                                 insertPosition = 0;
200                         }
201                 }
202                 
203                 try {
204                         getStyledDocument().insertString(sDocLength, buff, getStyledDocument().getStyle(style));
205                         /* Move insertPosition to the end of last inserted data */
206                         if (insertPosition != 0) {
207                                 insertPosition += buff.length();
208                         }
209                 } catch (BadLocationException e) {
210                         // TODO Auto-generated catch block
211                         e.printStackTrace();
212                 }
213                 
214                 /* Special case for Scilab when clc or tohome have been used */
215                 String[] lines = buff.split(StringConstants.NEW_LINE);
216
217                 numberOfLines += lines.length;
218
219                 while (numberOfLines > maxNumberOfLines) {
220                         try {
221                                 getStyledDocument().remove(0, 
222                                                 getStyledDocument().getText(0, getStyledDocument().getLength()).indexOf(StringConstants.NEW_LINE, 0));
223                                 numberOfLines--;
224                         } catch (BadLocationException e) {
225                                 e.printStackTrace();
226                         }
227                 }
228                 
229                 /* Change the size of the input command view if necessary */
230                 /* - if the console size has been forced to a value */
231                 /* - if a carriage return has been appended */
232                 if (console != null
233                                 && console.getInputCommandViewSizeForced()
234                                 && lines.length > 0) {
235
236                         JTextPane outputView = ((JTextPane) console
237                                         .getConfiguration().getOutputView());
238
239                         // Get JScrollPane viewport size to adapt input command
240                         // view size
241                         JScrollPane jSP = console.getJScrollPane();
242                         Dimension jSPExtSize = jSP.getViewport()
243                         .getExtentSize();
244
245                         /* Height of a text line in the ouput view */
246                         int charHeight = outputView.getFontMetrics(
247                                         outputView.getFont()).getHeight();
248
249                         JPanel promptView = ((JPanel) console
250                                         .getConfiguration().getPromptView());
251
252                         /* Input command view dimensions */
253                         JTextPane inputCmdView = ((JTextPane) console
254                                         .getConfiguration().getInputCommandView());
255                         int height = inputCmdView.getPreferredSize().height;
256                         int width = inputCmdView.getPreferredSize().width
257                         - jSPExtSize.width;
258
259                         int promptViewHeight = promptView.getPreferredSize().height;
260
261                         /* New dimension for the input command view */
262                         /*
263                          * -1 because last EOL removed in
264                          * SwingScilabConsole.readline
265                          */
266                         int newHeight = height - (lines.length - 1)
267                         * charHeight;
268                         Dimension newDim = null;
269
270                         if (newHeight > promptViewHeight) {
271                                 /*
272                                  * If the input command view is bigger than the
273                                  * promptUI
274                                  */
275                                 /*
276                                  * It's height is descreased according to line
277                                  * number of lines added to output view
278                                  */
279                                 newDim = new Dimension(width, newHeight);
280                         } else {
281                                 /*
282                                  * If the input command view is smaller than the
283                                  * promptUI
284                                  */
285                                 /* It's height adapted to the promptUI height */
286                                 newDim = new Dimension(width, promptViewHeight);
287                                 console.setInputCommandViewSizeForced(false);
288                         }
289                         /* Change the input command view size */
290                         ((JTextPane) console.getConfiguration()
291                                         .getInputCommandView())
292                                         .setPreferredSize(newDim);
293                         ((JTextPane) console.getConfiguration()
294                                         .getInputCommandView()).invalidate();
295                         ((JTextPane) console.getConfiguration()
296                                         .getInputCommandView()).doLayout();
297                 }
298                 /* Update scroll only if console has been set */
299                 /* TODO : Must not do this each time... consume pretty much computing resources */
300                 if (console != null) {
301                         console.updateScrollPosition();
302                 }
303         }
304
305         /**
306          * Adds text to the output view and change the size of others components if
307          * necessary
308          *
309          * @param content
310          *            text to add
311          */
312         public void append(String content) {
313                 //append(content, activeStyle);
314                 displayLineBuffer(content, activeStyle);
315         }
316
317         /**
318          * Adds text to the output view and change the size of others components if
319          * necessary
320          *
321          * @param content
322          *            text to add
323          * @param styleName
324          *            style to set for content
325          */
326         public void append(String content, String styleName) {
327                 if (styleName.equals(lastAppendedStyle) && bufferQueue.size() > 1) {
328                         currentWorkingBuffer.append(content);
329                 } else {
330                         lastAppendedStyle = styleName;
331                         styleQueue.add(lastAppendedStyle);
332                         try {
333                                 currentWorkingBuffer = new StringBuffer(content);
334                                 bufferQueue.put(currentWorkingBuffer);
335                         } catch (InterruptedException e) {
336                                 e.printStackTrace();
337                         }
338                 }
339                 if (!thread.isAlive()) {
340                         thread.run();
341                 }
342         }
343
344         /**
345          * Gets the error writer
346          *
347          * @return the error writer
348          * @see com.artenum.rosetta.interfaces.ui.OutputView#getErrorWriter()
349          */
350         public Writer getErrorWriter() {
351                 return new BufferedWriter(StyleContext.DEFAULT_STYLE, bufferQueue,
352                                 styleQueue);
353         }
354
355         /**
356          * Gets the writer
357          *
358          * @return the writer
359          * @see com.artenum.rosetta.interfaces.ui.OutputView#getWriter()
360          */
361         public Writer getWriter() {
362                 return new BufferedWriter(StyleContext.DEFAULT_STYLE, bufferQueue,
363                                 styleQueue);
364         }
365
366         /**
367          * Resets the output view (remove text)
368          *
369          * @see com.artenum.rosetta.interfaces.ui.OutputView#reset()
370          */
371         public void reset() {
372                 setText("");
373                 setCaretPosition(0);
374         }
375
376         /**
377          * Move the caret to the beginning of the styled document
378          *
379          * @see com.artenum.rosetta.interfaces.ui.OutputView#setCaretPositionToBeginning()
380          */
381         public void setCaretPositionToBeginning() {
382                 insertPosition = 0;
383                 setCaretPosition(0);
384         }
385
386         /**
387          * Move the caret to the end of the styled document
388          *
389          * @see com.artenum.rosetta.interfaces.ui.OutputView#setCaretPositionToEnd()
390          */
391         public void setCaretPositionToEnd() {
392                 insertPosition = 0;
393                 setCaretPosition(getStyledDocument().getLength());
394         }
395
396         /**
397          * Set the style for current text
398          *
399          * @param styleName
400          *            the style to set
401          * @see com.artenum.rosetta.interfaces.ui.OutputView#setStyleName(java.lang.String)
402          */
403         public void setStyleName(String styleName) {
404                 activeStyle = styleName;
405         }
406
407         /**
408          * Sets the console object containing this output view
409          *
410          * @param c
411          *            the console associated
412          */
413         public void setConsole(SciConsole c) {
414                 console = c;
415
416                 // Drag n' Drop handling
417                 this.setDropTarget(new DropTarget(this,
418                                 DnDConstants.ACTION_COPY_OR_MOVE, new SciDropTargetListener(
419                                                 console)));
420
421                 // Commented because now done by the caret class
422                 //FocusMouseListener focusGrabber = new FocusMouseListener(console);
423                 //this.addMouseListener(focusGrabber);
424         }
425
426         /**
427          * Gets the console object containing this output view
428          *
429          * @return the console associated
430          */
431         public SciConsole getConsole() {
432                 return console;
433         }
434
435         /**
436          * Get the current thread used to display
437          * @return the thread
438          */
439         public Thread getThread() {
440                 return thread;
441         }
442         
443         /**
444          * Set the maximum number of lines to keep before deleting the older one
445          * @param number the maximum
446          */
447         public void setMaxSize(int number) {
448                 maxNumberOfLines = number;
449         }
450
451 }