retrofit from gjexer
[nikiroo-utils.git] / src / jexer / 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 // Constructors -----------------------------------------------------------
78 // ------------------------------------------------------------------------
79
80 /**
81 * Public constructor sets window title.
82 *
83 * @param parent the main application
84 * @param title the window title
85 */
86 public TEditorWindow(final TApplication parent, final String title) {
87
88 super(parent, title, 0, 0, parent.getScreen().getWidth(),
89 parent.getScreen().getHeight() - 2, RESIZABLE);
90
91 editField = addEditor("", 0, 0, getWidth() - 2, getHeight() - 2);
92 setupAfterEditor();
93 }
94
95 /**
96 * Public constructor sets window title and contents.
97 *
98 * @param parent the main application
99 * @param title the window title, usually a filename
100 * @param contents the data for the editing window, usually the file data
101 */
102 public TEditorWindow(final TApplication parent, final String title,
103 final String contents) {
104
105 super(parent, title, 0, 0, parent.getScreen().getWidth(),
106 parent.getScreen().getHeight() - 2, RESIZABLE);
107
108 filename = title;
109 editField = addEditor(contents, 0, 0, getWidth() - 2, getHeight() - 2);
110 setupAfterEditor();
111 }
112
113 /**
114 * Public constructor opens a file.
115 *
116 * @param parent the main application
117 * @param file the file to open
118 * @throws IOException if a java.io operation throws
119 */
120 public TEditorWindow(final TApplication parent,
121 final File file) throws IOException {
122
123 super(parent, file.getName(), 0, 0, parent.getScreen().getWidth(),
124 parent.getScreen().getHeight() - 2, RESIZABLE);
125
126 filename = file.getName();
127 String contents = readFileData(file);
128 editField = addEditor(contents, 0, 0, getWidth() - 2, getHeight() - 2);
129 setupAfterEditor();
130 }
131
132 /**
133 * Public constructor.
134 *
135 * @param parent the main application
136 */
137 public TEditorWindow(final TApplication parent) {
138 this(parent, i18n.getString("newTextDocument"));
139 }
140
141 // ------------------------------------------------------------------------
142 // TWindow ----------------------------------------------------------------
143 // ------------------------------------------------------------------------
144
145 /**
146 * Draw the window.
147 */
148 @Override
149 public void draw() {
150 // Draw as normal.
151 super.draw();
152
153 // Add the row:col on the bottom row
154 CellAttributes borderColor = getBorder();
155 String location = String.format(" %d:%d ",
156 editField.getEditingRowNumber(),
157 editField.getEditingColumnNumber());
158 int colon = location.indexOf(':');
159 putStringXY(10 - colon, getHeight() - 1, location, borderColor);
160
161 if (editField.isDirty()) {
162 putCharXY(2, getHeight() - 1, GraphicsChars.OCTOSTAR, borderColor);
163 }
164 }
165
166 /**
167 * Handle mouse press events.
168 *
169 * @param mouse mouse button press event
170 */
171 @Override
172 public void onMouseDown(final TMouseEvent mouse) {
173 // Use TWidget's code to pass the event to the children.
174 super.onMouseDown(mouse);
175
176 if (mouseOnEditor(mouse)) {
177 // The editor might have changed, update the scollbars.
178 setBottomValue(editField.getMaximumRowNumber());
179 setVerticalValue(editField.getVisibleRowNumber());
180 setRightValue(editField.getMaximumColumnNumber());
181 setHorizontalValue(editField.getEditingColumnNumber());
182 } else {
183 if (mouse.isMouseWheelUp() || mouse.isMouseWheelDown()) {
184 // Vertical scrollbar actions
185 editField.setVisibleRowNumber(getVerticalValue());
186 }
187 }
188 }
189
190 /**
191 * Handle mouse release events.
192 *
193 * @param mouse mouse button release event
194 */
195 @Override
196 public void onMouseUp(final TMouseEvent mouse) {
197 // Use TWidget's code to pass the event to the children.
198 super.onMouseUp(mouse);
199
200 if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
201 // Clicked on vertical scrollbar
202 editField.setVisibleRowNumber(getVerticalValue());
203 }
204
205 // TODO: horizontal scrolling
206 }
207
208 /**
209 * Method that subclasses can override to handle mouse movements.
210 *
211 * @param mouse mouse motion event
212 */
213 @Override
214 public void onMouseMotion(final TMouseEvent mouse) {
215 // Use TWidget's code to pass the event to the children.
216 super.onMouseMotion(mouse);
217
218 if (mouseOnEditor(mouse) && mouse.isMouse1()) {
219 // The editor might have changed, update the scollbars.
220 setBottomValue(editField.getMaximumRowNumber());
221 setVerticalValue(editField.getVisibleRowNumber());
222 setRightValue(editField.getMaximumColumnNumber());
223 setHorizontalValue(editField.getEditingColumnNumber());
224 } else {
225 if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
226 // Clicked/dragged on vertical scrollbar
227 editField.setVisibleRowNumber(getVerticalValue());
228 }
229
230 // TODO: horizontal scrolling
231 }
232
233 }
234
235 /**
236 * Handle keystrokes.
237 *
238 * @param keypress keystroke event
239 */
240 @Override
241 public void onKeypress(final TKeypressEvent keypress) {
242 // Use TWidget's code to pass the event to the children.
243 super.onKeypress(keypress);
244
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());
250 }
251
252 /**
253 * Handle window/screen resize events.
254 *
255 * @param event resize event
256 */
257 @Override
258 public void onResize(final TResizeEvent event) {
259 if (event.getType() == TResizeEvent.Type.WIDGET) {
260 // Resize the text field
261 TResizeEvent editSize = new TResizeEvent(TResizeEvent.Type.WIDGET,
262 event.getWidth() - 2, event.getHeight() - 2);
263 editField.onResize(editSize);
264
265 // Have TScrollableWindow handle the scrollbars
266 super.onResize(event);
267 return;
268 }
269
270 // Pass to children instead
271 for (TWidget widget: getChildren()) {
272 widget.onResize(event);
273 }
274 }
275
276 /**
277 * Method that subclasses can override to handle posted command events.
278 *
279 * @param command command event
280 */
281 @Override
282 public void onCommand(final TCommandEvent command) {
283 if (command.equals(cmOpen)) {
284 try {
285 String filename = fileOpenBox(".");
286 if (filename != null) {
287 try {
288 String contents = readFileData(filename);
289 new TEditorWindow(getApplication(), filename, contents);
290 } catch (IOException e) {
291 messageBox(i18n.getString("errorDialogTitle"),
292 MessageFormat.format(i18n.
293 getString("errorReadingFile"), e.getMessage()));
294 }
295 }
296 } catch (IOException e) {
297 messageBox(i18n.getString("errorDialogTitle"),
298 MessageFormat.format(i18n.
299 getString("errorOpeningFileDialog"), e.getMessage()));
300 }
301 return;
302 }
303
304 if (command.equals(cmSave)) {
305 if (filename.length() > 0) {
306 try {
307 editField.saveToFilename(filename);
308 } catch (IOException e) {
309 messageBox(i18n.getString("errorDialogTitle"),
310 MessageFormat.format(i18n.
311 getString("errorSavingFile"), e.getMessage()));
312 }
313 }
314 return;
315 }
316
317 // Didn't handle it, let children get it instead
318 super.onCommand(command);
319 }
320
321 // ------------------------------------------------------------------------
322 // TEditorWindow ----------------------------------------------------------
323 // ------------------------------------------------------------------------
324
325 /**
326 * Setup other fields after the editor is created.
327 */
328 private void setupAfterEditor() {
329 hScroller = new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
330 vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
331 setMinimumWindowWidth(25);
332 setMinimumWindowHeight(10);
333 setTopValue(1);
334 setBottomValue(editField.getMaximumRowNumber());
335 setLeftValue(1);
336 setRightValue(editField.getMaximumColumnNumber());
337
338 statusBar = newStatusBar(i18n.getString("statusBar"));
339 statusBar.addShortcutKeypress(kbF1, cmHelp,
340 i18n.getString("statusBarHelp"));
341 statusBar.addShortcutKeypress(kbF2, cmSave,
342 i18n.getString("statusBarSave"));
343 statusBar.addShortcutKeypress(kbF3, cmOpen,
344 i18n.getString("statusBarOpen"));
345 statusBar.addShortcutKeypress(kbF10, cmMenu,
346 i18n.getString("statusBarMenu"));
347 }
348
349 /**
350 * Read file data into a string.
351 *
352 * @param file the file to open
353 * @return the file contents
354 * @throws IOException if a java.io operation throws
355 */
356 private String readFileData(final File file) throws IOException {
357 StringBuilder fileContents = new StringBuilder();
358 Scanner scanner = new Scanner(file);
359 String EOL = System.getProperty("line.separator");
360
361 try {
362 while (scanner.hasNextLine()) {
363 fileContents.append(scanner.nextLine() + EOL);
364 }
365 return fileContents.toString();
366 } finally {
367 scanner.close();
368 }
369 }
370
371 /**
372 * Read file data into a string.
373 *
374 * @param filename the file to open
375 * @return the file contents
376 * @throws IOException if a java.io operation throws
377 */
378 private String readFileData(final String filename) throws IOException {
379 return readFileData(new File(filename));
380 }
381
382 /**
383 * Check if a mouse press/release/motion event coordinate is over the
384 * editor.
385 *
386 * @param mouse a mouse-based event
387 * @return whether or not the mouse is on the editor
388 */
389 private final boolean mouseOnEditor(final TMouseEvent mouse) {
390 if ((mouse.getAbsoluteX() >= getAbsoluteX() + 1)
391 && (mouse.getAbsoluteX() < getAbsoluteX() + getWidth() - 1)
392 && (mouse.getAbsoluteY() >= getAbsoluteY() + 1)
393 && (mouse.getAbsoluteY() < getAbsoluteY() + getHeight() - 1)
394 ) {
395 return true;
396 }
397 return false;
398 }
399
400 }