Merge branch 'subtree'
[fanfix.git] / src / jexer / TButton.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;
30
31 import jexer.bits.CellAttributes;
32 import jexer.bits.Color;
33 import jexer.bits.GraphicsChars;
34 import jexer.bits.MnemonicString;
35 import jexer.bits.StringUtils;
36 import jexer.event.TKeypressEvent;
37 import jexer.event.TMouseEvent;
38 import static jexer.TKeypress.kbEnter;
39 import static jexer.TKeypress.kbSpace;
40
41 /**
42 * TButton implements a simple button. To make the button do something, pass
43 * a TAction class to its constructor.
44 *
45 * @see TAction#DO()
46 */
47 public class TButton extends TWidget {
48
49 // ------------------------------------------------------------------------
50 // Variables --------------------------------------------------------------
51 // ------------------------------------------------------------------------
52
53 /**
54 * The shortcut and button text.
55 */
56 private MnemonicString mnemonic;
57
58 /**
59 * Remember mouse state.
60 */
61 private TMouseEvent mouse;
62
63 /**
64 * True when the button is being pressed and held down.
65 */
66 private boolean inButtonPress = false;
67
68 /**
69 * The action to perform when the button is clicked.
70 */
71 private TAction action;
72
73 /**
74 * The background color used for the button "shadow", or null for "no
75 * shadow".
76 */
77 private CellAttributes shadowColor;
78
79 // ------------------------------------------------------------------------
80 // Constructors -----------------------------------------------------------
81 // ------------------------------------------------------------------------
82
83 /**
84 * Private constructor.
85 *
86 * @param parent parent widget
87 * @param text label on the button
88 * @param x column relative to parent
89 * @param y row relative to parent
90 */
91 private TButton(final TWidget parent, final String text,
92 final int x, final int y) {
93
94 // Set parent and window
95 super(parent);
96
97 mnemonic = new MnemonicString(text);
98
99 setX(x);
100 setY(y);
101 super.setHeight(2);
102 super.setWidth(StringUtils.width(mnemonic.getRawLabel()) + 3);
103
104 shadowColor = new CellAttributes();
105 shadowColor.setTo(getWindow().getBackground());
106 shadowColor.setForeColor(Color.BLACK);
107 shadowColor.setBold(false);
108
109 // Since we set dimensions after TWidget's constructor, we need to
110 // update the layout manager.
111 if (getParent().getLayoutManager() != null) {
112 getParent().getLayoutManager().remove(this);
113 getParent().getLayoutManager().add(this);
114 }
115 }
116
117 /**
118 * Public constructor.
119 *
120 * @param parent parent widget
121 * @param text label on the button
122 * @param x column relative to parent
123 * @param y row relative to parent
124 * @param action to call when button is pressed
125 */
126 public TButton(final TWidget parent, final String text,
127 final int x, final int y, final TAction action) {
128
129 this(parent, text, x, y);
130 this.action = action;
131 }
132
133 /**
134 * The action to call when the button is pressed.
135 **/
136 public TAction getAction() {
137 return action;
138 }
139
140 /**
141 * The action to call when the button is pressed.
142 **/
143 public void setAction(TAction action) {
144 this.action = action;
145 }
146
147 // ------------------------------------------------------------------------
148 // Event handlers ---------------------------------------------------------
149 // ------------------------------------------------------------------------
150
151 /**
152 * Returns true if the mouse is currently on the button.
153 *
154 * @return if true the mouse is currently on the button
155 */
156 private boolean mouseOnButton() {
157 int rightEdge = getWidth() - 1;
158 if (inButtonPress) {
159 rightEdge++;
160 }
161 if ((mouse != null)
162 && (mouse.getY() == 0)
163 && (mouse.getX() >= 0)
164 && (mouse.getX() < rightEdge)
165 ) {
166 return true;
167 }
168 return false;
169 }
170
171 /**
172 * Handle mouse button presses.
173 *
174 * @param mouse mouse button event
175 */
176 @Override
177 public void onMouseDown(final TMouseEvent mouse) {
178 this.mouse = mouse;
179
180 if ((mouseOnButton()) && (mouse.isMouse1())) {
181 // Begin button press
182 inButtonPress = true;
183 }
184 }
185
186 /**
187 * Handle mouse button releases.
188 *
189 * @param mouse mouse button release event
190 */
191 @Override
192 public void onMouseUp(final TMouseEvent mouse) {
193 this.mouse = mouse;
194
195 if (inButtonPress && mouse.isMouse1()) {
196 // Dispatch the event
197 dispatch();
198 }
199
200 }
201
202 /**
203 * Handle mouse movements.
204 *
205 * @param mouse mouse motion event
206 */
207 @Override
208 public void onMouseMotion(final TMouseEvent mouse) {
209 this.mouse = mouse;
210
211 if (!mouseOnButton()) {
212 inButtonPress = false;
213 }
214 }
215
216 /**
217 * Handle keystrokes.
218 *
219 * @param keypress keystroke event
220 */
221 @Override
222 public void onKeypress(final TKeypressEvent keypress) {
223 if (keypress.equals(kbEnter)
224 || keypress.equals(kbSpace)
225 ) {
226 // Dispatch
227 dispatch();
228 return;
229 }
230
231 // Pass to parent for the things we don't care about.
232 super.onKeypress(keypress);
233 }
234
235 // ------------------------------------------------------------------------
236 // TWidget ----------------------------------------------------------------
237 // ------------------------------------------------------------------------
238
239 /**
240 * Override TWidget's width: we can only set width at construction time.
241 *
242 * @param width new widget width (ignored)
243 */
244 @Override
245 public void setWidth(final int width) {
246 // Do nothing
247 }
248
249 /**
250 * Override TWidget's height: we can only set height at construction
251 * time.
252 *
253 * @param height new widget height (ignored)
254 */
255 @Override
256 public void setHeight(final int height) {
257 // Do nothing
258 }
259
260 /**
261 * Draw a button with a shadow.
262 */
263 @Override
264 public void draw() {
265 CellAttributes buttonColor;
266 CellAttributes menuMnemonicColor;
267
268 if (!isEnabled()) {
269 buttonColor = getTheme().getColor("tbutton.disabled");
270 menuMnemonicColor = getTheme().getColor("tbutton.disabled");
271 } else if (isAbsoluteActive()) {
272 buttonColor = getTheme().getColor("tbutton.active");
273 menuMnemonicColor = getTheme().getColor("tbutton.mnemonic.highlighted");
274 } else {
275 buttonColor = getTheme().getColor("tbutton.inactive");
276 menuMnemonicColor = getTheme().getColor("tbutton.mnemonic");
277 }
278
279 if (inButtonPress) {
280 putCharXY(1, 0, ' ', buttonColor);
281 putStringXY(2, 0, mnemonic.getRawLabel(), buttonColor);
282 putCharXY(getWidth() - 1, 0, ' ', buttonColor);
283 } else {
284 putCharXY(0, 0, ' ', buttonColor);
285 putStringXY(1, 0, mnemonic.getRawLabel(), buttonColor);
286 putCharXY(getWidth() - 2, 0, ' ', buttonColor);
287
288 if (shadowColor != null) {
289 putCharXY(getWidth() - 1, 0,
290 GraphicsChars.CP437[0xDC], shadowColor);
291 hLineXY(1, 1, getWidth() - 1,
292 GraphicsChars.CP437[0xDF], shadowColor);
293 }
294 }
295 if (mnemonic.getScreenShortcutIdx() >= 0) {
296 if (inButtonPress) {
297 putCharXY(2 + mnemonic.getScreenShortcutIdx(), 0,
298 mnemonic.getShortcut(), menuMnemonicColor);
299 } else {
300 putCharXY(1 + mnemonic.getScreenShortcutIdx(), 0,
301 mnemonic.getShortcut(), menuMnemonicColor);
302 }
303 }
304 }
305
306 // ------------------------------------------------------------------------
307 // TButton ----------------------------------------------------------------
308 // ------------------------------------------------------------------------
309
310 /**
311 * Get the mnemonic string for this button.
312 *
313 * @return mnemonic string
314 */
315 public MnemonicString getMnemonic() {
316 return mnemonic;
317 }
318
319 /**
320 * Act as though the button was pressed. This is useful for other UI
321 * elements to get the same action as if the user clicked the button.
322 */
323 public void dispatch() {
324 if (action != null) {
325 action.DO(this);
326 inButtonPress = false;
327 }
328 }
329
330 /**
331 * Set the background color used for the button "shadow". If null, no
332 * shadow will be drawn.
333 *
334 * @param color the new background color, or null for no shadow
335 */
336 public void setShadowColor(final CellAttributes color) {
337 if (color != null) {
338 shadowColor = new CellAttributes();
339 shadowColor.setTo(color);
340 shadowColor.setForeColor(Color.BLACK);
341 shadowColor.setBold(false);
342 } else {
343 shadowColor = null;
344 }
345 }
346
347 }