Localize strings
[nikiroo-utils.git] / src / jexer / menu / TMenu.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2017 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.menu;
30
31 import java.util.ResourceBundle;
32
33 import jexer.TApplication;
34 import jexer.TKeypress;
35 import jexer.TWidget;
36 import jexer.TWindow;
37 import jexer.bits.CellAttributes;
38 import jexer.bits.GraphicsChars;
39 import jexer.bits.MnemonicString;
40 import jexer.event.TKeypressEvent;
41 import jexer.event.TMouseEvent;
42 import static jexer.TKeypress.*;
43
44 /**
45 * TMenu is a top-level collection of TMenuItems.
46 */
47 public final class TMenu extends TWindow {
48
49 /**
50 * Translated strings.
51 */
52 private static final ResourceBundle i18n = ResourceBundle.getBundle(TMenu.class.getName());
53
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
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
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
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() {
140 CellAttributes background = getTheme().getColor("tmenu");
141
142 assert (isAbsoluteActive());
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;
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;
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;
232
233 // See if we should activate a different menu item
234 for (TWidget widget: getChildren()) {
235 if ((mouse.isMouse1())
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) {
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 ) {
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)) {
283 getApplication().switchMenu(true);
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
296 if (!keypress.getKey().isFnKey()
297 && !keypress.getKey().isAlt()
298 && !keypress.getKey().isCtrl()) {
299 for (TWidget widget: getChildren()) {
300 TMenuItem item = (TMenuItem) widget;
301 if ((item.getMnemonic() != null)
302 && (Character.toLowerCase(item.getMnemonic().getShortcut())
303 == Character.toLowerCase(keypress.getKey().getChar()))
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()) {
315 if (widget.isActive()) {
316 widget.handleEvent(keypress);
317 return;
318 }
319 }
320 }
321
322 /**
323 * Convenience function to add a menu item.
324 *
325 * @param id menu item ID. Must be greater than 1024.
326 * @param label menu item label
327 * @return the new menu item
328 */
329 public TMenuItem addItem(final int id, final String label) {
330 assert (id >= 1024);
331 return addItemInternal(id, label, null);
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 */
342 public TMenuItem addItem(final int id, final String label,
343 final TKeypress key) {
344
345 assert (id >= 1024);
346 return addItemInternal(id, label, key);
347 }
348
349 /**
350 * Convenience function to add a custom menu item.
351 *
352 * @param id menu item ID. Must be greater than 1024.
353 * @param label menu item label
354 * @param key global keyboard accelerator
355 * @return the new menu item
356 */
357 private TMenuItem addItemInternal(final int id, final String label,
358 final TKeypress key) {
359
360 int newY = getChildren().size() + 1;
361 assert (newY < getHeight());
362
363 TMenuItem menuItem = new TMenuItem(this, id, 1, newY, label);
364 menuItem.setKey(key);
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 }
372 getApplication().addMenuItem(menuItem);
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 */
385 public TMenuItem addDefaultItem(final int id) {
386 assert (id >= 0);
387 assert (id < 1024);
388
389 String label;
390 TKeypress key = null;
391
392 switch (id) {
393
394 case MID_EXIT:
395 label = i18n.getString("menuExit");
396 key = kbAltX;
397 break;
398
399 case MID_SHELL:
400 label = i18n.getString("menuShell");
401 break;
402
403 case MID_OPEN_FILE:
404 label = i18n.getString("menuOpen");
405 key = kbAltO;
406 break;
407
408 case MID_CUT:
409 label = i18n.getString("menuCut");
410 key = kbCtrlX;
411 break;
412 case MID_COPY:
413 label = i18n.getString("menuCopy");
414 key = kbCtrlC;
415 break;
416 case MID_PASTE:
417 label = i18n.getString("menuPaste");
418 key = kbCtrlV;
419 break;
420 case MID_CLEAR:
421 label = i18n.getString("menuClear");
422 // key = kbDel;
423 break;
424
425 case MID_TILE:
426 label = i18n.getString("menuWindowTile");
427 break;
428 case MID_CASCADE:
429 label = i18n.getString("menuWindowCascade");
430 break;
431 case MID_CLOSE_ALL:
432 label = i18n.getString("menuWindowCloseAll");
433 break;
434 case MID_WINDOW_MOVE:
435 label = i18n.getString("menuWindowMove");
436 key = kbCtrlF5;
437 break;
438 case MID_WINDOW_ZOOM:
439 label = i18n.getString("menuWindowZoom");
440 key = kbF5;
441 break;
442 case MID_WINDOW_NEXT:
443 label = i18n.getString("menuWindowNext");
444 key = kbF6;
445 break;
446 case MID_WINDOW_PREVIOUS:
447 label = i18n.getString("menuWindowPrevious");
448 key = kbShiftF6;
449 break;
450 case MID_WINDOW_CLOSE:
451 label = i18n.getString("menuWindowClose");
452 key = kbCtrlW;
453 break;
454
455 case MID_HELP_CONTENTS:
456 label = i18n.getString("menuHelpContents");
457 break;
458 case MID_HELP_INDEX:
459 label = i18n.getString("menuHelpIndex");
460 key = kbShiftF1;
461 break;
462 case MID_HELP_SEARCH:
463 label = i18n.getString("menuHelpSearch");
464 key = kbCtrlF1;
465 break;
466 case MID_HELP_PREVIOUS:
467 label = i18n.getString("menuHelpPrevious");
468 key = kbAltF1;
469 break;
470 case MID_HELP_HELP:
471 label = i18n.getString("menuHelpHelp");
472 break;
473 case MID_HELP_ACTIVE_FILE:
474 label = i18n.getString("menuHelpActive");
475 break;
476 case MID_ABOUT:
477 label = i18n.getString("menuHelpAbout");
478 break;
479
480 default:
481 throw new IllegalArgumentException("Invalid menu ID: " + id);
482 }
483
484 return addItemInternal(id, label, key);
485 }
486
487 /**
488 * Convenience function to add a menu separator.
489 */
490 public void addSeparator() {
491 int newY = getChildren().size() + 1;
492 assert (newY < getHeight());
493
494 // We just have to construct it, don't need to hang onto what it
495 // makes.
496 new TMenuSeparator(this, 1, newY);
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 */
507 public TSubMenu addSubMenu(final String title) {
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 }