001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 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 https://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 edu.wisc.ssec.mcidasv.McIDASV; 040import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; 041import org.fife.ui.rsyntaxtextarea.SyntaxConstants; 042import org.fife.ui.rtextarea.RTextScrollPane; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046/** 047 * A bare-bones text editor that can do relatively robust syntax highlighting 048 * of Jython code. 049 * 050 * <p>This class relies <i>very heavily</i> upon the wonderful 051 * <a href="https://github.com/bobbylight/RSyntaxTextArea">RSyntaxTextArea</a> 052 * project.</p> 053 */ 054public class JythonEditor implements UndoableEditListener { 055 056 private static final Logger logger = LoggerFactory.getLogger(JythonEditor.class); 057 058 /** Text area that contains the syntax-highlighted text. */ 059 private final McvJythonTextArea textArea; 060 061 /** Scroll pane for {@link #textArea}. */ 062 private final RTextScrollPane scrollPane; 063 064 /** Undo manager. */ 065 private final UndoManager undo; 066 067 /** 068 * Creates a new JythonEditor. 069 */ 070 public JythonEditor() { 071 textArea = new McvJythonTextArea(20, 60); 072 if (McIDASV.isDarkMode()) { 073 UIManager.applyTextTheme(getClass(), textArea, "/edu/wisc/ssec/mcidasv/resources/monokai-flatlaf.xml"); 074 } 075 076 textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PYTHON); 077 078 textArea.setCodeFoldingEnabled(true); 079 textArea.setWhitespaceVisible(true); 080 081 // hrm? 082 textArea.setBracketMatchingEnabled(true); 083 textArea.setPaintMatchedBracketPair(true); 084 085 textArea.setAnimateBracketMatching(true); 086 textArea.setPaintTabLines(true); 087 textArea.setTabsEmulated(true); 088 textArea.setTabSize(4); 089 090 scrollPane = new RTextScrollPane(textArea); 091 092 undo = new UndoManager(); 093 094 addUndoableEditListener(this); 095 } 096 097 /** 098 * Returns the text area responsible for syntax highlighting. 099 * 100 * @return Reference to {@link #textArea}. 101 */ 102 public JTextComponent getTextComponent() { 103 return textArea; 104 } 105 106 /** 107 * Returns the {@code JScrollPane} that contains {@link #textArea}. 108 * 109 * @return {@code JScrollPane} with the text area. Suitable for adding to 110 * a {@code JPanel}. 111 */ 112 public JScrollPane getScrollPane() { 113 return scrollPane; 114 } 115 116 /** 117 * Sets the text of this document to the given {@code String}. 118 * 119 * @param text New text to use in {@link #textArea}. 120 */ 121 public void setText(String text) { 122 textArea.setText(text); 123 } 124 125 /** 126 * Returns a string containing the text of {@link #textArea}. 127 * 128 * @return The current contents of the text area. 129 */ 130 public String getText() { 131 return textArea.getText(); 132 } 133 134 /** 135 * Controls whether or not changes can be made to the contents of 136 * {@link #textArea}. 137 * 138 * @param enabled {@code true} if the editor should be enabled, 139 * {@code false} otherwise. 140 */ 141 public void setEnabled(boolean enabled) { 142 textArea.setEnabled(enabled); 143 scrollPane.getGutter().setEnabled(enabled); 144 scrollPane.getGutter().setBackground(textArea.getBackground()); 145 } 146 147 /** 148 * Returns the component (aka {@literal "the gutter"} that contains 149 * optional information like line numbers. 150 * 151 * @return {@code JPanel} that contains the line numbers. 152 */ 153 public JPanel getLineNumberComponent() { 154 return scrollPane.getGutter(); 155 } 156 157 /** 158 * Copies selected text into system clipboard. 159 */ 160 public void copy() { 161 textArea.copy(); 162 } 163 164 /** 165 * Whether or not changes can be made to {@link #textArea}. 166 * 167 * @return {@code true} if changes are allowed, {@code false} otherwise. 168 */ 169 public boolean isEnabled() { 170 return textArea.isEnabled(); 171 } 172 173 /** 174 * Insert the given text at the caret. 175 * 176 * @param textToInsert Text to insert. 177 */ 178 public void insertText(String textToInsert) { 179 int pos = textArea.getCaretPosition(); 180 String t = textArea.getText(); 181 t = t.substring(0, pos) + textToInsert + t.substring(pos); 182 textArea.setText(t); 183 textArea.setCaretPosition(pos + textToInsert.length()); 184 } 185 186 /** 187 * Handles undoable edits 188 * 189 * @param e Event that represents the undoable edit. 190 */ 191 @Override public void undoableEditHappened(UndoableEditEvent e) { 192 if (e.getEdit().isSignificant()) { 193 undo.addEdit(e.getEdit()); 194 } 195 } 196 197 /** 198 * Adds the given undoable edit listener to {@link #textArea}. 199 * 200 * @param l Listener to add. 201 */ 202 public void addUndoableEditListener(UndoableEditListener l) { 203 textArea.getDocument().addUndoableEditListener(l); 204 } 205 206 /** 207 * Remove the given undoable edit listener from {@link #textArea}. 208 * 209 * @param l Listener to remove. 210 */ 211 public void removeUndoableEditListener(UndoableEditListener l) { 212 textArea.getDocument().removeUndoableEditListener(l); 213 } 214 215 /** 216 * Returns the default {@link JPopupMenu} created by 217 * {@link RSyntaxTextArea#createPopupMenu()}. 218 * 219 * @return Popup menu. 220 */ 221 public JPopupMenu createPopupMenu() { 222 return textArea.makePopupMenu(); 223 } 224 225 public static class McvJythonTextArea extends RSyntaxTextArea { 226 227 McvJythonTextArea(int rows, int columns) { 228 super(rows, columns); 229 } 230 231 @Override protected JPopupMenu createPopupMenu() { 232 // this is needed so that the popup is disabled by default, which 233 // allows JythonManager's addMouseListener stuff to work a bit 234 // better. 235 return null; 236 } 237 238 public JPopupMenu makePopupMenu() { 239 // this method is mostly for getting around the fact that 240 // createPopupMenu is protected 241 return super.createPopupMenu(); 242 } 243 } 244}