/************************************************************** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ package com.sun.star.script.framework.provider.beanshell; import javax.swing.JTextArea; import javax.swing.JScrollPane; import javax.swing.JComponent; import javax.swing.event.DocumentListener; import javax.swing.event.DocumentEvent; import java.awt.Graphics; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.Dimension; public class PlainSourceView extends JScrollPane implements ScriptSourceView, DocumentListener { private ScriptSourceModel model; private JTextArea ta; private GlyphGutter gg; private int linecount; private boolean isModified = false; public PlainSourceView(ScriptSourceModel model) { this.model = model; initUI(); model.setView(this); } public void clear() { ta.setText(""); } public void update() { /* Remove ourselves as a DocumentListener while loading the source so we don't get a storm of DocumentEvents during loading */ ta.getDocument().removeDocumentListener(this); if (isModified == false) { int pos = ta.getCaretPosition(); ta.setText(model.getText()); try { ta.setCaretPosition(pos); } catch (IllegalArgumentException iae) { // do nothing and allow JTextArea to set it's own position } } // scroll to currentPosition of the model try { int line = ta.getLineStartOffset(model.getCurrentPosition()); Rectangle rect = ta.modelToView(line); ta.scrollRectToVisible(rect); } catch (Exception e) { // couldn't scroll to line, do nothing } gg.repaint(); // Add back the listener ta.getDocument().addDocumentListener(this); } public boolean isModified() { return isModified; } public void setModified(boolean value) { isModified = value; } private void initUI() { ta = new JTextArea(); ta.setRows(15); ta.setColumns(40); ta.setLineWrap(false); ta.insert(model.getText(), 0); linecount = ta.getLineCount(); gg = new GlyphGutter(this); setViewportView(ta); setRowHeaderView(gg); ta.getDocument().addDocumentListener(this); } /* Implementation of DocumentListener interface */ public void insertUpdate(DocumentEvent e) { doChanged(e); } public void removeUpdate(DocumentEvent e) { doChanged(e); } public void changedUpdate(DocumentEvent e) { doChanged(e); } /* If the number of lines in the JTextArea has changed then update the GlyphGutter */ public void doChanged(DocumentEvent e) { isModified = true; if (linecount != ta.getLineCount()) { gg.update(); linecount = ta.getLineCount(); } } public String getText() { return ta.getText(); } public JTextArea getTextArea() { return ta; } public int getCurrentPosition() { return model.getCurrentPosition(); } } class GlyphGutter extends JComponent { private PlainSourceView view; private final String DUMMY_STRING = "99"; GlyphGutter(PlainSourceView view) { this.view = view; update(); } public void update() { JTextArea textArea = view.getTextArea(); Font font = textArea.getFont(); setFont(font); FontMetrics metrics = getFontMetrics(font); int h = metrics.getHeight(); int lineCount = textArea.getLineCount() + 1; String dummy = Integer.toString(lineCount); if (dummy.length() < 2) { dummy = DUMMY_STRING; } Dimension d = new Dimension(); d.width = metrics.stringWidth(dummy) + 16; d.height = lineCount * h + 100; setPreferredSize(d); setSize(d); } public void paintComponent(Graphics g) { JTextArea textArea = view.getTextArea(); Font font = textArea.getFont(); g.setFont(font); FontMetrics metrics = getFontMetrics(font); Rectangle clip = g.getClipBounds(); g.setColor(getBackground()); g.fillRect(clip.x, clip.y, clip.width, clip.height); int ascent = metrics.getMaxAscent(); int h = metrics.getHeight(); int lineCount = textArea.getLineCount() + 1; int startLine = clip.y / h; int endLine = (clip.y + clip.height) / h + 1; int width = getWidth(); if (endLine > lineCount) { endLine = lineCount; } for (int i = startLine; i < endLine; i++) { String text; text = Integer.toString(i + 1) + " "; int w = metrics.stringWidth(text); int y = i * h; g.setColor(Color.blue); g.drawString(text, 0, y + ascent); int x = width - ascent; // if currentPosition is not -1 then a red arrow will be drawn if (i == view.getCurrentPosition()) { drawArrow(g, ascent, x, y); } } } private void drawArrow(Graphics g, int ascent, int x, int y) { Polygon arrow = new Polygon(); int dx = x; y += ascent - 10; int dy = y; arrow.addPoint(dx, dy + 3); arrow.addPoint(dx + 5, dy + 3); for (x = dx + 5; x <= dx + 10; x++, y++) { arrow.addPoint(x, y); } for (x = dx + 9; x >= dx + 5; x--, y++) { arrow.addPoint(x, y); } arrow.addPoint(dx + 5, dy + 7); arrow.addPoint(dx, dy + 7); g.setColor(Color.red); g.fillPolygon(arrow); g.setColor(Color.black); g.drawPolygon(arrow); } }