2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
32 import java
.io
.IOException
;
33 import java
.text
.MessageFormat
;
34 import java
.util
.ResourceBundle
;
36 import jexer
.event
.TCommandEvent
;
37 import jexer
.event
.TKeypressEvent
;
38 import jexer
.event
.TMenuEvent
;
39 import jexer
.event
.TMouseEvent
;
40 import jexer
.event
.TResizeEvent
;
41 import jexer
.menu
.TMenu
;
42 import jexer
.menu
.TMenuItem
;
43 import static jexer
.TCommand
.*;
44 import static jexer
.TKeypress
.*;
47 * TTableWindow is used to display and edit regular two-dimensional tables of
50 public class TTableWindow
extends TScrollableWindow
{
55 private static final ResourceBundle i18n
= ResourceBundle
.getBundle(TTableWindow
.class.getName());
57 // ------------------------------------------------------------------------
58 // Variables --------------------------------------------------------------
59 // ------------------------------------------------------------------------
64 private TTableWidget tableField
;
66 // ------------------------------------------------------------------------
67 // Constructors -----------------------------------------------------------
68 // ------------------------------------------------------------------------
71 * Public constructor sets window title.
73 * @param parent the main application
74 * @param title the window title
76 public TTableWindow(final TApplication parent
, final String title
) {
78 super(parent
, title
, 0, 0, parent
.getScreen().getWidth() / 2,
79 parent
.getScreen().getHeight() / 2 - 2, RESIZABLE
| CENTERED
);
81 tableField
= addTable(0, 0, getWidth() - 2, getHeight() - 2);
86 * Public constructor loads a grid from a RFC4180 CSV file.
88 * @param parent the main application
89 * @param csvFile a File referencing the CSV data
90 * @throws IOException if a java.io operation throws
92 public TTableWindow(final TApplication parent
,
93 final File csvFile
) throws IOException
{
95 super(parent
, csvFile
.getName(), 0, 0,
96 parent
.getScreen().getWidth() / 2,
97 parent
.getScreen().getHeight() / 2 - 2,
98 RESIZABLE
| CENTERED
);
100 tableField
= addTable(0, 0, getWidth() - 2, getHeight() - 2, 1, 1);
102 tableField
.loadCsvFile(csvFile
);
105 // ------------------------------------------------------------------------
106 // Event handlers ---------------------------------------------------------
107 // ------------------------------------------------------------------------
110 * Called by application.switchWindow() when this window gets the
111 * focus, and also by application.addWindow().
113 public void onFocus() {
114 // Enable the table menu items.
115 getApplication().enableMenuItem(TMenu
.MID_TABLE_RENAME_COLUMN
);
116 getApplication().enableMenuItem(TMenu
.MID_TABLE_RENAME_ROW
);
117 getApplication().enableMenuItem(TMenu
.MID_TABLE_VIEW_ROW_LABELS
);
118 getApplication().enableMenuItem(TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
);
119 getApplication().enableMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
);
120 getApplication().enableMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
);
121 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_NONE
);
122 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_ALL
);
123 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_CELL_NONE
);
124 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_CELL_ALL
);
125 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_RIGHT
);
126 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_LEFT
);
127 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_TOP
);
128 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_BOTTOM
);
129 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
);
130 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
);
131 getApplication().enableMenuItem(TMenu
.MID_TABLE_DELETE_LEFT
);
132 getApplication().enableMenuItem(TMenu
.MID_TABLE_DELETE_UP
);
133 getApplication().enableMenuItem(TMenu
.MID_TABLE_DELETE_ROW
);
134 getApplication().enableMenuItem(TMenu
.MID_TABLE_DELETE_COLUMN
);
135 getApplication().enableMenuItem(TMenu
.MID_TABLE_INSERT_LEFT
);
136 getApplication().enableMenuItem(TMenu
.MID_TABLE_INSERT_RIGHT
);
137 getApplication().enableMenuItem(TMenu
.MID_TABLE_INSERT_ABOVE
);
138 getApplication().enableMenuItem(TMenu
.MID_TABLE_INSERT_BELOW
);
139 getApplication().enableMenuItem(TMenu
.MID_TABLE_COLUMN_NARROW
);
140 getApplication().enableMenuItem(TMenu
.MID_TABLE_COLUMN_WIDEN
);
141 getApplication().enableMenuItem(TMenu
.MID_TABLE_FILE_OPEN_CSV
);
142 getApplication().enableMenuItem(TMenu
.MID_TABLE_FILE_SAVE_CSV
);
143 getApplication().enableMenuItem(TMenu
.MID_TABLE_FILE_SAVE_TEXT
);
145 if (tableField
!= null) {
147 // Set the menu to match the flags.
148 TMenuItem menuItem
= getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_ROW_LABELS
);
149 if (menuItem
!= null) {
150 menuItem
.setChecked(tableField
.getShowRowLabels());
152 menuItem
= getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
);
153 if (menuItem
!= null) {
154 menuItem
.setChecked(tableField
.getShowColumnLabels());
156 menuItem
= getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
);
157 if (menuItem
!= null) {
158 menuItem
.setChecked(tableField
.getHighlightRow());
160 menuItem
= getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
);
161 if (menuItem
!= null) {
162 menuItem
.setChecked(tableField
.getHighlightColumn());
168 * Called by application.switchWindow() when another window gets the
171 public void onUnfocus() {
172 // Disable the table menu items.
173 getApplication().disableMenuItem(TMenu
.MID_TABLE_RENAME_COLUMN
);
174 getApplication().disableMenuItem(TMenu
.MID_TABLE_RENAME_ROW
);
175 getApplication().disableMenuItem(TMenu
.MID_TABLE_VIEW_ROW_LABELS
);
176 getApplication().disableMenuItem(TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
);
177 getApplication().disableMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
);
178 getApplication().disableMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
);
179 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_NONE
);
180 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_ALL
);
181 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_CELL_NONE
);
182 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_CELL_ALL
);
183 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_RIGHT
);
184 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_LEFT
);
185 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_TOP
);
186 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_BOTTOM
);
187 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
);
188 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
);
189 getApplication().disableMenuItem(TMenu
.MID_TABLE_DELETE_LEFT
);
190 getApplication().disableMenuItem(TMenu
.MID_TABLE_DELETE_UP
);
191 getApplication().disableMenuItem(TMenu
.MID_TABLE_DELETE_ROW
);
192 getApplication().disableMenuItem(TMenu
.MID_TABLE_DELETE_COLUMN
);
193 getApplication().disableMenuItem(TMenu
.MID_TABLE_INSERT_LEFT
);
194 getApplication().disableMenuItem(TMenu
.MID_TABLE_INSERT_RIGHT
);
195 getApplication().disableMenuItem(TMenu
.MID_TABLE_INSERT_ABOVE
);
196 getApplication().disableMenuItem(TMenu
.MID_TABLE_INSERT_BELOW
);
197 getApplication().disableMenuItem(TMenu
.MID_TABLE_COLUMN_NARROW
);
198 getApplication().disableMenuItem(TMenu
.MID_TABLE_COLUMN_WIDEN
);
199 getApplication().disableMenuItem(TMenu
.MID_TABLE_FILE_OPEN_CSV
);
200 getApplication().disableMenuItem(TMenu
.MID_TABLE_FILE_SAVE_CSV
);
201 getApplication().disableMenuItem(TMenu
.MID_TABLE_FILE_SAVE_TEXT
);
204 // ------------------------------------------------------------------------
205 // TWindow ----------------------------------------------------------------
206 // ------------------------------------------------------------------------
209 * Handle mouse press events.
211 * @param mouse mouse button press event
214 public void onMouseDown(final TMouseEvent mouse
) {
215 // Use TWidget's code to pass the event to the children.
216 super.onMouseDown(mouse
);
218 if (mouseOnTable(mouse
)) {
219 // The table might have changed, update the scollbars.
220 setBottomValue(tableField
.getRowCount() - 1);
221 setVerticalValue(tableField
.getSelectedRowNumber());
222 setRightValue(tableField
.getColumnCount() - 1);
223 setHorizontalValue(tableField
.getSelectedColumnNumber());
228 * Handle mouse release events.
230 * @param mouse mouse button release event
233 public void onMouseUp(final TMouseEvent mouse
) {
234 // Use TWidget's code to pass the event to the children.
235 super.onMouseUp(mouse
);
237 if (mouse
.isMouse1() && mouseOnVerticalScroller(mouse
)) {
238 // Clicked/dragged on vertical scrollbar.
239 tableField
.setSelectedRowNumber(getVerticalValue());
241 if (mouse
.isMouse1() && mouseOnHorizontalScroller(mouse
)) {
242 // Clicked/dragged on horizontal scrollbar.
243 tableField
.setSelectedColumnNumber(getHorizontalValue());
248 * Method that subclasses can override to handle mouse movements.
250 * @param mouse mouse motion event
253 public void onMouseMotion(final TMouseEvent mouse
) {
254 // Use TWidget's code to pass the event to the children.
255 super.onMouseMotion(mouse
);
257 if (mouseOnTable(mouse
) && mouse
.isMouse1()) {
258 // The table might have changed, update the scollbars.
259 setBottomValue(tableField
.getRowCount() - 1);
260 setVerticalValue(tableField
.getSelectedRowNumber());
261 setRightValue(tableField
.getColumnCount() - 1);
262 setHorizontalValue(tableField
.getSelectedColumnNumber());
264 if (mouse
.isMouse1() && mouseOnVerticalScroller(mouse
)) {
265 // Clicked/dragged on vertical scrollbar.
266 tableField
.setSelectedRowNumber(getVerticalValue());
268 if (mouse
.isMouse1() && mouseOnHorizontalScroller(mouse
)) {
269 // Clicked/dragged on horizontal scrollbar.
270 tableField
.setSelectedColumnNumber(getHorizontalValue());
279 * @param keypress keystroke event
282 public void onKeypress(final TKeypressEvent keypress
) {
283 // Use TWidget's code to pass the event to the children.
284 super.onKeypress(keypress
);
286 // The table might have changed, update the scollbars.
287 setBottomValue(tableField
.getRowCount() - 1);
288 setVerticalValue(tableField
.getSelectedRowNumber());
289 setRightValue(tableField
.getColumnCount() - 1);
290 setHorizontalValue(tableField
.getSelectedColumnNumber());
294 * Handle window/screen resize events.
296 * @param event resize event
299 public void onResize(final TResizeEvent event
) {
300 if (event
.getType() == TResizeEvent
.Type
.WIDGET
) {
302 TResizeEvent tableSize
= new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
303 event
.getWidth() - 2, event
.getHeight() - 2);
304 tableField
.onResize(tableSize
);
306 // Have TScrollableWindow handle the scrollbars
307 super.onResize(event
);
311 // Pass to children instead
312 for (TWidget widget
: getChildren()) {
313 widget
.onResize(event
);
318 * Method that subclasses can override to handle posted command events.
320 * @param command command event
323 public void onCommand(final TCommandEvent command
) {
324 if (command
.equals(cmOpen
)) {
326 String filename
= fileOpenBox(".");
327 if (filename
!= null) {
329 new TTableWindow(getApplication(), new File(filename
));
330 } catch (IOException e
) {
331 messageBox(i18n
.getString("errorDialogTitle"),
332 MessageFormat
.format(i18n
.
333 getString("errorReadingFile"), e
.getMessage()));
336 } catch (IOException e
) {
337 messageBox(i18n
.getString("errorDialogTitle"),
338 MessageFormat
.format(i18n
.
339 getString("errorOpeningFileDialog"), e
.getMessage()));
344 if (command
.equals(cmSave
)) {
346 String filename
= fileSaveBox(".");
347 if (filename
!= null) {
348 tableField
.saveToCsvFilename(filename
);
350 } catch (IOException e
) {
351 messageBox(i18n
.getString("errorDialogTitle"),
352 MessageFormat
.format(i18n
.
353 getString("errorWritingFile"), e
.getMessage()));
358 // Didn't handle it, let children get it instead
359 super.onCommand(command
);
363 * Handle posted menu events.
365 * @param menu menu event
368 public void onMenu(final TMenuEvent menu
) {
369 TInputBox inputBox
= null;
370 String filename
= null;
372 switch (menu
.getId()) {
373 case TMenu
.MID_TABLE_RENAME_COLUMN
:
374 inputBox
= inputBox(i18n
.getString("renameColumnInputTitle"),
375 i18n
.getString("renameColumnInputCaption"),
376 tableField
.getColumnLabel(tableField
.getSelectedColumnNumber()),
377 TMessageBox
.Type
.OKCANCEL
);
378 if (inputBox
.isOk()) {
379 tableField
.setColumnLabel(tableField
.getSelectedColumnNumber(),
383 case TMenu
.MID_TABLE_RENAME_ROW
:
384 inputBox
= inputBox(i18n
.getString("renameRowInputTitle"),
385 i18n
.getString("renameRowInputCaption"),
386 tableField
.getRowLabel(tableField
.getSelectedRowNumber()),
387 TMessageBox
.Type
.OKCANCEL
);
388 if (inputBox
.isOk()) {
389 tableField
.setRowLabel(tableField
.getSelectedRowNumber(),
393 case TMenu
.MID_TABLE_VIEW_ROW_LABELS
:
394 tableField
.setShowRowLabels(getApplication().getMenuItem(
395 menu
.getId()).getChecked());
397 case TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
:
398 tableField
.setShowColumnLabels(getApplication().getMenuItem(
399 menu
.getId()).getChecked());
401 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
:
402 tableField
.setHighlightRow(getApplication().getMenuItem(
403 menu
.getId()).getChecked());
405 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
:
406 tableField
.setHighlightColumn(getApplication().getMenuItem(
407 menu
.getId()).getChecked());
409 case TMenu
.MID_TABLE_BORDER_NONE
:
410 tableField
.setBorderAllNone();
412 case TMenu
.MID_TABLE_BORDER_ALL
:
413 tableField
.setBorderAllSingle();
415 case TMenu
.MID_TABLE_BORDER_CELL_NONE
:
416 tableField
.setBorderCellNone();
418 case TMenu
.MID_TABLE_BORDER_CELL_ALL
:
419 tableField
.setBorderCellSingle();
421 case TMenu
.MID_TABLE_BORDER_RIGHT
:
422 tableField
.setBorderColumnRightSingle();
424 case TMenu
.MID_TABLE_BORDER_LEFT
:
425 tableField
.setBorderColumnLeftSingle();
427 case TMenu
.MID_TABLE_BORDER_TOP
:
428 tableField
.setBorderRowAboveSingle();
430 case TMenu
.MID_TABLE_BORDER_BOTTOM
:
431 tableField
.setBorderRowBelowSingle();
433 case TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
:
434 tableField
.setBorderRowBelowDouble();
436 case TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
:
437 tableField
.setBorderRowBelowThick();
439 case TMenu
.MID_TABLE_DELETE_LEFT
:
440 tableField
.deleteCellShiftLeft();
442 case TMenu
.MID_TABLE_DELETE_UP
:
443 tableField
.deleteCellShiftUp();
445 case TMenu
.MID_TABLE_DELETE_ROW
:
446 tableField
.deleteRow(tableField
.getSelectedRowNumber());
448 case TMenu
.MID_TABLE_DELETE_COLUMN
:
449 tableField
.deleteColumn(tableField
.getSelectedColumnNumber());
451 case TMenu
.MID_TABLE_INSERT_LEFT
:
452 tableField
.insertColumnLeft(tableField
.getSelectedColumnNumber());
454 case TMenu
.MID_TABLE_INSERT_RIGHT
:
455 tableField
.insertColumnRight(tableField
.getSelectedColumnNumber());
457 case TMenu
.MID_TABLE_INSERT_ABOVE
:
458 tableField
.insertRowAbove(tableField
.getSelectedColumnNumber());
460 case TMenu
.MID_TABLE_INSERT_BELOW
:
461 tableField
.insertRowBelow(tableField
.getSelectedColumnNumber());
463 case TMenu
.MID_TABLE_COLUMN_NARROW
:
464 tableField
.setColumnWidth(tableField
.getSelectedColumnNumber(),
465 tableField
.getColumnWidth(tableField
.getSelectedColumnNumber()) - 1);
467 case TMenu
.MID_TABLE_COLUMN_WIDEN
:
468 tableField
.setColumnWidth(tableField
.getSelectedColumnNumber(),
469 tableField
.getColumnWidth(tableField
.getSelectedColumnNumber()) + 1);
471 case TMenu
.MID_TABLE_FILE_OPEN_CSV
:
473 filename
= fileOpenBox(".");
474 if (filename
!= null) {
476 new TTableWindow(getApplication(), new File(filename
));
477 } catch (IOException e
) {
478 messageBox(i18n
.getString("errorDialogTitle"),
479 MessageFormat
.format(i18n
.
480 getString("errorReadingFile"), e
.getMessage()));
483 } catch (IOException e
) {
484 messageBox(i18n
.getString("errorDialogTitle"),
485 MessageFormat
.format(i18n
.
486 getString("errorOpeningFileDialog"), e
.getMessage()));
489 case TMenu
.MID_TABLE_FILE_SAVE_CSV
:
491 filename
= fileSaveBox(".");
492 if (filename
!= null) {
493 tableField
.saveToCsvFilename(filename
);
495 } catch (IOException e
) {
496 messageBox(i18n
.getString("errorDialogTitle"),
497 MessageFormat
.format(i18n
.
498 getString("errorWritingFile"), e
.getMessage()));
501 case TMenu
.MID_TABLE_FILE_SAVE_TEXT
:
503 filename
= fileSaveBox(".");
504 if (filename
!= null) {
505 tableField
.saveToTextFilename(filename
);
507 } catch (IOException e
) {
508 messageBox(i18n
.getString("errorDialogTitle"),
509 MessageFormat
.format(i18n
.
510 getString("errorWritingFile"), e
.getMessage()));
520 // ------------------------------------------------------------------------
521 // TTableWindow -----------------------------------------------------------
522 // ------------------------------------------------------------------------
525 * Setup other fields after the table is created.
527 private void setupAfterTable() {
528 hScroller
= new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
529 vScroller
= new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
530 setMinimumWindowWidth(25);
531 setMinimumWindowHeight(10);
532 setTopValue(tableField
.getSelectedRowNumber());
533 setBottomValue(tableField
.getRowCount() - 1);
534 setLeftValue(tableField
.getSelectedColumnNumber());
535 setRightValue(tableField
.getColumnCount() - 1);
537 statusBar
= newStatusBar(i18n
.getString("statusBar"));
538 statusBar
.addShortcutKeypress(kbF1
, cmHelp
,
539 i18n
.getString("statusBarHelp"));
541 statusBar
.addShortcutKeypress(kbF2
, cmSave
,
542 i18n
.getString("statusBarSave"));
543 statusBar
.addShortcutKeypress(kbF3
, cmOpen
,
544 i18n
.getString("statusBarOpen"));
545 statusBar
.addShortcutKeypress(kbF10
, cmMenu
,
546 i18n
.getString("statusBarMenu"));
548 // Synchronize the menu with tableField's flags.
553 * Check if a mouse press/release/motion event coordinate is over the
556 * @param mouse a mouse-based event
557 * @return whether or not the mouse is on the table
559 private boolean mouseOnTable(final TMouseEvent mouse
) {
560 if ((mouse
.getAbsoluteX() >= getAbsoluteX() + 1)
561 && (mouse
.getAbsoluteX() < getAbsoluteX() + getWidth() - 1)
562 && (mouse
.getAbsoluteY() >= getAbsoluteY() + 1)
563 && (mouse
.getAbsoluteY() < getAbsoluteY() + getHeight() - 1)