]> icculus.org git repositories - taylor/freespace2.git/blob - src/ui/button.cpp
Initial revision
[taylor/freespace2.git] / src / ui / button.cpp
1 /*
2  * $Logfile: /Freespace2/code/UI/BUTTON.cpp $
3  * $Revision$
4  * $Date$
5  * $Author$
6  *
7  * Code for pushbuttons
8  *
9  * $Log$
10  * Revision 1.1  2002/05/03 03:28:11  root
11  * Initial revision
12  *
13  * 
14  * 9     8/16/99 9:45a Jefff
15  * changes to cursor management to allow a 2nd temporary cursor
16  * 
17  * 8     8/05/99 2:44p Jefff
18  * added disabled callback to UI_BUTTON
19  * 
20  * 7     5/21/99 6:45p Dave
21  * Sped up ui loading a bit. Sped up localization disk access stuff. Multi
22  * start game screen, multi password, and multi pxo-help screen.
23  * 
24  * 6     2/11/99 3:08p Dave
25  * PXO refresh button. Very preliminary squad war support.
26  * 
27  * 5     12/21/98 9:05a Dave
28  * Changed UI code so it doesn't require 3 bitmaps for a 3 state button.
29  * 
30  * 4     12/02/98 5:47p Dave
31  * Put in interface xstr code. Converted barracks screen to new format.
32  * 
33  * 3     10/13/98 9:29a Dave
34  * Started neatening up freespace.h. Many variables renamed and
35  * reorganized. Added AlphaColors.[h,cpp]
36  * 
37  * 2     10/07/98 10:54a Dave
38  * Initial checkin.
39  * 
40  * 1     10/07/98 10:51a Dave
41  * 
42  * 42    5/12/98 11:59p Dave
43  * Put in some more functionality for Parallax Online.
44  * 
45  * 41    5/11/98 5:29p Hoffoss
46  * Added mouse button mapped to joystick button support.
47  * 
48  * 40    5/03/98 1:55a Lawrance
49  * Add function call that forces button code to skip first callback for
50  * highlighing
51  * 
52  * 39    4/22/98 5:19p Lawrance
53  * only strdup strings that are not zero length
54  * 
55  * 38    4/14/98 5:06p Dave
56  * Don't load or send invalid pilot pics. Fixed chatbox graphic errors.
57  * Made chatbox display team icons in a team vs. team game. Fixed up pause
58  * and endgame sequencing issues.
59  * 
60  * 37    4/13/98 4:30p Hoffoss
61  * Fixed a bunch of stupid little bugs in options screen.  Also changed
62  * forced_draw() to work like it used to.
63  * 
64  * 36    4/11/98 7:59p Lawrance
65  * Add support for help overlays
66  * 
67  * 35    4/10/98 4:51p Hoffoss
68  * Made several changes related to tooltips.
69  * 
70  * 34    3/12/98 4:03p Lawrance
71  * Only allow one pressed button at a time
72  * 
73  * 33    2/11/98 6:24p Hoffoss
74  * Fixed bug where disabled and hidden buttons give failed sound when
75  * pressed.  Shouldn't happen when they are hidden.
76  * 
77  * 32    2/06/98 3:36p Hoffoss
78  * Made disabled buttons play failed sound if clicked on.  This is now
79  * standard behavior for all UI buttons everywhere.
80  * 
81  * 31    2/03/98 4:21p Hoffoss
82  * Made UI controls draw white text when disabled.
83  * 
84  * 30    1/26/98 6:28p Lawrance
85  * Add ability to for a button press event externally.
86  * 
87  * 29    1/16/98 4:36p Hoffoss
88  * Fixed hotkey so it doesn't register twice if mouse is also over button.
89  * 
90  * 28    1/15/98 3:07p Hoffoss
91  * Fixed bug where button couldn't be pressed by the mouse now.  Argh.
92  * 
93  * 27    1/15/98 2:59p Hoffoss
94  * Fixed bug with hotkeys not working unless mouse was actually over
95  * button as well.
96  * 
97  * 26    1/15/98 1:58p Hoffoss
98  * Made buttons that don't repeat only trigger when the mouse button goes
99  * up while over the button.
100  * 
101  * 25    1/15/98 11:09a Hoffoss
102  * Embelished this file with nifty comments.
103  * 
104  * 24    1/15/98 10:28a Hoffoss
105  * Fixed ui buttons to handle modified hotkeys (like Shift-arrow) and
106  * fixed bug in debriefing.
107  * 
108  * 23    1/14/98 6:43p Hoffoss
109  * Massive changes to UI code.  A lot cleaner and better now.  Did all
110  * this to get the new UI_DOT_SLIDER to work properly, which the old code
111  * wasn't flexible enough to handle.
112  * 
113  * 22    1/02/98 9:11p Lawrance
114  * Add button_hot() function
115  * 
116  * 21    11/19/97 8:32p Hoffoss
117  * Changed UI buttons so they go back to unpressed when they are disabled.
118  * 
119  * 20    11/16/97 3:19p Hoffoss
120  * Fixed the code style so I could read the damn stuff.
121  * 
122  * 19    10/29/97 7:25p Hoffoss
123  * Added crude support for UI button double click checking.
124  * 
125  * 18    10/01/97 4:39p Lawrance
126  * null out text when free'ed
127  * 
128  * 17    9/30/97 8:50p Lawrance
129  * don't eat keypress when a hotkey is used
130  * 
131  * 16    9/18/97 10:32p Lawrance
132  * fix a bug that was wiping out some flags when reset was called
133  * 
134  * 15    9/07/97 10:05p Lawrance
135  * remove sound refrences in code, use callbacks instead
136  * 
137  * 14    8/30/97 12:23p Lawrance
138  * add button function to reset the status of a button
139  * 
140  * 13    8/26/97 9:23a Lawrance
141  * fix bug with repeatable buttons
142  * 
143  * 12    8/24/97 5:24p Lawrance
144  * improve drawing of buttons 
145  * 
146  * 11    8/18/97 5:28p Lawrance
147  * integrating sounds for when mouse goes over an active control
148  * 
149  * 10    8/17/97 2:42p Lawrance
150  * add code to have selected bitmap for buttons linger for a certain time
151  * 
152  * 9     6/13/97 5:51p Lawrance
153  * add in support for repeating buttons
154  * 
155  * 8     6/12/97 11:09p Lawrance
156  * getting map and text integrated into briefing
157  * 
158  * 7     6/12/97 12:39p John
159  * made ui use freespace colors
160  * 
161  * 6     6/11/97 1:13p John
162  * Started fixing all the text colors in the game.
163  * 
164  * 5     5/22/97 5:36p Lawrance
165  * allowing custom art for scrollbars
166  * 
167  * 4     5/21/97 11:07a Lawrance
168  * integrate masks and custom bitmaps
169  * 
170  * 3     1/28/97 4:58p Lawrance
171  * allowing hidden UI components
172  * 
173  * 2     12/03/96 11:29a John
174  * Made scroll buttons on listbox scroll once, then delay, then repeat
175  * when the buttons are held down.
176  * 
177  * 1     11/14/96 6:55p John
178  *
179  * $NoKeywords: $
180  */
181
182 #include "uidefs.h"
183 #include "ui.h"
184 #include "timer.h"
185 #include "gamesnd.h"
186 #include "alphacolors.h"
187
188 // ---------------------------------------------------------------------------------------
189 // input:
190 //                      do_repeat               =>              property of button, set to 1 to allow pressed events if mouse
191 //                                                                              pointer is held over button with left mouse button down,
192 //                                                                              otherwise 0 (useful for buttons that scroll items)
193 //                      ignore_focus    =>              whether to allow Enter/Spacebar to affect pressed state when
194 //                                                                              control has focus
195 //
196 void UI_BUTTON::create(UI_WINDOW *wnd, char *_text, int _x, int _y, int _w, int _h, int do_repeat, int ignore_focus)
197 {
198         text = NULL;
199
200         if (_text) {
201                 if ( strlen(_text) > 0 ) {
202                         text = strdup(_text);
203                 }
204         }
205
206         // register gadget with UI window
207         base_create( wnd, UI_KIND_BUTTON, _x, _y, _w, _h );
208
209         // initialize variables
210         m_flags = 0;
211         next_repeat = 0;
212         m_just_highlighted_function = NULL;             // assume there is no callback
213         m_disabled_function = NULL;                             // ditto
214         if (do_repeat) {
215                 m_flags |= BF_REPEATS;
216                 next_repeat = 1;
217         }
218
219         m_press_linger = 1;
220         first_callback = 1;
221
222         hotkey_if_focus = KEY_SPACEBAR;
223
224         if (ignore_focus){
225                 m_flags |= BF_IGNORE_FOCUS;
226         }
227
228         custom_cursor_bmap = -1;
229         previous_cursor_bmap = -1;
230 };
231
232 void UI_BUTTON::destroy()
233 {
234         if (text) {
235                 free(text);
236                 text = NULL;
237         }
238
239         UI_GADGET::destroy();  // call base as well
240 }
241
242 // sets a hotkey for button that works only when it had focus (or derived focus)
243 void UI_BUTTON::set_hotkey_if_focus(int key)
244 {
245         hotkey_if_focus = key;
246 }
247
248 void UI_BUTTON::reset_status()
249 {
250         m_flags &= ~BF_HIGHLIGHTED;
251         m_flags &= ~BF_HOTKEY_JUST_PRESSED;
252         m_flags &= ~BF_DOWN;
253         m_flags &= ~BF_DOUBLE_CLICKED;
254         m_flags &= ~BF_JUST_HIGHLIGHTED;
255         m_flags &= ~BF_CLICKED;
256 }
257
258 // reset anything that needs to be at the start of a new frame before processing
259 void UI_BUTTON::frame_reset()
260 {
261         m_flags &= ~BF_HIGHLIGHTED;
262         m_flags &= ~BF_HOTKEY_JUST_PRESSED;
263         m_flags &= ~BF_DOWN;
264         m_flags &= ~BF_JUST_PRESSED;
265         m_flags &= ~BF_JUST_RELEASED;
266         m_flags &= ~BF_CLICKED;
267         m_flags &= ~BF_DOUBLE_CLICKED;
268         m_flags &= ~BF_JUST_HIGHLIGHTED;
269
270         restore_previous_cursor();
271 }
272
273 // Force button to draw a specified frame
274 void UI_BUTTON::draw_forced(int frame_num)
275 {
276         if (uses_bmaps) {
277                 if (bmap_ids[frame_num] >= 0) {
278                         gr_set_bitmap(bmap_ids[frame_num]);
279                         gr_bitmap(x, y);
280                         
281                         // my_wnd->draw_tooltip();
282
283                         // redraw any associated xstr
284                         my_wnd->draw_XSTR_forced(this, frame_num);
285                 }
286         }
287 }
288
289 // Render button.  How it draws exactly depends on it's current state.
290 void UI_BUTTON::draw()
291 {
292         int offset, frame_num = -1;
293
294         if (uses_bmaps) {
295                 gr_reset_clip();
296                 // if button is down, draw it that way
297                 if (button_down()) {
298                         if (bmap_ids[B_PRESSED] >= 0){
299                                 frame_num = B_PRESSED;
300                         }
301                 // otherwise if button is disabled, draw it that way
302                 } else if (disabled_flag) {
303                         if (bmap_ids[B_DISABLED] >= 0){
304                                 frame_num = B_DISABLED;
305                         }
306                 // otherwise, if button is highlighted (mouse is over it, but mouse buttons not down) draw it that way
307                 } else if (m_flags & BF_HIGHLIGHTED) {
308                         if (bmap_ids[B_HIGHLIGHT] >= 0){
309                                 frame_num = B_HIGHLIGHT;
310                         }
311                 // otherwise, just draw it normally
312                 } else {
313                         if (bmap_ids[B_NORMAL] >= 0){
314                                 frame_num = B_NORMAL;
315                         }
316                 }
317
318                 if (frame_num >= 0) {
319                         gr_set_bitmap(bmap_ids[frame_num]);
320                         gr_bitmap(x, y);
321                 }
322         } else {
323                 gr_set_font(my_wnd->f_id);
324                 gr_set_clip( x, y, w, h );
325
326                 // draw the button's box
327                 if (button_down()) {
328                         ui_draw_box_in( 0, 0, w-1, h-1 );
329                         offset = 1;
330
331                 } else {
332                         ui_draw_box_out( 0, 0, w-1, h-1 );
333                         offset = 0;
334                 }
335
336                 // now draw the button's text
337                 if (disabled_flag){
338                         gr_set_color_fast(&CDARK_GRAY);
339                 } else if (my_wnd->selected_gadget == this){
340                         gr_set_color_fast(&CBRIGHT_GREEN);
341                 } else {
342                         gr_set_color_fast(&CBLACK);
343                 }
344
345                 if (text){
346                         ui_string_centered( Middle(w) + offset, Middle(h) + offset, text );
347                 }
348
349                 gr_reset_clip();
350         }
351 }
352
353 // process() is called to process the button, which amounts to:
354 //   If mouse is over button, hilight it
355 //   If highlighted and mouse button down, flag button as down
356 //   If hotkey pressed, flag button as down
357 //   If hotkey_if_focus pressed, and button has focus, flag button as down
358 //   Set various BF_JUST_* flags if events changed from last frame
359 //
360 void UI_BUTTON::process(int focus)
361 {
362         int mouse_on_me, old_flags;
363
364         old_flags = m_flags;
365         frame_reset();
366
367         // check mouse over control and handle hilighting state
368         mouse_on_me = is_mouse_on();
369
370         // if gadget is disabled, force button up and return
371         if (disabled_flag) {
372                 if (old_flags & BF_DOWN){
373                         m_flags |= BF_JUST_RELEASED;
374                 }
375
376                 if (!hidden && !my_wnd->use_hack_to_get_around_stupid_problem_flag) {
377                         if (mouse_on_me && B1_JUST_PRESSED){
378                                 gamesnd_play_iface(SND_GENERAL_FAIL);
379                         }
380
381                         if ( (hotkey >= 0) && (my_wnd->keypress == hotkey) ){
382                                 gamesnd_play_iface(SND_GENERAL_FAIL);
383                         }
384                 }
385
386                 // do callback if the button is disabled
387                 if (mouse_on_me && B1_JUST_PRESSED){
388                         if (m_disabled_function != NULL) {
389                                 m_disabled_function();
390                         }
391                 }
392
393                 return;
394         }
395
396         // check focus and derived focus with one variable
397         if (my_wnd->selected_gadget == this) {
398                 focus = 1;
399         }
400
401         // show alternate cursor, perhaps?
402         maybe_show_custom_cursor();
403
404         if ( !mouse_on_me ) {
405                 next_repeat = 0;
406         } else {
407                 m_flags |= BF_HIGHLIGHTED;
408                 if ( !(old_flags & BF_HIGHLIGHTED) ) {
409                         int do_callback = 1;
410                         m_flags |= BF_JUST_HIGHLIGHTED;
411                         // if a callback exists, call it
412                         if (m_just_highlighted_function) {
413
414                                 if ( m_flags & BF_SKIP_FIRST_HIGHLIGHT_CALLBACK ) {
415                                         if ( first_callback ) {
416                                                 do_callback = 0;
417                                         }
418                                 }
419
420                                 first_callback = 0;                                             
421                                 if ( do_callback ) {
422                                         m_just_highlighted_function();
423                                 }
424                         }
425                 }
426         }
427
428         // check if mouse is pressed
429         if ( B1_PRESSED && mouse_on_me )        {
430                 m_flags |= BF_DOWN;
431                 capture_mouse();
432         }
433
434         // check if hotkey is down or not
435         if ( (hotkey >= 0) && (my_wnd->keypress == hotkey) ) {
436                 m_flags |= BF_DOWN | BF_CLICKED;
437         }
438
439         // only check for space/enter keystrokes if we are not ignoring the focus (this is the
440         // default behavior)
441         if ( !(m_flags & BF_IGNORE_FOCUS) ) {
442                 if ( focus && (hotkey_if_focus >= 0) ) {
443                         if (my_wnd->keypress == hotkey_if_focus)
444                                 m_flags |= BF_DOWN | BF_CLICKED;
445
446                         if ( (hotkey_if_focus == KEY_SPACEBAR) && (my_wnd->keypress == KEY_ENTER) )
447                                 m_flags |= BF_DOWN | BF_CLICKED;
448                 }
449         }
450
451         // handler for button not down
452         if ( !(m_flags & BF_DOWN) ) {
453                 next_repeat = 0;
454                 if ( (old_flags & BF_DOWN) && !(old_flags & BF_CLICKED) )  // check for release of mouse, not hotkey
455                         m_flags |= BF_JUST_RELEASED;
456
457                 // non-repeating buttons behave sort of uniquely..  They activate when released over button
458                 if (!(m_flags & BF_REPEATS)) {
459                         if ( (m_flags & BF_JUST_RELEASED) && (m_flags & BF_HIGHLIGHTED) )
460                                 m_flags |= BF_CLICKED;
461                 }
462
463                 return;
464         }
465
466         // check if button just went down this frame
467         if ( !(old_flags & BF_DOWN) ) {
468                 m_flags |= BF_JUST_PRESSED;
469                 m_press_linger = timestamp(100);
470                 if (user_function)
471                         user_function();
472
473                 if (m_flags & BF_REPEATS) {
474                         next_repeat = timestamp(B_REPEAT_TIME * 3);
475                         m_flags |= BF_CLICKED;
476                 }
477         }
478
479         // check if a repeat event should occur
480         if ( timestamp_elapsed(next_repeat) && (m_flags & BF_REPEATS) ) {
481                 next_repeat = timestamp(B_REPEAT_TIME);
482                 m_flags |= BF_CLICKED;
483                 m_press_linger = timestamp(100);
484         }
485
486         // check for double click occurance
487         if (B1_DOUBLE_CLICKED && mouse_on_me) {
488                 m_flags |= BF_DOUBLE_CLICKED;
489                 m_press_linger = timestamp(100);
490         }
491 }
492
493 // Check if button should do it's function in life (trigger the event)
494 //
495 int UI_BUTTON::pressed()
496 {
497         if (m_flags & BF_CLICKED)
498                 return TRUE;
499
500         return FALSE;
501 }
502
503 int UI_BUTTON::double_clicked()
504 {
505         if ( m_flags & BF_DOUBLE_CLICKED )
506                 return TRUE;
507         else
508                 return FALSE;
509 }
510
511 int UI_BUTTON::just_pressed()
512 {
513         if ( m_flags & BF_JUST_PRESSED )
514                 return TRUE;
515         else
516                 return FALSE;
517 }
518
519 int UI_BUTTON::just_highlighted()
520 {
521         if ( m_flags & BF_JUST_HIGHLIGHTED )
522                 return TRUE;
523         else
524                 return FALSE;
525 }
526
527 // ----------------------------------------------------------------------------------
528 // Checks if button is down (or up).  This checks the button instead, rather than any
529 // events that may have caused it to be down.  Buttons also stay down for a certain amount
530 // of time minimum, to make sure it's long enough for the user to see it has went down (since
531 // one frame is probably far to quick for users to notice it).  Basically, this indicates
532 // how the button is being drawn, if you want to think of it that way.
533 int UI_BUTTON::button_down()
534 {
535         if ( (m_flags & BF_DOWN) || !timestamp_elapsed(m_press_linger) )
536                 return TRUE;
537         else
538                 return FALSE;
539 }
540
541 // ------------------------------------------------------------
542 // set the callback function for when the mouse first goes over
543 // a button
544 //
545 void UI_BUTTON::set_highlight_action( void (*user_function)(void) )
546 {
547         m_just_highlighted_function = user_function;
548 }
549
550 void UI_BUTTON::set_disabled_action( void (*user_function)(void) )
551 {
552         m_disabled_function = user_function;
553 }
554
555
556 // Is the mouse over this button?
557 int UI_BUTTON::button_hilighted()
558 {
559         return m_flags & BF_HIGHLIGHTED;
560 }
561
562 // Is the mouse over this button?
563 void UI_BUTTON::set_button_hilighted()
564 {
565         m_flags |= BF_HIGHLIGHTED;
566 }
567
568 // Force button to get pressed
569 void UI_BUTTON::press_button()
570 {
571         if ( !disabled_flag ) {
572                 m_flags |= BF_DOWN | BF_CLICKED;
573                 //m_flags |= BF_JUST_PRESSED;
574         }
575 }
576                 
577 // reset the "pressed" timestamps
578 void UI_BUTTON::reset_timestamps()
579 {
580         m_press_linger = 1;
581         next_repeat = 0;
582 }
583
584 void UI_BUTTON::skip_first_highlight_callback()
585 {
586         m_flags |= BF_SKIP_FIRST_HIGHLIGHT_CALLBACK;
587         first_callback = 1;
588 }
589
590 void UI_BUTTON::repeatable(int yes)
591 {
592         if(yes){
593                 m_flags |= BF_REPEATS;
594                 next_repeat = 1;
595         } else {
596                 m_flags &= ~(BF_REPEATS);
597                 next_repeat = 0;
598         }
599 }
600
601 void UI_BUTTON::maybe_show_custom_cursor()
602 {
603         if (disabled_flag)
604                 return;
605
606         // set the mouseover cursor 
607         if (is_mouse_on()) {
608                 if ((custom_cursor_bmap >= 0) && (previous_cursor_bmap < 0)) {
609                         previous_cursor_bmap = gr_get_cursor_bitmap();
610                         gr_set_cursor_bitmap(custom_cursor_bmap, GR_CURSOR_LOCK);                       // set and lock
611                 }
612         }
613 }
614
615 void UI_BUTTON::restore_previous_cursor()
616 {
617         if (previous_cursor_bmap >= 0) {
618                 gr_set_cursor_bitmap(previous_cursor_bmap, GR_CURSOR_UNLOCK);           // restore and unlock
619                 previous_cursor_bmap = -1;
620         }
621 }