Download
cyberslak
/lightsout
/main.c
(View History)
cyberslak Properly check for Thread Manager before using it | Latest amendment: 25 on 2025-04-21 |
1 | // SPDX-License-Identifier: MIT |
2 | |
3 | #include <stdlib.h> |
4 | #include <string.h> |
5 | #include <stdio.h> |
6 | #include <unistd.h> |
7 | #include "util.h" |
8 | #include "net.h" |
9 | #include "ha.h" |
10 | #include "list.h" |
11 | #include "slider.h" |
12 | #include "preferences.h" |
13 | #include "entity_ldef.h" |
14 | |
15 | #define kScrollBarWidth 15 |
16 | #define kSliderCDEFId 128 |
17 | #define kEntityLDEFId 128 |
18 | #define kCustomSliderProc (16 * kSliderCDEFId) |
19 | #define kCustomListProc (kEntityLDEFId) |
20 | #define kBarUpdateRate (1 * 60) // 1 second(s) |
21 | #define kBarWidth 100 |
22 | #define kCtrlWidth 40 |
23 | #define kCtrlHeight 80 |
24 | #define kCtrlPad ((kBarWidth - kCtrlWidth) / 2) |
25 | |
26 | #define kEntityListDialog 128 |
27 | |
28 | #define mApple 128 |
29 | #define mFile 129 |
30 | #define mEdit 130 |
31 | |
32 | #define miAbout 1 |
33 | #define miEntityAdd 1 |
34 | #define miQuit 3 |
35 | #define miClear 4 |
36 | |
37 | /* GLOBALS */ |
38 | Boolean gRunning = true; |
39 | short gNumBars = 0; |
40 | Boolean gHaveScrollBar = false; |
41 | |
42 | Handle gSliderCDEF, gEntityLDEF; |
43 | PrefHandle gPreferences; |
44 | MenuHandle gEditMenu; |
45 | |
46 | Pattern gSliderPattern, gBackPattern; |
47 | |
48 | WindowPtr gMainWindow = NULL; |
49 | list_t gEntities = LIST_INIT(gEntities); |
50 | |
51 | /* PROTOTYPES */ |
52 | void ensure_valid_prefs(void); |
53 | void dialog_info_free(struct dialog_info *dinfo); |
54 | void entity_list_add(struct dialog_info *dinfo); |
55 | void entity_list_mousedown(EventRecord* evt, WindowPtr win); |
56 | void entity_list_activate(bool activate, WindowPtr win); |
57 | void entity_list_show(void); |
58 | void event_loop(void); |
59 | void event_update(WindowPtr win); |
60 | void event_activate(bool activate, EventRecord* evt, WindowPtr win); |
61 | void event_mousedown(EventRecord*, WindowPtr, short); |
62 | |
63 | void menu_init(void); |
64 | void menu_click(long); |
65 | |
66 | void lightsout_init(Handle previousEnts); |
67 | void bar_create(WindowPtr win, const char* id); |
68 | |
69 | void resolve_slider_cdef(); |
70 | void resolve_entity_ldef(); |
71 | |
72 | pascal void dialog_list_update(WindowPtr theWin, short itemNo); |
73 | |
74 | void bar_draw(struct entity*); |
75 | void bars_draw(); |
76 | void draw_background(); |
77 | void bars_update(); |
78 | |
79 | void scrollbar_create(WindowPtr win); |
80 | void scrollbar_remove(WindowPtr win); |
81 | |
82 | void update_entity_positions(short scrollValue); |
83 | void get_entity_bounds(struct entity *ent, Rect* out); |
84 | |
85 | struct dialog_info |
86 | { |
87 | ListHandle lHnd; |
88 | struct list ents; |
89 | void (*handle_mousedown)(EventRecord*, WindowPtr); |
90 | void (*handle_activate)(bool activate, WindowPtr); |
91 | }; |
92 | |
93 | struct window_info |
94 | { |
95 | ControlHandle scrollBar; |
96 | }; |
97 | |
98 | enum control_type |
99 | { |
100 | CONTROL_SLIDER, |
101 | CONTROL_SCROLLBAR, |
102 | }; |
103 | |
104 | void ensure_valid_prefs(void) |
105 | { |
106 | bool updatedPrefs = false; |
107 | |
108 | gPreferences = preferences_load(); |
109 | |
110 | while (ha_test_prefs() != 0) |
111 | { |
112 | info("Server configuration is invalid; " |
113 | "please review your settings."); |
114 | |
115 | // preferences invalid; show preferences dialog |
116 | if (preferences_dialog() != 0) |
117 | { |
118 | ExitToShell(); |
119 | } else { |
120 | updatedPrefs = true; |
121 | } |
122 | } |
123 | |
124 | if (updatedPrefs) |
125 | preferences_save(gPreferences); |
126 | } |
127 | |
128 | int main() |
129 | { |
130 | toolbox_init(); |
131 | memory_init(); |
132 | |
133 | net_init(); |
134 | |
135 | resolve_slider_cdef(); |
136 | resolve_entity_ldef(); |
137 | GetIndPattern(&gSliderPattern, 0, 22); |
138 | GetIndPattern(&gBackPattern, 0, 3); |
139 | |
140 | menu_init(); |
141 | |
142 | ensure_valid_prefs(); |
143 | |
144 | lightsout_init(preferences_get_entities()); |
145 | event_loop(); |
146 | |
147 | net_fini(); |
148 | |
149 | preferences_save_entities(&gEntities); |
150 | |
151 | return 0; |
152 | } |
153 | |
154 | void resolve_slider_cdef() |
155 | { |
156 | def_jmp_t* slider_cdef; |
157 | gSliderCDEF = GetResource('CDEF', 128); |
158 | HLock(gSliderCDEF); |
159 | |
160 | slider_cdef = (def_jmp_t*)*gSliderCDEF; |
161 | slider_cdef->addr = slider_proc; |
162 | } |
163 | |
164 | void resolve_entity_ldef() |
165 | { |
166 | def_jmp_t* entity_ldef; |
167 | gEntityLDEF = GetResource('LDEF', 128); |
168 | HLock(gEntityLDEF); |
169 | |
170 | entity_ldef = (def_jmp_t*)*gEntityLDEF; |
171 | entity_ldef->addr = entity_ldef_proc; |
172 | } |
173 | |
174 | void scrollbar_create(WindowPtr win) |
175 | { |
176 | struct window_info *info = (void*)GetWRefCon(win); |
177 | Rect sbRect; |
178 | short winHeight = rect_height(&win->portRect); |
179 | short winWidth = rect_width(&win->portRect); |
180 | |
181 | sbRect.top = winHeight - kScrollBarWidth; |
182 | sbRect.left = -1; |
183 | sbRect.right = winWidth + 1; |
184 | sbRect.bottom = winHeight + 1; |
185 | |
186 | info->scrollBar = NewControl(win, |
187 | &sbRect, |
188 | (u8*)"", |
189 | true, |
190 | 0, |
191 | 0, |
192 | 1, |
193 | scrollBarProc, |
194 | CONTROL_SCROLLBAR); |
195 | } |
196 | |
197 | void scrollbar_remove(WindowPtr win) |
198 | { |
199 | struct window_info *info = (void*)GetWRefCon(win); |
200 | |
201 | if (info->scrollBar) |
202 | { |
203 | DisposeControl(info->scrollBar); |
204 | info->scrollBar = NULL; |
205 | } |
206 | } |
207 | |
208 | void handle_scrollbar(WindowPtr win) |
209 | { |
210 | short width = rect_width(&win->portRect); |
211 | short height = rect_height(&win->portRect); |
212 | if (gNumBars > 3 && !gHaveScrollBar) |
213 | { |
214 | SizeWindow(win, width, height + kScrollBarWidth, true); |
215 | scrollbar_create(win); |
216 | gHaveScrollBar = true; |
217 | } else if (gNumBars > 3) |
218 | { |
219 | struct window_info *info = (void*)GetWRefCon(win); |
220 | SetControlMaximum(info->scrollBar, gNumBars - 3); |
221 | } |
222 | |
223 | if (gNumBars <= 3 && gHaveScrollBar) |
224 | { |
225 | SizeWindow(win, width, height - kScrollBarWidth, true); |
226 | scrollbar_remove(win); |
227 | gHaveScrollBar = false; |
228 | } |
229 | } |
230 | |
231 | void bar_create(WindowPtr win, const char* id) |
232 | { |
233 | struct entity* ent = xmalloc(sizeof(struct entity)); |
234 | Rect controlPos; |
235 | Rect updateBounds; |
236 | |
237 | short winHeight; |
238 | short barOffset = gNumBars * kBarWidth; |
239 | |
240 | snprintf(ent->id, 128, "%s", id); |
241 | |
242 | winHeight = rect_height(&win->portRect); |
243 | winHeight -= (gHaveScrollBar ? kScrollBarWidth : 0); |
244 | |
245 | controlPos.top = (winHeight - kCtrlHeight)/2; |
246 | controlPos.bottom = controlPos.top + kCtrlHeight; |
247 | controlPos.left = barOffset + kCtrlPad; |
248 | controlPos.right = controlPos.left + kCtrlWidth; |
249 | |
250 | ha_get_entity_state(ent->id, &ent->state); |
251 | |
252 | ent->ctrl = NewControl( |
253 | win, |
254 | &controlPos, |
255 | nil, |
256 | true, |
257 | ent->state.brightness, // current value |
258 | 0, 255, |
259 | kCustomSliderProc, |
260 | CONTROL_SLIDER |
261 | ); |
262 | |
263 | ent->outerRgn = NewRgn(); |
264 | ent->selected = false; |
265 | |
266 | get_entity_bounds(ent, &updateBounds); |
267 | |
268 | updateBounds.right += 2; // bg edge |
269 | |
270 | list_add_tail(&gEntities, &ent->node); |
271 | |
272 | gNumBars++; |
273 | |
274 | barOffset += kBarWidth; |
275 | |
276 | handle_scrollbar(win); |
277 | |
278 | with_port(win, { |
279 | InvalRect(&updateBounds); |
280 | }) |
281 | } |
282 | |
283 | void bar_remove(WindowPtr win, struct entity *ent) |
284 | { |
285 | struct window_info *info = (void*)GetWRefCon(win); |
286 | short scrollValue = 0; |
287 | Rect barRect; |
288 | |
289 | get_entity_bounds(ent, &barRect); |
290 | |
291 | list_del(&ent->node); |
292 | DisposeControl(ent->ctrl); |
293 | DisposeRgn(ent->outerRgn); |
294 | free(ent); |
295 | |
296 | // you can't just update the bar rect, |
297 | // because other bars might shift into the window area |
298 | with_port(win, { |
299 | InvalRect(&win->portRect); |
300 | }) |
301 | |
302 | gNumBars--; |
303 | |
304 | handle_scrollbar(win); |
305 | |
306 | if (info->scrollBar) |
307 | scrollValue = GetControlValue(info->scrollBar); |
308 | |
309 | update_entity_positions(scrollValue); |
310 | } |
311 | |
312 | /** Update a list in a dialog. |
313 | * |
314 | * Assumes the list is stored in the window reference of the |
315 | * dialog. Then calls LUpdate to update the list contents, |
316 | * and FrameRect to draw the border around the list. |
317 | */ |
318 | pascal void dialog_list_update(WindowPtr theWin, short itemNo) |
319 | { |
320 | struct dialog_info *dinfo = (void*)GetWRefCon(theWin); |
321 | ListHandle lHnd = dinfo->lHnd; |
322 | Rect frameRect = (**lHnd).rView; |
323 | frameRect.right += kScrollBarWidth; |
324 | rect_expand(&frameRect, 1); |
325 | |
326 | LUpdate(theWin->visRgn, lHnd); |
327 | |
328 | FrameRect(&frameRect); |
329 | } |
330 | |
331 | void dialog_info_free(struct dialog_info *dinfo) |
332 | { |
333 | list_t *node; |
334 | LDispose(dinfo->lHnd); |
335 | |
336 | while ((node = list_pop(&dinfo->ents)) != NULL) |
337 | { |
338 | free(container_of(node, struct entity, node)); |
339 | } |
340 | |
341 | free(dinfo); |
342 | } |
343 | |
344 | void entity_list_add(struct dialog_info* dinfo) |
345 | { |
346 | Point pt = {0, 0}; |
347 | struct entity* ent; |
348 | if (LGetSelect(true, &pt, dinfo->lHnd)) |
349 | { |
350 | short len = 4; |
351 | LGetCell(&ent, &len, pt, dinfo->lHnd); |
352 | bar_create(gMainWindow, ent->id); |
353 | } |
354 | } |
355 | |
356 | void entity_list_mousedown(EventRecord* evt, WindowPtr win) |
357 | { |
358 | DialogPtr theDialog; |
359 | short itemHit; |
360 | struct dialog_info* dinfo = (void*)GetWRefCon(win); |
361 | |
362 | with_port(win, { |
363 | DialogSelect(evt, &theDialog, &itemHit); |
364 | |
365 | GlobalToLocal(&evt->where); |
366 | |
367 | switch (itemHit) |
368 | { |
369 | case 1: |
370 | entity_list_add(dinfo); |
371 | |
372 | // must dispose of list before dialog |
373 | dialog_info_free(dinfo); |
374 | DisposeDialog(theDialog); |
375 | break; |
376 | case 2: |
377 | LClick(evt->where, evt->modifiers, dinfo->lHnd); |
378 | break; |
379 | } |
380 | }) |
381 | } |
382 | |
383 | void entity_list_activate(bool activate, WindowPtr win) |
384 | { |
385 | struct dialog_info *dinfo = (void*)GetWRefCon(win); |
386 | LActivate(activate, dinfo->lHnd); |
387 | } |
388 | |
389 | /* Open the entity selection dialog. */ |
390 | void entity_list_show() |
391 | { |
392 | short type; |
393 | Handle itemHnd; |
394 | Rect itemRect, contentRect; |
395 | Rect dataBounds; |
396 | Point cSize = {0, 0}; |
397 | Point pt = {0, 0}; |
398 | ListHandle lHnd; |
399 | struct dialog_info *dinfo = xmalloc(sizeof(struct dialog_info)); |
400 | struct entity *ent; |
401 | list_t *node; |
402 | |
403 | short numEnts = ha_get_entities(&dinfo->ents); |
404 | |
405 | DialogPtr dlog = GetNewDialog( |
406 | kEntityListDialog, nil, (WindowPtr)-1); |
407 | |
408 | GetDialogItem(dlog, 2, &type, &itemHnd, &itemRect); |
409 | SetDialogItem(dlog, 2, type, (Handle)dialog_list_update, &itemRect); |
410 | |
411 | contentRect = itemRect; |
412 | contentRect.right -= kScrollBarWidth; |
413 | |
414 | rect_expand(&itemRect, 1); |
415 | |
416 | SetRect(&dataBounds, 0, 0, 1, 0); |
417 | lHnd = LNew(&contentRect, &dataBounds, cSize, kCustomListProc, |
418 | dlog, true, false, false, true); |
419 | |
420 | // todo allow multiple selections |
421 | (**lHnd).selFlags |= lOnlyOne; |
422 | |
423 | dinfo->lHnd = lHnd; |
424 | dinfo->handle_mousedown = entity_list_mousedown; |
425 | dinfo->handle_activate = entity_list_activate; |
426 | |
427 | SetWRefCon(dlog, (long)dinfo); |
428 | |
429 | LAddRow(numEnts, 0, lHnd); |
430 | |
431 | list_foreach(&dinfo->ents, node) |
432 | { |
433 | ent = container_of(node, struct entity, node); |
434 | LSetCell(&ent, sizeof(Ptr), pt, lHnd); |
435 | pt.v++; |
436 | } |
437 | |
438 | ShowWindow(dlog); |
439 | } |
440 | |
441 | void lightsout_init(Handle previousEnts) |
442 | { |
443 | WindowPtr win; |
444 | short controlWidth = 40; |
445 | short controlHeight = 80; |
446 | struct window_info *wi = xmalloc(sizeof(struct window_info)); |
447 | |
448 | win = GetNewCWindow(128, nil, (WindowPtr)-1); |
449 | SetWTitle(win, "\pLights Out"); |
450 | SetWRefCon(win, (long)wi); |
451 | |
452 | wi->scrollBar = NULL; |
453 | |
454 | SetPort(win); |
455 | |
456 | win_center(win); |
457 | |
458 | gMainWindow = win; |
459 | |
460 | if (previousEnts) |
461 | { |
462 | // previous entities are packed into the handle |
463 | // as consecutive NUL-terminated strings. |
464 | size_t sz = GetHandleSize(previousEnts); |
465 | size_t idx = 0; |
466 | size_t len = 0; |
467 | HLock(previousEnts); |
468 | |
469 | while (idx < sz) |
470 | { |
471 | len = strlen(*previousEnts + idx); |
472 | bar_create(win, *previousEnts + idx); |
473 | idx += len + 1; |
474 | } |
475 | |
476 | HUnlock(previousEnts); |
477 | } |
478 | |
479 | ShowWindow(win); |
480 | } |
481 | |
482 | void menu_init(void) |
483 | { |
484 | Handle menuBar = GetNewMBar(128); |
485 | MenuHandle appleMenu; |
486 | short i; |
487 | |
488 | SetMenuBar(menuBar); |
489 | |
490 | gEditMenu = GetMenuHandle(mEdit); |
491 | |
492 | for (i = 1; i <= miClear; ++i) |
493 | DisableItem(gEditMenu, i); |
494 | |
495 | appleMenu = GetMenuHandle(mApple); |
496 | AppendResMenu(appleMenu, 'DRVR'); |
497 | |
498 | DrawMenuBar(); |
499 | } |
500 | |
501 | bool menu_item_enabled(MenuHandle hnd, short itemId) |
502 | { |
503 | if (itemId < 32) |
504 | { |
505 | return (**hnd).enableFlags & (1 << itemId); |
506 | } |
507 | return (**hnd).enableFlags & 1; |
508 | } |
509 | |
510 | void menu_update() |
511 | { |
512 | struct entity *ent; |
513 | list_t *node; |
514 | bool any_selected = false; |
515 | bool itemEnabled = menu_item_enabled(gEditMenu, miClear); |
516 | |
517 | list_foreach(&gEntities, node) |
518 | { |
519 | ent = container_of(node, struct entity, node); |
520 | if (ent->selected) |
521 | any_selected = true; |
522 | } |
523 | |
524 | if (any_selected && !itemEnabled) |
525 | { |
526 | EnableItem(gEditMenu, miClear); |
527 | } |
528 | else if (!any_selected && itemEnabled) |
529 | { |
530 | DisableItem(gEditMenu, miClear); |
531 | } |
532 | } |
533 | |
534 | void menu_click(long menuChoice) |
535 | { |
536 | short menuId, itemId; |
537 | MenuHandle theMenu; |
538 | GrafPtr oldPort; |
539 | Str255 itemName; |
540 | menuId = HiWord(menuChoice); |
541 | itemId = LoWord(menuChoice); |
542 | |
543 | switch (menuId) |
544 | { |
545 | case mApple: |
546 | if (itemId == miAbout) |
547 | { |
548 | info("LightsOut\r© 2025 Sam van Kampen"); |
549 | } else |
550 | { |
551 | theMenu = GetMenuHandle(menuId); |
552 | GetMenuItemText(theMenu, itemId, itemName); |
553 | GetPort(&oldPort); |
554 | OpenDeskAcc(itemName); |
555 | SetPort(oldPort); |
556 | } |
557 | break; |
558 | case mFile: |
559 | if (itemId == miEntityAdd) |
560 | { |
561 | entity_list_show(); |
562 | } |
563 | else if (itemId == miQuit) |
564 | { |
565 | gRunning = false; |
566 | } |
567 | break; |
568 | case mEdit: |
569 | if (itemId == miClear && FrontWindow() == gMainWindow) |
570 | { |
571 | struct entity *ent; |
572 | list_t *node, *prev; |
573 | list_foreach_safe_rev(&gEntities, node, prev) |
574 | { |
575 | ent = container_of(node, struct entity, node); |
576 | if (ent->selected) |
577 | bar_remove(gMainWindow, ent); |
578 | } |
579 | } |
580 | break; |
581 | } |
582 | |
583 | HiliteMenu(0); |
584 | } |
585 | |
586 | void get_entity_bounds(struct entity *ent, Rect* out) |
587 | { |
588 | Rect* sliderBounds; |
589 | short scrollBarCompensation = (gHaveScrollBar ? kScrollBarWidth : 0); |
590 | WindowPtr win = (**(ent->ctrl)).contrlOwner; |
591 | |
592 | HLock((Handle)ent->ctrl); |
593 | sliderBounds = &(*(ent->ctrl))->contrlRect; |
594 | HUnlock((Handle)ent->ctrl); |
595 | |
596 | SetRect(out, sliderBounds->left - kCtrlPad, |
597 | 0, sliderBounds->right + kCtrlPad, |
598 | win->portRect.bottom - scrollBarCompensation); |
599 | } |
600 | |
601 | void bar_draw(struct entity *ent) |
602 | { |
603 | ControlHandle ctrl = ent->ctrl; |
604 | char buf[128]; |
605 | unsigned char* pBuf; |
606 | Rect sliderBounds = (*ctrl)->contrlRect; |
607 | Rect barBounds; |
608 | short width; |
609 | short val = GetControlValue(ctrl); |
610 | |
611 | TextFont(1); |
612 | TextSize(10); |
613 | |
614 | get_entity_bounds(ent, &barBounds); |
615 | |
616 | EraseRect(&barBounds); |
617 | FrameRect(&barBounds); |
618 | |
619 | snprintf(buf, 128, "%d%%", |
620 | (val * 100 + 127) / 255); |
621 | |
622 | pBuf = c2pstr(buf); |
623 | width = StringWidth(pBuf); |
624 | |
625 | MoveTo(barBounds.left + (kBarWidth - width)/2, |
626 | sliderBounds.bottom + 20); |
627 | |
628 | DrawString(pBuf); |
629 | |
630 | strcpy(buf, ent->state.name); |
631 | pBuf = c2pstr(buf); |
632 | width = StringWidth(pBuf); |
633 | |
634 | MoveTo(barBounds.left + (kBarWidth - width) / 2, |
635 | sliderBounds.top - 10); |
636 | |
637 | DrawString(pBuf); |
638 | |
639 | SetRectRgn(ent->outerRgn, 0, 0, 0, 0); |
640 | |
641 | OpenRgn(); |
642 | FrameRect(&barBounds); |
643 | FrameRoundRect(&sliderBounds, 15, 15); |
644 | CloseRgn(ent->outerRgn); |
645 | |
646 | if (ent->selected) |
647 | { |
648 | InvertRect(&barBounds); |
649 | } |
650 | } |
651 | |
652 | void bars_draw() |
653 | { |
654 | list_t *node; |
655 | struct entity *ent; |
656 | |
657 | list_foreach(&gEntities, node) |
658 | { |
659 | ent = container_of(node, struct entity, node); |
660 | |
661 | bar_draw(ent); |
662 | } |
663 | } |
664 | |
665 | void bars_update() |
666 | { |
667 | struct entity *ent; |
668 | list_t *node; |
669 | static uint32_t lastTickCount = 0; |
670 | WindowPeek win = (WindowPeek)gMainWindow; |
671 | short val; |
672 | bool changed = false; |
673 | |
674 | if (lastTickCount + kBarUpdateRate > TickCount()) |
675 | return; |
676 | |
677 | list_foreach(&gEntities, node) |
678 | { |
679 | ent = container_of(node, struct entity, node); |
680 | val = GetControlValue(ent->ctrl); |
681 | ha_get_entity_state(ent->id, &ent->state); |
682 | |
683 | if (ent->state.brightness != val) |
684 | { |
685 | SetControlValue(ent->ctrl, ent->state.brightness); |
686 | changed = true; |
687 | } |
688 | } |
689 | |
690 | if (changed) |
691 | { |
692 | with_port(gMainWindow, { |
693 | InvalRect(&gMainWindow->portRect); |
694 | }) |
695 | } |
696 | |
697 | lastTickCount = TickCount(); |
698 | } |
699 | |
700 | void draw_background(WindowPtr win) |
701 | { |
702 | Rect drawRect; |
703 | short height = rect_height(&win->portRect); |
704 | short width = rect_width(&win->portRect); |
705 | short offset = kBarWidth * gNumBars; |
706 | if (offset > width) |
707 | return; |
708 | |
709 | SetRect(&drawRect, offset, 0, width, height); |
710 | |
711 | FillRect(&drawRect, &gBackPattern); |
712 | FrameRect(&drawRect); |
713 | } |
714 | |
715 | struct entity *entity_for_control(ControlHandle hnd) |
716 | { |
717 | struct entity *ent; |
718 | list_t *node; |
719 | |
720 | list_foreach(&gEntities, node) |
721 | { |
722 | ent = container_of(node, struct entity, node); |
723 | if (ent->ctrl == hnd) |
724 | return ent; |
725 | } |
726 | |
727 | return NULL; |
728 | } |
729 | |
730 | void event_update(WindowPtr win) |
731 | { |
732 | WindowPeek wPeek = (WindowPeek)win; |
733 | |
734 | with_port(win, { |
735 | if (win == gMainWindow) |
736 | { |
737 | bars_draw(); |
738 | draw_background(win); |
739 | UpdateControls(win, win->visRgn); |
740 | } |
741 | }) |
742 | } |
743 | |
744 | void update_entity_positions(short scrollValue) |
745 | { |
746 | struct entity *ent; |
747 | list_t *node; |
748 | Rect curPos; |
749 | short i = 0; |
750 | |
751 | list_foreach(&gEntities, node) |
752 | { |
753 | ent = container_of(node, struct entity, node); |
754 | curPos = (**(ent->ctrl)).contrlRect; |
755 | MoveControl(ent->ctrl, |
756 | (i - scrollValue) * kBarWidth + kCtrlPad, |
757 | curPos.top); |
758 | |
759 | i++; |
760 | } |
761 | } |
762 | |
763 | void |
764 | control_mousedown(WindowPtr win, EventRecord* evt, |
765 | ControlHandle ctrl, short part) |
766 | { |
767 | short orig_val = GetControlValue(ctrl); |
768 | if (part = TrackControl(ctrl, evt->where, nil)) |
769 | { |
770 | long ref = GetControlReference(ctrl); |
771 | switch (ref) |
772 | { |
773 | case CONTROL_SLIDER: |
774 | { |
775 | struct entity *ent = entity_for_control(ctrl); |
776 | short val = GetControlValue(ctrl); |
777 | InvalRect(&win->portRect); |
778 | ent->state.brightness = val; |
779 | ha_set_entity_state(ent->id, &ent->state); |
780 | break; |
781 | } |
782 | case CONTROL_SCROLLBAR: |
783 | { |
784 | short val = orig_val; |
785 | short max = GetControlMaximum(ctrl); |
786 | |
787 | switch (part) |
788 | { |
789 | case inPageUp: // left gray |
790 | case inUpButton: // left |
791 | val = MAX(0, val - 1); |
792 | break; |
793 | case inPageDown: // right gray |
794 | case inDownButton: // right |
795 | val = MIN(max, val + 1); |
796 | break; |
797 | case inThumb: |
798 | // control manager calculates new value for us |
799 | val = GetControlValue(ctrl); |
800 | break; |
801 | } |
802 | SetControlValue(ctrl, val); |
803 | |
804 | if (val != orig_val) |
805 | { |
806 | update_entity_positions(val); |
807 | InvalRect(&win->portRect); |
808 | } |
809 | break; |
810 | } |
811 | } |
812 | } |
813 | } |
814 | |
815 | struct entity* find_entity_for_click(WindowPtr win, Point where) |
816 | { |
817 | struct entity *ent; |
818 | list_t *node; |
819 | |
820 | if (win != gMainWindow) |
821 | return NULL; |
822 | |
823 | list_foreach(&gEntities, node) |
824 | { |
825 | ent = container_of(node, struct entity, node); |
826 | if (PtInRgn(where, ent->outerRgn)) |
827 | return ent; |
828 | } |
829 | |
830 | return NULL; |
831 | } |
832 | |
833 | void |
834 | event_mousedown(EventRecord* evt, WindowPtr win, short inPart) |
835 | { |
836 | WindowPtr frontWin = FrontWindow(); |
837 | ControlHandle ctrl; |
838 | short inCtrlPart; |
839 | long menuChoice; |
840 | |
841 | if (win_is_dialog(frontWin) && win != frontWin) |
842 | { |
843 | SysBeep(20); |
844 | return; |
845 | } |
846 | |
847 | switch (inPart) |
848 | { |
849 | case inGoAway: |
850 | break; |
851 | case inMenuBar: |
852 | menuChoice = MenuSelect(evt->where); |
853 | if (menuChoice > 0) |
854 | menu_click(menuChoice); |
855 | break; |
856 | case inContent: |
857 | if (win_is_dialog(win)) |
858 | { |
859 | struct dialog_info* dinfo = (void*)GetWRefCon(win); |
860 | dinfo->handle_mousedown(evt, win); |
861 | } else { |
862 | struct entity* ent; |
863 | GlobalToLocal(&evt->where); |
864 | inCtrlPart = FindControl(evt->where, win, &ctrl); |
865 | if (inCtrlPart) |
866 | { |
867 | control_mousedown(win, evt, ctrl, inCtrlPart); |
868 | } |
869 | |
870 | if ((ent = find_entity_for_click(win, evt->where))) |
871 | { |
872 | Rect entityBounds; |
873 | ent->selected = !ent->selected; |
874 | get_entity_bounds(ent, &entityBounds); |
875 | InvalRect(&entityBounds); |
876 | } |
877 | } |
878 | break; |
879 | case inDrag: |
880 | DragWindow(win, evt->where, &qd.screenBits.bounds); |
881 | break; |
882 | case inSysWindow: |
883 | SystemClick(evt, win); |
884 | break; |
885 | default: |
886 | break; |
887 | } |
888 | } |
889 | |
890 | void event_activate(bool activate, EventRecord* evt, WindowPtr win) |
891 | { |
892 | short junk; |
893 | if (win_is_dialog(win)) |
894 | { |
895 | struct dialog_info* dinfo = (void*)GetWRefCon(win); |
896 | DialogSelect(evt, &(DialogPtr)win, &junk); |
897 | dinfo->handle_activate(activate, win); |
898 | } |
899 | } |
900 | |
901 | void event_loop() |
902 | { |
903 | EventRecord event; |
904 | WindowPtr win; |
905 | |
906 | short theChar; |
907 | short junk; |
908 | short inPart; |
909 | |
910 | while (gRunning) |
911 | { |
912 | if (WaitNextEvent(everyEvent, &event, 60L, nil)) |
913 | { |
914 | switch(event.what) |
915 | { |
916 | case mouseDown: |
917 | { |
918 | inPart = FindWindow(event.where, &win); |
919 | with_port(win, { |
920 | event_mousedown(&event, win, inPart); |
921 | }) |
922 | break; |
923 | } |
924 | case updateEvt: |
925 | win = (WindowPtr)event.message; |
926 | if (win_is_dialog(win)) |
927 | { |
928 | DialogSelect(&event, &(DialogPtr)win, &junk); |
929 | } else { |
930 | BeginUpdate(win); |
931 | event_update(win); |
932 | EndUpdate(win); |
933 | } |
934 | break; |
935 | case keyDown: |
936 | case autoKey: |
937 | theChar = event.message & charCodeMask; |
938 | if (event.modifiers & cmdKey) |
939 | menu_click(MenuKey(theChar)); |
940 | break; |
941 | case activateEvt: |
942 | { |
943 | bool activate = event.modifiers & 1; |
944 | win = (WindowPtr)event.message; |
945 | event_activate(activate, &event, win); |
946 | break; |
947 | } |
948 | case osEvt: |
949 | if ((event.message >> 24) == suspendResumeMessage) |
950 | { |
951 | bool activate = event.message & 1; |
952 | win = FrontWindow(); |
953 | event_activate(activate, &event, win); |
954 | } |
955 | break; |
956 | default: |
957 | break; |
958 | } |
959 | } else { |
960 | menu_update(); |
961 | bars_update(); |
962 | } |
963 | } |
964 | } |