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