]> icculus.org git repositories - taylor/freespace2.git/blob - src/ui/gadget.cpp
fix issue with looping audio streams
[taylor/freespace2.git] / src / ui / gadget.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/GADGET.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * Functions for the base gadget class
16  *
17  * $Log$
18  * Revision 1.8  2005/10/01 21:55:00  taylor
19  * fix a small bug in UI_GADGET that could leave a control animation loaded in memory without a way to unload it
20  * allow a slider with no defined hotspot to function properly (fixes the FS1 skill slider mask problem)
21  *
22  * Revision 1.7  2003/05/25 02:30:44  taylor
23  * Freespace 1 support
24  *
25  * Revision 1.6  2002/06/09 04:41:29  relnev
26  * added copyright header
27  *
28  * Revision 1.5  2002/05/28 21:03:38  relnev
29  * implemented stub
30  *
31  * Revision 1.4  2002/05/26 20:22:48  theoddone33
32  * Most of network/ works
33  *
34  * Revision 1.3  2002/05/07 03:16:53  theoddone33
35  * The Great Newline Fix
36  *
37  * Revision 1.2  2002/05/04 04:36:56  theoddone33
38  * More changes, took out a lot of the sound stuff which will bite later but
39  * I don't care.
40  *
41  * Revision 1.1.1.1  2002/05/03 03:28:11  root
42  * Initial import.
43  *
44  * 
45  * 18    8/11/99 3:21p Jefff
46  * set_bmaps clarification by daveb
47  * 
48  * 17    8/10/99 6:54p Dave
49  * Mad optimizations. Added paging to the nebula effect.
50  * 
51  * 16    7/16/99 1:50p Dave
52  * 8 bit aabitmaps. yay.
53  * 
54  * 15    6/25/99 11:59a Dave
55  * Multi options screen.
56  * 
57  * 14    5/21/99 6:45p Dave
58  * Sped up ui loading a bit. Sped up localization disk access stuff. Multi
59  * start game screen, multi password, and multi pxo-help screen.
60  * 
61  * 13    5/03/99 8:33p Dave
62  * New version of multi host options screen.
63  * 
64  * 12    4/16/99 5:23p Neilk
65  * Removed bitmap mprintf's to make debugging faster
66  * 
67  * 11    2/11/99 3:08p Dave
68  * PXO refresh button. Very preliminary squad war support.
69  * 
70  * 10    2/05/99 4:09p Anoop
71  * Hopefully the last fix for this file :)
72  * 
73  * 9     2/05/99 3:26p Enricco
74  * Oops. Some .ani's can have 4 frames.
75  * 
76  * 8     2/05/99 3:07p Davidg
77  * Fixed braindead bmap setting code.
78  * 
79  * 7     2/01/99 5:55p Dave
80  * Removed the idea of explicit bitmaps for buttons. Fixed text
81  * highlighting for disabled gadgets.
82  * 
83  * 6     12/21/98 9:05a Dave
84  * Changed UI code so it doesn't require 3 bitmaps for a 3 state button.
85  * 
86  * 5     12/18/98 1:13a Dave
87  * Rough 1024x768 support for Direct3D. Proper detection and usage through
88  * the launcher.
89  * 
90  * 4     12/02/98 5:47p Dave
91  * Put in interface xstr code. Converted barracks screen to new format.
92  * 
93  * 3     11/30/98 1:07p Dave
94  * 16 bit conversion, first run.
95  * 
96  * 2     10/07/98 10:54a Dave
97  * Initial checkin.
98  * 
99  * 1     10/07/98 10:51a Dave
100  * 
101  * 22    5/11/98 5:29p Hoffoss
102  * Added mouse button mapped to joystick button support.
103  * 
104  * 21    4/12/98 2:09p Dave
105  * Make main hall door text less stupid. Make sure inputbox focus in the
106  * multi host options screen is managed more intelligently.
107  * 
108  * 20    4/09/98 7:14p Hoffoss
109  * Did some cool changes for tooltips.
110  * 
111  * 19    2/11/98 6:24p Hoffoss
112  * Fixed bug where disabled and hidden buttons give failed sound when
113  * pressed.  Shouldn't happen when they are hidden.
114  * 
115  * 18    1/15/98 11:54a Hoffoss
116  * Embelished file with nifty comments.
117  * 
118  * 17    1/14/98 6:43p Hoffoss
119  * Massive changes to UI code.  A lot cleaner and better now.  Did all
120  * this to get the new UI_DOT_SLIDER to work properly, which the old code
121  * wasn't flexible enough to handle.
122  * 
123  * 16    12/30/97 4:26p Lawrance
124  * Disable right-click movement of ui controls
125  * 
126  * 15    10/24/97 10:58p Hoffoss
127  * Made some changes to the UI code to do some things I need it to do.
128  * 
129  * 14    10/12/97 3:45p Lawrance
130  * only allow movement of UI controls in debug
131  * 
132  * 13    10/09/97 4:56p Lawrance
133  * init GADGET stuff in base_create(), not the constructor
134  * 
135  * 12    9/07/97 10:05p Lawrance
136  * don't set hotspot_num, done in gadget constructor
137  * 
138  * 11    8/29/97 7:34p Lawrance
139  * use bmpman for storing bitmaps for ui controls
140  * 
141  * 10    8/18/97 5:28p Lawrance
142  * integrating sounds for when mouse goes over an active control
143  * 
144  * 9     7/20/97 7:00p Lawrance
145  * changed name of some anim_ functions to be more consistent
146  * 
147  * 8     5/26/97 10:26a Lawrance
148  * get slider control working 100%
149  * 
150  * 7     5/22/97 5:36p Lawrance
151  * allowing custom art for scrollbars
152  * 
153  * 6     5/21/97 11:07a Lawrance
154  * integrate masks and custom bitmaps
155  * 
156  * 5     4/28/97 2:19p Lawrance
157  * added clear_focus() function
158  * 
159  * 4     1/28/97 4:58p Lawrance
160  * allowing hidden UI components
161  * 
162  * 3     12/02/96 2:17p John
163  * Made right button drag UI gadgets around and
164  * Ctrl+Shift+Alt+F12 dumps out where they are.
165  * 
166  * 2     11/21/96 10:58a John
167  * Started adding code to drag buttons.
168  * 
169  * 1     11/14/96 6:55p John
170  *
171  * $NoKeywords: $
172  */
173
174 #include "uidefs.h"
175 #include "ui.h"
176 #include "bmpman.h"
177 #include "animplay.h"
178
179 // constructor
180 UI_GADGET::UI_GADGET()
181 {
182 }
183
184 // destructor
185 UI_GADGET::~UI_GADGET()
186 {
187 }
188
189 int UI_GADGET::get_hotspot()
190 {
191         if (!linked_to_hotspot)
192                 return -1;
193
194         return hotspot_num;
195 }
196
197 void UI_GADGET::reset()
198 {
199         m_flags = 0;
200 }
201
202 // --------------------------------------------------------------------
203 // Links a hotspot (palette index in mask) to the given gadget.
204 //
205 void UI_GADGET::link_hotspot(int num)
206 {
207         hotspot_num = num;
208         linked_to_hotspot = 1;
209 }
210
211 // --------------------------------------------------------------------
212 // Extract MAX_BMAPS_PER_CONTROL bitmaps for the different states of the control.
213 //
214 // The bitmap ids are stored in the bmap_ids[] array.  If you don't want to store
215 // from the zero index, fill in the offset parameter.  offset is a default parameter
216 // with a default value of zero.
217 //
218 // NOTE:  The bitmaps stored in a .ani file.  What each frame is used for
219 //                       is left up to the component to decide.
220 //
221
222 // loads nframes bitmaps, starting at index start_frame.
223 // anything < start_frame will not be loaded.
224 // this keeps the loading code from trying to load bitmaps which don't exist
225 // and taking an unnecessary disk hit.          
226 int UI_GADGET::set_bmaps(const char *ani_fname, int nframes, int start_frame)
227 {
228         int i;  
229 #ifndef MAKE_FS1
230         char full_name[MAX_FILENAME_LEN] = "";
231         char tmp[10];
232         int num_digits;
233         int its_all_good = 0;
234         int s_idx;
235 #endif
236         int idx;        
237         
238         // clear out all frames
239         for(idx=0; idx<MAX_BMAPS_PER_GADGET; idx++){
240                 bmap_ids[idx] = -1;
241         }
242         
243         // load all the bitmaps
244
245         SDL_assert(nframes < MAX_BMAPS_PER_GADGET);             
246         m_num_frames = nframes;         
247 #ifndef MAKE_FS1
248         // FS1 uses real anis instead of frame based pcxs so this code just slows down
249         // searching and therefore loading
250         for(idx=start_frame; idx<nframes; idx++){
251                 // clear the string
252                 SDL_strlcpy(full_name, "", SDL_arraysize(full_name));
253
254                 // get the # of digits for this index
255                 num_digits = (idx < 10) ? 1 : (idx < 100) ? 2 : (idx < 1000) ? 3 : 4;
256
257                 // build the actual filename
258                 SDL_strlcpy(full_name, ani_fname, SDL_arraysize(full_name));
259                 for(s_idx=0; s_idx<(4-num_digits); s_idx++){
260                         SDL_strlcat(full_name, NOX("0"), SDL_arraysize(full_name));
261                 }
262
263                 SDL_snprintf(tmp, SDL_arraysize(tmp), "%d", idx);
264                 SDL_strlcat(full_name, tmp, SDL_arraysize(full_name));
265
266                 // try and load the bitmap                              
267                 bmap_ids[idx] = bm_load(full_name);     
268                 if(bmap_ids[idx] != -1){                
269                         // bm_lock(bmap_ids[idx], 16, 0);
270                         // bm_unlock(bmap_ids[idx]);
271                         
272                         its_all_good = 1;
273                 } else {
274                         // mprintf(("Skipping %s\n", full_name));
275                         // its_all_good = 0;
276                 }
277         }
278
279         // done
280         if(its_all_good){
281                 uses_bmaps = 1;         
282                 return 0;
283         }
284 #endif
285
286         // no go, so try and load as an ani. try and load as an .ani    
287         bmap_ids[0] = bm_load_animation(ani_fname, &m_num_frames);      
288         if((bmap_ids[0] >= 0) && (m_num_frames <= MAX_BMAPS_PER_GADGET)){                                       
289                 // seems pretty stupid that we didn't just use a variable for the first frame and access all
290                 // other frames offset from it instead of accessing this bmap_ids[] array, but probably too
291                 // much trouble to go through and change this anymore.  How sad..
292                 for ( i=1; i<m_num_frames; i++ ) {
293                         bmap_ids[i] = bmap_ids[0] + i;
294                 }       
295         }       
296
297         // flag that this control is using bitmaps for art      
298         uses_bmaps = 1;
299         return 0;
300 }
301
302 // Handle drawing of all children of the gadget.  Since this the base of all other gadgets,
303 // it doesn't have any look to it (kind of like a soul.  Can you see someone's soul?) and thus
304 // doesn't actually render itself.
305 //
306 void UI_GADGET::draw()
307 {
308         UI_GADGET *cur;
309
310         // if hidden, hide children as well
311         if (hidden){
312                 return;
313         }
314
315         if (children) {
316                 cur = children;
317                 do {
318                         cur->draw();
319                         cur = cur->next;
320
321                 } while (cur != children);
322         }
323 }
324
325 //      Free up bitmaps used by the gadget, and call children to destroy themselves as well.
326 //
327 void UI_GADGET::destroy()
328 {
329         int i;
330         UI_GADGET *cur;
331
332         for ( i=0; i<m_num_frames; i++ ) {
333                 if (bmap_ids[i] != -1) {
334                         bm_release(bmap_ids[i]);
335                         bmap_ids[i] = -1;
336                 }
337         }
338
339         if (children) {
340                 cur = children;
341                 do {
342                         cur->destroy();
343                         cur = cur->next;
344
345                 } while (cur != children);
346         }
347 }
348
349 // Use this if you need to change the size and position of a gadget after you have created it.
350 //
351 void UI_GADGET::update_dimensions(int _x, int _y, int _w, int _h)
352 {
353         if ( _x != -1 ) x = _x;
354         if ( _y != -1 ) y = _y;
355         if ( _w != -1 ) w = _w;
356         if ( _h != -1 ) h = _h; 
357 }
358
359 void UI_GADGET::get_dimensions(int *x_, int *y_, int *w_, int *h_)
360 {
361         *x_ = x;
362         *y_ = y;
363         *w_ = w;
364         *h_ = h;        
365 }
366
367 // Hide (or show) a gadget.
368 //  n != 0: Hide gadget
369 //  n == 0: Show gadget
370 //
371 void UI_GADGET::hide(int n)
372 {
373         hidden = n ? 1 : 0;
374 }
375
376 void UI_GADGET::unhide()
377 {
378         hidden = 0;
379 }
380
381 // Capture mouse with this gadget, which basically means only this gadget will get process()
382 // called on it until the mouse button 1 is released.
383 //
384 void UI_GADGET::capture_mouse()
385 {
386         my_wnd->capture_mouse(this);
387 }
388
389 // Check if (return true if):
390 //   mouse_captured():  this gadget has the mouse captured.
391 //   mouse_captured(x): gadget x has the mouse captured.
392 //
393 int UI_GADGET::mouse_captured(UI_GADGET *gadget)
394 {
395         if (!gadget)
396                 gadget = this;
397
398         return (my_wnd->mouse_captured_gadget == gadget);
399 }
400
401 // Set up the gadget and registers it with the UI window
402 //
403 void UI_GADGET::base_create(UI_WINDOW *wnd, int _kind, int _x, int _y, int _w, int _h)
404 {
405         int i;
406
407         // initialize data with passed values
408         kind = _kind;
409         x = _x;
410         y = _y;
411         w = _w;
412         h = _h;
413
414         // set up reference to UI window and initialize as having no family
415         my_wnd = wnd;
416         parent = NULL;
417         children = NULL;
418         next = prev = this;
419
420         // this actually links the gadget into the UI window's top level family (as the youngest gadget)
421         set_parent(NULL);
422
423         // initialize variables
424         hotkey = -1;
425         user_function = NULL;
426         disabled_flag = 0;
427         base_dragging = 0;
428         base_drag_x = base_drag_y = 0;
429         hotspot_num = -1;
430         hidden = 0;
431         linked_to_hotspot = 0;
432         uses_bmaps = 0;
433         m_num_frames = 0;
434         for ( i=0; i<MAX_BMAPS_PER_GADGET; i++ ) {
435                 bmap_ids[i] = -1;
436         }
437 }
438
439 void UI_GADGET::set_hotkey(int key)
440 {
441         hotkey = key;
442 }
443
444 // Far as I can tell, the callback function is handled differently for each gadget type, if
445 // handled by a given gadget type at all.
446 //
447 void UI_GADGET::set_callback(void (*_user_function)(void))
448 {
449         user_function = _user_function;
450 }
451
452 // get the next enabled gadget in sibling list or self if none
453 //
454 UI_GADGET *UI_GADGET::get_next()
455 {
456         UI_GADGET *tmp;
457
458         tmp = next;
459         while ((tmp != this) && tmp->disabled_flag)
460                 tmp = tmp->next;
461
462         return tmp;
463 }
464
465 // get the previous enabled gadget in sibling list or self if none
466 //
467 UI_GADGET *UI_GADGET::get_prev()
468 {
469         UI_GADGET *tmp;
470
471         tmp = prev;
472         while ((tmp != this) && tmp->disabled_flag)
473                 tmp = tmp->prev;
474
475         return tmp;
476 }
477
478 // Set this gadget as the focus (selected gadget) of the UI window
479 //
480 void UI_GADGET::set_focus()
481 {
482         my_wnd->selected_gadget = this;
483
484         if (kind == UI_KIND_INPUTBOX) {
485                 SDL_StartTextInput();
486         }
487 }
488
489 // Make no gadget have focus in the UI window.
490 //
491 void UI_GADGET::clear_focus()
492 {
493         my_wnd->selected_gadget = NULL;
494
495         if (kind == UI_KIND_INPUTBOX) {
496                 SDL_StopTextInput();
497         }
498 }
499
500 // Return true or false if this gadget currently has the focus
501 int UI_GADGET::has_focus()
502 {
503         return my_wnd->selected_gadget == this ? 1 : 0;
504 }
505
506 // get mouse pointer position relative to gadget's origin (UL corner)
507 //
508 void UI_GADGET::get_mouse_pos(int *xx, int *yy)
509 {
510         if (xx)
511                 *xx = ui_mouse.x - x;
512         if (yy)
513                 *yy = ui_mouse.y - y;
514 }
515
516 // Call process() for all children of gadget.  As a base gadget for all other gadget types,
517 // it doesn't actually do anything for itself.
518 //
519 void UI_GADGET::process(int focus)
520 {
521         UI_GADGET *tmp;
522
523         if (disabled_flag)
524                 return;
525
526         tmp = children;  // process all children of gadget
527         if (tmp) {
528                 do {
529                         tmp->process();
530                         tmp = tmp->next;
531
532                 } while (tmp != children);
533         }
534 }
535
536 // Check if the mouse is over the gadget's area or not,
537 //
538 int UI_GADGET::is_mouse_on()
539 {
540         int offset, pixel_val;
541         ubyte *mask_data;
542         int mask_w, mask_h;
543
544         // if linked to a hotspot, use the mask for determination
545         if (linked_to_hotspot) {
546                 mask_data = (ubyte*)my_wnd->get_mask_data(&mask_w, &mask_h);
547                 if ( mask_data == NULL ) {
548                         nprintf(("Warning", "No mask defined, but control is linked to hotspot\n"));
549                         Int3();
550                         return 0;
551                 }
552
553                 // if the mouse values are out of range of the bitmap
554                 // NOTE : this happens when using smaller mask bitmaps than the screen resolution (during development)
555                 if((ui_mouse.x >= mask_w) || (ui_mouse.y >= mask_h)){
556                         return 0;
557                 }
558
559                 // check the pixel value under the mouse
560                 offset = ui_mouse.y * mask_w + ui_mouse.x;
561                 pixel_val = *(mask_data + offset);
562                 if (pixel_val == hotspot_num){
563                         return 1;
564                 } else {
565                         return 0;
566                 }
567         // otherwise, we just check the bounding box area
568         } else {
569                 if ((ui_mouse.x >= x) && (ui_mouse.x < x + w) && (ui_mouse.y >= y) && (ui_mouse.y < y + h) ){
570                         return 1;
571                 } else {
572                         return 0;
573                 }
574         }
575 }
576
577 // check if mouse is over any child of this gadget
578 //
579 int UI_GADGET::is_mouse_on_children()
580 {
581         UI_GADGET *tmp;
582         
583         tmp = children;
584         if (tmp) {
585                 do {
586                         if (tmp->is_mouse_on())
587                                 return 1;
588                         if (tmp->is_mouse_on_children())
589                                 return 1;
590
591                         tmp = tmp->next;
592
593                 } while (tmp != children);
594         }
595
596         return 0;       
597 }
598
599 void UI_GADGET::disable()
600 {
601         disabled_flag = 1;
602 }
603
604 // Enables (or possibly disables) the gadget.  n is an optional argument.  If not supplied,
605 // enables the garget.  If supplied, enables garget if n is true, disables it if n is false.
606 //
607 void UI_GADGET::enable(int n)
608 {
609         disabled_flag = n ? 0 : 1;
610 }
611
612 // Check if gadget is disabled
613 //
614 int UI_GADGET::disabled()
615 {
616         return disabled_flag;
617 }
618
619 // Check if gadget is enabled
620 //
621 int UI_GADGET::enabled()
622 {
623         return !disabled_flag;
624 }
625
626 void UI_GADGET::drag_with_children( int dx, int dy )
627 {
628         UI_GADGET *tmp;
629
630         x = dx + base_start_x;
631         y = dy + base_start_y;
632         
633         tmp = children;
634         if (tmp) {
635                 do {
636                         tmp->drag_with_children(dx, dy);
637                         tmp = tmp->next;
638
639                 } while (tmp != children);
640         }
641 }
642
643 void UI_GADGET::start_drag_with_children()
644 {
645         UI_GADGET *tmp;
646
647         base_dragging = 1;
648         base_start_x = x;
649         base_start_y = y;
650         
651         tmp = children;
652         if (tmp) {
653                 do {
654                         tmp->start_drag_with_children();
655                         tmp = tmp->next;
656
657                 } while (tmp != children);
658         }
659 }
660
661 void UI_GADGET::stop_drag_with_children()
662 {
663         UI_GADGET *tmp;
664
665         base_dragging = 0;
666         tmp = children;
667         if (tmp) {
668                 do {
669                         tmp->stop_drag_with_children();
670                         tmp = tmp->next;
671
672                 } while (tmp != children);
673         }
674 }
675
676 // Returns 1 if moving
677 int UI_GADGET::check_move()
678 {
679         #if 0
680                 if ( parent != NULL ) return base_dragging;
681
682                 if ( !base_dragging )   {
683
684                         if ( B2_JUST_PRESSED )  {
685                                 if ( is_mouse_on() || is_mouse_on_children() ) {
686                                         start_drag_with_children();
687                                         base_drag_x = ui_mouse.x;
688                                         base_drag_y = ui_mouse.y;
689                                         return 1;
690                                 } else {
691                                         return 0;
692                                 }
693                         } else 
694                                 return 0;
695                 } else {
696                         drag_with_children(ui_mouse.x - base_drag_x,ui_mouse.y - base_drag_y);
697                         nprintf(( "UI", "UI: X=%d, Y=%d, Delta=(%d,%d)\n", x, y, (ui_mouse.x - base_drag_x), (ui_mouse.y - base_drag_y) ));
698                         if (B2_RELEASED)        {
699                                 stop_drag_with_children();
700                         }
701                         return 1;
702                 }
703         #endif
704         return 0;
705 }
706
707 // Take gadget out of family.  Children of this gadget stay with gadget, though.
708 //
709 // A family is basically the whole parent/sibling/children hierarchy for gadgets.  Any gadget
710 // that is linked to another gadget by one of these pointers is said to be in the same family
711 // as that gadget.  This function, therefore, caused all references to a gadget by it's
712 // family's gadgets' sibling or children pointers to be broken, and all references to any of them
713 // by this gadget's parent or sibling pointers to also be broken.  This isolates the gadget and
714 // it's children into a new family.
715 //
716 void UI_GADGET::remove_from_family()
717 {
718         if (parent) {
719                 if (parent->children == this) {
720                         if (next == this)  // an only child?
721                                 parent->children = NULL;  // if so, parent now has no children
722                         else
723                                 parent->children = next;  // next sibling is now the eldest
724                 }
725
726         } else {
727                 if (my_wnd->first_gadget == this) {
728                         if (next == this)  // an only child?
729                                 my_wnd->first_gadget = NULL;  // if so, parent now has no children
730                         else
731                                 my_wnd->first_gadget = next;  // next sibling is now the eldest
732                 }
733         }
734
735         parent = NULL;
736         if (next != this) {  // does gadget have siblings?
737                 next->prev = prev;
738                 prev->next = next;
739         }
740
741         next = prev = this;
742 }
743
744 // Put gadget into a new family (removing from old one if needed first).
745 // See remove_from_family() for definition of what a family is.
746 //
747 void UI_GADGET::set_parent(UI_GADGET *daddy)
748 {
749         remove_from_family();
750         parent = daddy;
751
752         if (!daddy) {
753                 if (!my_wnd->first_gadget) {
754                         my_wnd->first_gadget = this;
755
756                 } else {
757                         UI_GADGET *eldest_sibling, *youngest_sibling;
758
759                         eldest_sibling = my_wnd->first_gadget;
760                         youngest_sibling = eldest_sibling->prev;
761
762                         next = eldest_sibling;
763                         prev = youngest_sibling;
764
765                         eldest_sibling->prev = this;
766                         youngest_sibling->next = this;
767                 }
768
769                 return;
770         }
771
772         if (!daddy->children) {
773                 daddy->children = this;
774
775         } else {
776                 UI_GADGET *eldest_sibling, *youngest_sibling;
777
778                 eldest_sibling = daddy->children;
779                 youngest_sibling = eldest_sibling->prev;
780
781                 next = eldest_sibling;
782                 prev = youngest_sibling->prev;
783
784                 eldest_sibling->prev = this;
785                 youngest_sibling->next = this;
786         }
787 }
788