/* * 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 javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.KeyStroke; 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.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.awt.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.prefs.Preferences; import org.codehaus.groovy.runtime.StringGroovyMethods; /** * 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; } }