001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2016
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 *
010 * All Rights Reserved
011 *
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.
014 *
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 *
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 *
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.ui;
030
031import javax.swing.JPanel;
032import javax.swing.JPopupMenu;
033import javax.swing.JScrollPane;
034import javax.swing.event.UndoableEditEvent;
035import javax.swing.event.UndoableEditListener;
036import javax.swing.text.JTextComponent;
037import javax.swing.undo.UndoManager;
038
039import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
040import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
041import org.fife.ui.rtextarea.RTextScrollPane;
042
043/**
044 * A bare-bones text editor that can do relatively robust syntax highlighting
045 * of Jython code.
046 *
047 * <p>This class relies <i>very heavily</i> upon the wonderful
048 * <a href="https://github.com/bobbylight/RSyntaxTextArea">RSyntaxTextArea</a>
049 * project.</p>
050 */
051public class JythonEditor implements UndoableEditListener {
052
053    /** Text area that contains the syntax-highlighted text. */
054    private final McvJythonTextArea textArea;
055
056    /** Scroll pane for {@link #textArea}. */
057    private final RTextScrollPane scrollPane;
058
059    /** Undo manager. */
060    private final UndoManager undo;
061
062    /**
063     * Creates a new JythonEditor.
064     */
065    public JythonEditor() {
066        textArea = new McvJythonTextArea(20, 60);
067
068        textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PYTHON);
069
070        textArea.setCodeFoldingEnabled(true);
071        textArea.setWhitespaceVisible(true);
072
073        // hrm?
074        textArea.setBracketMatchingEnabled(true);
075        textArea.setPaintMatchedBracketPair(true);
076
077        textArea.setAnimateBracketMatching(true);
078        textArea.setPaintTabLines(true);
079        textArea.setTabsEmulated(true);
080        textArea.setTabSize(4);
081
082        scrollPane = new RTextScrollPane(textArea);
083
084        undo = new UndoManager();
085
086        addUndoableEditListener(this);
087    }
088
089    /**
090     * Returns the text area responsible for syntax highlighting.
091     *
092     * @return Reference to {@link #textArea}.
093     */
094    public JTextComponent getTextComponent() {
095        return textArea;
096    }
097
098    /**
099     * Returns the {@code JScrollPane} that contains {@link #textArea}.
100     *
101     * @return {@code JScrollPane} with the text area. Suitable for adding to
102     * a {@code JPanel}.
103     */
104    public JScrollPane getScrollPane() {
105        return scrollPane;
106    }
107
108    /**
109     * Sets the text of this document to the given {@code String}.
110     *
111     * @param text New text to use in {@link #textArea}.
112     */
113    public void setText(String text) {
114        textArea.setText(text);
115    }
116
117    /**
118     * Returns a string containing the text of {@link #textArea}.
119     *
120     * @return The current contents of the text area.
121     */
122    public String getText() {
123        return textArea.getText();
124    }
125
126    /**
127     * Controls whether or not changes can be made to the contents of
128     * {@link #textArea}.
129     *
130     * @param enabled {@code true} if the editor should be enabled,
131     *                {@code false} otherwise.
132     */
133    public void setEnabled(boolean enabled) {
134        textArea.setEnabled(enabled);
135        scrollPane.getGutter().setEnabled(enabled);
136        scrollPane.getGutter().setBackground(textArea.getBackground());
137    }
138
139    /**
140     * Returns the component (aka {@literal "the gutter"} that contains
141     * optional information like line numbers.
142     *
143     * @return {@code JPanel} that contains the line numbers.
144     */
145    public JPanel getLineNumberComponent() {
146        return scrollPane.getGutter();
147    }
148
149    /**
150     * Copies selected text into system clipboard.
151     */
152    public void copy() {
153        textArea.copy();
154    }
155
156    /**
157     * Whether or not changes can be made to {@link #textArea}.
158     *
159     * @return {@code true} if changes are allowed, {@code false} otherwise.
160     */
161    public boolean isEnabled() {
162        return textArea.isEnabled();
163    }
164
165    /**
166     * Insert the given text at the caret.
167     *
168     * @param textToInsert Text to insert.
169     */
170    public void insertText(String textToInsert) {
171        int pos = textArea.getCaretPosition();
172        String t = textArea.getText();
173        t = t.substring(0, pos) + textToInsert + t.substring(pos);
174        textArea.setText(t);
175        textArea.setCaretPosition(pos + textToInsert.length());
176    }
177
178    /**
179     * Handles undoable edits
180     *
181     * @param e Event that represents the undoable edit.
182     */
183    @Override public void undoableEditHappened(UndoableEditEvent e) {
184        if (e.getEdit().isSignificant()) {
185            undo.addEdit(e.getEdit());
186        }
187    }
188
189    /**
190     * Adds the given undoable edit listener to {@link #textArea}.
191     *
192     * @param l Listener to add.
193     */
194    public void addUndoableEditListener(UndoableEditListener l) {
195        textArea.getDocument().addUndoableEditListener(l);
196    }
197
198    /**
199     * Remove the given undoable edit listener from {@link #textArea}.
200     *
201     * @param l Listener to remove.
202     */
203    public void removeUndoableEditListener(UndoableEditListener l) {
204        textArea.getDocument().removeUndoableEditListener(l);
205    }
206
207    /**
208     * Returns the default {@link JPopupMenu} created by
209     * {@link RSyntaxTextArea#createPopupMenu()}.
210     *
211     * @return Popup menu.
212     */
213    public JPopupMenu createPopupMenu() {
214        return textArea.makePopupMenu();
215    }
216
217    public static class McvJythonTextArea extends RSyntaxTextArea {
218
219        McvJythonTextArea(int rows, int columns) {
220            super(rows, columns);
221        }
222
223        @Override protected JPopupMenu createPopupMenu() {
224            // this is needed so that the popup is disabled by default, which
225            // allows JythonManager's addMouseListener stuff to work a bit
226            // better.
227            return null;
228        }
229
230        public JPopupMenu makePopupMenu() {
231            // this method is mostly for getting around the fact that
232            // createPopupMenu is protected
233            return super.createPopupMenu();
234        }
235    }
236}