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