1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 package com.sun.star.script.framework.provider.beanshell;
24 
25 import javax.swing.JTextArea;
26 import javax.swing.JScrollPane;
27 import javax.swing.JComponent;
28 import javax.swing.event.DocumentListener;
29 import javax.swing.event.DocumentEvent;
30 
31 import java.awt.Graphics;
32 import java.awt.Color;
33 import java.awt.Font;
34 import java.awt.FontMetrics;
35 import java.awt.Polygon;
36 import java.awt.Rectangle;
37 import java.awt.Dimension;
38 
39 public class PlainSourceView extends JScrollPane
40     implements ScriptSourceView, DocumentListener {
41 
42     private ScriptSourceModel model;
43     private JTextArea ta;
44     private GlyphGutter gg;
45     private int linecount;
46     private boolean isModified = false;
47 
PlainSourceView(ScriptSourceModel model)48     public PlainSourceView(ScriptSourceModel model) {
49         this.model = model;
50         initUI();
51         model.setView(this);
52     }
53 
clear()54     public void clear() {
55         ta.setText("");
56     }
57 
update()58     public void update() {
59         /* Remove ourselves as a DocumentListener while loading the source
60            so we don't get a storm of DocumentEvents during loading */
61         ta.getDocument().removeDocumentListener(this);
62 
63         if (isModified == false)
64         {
65             int pos = ta.getCaretPosition();
66             ta.setText(model.getText());
67             try {
68                 ta.setCaretPosition(pos);
69             }
70             catch (IllegalArgumentException iae) {
71                 // do nothing and allow JTextArea to set it's own position
72             }
73         }
74 
75         // scroll to currentPosition of the model
76         try {
77             int line = ta.getLineStartOffset(model.getCurrentPosition());
78             Rectangle rect = ta.modelToView(line);
79             ta.scrollRectToVisible(rect);
80         }
81         catch (Exception e) {
82             // couldn't scroll to line, do nothing
83         }
84 
85         gg.repaint();
86 
87         // Add back the listener
88         ta.getDocument().addDocumentListener(this);
89     }
90 
isModified()91     public boolean isModified() {
92         return isModified;
93     }
94 
setModified(boolean value)95     public void setModified(boolean value) {
96         isModified = value;
97     }
98 
initUI()99     private void initUI() {
100         ta = new JTextArea();
101         ta.setRows(15);
102         ta.setColumns(40);
103         ta.setLineWrap(false);
104         ta.insert(model.getText(), 0);
105         linecount = ta.getLineCount();
106 
107         gg = new GlyphGutter(this);
108 
109         setViewportView(ta);
110         setRowHeaderView(gg);
111 
112         ta.getDocument().addDocumentListener(this);
113     }
114 
115     /* Implementation of DocumentListener interface */
insertUpdate(DocumentEvent e)116     public void insertUpdate(DocumentEvent e) {
117         doChanged(e);
118     }
119 
removeUpdate(DocumentEvent e)120     public void removeUpdate(DocumentEvent e) {
121         doChanged(e);
122     }
123 
changedUpdate(DocumentEvent e)124     public void changedUpdate(DocumentEvent e) {
125         doChanged(e);
126     }
127 
128     /* If the number of lines in the JTextArea has changed then update the
129        GlyphGutter */
doChanged(DocumentEvent e)130     public void doChanged(DocumentEvent e) {
131         isModified = true;
132 
133         if (linecount != ta.getLineCount()) {
134             gg.update();
135             linecount = ta.getLineCount();
136         }
137     }
138 
getText()139     public String getText() {
140         return ta.getText();
141     }
142 
getTextArea()143     public JTextArea getTextArea() {
144         return ta;
145     }
146 
getCurrentPosition()147     public int getCurrentPosition() {
148         return model.getCurrentPosition();
149     }
150 }
151 
152 class GlyphGutter extends JComponent {
153 
154     private PlainSourceView view;
155     private final String DUMMY_STRING = "99";
156 
GlyphGutter(PlainSourceView view)157     GlyphGutter(PlainSourceView view) {
158         this.view = view;
159         update();
160     }
161 
update()162     public void update() {
163         JTextArea textArea = view.getTextArea();
164         Font font = textArea.getFont();
165         setFont(font);
166 
167         FontMetrics metrics = getFontMetrics(font);
168         int h = metrics.getHeight();
169         int lineCount = textArea.getLineCount() + 1;
170 
171         String dummy = Integer.toString(lineCount);
172         if (dummy.length() < 2) {
173             dummy = DUMMY_STRING;
174         }
175 
176         Dimension d = new Dimension();
177         d.width = metrics.stringWidth(dummy) + 16;
178         d.height = lineCount * h + 100;
179         setPreferredSize(d);
180         setSize(d);
181     }
182 
paintComponent(Graphics g)183     public void paintComponent(Graphics g) {
184         JTextArea textArea = view.getTextArea();
185 
186         Font font = textArea.getFont();
187         g.setFont(font);
188 
189         FontMetrics metrics = getFontMetrics(font);
190         Rectangle clip = g.getClipBounds();
191 
192         g.setColor(getBackground());
193         g.fillRect(clip.x, clip.y, clip.width, clip.height);
194 
195         int ascent = metrics.getMaxAscent();
196         int h = metrics.getHeight();
197         int lineCount = textArea.getLineCount() + 1;
198 
199         int startLine = clip.y / h;
200         int endLine = (clip.y + clip.height) / h + 1;
201         int width = getWidth();
202         if (endLine > lineCount) {
203             endLine = lineCount;
204         }
205 
206         for (int i = startLine; i < endLine; i++) {
207             String text;
208             text = Integer.toString(i + 1) + " ";
209             int w = metrics.stringWidth(text);
210             int y = i * h;
211             g.setColor(Color.blue);
212             g.drawString(text, 0, y + ascent);
213             int x = width - ascent;
214 
215             // if currentPosition is not -1 then a red arrow will be drawn
216             if (i == view.getCurrentPosition()) {
217                 drawArrow(g, ascent, x, y);
218             }
219         }
220     }
221 
drawArrow(Graphics g, int ascent, int x, int y)222     private void drawArrow(Graphics g, int ascent, int x, int y) {
223         Polygon arrow = new Polygon();
224         int dx = x;
225         y += ascent - 10;
226         int dy = y;
227         arrow.addPoint(dx, dy + 3);
228         arrow.addPoint(dx + 5, dy + 3);
229         for (x = dx + 5; x <= dx + 10; x++, y++) {
230             arrow.addPoint(x, y);
231         }
232         for (x = dx + 9; x >= dx + 5; x--, y++) {
233             arrow.addPoint(x, y);
234         }
235         arrow.addPoint(dx + 5, dy + 7);
236         arrow.addPoint(dx, dy + 7);
237 
238         g.setColor(Color.red);
239         g.fillPolygon(arrow);
240         g.setColor(Color.black);
241         g.drawPolygon(arrow);
242     }
243 }
244