Add 'src/jexer/' from commit 'cf01c92f5809a0732409e280fb0f32f27393618d'
[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 // Event handlers ---------------------------------------------------------
135 // ------------------------------------------------------------------------
136
137 /**
138 * Returns true if the mouse is currently on the button.
139 *
140 * @return if true the mouse is currently on the button
141 */
142 private boolean mouseOnButton() {
143 int rightEdge = getWidth() - 1;
144 if (inButtonPress) {
145 rightEdge++;
146 }
147 if ((mouse != null)
148 && (mouse.getY() == 0)
149 && (mouse.getX() >= 0)
150 && (mouse.getX() < rightEdge)
151 ) {
152 return true;
153 }
154 return false;
155 }
156
157 /**
158 * Handle mouse button presses.
159 *
160 * @param mouse mouse button event
161 */
162 @Override
163 public void onMouseDown(final TMouseEvent mouse) {
164 this.mouse = mouse;
165
166 if ((mouseOnButton()) && (mouse.isMouse1())) {
167 // Begin button press
168 inButtonPress = true;
169 }
170 }
171
172 /**
173 * Handle mouse button releases.
174 *
175 * @param mouse mouse button release event
176 */
177 @Override
178 public void onMouseUp(final TMouseEvent mouse) {
179 this.mouse = mouse;
180
181 if (inButtonPress && mouse.isMouse1()) {
182 // Dispatch the event
183 dispatch();
184 }
185
186 }
187
188 /**
189 * Handle mouse movements.
190 *
191 * @param mouse mouse motion event
192 */
193 @Override
194 public void onMouseMotion(final TMouseEvent mouse) {
195 this.mouse = mouse;
196
197 if (!mouseOnButton()) {
198 inButtonPress = false;
199 }
200 }
201
202 /**
203 * Handle keystrokes.
204 *
205 * @param keypress keystroke event
206 */
207 @Override
208 public void onKeypress(final TKeypressEvent keypress) {
209 if (keypress.equals(kbEnter)
210 || keypress.equals(kbSpace)
211 ) {
212 // Dispatch
213 dispatch();
214 return;
215 }
216
217 // Pass to parent for the things we don't care about.
218 super.onKeypress(keypress);
219 }
220
221 // ------------------------------------------------------------------------
222 // TWidget ----------------------------------------------------------------
223 // ------------------------------------------------------------------------
224
225 /**
226 * Override TWidget's width: we can only set width at construction time.
227 *
228 * @param width new widget width (ignored)
229 */
230 @Override
231 public void setWidth(final int width) {
232 // Do nothing
233 }
234
235 /**
236 * Override TWidget's height: we can only set height at construction
237 * time.
238 *
239 * @param height new widget height (ignored)
240 */
241 @Override
242 public void setHeight(final int height) {
243 // Do nothing
244 }
245
246 /**
247 * Draw a button with a shadow.
248 */
249 @Override
250 public void draw() {
251 CellAttributes buttonColor;
252 CellAttributes menuMnemonicColor;
253
254 if (!isEnabled()) {
255 buttonColor = getTheme().getColor("tbutton.disabled");
256 menuMnemonicColor = getTheme().getColor("tbutton.disabled");
257 } else if (isAbsoluteActive()) {
258 buttonColor = getTheme().getColor("tbutton.active");
259 menuMnemonicColor = getTheme().getColor("tbutton.mnemonic.highlighted");
260 } else {
261 buttonColor = getTheme().getColor("tbutton.inactive");
262 menuMnemonicColor = getTheme().getColor("tbutton.mnemonic");
263 }
264
265 if (inButtonPress) {
266 putCharXY(1, 0, ' ', buttonColor);
267 putStringXY(2, 0, mnemonic.getRawLabel(), buttonColor);
268 putCharXY(getWidth() - 1, 0, ' ', buttonColor);
269 } else {
270 putCharXY(0, 0, ' ', buttonColor);
271 putStringXY(1, 0, mnemonic.getRawLabel(), buttonColor);
272 putCharXY(getWidth() - 2, 0, ' ', buttonColor);
273
274 if (shadowColor != null) {
275 putCharXY(getWidth() - 1, 0,
276 GraphicsChars.CP437[0xDC], shadowColor);
277 hLineXY(1, 1, getWidth() - 1,
278 GraphicsChars.CP437[0xDF], shadowColor);
279 }
280 }
281 if (mnemonic.getScreenShortcutIdx() >= 0) {
282 if (inButtonPress) {
283 putCharXY(2 + mnemonic.getScreenShortcutIdx(), 0,
284 mnemonic.getShortcut(), menuMnemonicColor);
285 } else {
286 putCharXY(1 + mnemonic.getScreenShortcutIdx(), 0,
287 mnemonic.getShortcut(), menuMnemonicColor);
288 }
289 }
290 }
291
292 // ------------------------------------------------------------------------
293 // TButton ----------------------------------------------------------------
294 // ------------------------------------------------------------------------
295
296 /**
297 * Get the mnemonic string for this button.
298 *
299 * @return mnemonic string
300 */
301 public MnemonicString getMnemonic() {
302 return mnemonic;
303 }
304
305 /**
306 * Act as though the button was pressed. This is useful for other UI
307 * elements to get the same action as if the user clicked the button.
308 */
309 public void dispatch() {
310 if (action != null) {
311 action.DO(this);
312 inButtonPress = false;
313 }
314 }
315
316 /**
317 * Set the background color used for the button "shadow". If null, no
318 * shadow will be drawn.
319 *
320 * @param color the new background color, or null for no shadow
321 */
322 public void setShadowColor(final CellAttributes color) {
323 if (color != null) {
324 shadowColor = new CellAttributes();
325 shadowColor.setTo(color);
326 shadowColor.setForeColor(Color.BLACK);
327 shadowColor.setBold(false);
328 } else {
329 shadowColor = null;
330 }
331 }
332
333 }