Preferences: continue to connect SciNotes
[scilab.git] / scilab / modules / scinotes / src / java / org / scilab / modules / scinotes / ScilabPlainView.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - Calixte DENIZET
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.scinotes;
14
15 import java.awt.Graphics;
16 import java.awt.Graphics2D;
17 import java.awt.Color;
18 import java.awt.Font;
19 import java.awt.FontMetrics;
20 import java.awt.font.FontRenderContext;
21 import java.awt.font.TextLayout;
22 import java.awt.Shape;
23 import java.awt.Rectangle;
24 import java.awt.geom.Rectangle2D;
25 import java.awt.Toolkit;
26 import java.awt.Component;
27 import java.util.Map;
28
29 import java.io.IOException;
30
31 import javax.swing.text.Utilities;
32 import javax.swing.text.PlainView;
33 import javax.swing.text.Segment;
34 import javax.swing.text.Element;
35 import javax.swing.text.BadLocationException;
36
37 import org.scilab.modules.scinotes.utils.ConfigSciNotesManager;
38
39 /**
40  * This important class is mainly used to render a document in using a PlainView (no line-wrapping)
41  * @author Calixte DENIZET
42  */
43 public class ScilabPlainView extends PlainView {
44
45     private static final String DESKTOPHINTS = "awt.font.desktophints";
46
47     private ScilabContext context;
48     private ScilabLexer lexer;
49     private ScilabDocument doc;
50     private Segment text = new Segment();
51     private boolean isTabViewable = true;
52     private boolean isWhiteViewable = true;
53     private boolean enable = SciNotesOptions.getSciNotesDisplay().keywordsColorization;
54
55     private int tabType;
56     private String tabCharacter = " ";
57     private int tabLength = 4;
58
59     private int numOfColumns = 80;
60     private Color lineColor = new Color(220, 220, 220);
61
62     private final Rectangle rect = new Rectangle();
63     private Map desktopFontHints;
64     private boolean enableDesktopFontHints = true;
65
66     private int whiteHeight;
67     private int whiteWidth;
68
69     private boolean unselected = true;
70
71     /**
72      * The constructor to set this view for an element with a context (containing infos
73      * such as colors or fonts of the keywords).
74      * @param elem the element to view
75      * @param context used to view the element
76      */
77     ScilabPlainView(Element elem, ScilabContext context) {
78         super(elem);
79         this.context = context;
80         doc = (ScilabDocument) getDocument();
81         if (doc.getBinary()) {
82             disable();
83         }
84         doc.setView(this);
85         lexer = doc.createLexer();
86         setTabRepresentation(ScilabView.TABVERTICAL);
87     }
88
89     /**
90      * A tabulation can be drawn with a mark
91      * @param b true if viewable or not
92      */
93     public void setTabViewable(boolean b) {
94         isTabViewable = b;
95     }
96
97     /**
98      * A white can be drawn with a mark
99      * @param b true if viewable or not
100      */
101     public void setWhiteViewable(boolean b) {
102         isWhiteViewable = b;
103     }
104
105     /**
106      * Disable this view
107      */
108     public void disable() {
109         enable = false;
110     }
111
112     /**
113      * Enable this view
114      */
115     public void enable() {
116         enable = true;
117     }
118
119     /**
120      * Enable this view
121      */
122     public void enable(boolean b) {
123         enable = b;
124     }
125
126     /**
127      * If n > 0, then a line will be drawn to see the maximum of chars recommanded in a line
128      * (80 by default).
129      * @param n the maximum of column recommanded in this view
130      */
131     public void setMaxColumns(int n) {
132         numOfColumns = n;
133     }
134
135     /**
136      * @return the width of a white
137      */
138     public int getWhiteWidth() {
139         return whiteWidth;
140     }
141
142     /**
143      * {@inheritDoc}
144      */
145     public float nextTabStop(float x, int tabOffset) {
146         return x + whiteWidth * tabLength;
147     }
148
149     /**
150      * This method can be used to draw anything you want in the editor (such as
151      * the line of maximum recommanded chars).
152      * @param g the graphics where to draw
153      * @param a the shape bounding the visible area
154      * @overload paint method in WrappedPlainView
155      */
156     public void paint(Graphics g, Shape a) {
157         if (numOfColumns > 0) {
158             g.setColor(lineColor);
159             Component c = getContainer();
160             g.drawLine(numOfColumns * whiteWidth, 0, numOfColumns * whiteWidth, c.getHeight());
161         }
162         super.paint(g, a);
163     }
164
165     /**
166      * A trick to easily determine the y-coordinate of a the line n (useful in SciNotesLineNumberPanel)
167      * @param n the line number
168      * @return the y-coordinate of the line
169      */
170     public int getLineAllocation(int n) {
171         rect.setLocation(0, 4); // Why 4 ?? Because it works with 4 !
172         try {
173             return lineToRect(rect, n).y;
174         } catch (ArrayIndexOutOfBoundsException e) {
175             return 0;
176         }
177     }
178
179     /**
180      * Used when the font is changed in the pane
181      */
182     public void reinitialize() {
183         desktopFontHints = null;
184         enableDesktopFontHints = true;
185     }
186
187     /**
188      * Very important method since we draw the text in this method !!
189      * @param g the graphics where to draw
190      * @param sx the x-coordinate where to draw
191      * @param sy the y-coordinate ... (guess the end pf the sentence)
192      * @param p0 the start of the text in the doc
193      * @param p1 the end of the text in the doc
194      * @return the x-coordinate where to draw the next piece of text
195      * @throws BadLocationException if p0 and p1 are bad positions in the text
196      */
197     protected int drawUnselectedText(Graphics g, int sx, int sy, int p0, int p1) throws BadLocationException {
198         if (!enable) {
199             return super.drawUnselectedText(g, sx, sy, p0, p1);
200         }
201
202         if (enableDesktopFontHints && desktopFontHints == null) {
203             /* This hint is used to have antialiased fonts in the view in using
204                the same method (differents way to antialias with LCD screen) as the desktop. */
205             desktopFontHints = (Map) Toolkit.getDefaultToolkit().getDesktopProperty(DESKTOPHINTS);
206             calculateHeight(((Graphics2D) g).getFontRenderContext(), context.tokenFonts[0]);
207             enableDesktopFontHints = desktopFontHints != null;
208         }
209
210         if (enableDesktopFontHints) {
211             ((Graphics2D) g).addRenderingHints(desktopFontHints);
212         }
213
214         /* The lexer returns all tokens between the pos p0 and p1.
215            The value of the returned token determinates the color and the font.
216            The lines can be broken by the Pane so we must look at previous
217            and next chars to know if p0 or p1 is "inside" a token. */
218
219         Element elem = doc.getDefaultRootElement();
220         Element line = elem.getElement(elem.getElementIndex(p0));
221
222         int prevTok = -1;
223         int tok = -1;
224         int mark = p0;
225         int start = p0;
226         int x = sx;
227         int y = sy;
228         boolean isBroken = false;
229
230         int startL = line.getStartOffset();
231         int endL = line.getEndOffset();
232
233         if (startL != start) {
234             //we are drawing a broken line
235             try {
236                 lexer.setRange(startL, endL);
237                 while (startL < start) {
238                     tok = lexer.scan();
239                     startL = lexer.start + lexer.yychar() + lexer.yylength();
240                 }
241                 isBroken = true;
242             } catch (IOException e) { }
243         }
244
245         if (!isBroken) {
246             lexer.setRange(start, endL);
247         }
248
249         while (start < p1 && tok != ScilabLexerConstants.EOF) {
250
251             try {
252                 if (!isBroken) {
253                     tok = lexer.scan();
254                 } else {
255                     isBroken = false;
256                 }
257             } catch (IOException e) { }
258
259             start = lexer.start + lexer.yychar();
260
261             int end = Math.min(p1, start + lexer.yylength());
262
263             if (end != mark) {
264                 if (tok != prevTok) {
265                     if (unselected) {
266                         g.setColor(context.tokenColors[tok]);
267                     } else {
268                         g.setColor(Color.WHITE);
269                     }
270                     g.setFont(context.tokenFonts[tok]);
271                     prevTok = tok;
272                 }
273
274                 doc.getText(mark, end - mark, text);
275
276                 int w;
277
278                 if ((context.tokenAttrib[tok] & 1) != 0) {
279                     w = Utilities.getTabbedTextWidth(text, g.getFontMetrics(), x, this, mark);
280                     g.drawLine(x, y + 1, x + w, y + 1);
281                 }
282
283                 if ((context.tokenAttrib[tok] & 2) != 0) {
284                     w = Utilities.getTabbedTextWidth(text, g.getFontMetrics(), x, this, mark);
285                     g.drawLine(x, y - whiteHeight, x + w, y - whiteHeight);
286                 }
287
288                 switch (tok) {
289                     case ScilabLexerConstants.WHITE :
290                     case ScilabLexerConstants.WHITE_COMMENT :
291                     case ScilabLexerConstants.WHITE_STRING :
292                         if (isWhiteViewable) {
293                             w = Utilities.getTabbedTextWidth(text, g.getFontMetrics(), x, this, mark);
294                             g.drawLine(x + (w - 1) / 2, y - whiteHeight, x + (w + 1) / 2, y - whiteHeight);
295                         }
296                         break;
297                     case ScilabLexerConstants.TAB :
298                     case ScilabLexerConstants.TAB_COMMENT :
299                     case ScilabLexerConstants.TAB_STRING :
300                         if (isTabViewable) {
301                             paintTab(text, x, y, g, mark);
302                         }
303                         break;
304                     case ScilabLexerConstants.ERROR :
305                         if (unselected) {
306                             g.setColor(Color.RED);
307                         } else {
308                             g.setColor(Color.WHITE);
309                         }
310                         w = Utilities.getTabbedTextWidth(text, g.getFontMetrics(), x, this, mark);
311                         for (int i = 0; i < w; i += 4) {
312                             g.drawLine(x + i, y + 2, x + i + 1, y + 2);
313                         }
314                         for (int i = 2; i < w; i += 4) {
315                             g.drawLine(x + i, y + 1, x + i + 1, y + 1);
316                         }
317                         break;
318                     default :
319                         break;
320                 }
321
322                 x = Utilities.drawTabbedText(text, x, y, g, this, mark);
323                 mark = end;
324             }
325
326             start = end;
327         }
328
329         return x;
330     }
331
332     /**
333      * {@inheritDoc}
334      */
335     protected void drawLine(int lineIndex, Graphics g, int x, int y) {
336         Element elem = doc.getDefaultRootElement().getElement(lineIndex);
337         int p0 = elem.getStartOffset();
338         int p1 = elem.getEndOffset();
339         ScilabEditorPane pane = (ScilabEditorPane) getContainer();
340         int sel0 = pane.getSelectionStart();
341         int sel1 = pane.getSelectionEnd();
342         int[] selC = pane.isNearColumnSelection(p0);
343
344         try {
345             if (sel0 == sel1) {
346                 if (selC == null) {
347                     drawUnselectedText(g, x, y, p0, p1);
348                     return;
349                 }
350                 sel0 = selC[0];
351                 sel1 = selC[1];
352                 if (sel0 == sel1) {
353                     drawUnselectedText(g, x, y, p0, p1);
354                     return;
355                 }
356             }
357
358             if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) {
359                 drawSelectedText(g, x, y, p0, p1);
360             } else if (sel0 >= p0 && sel0 <= p1) {
361                 if (sel1 >= p0 && sel1 <= p1) {
362                     x = drawUnselectedText(g, x, y, p0, sel0);
363                     x = drawSelectedText(g, x, y, sel0, sel1);
364                     drawUnselectedText(g, x, y, sel1, p1);
365                 } else {
366                     x = drawUnselectedText(g, x, y, p0, sel0);
367                     drawSelectedText(g, x, y, sel0, p1);
368                 }
369             } else if (sel1 >= p0 && sel1 <= p1) {
370                 x = drawSelectedText(g, x, y, p0, sel1);
371                 drawUnselectedText(g, x, y, sel1, p1);
372             } else {
373                 drawUnselectedText(g, x, y, p0, p1);
374             }
375         } catch (BadLocationException e) { }
376     }
377
378     /**
379      * Draw the selected text.
380      * @param g the graphics where to draw
381      * @param sx the x-coordinate where to draw
382      * @param sy the y-coordinate ... (guess the end pf the sentence)
383      * @param p0 the start of the text in the doc
384      * @param p1 the end of the text in the doc
385      * @return the x-coordinate where to draw the next piece of text
386      * @throws BadLocationException if p0 and p1 are bad positions in the text
387      */
388     protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException {
389         unselected = false;
390         int z = drawUnselectedText(g, x, y, p0, p1);
391         unselected = true;
392         return z;
393     }
394
395     /**
396      * Used to give the way to represent a tabulation. By default TABVERTICAL is used.
397      * @param type must be TABVERTICAL or TABDOUBLECHEVRONS or TABHORIZONTAL
398      * If a bad value is given, then nothing will be drawn
399      */
400     public void setTabRepresentation(int type) {
401         this.tabType = type;
402     }
403
404     /**
405      * Used to represent a tabulation with the given character ('|' or '#' or...)
406      * @param rep the char representing a tab
407      */
408     public void setTabRepresentation(char rep) {
409         setTabRepresentation(ScilabView.TABCHARACTER);
410         this.tabCharacter = Character.toString(rep);
411     }
412
413     /**
414      * Used to represent a tabulation
415      * @param tabulation a Tabulation
416      */
417     public void setTabRepresentation(TabManager.Tabulation tabulation) {
418         if (tabulation.type == ScilabView.TABCHARACTER) {
419             setTabRepresentation(tabulation.rep);
420         } else {
421             setTabRepresentation(tabulation.type);
422         }
423         tabLength = tabulation.number;
424     }
425
426     /**
427      * Used to represent the default tabulation got with ConfigSciNotesManager
428      */
429     public void setDefaultTabRepresentation() {
430         setTabRepresentation(new TabManager.Tabulation());
431     }
432
433     /**
434      * Method to paint a tabulation according to the setTabRepresentation.
435      * @param text the segment of text representing a tabulation
436      * @param x the x-coordinate where to draw
437      * @param y the y-coordinate where to draw
438      * @param g the graphics ... (yeah ! once again)
439      * @param start the position in the document
440      */
441     protected void paintTab(Segment text, int x, int y, Graphics g, int start) {
442         FontMetrics fm = g.getFontMetrics();
443         int w = Utilities.getTabbedTextWidth(text, fm, x, this, start);
444         switch (tabType) {
445             case ScilabView.TABVERTICAL :
446                 g.drawLine(x, y + 4, x, y + 4 - fm.getHeight());
447                 break;
448             case ScilabView.TABDOUBLECHEVRONS :
449                 g.drawString("\u00BB", x, y);
450                 break;
451             case ScilabView.TABHORIZONTAL :
452                 g.drawLine(x, y - whiteHeight, x + w - 1, y - whiteHeight);
453                 break;
454             case ScilabView.TABCHARACTER :
455                 g.drawString(tabCharacter, x, y);
456                 break;
457             default :
458         }
459     }
460
461     /**
462      * Determinates the height of a '+' to have the vertical shift
463      * to draw a line which strokes the text or to draw the mark
464      * let by a white.
465      * @param frc a font context
466      * @param f the font where to take the '+'
467      */
468     private void calculateHeight(FontRenderContext frc, Font f) {
469         TextLayout layout = new TextLayout("+", f, frc);
470         Rectangle2D rectangle = layout.getBounds();
471         whiteHeight = (int) Math.round(-rectangle.getY() / 2);
472         layout = new TextLayout("w", f, frc);
473         rectangle = layout.getBounds();
474         whiteWidth = (int) Math.round(rectangle.getWidth());
475     }
476 }