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