85bb91a273169da8f4fdd0e4749c521c4f6b5968
[fanfix.git] / TEditorWindow.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer;
30
31 import java.io.File;
32 import java.io.IOException;
33 import java.text.MessageFormat;
34 import java.util.ResourceBundle;
35 import java.util.Scanner;
36
37 import jexer.TApplication;
38 import jexer.TEditorWidget;
39 import jexer.THScroller;
40 import jexer.TScrollableWindow;
41 import jexer.TVScroller;
42 import jexer.TWidget;
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.*;
51
52 /**
53 * TEditorWindow is a basic text file editor.
54 */
55 public class TEditorWindow extends TScrollableWindow {
56
57 /**
58 * Translated strings.
59 */
60 private static final ResourceBundle i18n = ResourceBundle.getBundle(TEditorWindow.class.getName());
61
62 // ------------------------------------------------------------------------
63 // Variables --------------------------------------------------------------
64 // ------------------------------------------------------------------------
65
66 /**
67 * Hang onto my TEditor so I can resize it with the window.
68 */
69 private TEditorWidget editField;
70
71 /**
72 * The fully-qualified name of the file being edited.
73 */
74 private String filename = "";
75
76 /**
77 * If true, hide the mouse after typing a keystroke.
78 */
79 private boolean hideMouseWhenTyping = true;
80
81 /**
82 * If true, the mouse should not be displayed because a keystroke was
83 * typed.
84 */
85 private boolean typingHidMouse = false;
86
87 // ------------------------------------------------------------------------
88 // Constructors -----------------------------------------------------------
89 // ------------------------------------------------------------------------
90
91 /**
92 * Public constructor sets window title.
93 *
94 * @param parent the main application
95 * @param title the window title
96 */
97 public TEditorWindow(final TApplication parent, final String title) {
98
99 super(parent, title, 0, 0, parent.getScreen().getWidth(),
100 parent.getScreen().getHeight() - 2, RESIZABLE);
101
102 editField = addEditor("", 0, 0, getWidth() - 2, getHeight() - 2);
103 setupAfterEditor();
104 }
105
106 /**
107 * Public constructor sets window title and contents.
108 *
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
112 */
113 public TEditorWindow(final TApplication parent, final String title,
114 final String contents) {
115
116 super(parent, title, 0, 0, parent.getScreen().getWidth(),
117 parent.getScreen().getHeight() - 2, RESIZABLE);
118
119 filename = title;
120 editField = addEditor(contents, 0, 0, getWidth() - 2, getHeight() - 2);
121 setupAfterEditor();
122 }
123
124 /**
125 * Public constructor opens a file.
126 *
127 * @param parent the main application
128 * @param file the file to open
129 * @throws IOException if a java.io operation throws
130 */
131 public TEditorWindow(final TApplication parent,
132 final File file) throws IOException {
133
134 super(parent, file.getName(), 0, 0, parent.getScreen().getWidth(),
135 parent.getScreen().getHeight() - 2, RESIZABLE);
136
137 filename = file.getName();
138 String contents = readFileData(file);
139 editField = addEditor(contents, 0, 0, getWidth() - 2, getHeight() - 2);
140 setupAfterEditor();
141 }
142
143 /**
144 * Public constructor.
145 *
146 * @param parent the main application
147 */
148 public TEditorWindow(final TApplication parent) {
149 this(parent, i18n.getString("newTextDocument"));
150 }
151
152 // ------------------------------------------------------------------------
153 // TWindow ----------------------------------------------------------------
154 // ------------------------------------------------------------------------
155
156 /**
157 * Draw the window.
158 */
159 @Override
160 public void draw() {
161 // Draw as normal.
162 super.draw();
163
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);
171
172 if (editField.isDirty()) {
173 putCharXY(2, getHeight() - 1, GraphicsChars.OCTOSTAR, borderColor);
174 }
175 }
176
177 /**
178 * Handle mouse press events.
179 *
180 * @param mouse mouse button press event
181 */
182 @Override
183 public void onMouseDown(final TMouseEvent mouse) {
184 // Use TWidget's code to pass the event to the children.
185 super.onMouseDown(mouse);
186
187 if (hideMouseWhenTyping) {
188 typingHidMouse = false;
189 }
190
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());
197 } else {
198 if (mouse.isMouseWheelUp() || mouse.isMouseWheelDown()) {
199 // Vertical scrollbar actions
200 editField.setVisibleRowNumber(getVerticalValue());
201 }
202 }
203 }
204
205 /**
206 * Handle mouse release events.
207 *
208 * @param mouse mouse button release event
209 */
210 @Override
211 public void onMouseUp(final TMouseEvent mouse) {
212 // Use TWidget's code to pass the event to the children.
213 super.onMouseUp(mouse);
214
215 if (hideMouseWhenTyping) {
216 typingHidMouse = false;
217 }
218
219 if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
220 // Clicked on vertical scrollbar
221 editField.setVisibleRowNumber(getVerticalValue());
222 }
223
224 // TODO: horizontal scrolling
225 }
226
227 /**
228 * Method that subclasses can override to handle mouse movements.
229 *
230 * @param mouse mouse motion event
231 */
232 @Override
233 public void onMouseMotion(final TMouseEvent mouse) {
234 // Use TWidget's code to pass the event to the children.
235 super.onMouseMotion(mouse);
236
237 if (hideMouseWhenTyping) {
238 typingHidMouse = false;
239 }
240
241 if (mouseOnEditor(mouse) && mouse.isMouse1()) {
242 // The editor might have changed, update the scollbars.
243 setBottomValue(editField.getMaximumRowNumber());
244 setVerticalValue(editField.getVisibleRowNumber());
245 setRightValue(editField.getMaximumColumnNumber());
246 setHorizontalValue(editField.getEditingColumnNumber());
247 } else {
248 if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
249 // Clicked/dragged on vertical scrollbar
250 editField.setVisibleRowNumber(getVerticalValue());
251 }
252
253 // TODO: horizontal scrolling
254 }
255
256 }
257
258 /**
259 * Handle keystrokes.
260 *
261 * @param keypress keystroke event
262 */
263 @Override
264 public void onKeypress(final TKeypressEvent keypress) {
265 if (hideMouseWhenTyping) {
266 typingHidMouse = true;
267 }
268
269 // Use TWidget's code to pass the event to the children.
270 super.onKeypress(keypress);
271
272 // The editor might have changed, update the scollbars.
273 setBottomValue(editField.getMaximumRowNumber());
274 setVerticalValue(editField.getVisibleRowNumber());
275 setRightValue(editField.getMaximumColumnNumber());
276 setHorizontalValue(editField.getEditingColumnNumber());
277 }
278
279 /**
280 * Handle window/screen resize events.
281 *
282 * @param event resize event
283 */
284 @Override
285 public void onResize(final TResizeEvent event) {
286 if (event.getType() == TResizeEvent.Type.WIDGET) {
287 // Resize the text field
288 TResizeEvent editSize = new TResizeEvent(TResizeEvent.Type.WIDGET,
289 event.getWidth() - 2, event.getHeight() - 2);
290 editField.onResize(editSize);
291
292 // Have TScrollableWindow handle the scrollbars
293 super.onResize(event);
294 return;
295 }
296
297 // Pass to children instead
298 for (TWidget widget: getChildren()) {
299 widget.onResize(event);
300 }
301 }
302
303 /**
304 * Method that subclasses can override to handle posted command events.
305 *
306 * @param command command event
307 */
308 @Override
309 public void onCommand(final TCommandEvent command) {
310 if (command.equals(cmOpen)) {
311 try {
312 String filename = fileOpenBox(".");
313 if (filename != null) {
314 try {
315 String contents = readFileData(filename);
316 new TEditorWindow(getApplication(), filename, contents);
317 } catch (IOException e) {
318 messageBox(i18n.getString("errorDialogTitle"),
319 MessageFormat.format(i18n.
320 getString("errorReadingFile"), e.getMessage()));
321 }
322 }
323 } catch (IOException e) {
324 messageBox(i18n.getString("errorDialogTitle"),
325 MessageFormat.format(i18n.
326 getString("errorOpeningFileDialog"), e.getMessage()));
327 }
328 return;
329 }
330
331 if (command.equals(cmSave)) {
332 if (filename.length() > 0) {
333 try {
334 editField.saveToFilename(filename);
335 } catch (IOException e) {
336 messageBox(i18n.getString("errorDialogTitle"),
337 MessageFormat.format(i18n.
338 getString("errorSavingFile"), e.getMessage()));
339 }
340 }
341 return;
342 }
343
344 // Didn't handle it, let children get it instead
345 super.onCommand(command);
346 }
347
348 /**
349 * Returns true if this window does not want the application-wide mouse
350 * cursor drawn over it.
351 *
352 * @return true if this window does not want the application-wide mouse
353 * cursor drawn over it
354 */
355 @Override
356 public boolean hasHiddenMouse() {
357 return (super.hasHiddenMouse() || typingHidMouse);
358 }
359
360 // ------------------------------------------------------------------------
361 // TEditorWindow ----------------------------------------------------------
362 // ------------------------------------------------------------------------
363
364 /**
365 * Setup other fields after the editor is created.
366 */
367 private void setupAfterEditor() {
368 hScroller = new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
369 vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
370 setMinimumWindowWidth(25);
371 setMinimumWindowHeight(10);
372 setTopValue(1);
373 setBottomValue(editField.getMaximumRowNumber());
374 setLeftValue(1);
375 setRightValue(editField.getMaximumColumnNumber());
376
377 statusBar = newStatusBar(i18n.getString("statusBar"));
378 statusBar.addShortcutKeypress(kbF1, cmHelp,
379 i18n.getString("statusBarHelp"));
380 statusBar.addShortcutKeypress(kbF2, cmSave,
381 i18n.getString("statusBarSave"));
382 statusBar.addShortcutKeypress(kbF3, cmOpen,
383 i18n.getString("statusBarOpen"));
384 statusBar.addShortcutKeypress(kbF10, cmMenu,
385 i18n.getString("statusBarMenu"));
386
387 // Hide mouse when typing option
388 if (System.getProperty("jexer.TEditor.hideMouseWhenTyping",
389 "true").equals("false")) {
390
391 hideMouseWhenTyping = false;
392 }
393 }
394
395 /**
396 * Read file data into a string.
397 *
398 * @param file the file to open
399 * @return the file contents
400 * @throws IOException if a java.io operation throws
401 */
402 private String readFileData(final File file) throws IOException {
403 StringBuilder fileContents = new StringBuilder();
404 Scanner scanner = new Scanner(file);
405 String EOL = System.getProperty("line.separator");
406
407 try {
408 while (scanner.hasNextLine()) {
409 fileContents.append(scanner.nextLine() + EOL);
410 }
411 return fileContents.toString();
412 } finally {
413 scanner.close();
414 }
415 }
416
417 /**
418 * Read file data into a string.
419 *
420 * @param filename the file to open
421 * @return the file contents
422 * @throws IOException if a java.io operation throws
423 */
424 private String readFileData(final String filename) throws IOException {
425 return readFileData(new File(filename));
426 }
427
428 /**
429 * Check if a mouse press/release/motion event coordinate is over the
430 * editor.
431 *
432 * @param mouse a mouse-based event
433 * @return whether or not the mouse is on the editor
434 */
435 private boolean mouseOnEditor(final TMouseEvent mouse) {
436 if ((mouse.getAbsoluteX() >= getAbsoluteX() + 1)
437 && (mouse.getAbsoluteX() < getAbsoluteX() + getWidth() - 1)
438 && (mouse.getAbsoluteY() >= getAbsoluteY() + 1)
439 && (mouse.getAbsoluteY() < getAbsoluteY() + getHeight() - 1)
440 ) {
441 return true;
442 }
443 return false;
444 }
445
446 }