1 // Rewritten mostly by Kris Morness
2 #include "Button_Sound_Control.h"
3 #include "Button_System.h"
4 #include "Debug.h"
5 #include "Font.h"
6 #include "HImage.h"
7 #include "Input.h"
8 #include "MemMan.h"
9 #include "VObject.h"
10 #include "VObject_Blitters.h"
11 #include "VSurface.h"
12 #include "Video.h"
13 #include "WCheck.h"
14 #include "WordWrap.h"
15
16 #ifdef _JA2_RENDER_DIRTY
17 # include "Font_Control.h"
18 #endif
19
20 #include <string_theory/string>
21
22 #include <stdexcept>
23
24
25 // Names of the default generic button image files.
26 #define DEFAULT_GENERIC_BUTTON_OFF "genbutn.sti"
27 #define DEFAULT_GENERIC_BUTTON_ON "genbutn2.sti"
28 #define DEFAULT_GENERIC_BUTTON_OFF_HI "genbutn3.sti"
29 #define DEFAULT_GENERIC_BUTTON_ON_HI "genbutn4.sti"
30
31
32 #define MSYS_STARTING_CURSORVAL 0
33
34
35 #define MAX_BUTTON_ICONS 40
36
37
38 #define GUI_BTN_NONE 0
39 #define GUI_BTN_DUPLICATE_VOBJ 1
40
41
42 /* Kris: December 2, 1997
43 * Special internal debugging utilities that will ensure that you don't attempt
44 * to delete an already deleted button, or it's images, etc. It will also
45 * ensure that you don't create the same button that already exists.
46 * TO REMOVE ALL DEBUG FUNCTIONALITY: simply comment out BUTTONSYSTEM_DEBUGGING
47 * definition
48 */
49 #if defined _DEBUG
50 # define BUTTONSYSTEM_DEBUGGING
51 #endif
52
53
54 #define FOR_EACH_BUTTON(iter) \
55 FOR_EACH(GUI_BUTTON*, iter, ButtonList) \
56 if (!*iter) continue; else
57
58
59 #ifdef BUTTONSYSTEM_DEBUGGING
60
61 // Called immediately before assigning the button to the button list.
AssertFailIfIdenticalButtonAttributesFound(const GUI_BUTTON * b)62 static void AssertFailIfIdenticalButtonAttributesFound(const GUI_BUTTON* b)
63 {
64 FOR_EACH_BUTTON(i)
65 {
66 GUI_BUTTON const* const c = *i;
67 if (c->uiFlags & BUTTON_DELETION_PENDING) continue;
68 if (c->uiFlags & BUTTON_NO_DUPLICATE) continue;
69 if (b->Area.PriorityLevel != c->Area.PriorityLevel) continue;
70 if (b->X() != c->X()) continue;
71 if (b->Y() != c->Y()) continue;
72 if (b->BottomRightX() != c->BottomRightX()) continue;
73 if (b->BottomRightY() != c->BottomRightY()) continue;
74 if (b->ClickCallback != c->ClickCallback) continue;
75 if (b->MoveCallback != c->MoveCallback) continue;
76 /* if we get this far, it is reasonably safe to assume that the newly
77 * created button already exists. Placing a break point on the following
78 * assert will allow the coder to easily isolate the case!
79 */
80 SLOGA("Attempting to create a button that has already been created (existing buttonID %d).", c->IDNum);
81 }
82 }
83
84 #endif
85
86
87 /* Kris:
88 * These are the variables used for the anchoring of a particular button. When
89 * you click on a button, it get's anchored, until you release the mouse button.
90 * When you move around, you don't want to select other buttons, even when you
91 * release it. This follows the Windows 95 convention.
92 */
93 static GUI_BUTTON* gpAnchoredButton;
94 static GUI_BUTTON* gpPrevAnchoredButton;
95 static BOOLEAN gfAnchoredState;
96
97 static INT8 gbDisabledButtonStyle;
98
99 BOOLEAN gfRenderHilights = TRUE;
100
101 // Struct definition for the QuickButton pictures.
102 struct BUTTON_PICS
103 {
104 HVOBJECT vobj; // The Image itself
105 INT32 Grayed; // Index to use for a "Grayed-out" button
106 INT32 OffNormal; // Index to use when button is OFF
107 INT32 OffHilite; // Index to use when button is OFF w/ hilite on it
108 INT32 OnNormal; // Index to use when button is ON
109 INT32 OnHilite; // Index to use when button is ON w/ hilite on it
110 ButtonDimensions max; // width/height of largest image in use
111 UINT32 fFlags; // Special image flags
112 };
113
114 static BUTTON_PICS ButtonPictures[MAX_BUTTON_PICS];
115
116 SGPVSurface* ButtonDestBuffer;
117
118 GUI_BUTTON* ButtonList[MAX_BUTTONS];
119
120
GetDimensionsOfButtonPic(const BUTTON_PICS * const pics)121 const ButtonDimensions* GetDimensionsOfButtonPic(const BUTTON_PICS* const pics)
122 {
123 return &pics->max;
124 }
125
126
127 static HVOBJECT GenericButtonOffNormal;
128 static HVOBJECT GenericButtonOffHilite;
129 static HVOBJECT GenericButtonOnNormal;
130 static HVOBJECT GenericButtonOnHilite;
131 static UINT16 GenericButtonFillColors;
132
133 static HVOBJECT GenericButtonIcons[MAX_BUTTON_ICONS];
134
135 static BOOLEAN gfDelayButtonDeletion = FALSE;
136 static BOOLEAN gfPendingButtonDeletion = FALSE;
137
138 extern MOUSE_REGION* MSYS_PrevRegion;
139
140
141 // Finds an available slot for loading button pictures
FindFreeButtonSlot(void)142 static BUTTON_PICS* FindFreeButtonSlot(void)
143 {
144 // Search for a slot
145 FOR_EACH(BUTTON_PICS, i, ButtonPictures)
146 {
147 if (i->vobj == NULL) return i;
148 }
149 throw std::runtime_error("Out of button image slots");
150 }
151
152
SetMaxSize(BUTTON_PICS * const pics,const INT32 img_idx)153 static void SetMaxSize(BUTTON_PICS* const pics, const INT32 img_idx)
154 {
155 if (img_idx == BUTTON_NO_IMAGE) return;
156 ETRLEObject const& e = pics->vobj->SubregionProperties(img_idx);
157 UINT32 const w = e.sOffsetX + e.usWidth;
158 UINT32 const h = e.sOffsetY + e.usHeight;
159 if (pics->max.w < w) pics->max.w = w;
160 if (pics->max.h < h) pics->max.h = h;
161 }
162
163
InitButtonImage(BUTTON_PICS * const pics,const HVOBJECT VObj,const UINT32 Flags,const INT32 Grayed,const INT32 OffNormal,const INT32 OffHilite,const INT32 OnNormal,const INT32 OnHilite)164 static void InitButtonImage(BUTTON_PICS* const pics, const HVOBJECT VObj, const UINT32 Flags, const INT32 Grayed, const INT32 OffNormal, const INT32 OffHilite, const INT32 OnNormal, const INT32 OnHilite)
165 {
166 pics->vobj = VObj;
167
168 // Init the QuickButton image structure with indexes to use
169 pics->Grayed = Grayed;
170 pics->OffNormal = OffNormal;
171 pics->OffHilite = OffHilite;
172 pics->OnNormal = OnNormal;
173 pics->OnHilite = OnHilite;
174 pics->fFlags = Flags;
175
176 // Fit the button size to the largest image in the set
177 pics->max.w = 0;
178 pics->max.h = 0;
179 SetMaxSize(pics, Grayed);
180 SetMaxSize(pics, OffNormal);
181 SetMaxSize(pics, OffHilite);
182 SetMaxSize(pics, OnNormal);
183 SetMaxSize(pics, OnHilite);
184 }
185
186
LoadButtonImage(const char * filename,INT32 Grayed,INT32 OffNormal,INT32 OffHilite,INT32 OnNormal,INT32 OnHilite)187 BUTTON_PICS* LoadButtonImage(const char* filename, INT32 Grayed, INT32 OffNormal, INT32 OffHilite, INT32 OnNormal, INT32 OnHilite)
188 {
189 AssertMsg(filename != NULL, "Attempting to LoadButtonImage() with null filename.");
190
191 if (Grayed == BUTTON_NO_IMAGE &&
192 OffNormal == BUTTON_NO_IMAGE &&
193 OffHilite == BUTTON_NO_IMAGE &&
194 OnNormal == BUTTON_NO_IMAGE &&
195 OnHilite == BUTTON_NO_IMAGE)
196 {
197 throw std::logic_error("No button pictures selected");
198 }
199
200 BUTTON_PICS* const UseSlot = FindFreeButtonSlot();
201 SGPVObject* const VObj = AddVideoObjectFromFile(filename);
202 InitButtonImage(UseSlot, VObj, GUI_BTN_NONE, Grayed, OffNormal, OffHilite, OnNormal, OnHilite);
203 return UseSlot;
204 }
205
206
LoadButtonImage(char const * const filename,INT32 const off_normal,INT32 const on_normal)207 BUTTON_PICS* LoadButtonImage(char const* const filename, INT32 const off_normal, INT32 const on_normal)
208 {
209 return LoadButtonImage(filename, -1, off_normal, -1, on_normal, -1);
210 }
211
212
UseLoadedButtonImage(BUTTON_PICS * const LoadedImg,const INT32 Grayed,const INT32 OffNormal,const INT32 OffHilite,const INT32 OnNormal,const INT32 OnHilite)213 BUTTON_PICS* UseLoadedButtonImage(BUTTON_PICS* const LoadedImg, const INT32 Grayed, const INT32 OffNormal, const INT32 OffHilite, const INT32 OnNormal, const INT32 OnHilite)
214 {
215 if (Grayed == BUTTON_NO_IMAGE &&
216 OffNormal == BUTTON_NO_IMAGE &&
217 OffHilite == BUTTON_NO_IMAGE &&
218 OnNormal == BUTTON_NO_IMAGE &&
219 OnHilite == BUTTON_NO_IMAGE)
220 {
221 throw std::logic_error("No button pictures selected for pre-loaded button image");
222 }
223
224 // Is button image index given valid?
225 const HVOBJECT vobj = LoadedImg->vobj;
226 if (!vobj)
227 {
228 throw std::logic_error("Invalid button picture handle given for pre-loaded button image");
229 }
230
231 BUTTON_PICS* const UseSlot = FindFreeButtonSlot();
232 InitButtonImage(UseSlot, vobj, GUI_BTN_DUPLICATE_VOBJ, Grayed, OffNormal, OffHilite, OnNormal, OnHilite);
233 return UseSlot;
234 }
235
236
UseLoadedButtonImage(BUTTON_PICS * const img,INT32 const off_normal,INT32 const on_normal)237 BUTTON_PICS* UseLoadedButtonImage(BUTTON_PICS* const img, INT32 const off_normal, INT32 const on_normal)
238 {
239 return UseLoadedButtonImage(img, -1, off_normal, -1, on_normal, -1);
240 }
241
242
UnloadButtonImage(BUTTON_PICS * const pics)243 void UnloadButtonImage(BUTTON_PICS* const pics)
244 {
245 #if defined BUTTONSYSTEM_DEBUGGING
246 AssertMsg(pics->vobj != NULL, "Attempting to UnloadButtonImage that has a null vobj (already deleted).");
247 #endif
248 if (pics->vobj == NULL) return;
249
250 // If this is a duplicated button image, then don't trash the vobject
251 if (!(pics->fFlags & GUI_BTN_DUPLICATE_VOBJ))
252 {
253 /* Deleting a non-duplicate, so see if any dups present. if so, then convert
254 * one of them to an original!
255 */
256 FOR_EACH(BUTTON_PICS, other, ButtonPictures)
257 {
258 if (other == pics) continue;
259 if (other->vobj != pics->vobj) continue;
260 if (!(other->fFlags & GUI_BTN_DUPLICATE_VOBJ)) continue;
261
262 /* If we got here, then we got a duplicate object of the one we want to
263 * delete, so convert it to an original!
264 */
265 other->fFlags &= ~GUI_BTN_DUPLICATE_VOBJ;
266
267 // Now remove this button, but not its vobject
268 goto remove_pic;
269 }
270
271 DeleteVideoObject(pics->vobj);
272 }
273
274 remove_pic:
275 pics->vobj = NULL;
276 }
277
278
EnableButton(GUIButtonRef const b)279 void EnableButton(GUIButtonRef const b)
280 {
281 CHECKV(b != NULL); // XXX HACK000C
282 b->uiFlags |= BUTTON_ENABLED | BUTTON_DIRTY;
283 }
284
285
DisableButton(GUIButtonRef const b)286 void DisableButton(GUIButtonRef const b)
287 {
288 CHECKV(b != NULL); // XXX HACK000C
289 b->uiFlags &= ~BUTTON_ENABLED;
290 b->uiFlags |= BUTTON_DIRTY;
291 }
292
293
EnableButton(GUIButtonRef const b,bool const enable)294 void EnableButton(GUIButtonRef const b, bool const enable)
295 {
296 enable ? EnableButton(b) : DisableButton(b);
297 }
298
299
300 /* Initializes the button image sub-system. This function is called by
301 * InitButtonSystem.
302 */
InitializeButtonImageManager(void)303 static void InitializeButtonImageManager(void)
304 {
305 // Blank out all QuickButton images
306 for (int x = 0; x < MAX_BUTTON_PICS; ++x)
307 {
308 BUTTON_PICS* const pics = &ButtonPictures[x];
309 pics->vobj = NULL;
310 pics->Grayed = -1;
311 pics->OffNormal = -1;
312 pics->OffHilite = -1;
313 pics->OnNormal = -1;
314 pics->OnHilite = -1;
315 }
316
317 // Blank out all Generic button data
318 GenericButtonOffNormal = NULL;
319 GenericButtonOffHilite = NULL;
320 GenericButtonOnNormal = NULL;
321 GenericButtonOnHilite = NULL;
322 GenericButtonFillColors = 0;
323
324 // Blank out all icon images
325 for (int x = 0; x < MAX_BUTTON_ICONS; ++x)
326 GenericButtonIcons[x] = NULL;
327
328 // Load the default generic button images
329 GenericButtonOffNormal = AddVideoObjectFromFile(DEFAULT_GENERIC_BUTTON_OFF);
330 GenericButtonOnNormal = AddVideoObjectFromFile(DEFAULT_GENERIC_BUTTON_ON);
331
332 /* Load up the off hilite and on hilite images. We won't check for errors
333 * because if the file doesn't exists, the system simply ignores that file.
334 * These are only here as extra images, they aren't required for operation
335 * (only OFF Normal and ON Normal are required).
336 */
337 try
338 {
339 GenericButtonOffHilite = AddVideoObjectFromFile(DEFAULT_GENERIC_BUTTON_OFF_HI);
340 }
341 catch (...) { /* see comment above */ }
342 try
343 {
344 GenericButtonOnHilite = AddVideoObjectFromFile(DEFAULT_GENERIC_BUTTON_ON_HI);
345 }
346 catch (...) { /* see comment above */ }
347
348 UINT8 const Pix = GenericButtonOffNormal->GetETRLEPixelValue(8, 0, 0);
349 GenericButtonFillColors = GenericButtonOffNormal->Palette16()[Pix];
350 }
351
352
353 // Finds the next available slot for button icon images.
FindFreeIconSlot(void)354 static INT16 FindFreeIconSlot(void)
355 {
356 for (INT16 x = 0; x < MAX_BUTTON_ICONS; ++x)
357 {
358 if (GenericButtonIcons[x] == NULL) return x;
359 }
360 throw std::runtime_error("Out of generic button icon slots");
361 }
362
363
LoadGenericButtonIcon(const char * filename)364 INT16 LoadGenericButtonIcon(const char* filename)
365 {
366 AssertMsg(filename != NULL, "Attempting to LoadGenericButtonIcon() with null filename.");
367
368 // Get slot for icon image
369 INT16 const ImgSlot = FindFreeIconSlot();
370
371 // Load the icon
372 GenericButtonIcons[ImgSlot] = AddVideoObjectFromFile(filename);
373
374 // Return the slot number
375 return ImgSlot;
376 }
377
378
UnloadGenericButtonIcon(INT16 GenImg)379 void UnloadGenericButtonIcon(INT16 GenImg)
380 {
381 AssertMsg(0 <= GenImg && GenImg < MAX_BUTTON_ICONS, String("Attempting to UnloadGenericButtonIcon with out of range index %d.", GenImg));
382
383 #if defined BUTTONSYSTEM_DEBUGGING
384 AssertMsg(GenericButtonIcons[GenImg], "Attempting to UnloadGenericButtonIcon that has no icon (already deleted).");
385 #endif
386 if (!GenericButtonIcons[GenImg]) return;
387 // If an icon is present in the slot, remove it.
388 DeleteVideoObject(GenericButtonIcons[GenImg]);
389 GenericButtonIcons[GenImg] = NULL;
390 }
391
392
393 // Cleans up, and shuts down the button image manager sub-system.
ShutdownButtonImageManager(void)394 static void ShutdownButtonImageManager(void)
395 {
396 // Remove all QuickButton images
397 FOR_EACH(BUTTON_PICS, i, ButtonPictures)
398 {
399 if (i->vobj != NULL) UnloadButtonImage(i);
400 }
401
402 // Remove all GenericButton images
403 if (GenericButtonOffNormal != NULL)
404 {
405 DeleteVideoObject(GenericButtonOffNormal);
406 GenericButtonOffNormal = NULL;
407 }
408
409 if (GenericButtonOffHilite != NULL)
410 {
411 DeleteVideoObject(GenericButtonOffHilite);
412 GenericButtonOffHilite = NULL;
413 }
414
415 if (GenericButtonOnNormal != NULL)
416 {
417 DeleteVideoObject(GenericButtonOnNormal);
418 GenericButtonOnNormal = NULL;
419 }
420
421 if (GenericButtonOnHilite != NULL)
422 {
423 DeleteVideoObject(GenericButtonOnHilite);
424 GenericButtonOnHilite = NULL;
425 }
426
427 GenericButtonFillColors = 0;
428
429 // Remove all button icons
430 for (int x = 0; x < MAX_BUTTON_ICONS; ++x)
431 {
432 if (GenericButtonIcons[x] != NULL) UnloadGenericButtonIcon(x);
433 }
434 }
435
436
InitButtonSystem(void)437 void InitButtonSystem(void)
438 {
439 ButtonDestBuffer = FRAME_BUFFER;
440
441 // Initialize the button image manager sub-system
442 InitializeButtonImageManager();
443 }
444
445
ShutdownButtonSystem(void)446 void ShutdownButtonSystem(void)
447 {
448 // Kill off all buttons in the system
449 FOR_EACH_BUTTON(i)
450 {
451 delete *i;
452 }
453 ShutdownButtonImageManager();
454 }
455
456
RemoveButtonsMarkedForDeletion(void)457 static void RemoveButtonsMarkedForDeletion(void)
458 {
459 FOR_EACH_BUTTON(i)
460 {
461 if ((*i)->uiFlags & BUTTON_DELETION_PENDING) delete *i;
462 }
463 }
464
465
RemoveButton(GUIButtonRef & btn)466 void RemoveButton(GUIButtonRef& btn)
467 {
468 INT32 const btn_id = btn.ID();
469 btn.Reset();
470
471 CHECKV(0 < btn_id && btn_id < MAX_BUTTONS); // XXX HACK000C
472 AssertMsg(0 < btn_id && btn_id < MAX_BUTTONS, String("ButtonID %d is out of range.", btn_id));
473 GUI_BUTTON* const b = ButtonList[btn_id];
474 CHECKV(b); // XXX HACK000C
475 AssertMsg(b, String("Accessing non-existent button %d.", btn_id));
476
477 /* If we happen to be in the middle of a callback, and attempt to delete a
478 * button, like deleting a node during list processing, then we delay it till
479 * after the callback is completed.
480 */
481 if (gfDelayButtonDeletion)
482 {
483 b->uiFlags |= BUTTON_DELETION_PENDING;
484 gfPendingButtonDeletion = TRUE;
485 return;
486 }
487
488 delete b;
489 }
490
491
492 // Finds the next available button slot.
GetNextButtonNumber(void)493 static INT32 GetNextButtonNumber(void)
494 {
495 /* Never hand out ID 0. Slot 0 is always a null pointer */
496 for (INT32 x = 1; x < MAX_BUTTONS; x++)
497 {
498 if (ButtonList[x] == NULL) return x;
499 }
500 throw std::runtime_error("No more button slots");
501 }
502
503
504 static void QuickButtonCallbackMButn(MOUSE_REGION* reg, INT32 reason);
505 static void QuickButtonCallbackMMove(MOUSE_REGION* reg, INT32 reason);
506
507
GUI_BUTTON(UINT32 const flags,INT16 const left,INT16 const top,INT16 const width,INT16 const height,INT8 const priority,GUI_CALLBACK const click,GUI_CALLBACK const move)508 GUI_BUTTON::GUI_BUTTON(UINT32 const flags, INT16 const left, INT16 const top, INT16 const width, INT16 const height, INT8 const priority, GUI_CALLBACK const click, GUI_CALLBACK const move) :
509 IDNum(GetNextButtonNumber()),
510 image(0),
511 Area(left, top, width, height, priority, MSYS_STARTING_CURSORVAL, QuickButtonCallbackMMove, QuickButtonCallbackMButn),
512 ClickCallback(click),
513 MoveCallback(move),
514 uiFlags(BUTTON_DIRTY | BUTTON_ENABLED | flags),
515 uiOldFlags(0),
516 bDisabledStyle(GUI_BUTTON::DISABLED_STYLE_DEFAULT),
517 codepoints(),
518 usFont(0),
519 sForeColor(0),
520 sShadowColor(-1),
521 sForeColorDown(-1),
522 sShadowColorDown(-1),
523 sForeColorHilited(-1),
524 sShadowColorHilited(-1),
525 bJustification(GUI_BUTTON::TEXT_CENTER),
526 bTextXOffset(-1),
527 bTextYOffset(-1),
528 bTextXSubOffSet(-1),
529 bTextYSubOffSet(-1),
530 fShiftText(TRUE),
531 sWrappedWidth(-1),
532 icon(0),
533 usIconIndex(-1),
534 bIconXOffset(-1),
535 bIconYOffset(-1),
536 fShiftImage(TRUE),
537 ubToggleButtonActivated(FALSE),
538 ubSoundSchemeID(BUTTON_SOUND_SCHEME_NONE)
539 {
540 AssertMsg(left >= 0 && top >= 0 && width >= 0 && height >= 0, String("Attempting to create button with invalid coordinates %dx%d+%dx%d", left, top, width, height));
541
542 Area.SetUserPtr(this);
543
544 #ifdef BUTTONSYSTEM_DEBUGGING
545 AssertFailIfIdenticalButtonAttributesFound(this);
546 #endif
547
548 ButtonList[IDNum] = this;
549
550 SpecifyButtonSoundScheme(this, BUTTON_SOUND_SCHEME_GENERIC);
551 }
552
553
~GUI_BUTTON()554 GUI_BUTTON::~GUI_BUTTON()
555 {
556 if (this == gpAnchoredButton) gpAnchoredButton = 0;
557 if (this == gpPrevAnchoredButton) gpPrevAnchoredButton = 0;
558
559 ButtonList[IDNum] = 0;
560
561 if (uiFlags & BUTTON_SELFDELETE_IMAGE)
562 {
563 /* Checkboxes and simple create buttons have their own graphics associated
564 * with them, and it is handled internally. We delete it here. This
565 * provides the advantage of less micromanagement, but with the
566 * disadvantage of wasting more memory if you have lots of buttons using the
567 * same graphics.
568 */
569 UnloadButtonImage(image);
570 }
571 }
572
573
574 static void DefaultMoveCallback(GUI_BUTTON* btn, INT32 reason);
575
576
CreateIconButton(INT16 Icon,INT16 IconIndex,INT16 xloc,INT16 yloc,INT16 w,INT16 h,INT16 Priority,GUI_CALLBACK ClickCallback)577 GUIButtonRef CreateIconButton(INT16 Icon, INT16 IconIndex, INT16 xloc, INT16 yloc, INT16 w, INT16 h, INT16 Priority, GUI_CALLBACK ClickCallback)
578 {
579 // if button size is too small, adjust it.
580 if (w < 4) w = 4;
581 if (h < 3) h = 3;
582
583 GUI_BUTTON* const b = new GUI_BUTTON(BUTTON_GENERIC, xloc, yloc, w, h, Priority, ClickCallback, DefaultMoveCallback);
584 b->icon = GenericButtonIcons[Icon];
585 b->usIconIndex = IconIndex;
586 return b;
587 }
588
589
CreateTextButton(const ST::string & str,SGPFont font,INT16 sForeColor,INT16 sShadowColor,INT16 xloc,INT16 yloc,INT16 w,INT16 h,INT16 Priority,GUI_CALLBACK ClickCallback)590 GUIButtonRef CreateTextButton(const ST::string& str, SGPFont font, INT16 sForeColor, INT16 sShadowColor, INT16 xloc, INT16 yloc, INT16 w, INT16 h, INT16 Priority, GUI_CALLBACK ClickCallback)
591 {
592 // if button size is too small, adjust it.
593 if (w < 4) w = 4;
594 if (h < 3) h = 3;
595
596 GUI_BUTTON* const b = new GUI_BUTTON(BUTTON_GENERIC, xloc, yloc, w, h, Priority, ClickCallback, DefaultMoveCallback);
597 b->codepoints = str.to_utf32();
598 b->usFont = font;
599 b->sForeColor = sForeColor;
600 b->sShadowColor = sShadowColor;
601 return b;
602 }
603
604
CreateHotSpot(INT16 xloc,INT16 yloc,INT16 Width,INT16 Height,INT16 Priority,GUI_CALLBACK ClickCallback)605 GUIButtonRef CreateHotSpot(INT16 xloc, INT16 yloc, INT16 Width, INT16 Height, INT16 Priority, GUI_CALLBACK ClickCallback)
606 {
607 return new GUI_BUTTON(BUTTON_HOT_SPOT, xloc, yloc, Width, Height, Priority, ClickCallback, DefaultMoveCallback);
608 }
609
610
QuickCreateButtonInternal(BUTTON_PICS * const pics,const INT16 xloc,const INT16 yloc,const INT32 Type,const INT16 Priority,const GUI_CALLBACK MoveCallback,const GUI_CALLBACK ClickCallback)611 static GUIButtonRef QuickCreateButtonInternal(BUTTON_PICS* const pics, const INT16 xloc, const INT16 yloc, const INT32 Type, const INT16 Priority, const GUI_CALLBACK MoveCallback, const GUI_CALLBACK ClickCallback)
612 {
613 // Is there a QuickButton image in the given image slot?
614 if (!pics->vobj)
615 {
616 throw std::runtime_error("QuickCreateButton: Invalid button image");
617 }
618
619 GUI_BUTTON* const b = new GUI_BUTTON((Type & (BUTTON_CHECKBOX | BUTTON_NEWTOGGLE)) | BUTTON_QUICK, xloc, yloc, pics->max.w, pics->max.h, Priority, ClickCallback, MoveCallback);
620 b->image = pics;
621 return b;
622 }
623
624
QuickCreateButton(BUTTON_PICS * const image,const INT16 x,const INT16 y,const INT16 priority,const GUI_CALLBACK click)625 GUIButtonRef QuickCreateButton(BUTTON_PICS* const image, const INT16 x, const INT16 y, const INT16 priority, const GUI_CALLBACK click)
626 {
627 return QuickCreateButtonInternal(image, x, y, BUTTON_TOGGLE, priority, DefaultMoveCallback, click);
628 }
629
630
QuickCreateButtonNoMove(BUTTON_PICS * const image,const INT16 x,const INT16 y,const INT16 priority,const GUI_CALLBACK click)631 GUIButtonRef QuickCreateButtonNoMove(BUTTON_PICS* const image, const INT16 x, const INT16 y, const INT16 priority, const GUI_CALLBACK click)
632 {
633 return QuickCreateButtonInternal(image, x, y, BUTTON_TOGGLE, priority, MSYS_NO_CALLBACK, click);
634 }
635
636
QuickCreateButtonToggle(BUTTON_PICS * const image,const INT16 x,const INT16 y,const INT16 priority,const GUI_CALLBACK click)637 GUIButtonRef QuickCreateButtonToggle(BUTTON_PICS* const image, const INT16 x, const INT16 y, const INT16 priority, const GUI_CALLBACK click)
638 {
639 return QuickCreateButtonInternal(image, x, y, BUTTON_NEWTOGGLE, priority, MSYS_NO_CALLBACK, click);
640 }
641
642
QuickCreateButtonImg(const char * gfx,INT32 grayed,INT32 off_normal,INT32 off_hilite,INT32 on_normal,INT32 on_hilite,INT16 x,INT16 y,INT16 priority,GUI_CALLBACK click)643 GUIButtonRef QuickCreateButtonImg(const char* gfx, INT32 grayed, INT32 off_normal, INT32 off_hilite, INT32 on_normal, INT32 on_hilite, INT16 x, INT16 y, INT16 priority, GUI_CALLBACK click)
644 {
645 BUTTON_PICS* const img = LoadButtonImage(gfx, grayed, off_normal, off_hilite, on_normal, on_hilite);
646 GUIButtonRef const btn = QuickCreateButton(img, x, y, priority, click);
647 btn->uiFlags |= BUTTON_SELFDELETE_IMAGE;
648 return btn;
649 }
650
651
QuickCreateButtonImg(char const * const gfx,INT32 const off_normal,INT32 const on_normal,INT16 const x,INT16 const y,INT16 const priority,GUI_CALLBACK const click)652 GUIButtonRef QuickCreateButtonImg(char const* const gfx, INT32 const off_normal, INT32 const on_normal, INT16 const x, INT16 const y, INT16 const priority, GUI_CALLBACK const click)
653 {
654 return QuickCreateButtonImg(gfx, -1, off_normal, -1, on_normal, -1, x, y, priority, click);
655 }
656
657
CreateIconAndTextButton(BUTTON_PICS * Image,const ST::string & str,SGPFont font,INT16 sForeColor,INT16 sShadowColor,INT16 sForeColorDown,INT16 sShadowColorDown,INT16 xloc,INT16 yloc,INT16 Priority,GUI_CALLBACK ClickCallback)658 GUIButtonRef CreateIconAndTextButton(BUTTON_PICS* Image, const ST::string& str, SGPFont font, INT16 sForeColor, INT16 sShadowColor, INT16 sForeColorDown, INT16 sShadowColorDown, INT16 xloc, INT16 yloc, INT16 Priority, GUI_CALLBACK ClickCallback)
659 {
660 GUIButtonRef const b = QuickCreateButton(Image, xloc, yloc, Priority, ClickCallback);
661 b->codepoints = str.to_utf32();
662 b->usFont = font;
663 b->sForeColor = sForeColor;
664 b->sShadowColor = sShadowColor;
665 b->sForeColorDown = sForeColorDown;
666 b->sShadowColorDown = sShadowColorDown;
667 return b;
668 }
669
670
CreateLabel(const ST::string & str,SGPFont font,INT16 forecolor,INT16 shadowcolor,INT16 x,INT16 y,INT16 w,INT16 h,INT16 priority)671 GUIButtonRef CreateLabel(const ST::string& str, SGPFont font, INT16 forecolor, INT16 shadowcolor, INT16 x, INT16 y, INT16 w, INT16 h, INT16 priority)
672 {
673 GUIButtonRef const btn = CreateTextButton(str, font, forecolor, shadowcolor, x, y, w, h, priority, NULL);
674 btn->SpecifyDisabledStyle(GUI_BUTTON::DISABLED_STYLE_NONE);
675 DisableButton(btn);
676 return btn;
677 }
678
679
SpecifyText(const ST::string & str)680 void GUI_BUTTON::SpecifyText(const ST::string& str)
681 {
682 this->codepoints = str.to_utf32();
683 uiFlags |= BUTTON_DIRTY;
684 }
685
686
SpecifyDownTextColors(INT16 const fore_colour_down,INT16 const shadow_colour_down)687 void GUI_BUTTON::SpecifyDownTextColors(INT16 const fore_colour_down, INT16 const shadow_colour_down)
688 {
689 sForeColorDown = fore_colour_down;
690 sShadowColorDown = shadow_colour_down;
691 uiFlags |= BUTTON_DIRTY;
692 }
693
694
SpecifyHilitedTextColors(INT16 const fore_colour_highlighted,INT16 const shadow_colour_highlighted)695 void GUI_BUTTON::SpecifyHilitedTextColors(INT16 const fore_colour_highlighted, INT16 const shadow_colour_highlighted)
696 {
697 sForeColorHilited = fore_colour_highlighted;
698 sShadowColorHilited = shadow_colour_highlighted;
699 uiFlags |= BUTTON_DIRTY;
700 }
701
702
SpecifyTextJustification(Justification const j)703 void GUI_BUTTON::SpecifyTextJustification(Justification const j)
704 {
705 bJustification = j;
706 uiFlags |= BUTTON_DIRTY;
707 }
708
709
SpecifyGeneralTextAttributes(const ST::string & str,SGPFont font,INT16 fore_colour,INT16 shadow_colour)710 void GUI_BUTTON::SpecifyGeneralTextAttributes(const ST::string& str, SGPFont font, INT16 fore_colour, INT16 shadow_colour)
711 {
712 SpecifyText(str);
713 usFont = font;
714 sForeColor = fore_colour;
715 sShadowColor = shadow_colour;
716 uiFlags |= BUTTON_DIRTY;
717 }
718
719
SpecifyTextOffsets(INT8 const text_x_offset,INT8 const text_y_offset,BOOLEAN const shift_text)720 void GUI_BUTTON::SpecifyTextOffsets(INT8 const text_x_offset, INT8 const text_y_offset, BOOLEAN const shift_text)
721 {
722 bTextXOffset = text_x_offset;
723 bTextYOffset = text_y_offset;
724 fShiftText = shift_text;
725 }
726
727
SpecifyTextSubOffsets(INT8 const text_x_offset,INT8 const text_y_offset,BOOLEAN const shift_text)728 void GUI_BUTTON::SpecifyTextSubOffsets(INT8 const text_x_offset, INT8 const text_y_offset, BOOLEAN const shift_text)
729 {
730 bTextXSubOffSet = text_x_offset;
731 bTextYSubOffSet = text_y_offset;
732 fShiftText = shift_text;
733 }
734
735
SpecifyTextWrappedWidth(INT16 const wrapped_width)736 void GUI_BUTTON::SpecifyTextWrappedWidth(INT16 const wrapped_width)
737 {
738 sWrappedWidth = wrapped_width;
739 }
740
741
SpecifyDisabledStyle(DisabledStyle const style)742 void GUI_BUTTON::SpecifyDisabledStyle(DisabledStyle const style)
743 {
744 bDisabledStyle = style;
745 }
746
747
SpecifyIcon(SGPVObject const * const icon_,UINT16 const usVideoObjectIndex,INT8 const bXOffset,INT8 const bYOffset,BOOLEAN const)748 void GUI_BUTTON::SpecifyIcon(SGPVObject const* const icon_, UINT16 const usVideoObjectIndex, INT8 const bXOffset, INT8 const bYOffset, BOOLEAN const)
749 {
750 icon = icon_;
751 usIconIndex = usVideoObjectIndex;
752
753 if (!icon_) return;
754
755 bIconXOffset = bXOffset;
756 bIconYOffset = bYOffset;
757 fShiftImage = TRUE;
758
759 uiFlags |= BUTTON_DIRTY;
760 }
761
762
AllowDisabledFastHelp()763 void GUI_BUTTON::AllowDisabledFastHelp()
764 {
765 Area.uiFlags |= MSYS_ALLOW_DISABLED_FASTHELP;
766 }
767
768
SetFastHelpText(const ST::string & str)769 void GUI_BUTTON::SetFastHelpText(const ST::string& str)
770 {
771 Area.SetFastHelpText(str);
772 }
773
774
775 /* Dispatches all button callbacks for mouse movement. This function gets
776 * called by the Mouse System. *DO NOT CALL DIRECTLY*
777 */
QuickButtonCallbackMMove(MOUSE_REGION * reg,INT32 reason)778 static void QuickButtonCallbackMMove(MOUSE_REGION* reg, INT32 reason)
779 {
780 Assert(reg != NULL);
781 GUI_BUTTON* const b = reg->GetUserPtr<GUI_BUTTON>();
782
783 // ATE: New stuff for toggle buttons that work with new Win95 paradigm
784 if (b->uiFlags & BUTTON_NEWTOGGLE &&
785 reason & MSYS_CALLBACK_REASON_LOST_MOUSE &&
786 b->ubToggleButtonActivated)
787 {
788 b->uiFlags ^= BUTTON_CLICKED_ON;
789 b->ubToggleButtonActivated = FALSE;
790 }
791
792 if (!b->Enabled()) return;
793
794 if (reason & (MSYS_CALLBACK_REASON_LOST_MOUSE | MSYS_CALLBACK_REASON_GAIN_MOUSE))
795 {
796 b->uiFlags |= BUTTON_DIRTY;
797 }
798
799 if (b->MoveCallback) b->MoveCallback(b, reason);
800 }
801
802
803 /* Dispatches all button callbacks for button presses. This function is called
804 * by the Mouse System. *DO NOT CALL DIRECTLY*
805 */
QuickButtonCallbackMButn(MOUSE_REGION * reg,INT32 reason)806 static void QuickButtonCallbackMButn(MOUSE_REGION* reg, INT32 reason)
807 {
808 Assert(reg != NULL);
809 GUI_BUTTON* const b = reg->GetUserPtr<GUI_BUTTON>();
810
811 // ATE: New stuff for toggle buttons that work with new Win95 paradigm
812 if (!b->Enabled())
813 {
814 // Should we play a sound if clicked on while disabled?
815 if (b->ubSoundSchemeID &&
816 reason & (MSYS_CALLBACK_REASON_LBUTTON_DWN | MSYS_CALLBACK_REASON_RBUTTON_DWN))
817 {
818 PlayButtonSound(b, BUTTON_SOUND_DISABLED_CLICK);
819 }
820 return;
821 }
822
823 bool StateBefore = b->Clicked();
824 bool StateAfter = true; // XXX HACK000E
825
826 if (b->uiFlags & BUTTON_NEWTOGGLE)
827 {
828 if (reason & MSYS_CALLBACK_REASON_LBUTTON_DWN)
829 {
830 if (!b->ubToggleButtonActivated)
831 {
832 b->uiFlags ^= BUTTON_CLICKED_ON;
833 b->ubToggleButtonActivated = TRUE;
834 }
835 }
836 else if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
837 {
838 b->ubToggleButtonActivated = FALSE;
839 }
840 }
841
842 /* Kris:
843 * Set the anchored button incase the user moves mouse off region while still
844 * holding down the button, but only if the button is up. In Win95, buttons
845 * that are already down, and anchored never change state, unless you release
846 * the mouse in the button area.
847 */
848 if (b->MoveCallback == DefaultMoveCallback)
849 {
850 if (reason & MSYS_CALLBACK_REASON_LBUTTON_DWN)
851 {
852 gpAnchoredButton = b;
853 gfAnchoredState = StateBefore;
854 b->uiFlags |= BUTTON_CLICKED_ON;
855 }
856 else if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
857 {
858 b->uiFlags &= ~BUTTON_CLICKED_ON;
859 }
860 }
861 else if (b->uiFlags & BUTTON_CHECKBOX)
862 {
863 if (reason & MSYS_CALLBACK_REASON_LBUTTON_DWN)
864 {
865 /* The check box button gets anchored, though it doesn't actually use the
866 * anchoring move callback. The effect is different, we don't want to
867 * toggle the button state, but we do want to anchor this button so that
868 * we don't effect any other buttons while we move the mouse around in
869 * anchor mode.
870 */
871 gpAnchoredButton = b;
872 gfAnchoredState = StateBefore;
873
874 /* Trick the before state of the button to be different so the sound will
875 * play properly as checkbox buttons are processed differently.
876 */
877 StateBefore = !b->Clicked();
878 StateAfter = !StateBefore;
879 }
880 else if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
881 {
882 b->uiFlags ^= BUTTON_CLICKED_ON; //toggle the checkbox state upon release inside button area.
883 /* Trick the before state of the button to be different so the sound will
884 * play properly as checkbox buttons are processed differently.
885 */
886 StateBefore = !b->Clicked();
887 StateAfter = !StateBefore;
888 }
889 }
890
891 // If there is a callback function with this button, call it
892 if (b->ClickCallback != NULL)
893 {
894 /* Kris: January 6, 1998
895 * Added these checks to avoid a case where it was possible to process a
896 * leftbuttonup message when the button wasn't anchored, and should have
897 * been.
898 */
899 gfDelayButtonDeletion = TRUE;
900 if (!(reason & MSYS_CALLBACK_REASON_LBUTTON_UP) ||
901 b->MoveCallback != DefaultMoveCallback ||
902 gpPrevAnchoredButton == b)
903 {
904 b->ClickCallback(b, reason);
905 }
906 gfDelayButtonDeletion = FALSE;
907 }
908 else if (reason & MSYS_CALLBACK_REASON_LBUTTON_DWN)
909 {
910 // Otherwise, do default action with this button.
911 b->uiFlags ^= BUTTON_CLICKED_ON;
912 }
913
914 if (b->uiFlags & BUTTON_CHECKBOX)
915 {
916 StateAfter = b->Clicked();
917 }
918
919 // Play sounds for this enabled button (disabled sounds have already been done)
920 if (b->ubSoundSchemeID && b->Enabled())
921 {
922 if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
923 {
924 if (StateBefore && !StateAfter)
925 {
926 PlayButtonSound(b, BUTTON_SOUND_CLICKED_OFF);
927 }
928 }
929 else if (reason & MSYS_CALLBACK_REASON_LBUTTON_DWN)
930 {
931 if (!StateBefore && StateAfter)
932 {
933 PlayButtonSound(b, BUTTON_SOUND_CLICKED_ON);
934 }
935 }
936 }
937
938 if (StateBefore != StateAfter)
939 {
940 InvalidateRegion(b->X(), b->Y(), b->BottomRightX(), b->BottomRightY());
941 }
942
943 if (gfPendingButtonDeletion) RemoveButtonsMarkedForDeletion();
944 }
945
946
947 static void DrawButtonFromPtr(GUI_BUTTON* b);
948
949
RenderButtons(void)950 void RenderButtons(void)
951 {
952 SaveFontSettings();
953 FOR_EACH_BUTTON(i)
954 {
955 // If the button exists, and it's not owned by another object, draw it
956 // Kris: and make sure that the button isn't hidden.
957 GUI_BUTTON* const b = *i;
958 if (!(b->Area.uiFlags & MSYS_REGION_ENABLED)) continue;
959
960 if ((b->uiFlags ^ b->uiOldFlags) & (BUTTON_CLICKED_ON | BUTTON_ENABLED))
961 {
962 // Something is different, set dirty!
963 b->uiFlags |= BUTTON_DIRTY;
964 }
965
966 // Set old flags
967 b->uiOldFlags = b->uiFlags;
968
969 if (b->uiFlags & BUTTON_FORCE_UNDIRTY)
970 {
971 b->uiFlags &= ~BUTTON_DIRTY;
972 b->uiFlags &= ~BUTTON_FORCE_UNDIRTY;
973 }
974
975 // Check if we need to update!
976 if (b->uiFlags & BUTTON_DIRTY)
977 {
978 // Turn off dirty flag
979 b->uiFlags &= ~BUTTON_DIRTY;
980 DrawButtonFromPtr(b);
981
982 InvalidateRegion(b->X(), b->Y(), b->BottomRightX(), b->BottomRightY());
983 }
984 }
985
986 RestoreFontSettings();
987 }
988
989
MarkAButtonDirty(GUIButtonRef const b)990 void MarkAButtonDirty(GUIButtonRef const b)
991 {
992 // surgical dirtying -> marks a user specified button dirty, without dirty the whole lot of them
993 CHECKV(b != NULL); // XXX HACK000C
994 b->uiFlags |= BUTTON_DIRTY;
995 }
996
997
MarkButtonsDirty(void)998 void MarkButtonsDirty(void)
999 {
1000 FOR_EACH_BUTTON(i)
1001 {
1002 (*i)->uiFlags |= BUTTON_DIRTY;
1003 }
1004 }
1005
1006
UnMarkButtonDirty(GUIButtonRef const b)1007 void UnMarkButtonDirty(GUIButtonRef const b)
1008 {
1009 CHECKV(b != NULL); // XXX HACK000C
1010 b->uiFlags &= ~BUTTON_DIRTY;
1011 }
1012
1013
UnmarkButtonsDirty(void)1014 void UnmarkButtonsDirty(void)
1015 {
1016 FOR_EACH_BUTTON(i)
1017 {
1018 UnMarkButtonDirty(*i);
1019 }
1020 }
1021
1022
ForceButtonUnDirty(GUIButtonRef const b)1023 void ForceButtonUnDirty(GUIButtonRef const b)
1024 {
1025 CHECKV(b != NULL); // XXX HACK000C
1026 b->uiFlags &= ~BUTTON_DIRTY;
1027 b->uiFlags |= BUTTON_FORCE_UNDIRTY;
1028 }
1029
1030
Draw()1031 void GUI_BUTTON::Draw()
1032 {
1033 if (!codepoints.empty()) SaveFontSettings();
1034 if (Area.uiFlags & MSYS_REGION_ENABLED) DrawButtonFromPtr(this);
1035 if (!codepoints.empty()) RestoreFontSettings();
1036 }
1037
1038
1039 static void DrawCheckBoxButton(const GUI_BUTTON* b);
1040 static void DrawGenericButton( const GUI_BUTTON* b);
1041 static void DrawHatchOnButton( const GUI_BUTTON* b);
1042 static void DrawIconOnButton( const GUI_BUTTON* b);
1043 static void DrawQuickButton( const GUI_BUTTON* b);
1044 static void DrawShadeOnButton( const GUI_BUTTON* b);
1045 static void DrawTextOnButton( const GUI_BUTTON* b);
1046
1047
1048 // Given a pointer to a GUI_BUTTON structure, draws the button on the screen.
DrawButtonFromPtr(GUI_BUTTON * b)1049 static void DrawButtonFromPtr(GUI_BUTTON* b)
1050 {
1051 // Draw the appropriate button according to button type
1052 gbDisabledButtonStyle = GUI_BUTTON::DISABLED_STYLE_NONE;
1053 switch (b->uiFlags & BUTTON_TYPES)
1054 {
1055 case BUTTON_QUICK: DrawQuickButton(b); break;
1056 case BUTTON_GENERIC: DrawGenericButton(b); break;
1057 case BUTTON_CHECKBOX: DrawCheckBoxButton(b); break;
1058
1059 case BUTTON_HOT_SPOT:
1060 return; // hotspots don't have text, but if you want to, change this to a break!
1061 }
1062 if (b->icon) DrawIconOnButton(b);
1063 if (!b->codepoints.empty()) DrawTextOnButton(b);
1064 /* If the button is disabled, and a style has been calculated, then draw the
1065 * style last.
1066 */
1067 switch (gbDisabledButtonStyle)
1068 {
1069 case GUI_BUTTON::DISABLED_STYLE_HATCHED: DrawHatchOnButton(b); break;
1070 case GUI_BUTTON::DISABLED_STYLE_SHADED: DrawShadeOnButton(b); break;
1071 }
1072 }
1073
1074
1075 // Draws a QuickButton type button on the screen.
DrawQuickButton(const GUI_BUTTON * b)1076 static void DrawQuickButton(const GUI_BUTTON* b)
1077 {
1078 const BUTTON_PICS* const pics = b->image;
1079
1080 INT32 UseImage = 0;
1081 if (b->Enabled())
1082 {
1083 if (b->Clicked())
1084 {
1085 // Is the mouse over this area, and we have a hilite image?
1086 if (b->Area.uiFlags & MSYS_MOUSE_IN_AREA &&
1087 gfRenderHilights &&
1088 pics->OnHilite != -1)
1089 {
1090 UseImage = pics->OnHilite;
1091 }
1092 else if (pics->OnNormal != -1)
1093 {
1094 UseImage = pics->OnNormal;
1095 }
1096 }
1097 else
1098 {
1099 // Is the mouse over the button, and do we have hilite image?
1100 if (b->Area.uiFlags & MSYS_MOUSE_IN_AREA &&
1101 gfRenderHilights &&
1102 pics->OffHilite != -1)
1103 {
1104 UseImage = pics->OffHilite;
1105 }
1106 else if (pics->OffNormal != -1)
1107 {
1108 UseImage = pics->OffNormal;
1109 }
1110 }
1111 }
1112 else if (pics->Grayed != -1)
1113 {
1114 // Button is diabled so use the "Grayed-out" image
1115 UseImage = pics->Grayed;
1116 }
1117 else
1118 {
1119 UseImage = pics->OffNormal;
1120 switch (b->bDisabledStyle)
1121 {
1122 case GUI_BUTTON::DISABLED_STYLE_DEFAULT:
1123 gbDisabledButtonStyle = !b->codepoints.empty() ?
1124 GUI_BUTTON::DISABLED_STYLE_SHADED :
1125 GUI_BUTTON::DISABLED_STYLE_HATCHED;
1126 break;
1127
1128 case GUI_BUTTON::DISABLED_STYLE_HATCHED:
1129 case GUI_BUTTON::DISABLED_STYLE_SHADED:
1130 gbDisabledButtonStyle = b->bDisabledStyle;
1131 break;
1132 }
1133 }
1134
1135 BltVideoObject(ButtonDestBuffer, pics->vobj, UseImage, b->X(), b->Y());
1136 }
1137
1138
DrawHatchOnButton(const GUI_BUTTON * b)1139 static void DrawHatchOnButton(const GUI_BUTTON* b)
1140 {
1141 SGPRect ClipRect;
1142 ClipRect.iLeft = b->X();
1143 ClipRect.iRight = b->BottomRightX() - 1;
1144 ClipRect.iTop = b->Y();
1145 ClipRect.iBottom = b->BottomRightY() - 1;
1146 SGPVSurface::Lock l(ButtonDestBuffer);
1147 Blt16BPPBufferHatchRect(l.Buffer<UINT16>(), l.Pitch(), &ClipRect);
1148 }
1149
1150
DrawShadeOnButton(const GUI_BUTTON * b)1151 static void DrawShadeOnButton(const GUI_BUTTON* b)
1152 {
1153 ButtonDestBuffer->ShadowRect(b->X(), b->Y(), b->BottomRightX(), b->BottomRightY());
1154 }
1155
1156
DrawCheckBoxOnOff(BOOLEAN const on)1157 void GUI_BUTTON::DrawCheckBoxOnOff(BOOLEAN const on)
1158 {
1159 BOOLEAN const fLeftButtonState = gfLeftButtonState;
1160
1161 gfLeftButtonState = on;
1162 Area.uiFlags |= MSYS_MOUSE_IN_AREA;
1163 Draw();
1164
1165 gfLeftButtonState = fLeftButtonState;
1166 }
1167
1168
DrawCheckBoxButton(const GUI_BUTTON * b)1169 static void DrawCheckBoxButton(const GUI_BUTTON *b)
1170 {
1171 const BUTTON_PICS* const pics = b->image;
1172
1173 INT32 UseImage = 0;
1174 if (b->Enabled())
1175 {
1176 if (b->Clicked())
1177 {
1178 // Is the mouse over this area, and we have a hilite image?
1179 if (b->Area.uiFlags & MSYS_MOUSE_IN_AREA &&
1180 gfRenderHilights &&
1181 gfLeftButtonState &&
1182 pics->OnHilite != -1)
1183 {
1184 UseImage = pics->OnHilite;
1185 }
1186 else if (pics->OnNormal != -1)
1187 {
1188 UseImage = pics->OnNormal;
1189 }
1190 }
1191 else
1192 {
1193 // Is the mouse over the button, and do we have hilite image?
1194 if (b->Area.uiFlags & MSYS_MOUSE_IN_AREA &&
1195 gfRenderHilights &&
1196 gfLeftButtonState &&
1197 pics->OffHilite != -1)
1198 {
1199 UseImage = pics->OffHilite;
1200 }
1201 else if (pics->OffNormal != -1)
1202 {
1203 UseImage = pics->OffNormal;
1204 }
1205 }
1206 }
1207 else if (pics->Grayed != -1)
1208 {
1209 // Button is disabled so use the "Grayed-out" image
1210 UseImage = pics->Grayed;
1211 }
1212 else //use the disabled style
1213 {
1214 if (b->Clicked())
1215 {
1216 UseImage = pics->OnHilite;
1217 }
1218 else
1219 {
1220 UseImage = pics->OffHilite;
1221 }
1222 switch (b->bDisabledStyle)
1223 {
1224 case GUI_BUTTON::DISABLED_STYLE_DEFAULT:
1225 gbDisabledButtonStyle = GUI_BUTTON::DISABLED_STYLE_HATCHED;
1226 break;
1227
1228 case GUI_BUTTON::DISABLED_STYLE_HATCHED:
1229 case GUI_BUTTON::DISABLED_STYLE_SHADED:
1230 gbDisabledButtonStyle = b->bDisabledStyle;
1231 break;
1232 }
1233 }
1234
1235 BltVideoObject(ButtonDestBuffer, pics->vobj, UseImage, b->X(), b->Y());
1236 }
1237
1238
DrawIconOnButton(const GUI_BUTTON * b)1239 static void DrawIconOnButton(const GUI_BUTTON* b)
1240 {
1241 if (!b->icon) return;
1242
1243 // Get width and height of button area
1244 INT32 const width = b->W();
1245 INT32 const height = b->H();
1246
1247 // Compute viewable area (inside borders)
1248 SGPRect NewClip;
1249 NewClip.iLeft = b->X() + 3;
1250 NewClip.iRight = b->X() + width - 3;
1251 NewClip.iTop = b->Y() + 2;
1252 NewClip.iBottom = b->Y() + height - 2;
1253
1254 // Get Icon's blit start coordinates
1255 INT32 IconX = NewClip.iLeft;
1256 INT32 IconY = NewClip.iTop;
1257
1258 // Get current clip area
1259 SGPRect OldClip;
1260 GetClippingRect(&OldClip);
1261
1262 // Clip button's viewable area coords to screen
1263 if (NewClip.iLeft < OldClip.iLeft) NewClip.iLeft = OldClip.iLeft;
1264
1265 // Is button right off the right side of the screen?
1266 if (NewClip.iLeft > OldClip.iRight) return;
1267
1268 if (NewClip.iRight > OldClip.iRight) NewClip.iRight = OldClip.iRight;
1269
1270 // Is button completely off the left side of the screen?
1271 if (NewClip.iRight < OldClip.iLeft) return;
1272
1273 if (NewClip.iTop < OldClip.iTop) NewClip.iTop = OldClip.iTop;
1274
1275 // Are we right off the bottom of the screen?
1276 if (NewClip.iTop > OldClip.iBottom) return;
1277
1278 if (NewClip.iBottom > OldClip.iBottom) NewClip.iBottom = OldClip.iBottom;
1279
1280 // Are we off the top?
1281 if (NewClip.iBottom < OldClip.iTop) return;
1282
1283 // Did we clip the viewable area out of existance?
1284 if (NewClip.iRight <= NewClip.iLeft || NewClip.iBottom <= NewClip.iTop) return;
1285
1286 // Get the width and height of the icon itself
1287 SGPVObject const* const hvObject = b->icon;
1288 ETRLEObject const& pTrav = hvObject->SubregionProperties(b->usIconIndex);
1289
1290 /* Compute coordinates for centering the icon on the button or use the offset
1291 * system.
1292 */
1293 INT32 xp;
1294 if (b->bIconXOffset == -1)
1295 {
1296 const INT32 IconW = pTrav.usWidth + pTrav.sOffsetX;
1297 xp = IconX + (width - 6 - IconW) / 2;
1298 }
1299 else
1300 {
1301 xp = b->X() + b->bIconXOffset;
1302 }
1303
1304 INT32 yp;
1305 if (b->bIconYOffset == -1)
1306 {
1307 const INT32 IconH = pTrav.usHeight + pTrav.sOffsetY;
1308 yp = IconY + (height - 4 - IconH) / 2;
1309 }
1310 else
1311 {
1312 yp = b->Y() + b->bIconYOffset;
1313 }
1314
1315 /* Was the button clicked on? if so, move the image slightly for the illusion
1316 * that the image moved into the screen.
1317 */
1318 if (b->Clicked() && b->fShiftImage)
1319 {
1320 xp++;
1321 yp++;
1322 }
1323
1324 // Set the clipping rectangle to the viewable area of the button
1325 SetClippingRect(&NewClip);
1326
1327 BltVideoObject(ButtonDestBuffer, hvObject, b->usIconIndex, xp, yp);
1328
1329 // Restore previous clip region
1330 SetClippingRect(&OldClip);
1331 }
1332
1333
1334 // If a button has text attached to it, then it'll draw it last.
DrawTextOnButton(const GUI_BUTTON * b)1335 static void DrawTextOnButton(const GUI_BUTTON* b)
1336 {
1337 // If this button actually has a string to print
1338 if (b->codepoints.empty()) return;
1339
1340 // Get the width and height of this button
1341 INT32 const width = b->W();
1342 INT32 const height = b->H();
1343
1344 // Compute the viewable area on this button
1345 SGPRect NewClip;
1346 NewClip.iLeft = b->X() + 3;
1347 NewClip.iRight = b->X() + width - 3;
1348 NewClip.iTop = b->Y() + 2;
1349 NewClip.iBottom = b->Y() + height - 2;
1350
1351 // Get the starting coordinates to print
1352 const INT32 TextX = NewClip.iLeft;
1353 const INT32 TextY = NewClip.iTop;
1354
1355 // Get the current clipping area
1356 SGPRect OldClip;
1357 GetClippingRect(&OldClip);
1358
1359 // Clip the button's viewable area to the screen
1360 if (NewClip.iLeft < OldClip.iLeft) NewClip.iLeft = OldClip.iLeft;
1361
1362 // Are we off hte right side?
1363 if (NewClip.iLeft > OldClip.iRight) return;
1364
1365 if (NewClip.iRight > OldClip.iRight) NewClip.iRight = OldClip.iRight;
1366
1367 // Are we off the left side?
1368 if (NewClip.iRight < OldClip.iLeft) return;
1369
1370 if (NewClip.iTop < OldClip.iTop) NewClip.iTop = OldClip.iTop;
1371
1372 // Are we off the bottom of the screen?
1373 if (NewClip.iTop > OldClip.iBottom) return;
1374
1375 if (NewClip.iBottom > OldClip.iBottom) NewClip.iBottom = OldClip.iBottom;
1376
1377 // Are we off the top?
1378 if (NewClip.iBottom < OldClip.iTop) return;
1379
1380 // Did we clip the viewable area out of existance?
1381 if (NewClip.iRight <= NewClip.iLeft || NewClip.iBottom <= NewClip.iTop) return;
1382
1383 // Set the font printing settings to the buttons viewable area
1384 SetFontDestBuffer(ButtonDestBuffer, NewClip.iLeft, NewClip.iTop, NewClip.iRight, NewClip.iBottom);
1385
1386 // Compute the coordinates to center the text
1387 INT32 yp;
1388 if (b->bTextYOffset == -1)
1389 {
1390 yp = TextY + (height - GetFontHeight(b->usFont)) / 2 - 1;
1391 }
1392 else
1393 {
1394 yp = b->Y() + b->bTextYOffset;
1395 }
1396
1397 INT32 xp;
1398 if (b->bTextXOffset == -1)
1399 {
1400 switch (b->bJustification)
1401 {
1402 case GUI_BUTTON::TEXT_LEFT: xp = TextX + 3; break;
1403 case GUI_BUTTON::TEXT_RIGHT: xp = NewClip.iRight - StringPixLength(b->codepoints, b->usFont) - 3; break;
1404 default:
1405 case GUI_BUTTON::TEXT_CENTER: xp = TextX + (width - 6 - StringPixLength(b->codepoints, b->usFont)) / 2; break;
1406 }
1407 }
1408 else
1409 {
1410 xp = b->X() + b->bTextXOffset;
1411 }
1412
1413 // print the text
1414
1415 //Override the colors if necessary.
1416 INT16 sForeColor;
1417 if (b->Enabled() && b->Area.uiFlags & MSYS_MOUSE_IN_AREA && b->sForeColorHilited != -1)
1418 {
1419 sForeColor = b->sForeColorHilited;
1420 }
1421 else if (b->Clicked() && b->sForeColorDown != -1)
1422 {
1423 sForeColor = b->sForeColorDown;
1424 }
1425 else
1426 {
1427 sForeColor = b->sForeColor;
1428 }
1429
1430 UINT8 shadow;
1431 if (b->Enabled() && b->Area.uiFlags & MSYS_MOUSE_IN_AREA && b->sShadowColorHilited != -1)
1432 {
1433 shadow = b->sShadowColorHilited;
1434 }
1435 else if (b->Clicked() && b->sShadowColorDown != -1)
1436 {
1437 shadow = b->sShadowColorDown;
1438 }
1439 else if (b->sShadowColor != -1)
1440 {
1441 shadow = b->sShadowColor;
1442 }
1443 else
1444 {
1445 shadow = DEFAULT_SHADOW;
1446 }
1447
1448 SetFontAttributes(b->usFont, sForeColor, shadow);
1449
1450 if (b->Clicked() && b->fShiftText)
1451 {
1452 /* Was the button clicked on? if so, move the text slightly for the illusion
1453 * that the text moved into the screen. */
1454 xp++;
1455 yp++;
1456 }
1457
1458 if (b->sWrappedWidth != -1)
1459 {
1460 UINT8 bJustified = 0;
1461 switch (b->bJustification)
1462 {
1463 case GUI_BUTTON::TEXT_LEFT: bJustified = LEFT_JUSTIFIED; break;
1464 case GUI_BUTTON::TEXT_RIGHT: bJustified = RIGHT_JUSTIFIED; break;
1465 case GUI_BUTTON::TEXT_CENTER: bJustified = CENTER_JUSTIFIED; break;
1466 default: SLOGA("DrawTextOnButton: invalid text alignment"); break;
1467 }
1468 if (b->bTextXOffset == -1)
1469 {
1470 /* Kris:
1471 * There needs to be recalculation of the start positions based on the
1472 * justification and the width specified wrapped width. I was drawing a
1473 * double lined word on the right side of the button to find it drawing
1474 * way over to the left. I've added the necessary code for the right and
1475 * center justification.
1476 */
1477 yp = b->Y() + 2;
1478
1479 switch (b->bJustification)
1480 {
1481 case GUI_BUTTON::TEXT_RIGHT:
1482 xp = b->BottomRightX() - 3 - b->sWrappedWidth;
1483 if (b->fShiftText && b->Clicked())
1484 {
1485 xp++;
1486 yp++;
1487 }
1488 break;
1489
1490 case GUI_BUTTON::TEXT_CENTER:
1491 xp = b->X() + 3 + b->sWrappedWidth / 2;
1492 if (b->fShiftText && b->Clicked())
1493 {
1494 xp++;
1495 yp++;
1496 }
1497 break;
1498 }
1499 }
1500 yp += b->bTextYSubOffSet;
1501 xp += b->bTextXSubOffSet;
1502 DisplayWrappedString(xp, yp, b->sWrappedWidth, 1, b->usFont, sForeColor, b->codepoints, FONT_MCOLOR_BLACK, bJustified);
1503 }
1504 else
1505 {
1506 yp += b->bTextYSubOffSet;
1507 xp += b->bTextXSubOffSet;
1508 MPrint(xp, yp, b->codepoints);
1509 }
1510 // Restore the old text printing settings
1511 }
1512
1513
1514 /* This function is called by the DrawIconicButton and DrawTextButton routines
1515 * to draw the borders and background of the buttons.
1516 */
DrawGenericButton(const GUI_BUTTON * b)1517 static void DrawGenericButton(const GUI_BUTTON* b)
1518 {
1519 // Select the graphics to use depending on the current state of the button
1520 HVOBJECT BPic;
1521 if (!b->Enabled())
1522 {
1523 BPic = GenericButtonOffNormal;
1524 switch (b->bDisabledStyle)
1525 {
1526 case GUI_BUTTON::DISABLED_STYLE_DEFAULT:
1527 gbDisabledButtonStyle = !b->codepoints.empty() ?
1528 GUI_BUTTON::DISABLED_STYLE_SHADED :
1529 GUI_BUTTON::DISABLED_STYLE_HATCHED;
1530 break;
1531
1532 case GUI_BUTTON::DISABLED_STYLE_HATCHED:
1533 case GUI_BUTTON::DISABLED_STYLE_SHADED:
1534 gbDisabledButtonStyle = b->bDisabledStyle;
1535 break;
1536 }
1537 }
1538 else if (b->Clicked())
1539 {
1540 if (b->Area.uiFlags & MSYS_MOUSE_IN_AREA && GenericButtonOnHilite != NULL && gfRenderHilights)
1541 {
1542 BPic = GenericButtonOnHilite;
1543 }
1544 else
1545 {
1546 BPic = GenericButtonOnNormal;
1547 }
1548 }
1549 else
1550 {
1551 if (b->Area.uiFlags & MSYS_MOUSE_IN_AREA && GenericButtonOffHilite != NULL && gfRenderHilights)
1552 {
1553 BPic = GenericButtonOffHilite;
1554 }
1555 else
1556 {
1557 BPic = GenericButtonOffNormal;
1558 }
1559 }
1560
1561 const INT32 iBorderWidth = 3;
1562 const INT32 iBorderHeight = 2;
1563
1564 // Compute the number of button "chunks" needed to be blitted
1565 INT32 const width = b->W();
1566 INT32 const height = b->H();
1567 const INT32 NumChunksWide = width / iBorderWidth;
1568 INT32 NumChunksHigh = height / iBorderHeight;
1569 const INT32 hremain = height % iBorderHeight;
1570 const INT32 wremain = width % iBorderWidth;
1571
1572 INT32 const bx = b->X();
1573 INT32 const by = b->Y();
1574 INT32 const cx = bx + (NumChunksWide - 1) * iBorderWidth + wremain;
1575 INT32 const cy = by + (NumChunksHigh - 1) * iBorderHeight + hremain;
1576
1577 // Fill the button's area with the button's background color
1578 ColorFillVideoSurfaceArea(ButtonDestBuffer, b->X(), b->Y(), b->BottomRightX(), b->BottomRightY(), GenericButtonFillColors);
1579
1580 SGPVSurface::Lock l(ButtonDestBuffer);
1581 UINT16* const pDestBuf = l.Buffer<UINT16>();
1582 UINT32 const uiDestPitchBYTES = l.Pitch();
1583
1584 SGPRect ClipRect;
1585 GetClippingRect(&ClipRect);
1586
1587 // Draw the button's borders and corners (horizontally)
1588 for (INT32 q = 0; q < NumChunksWide; q++)
1589 {
1590 INT32 const ImgNum = (q == 0 ? 0 : 1);
1591 INT32 const x = bx + q * iBorderWidth;
1592 Blt8BPPDataTo16BPPBufferTransparentClip(pDestBuf, uiDestPitchBYTES, BPic, x, by, ImgNum, &ClipRect);
1593 Blt8BPPDataTo16BPPBufferTransparentClip(pDestBuf, uiDestPitchBYTES, BPic, x, cy, ImgNum + 5, &ClipRect);
1594 }
1595 // Blit the right side corners
1596 Blt8BPPDataTo16BPPBufferTransparentClip(pDestBuf, uiDestPitchBYTES, BPic, cx, by, 2, &ClipRect);
1597 Blt8BPPDataTo16BPPBufferTransparentClip(pDestBuf, uiDestPitchBYTES, BPic, cx, cy, 7, &ClipRect);
1598 // Draw the vertical members of the button's borders
1599 NumChunksHigh--;
1600
1601 if (hremain != 0)
1602 {
1603 INT32 const y = by + NumChunksHigh * iBorderHeight - iBorderHeight + hremain;
1604 Blt8BPPDataTo16BPPBufferTransparentClip(pDestBuf, uiDestPitchBYTES, BPic, bx, y, 3, &ClipRect);
1605 Blt8BPPDataTo16BPPBufferTransparentClip(pDestBuf, uiDestPitchBYTES, BPic, cx, y, 4, &ClipRect);
1606 }
1607
1608 for (INT32 q = 1; q < NumChunksHigh; q++)
1609 {
1610 INT32 const y = by + q * iBorderHeight;
1611 Blt8BPPDataTo16BPPBufferTransparentClip(pDestBuf, uiDestPitchBYTES, BPic, bx, y, 3, &ClipRect);
1612 Blt8BPPDataTo16BPPBufferTransparentClip(pDestBuf, uiDestPitchBYTES, BPic, cx, y, 4, &ClipRect);
1613 }
1614 }
1615
1616
CreateCheckBoxButton(INT16 x,INT16 y,const char * filename,INT16 Priority,GUI_CALLBACK ClickCallback)1617 GUIButtonRef CreateCheckBoxButton(INT16 x, INT16 y, const char* filename, INT16 Priority, GUI_CALLBACK ClickCallback)
1618 {
1619 Assert(filename != NULL);
1620 BUTTON_PICS* const ButPic = LoadButtonImage(filename, -1, 0, 1, 2, 3);
1621 GUIButtonRef const b = QuickCreateButtonInternal(ButPic, x, y, BUTTON_CHECKBOX, Priority, MSYS_NO_CALLBACK, ClickCallback);
1622
1623 //change the flags so that it isn't a quick button anymore
1624 b->uiFlags &= ~BUTTON_QUICK;
1625 b->uiFlags |= BUTTON_CHECKBOX | BUTTON_SELFDELETE_IMAGE;
1626
1627 return b;
1628 }
1629
1630
1631 /* Generic Button Movement Callback to reset the mouse button if the mouse is no
1632 * longer in the button region.
1633 */
DefaultMoveCallback(GUI_BUTTON * btn,INT32 reason)1634 static void DefaultMoveCallback(GUI_BUTTON* btn, INT32 reason)
1635 {
1636 // If the button isn't the anchored button, then we don't want to modify the button state.
1637 if (btn != gpAnchoredButton) return;
1638
1639 if (reason & MSYS_CALLBACK_REASON_LOST_MOUSE)
1640 {
1641 if (!gfAnchoredState)
1642 {
1643 btn->uiFlags &= ~BUTTON_CLICKED_ON;
1644 if (btn->ubSoundSchemeID)
1645 {
1646 PlayButtonSound(btn, BUTTON_SOUND_CLICKED_OFF);
1647 }
1648 }
1649 InvalidateRegion(btn->X(), btn->Y(), btn->BottomRightX(), btn->BottomRightY());
1650 }
1651 else if (reason & MSYS_CALLBACK_REASON_GAIN_MOUSE)
1652 {
1653 btn->uiFlags |= BUTTON_CLICKED_ON;
1654 if (btn->ubSoundSchemeID)
1655 {
1656 PlayButtonSound(btn, BUTTON_SOUND_CLICKED_ON);
1657 }
1658 InvalidateRegion(btn->X(), btn->Y(), btn->BottomRightX(), btn->BottomRightY());
1659 }
1660 }
1661
1662
ReleaseAnchorMode(void)1663 void ReleaseAnchorMode(void)
1664 {
1665 GUI_BUTTON* const b = gpAnchoredButton;
1666 if (!b) return;
1667
1668 if (gusMouseXPos < b->X() || b->BottomRightX() < gusMouseXPos ||
1669 gusMouseYPos < b->Y() || b->BottomRightY() < gusMouseYPos)
1670 {
1671 //released outside button area, so restore previous button state.
1672 if (gfAnchoredState)
1673 {
1674 b->uiFlags |= BUTTON_CLICKED_ON;
1675 }
1676 else
1677 {
1678 b->uiFlags &= ~BUTTON_CLICKED_ON;
1679 }
1680 InvalidateRegion(b->X(), b->Y(), b->BottomRightX(), b->BottomRightY());
1681 }
1682 gpPrevAnchoredButton = b;
1683 gpAnchoredButton = 0;
1684 }
1685
1686
Hide()1687 void GUI_BUTTON::Hide()
1688 {
1689 Area.Disable();
1690 uiFlags |= BUTTON_DIRTY;
1691 InvalidateRegion(X(), Y(), BottomRightX(), BottomRightY());
1692 }
1693
1694
HideButton(GUIButtonRef const b)1695 void HideButton(GUIButtonRef const b)
1696 {
1697 CHECKV(b != NULL); // XXX HACK000C
1698 b->Hide();
1699 }
1700
1701
Show()1702 void GUI_BUTTON::Show()
1703 {
1704 Area.Enable();
1705 uiFlags |= BUTTON_DIRTY;
1706 InvalidateRegion(X(), Y(), BottomRightX(), BottomRightY());
1707 }
1708
1709
ShowButton(GUIButtonRef const b)1710 void ShowButton(GUIButtonRef const b)
1711 {
1712 CHECKV(b != NULL); // XXX HACK000C
1713 b->Show();
1714 }
1715
1716
GetGenericButtonFillColor(void)1717 UINT16 GetGenericButtonFillColor(void)
1718 {
1719 return GenericButtonFillColors;
1720 }
1721