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_CUT
);
116 getApplication().enableMenuItem(TMenu
.MID_TABLE_RENAME_COLUMN
);
117 getApplication().enableMenuItem(TMenu
.MID_TABLE_RENAME_ROW
);
118 getApplication().enableMenuItem(TMenu
.MID_TABLE_VIEW_ROW_LABELS
);
119 getApplication().enableMenuItem(TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
);
120 getApplication().enableMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
);
121 getApplication().enableMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
);
122 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_NONE
);
123 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_ALL
);
124 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_CELL_NONE
);
125 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_CELL_ALL
);
126 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_RIGHT
);
127 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_LEFT
);
128 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_TOP
);
129 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_BOTTOM
);
130 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
);
131 getApplication().enableMenuItem(TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
);
132 getApplication().enableMenuItem(TMenu
.MID_TABLE_DELETE_LEFT
);
133 getApplication().enableMenuItem(TMenu
.MID_TABLE_DELETE_UP
);
134 getApplication().enableMenuItem(TMenu
.MID_TABLE_DELETE_ROW
);
135 getApplication().enableMenuItem(TMenu
.MID_TABLE_DELETE_COLUMN
);
136 getApplication().enableMenuItem(TMenu
.MID_TABLE_INSERT_LEFT
);
137 getApplication().enableMenuItem(TMenu
.MID_TABLE_INSERT_RIGHT
);
138 getApplication().enableMenuItem(TMenu
.MID_TABLE_INSERT_ABOVE
);
139 getApplication().enableMenuItem(TMenu
.MID_TABLE_INSERT_BELOW
);
140 getApplication().enableMenuItem(TMenu
.MID_TABLE_COLUMN_NARROW
);
141 getApplication().enableMenuItem(TMenu
.MID_TABLE_COLUMN_WIDEN
);
142 getApplication().enableMenuItem(TMenu
.MID_TABLE_FILE_OPEN_CSV
);
143 getApplication().enableMenuItem(TMenu
.MID_TABLE_FILE_SAVE_CSV
);
144 getApplication().enableMenuItem(TMenu
.MID_TABLE_FILE_SAVE_TEXT
);
146 if (tableField
!= null) {
148 // Set the menu to match the flags.
149 TMenuItem menuItem
= getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_ROW_LABELS
);
150 if (menuItem
!= null) {
151 menuItem
.setChecked(tableField
.getShowRowLabels());
153 menuItem
= getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
);
154 if (menuItem
!= null) {
155 menuItem
.setChecked(tableField
.getShowColumnLabels());
157 menuItem
= getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
);
158 if (menuItem
!= null) {
159 menuItem
.setChecked(tableField
.getHighlightRow());
161 menuItem
= getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
);
162 if (menuItem
!= null) {
163 menuItem
.setChecked(tableField
.getHighlightColumn());
169 * Called by application.switchWindow() when another window gets the
172 public void onUnfocus() {
173 // Disable the table menu items.
174 getApplication().disableMenuItem(TMenu
.MID_CUT
);
175 getApplication().disableMenuItem(TMenu
.MID_TABLE_RENAME_COLUMN
);
176 getApplication().disableMenuItem(TMenu
.MID_TABLE_RENAME_ROW
);
177 getApplication().disableMenuItem(TMenu
.MID_TABLE_VIEW_ROW_LABELS
);
178 getApplication().disableMenuItem(TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
);
179 getApplication().disableMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
);
180 getApplication().disableMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
);
181 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_NONE
);
182 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_ALL
);
183 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_CELL_NONE
);
184 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_CELL_ALL
);
185 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_RIGHT
);
186 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_LEFT
);
187 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_TOP
);
188 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_BOTTOM
);
189 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
);
190 getApplication().disableMenuItem(TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
);
191 getApplication().disableMenuItem(TMenu
.MID_TABLE_DELETE_LEFT
);
192 getApplication().disableMenuItem(TMenu
.MID_TABLE_DELETE_UP
);
193 getApplication().disableMenuItem(TMenu
.MID_TABLE_DELETE_ROW
);
194 getApplication().disableMenuItem(TMenu
.MID_TABLE_DELETE_COLUMN
);
195 getApplication().disableMenuItem(TMenu
.MID_TABLE_INSERT_LEFT
);
196 getApplication().disableMenuItem(TMenu
.MID_TABLE_INSERT_RIGHT
);
197 getApplication().disableMenuItem(TMenu
.MID_TABLE_INSERT_ABOVE
);
198 getApplication().disableMenuItem(TMenu
.MID_TABLE_INSERT_BELOW
);
199 getApplication().disableMenuItem(TMenu
.MID_TABLE_COLUMN_NARROW
);
200 getApplication().disableMenuItem(TMenu
.MID_TABLE_COLUMN_WIDEN
);
201 getApplication().disableMenuItem(TMenu
.MID_TABLE_FILE_OPEN_CSV
);
202 getApplication().disableMenuItem(TMenu
.MID_TABLE_FILE_SAVE_CSV
);
203 getApplication().disableMenuItem(TMenu
.MID_TABLE_FILE_SAVE_TEXT
);
206 // ------------------------------------------------------------------------
207 // TWindow ----------------------------------------------------------------
208 // ------------------------------------------------------------------------
211 * Handle mouse press events.
213 * @param mouse mouse button press event
216 public void onMouseDown(final TMouseEvent mouse
) {
217 // Use TWidget's code to pass the event to the children.
218 super.onMouseDown(mouse
);
220 if (mouseOnTable(mouse
)) {
221 // The table might have changed, update the scollbars.
222 setBottomValue(tableField
.getRowCount() - 1);
223 setVerticalValue(tableField
.getSelectedRowNumber());
224 setRightValue(tableField
.getColumnCount() - 1);
225 setHorizontalValue(tableField
.getSelectedColumnNumber());
230 * Handle mouse release events.
232 * @param mouse mouse button release event
235 public void onMouseUp(final TMouseEvent mouse
) {
236 // Use TWidget's code to pass the event to the children.
237 super.onMouseUp(mouse
);
239 if (mouse
.isMouse1() && mouseOnVerticalScroller(mouse
)) {
240 // Clicked/dragged on vertical scrollbar.
241 tableField
.setSelectedRowNumber(getVerticalValue());
243 if (mouse
.isMouse1() && mouseOnHorizontalScroller(mouse
)) {
244 // Clicked/dragged on horizontal scrollbar.
245 tableField
.setSelectedColumnNumber(getHorizontalValue());
250 * Method that subclasses can override to handle mouse movements.
252 * @param mouse mouse motion event
255 public void onMouseMotion(final TMouseEvent mouse
) {
256 // Use TWidget's code to pass the event to the children.
257 super.onMouseMotion(mouse
);
259 if (mouseOnTable(mouse
) && mouse
.isMouse1()) {
260 // The table might have changed, update the scollbars.
261 setBottomValue(tableField
.getRowCount() - 1);
262 setVerticalValue(tableField
.getSelectedRowNumber());
263 setRightValue(tableField
.getColumnCount() - 1);
264 setHorizontalValue(tableField
.getSelectedColumnNumber());
266 if (mouse
.isMouse1() && mouseOnVerticalScroller(mouse
)) {
267 // Clicked/dragged on vertical scrollbar.
268 tableField
.setSelectedRowNumber(getVerticalValue());
270 if (mouse
.isMouse1() && mouseOnHorizontalScroller(mouse
)) {
271 // Clicked/dragged on horizontal scrollbar.
272 tableField
.setSelectedColumnNumber(getHorizontalValue());
281 * @param keypress keystroke event
284 public void onKeypress(final TKeypressEvent keypress
) {
285 // Use TWidget's code to pass the event to the children.
286 super.onKeypress(keypress
);
288 // The table might have changed, update the scollbars.
289 setBottomValue(tableField
.getRowCount() - 1);
290 setVerticalValue(tableField
.getSelectedRowNumber());
291 setRightValue(tableField
.getColumnCount() - 1);
292 setHorizontalValue(tableField
.getSelectedColumnNumber());
296 * Handle window/screen resize events.
298 * @param event resize event
301 public void onResize(final TResizeEvent event
) {
302 if (event
.getType() == TResizeEvent
.Type
.WIDGET
) {
304 TResizeEvent tableSize
= new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
305 event
.getWidth() - 2, event
.getHeight() - 2);
306 tableField
.onResize(tableSize
);
308 // Have TScrollableWindow handle the scrollbars
309 super.onResize(event
);
313 // Pass to children instead
314 for (TWidget widget
: getChildren()) {
315 widget
.onResize(event
);
320 * Method that subclasses can override to handle posted command events.
322 * @param command command event
325 public void onCommand(final TCommandEvent command
) {
326 if (command
.equals(cmOpen
)) {
328 String filename
= fileOpenBox(".");
329 if (filename
!= null) {
331 new TTableWindow(getApplication(), new File(filename
));
332 } catch (IOException e
) {
333 messageBox(i18n
.getString("errorDialogTitle"),
334 MessageFormat
.format(i18n
.
335 getString("errorReadingFile"), e
.getMessage()));
338 } catch (IOException e
) {
339 messageBox(i18n
.getString("errorDialogTitle"),
340 MessageFormat
.format(i18n
.
341 getString("errorOpeningFileDialog"), e
.getMessage()));
346 if (command
.equals(cmSave
)) {
348 String filename
= fileSaveBox(".");
349 if (filename
!= null) {
350 tableField
.saveToCsvFilename(filename
);
352 } catch (IOException e
) {
353 messageBox(i18n
.getString("errorDialogTitle"),
354 MessageFormat
.format(i18n
.
355 getString("errorWritingFile"), e
.getMessage()));
360 // Didn't handle it, let children get it instead
361 super.onCommand(command
);
365 * Handle posted menu events.
367 * @param menu menu event
370 public void onMenu(final TMenuEvent menu
) {
371 TInputBox inputBox
= null;
372 String filename
= null;
374 switch (menu
.getId()) {
375 case TMenu
.MID_TABLE_RENAME_COLUMN
:
376 inputBox
= inputBox(i18n
.getString("renameColumnInputTitle"),
377 i18n
.getString("renameColumnInputCaption"),
378 tableField
.getColumnLabel(tableField
.getSelectedColumnNumber()),
379 TMessageBox
.Type
.OKCANCEL
);
380 if (inputBox
.isOk()) {
381 tableField
.setColumnLabel(tableField
.getSelectedColumnNumber(),
385 case TMenu
.MID_TABLE_RENAME_ROW
:
386 inputBox
= inputBox(i18n
.getString("renameRowInputTitle"),
387 i18n
.getString("renameRowInputCaption"),
388 tableField
.getRowLabel(tableField
.getSelectedRowNumber()),
389 TMessageBox
.Type
.OKCANCEL
);
390 if (inputBox
.isOk()) {
391 tableField
.setRowLabel(tableField
.getSelectedRowNumber(),
395 case TMenu
.MID_TABLE_VIEW_ROW_LABELS
:
396 tableField
.setShowRowLabels(getApplication().getMenuItem(
397 menu
.getId()).getChecked());
399 case TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
:
400 tableField
.setShowColumnLabels(getApplication().getMenuItem(
401 menu
.getId()).getChecked());
403 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
:
404 tableField
.setHighlightRow(getApplication().getMenuItem(
405 menu
.getId()).getChecked());
407 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
:
408 tableField
.setHighlightColumn(getApplication().getMenuItem(
409 menu
.getId()).getChecked());
411 case TMenu
.MID_TABLE_BORDER_NONE
:
412 tableField
.setBorderAllNone();
414 case TMenu
.MID_TABLE_BORDER_ALL
:
415 tableField
.setBorderAllSingle();
417 case TMenu
.MID_TABLE_BORDER_CELL_NONE
:
418 tableField
.setBorderCellNone();
420 case TMenu
.MID_TABLE_BORDER_CELL_ALL
:
421 tableField
.setBorderCellSingle();
423 case TMenu
.MID_TABLE_BORDER_RIGHT
:
424 tableField
.setBorderColumnRightSingle();
426 case TMenu
.MID_TABLE_BORDER_LEFT
:
427 tableField
.setBorderColumnLeftSingle();
429 case TMenu
.MID_TABLE_BORDER_TOP
:
430 tableField
.setBorderRowAboveSingle();
432 case TMenu
.MID_TABLE_BORDER_BOTTOM
:
433 tableField
.setBorderRowBelowSingle();
435 case TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
:
436 tableField
.setBorderRowBelowDouble();
438 case TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
:
439 tableField
.setBorderRowBelowThick();
441 case TMenu
.MID_TABLE_DELETE_LEFT
:
442 tableField
.deleteCellShiftLeft();
444 case TMenu
.MID_TABLE_DELETE_UP
:
445 tableField
.deleteCellShiftUp();
447 case TMenu
.MID_TABLE_DELETE_ROW
:
448 tableField
.deleteRow(tableField
.getSelectedRowNumber());
450 case TMenu
.MID_TABLE_DELETE_COLUMN
:
451 tableField
.deleteColumn(tableField
.getSelectedColumnNumber());
453 case TMenu
.MID_TABLE_INSERT_LEFT
:
454 tableField
.insertColumnLeft(tableField
.getSelectedColumnNumber());
456 case TMenu
.MID_TABLE_INSERT_RIGHT
:
457 tableField
.insertColumnRight(tableField
.getSelectedColumnNumber());
459 case TMenu
.MID_TABLE_INSERT_ABOVE
:
460 tableField
.insertRowAbove(tableField
.getSelectedColumnNumber());
462 case TMenu
.MID_TABLE_INSERT_BELOW
:
463 tableField
.insertRowBelow(tableField
.getSelectedColumnNumber());
465 case TMenu
.MID_TABLE_COLUMN_NARROW
:
466 tableField
.setColumnWidth(tableField
.getSelectedColumnNumber(),
467 tableField
.getColumnWidth(tableField
.getSelectedColumnNumber()) - 1);
469 case TMenu
.MID_TABLE_COLUMN_WIDEN
:
470 tableField
.setColumnWidth(tableField
.getSelectedColumnNumber(),
471 tableField
.getColumnWidth(tableField
.getSelectedColumnNumber()) + 1);
473 case TMenu
.MID_TABLE_FILE_OPEN_CSV
:
475 filename
= fileOpenBox(".");
476 if (filename
!= null) {
478 new TTableWindow(getApplication(), new File(filename
));
479 } catch (IOException e
) {
480 messageBox(i18n
.getString("errorDialogTitle"),
481 MessageFormat
.format(i18n
.
482 getString("errorReadingFile"), e
.getMessage()));
485 } catch (IOException e
) {
486 messageBox(i18n
.getString("errorDialogTitle"),
487 MessageFormat
.format(i18n
.
488 getString("errorOpeningFileDialog"), e
.getMessage()));
491 case TMenu
.MID_TABLE_FILE_SAVE_CSV
:
493 filename
= fileSaveBox(".");
494 if (filename
!= null) {
495 tableField
.saveToCsvFilename(filename
);
497 } catch (IOException e
) {
498 messageBox(i18n
.getString("errorDialogTitle"),
499 MessageFormat
.format(i18n
.
500 getString("errorWritingFile"), e
.getMessage()));
503 case TMenu
.MID_TABLE_FILE_SAVE_TEXT
:
505 filename
= fileSaveBox(".");
506 if (filename
!= null) {
507 tableField
.saveToTextFilename(filename
);
509 } catch (IOException e
) {
510 messageBox(i18n
.getString("errorDialogTitle"),
511 MessageFormat
.format(i18n
.
512 getString("errorWritingFile"), e
.getMessage()));
522 // ------------------------------------------------------------------------
523 // TTableWindow -----------------------------------------------------------
524 // ------------------------------------------------------------------------
527 * Setup other fields after the table is created.
529 private void setupAfterTable() {
530 hScroller
= new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
531 vScroller
= new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
532 setMinimumWindowWidth(25);
533 setMinimumWindowHeight(10);
534 setTopValue(tableField
.getSelectedRowNumber());
535 setBottomValue(tableField
.getRowCount() - 1);
536 setLeftValue(tableField
.getSelectedColumnNumber());
537 setRightValue(tableField
.getColumnCount() - 1);
539 statusBar
= newStatusBar(i18n
.getString("statusBar"));
540 statusBar
.addShortcutKeypress(kbF1
, cmHelp
,
541 i18n
.getString("statusBarHelp"));
543 statusBar
.addShortcutKeypress(kbF2
, cmSave
,
544 i18n
.getString("statusBarSave"));
545 statusBar
.addShortcutKeypress(kbF3
, cmOpen
,
546 i18n
.getString("statusBarOpen"));
547 statusBar
.addShortcutKeypress(kbF10
, cmMenu
,
548 i18n
.getString("statusBarMenu"));
550 // Synchronize the menu with tableField's flags.
555 * Check if a mouse press/release/motion event coordinate is over the
558 * @param mouse a mouse-based event
559 * @return whether or not the mouse is on the table
561 private boolean mouseOnTable(final TMouseEvent mouse
) {
562 if ((mouse
.getAbsoluteX() >= getAbsoluteX() + 1)
563 && (mouse
.getAbsoluteX() < getAbsoluteX() + getWidth() - 1)
564 && (mouse
.getAbsoluteY() >= getAbsoluteY() + 1)
565 && (mouse
.getAbsoluteY() < getAbsoluteY() + getHeight() - 1)