#46 mention Demo7 in readme
[fanfix.git] / src / jexer / TEditorWindow.java
CommitLineData
71a389c9
KL
1/*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
a69ed767 6 * Copyright (C) 2019 Kevin Lamonte
71a389c9
KL
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 */
29package jexer;
30
31import java.io.File;
32import java.io.IOException;
339652cc
KL
33import java.text.MessageFormat;
34import java.util.ResourceBundle;
71a389c9
KL
35import java.util.Scanner;
36
37import jexer.TApplication;
38import jexer.TEditorWidget;
39import jexer.THScroller;
40import jexer.TScrollableWindow;
41import jexer.TVScroller;
42import jexer.TWidget;
43import jexer.bits.CellAttributes;
44import jexer.bits.GraphicsChars;
45import jexer.event.TCommandEvent;
fe0770f9 46import jexer.event.TKeypressEvent;
71a389c9
KL
47import jexer.event.TMouseEvent;
48import jexer.event.TResizeEvent;
49import static jexer.TCommand.*;
50import static jexer.TKeypress.*;
51
52/**
53 * TEditorWindow is a basic text file editor.
54 */
55public class TEditorWindow extends TScrollableWindow {
56
339652cc
KL
57 /**
58 * Translated strings.
59 */
60 private static final ResourceBundle i18n = ResourceBundle.getBundle(TEditorWindow.class.getName());
61
615a0d99
KL
62 // ------------------------------------------------------------------------
63 // Variables --------------------------------------------------------------
64 // ------------------------------------------------------------------------
65
71a389c9
KL
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
e23989a4
KL
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
615a0d99
KL
87 // ------------------------------------------------------------------------
88 // Constructors -----------------------------------------------------------
89 // ------------------------------------------------------------------------
71a389c9
KL
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
df602ccf
KL
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
71a389c9
KL
143 /**
144 * Public constructor.
145 *
146 * @param parent the main application
147 */
148 public TEditorWindow(final TApplication parent) {
339652cc 149 this(parent, i18n.getString("newTextDocument"));
71a389c9
KL
150 }
151
615a0d99
KL
152 // ------------------------------------------------------------------------
153 // TWindow ----------------------------------------------------------------
154 // ------------------------------------------------------------------------
df602ccf 155
71a389c9
KL
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
71a389c9
KL
177 /**
178 * Handle mouse press events.
179 *
180 * @param mouse mouse button press event
181 */
182 @Override
183 public void onMouseDown(final TMouseEvent mouse) {
df602ccf
KL
184 // Use TWidget's code to pass the event to the children.
185 super.onMouseDown(mouse);
186
e23989a4
KL
187 if (hideMouseWhenTyping) {
188 typingHidMouse = false;
189 }
190
71a389c9 191 if (mouseOnEditor(mouse)) {
df602ccf 192 // The editor might have changed, update the scollbars.
71a389c9 193 setBottomValue(editField.getMaximumRowNumber());
b6faeac0 194 setVerticalValue(editField.getVisibleRowNumber());
71a389c9
KL
195 setRightValue(editField.getMaximumColumnNumber());
196 setHorizontalValue(editField.getEditingColumnNumber());
197 } else {
71a389c9 198 if (mouse.isMouseWheelUp() || mouse.isMouseWheelDown()) {
df602ccf 199 // Vertical scrollbar actions
b6faeac0 200 editField.setVisibleRowNumber(getVerticalValue());
71a389c9 201 }
fe0770f9
KL
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
e23989a4
KL
215 if (hideMouseWhenTyping) {
216 typingHidMouse = false;
217 }
218
fe0770f9
KL
219 if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
220 // Clicked on vertical scrollbar
b6faeac0 221 editField.setVisibleRowNumber(getVerticalValue());
fe0770f9
KL
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
e23989a4
KL
237 if (hideMouseWhenTyping) {
238 typingHidMouse = false;
239 }
240
fe0770f9
KL
241 if (mouseOnEditor(mouse) && mouse.isMouse1()) {
242 // The editor might have changed, update the scollbars.
243 setBottomValue(editField.getMaximumRowNumber());
b6faeac0 244 setVerticalValue(editField.getVisibleRowNumber());
fe0770f9
KL
245 setRightValue(editField.getMaximumColumnNumber());
246 setHorizontalValue(editField.getEditingColumnNumber());
247 } else {
248 if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
249 // Clicked/dragged on vertical scrollbar
b6faeac0 250 editField.setVisibleRowNumber(getVerticalValue());
fe0770f9
KL
251 }
252
71a389c9
KL
253 // TODO: horizontal scrolling
254 }
fe0770f9
KL
255
256 }
257
258 /**
259 * Handle keystrokes.
260 *
261 * @param keypress keystroke event
262 */
263 @Override
264 public void onKeypress(final TKeypressEvent keypress) {
e23989a4
KL
265 if (hideMouseWhenTyping) {
266 typingHidMouse = true;
267 }
268
fe0770f9
KL
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());
b6faeac0 274 setVerticalValue(editField.getVisibleRowNumber());
fe0770f9
KL
275 setRightValue(editField.getMaximumColumnNumber());
276 setHorizontalValue(editField.getEditingColumnNumber());
71a389c9
KL
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(".");
df602ccf
KL
313 if (filename != null) {
314 try {
315 String contents = readFileData(filename);
316 new TEditorWindow(getApplication(), filename, contents);
317 } catch (IOException e) {
339652cc
KL
318 messageBox(i18n.getString("errorDialogTitle"),
319 MessageFormat.format(i18n.
320 getString("errorReadingFile"), e.getMessage()));
df602ccf
KL
321 }
322 }
71a389c9 323 } catch (IOException e) {
339652cc
KL
324 messageBox(i18n.getString("errorDialogTitle"),
325 MessageFormat.format(i18n.
326 getString("errorOpeningFileDialog"), e.getMessage()));
71a389c9
KL
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) {
339652cc
KL
336 messageBox(i18n.getString("errorDialogTitle"),
337 MessageFormat.format(i18n.
338 getString("errorSavingFile"), e.getMessage()));
71a389c9
KL
339 }
340 }
341 return;
342 }
343
344 // Didn't handle it, let children get it instead
345 super.onCommand(command);
346 }
347
e23989a4
KL
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
615a0d99
KL
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"));
e23989a4
KL
386
387 // Hide mouse when typing option
388 if (System.getProperty("jexer.TEditor.hideMouseWhenTyping",
389 "true").equals("false")) {
390
391 hideMouseWhenTyping = false;
392 }
615a0d99
KL
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 */
c88c4ced 435 private boolean mouseOnEditor(final TMouseEvent mouse) {
615a0d99
KL
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
71a389c9 446}