diff options
Diffstat (limited to '')
-rw-r--r-- | scripting/java/com/sun/star/script/framework/provider/beanshell/PlainSourceView.java | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/scripting/java/com/sun/star/script/framework/provider/beanshell/PlainSourceView.java b/scripting/java/com/sun/star/script/framework/provider/beanshell/PlainSourceView.java new file mode 100644 index 000000000..e519587ff --- /dev/null +++ b/scripting/java/com/sun/star/script/framework/provider/beanshell/PlainSourceView.java @@ -0,0 +1,395 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package com.sun.star.script.framework.provider.beanshell; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; + +import javax.swing.AbstractAction; +import javax.swing.JComponent; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.KeyStroke; +import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.UndoableEditEvent; +import javax.swing.event.UndoableEditListener; +import javax.swing.text.BadLocationException; +import javax.swing.undo.CompoundEdit; +import javax.swing.undo.UndoManager; +import java.util.List; +import java.util.ArrayList; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PlainSourceView extends JScrollPane implements + ScriptSourceView, DocumentListener { + + private final ScriptSourceModel model; + private JTextArea ta; + private GlyphGutter gg; + private int linecount; + private boolean isModified = false; + private static final String undoKey = "Undo"; + private static final String redoKey = "Redo"; + private CompoundEdit compoundEdit = null; + private static final int noLimit = -1; + UndoManager undoManager; + private List<UnsavedChangesListener> unsavedListener = new ArrayList<UnsavedChangesListener>(); + + private static final Pattern tabPattern = Pattern.compile("^ *(\\t)"); + private static final Pattern indentationPattern = Pattern.compile("^([^\\S\\r\\n]*)(([^\\{])*\\{\\s*)*"); + + public PlainSourceView(ScriptSourceModel model) { + this.model = model; + initUI(); + model.setView(this); + } + + public void undo(){ + if(compoundEdit!=null){ + compoundEdit.end(); + undoManager.addEdit(compoundEdit); + compoundEdit = null; + } + if(undoManager.canUndo()){ + undoManager.undo(); + } + // check if it's the last undoable change + if(undoManager.canUndo() == false){ + setModified(false); + } + } + public void redo(){ + if(undoManager.canRedo()){ + undoManager.redo(); + } + } + 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) { + int pos = ta.getCaretPosition(); + ta.setText(model.getText()); + + try { + ta.setCaretPosition(pos); + } catch (IllegalArgumentException iae) { + // do nothing and allow JTextArea to set its own position + } + } + + // scroll to currentPosition of the model + try { + int line = ta.getLineStartOffset(model.getCurrentPosition()); + Rectangle rect = ta.modelToView(line); + if (rect != null) { + ta.scrollRectToVisible(rect); + } + } catch (BadLocationException e) { + // couldn't scroll to line, do nothing + } + + gg.repaint(); + + // Add back the listener + ta.getDocument().addDocumentListener(this); + } + + public boolean isModified() { + return isModified; + } + + private void notifyListeners (boolean isUnsaved) { + for (UnsavedChangesListener listener : unsavedListener) { + listener.onUnsavedChanges(isUnsaved); + } + } + + public void setModified(boolean value) { + if(value != isModified) { + notifyListeners(value); + isModified = value; + } + } + + private void initUI() { + try{ + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } + catch(Exception e){ + // What to do here + } + ta = new JTextArea(); + ta.setTabSize(4); + ta.setRows(15); + ta.setColumns(40); + ta.setLineWrap(false); + ta.insert(model.getText(), 0); + ta.setFont(new Font("Monospaced", ta.getFont().getStyle(), ta.getFont().getSize())); + undoManager = new UndoManager(); + undoManager.setLimit(noLimit); + ta.getDocument().addUndoableEditListener(new UndoableEditListener(){ + @Override + public void undoableEditHappened(UndoableEditEvent editEvent) { + if(compoundEdit == null){ + compoundEdit = new CompoundEdit(); + } + compoundEdit.addEdit(editEvent.getEdit()); + } + }); + + ta.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_MASK), undoKey); + ta.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_MASK), redoKey); + + ta.addKeyListener(new KeyAdapter(){ + @Override + public void keyPressed(KeyEvent ke) { + // if shift + tab was pressed, remove the first tab before any code begins + if (ke.isShiftDown() && ke.getKeyCode() == KeyEvent.VK_TAB) { + try { + int caretOffset = ta.getCaretPosition(); + int lineOffset = ta.getLineOfOffset(caretOffset); + int startOffset = ta.getLineStartOffset(lineOffset); + int endOffset = ta.getLineEndOffset(lineOffset); + + Matcher matcher = tabPattern.matcher(ta.getText(startOffset, endOffset - startOffset)); + if (matcher.find()) { + ta.replaceRange(null, startOffset + matcher.start(1), startOffset + matcher.end(1)); + } + } catch (BadLocationException e) { + // could not find correct location of the tab + } + } + // if the enter key was pressed, adjust indentation of the current line accordingly + if (ke.getKeyCode() == KeyEvent.VK_ENTER) { + try { + int caretOffset = ta.getCaretPosition(); + int lineOffset = ta.getLineOfOffset(caretOffset); + int startOffset = ta.getLineStartOffset(lineOffset); + int endOffset = ta.getLineEndOffset(lineOffset); + + Matcher matcher = indentationPattern.matcher(ta.getText(startOffset, endOffset - startOffset)); + // insert new line including indentation of the previous line + ta.insert("\n", caretOffset++); + if (matcher.find()) { + if (matcher.group(1).length() > 0) { + ta.insert(matcher.group(1), caretOffset++); + } + // if there is an open curly bracket in the current line, increase indentation level + if (matcher.group(3) != null) { + ta.insert("\t", caretOffset); + } + } + ke.consume(); + } catch (BadLocationException e) { + // could not find correct location of the indentation + } + } + } + + @Override + public void keyReleased(KeyEvent ke){ + if(ke.getKeyCode() == KeyEvent.VK_SPACE || ke.getKeyCode() == KeyEvent.VK_ENTER){ + compoundEdit.end(); + undoManager.addEdit(compoundEdit); + compoundEdit = null; + } + } + }); + + ta.getActionMap().put(undoKey, new AbstractAction(undoKey){ + @Override + public void actionPerformed(ActionEvent event) { + undo(); + } + }); + + ta.getActionMap().put(redoKey, new AbstractAction(redoKey){ + @Override + public void actionPerformed(ActionEvent event) { + redo(); + } + }); + + 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(); + } + + public void removeUpdate(DocumentEvent e) { + doChanged(); + } + + public void changedUpdate(DocumentEvent e) { + doChanged(); + } + + /* If the number of lines in the JTextArea has changed then update the + GlyphGutter */ + private void doChanged() { + setModified(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(); + } + + public void addListener(UnsavedChangesListener toAdd) { + unsavedListener.add(toAdd); + } +} + +class GlyphGutter extends JComponent { + + private final PlainSourceView view; + private static 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); + } + + @Override + 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 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); + } +} + |