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