cleanup
[fanfix.git] / src / jexer / menu / TMenu.java
CommitLineData
daa4106c 1/*
928811d8
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
928811d8 5 *
a69ed767 6 * Copyright (C) 2019 Kevin Lamonte
928811d8 7 *
e16dda65
KL
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:
928811d8 14 *
e16dda65
KL
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
928811d8 17 *
e16dda65
KL
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.
928811d8
KL
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29package jexer.menu;
30
339652cc
KL
31import java.util.ResourceBundle;
32
928811d8
KL
33import jexer.TApplication;
34import jexer.TKeypress;
35import jexer.TWidget;
36import jexer.TWindow;
37import jexer.bits.CellAttributes;
38import jexer.bits.GraphicsChars;
39import jexer.bits.MnemonicString;
40import jexer.event.TKeypressEvent;
41import jexer.event.TMouseEvent;
42import static jexer.TKeypress.*;
43
44/**
45 * TMenu is a top-level collection of TMenuItems.
46 */
051e2913 47public class TMenu extends TWindow {
928811d8 48
339652cc
KL
49 /**
50 * Translated strings.
51 */
52 private static final ResourceBundle i18n = ResourceBundle.getBundle(TMenu.class.getName());
53
d36057df
KL
54 // ------------------------------------------------------------------------
55 // Constants --------------------------------------------------------------
56 // ------------------------------------------------------------------------
928811d8
KL
57
58 // Reserved menu item IDs
59 public static final int MID_UNUSED = -1;
60
61 // File menu
62 public static final int MID_EXIT = 1;
63 public static final int MID_QUIT = MID_EXIT;
64 public static final int MID_OPEN_FILE = 2;
65 public static final int MID_SHELL = 3;
66
67 // Edit menu
68 public static final int MID_CUT = 10;
69 public static final int MID_COPY = 11;
70 public static final int MID_PASTE = 12;
71 public static final int MID_CLEAR = 13;
72
d36057df
KL
73 // Search menu
74 public static final int MID_FIND = 20;
75 public static final int MID_REPLACE = 21;
76 public static final int MID_SEARCH_AGAIN = 22;
77 public static final int MID_GOTO_LINE = 23;
78
928811d8 79 // Window menu
d36057df
KL
80 public static final int MID_TILE = 30;
81 public static final int MID_CASCADE = 31;
82 public static final int MID_CLOSE_ALL = 32;
83 public static final int MID_WINDOW_MOVE = 33;
84 public static final int MID_WINDOW_ZOOM = 34;
85 public static final int MID_WINDOW_NEXT = 35;
86 public static final int MID_WINDOW_PREVIOUS = 36;
87 public static final int MID_WINDOW_CLOSE = 37;
928811d8 88
55d2b2c2
KL
89 // Help menu
90 public static final int MID_HELP_CONTENTS = 40;
91 public static final int MID_HELP_INDEX = 41;
92 public static final int MID_HELP_SEARCH = 42;
93 public static final int MID_HELP_PREVIOUS = 43;
94 public static final int MID_HELP_HELP = 44;
95 public static final int MID_HELP_ACTIVE_FILE = 45;
96 public static final int MID_ABOUT = 46;
97
1c19fdea
KL
98 // Other
99 public static final int MID_REPAINT = 50;
100
d36057df
KL
101 // ------------------------------------------------------------------------
102 // Variables --------------------------------------------------------------
103 // ------------------------------------------------------------------------
104
105 /**
106 * If true, this is a sub-menu. Note package private access.
107 */
108 boolean isSubMenu = false;
109
110 /**
111 * The X position of the menu's title.
112 */
113 private int titleX;
114
115 /**
116 * The shortcut and title.
117 */
118 private MnemonicString mnemonic;
119
120 // ------------------------------------------------------------------------
121 // Constructors -----------------------------------------------------------
122 // ------------------------------------------------------------------------
123
928811d8
KL
124 /**
125 * Public constructor.
126 *
127 * @param parent parent application
128 * @param x column relative to parent
129 * @param y row relative to parent
130 * @param label mnemonic menu title. Label must contain a keyboard
43ad7b6c
KL
131 * shortcut (mnemonic), denoted by prefixing a letter with "&",
132 * e.g. "&File"
928811d8
KL
133 */
134 public TMenu(final TApplication parent, final int x, final int y,
135 final String label) {
136
137 super(parent, label, x, y, parent.getScreen().getWidth(),
138 parent.getScreen().getHeight());
139
928811d8
KL
140 // Setup the menu shortcut
141 mnemonic = new MnemonicString(label);
142 setTitle(mnemonic.getRawLabel());
143 assert (mnemonic.getShortcutIdx() >= 0);
144
145 // Recompute width and height to reflect an empty menu
146 setWidth(getTitle().length() + 4);
147 setHeight(2);
148
149 setActive(false);
150 }
151
d36057df
KL
152 // ------------------------------------------------------------------------
153 // Event handlers ---------------------------------------------------------
154 // ------------------------------------------------------------------------
928811d8
KL
155
156 /**
157 * Handle mouse button presses.
158 *
159 * @param mouse mouse button event
160 */
161 @Override
162 public void onMouseDown(final TMouseEvent mouse) {
163 this.mouse = mouse;
a69ed767 164 super.onMouseDown(mouse);
928811d8
KL
165
166 // Pass to children
167 for (TWidget widget: getChildren()) {
168 if (widget.mouseWouldHit(mouse)) {
169 // Dispatch to this child, also activate it
170 activate(widget);
171
172 // Set x and y relative to the child's coordinates
173 mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
174 mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
175 widget.handleEvent(mouse);
176 return;
177 }
178 }
179 }
180
181 /**
182 * Handle mouse button releases.
183 *
184 * @param mouse mouse button release event
185 */
186 @Override
187 public void onMouseUp(final TMouseEvent mouse) {
188 this.mouse = mouse;
928811d8
KL
189
190 // Pass to children
191 for (TWidget widget: getChildren()) {
192 if (widget.mouseWouldHit(mouse)) {
193 // Dispatch to this child, also activate it
194 activate(widget);
195
196 // Set x and y relative to the child's coordinates
197 mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
198 mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
199 widget.handleEvent(mouse);
200 return;
201 }
202 }
203 }
204
205 /**
206 * Handle mouse movements.
207 *
208 * @param mouse mouse motion event
209 */
210 @Override
211 public void onMouseMotion(final TMouseEvent mouse) {
212 this.mouse = mouse;
928811d8
KL
213
214 // See if we should activate a different menu item
215 for (TWidget widget: getChildren()) {
7c870d89 216 if ((mouse.isMouse1())
928811d8
KL
217 && (widget.mouseWouldHit(mouse))
218 ) {
219 // Activate this menu item
220 activate(widget);
221 if (widget instanceof TSubMenu) {
222 ((TSubMenu) widget).dispatch();
223 }
224 return;
225 }
226 }
227 }
228
229 /**
230 * Handle keystrokes.
231 *
232 * @param keypress keystroke event
233 */
234 @Override
235 public void onKeypress(final TKeypressEvent keypress) {
91c9a837
KL
236
237 /*
238 System.err.printf("keypress: %s active child: %s\n", keypress,
239 getActiveChild());
240 */
241
242 if (getActiveChild() != this) {
243 if ((getActiveChild() instanceof TSubMenu)
244 || (getActiveChild() instanceof TMenu)
245 ) {
928811d8
KL
246 getActiveChild().onKeypress(keypress);
247 return;
248 }
249 }
250
251 if (keypress.equals(kbEsc)) {
252 getApplication().closeMenu();
253 return;
254 }
255 if (keypress.equals(kbDown)) {
256 switchWidget(true);
257 return;
258 }
259 if (keypress.equals(kbUp)) {
260 switchWidget(false);
261 return;
262 }
263 if (keypress.equals(kbRight)) {
91c9a837 264 getApplication().switchMenu(true);
928811d8
KL
265 return;
266 }
267 if (keypress.equals(kbLeft)) {
268 if (isSubMenu) {
269 getApplication().closeSubMenu();
270 } else {
271 getApplication().switchMenu(false);
272 }
273 return;
274 }
275
276 // Switch to a menuItem if it has an mnemonic
7c870d89
KL
277 if (!keypress.getKey().isFnKey()
278 && !keypress.getKey().isAlt()
279 && !keypress.getKey().isCtrl()) {
928811d8
KL
280 for (TWidget widget: getChildren()) {
281 TMenuItem item = (TMenuItem) widget;
282 if ((item.getMnemonic() != null)
283 && (Character.toLowerCase(item.getMnemonic().getShortcut())
7c870d89 284 == Character.toLowerCase(keypress.getKey().getChar()))
928811d8
KL
285 ) {
286 // Send an enter keystroke to it
287 activate(item);
288 item.handleEvent(new TKeypressEvent(kbEnter));
289 return;
290 }
291 }
292 }
293
294 // Dispatch the keypress to an active widget
295 for (TWidget widget: getChildren()) {
7c870d89 296 if (widget.isActive()) {
928811d8
KL
297 widget.handleEvent(keypress);
298 return;
299 }
300 }
301 }
302
d36057df
KL
303 // ------------------------------------------------------------------------
304 // TWindow ----------------------------------------------------------------
305 // ------------------------------------------------------------------------
306
307 /**
308 * Draw a top-level menu with title and menu items.
309 */
310 @Override
311 public void draw() {
312 CellAttributes background = getTheme().getColor("tmenu");
313
314 assert (isAbsoluteActive());
315
316 // Fill in the interior background
317 for (int i = 0; i < getHeight(); i++) {
318 hLineXY(0, i, getWidth(), ' ', background);
319 }
320
321 // Draw the box
322 char cTopLeft;
323 char cTopRight;
324 char cBottomLeft;
325 char cBottomRight;
326 char cHSide;
327
328 cTopLeft = GraphicsChars.ULCORNER;
329 cTopRight = GraphicsChars.URCORNER;
330 cBottomLeft = GraphicsChars.LLCORNER;
331 cBottomRight = GraphicsChars.LRCORNER;
332 cHSide = GraphicsChars.SINGLE_BAR;
333
334 // Place the corner characters
335 putCharXY(1, 0, cTopLeft, background);
336 putCharXY(getWidth() - 2, 0, cTopRight, background);
337 putCharXY(1, getHeight() - 1, cBottomLeft, background);
338 putCharXY(getWidth() - 2, getHeight() - 1, cBottomRight, background);
339
340 // Draw the box lines
341 hLineXY(1 + 1, 0, getWidth() - 4, cHSide, background);
342 hLineXY(1 + 1, getHeight() - 1, getWidth() - 4, cHSide, background);
343
344 // Draw a shadow
a69ed767 345 drawBoxShadow(0, 0, getWidth(), getHeight());
d36057df
KL
346 }
347
348 // ------------------------------------------------------------------------
349 // TMenu ------------------------------------------------------------------
350 // ------------------------------------------------------------------------
351
352 /**
353 * Set the menu title X position.
354 *
355 * @param titleX the position
356 */
357 public void setTitleX(final int titleX) {
358 this.titleX = titleX;
359 }
360
361 /**
362 * Get the menu title X position.
363 *
364 * @return the position
365 */
366 public int getTitleX() {
367 return titleX;
368 }
369
370 /**
371 * Get the mnemonic string.
372 *
373 * @return the full mnemonic string
374 */
375 public MnemonicString getMnemonic() {
376 return mnemonic;
377 }
378
928811d8 379 /**
efb7af1f 380 * Convenience function to add a menu item.
928811d8
KL
381 *
382 * @param id menu item ID. Must be greater than 1024.
383 * @param label menu item label
928811d8
KL
384 * @return the new menu item
385 */
329fd62e 386 public TMenuItem addItem(final int id, final String label) {
928811d8 387 assert (id >= 1024);
efb7af1f 388 return addItemInternal(id, label, null);
928811d8
KL
389 }
390
a69ed767
KL
391 /**
392 * Convenience function to add a menu item.
393 *
394 * @param id menu item ID. Must be greater than 1024.
395 * @param label menu item label
396 * @param enabled default state for enabled
397 * @return the new menu item
398 */
399 public TMenuItem addItem(final int id, final String label,
400 final boolean enabled) {
401
402 assert (id >= 1024);
403 return addItemInternal(id, label, null, enabled);
404 }
405
928811d8
KL
406 /**
407 * Convenience function to add a custom menu item.
408 *
409 * @param id menu item ID. Must be greater than 1024.
410 * @param label menu item label
411 * @param key global keyboard accelerator
412 * @return the new menu item
413 */
329fd62e 414 public TMenuItem addItem(final int id, final String label,
928811d8
KL
415 final TKeypress key) {
416
efb7af1f
KL
417 assert (id >= 1024);
418 return addItemInternal(id, label, key);
928811d8
KL
419 }
420
68c5cd6b
KL
421 /**
422 * Convenience function to add a custom menu item.
423 *
424 * @param id menu item ID. Must be greater than 1024.
425 * @param label menu item label
426 * @param key global keyboard accelerator
427 * @param enabled default state for enabled
428 * @return the new menu item
429 */
430 public TMenuItem addItem(final int id, final String label,
431 final TKeypress key, final boolean enabled) {
432
433 TMenuItem item = addItem(id, label, key);
434 item.setEnabled(enabled);
435 return item;
436 }
437
928811d8 438 /**
efb7af1f 439 * Convenience function to add a custom menu item.
928811d8
KL
440 *
441 * @param id menu item ID. Must be greater than 1024.
442 * @param label menu item label
efb7af1f 443 * @param key global keyboard accelerator
928811d8
KL
444 * @return the new menu item
445 */
efb7af1f
KL
446 private TMenuItem addItemInternal(final int id, final String label,
447 final TKeypress key) {
928811d8 448
d36057df
KL
449 return addItemInternal(id, label, key, true);
450 }
451
452 /**
453 * Convenience function to add a custom menu item.
454 *
455 * @param id menu item ID. Must be greater than 1024.
456 * @param label menu item label
457 * @param key global keyboard accelerator
458 * @param enabled default state for enabled
459 * @return the new menu item
460 */
461 private TMenuItem addItemInternal(final int id, final String label,
462 final TKeypress key, final boolean enabled) {
463
928811d8
KL
464 int newY = getChildren().size() + 1;
465 assert (newY < getHeight());
466
467 TMenuItem menuItem = new TMenuItem(this, id, 1, newY, label);
efb7af1f 468 menuItem.setKey(key);
d36057df 469 menuItem.setEnabled(enabled);
928811d8
KL
470 setHeight(getHeight() + 1);
471 if (menuItem.getWidth() + 2 > getWidth()) {
472 setWidth(menuItem.getWidth() + 2);
473 }
474 for (TWidget widget: getChildren()) {
475 widget.setWidth(getWidth() - 2);
476 }
efb7af1f 477 getApplication().addMenuItem(menuItem);
928811d8
KL
478 getApplication().recomputeMenuX();
479 activate(0);
480 return menuItem;
481 }
482
483 /**
484 * Convenience function to add one of the default menu items.
485 *
486 * @param id menu item ID. Must be between 0 (inclusive) and 1023
487 * (inclusive).
488 * @return the new menu item
489 */
329fd62e 490 public TMenuItem addDefaultItem(final int id) {
d36057df
KL
491 return addDefaultItem(id, true);
492 }
493
494 /**
495 * Convenience function to add one of the default menu items.
496 *
497 * @param id menu item ID. Must be between 0 (inclusive) and 1023
498 * (inclusive).
499 * @param enabled default state for enabled
500 * @return the new menu item
501 */
502 public TMenuItem addDefaultItem(final int id, final boolean enabled) {
928811d8
KL
503 assert (id >= 0);
504 assert (id < 1024);
505
506 String label;
507 TKeypress key = null;
928811d8
KL
508
509 switch (id) {
510
511 case MID_EXIT:
339652cc 512 label = i18n.getString("menuExit");
928811d8
KL
513 key = kbAltX;
514 break;
515
516 case MID_SHELL:
339652cc 517 label = i18n.getString("menuShell");
928811d8
KL
518 break;
519
520 case MID_OPEN_FILE:
339652cc 521 label = i18n.getString("menuOpen");
b2d49e0f 522 key = kbF3;
928811d8
KL
523 break;
524
525 case MID_CUT:
339652cc 526 label = i18n.getString("menuCut");
928811d8
KL
527 key = kbCtrlX;
528 break;
529 case MID_COPY:
339652cc 530 label = i18n.getString("menuCopy");
928811d8
KL
531 key = kbCtrlC;
532 break;
533 case MID_PASTE:
339652cc 534 label = i18n.getString("menuPaste");
928811d8
KL
535 key = kbCtrlV;
536 break;
537 case MID_CLEAR:
339652cc 538 label = i18n.getString("menuClear");
30bd4abd 539 // key = kbDel;
928811d8
KL
540 break;
541
d36057df
KL
542 case MID_FIND:
543 label = i18n.getString("menuFind");
544 break;
545 case MID_REPLACE:
546 label = i18n.getString("menuReplace");
547 break;
548 case MID_SEARCH_AGAIN:
549 label = i18n.getString("menuSearchAgain");
550 break;
551 case MID_GOTO_LINE:
552 label = i18n.getString("menuGotoLine");
553 key = kbCtrlL;
554 break;
555
928811d8 556 case MID_TILE:
339652cc 557 label = i18n.getString("menuWindowTile");
928811d8
KL
558 break;
559 case MID_CASCADE:
339652cc 560 label = i18n.getString("menuWindowCascade");
928811d8
KL
561 break;
562 case MID_CLOSE_ALL:
339652cc 563 label = i18n.getString("menuWindowCloseAll");
928811d8
KL
564 break;
565 case MID_WINDOW_MOVE:
339652cc 566 label = i18n.getString("menuWindowMove");
928811d8
KL
567 key = kbCtrlF5;
568 break;
569 case MID_WINDOW_ZOOM:
339652cc 570 label = i18n.getString("menuWindowZoom");
928811d8
KL
571 key = kbF5;
572 break;
573 case MID_WINDOW_NEXT:
339652cc 574 label = i18n.getString("menuWindowNext");
928811d8
KL
575 key = kbF6;
576 break;
577 case MID_WINDOW_PREVIOUS:
339652cc 578 label = i18n.getString("menuWindowPrevious");
928811d8
KL
579 key = kbShiftF6;
580 break;
581 case MID_WINDOW_CLOSE:
339652cc 582 label = i18n.getString("menuWindowClose");
5dfd1c11 583 key = kbCtrlW;
928811d8
KL
584 break;
585
55d2b2c2 586 case MID_HELP_CONTENTS:
339652cc 587 label = i18n.getString("menuHelpContents");
55d2b2c2
KL
588 break;
589 case MID_HELP_INDEX:
339652cc 590 label = i18n.getString("menuHelpIndex");
55d2b2c2
KL
591 key = kbShiftF1;
592 break;
593 case MID_HELP_SEARCH:
339652cc 594 label = i18n.getString("menuHelpSearch");
55d2b2c2
KL
595 key = kbCtrlF1;
596 break;
597 case MID_HELP_PREVIOUS:
339652cc 598 label = i18n.getString("menuHelpPrevious");
55d2b2c2
KL
599 key = kbAltF1;
600 break;
601 case MID_HELP_HELP:
339652cc 602 label = i18n.getString("menuHelpHelp");
55d2b2c2
KL
603 break;
604 case MID_HELP_ACTIVE_FILE:
339652cc 605 label = i18n.getString("menuHelpActive");
55d2b2c2
KL
606 break;
607 case MID_ABOUT:
339652cc 608 label = i18n.getString("menuHelpAbout");
55d2b2c2
KL
609 break;
610
1c19fdea
KL
611 case MID_REPAINT:
612 label = i18n.getString("menuRepaintDesktop");
613 break;
614
928811d8
KL
615 default:
616 throw new IllegalArgumentException("Invalid menu ID: " + id);
617 }
618
d36057df 619 return addItemInternal(id, label, key, enabled);
928811d8
KL
620 }
621
622 /**
623 * Convenience function to add a menu separator.
624 */
329fd62e 625 public void addSeparator() {
928811d8
KL
626 int newY = getChildren().size() + 1;
627 assert (newY < getHeight());
628
cf9af8df
KL
629 // We just have to construct it, don't need to hang onto what it
630 // makes.
631 new TMenuSeparator(this, 1, newY);
928811d8
KL
632 setHeight(getHeight() + 1);
633 }
634
635 /**
636 * Convenience function to add a sub-menu.
637 *
638 * @param title menu title. Title must contain a keyboard shortcut,
43ad7b6c 639 * denoted by prefixing a letter with "&amp;", e.g. "&amp;File"
928811d8
KL
640 * @return the new sub-menu
641 */
329fd62e 642 public TSubMenu addSubMenu(final String title) {
928811d8
KL
643 int newY = getChildren().size() + 1;
644 assert (newY < getHeight());
645
646 TSubMenu subMenu = new TSubMenu(this, title, 1, newY);
647 setHeight(getHeight() + 1);
648 if (subMenu.getWidth() + 2 > getWidth()) {
649 setWidth(subMenu.getWidth() + 2);
650 }
651 for (TWidget widget: getChildren()) {
652 widget.setWidth(getWidth() - 2);
653 }
654 getApplication().recomputeMenuX();
655 activate(0);
656 subMenu.menu.setX(getX() + getWidth() - 2);
657
658 return subMenu;
659 }
660
661}