| 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 | } |