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