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