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