blob: b9be0ea0b89da8b04fb121c82c3b2759b0efa867 [file] [log] [blame]
/*
* 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 groovy.ui;
import groovy.ui.text.GroovyFilter;
import groovy.ui.text.StructuredSyntaxResources;
import groovy.ui.text.TextEditor;
import groovy.ui.text.TextUndoManager;
import org.codehaus.groovy.runtime.StringGroovyMethods;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Document;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.print.PrinterJob;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.prefs.Preferences;
/**
* Component which provides a styled editor for the console.
*
* @author hippy
* @author Danno Ferrin
* @author Tim Yates
* @author Guillaume Laforge
*/
public class ConsoleTextEditor extends JScrollPane {
public String getDefaultFamily() {
return defaultFamily;
}
public void setDefaultFamily(String defaultFamily) {
this.defaultFamily = defaultFamily;
}
private class LineNumbersPanel extends JPanel {
public LineNumbersPanel() {
int initialSize = 3 * Preferences.userNodeForPackage(Console.class).getInt("fontSize", 12);
setMinimumSize(new Dimension(initialSize, initialSize));
setPreferredSize(new Dimension(initialSize, initialSize));
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// starting position in document
int start = textEditor.viewToModel(getViewport().getViewPosition());
// end position in document
int end = textEditor.viewToModel(new Point(10,
getViewport().getViewPosition().y +
(int) textEditor.getVisibleRect().getHeight())
);
// translate offsets to lines
Document doc = textEditor.getDocument();
int startline = doc.getDefaultRootElement().getElementIndex(start) + 1;
int endline = doc.getDefaultRootElement().getElementIndex(end) + 1;
Font f = textEditor.getFont();
int fontHeight = g.getFontMetrics(f).getHeight();
int fontDesc = g.getFontMetrics(f).getDescent();
int startingY = -1;
try {
startingY = textEditor.modelToView(start).y + fontHeight - fontDesc;
} catch (BadLocationException e1) {
System.err.println(e1.getMessage());
}
g.setFont(f);
for (int line = startline, y = startingY; line <= endline; y += fontHeight, line++) {
String lineNumber = StringGroovyMethods.padLeft(Integer.toString(line), 4, " ");
g.drawString(lineNumber, 0, y);
}
}
}
private String defaultFamily = "Monospaced";
private static final PrinterJob PRINTER_JOB = PrinterJob.getPrinterJob();
private LineNumbersPanel numbersPanel = new LineNumbersPanel();
private boolean documentChangedSinceLastRepaint = false;
private TextEditor textEditor = new TextEditor(true, true, true) {
public void paintComponent(Graphics g) {
super.paintComponent(g);
// only repaint the line numbers in the gutter when the document has changed
// in case lines (hence line numbers) have been added or removed from the document
if (documentChangedSinceLastRepaint) {
numbersPanel.repaint();
documentChangedSinceLastRepaint = false;
}
}
};
private UndoAction undoAction = new UndoAction();
private RedoAction redoAction = new RedoAction();
private PrintAction printAction = new PrintAction();
private boolean editable = true;
private TextUndoManager undoManager;
/**
* Creates a new instance of ConsoleTextEditor
*/
public ConsoleTextEditor() {
textEditor.setFont(new Font(defaultFamily, Font.PLAIN,
Preferences.userNodeForPackage(Console.class).getInt("fontSize", 12)));
setViewportView(new JPanel(new BorderLayout()) {{
add(numbersPanel, BorderLayout.WEST);
add(textEditor, BorderLayout.CENTER);
}});
textEditor.setDragEnabled(editable);
getVerticalScrollBar().setUnitIncrement(10);
initActions();
DefaultStyledDocument doc = new DefaultStyledDocument();
doc.setDocumentFilter(new GroovyFilter(doc));
textEditor.setDocument(doc);
// add a document listener, to hint whether the line number gutter has to be repainted
// when the number of lines changes
doc.addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent documentEvent) {
documentChangedSinceLastRepaint = true;
}
public void removeUpdate(DocumentEvent documentEvent) {
documentChangedSinceLastRepaint = true;
}
public void changedUpdate(DocumentEvent documentEvent) {
documentChangedSinceLastRepaint = true;
int width = 3 * Preferences.userNodeForPackage(Console.class).getInt("fontSize", 12);
numbersPanel.setPreferredSize(new Dimension(width, width));
}
});
// create and add the undo/redo manager
this.undoManager = new TextUndoManager();
doc.addUndoableEditListener(undoManager);
// add the undo actions
undoManager.addPropertyChangeListener(undoAction);
undoManager.addPropertyChangeListener(redoAction);
doc.addDocumentListener(undoAction);
doc.addDocumentListener(redoAction);
InputMap im = textEditor.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_MASK, false);
im.put(ks, StructuredSyntaxResources.UNDO);
ActionMap am = textEditor.getActionMap();
am.put(StructuredSyntaxResources.UNDO, undoAction);
ks = KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_MASK, false);
im.put(ks, StructuredSyntaxResources.REDO);
am.put(StructuredSyntaxResources.REDO, redoAction);
ks = KeyStroke.getKeyStroke(KeyEvent.VK_P, InputEvent.CTRL_MASK, false);
im.put(ks, StructuredSyntaxResources.PRINT);
am.put(StructuredSyntaxResources.PRINT, printAction);
}
public void setShowLineNumbers(boolean showLineNumbers) {
if (showLineNumbers) {
setViewportView(new JPanel(new BorderLayout()) {{
add(numbersPanel, BorderLayout.WEST);
add(textEditor, BorderLayout.CENTER);
}});
} else {
setViewportView(textEditor);
}
}
public void setEditable(boolean editable) {
textEditor.setEditable(editable);
}
public boolean clipBoardAvailable() {
Transferable t = StructuredSyntaxResources.SYSTEM_CLIPBOARD.getContents(this);
return t.isDataFlavorSupported(DataFlavor.stringFlavor);
}
public TextEditor getTextEditor() {
return textEditor;
}
protected void initActions() {
ActionMap map = getActionMap();
PrintAction printAction = new PrintAction();
map.put(StructuredSyntaxResources.PRINT, printAction);
}
private class PrintAction extends AbstractAction {
public PrintAction() {
setEnabled(true);
}
public void actionPerformed(ActionEvent ae) {
PRINTER_JOB.setPageable(textEditor);
try {
if (PRINTER_JOB.printDialog()) {
PRINTER_JOB.print();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
} // end ConsoleTextEditor.PrintAction
private class RedoAction extends UpdateCaretListener implements PropertyChangeListener {
public RedoAction() {
setEnabled(false);
}
public void actionPerformed(ActionEvent ae) {
undoManager.redo();
setEnabled(undoManager.canRedo());
undoAction.setEnabled(undoManager.canUndo());
super.actionPerformed(ae);
}
public void propertyChange(PropertyChangeEvent pce) {
setEnabled(undoManager.canRedo());
}
} // end ConsoleTextEditor.RedoAction
private abstract class UpdateCaretListener extends AbstractAction implements DocumentListener {
protected int lastUpdate;
public void changedUpdate(DocumentEvent de) {
}
public void insertUpdate(DocumentEvent de) {
lastUpdate = de.getOffset() + de.getLength();
}
public void removeUpdate(DocumentEvent de) {
lastUpdate = de.getOffset();
}
public void actionPerformed(ActionEvent ae) {
textEditor.setCaretPosition(lastUpdate);
}
}
private class UndoAction extends UpdateCaretListener implements PropertyChangeListener {
public UndoAction() {
setEnabled(false);
}
public void actionPerformed(ActionEvent ae) {
undoManager.undo();
setEnabled(undoManager.canUndo());
redoAction.setEnabled(undoManager.canRedo());
super.actionPerformed(ae);
}
public void propertyChange(PropertyChangeEvent pce) {
setEnabled(undoManager.canUndo());
}
}
public Action getUndoAction() {
return undoAction;
}
public Action getRedoAction() {
return redoAction;
}
public Action getPrintAction() {
return printAction;
}
}