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