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