Fix ClassCastException
[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 *
a2018e99 6 * Copyright (C) 2017 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 */
47public final class TMenu extends TWindow {
48
339652cc
KL
49 /**
50 * Translated strings.
51 */
52 private static final ResourceBundle i18n = ResourceBundle.getBundle(TMenu.class.getName());
53
928811d8
KL
54 /**
55 * If true, this is a sub-menu. Note package private access.
56 */
57 boolean isSubMenu = false;
58
59 /**
60 * The shortcut and title.
61 */
62 private MnemonicString mnemonic;
63
64 /**
65 * Get the mnemonic string.
66 *
67 * @return the full mnemonic string
68 */
69 public MnemonicString getMnemonic() {
70 return mnemonic;
71 }
72
73 // Reserved menu item IDs
74 public static final int MID_UNUSED = -1;
75
76 // File menu
77 public static final int MID_EXIT = 1;
78 public static final int MID_QUIT = MID_EXIT;
79 public static final int MID_OPEN_FILE = 2;
80 public static final int MID_SHELL = 3;
81
82 // Edit menu
83 public static final int MID_CUT = 10;
84 public static final int MID_COPY = 11;
85 public static final int MID_PASTE = 12;
86 public static final int MID_CLEAR = 13;
87
88 // Window menu
89 public static final int MID_TILE = 20;
90 public static final int MID_CASCADE = 21;
91 public static final int MID_CLOSE_ALL = 22;
92 public static final int MID_WINDOW_MOVE = 23;
93 public static final int MID_WINDOW_ZOOM = 24;
94 public static final int MID_WINDOW_NEXT = 25;
95 public static final int MID_WINDOW_PREVIOUS = 26;
96 public static final int MID_WINDOW_CLOSE = 27;
97
55d2b2c2
KL
98 // Help menu
99 public static final int MID_HELP_CONTENTS = 40;
100 public static final int MID_HELP_INDEX = 41;
101 public static final int MID_HELP_SEARCH = 42;
102 public static final int MID_HELP_PREVIOUS = 43;
103 public static final int MID_HELP_HELP = 44;
104 public static final int MID_HELP_ACTIVE_FILE = 45;
105 public static final int MID_ABOUT = 46;
106
1c19fdea
KL
107 // Other
108 public static final int MID_REPAINT = 50;
109
928811d8
KL
110 /**
111 * Public constructor.
112 *
113 * @param parent parent application
114 * @param x column relative to parent
115 * @param y row relative to parent
116 * @param label mnemonic menu title. Label must contain a keyboard
117 * shortcut (mnemonic), denoted by prefixing a letter with "&",
118 * e.g. "&File"
119 */
120 public TMenu(final TApplication parent, final int x, final int y,
121 final String label) {
122
123 super(parent, label, x, y, parent.getScreen().getWidth(),
124 parent.getScreen().getHeight());
125
928811d8
KL
126 // Setup the menu shortcut
127 mnemonic = new MnemonicString(label);
128 setTitle(mnemonic.getRawLabel());
129 assert (mnemonic.getShortcutIdx() >= 0);
130
131 // Recompute width and height to reflect an empty menu
132 setWidth(getTitle().length() + 4);
133 setHeight(2);
134
135 setActive(false);
136 }
137
138 /**
139 * Draw a top-level menu with title and menu items.
140 */
141 @Override
142 public void draw() {
928811d8
KL
143 CellAttributes background = getTheme().getColor("tmenu");
144
7c870d89 145 assert (isAbsoluteActive());
928811d8
KL
146
147 // Fill in the interior background
148 for (int i = 0; i < getHeight(); i++) {
149 hLineXY(0, i, getWidth(), ' ', background);
150 }
151
152 // Draw the box
153 char cTopLeft;
154 char cTopRight;
155 char cBottomLeft;
156 char cBottomRight;
157 char cHSide;
158
159 cTopLeft = GraphicsChars.ULCORNER;
160 cTopRight = GraphicsChars.URCORNER;
161 cBottomLeft = GraphicsChars.LLCORNER;
162 cBottomRight = GraphicsChars.LRCORNER;
163 cHSide = GraphicsChars.SINGLE_BAR;
164
165 // Place the corner characters
166 putCharXY(1, 0, cTopLeft, background);
167 putCharXY(getWidth() - 2, 0, cTopRight, background);
168 putCharXY(1, getHeight() - 1, cBottomLeft, background);
169 putCharXY(getWidth() - 2, getHeight() - 1, cBottomRight, background);
170
171 // Draw the box lines
172 hLineXY(1 + 1, 0, getWidth() - 4, cHSide, background);
173 hLineXY(1 + 1, getHeight() - 1, getWidth() - 4, cHSide, background);
174
175 // Draw a shadow
176 getScreen().drawBoxShadow(0, 0, getWidth(), getHeight());
177 }
178
179 /**
180 * Handle mouse button presses.
181 *
182 * @param mouse mouse button event
183 */
184 @Override
185 public void onMouseDown(final TMouseEvent mouse) {
186 this.mouse = mouse;
928811d8
KL
187
188 // Pass to children
189 for (TWidget widget: getChildren()) {
190 if (widget.mouseWouldHit(mouse)) {
191 // Dispatch to this child, also activate it
192 activate(widget);
193
194 // Set x and y relative to the child's coordinates
195 mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
196 mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
197 widget.handleEvent(mouse);
198 return;
199 }
200 }
201 }
202
203 /**
204 * Handle mouse button releases.
205 *
206 * @param mouse mouse button release event
207 */
208 @Override
209 public void onMouseUp(final TMouseEvent mouse) {
210 this.mouse = mouse;
928811d8
KL
211
212 // Pass to children
213 for (TWidget widget: getChildren()) {
214 if (widget.mouseWouldHit(mouse)) {
215 // Dispatch to this child, also activate it
216 activate(widget);
217
218 // Set x and y relative to the child's coordinates
219 mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
220 mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
221 widget.handleEvent(mouse);
222 return;
223 }
224 }
225 }
226
227 /**
228 * Handle mouse movements.
229 *
230 * @param mouse mouse motion event
231 */
232 @Override
233 public void onMouseMotion(final TMouseEvent mouse) {
234 this.mouse = mouse;
928811d8
KL
235
236 // See if we should activate a different menu item
237 for (TWidget widget: getChildren()) {
7c870d89 238 if ((mouse.isMouse1())
928811d8
KL
239 && (widget.mouseWouldHit(mouse))
240 ) {
241 // Activate this menu item
242 activate(widget);
243 if (widget instanceof TSubMenu) {
244 ((TSubMenu) widget).dispatch();
245 }
246 return;
247 }
248 }
249 }
250
251 /**
252 * Handle keystrokes.
253 *
254 * @param keypress keystroke event
255 */
256 @Override
257 public void onKeypress(final TKeypressEvent keypress) {
91c9a837
KL
258
259 /*
260 System.err.printf("keypress: %s active child: %s\n", keypress,
261 getActiveChild());
262 */
263
264 if (getActiveChild() != this) {
265 if ((getActiveChild() instanceof TSubMenu)
266 || (getActiveChild() instanceof TMenu)
267 ) {
928811d8
KL
268 getActiveChild().onKeypress(keypress);
269 return;
270 }
271 }
272
273 if (keypress.equals(kbEsc)) {
274 getApplication().closeMenu();
275 return;
276 }
277 if (keypress.equals(kbDown)) {
278 switchWidget(true);
279 return;
280 }
281 if (keypress.equals(kbUp)) {
282 switchWidget(false);
283 return;
284 }
285 if (keypress.equals(kbRight)) {
91c9a837 286 getApplication().switchMenu(true);
928811d8
KL
287 return;
288 }
289 if (keypress.equals(kbLeft)) {
290 if (isSubMenu) {
291 getApplication().closeSubMenu();
292 } else {
293 getApplication().switchMenu(false);
294 }
295 return;
296 }
297
298 // Switch to a menuItem if it has an mnemonic
7c870d89
KL
299 if (!keypress.getKey().isFnKey()
300 && !keypress.getKey().isAlt()
301 && !keypress.getKey().isCtrl()) {
928811d8
KL
302 for (TWidget widget: getChildren()) {
303 TMenuItem item = (TMenuItem) widget;
304 if ((item.getMnemonic() != null)
305 && (Character.toLowerCase(item.getMnemonic().getShortcut())
7c870d89 306 == Character.toLowerCase(keypress.getKey().getChar()))
928811d8
KL
307 ) {
308 // Send an enter keystroke to it
309 activate(item);
310 item.handleEvent(new TKeypressEvent(kbEnter));
311 return;
312 }
313 }
314 }
315
316 // Dispatch the keypress to an active widget
317 for (TWidget widget: getChildren()) {
7c870d89 318 if (widget.isActive()) {
928811d8
KL
319 widget.handleEvent(keypress);
320 return;
321 }
322 }
323 }
324
325 /**
efb7af1f 326 * Convenience function to add a menu item.
928811d8
KL
327 *
328 * @param id menu item ID. Must be greater than 1024.
329 * @param label menu item label
928811d8
KL
330 * @return the new menu item
331 */
329fd62e 332 public TMenuItem addItem(final int id, final String label) {
928811d8 333 assert (id >= 1024);
efb7af1f 334 return addItemInternal(id, label, null);
928811d8
KL
335 }
336
337 /**
338 * Convenience function to add a custom menu item.
339 *
340 * @param id menu item ID. Must be greater than 1024.
341 * @param label menu item label
342 * @param key global keyboard accelerator
343 * @return the new menu item
344 */
329fd62e 345 public TMenuItem addItem(final int id, final String label,
928811d8
KL
346 final TKeypress key) {
347
efb7af1f
KL
348 assert (id >= 1024);
349 return addItemInternal(id, label, key);
928811d8
KL
350 }
351
352 /**
efb7af1f 353 * Convenience function to add a custom menu item.
928811d8
KL
354 *
355 * @param id menu item ID. Must be greater than 1024.
356 * @param label menu item label
efb7af1f 357 * @param key global keyboard accelerator
928811d8
KL
358 * @return the new menu item
359 */
efb7af1f
KL
360 private TMenuItem addItemInternal(final int id, final String label,
361 final TKeypress key) {
928811d8 362
928811d8
KL
363 int newY = getChildren().size() + 1;
364 assert (newY < getHeight());
365
366 TMenuItem menuItem = new TMenuItem(this, id, 1, newY, label);
efb7af1f 367 menuItem.setKey(key);
928811d8
KL
368 setHeight(getHeight() + 1);
369 if (menuItem.getWidth() + 2 > getWidth()) {
370 setWidth(menuItem.getWidth() + 2);
371 }
372 for (TWidget widget: getChildren()) {
373 widget.setWidth(getWidth() - 2);
374 }
efb7af1f 375 getApplication().addMenuItem(menuItem);
928811d8
KL
376 getApplication().recomputeMenuX();
377 activate(0);
378 return menuItem;
379 }
380
381 /**
382 * Convenience function to add one of the default menu items.
383 *
384 * @param id menu item ID. Must be between 0 (inclusive) and 1023
385 * (inclusive).
386 * @return the new menu item
387 */
329fd62e 388 public TMenuItem addDefaultItem(final int id) {
928811d8
KL
389 assert (id >= 0);
390 assert (id < 1024);
391
392 String label;
393 TKeypress key = null;
928811d8
KL
394
395 switch (id) {
396
397 case MID_EXIT:
339652cc 398 label = i18n.getString("menuExit");
928811d8
KL
399 key = kbAltX;
400 break;
401
402 case MID_SHELL:
339652cc 403 label = i18n.getString("menuShell");
928811d8
KL
404 break;
405
406 case MID_OPEN_FILE:
339652cc 407 label = i18n.getString("menuOpen");
928811d8
KL
408 key = kbAltO;
409 break;
410
411 case MID_CUT:
339652cc 412 label = i18n.getString("menuCut");
928811d8
KL
413 key = kbCtrlX;
414 break;
415 case MID_COPY:
339652cc 416 label = i18n.getString("menuCopy");
928811d8
KL
417 key = kbCtrlC;
418 break;
419 case MID_PASTE:
339652cc 420 label = i18n.getString("menuPaste");
928811d8
KL
421 key = kbCtrlV;
422 break;
423 case MID_CLEAR:
339652cc 424 label = i18n.getString("menuClear");
30bd4abd 425 // key = kbDel;
928811d8
KL
426 break;
427
428 case MID_TILE:
339652cc 429 label = i18n.getString("menuWindowTile");
928811d8
KL
430 break;
431 case MID_CASCADE:
339652cc 432 label = i18n.getString("menuWindowCascade");
928811d8
KL
433 break;
434 case MID_CLOSE_ALL:
339652cc 435 label = i18n.getString("menuWindowCloseAll");
928811d8
KL
436 break;
437 case MID_WINDOW_MOVE:
339652cc 438 label = i18n.getString("menuWindowMove");
928811d8
KL
439 key = kbCtrlF5;
440 break;
441 case MID_WINDOW_ZOOM:
339652cc 442 label = i18n.getString("menuWindowZoom");
928811d8
KL
443 key = kbF5;
444 break;
445 case MID_WINDOW_NEXT:
339652cc 446 label = i18n.getString("menuWindowNext");
928811d8
KL
447 key = kbF6;
448 break;
449 case MID_WINDOW_PREVIOUS:
339652cc 450 label = i18n.getString("menuWindowPrevious");
928811d8
KL
451 key = kbShiftF6;
452 break;
453 case MID_WINDOW_CLOSE:
339652cc 454 label = i18n.getString("menuWindowClose");
5dfd1c11 455 key = kbCtrlW;
928811d8
KL
456 break;
457
55d2b2c2 458 case MID_HELP_CONTENTS:
339652cc 459 label = i18n.getString("menuHelpContents");
55d2b2c2
KL
460 break;
461 case MID_HELP_INDEX:
339652cc 462 label = i18n.getString("menuHelpIndex");
55d2b2c2
KL
463 key = kbShiftF1;
464 break;
465 case MID_HELP_SEARCH:
339652cc 466 label = i18n.getString("menuHelpSearch");
55d2b2c2
KL
467 key = kbCtrlF1;
468 break;
469 case MID_HELP_PREVIOUS:
339652cc 470 label = i18n.getString("menuHelpPrevious");
55d2b2c2
KL
471 key = kbAltF1;
472 break;
473 case MID_HELP_HELP:
339652cc 474 label = i18n.getString("menuHelpHelp");
55d2b2c2
KL
475 break;
476 case MID_HELP_ACTIVE_FILE:
339652cc 477 label = i18n.getString("menuHelpActive");
55d2b2c2
KL
478 break;
479 case MID_ABOUT:
339652cc 480 label = i18n.getString("menuHelpAbout");
55d2b2c2
KL
481 break;
482
1c19fdea
KL
483 case MID_REPAINT:
484 label = i18n.getString("menuRepaintDesktop");
485 break;
486
928811d8
KL
487 default:
488 throw new IllegalArgumentException("Invalid menu ID: " + id);
489 }
490
efb7af1f 491 return addItemInternal(id, label, key);
928811d8
KL
492 }
493
494 /**
495 * Convenience function to add a menu separator.
496 */
329fd62e 497 public void addSeparator() {
928811d8
KL
498 int newY = getChildren().size() + 1;
499 assert (newY < getHeight());
500
cf9af8df
KL
501 // We just have to construct it, don't need to hang onto what it
502 // makes.
503 new TMenuSeparator(this, 1, newY);
928811d8
KL
504 setHeight(getHeight() + 1);
505 }
506
507 /**
508 * Convenience function to add a sub-menu.
509 *
510 * @param title menu title. Title must contain a keyboard shortcut,
511 * denoted by prefixing a letter with "&", e.g. "&File"
512 * @return the new sub-menu
513 */
329fd62e 514 public TSubMenu addSubMenu(final String title) {
928811d8
KL
515 int newY = getChildren().size() + 1;
516 assert (newY < getHeight());
517
518 TSubMenu subMenu = new TSubMenu(this, title, 1, newY);
519 setHeight(getHeight() + 1);
520 if (subMenu.getWidth() + 2 > getWidth()) {
521 setWidth(subMenu.getWidth() + 2);
522 }
523 for (TWidget widget: getChildren()) {
524 widget.setWidth(getWidth() - 2);
525 }
526 getApplication().recomputeMenuX();
527 activate(0);
528 subMenu.menu.setX(getX() + getWidth() - 2);
529
530 return subMenu;
531 }
532
533}