2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
32 import java
.io
.IOException
;
33 import java
.text
.MessageFormat
;
34 import java
.util
.ResourceBundle
;
35 import java
.util
.Scanner
;
37 import jexer
.TApplication
;
38 import jexer
.TEditorWidget
;
39 import jexer
.THScroller
;
40 import jexer
.TScrollableWindow
;
41 import jexer
.TVScroller
;
43 import jexer
.bits
.CellAttributes
;
44 import jexer
.bits
.GraphicsChars
;
45 import jexer
.event
.TCommandEvent
;
46 import jexer
.event
.TKeypressEvent
;
47 import jexer
.event
.TMouseEvent
;
48 import jexer
.event
.TResizeEvent
;
49 import static jexer
.TCommand
.*;
50 import static jexer
.TKeypress
.*;
53 * TEditorWindow is a basic text file editor.
55 public class TEditorWindow
extends TScrollableWindow
{
60 private static final ResourceBundle i18n
= ResourceBundle
.getBundle(TEditorWindow
.class.getName());
62 // ------------------------------------------------------------------------
63 // Variables --------------------------------------------------------------
64 // ------------------------------------------------------------------------
67 * Hang onto my TEditor so I can resize it with the window.
69 private TEditorWidget editField
;
72 * The fully-qualified name of the file being edited.
74 private String filename
= "";
77 * If true, hide the mouse after typing a keystroke.
79 private boolean hideMouseWhenTyping
= true;
82 * If true, the mouse should not be displayed because a keystroke was
85 private boolean typingHidMouse
= false;
87 // ------------------------------------------------------------------------
88 // Constructors -----------------------------------------------------------
89 // ------------------------------------------------------------------------
92 * Public constructor sets window title.
94 * @param parent the main application
95 * @param title the window title
97 public TEditorWindow(final TApplication parent
, final String title
) {
99 super(parent
, title
, 0, 0, parent
.getScreen().getWidth(),
100 parent
.getDesktopBottom() - parent
.getDesktopTop(), RESIZABLE
);
102 editField
= addEditor("", 0, 0, getWidth() - 2, getHeight() - 2);
107 * Public constructor sets window title and contents.
109 * @param parent the main application
110 * @param title the window title, usually a filename
111 * @param contents the data for the editing window, usually the file data
113 public TEditorWindow(final TApplication parent
, final String title
,
114 final String contents
) {
116 super(parent
, title
, 0, 0, parent
.getScreen().getWidth(),
117 parent
.getDesktopBottom() - parent
.getDesktopTop(), RESIZABLE
);
120 editField
= addEditor(contents
, 0, 0, getWidth() - 2, getHeight() - 2);
125 * Public constructor opens a file.
127 * @param parent the main application
128 * @param file the file to open
129 * @throws IOException if a java.io operation throws
131 public TEditorWindow(final TApplication parent
,
132 final File file
) throws IOException
{
134 super(parent
, file
.getName(), 0, 0, parent
.getScreen().getWidth(),
135 parent
.getDesktopBottom() - parent
.getDesktopTop(), RESIZABLE
);
137 filename
= file
.getName();
138 String contents
= readFileData(file
);
139 editField
= addEditor(contents
, 0, 0, getWidth() - 2, getHeight() - 2);
144 * Public constructor.
146 * @param parent the main application
148 public TEditorWindow(final TApplication parent
) {
149 this(parent
, i18n
.getString("newTextDocument"));
152 // ------------------------------------------------------------------------
153 // TWindow ----------------------------------------------------------------
154 // ------------------------------------------------------------------------
164 // Add the row:col on the bottom row
165 CellAttributes borderColor
= getBorder();
166 String location
= String
.format(" %d:%d ",
167 editField
.getEditingRowNumber(),
168 editField
.getEditingColumnNumber());
169 int colon
= location
.indexOf(':');
170 putStringXY(10 - colon
, getHeight() - 1, location
, borderColor
);
172 if (editField
.isDirty()) {
173 putCharXY(2, getHeight() - 1, GraphicsChars
.OCTOSTAR
, borderColor
);
178 * Handle mouse press events.
180 * @param mouse mouse button press event
183 public void onMouseDown(final TMouseEvent mouse
) {
184 // Use TWidget's code to pass the event to the children.
185 super.onMouseDown(mouse
);
187 if (hideMouseWhenTyping
) {
188 typingHidMouse
= false;
191 if (mouseOnEditor(mouse
)) {
192 // The editor might have changed, update the scollbars.
193 setBottomValue(editField
.getMaximumRowNumber());
194 setVerticalValue(editField
.getVisibleRowNumber());
195 setRightValue(editField
.getMaximumColumnNumber());
196 setHorizontalValue(editField
.getEditingColumnNumber());
198 if (mouse
.isMouseWheelUp() || mouse
.isMouseWheelDown()) {
199 // Vertical scrollbar actions
200 editField
.setVisibleRowNumber(getVerticalValue());
206 * Handle mouse release events.
208 * @param mouse mouse button release event
211 public void onMouseUp(final TMouseEvent mouse
) {
212 // Use TWidget's code to pass the event to the children.
213 super.onMouseUp(mouse
);
215 if (hideMouseWhenTyping
) {
216 typingHidMouse
= false;
219 if (mouse
.isMouse1() && mouseOnVerticalScroller(mouse
)) {
220 // Clicked on vertical scrollbar
221 editField
.setVisibleRowNumber(getVerticalValue());
223 if (mouse
.isMouse1() && mouseOnHorizontalScroller(mouse
)) {
224 // Clicked on horizontal scrollbar
225 editField
.setVisibleColumnNumber(getHorizontalValue());
226 setHorizontalValue(editField
.getVisibleColumnNumber());
231 * Method that subclasses can override to handle mouse movements.
233 * @param mouse mouse motion event
236 public void onMouseMotion(final TMouseEvent mouse
) {
237 // Use TWidget's code to pass the event to the children.
238 super.onMouseMotion(mouse
);
240 if (hideMouseWhenTyping
) {
241 typingHidMouse
= false;
244 if (mouseOnEditor(mouse
) && mouse
.isMouse1()) {
245 // The editor might have changed, update the scollbars.
246 setBottomValue(editField
.getMaximumRowNumber());
247 setVerticalValue(editField
.getVisibleRowNumber());
248 setRightValue(editField
.getMaximumColumnNumber());
249 setHorizontalValue(editField
.getEditingColumnNumber());
251 if (mouse
.isMouse1() && mouseOnVerticalScroller(mouse
)) {
252 // Clicked/dragged on vertical scrollbar
253 editField
.setVisibleRowNumber(getVerticalValue());
255 if (mouse
.isMouse1() && mouseOnHorizontalScroller(mouse
)) {
256 // Clicked/dragged on horizontal scrollbar
257 editField
.setVisibleColumnNumber(getHorizontalValue());
258 setHorizontalValue(editField
.getVisibleColumnNumber());
267 * @param keypress keystroke event
270 public void onKeypress(final TKeypressEvent keypress
) {
271 if (hideMouseWhenTyping
) {
272 typingHidMouse
= true;
275 // Use TWidget's code to pass the event to the children.
276 super.onKeypress(keypress
);
278 // The editor might have changed, update the scollbars.
279 setBottomValue(editField
.getMaximumRowNumber());
280 setVerticalValue(editField
.getVisibleRowNumber());
281 setRightValue(editField
.getMaximumColumnNumber());
282 setHorizontalValue(editField
.getEditingColumnNumber());
286 * Handle window/screen resize events.
288 * @param event resize event
291 public void onResize(final TResizeEvent event
) {
292 if (event
.getType() == TResizeEvent
.Type
.WIDGET
) {
293 // Resize the text field
294 TResizeEvent editSize
= new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
295 event
.getWidth() - 2, event
.getHeight() - 2);
296 editField
.onResize(editSize
);
298 // Have TScrollableWindow handle the scrollbars
299 super.onResize(event
);
303 // Pass to children instead
304 for (TWidget widget
: getChildren()) {
305 widget
.onResize(event
);
310 * Method that subclasses can override to handle posted command events.
312 * @param command command event
315 public void onCommand(final TCommandEvent command
) {
316 if (command
.equals(cmOpen
)) {
318 String filename
= fileOpenBox(".");
319 if (filename
!= null) {
321 String contents
= readFileData(filename
);
322 new TEditorWindow(getApplication(), filename
, contents
);
323 } catch (IOException e
) {
324 messageBox(i18n
.getString("errorDialogTitle"),
325 MessageFormat
.format(i18n
.
326 getString("errorReadingFile"), e
.getMessage()));
329 } catch (IOException e
) {
330 messageBox(i18n
.getString("errorDialogTitle"),
331 MessageFormat
.format(i18n
.
332 getString("errorOpeningFileDialog"), e
.getMessage()));
337 if (command
.equals(cmSave
)) {
338 if (filename
.length() > 0) {
340 editField
.saveToFilename(filename
);
341 } catch (IOException e
) {
342 messageBox(i18n
.getString("errorDialogTitle"),
343 MessageFormat
.format(i18n
.
344 getString("errorSavingFile"), e
.getMessage()));
350 // Didn't handle it, let children get it instead
351 super.onCommand(command
);
355 * Returns true if this window does not want the application-wide mouse
356 * cursor drawn over it.
358 * @return true if this window does not want the application-wide mouse
359 * cursor drawn over it
362 public boolean hasHiddenMouse() {
363 return (super.hasHiddenMouse() || typingHidMouse
);
366 // ------------------------------------------------------------------------
367 // TEditorWindow ----------------------------------------------------------
368 // ------------------------------------------------------------------------
371 * Setup other fields after the editor is created.
373 private void setupAfterEditor() {
374 hScroller
= new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
375 vScroller
= new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
376 setMinimumWindowWidth(25);
377 setMinimumWindowHeight(10);
379 setBottomValue(editField
.getMaximumRowNumber());
381 setRightValue(editField
.getMaximumColumnNumber());
383 statusBar
= newStatusBar(i18n
.getString("statusBar"));
384 statusBar
.addShortcutKeypress(kbF1
, cmHelp
,
385 i18n
.getString("statusBarHelp"));
386 statusBar
.addShortcutKeypress(kbF2
, cmSave
,
387 i18n
.getString("statusBarSave"));
388 statusBar
.addShortcutKeypress(kbF3
, cmOpen
,
389 i18n
.getString("statusBarOpen"));
390 statusBar
.addShortcutKeypress(kbF10
, cmMenu
,
391 i18n
.getString("statusBarMenu"));
393 // Hide mouse when typing option
394 if (System
.getProperty("jexer.TEditor.hideMouseWhenTyping",
395 "true").equals("false")) {
397 hideMouseWhenTyping
= false;
402 * Read file data into a string.
404 * @param file the file to open
405 * @return the file contents
406 * @throws IOException if a java.io operation throws
408 private String
readFileData(final File file
) throws IOException
{
409 StringBuilder fileContents
= new StringBuilder();
410 Scanner scanner
= new Scanner(file
);
411 String EOL
= System
.getProperty("line.separator");
414 while (scanner
.hasNextLine()) {
415 fileContents
.append(scanner
.nextLine() + EOL
);
417 return fileContents
.toString();
424 * Read file data into a string.
426 * @param filename the file to open
427 * @return the file contents
428 * @throws IOException if a java.io operation throws
430 private String
readFileData(final String filename
) throws IOException
{
431 return readFileData(new File(filename
));
435 * Check if a mouse press/release/motion event coordinate is over the
438 * @param mouse a mouse-based event
439 * @return whether or not the mouse is on the editor
441 private boolean mouseOnEditor(final TMouseEvent mouse
) {
442 if ((mouse
.getAbsoluteX() >= getAbsoluteX() + 1)
443 && (mouse
.getAbsoluteX() < getAbsoluteX() + getWidth() - 1)
444 && (mouse
.getAbsoluteY() >= getAbsoluteY() + 1)
445 && (mouse
.getAbsoluteY() < getAbsoluteY() + getHeight() - 1)