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
.TMenuEvent
;
48 import jexer
.event
.TMouseEvent
;
49 import jexer
.event
.TResizeEvent
;
50 import jexer
.menu
.TMenu
;
51 import static jexer
.TCommand
.*;
52 import static jexer
.TKeypress
.*;
55 * TEditorWindow is a basic text file editor.
57 public class TEditorWindow
extends TScrollableWindow
{
62 private static final ResourceBundle i18n
= ResourceBundle
.getBundle(TEditorWindow
.class.getName());
64 // ------------------------------------------------------------------------
65 // Variables --------------------------------------------------------------
66 // ------------------------------------------------------------------------
69 * Hang onto my TEditor so I can resize it with the window.
71 private TEditorWidget editField
;
74 * The fully-qualified name of the file being edited.
76 private String filename
= "";
79 * If true, hide the mouse after typing a keystroke.
81 private boolean hideMouseWhenTyping
= true;
84 * If true, the mouse should not be displayed because a keystroke was
87 private boolean typingHidMouse
= false;
89 // ------------------------------------------------------------------------
90 // Constructors -----------------------------------------------------------
91 // ------------------------------------------------------------------------
94 * Public constructor sets window title.
96 * @param parent the main application
97 * @param title the window title
99 public TEditorWindow(final TApplication parent
, final String title
) {
101 super(parent
, title
, 0, 0, parent
.getScreen().getWidth(),
102 parent
.getDesktopBottom() - parent
.getDesktopTop(), RESIZABLE
);
104 editField
= addEditor("", 0, 0, getWidth() - 2, getHeight() - 2);
109 * Public constructor sets window title and contents.
111 * @param parent the main application
112 * @param title the window title, usually a filename
113 * @param contents the data for the editing window, usually the file data
115 public TEditorWindow(final TApplication parent
, final String title
,
116 final String contents
) {
118 super(parent
, title
, 0, 0, parent
.getScreen().getWidth(),
119 parent
.getDesktopBottom() - parent
.getDesktopTop(), RESIZABLE
);
122 editField
= addEditor(contents
, 0, 0, getWidth() - 2, getHeight() - 2);
127 * Public constructor opens a file.
129 * @param parent the main application
130 * @param file the file to open
131 * @throws IOException if a java.io operation throws
133 public TEditorWindow(final TApplication parent
,
134 final File file
) throws IOException
{
136 super(parent
, file
.getName(), 0, 0, parent
.getScreen().getWidth(),
137 parent
.getDesktopBottom() - parent
.getDesktopTop(), RESIZABLE
);
139 filename
= file
.getName();
140 String contents
= readFileData(file
);
141 editField
= addEditor(contents
, 0, 0, getWidth() - 2, getHeight() - 2);
146 * Public constructor.
148 * @param parent the main application
150 public TEditorWindow(final TApplication parent
) {
151 this(parent
, i18n
.getString("newTextDocument"));
154 // ------------------------------------------------------------------------
155 // Event handlers ---------------------------------------------------------
156 // ------------------------------------------------------------------------
159 * Called by application.switchWindow() when this window gets the
160 * focus, and also by application.addWindow().
162 public void onFocus() {
164 getApplication().enableMenuItem(TMenu
.MID_UNDO
);
165 getApplication().enableMenuItem(TMenu
.MID_REDO
);
169 * Called by application.switchWindow() when another window gets the
172 public void onUnfocus() {
174 getApplication().disableMenuItem(TMenu
.MID_UNDO
);
175 getApplication().disableMenuItem(TMenu
.MID_REDO
);
179 * Handle mouse press events.
181 * @param mouse mouse button press event
184 public void onMouseDown(final TMouseEvent mouse
) {
185 // Use TWidget's code to pass the event to the children.
186 super.onMouseDown(mouse
);
188 if (hideMouseWhenTyping
) {
189 typingHidMouse
= false;
192 if (mouseOnEditor(mouse
)) {
193 // The editor might have changed, update the scollbars.
194 setBottomValue(editField
.getMaximumRowNumber());
195 setVerticalValue(editField
.getVisibleRowNumber());
196 setRightValue(editField
.getMaximumColumnNumber());
197 setHorizontalValue(editField
.getEditingColumnNumber());
199 if (mouse
.isMouseWheelUp() || mouse
.isMouseWheelDown()) {
200 // Vertical scrollbar actions
201 editField
.setVisibleRowNumber(getVerticalValue());
207 * Handle mouse release events.
209 * @param mouse mouse button release event
212 public void onMouseUp(final TMouseEvent mouse
) {
213 // Use TWidget's code to pass the event to the children.
214 super.onMouseUp(mouse
);
216 if (hideMouseWhenTyping
) {
217 typingHidMouse
= false;
220 if (mouse
.isMouse1() && mouseOnVerticalScroller(mouse
)) {
221 // Clicked on vertical scrollbar
222 editField
.setVisibleRowNumber(getVerticalValue());
224 if (mouse
.isMouse1() && mouseOnHorizontalScroller(mouse
)) {
225 // Clicked on horizontal scrollbar
226 editField
.setVisibleColumnNumber(getHorizontalValue());
227 setHorizontalValue(editField
.getVisibleColumnNumber());
232 * Method that subclasses can override to handle mouse movements.
234 * @param mouse mouse motion event
237 public void onMouseMotion(final TMouseEvent mouse
) {
238 // Use TWidget's code to pass the event to the children.
239 super.onMouseMotion(mouse
);
241 if (hideMouseWhenTyping
) {
242 typingHidMouse
= false;
245 if (mouseOnEditor(mouse
) && mouse
.isMouse1()) {
246 // The editor might have changed, update the scollbars.
247 setBottomValue(editField
.getMaximumRowNumber());
248 setVerticalValue(editField
.getVisibleRowNumber());
249 setRightValue(editField
.getMaximumColumnNumber());
250 setHorizontalValue(editField
.getEditingColumnNumber());
252 if (mouse
.isMouse1() && mouseOnVerticalScroller(mouse
)) {
253 // Clicked/dragged on vertical scrollbar
254 editField
.setVisibleRowNumber(getVerticalValue());
256 if (mouse
.isMouse1() && mouseOnHorizontalScroller(mouse
)) {
257 // Clicked/dragged on horizontal scrollbar
258 editField
.setVisibleColumnNumber(getHorizontalValue());
259 setHorizontalValue(editField
.getVisibleColumnNumber());
268 * @param keypress keystroke event
271 public void onKeypress(final TKeypressEvent keypress
) {
272 if (hideMouseWhenTyping
) {
273 typingHidMouse
= true;
276 // Use TWidget's code to pass the event to the children.
277 super.onKeypress(keypress
);
279 // The editor might have changed, update the scollbars.
280 setBottomValue(editField
.getMaximumRowNumber());
281 setVerticalValue(editField
.getVisibleRowNumber());
282 setRightValue(editField
.getMaximumColumnNumber());
283 setHorizontalValue(editField
.getEditingColumnNumber());
287 * Handle window/screen resize events.
289 * @param event resize event
292 public void onResize(final TResizeEvent event
) {
293 if (event
.getType() == TResizeEvent
.Type
.WIDGET
) {
294 // Resize the text field
295 TResizeEvent editSize
= new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
296 event
.getWidth() - 2, event
.getHeight() - 2);
297 editField
.onResize(editSize
);
299 // Have TScrollableWindow handle the scrollbars
300 super.onResize(event
);
304 // Pass to children instead
305 for (TWidget widget
: getChildren()) {
306 widget
.onResize(event
);
311 * Method that subclasses can override to handle posted command events.
313 * @param command command event
316 public void onCommand(final TCommandEvent command
) {
317 if (command
.equals(cmOpen
)) {
319 String filename
= fileOpenBox(".");
320 if (filename
!= null) {
322 String contents
= readFileData(filename
);
323 new TEditorWindow(getApplication(), filename
, contents
);
324 } catch (IOException e
) {
325 messageBox(i18n
.getString("errorDialogTitle"),
326 MessageFormat
.format(i18n
.
327 getString("errorReadingFile"), e
.getMessage()));
330 } catch (IOException e
) {
331 messageBox(i18n
.getString("errorDialogTitle"),
332 MessageFormat
.format(i18n
.
333 getString("errorOpeningFileDialog"), e
.getMessage()));
338 if (command
.equals(cmSave
)) {
339 if (filename
.length() > 0) {
341 editField
.saveToFilename(filename
);
342 } catch (IOException e
) {
343 messageBox(i18n
.getString("errorDialogTitle"),
344 MessageFormat
.format(i18n
.
345 getString("errorSavingFile"), e
.getMessage()));
351 // Didn't handle it, let children get it instead
352 super.onCommand(command
);
356 * Handle posted menu events.
358 * @param menu menu event
361 public void onMenu(final TMenuEvent menu
) {
362 switch (menu
.getId()) {
372 // ------------------------------------------------------------------------
373 // TWindow ----------------------------------------------------------------
374 // ------------------------------------------------------------------------
384 // Add the row:col on the bottom row
385 CellAttributes borderColor
= getBorder();
386 String location
= String
.format(" %d:%d ",
387 editField
.getEditingRowNumber(),
388 editField
.getEditingColumnNumber());
389 int colon
= location
.indexOf(':');
390 putStringXY(10 - colon
, getHeight() - 1, location
, borderColor
);
392 if (editField
.isDirty()) {
393 putCharXY(2, getHeight() - 1, GraphicsChars
.OCTOSTAR
, borderColor
);
398 * Returns true if this window does not want the application-wide mouse
399 * cursor drawn over it.
401 * @return true if this window does not want the application-wide mouse
402 * cursor drawn over it
405 public boolean hasHiddenMouse() {
406 return (super.hasHiddenMouse() || typingHidMouse
);
409 // ------------------------------------------------------------------------
410 // TEditorWindow ----------------------------------------------------------
411 // ------------------------------------------------------------------------
414 * Setup other fields after the editor is created.
416 private void setupAfterEditor() {
417 hScroller
= new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
418 vScroller
= new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
419 setMinimumWindowWidth(25);
420 setMinimumWindowHeight(10);
422 setBottomValue(editField
.getMaximumRowNumber());
424 setRightValue(editField
.getMaximumColumnNumber());
426 statusBar
= newStatusBar(i18n
.getString("statusBar"));
427 statusBar
.addShortcutKeypress(kbF1
, cmHelp
,
428 i18n
.getString("statusBarHelp"));
429 statusBar
.addShortcutKeypress(kbF2
, cmSave
,
430 i18n
.getString("statusBarSave"));
431 statusBar
.addShortcutKeypress(kbF3
, cmOpen
,
432 i18n
.getString("statusBarOpen"));
433 statusBar
.addShortcutKeypress(kbF10
, cmMenu
,
434 i18n
.getString("statusBarMenu"));
436 // Hide mouse when typing option
437 if (System
.getProperty("jexer.TEditor.hideMouseWhenTyping",
438 "true").equals("false")) {
440 hideMouseWhenTyping
= false;
445 * Read file data into a string.
447 * @param file the file to open
448 * @return the file contents
449 * @throws IOException if a java.io operation throws
451 private String
readFileData(final File file
) throws IOException
{
452 StringBuilder fileContents
= new StringBuilder();
453 Scanner scanner
= new Scanner(file
);
454 String EOL
= System
.getProperty("line.separator");
457 while (scanner
.hasNextLine()) {
458 fileContents
.append(scanner
.nextLine() + EOL
);
460 return fileContents
.toString();
467 * Read file data into a string.
469 * @param filename the file to open
470 * @return the file contents
471 * @throws IOException if a java.io operation throws
473 private String
readFileData(final String filename
) throws IOException
{
474 return readFileData(new File(filename
));
478 * Check if a mouse press/release/motion event coordinate is over the
481 * @param mouse a mouse-based event
482 * @return whether or not the mouse is on the editor
484 private boolean mouseOnEditor(final TMouseEvent mouse
) {
485 if ((mouse
.getAbsoluteX() >= getAbsoluteX() + 1)
486 && (mouse
.getAbsoluteX() < getAbsoluteX() + getWidth() - 1)
487 && (mouse
.getAbsoluteY() >= getAbsoluteY() + 1)
488 && (mouse
.getAbsoluteY() < getAbsoluteY() + getHeight() - 1)