Merge branch 'upstream' into subtree
[nikiroo-utils.git] / menu / TMenuItem.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 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 jexer.TKeypress;
32 import jexer.TWidget;
33 import jexer.bits.CellAttributes;
34 import jexer.bits.GraphicsChars;
35 import jexer.bits.MnemonicString;
36 import jexer.bits.StringUtils;
37 import jexer.event.TKeypressEvent;
38 import jexer.event.TMouseEvent;
39 import jexer.event.TMenuEvent;
40 import static jexer.TKeypress.*;
41
42 /**
43 * TMenuItem implements a menu item.
44 */
45 public class TMenuItem extends TWidget {
46
47 // ------------------------------------------------------------------------
48 // Variables --------------------------------------------------------------
49 // ------------------------------------------------------------------------
50
51 /**
52 * Label for this menu item.
53 */
54 private String label;
55
56 /**
57 * Menu ID. IDs less than 1024 are reserved for common system
58 * functions. Existing ones are defined in TMenu, i.e. TMenu.MID_EXIT.
59 */
60 private int id = TMenu.MID_UNUSED;
61
62 /**
63 * When true, this item can be checked or unchecked.
64 */
65 private boolean checkable = false;
66
67 /**
68 * When true, this item is checked.
69 */
70 private boolean checked = false;
71
72 /**
73 * Global shortcut key.
74 */
75 private TKeypress key;
76
77 /**
78 * The title string. Use '&' to specify a mnemonic, i.e. "&File" will
79 * highlight the 'F' and allow 'f' or 'F' to select it.
80 */
81 private MnemonicString mnemonic;
82
83 /**
84 * An optional 2-cell-wide picture/icon for this item.
85 */
86 private int icon = -1;
87
88 // ------------------------------------------------------------------------
89 // Constructors -----------------------------------------------------------
90 // ------------------------------------------------------------------------
91
92 /**
93 * Package private constructor.
94 *
95 * @param parent parent widget
96 * @param id menu id
97 * @param x column relative to parent
98 * @param y row relative to parent
99 * @param label menu item title
100 */
101 TMenuItem(final TMenu parent, final int id, final int x, final int y,
102 final String label) {
103
104 this(parent, id, x, y, label, -1);
105 }
106
107 /**
108 * Package private constructor.
109 *
110 * @param parent parent widget
111 * @param id menu id
112 * @param x column relative to parent
113 * @param y row relative to parent
114 * @param label menu item title
115 * @param icon icon picture/emoji
116 */
117 TMenuItem(final TMenu parent, final int id, final int x, final int y,
118 final String label, final int icon) {
119
120 // Set parent and window
121 super(parent);
122
123 mnemonic = new MnemonicString(label);
124
125 setX(x);
126 setY(y);
127 setHeight(1);
128 this.label = mnemonic.getRawLabel();
129 if (parent.useIcons) {
130 setWidth(StringUtils.width(label) + 6);
131 } else {
132 setWidth(StringUtils.width(label) + 4);
133 }
134 this.id = id;
135 this.icon = icon;
136
137 // Default state for some known menu items
138 switch (id) {
139
140 case TMenu.MID_CUT:
141 setEnabled(false);
142 break;
143 case TMenu.MID_COPY:
144 setEnabled(false);
145 break;
146 case TMenu.MID_PASTE:
147 setEnabled(false);
148 break;
149 case TMenu.MID_CLEAR:
150 setEnabled(false);
151 break;
152
153 case TMenu.MID_TILE:
154 break;
155 case TMenu.MID_CASCADE:
156 break;
157 case TMenu.MID_CLOSE_ALL:
158 break;
159 case TMenu.MID_WINDOW_MOVE:
160 break;
161 case TMenu.MID_WINDOW_ZOOM:
162 break;
163 case TMenu.MID_WINDOW_NEXT:
164 break;
165 case TMenu.MID_WINDOW_PREVIOUS:
166 break;
167 case TMenu.MID_WINDOW_CLOSE:
168 break;
169 default:
170 break;
171 }
172
173 }
174
175 // ------------------------------------------------------------------------
176 // Event handlers ---------------------------------------------------------
177 // ------------------------------------------------------------------------
178
179 /**
180 * Returns true if the mouse is currently on the menu item.
181 *
182 * @param mouse mouse event
183 * @return if true then the mouse is currently on this item
184 */
185 private boolean mouseOnMenuItem(final TMouseEvent mouse) {
186 if ((mouse.getY() == 0)
187 && (mouse.getX() >= 0)
188 && (mouse.getX() < getWidth())
189 ) {
190 return true;
191 }
192 return false;
193 }
194
195 /**
196 * Handle mouse button releases.
197 *
198 * @param mouse mouse button release event
199 */
200 @Override
201 public void onMouseUp(final TMouseEvent mouse) {
202 if ((mouseOnMenuItem(mouse)) && (mouse.isMouse1())) {
203 dispatch();
204 return;
205 }
206 }
207
208 /**
209 * Handle keystrokes.
210 *
211 * @param keypress keystroke event
212 */
213 @Override
214 public void onKeypress(final TKeypressEvent keypress) {
215 if (keypress.equals(kbEnter)) {
216 dispatch();
217 return;
218 }
219
220 // Pass to parent for the things we don't care about.
221 super.onKeypress(keypress);
222 }
223
224 // ------------------------------------------------------------------------
225 // TWidget ----------------------------------------------------------------
226 // ------------------------------------------------------------------------
227
228 /**
229 * Draw a menu item with label.
230 */
231 @Override
232 public void draw() {
233 CellAttributes background = getTheme().getColor("tmenu");
234 CellAttributes menuColor;
235 CellAttributes menuMnemonicColor;
236 if (isAbsoluteActive()) {
237 menuColor = getTheme().getColor("tmenu.highlighted");
238 menuMnemonicColor = getTheme().getColor("tmenu.mnemonic.highlighted");
239 } else {
240 if (isEnabled()) {
241 menuColor = getTheme().getColor("tmenu");
242 menuMnemonicColor = getTheme().getColor("tmenu.mnemonic");
243 } else {
244 menuColor = getTheme().getColor("tmenu.disabled");
245 menuMnemonicColor = getTheme().getColor("tmenu.disabled");
246 }
247 }
248
249 boolean useIcons = ((TMenu) getParent()).useIcons;
250
251 char cVSide = GraphicsChars.WINDOW_SIDE;
252 vLineXY(0, 0, 1, cVSide, background);
253 vLineXY(getWidth() - 1, 0, 1, cVSide, background);
254
255 hLineXY(1, 0, getWidth() - 2, ' ', menuColor);
256 putStringXY(2 + (useIcons ? 2 : 0), 0, mnemonic.getRawLabel(),
257 menuColor);
258 if (key != null) {
259 String keyLabel = key.toString();
260 putStringXY((getWidth() - StringUtils.width(keyLabel) - 2), 0,
261 keyLabel, menuColor);
262 }
263 if (mnemonic.getScreenShortcutIdx() >= 0) {
264 putCharXY(2 + (useIcons ? 2 : 0) + mnemonic.getScreenShortcutIdx(),
265 0, mnemonic.getShortcut(), menuMnemonicColor);
266 }
267 if (checked) {
268 assert (checkable);
269 putCharXY(1, 0, GraphicsChars.CHECK, menuColor);
270 }
271 if ((useIcons == true) && (icon != -1)) {
272 putCharXY(2, 0, icon, menuColor);
273 }
274 }
275
276 // ------------------------------------------------------------------------
277 // TMenuItem --------------------------------------------------------------
278 // ------------------------------------------------------------------------
279
280 /**
281 * Get the menu item ID.
282 *
283 * @return the id
284 */
285 public final int getId() {
286 return id;
287 }
288
289 /**
290 * Set checkable flag.
291 *
292 * @param checkable if true, this menu item can be checked/unchecked
293 */
294 public final void setCheckable(final boolean checkable) {
295 this.checkable = checkable;
296 }
297
298 /**
299 * Get checkable flag.
300 *
301 * @return true if this menu item is both checkable and checked
302 */
303 public final boolean getChecked() {
304 return ((checkable == true) && (checked == true));
305 }
306
307 /**
308 * Set checked flag. Note that setting checked on an item checkable will
309 * do nothing.
310 *
311 * @param checked if true, and if this menu item is checkable, then
312 * getChecked() will return true
313 */
314 public final void setChecked(final boolean checked) {
315 if (checkable) {
316 this.checked = checked;
317 } else {
318 this.checked = false;
319 }
320 }
321
322 /**
323 * Get the mnemonic string for this menu item.
324 *
325 * @return mnemonic string
326 */
327 public final MnemonicString getMnemonic() {
328 return mnemonic;
329 }
330
331 /**
332 * Get a global accelerator key for this menu item.
333 *
334 * @return global keyboard accelerator, or null if no key is associated
335 * with this item
336 */
337 public final TKeypress getKey() {
338 return key;
339 }
340
341 /**
342 * Set a global accelerator key for this menu item.
343 *
344 * @param key global keyboard accelerator
345 */
346 public final void setKey(final TKeypress key) {
347 this.key = key;
348
349 if (key != null) {
350 int newWidth = (StringUtils.width(label) + 4 +
351 StringUtils.width(key.toString()) + 2);
352 if (((TMenu) getParent()).useIcons) {
353 newWidth += 2;
354 }
355 if (newWidth > getWidth()) {
356 setWidth(newWidth);
357 }
358 }
359 }
360
361 /**
362 * Get a picture/emoji icon for this menu item.
363 *
364 * @return the codepoint, or -1 if no icon is specified for this menu
365 * item
366 */
367 public final int getIcon() {
368 return icon;
369 }
370
371 /**
372 * Set a picture/emoji icon for this menu item.
373 *
374 * @param icon a codepoint, or -1 to unset the icon
375 */
376 public final void setIcon(final int icon) {
377 this.icon = icon;
378 }
379
380 /**
381 * Dispatch event(s) due to selection or click.
382 */
383 public void dispatch() {
384 assert (isEnabled());
385
386 getApplication().postMenuEvent(new TMenuEvent(id));
387 if (checkable) {
388 checked = !checked;
389 }
390 }
391
392 }