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