From e4a570b247349051454541e3c0d14af8c1688866 Mon Sep 17 00:00:00 2001 From: John Bintz Date: Mon, 3 Oct 2022 20:09:48 -0400 Subject: [PATCH] it's working, now to add the last parts --- main | Bin 10656 -> 7900 bytes main.c | 615 ++++++++++++++++++++++++++++++++++++++++----------------- main.h | 0 3 files changed, 430 insertions(+), 185 deletions(-) create mode 100644 main.h diff --git a/main b/main index df9ca7385441b01e6621ab0524a2c338d6467b6a..bad21ae43adec703dd688c14ffe445caaf2ae16c 100755 GIT binary patch delta 4504 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
  • 5hZjddbMe&hYrHtxSCKri5)+XJ~)ef8^Kb+qmNcAnIm+c+-0 zn{mPA_a$OH8RtYLh?@`kj<0gy9#pM+b`bZV{YVGIh&$px2>gBH__s~qk8Rl88c(*hk6*;D2izKfZ^J)1Qj!EgRO~O8;B8;qM3jxBeu4@$K|z4*!=&Ch*@L zOEBbf^fvs5f&Zw$54~XC{0h>VQrfYuP4%g5$GY0^olZWA)52##Y*2kDHGipuwyKm> z~O{MD#j(%IH}k=mEZ&DG53_B}%{f)FWb7IsYrg z&4^VcK!xz(VhU!BbAk8MW3U|sfP(|j-;|*+!LO$G(OAM{X$i~pJ0-@jr|E24Jr z5CyG>?jxEg)+IE|@de!!!+uC3y&&T3$X^PKup#6F$Uj6rggfnrC=cM?t)hGi=^WCC z@Xymz*$E>rW@n7Ngk8wq>rcRkCuyl+O*rBU2}BIIR^lilp0Q@IE+1hx4?=oJ|NiHO zoy%G*-TVBYV-g@!oRj)}r4)CpxGw1iCj4@NCGcI&>BcW7CT!W6Jv?DLW5q8j$li?a z!t!X&`aM5*X0M1eUO8k$VG$|D-k8N#RKu=D>5>(Z&O&^%Q5;x<;Txz;-~kk|g;Bsi zc1+G%;7lCYpqsD2$$A-74aSRLug*FwIgTtNB(r~e{v>SMU?%F~^b4!W{RWoL@T-pl z?D9Pu$wCJ(@_-{33lVtF#{GajHID79>I2?)grtz4$YMZ0^x8Py>ykl3<3#;oL?Coh z`s5hmL^wUthz?Ky8sn#6u+N0w`j|RX65jumlJWbOS5Z2Fe8Bi##ljtR9qoEYJI==C zwTlQhJ#W#?(Vu$~>@pcvHv9xmrV)Z6K*GcE3mWK*Q7@PUOJ9bB`lM07>_`QfHfRe? zI)XAD(-ovoQTSbBQjc!qjT<&Kr$lc%T}D_Em}MP7gCe8ztuN8T=+!R9RtOtzbXg4P z(k-xh+(&W`K5w91%v2F;-m+nRv#76azzFe^~RrcDDfOSr{TzbC2oDe*@q!CTIWv diff --git a/main.c b/main.c index 042edab..0f7d3ec 100644 --- a/main.c +++ b/main.c @@ -1,24 +1,41 @@ +/** + * Topaz's Pizza Timer + * Built for a future video + * https://theindustriousrabbit.video + * + * Copyright 2022 John Bintz + * MIT License + */ + #include +// basic amiga stuff +#include +#include + +// allow us to load and scale fonts +#include + +// timer stuff. using proto/timer.h did not work well, likely +// because of how you have to open the device. +#include + // this is the manual for the original intuition stuff. // not much here is different: https://ia801905.us.archive.org/33/items/Amiga_Intuition_Reference_Manaual_1985_Adn-Wesley_Publishing_Company/Amiga_Intuition_Reference_Manaual_1985_Addison-Wesley_Publishing_Company.pdf -#include -#include -#include -#include - -#include -#include - // gadtools gives a proper set of components for building UIs. // http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0278.html +#include #include +#include + +// drawin' stuff +// This is where we get font handling #include +// the main app window #define WINDOW_WIDTH (240) -#define WINDOW_HEIGHT (100) -#define WINDOW_TITLE "Topaz Timer" - +#define WINDOW_HEIGHT (80) +#define WINDOW_TITLE "Topaz's Pizza Timer" #define WINDOW_CHROME_WIDTH (4) struct NewWindow winlayout = { @@ -27,7 +44,7 @@ struct NewWindow winlayout = { 0, 1, // detailpen, blockpen, // you have to add the different gadget types you're looking for // http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node0106.html - IDCMP_CLOSEWINDOW | BUTTONIDCMP | SLIDERIDCMP, // IDCMP flags + IDCMP_REFRESHWINDOW | IDCMP_CLOSEWINDOW | BUTTONIDCMP | SLIDERIDCMP | IDCMP_MENUPICK, // IDCMP flags WFLG_SMART_REFRESH | WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_CLOSEGADGET | WFLG_ACTIVATE, // window flag from Window struct NULL, // FirstGadget NULL, // menu checkmark @@ -38,75 +55,130 @@ struct NewWindow winlayout = { WINDOW_WIDTH, WINDOW_HEIGHT, // max size, WBENCHSCREEN // screen where you want the window to open }; +struct Screen *screen; +struct Window *window; +struct Gadget *windowGadgets; +//struct NewWindow aboutWindowLayout = { }; +struct Window *aboutWindow; +struct Gadget *aboutWindowGadgets; + +// Fonts! struct TextAttr Topaz80 = { "topaz.font", 8, 0, 0 }; // There's no native 16 pixel Topaz font so we'll use // diskfont.library to make one. struct TextAttr Topaz160 = { "topaz.font", 16, 0, 0 }; +struct TextFont *topaz80Font; +struct TextFont *topaz160Font; +// Gadtools stuff #define START_STOP_BUTTON_ID 0 #define RESET_BUTTON_ID 1 #define HOURS_SLIDER_ID 2 #define MINUTES_SLIDER_ID 3 #define SECONDS_SLIDER_ID 5 -#define TIMER_COUNTDOWN_COUNT 3 - -struct Window *window; -struct TextFont *topaz80Font; -struct TextFont *topaz160Font; -struct Screen *screen; void *visualInfo; +struct Gadget *timerGadget, *hourSlider, *minuteSlider, *secondSlider; -struct Gadget *timerGadget; +// gadtools menus +// https://wiki.amigaos.net/wiki/GadTools_Menus +struct MenuData { + int id; +}; +#define MENU_ABOUT_ID 0 +#define MENU_QUIT_ID 1 + +struct MenuData MENU_ABOUT = { MENU_ABOUT_ID }; +struct MenuData MENU_QUIT = { MENU_QUIT_ID }; + +struct NewMenu appMenu[] = { + { NM_TITLE, "Pizza Timer", 0, 0, 0, 0, }, + { NM_ITEM, "About...", 0, 0, 0, &MENU_ABOUT }, + { NM_ITEM, "Quit", "Q", 0, 0, &MENU_QUIT }, + { NM_END, NULL, 0, 0, 0, 0, }, +}; + +struct Menu *menu; + +// timer stuff +#define TIMER_INTERVAL 200000 + +struct Device *TimerBase; +struct timerequest *TimerIO; +struct timeval currentSystemTime; + +// our business logic +// for how long should I cook this pizza? unsigned int uiHours = 0; unsigned int uiMinutes = 12; unsigned int uiSeconds = 0; -unsigned int activeHours; -unsigned int activeMinutes; -unsigned int activeSeconds; +unsigned int priorHours, originalUiHours; +unsigned int priorMinutes, originalUiMinutes; +unsigned int priorSeconds, originalUiSeconds; char timerText[9]; - -BOOL timerIsRunning; - -struct Device *TimerBase; -static struct IORequest timereq; +ULONG timerStartTime = 0, timerDistance, timerBuild; +BOOL timerIsRunning = FALSE; +BOOL timerStarted = FALSE; +BOOL terminated = FALSE; /** * Initialize system stuff. */ -int setup() { - // http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node0308.html +// This function, despite taking no parameters, still needs void as a +// parameter. +// http://www.hipooniosamigasite.org/amigadocs/files/LOON-DOCS-DISKS/LOON5/LatticeC.pt3 +int setup(void) { + struct MsgPort *timerPort; + // make sure the font exists on the computer + // http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node0308.html if (NULL == (topaz80Font = OpenFont(&Topaz80))) { return 0; } + // load this via disk so it can be scaled up + // http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node0137.html if (NULL == (topaz160Font = OpenDiskFont(&Topaz160))) { return 0; } + // get a handle on the Workbench screen if (NULL == (screen = LockPubScreen(NULL))) { return 0; } + // Get...visual info. GadTools needs this, and it's all private. + // secrets. secrets between gadtools and the amiga. + // don't snoop. they wouldn't like it. if (NULL == (visualInfo = GetVisualInfo(screen, TAG_END))) { return 0; } - if (0 != (OpenDevice("timer.device", 0, &timereq, 0))) { + // set up the async timer message port + // we create this port, but it ends up in the timerequest structure when + // we're done with the device open, so we don't have to worry about + // hanging onto a pointer to it. + if (NULL == (timerPort = CreatePort(0, 0))) { + return 0; + } + // create an IO object we can get responses to on our timer port + if (NULL == (TimerIO = (struct timerequest *)CreateExtIO(timerPort, sizeof(struct timerequest)))) { + return 0; + } + // OpenDevice returns an error code on failure, 0 on success, like a Unix command + // Give OpenDevice the thing we will use to communicate with the timer device + if (0 != (OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest *)TimerIO, 0L))) { return 0; } - TimerBase = timereq.io_Device; - - activeHours = uiHours; - activeMinutes = uiMinutes; - activeSeconds = uiSeconds; + // this allows us to use GetSysTime, rather than performing an IO operation + // to get the current time. + TimerBase = TimerIO->tr_node.io_Device; return 1; } @@ -114,19 +186,38 @@ int setup() { /** * Tear down system stuff. */ -void teardown() { +void teardown(void) { + // http://amigadev.elowar.com/read/ADCD_2.1/Devices_Manual_guide/node0196.html + struct MsgPort *tempTimerPort; + + if (TimerIO) { + CloseDevice((struct IORequest *)TimerIO); + tempTimerPort = TimerIO->tr_node.io_Message.mn_ReplyPort; + + if (tempTimerPort) { + DeletePort(tempTimerPort); + } + } + // all right, then. keep your secrets. if (visualInfo) FreeVisualInfo(visualInfo); if (screen) UnlockPubScreen(NULL, screen); if (topaz80Font) CloseFont(topaz80Font); if (topaz160Font) CloseFont(topaz160Font); - if (timereq.io_Device) CloseDevice(&timereq); } -struct Gadget *buildUI() { +/** + * Build the GadTools gadgets for this UI. + * Normally I'd move this to a separate piece of code, + * but for example purposes I want everything in this one file + * for easier searching. + */ +struct Gadget *buildUI(void) { struct NewGadget ng; struct Gadget *currentGadget; struct Gadget *glist; + // Add an empty gadget for GadTools to store extra data. + // http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node0274.html currentGadget = CreateContext(&glist); ng.ng_LeftEdge = WINDOW_CHROME_WIDTH; @@ -139,7 +230,6 @@ struct Gadget *buildUI() { ng.ng_TextAttr = &Topaz80; ng.ng_VisualInfo = visualInfo; - // TODO: use constants to indicate which gadget is which ng.ng_GadgetID = NULL; ng.ng_Flags = PLACETEXT_IN; @@ -152,9 +242,8 @@ struct Gadget *buildUI() { TEXT_KIND, currentGadget, &ng, - GTTX_Text, "", + GTTX_Text, &timerText, GTTX_Justification, GTJ_CENTER, - GTTX_Border, TRUE, TAG_END ); @@ -188,26 +277,30 @@ struct Gadget *buildUI() { currentGadget, &ng, GT_Underscore, '_', - GA_Disabled, timerIsRunning, + GA_Disabled, timerIsRunning || !timerStarted, TAG_END ); ng.ng_LeftEdge = 85; ng.ng_Width = (WINDOW_WIDTH - WINDOW_CHROME_WIDTH * 2) - 85 + 4; ng.ng_TopEdge += 12; + // The level is also displayed to the left of the slider by default. + // We need to leave space for its rendering, which is done separately + // from label rendering. ng.ng_GadgetText = "Hours: "; ng.ng_Flags = PLACETEXT_LEFT; ng.ng_GadgetID = HOURS_SLIDER_ID; - currentGadget = CreateGadget( + hourSlider = currentGadget = CreateGadget( SLIDER_KIND, currentGadget, &ng, - GT_Underscore, '_', GTSL_Min, 0, GTSL_Max, 23, GTSL_Level, uiHours, + // You need both MaxLevelLen and LevelFormat for the label to display. GTSL_MaxLevelLen, 2, + // The level is also a long, so use long number formattting. GTSL_LevelFormat, "%2ld", GA_Disabled, timerIsRunning, TAG_END @@ -217,11 +310,10 @@ struct Gadget *buildUI() { ng.ng_GadgetText = "Mins: "; ng.ng_GadgetID = MINUTES_SLIDER_ID; - currentGadget = CreateGadget( + minuteSlider = currentGadget = CreateGadget( SLIDER_KIND, currentGadget, &ng, - GT_Underscore, '_', GTSL_Min, 0, GTSL_Max, 59, GTSL_Level, uiMinutes, @@ -235,11 +327,10 @@ struct Gadget *buildUI() { ng.ng_GadgetText = "Secs: "; ng.ng_GadgetID = SECONDS_SLIDER_ID; - currentGadget = CreateGadget( + secondSlider = currentGadget = CreateGadget( SLIDER_KIND, currentGadget, &ng, - GT_Underscore, '_', GTSL_Min, 0, GTSL_Max, 59, GTSL_Level, uiSeconds, @@ -252,170 +343,324 @@ struct Gadget *buildUI() { return glist; } -void setTimerText() { - // TODO: don't rerender if the time hasn't changed - sprintf(timerText, "%02d:%02d:%02d", activeHours, activeMinutes, activeSeconds); - GT_SetGadgetAttrs( - timerGadget, - window, - NULL, - GTTX_Text, &timerText, - TAG_DONE +void setTimerText(void) { + // Only change the timer widget text if the time is different + // from the last render. This prevents unnecessary renders and potential + // flashes of UI redraw. + if ( + (priorHours != uiHours) || + (priorMinutes != uiMinutes) || + (priorSeconds != uiSeconds) + ) { + // _Technically_ this will change the label's contents all on its own, + // but the newly-rendered label stomps on the old one without clearing + // it out, so we need to force the full label redraw with GT_SetGadgetAttrs. + sprintf(timerText, "%02d:%02d:%02d", uiHours, uiMinutes, uiSeconds); + + GT_SetGadgetAttrs( + timerGadget, + window, + NULL, + GTTX_Text, &timerText, + TAG_DONE + ); + + priorHours = uiHours; + priorMinutes = uiMinutes; + priorSeconds = uiSeconds; + } +} + + +/** + * Ask the timer to send a message to our message port + * after a defined number of milliseconds. + */ +void startTimer(void) { + TimerIO->tr_node.io_Command = TR_ADDREQUEST; + TimerIO->tr_time.tv_secs = 0; + TimerIO->tr_time.tv_micro = TIMER_INTERVAL; + + // SendIO is async, it doesn't wait for the IO to complete before continuing + SendIO((struct IORequest *)TimerIO); +} + +/** + * Render the UI. + */ +void renderUI(void) { + windowGadgets = buildUI(); + + // Use -1 for working with all gadgets. + // You need to add, refresh, and refresh window when adding + // GadTools gadgets. + AddGList(window, windowGadgets, -1, -1, NULL); + RefreshGList(windowGadgets, window, NULL, -1); + setTimerText(); + GT_RefreshWindow(window, NULL); +} + +/** + * Clear the current UI. + */ +void clearUI(void) { + RemoveGList(window, windowGadgets, -1); + FreeGadgets(windowGadgets); +} + +/** + * Toggle the timer between started and stopped. + */ +void handleToggleTimer(void) { + timerIsRunning = !timerIsRunning; + + // TODO: don't reset the timer when it's stopped/started + + if (timerIsRunning) { + // http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node04FA.html + GetSysTime(¤tSystemTime); + + // timers are microsecond resolution + timerStartTime = currentSystemTime.tv_secs * 1000000 + currentSystemTime.tv_micro; + + priorHours = NULL; + priorMinutes = NULL; + priorSeconds = NULL; + + originalUiHours = uiHours; + originalUiMinutes = uiMinutes; + originalUiSeconds = uiSeconds; + + timerStarted = TRUE; + + // start the async timer + startTimer(); + } else { + GT_SetGadgetAttrs( + hourSlider, + window, + NULL, + GTSL_Level, uiHours, + TAG_DONE + ); + + GT_SetGadgetAttrs( + minuteSlider, + window, + NULL, + GTSL_Level, uiMinutes, + TAG_DONE + ); + + GT_SetGadgetAttrs( + secondSlider, + window, + NULL, + GTSL_Level, uiSeconds, + TAG_DONE + ); + } + + clearUI(); + renderUI(); +} + +/** + * Reset the timer. + */ +void handleResetTimer(void) { + uiHours = originalUiHours; + uiMinutes = originalUiMinutes; + uiSeconds = originalUiSeconds; + + timerStarted = FALSE; + + setTimerText(); + clearUI(); + renderUI(); +} + +/** + * Process a single Intuition message. + */ +void handleIntuitionMessage(struct IntuiMessage *iMessage) { + struct Gadget *targetGadget; + struct Menu *targetMenu; + struct MenuData *menuData; + + BOOL rerenderTimer = FALSE; + + switch (iMessage->Class) { + // We've released a button. + case IDCMP_GADGETUP: + targetGadget = (struct Gadget *)iMessage->IAddress; + + switch (targetGadget->GadgetID) { + case START_STOP_BUTTON_ID: + handleToggleTimer(); + + break; + case RESET_BUTTON_ID: + handleResetTimer(); + rerenderTimer = TRUE; + + break; + } + + break; + // We picked a menu item. + case IDCMP_MENUPICK: + // https://en.wikibooks.org/wiki/Aros/Developer/Docs/Libraries/GadTools + targetMenu = (struct Menu *)ItemAddress(menu, iMessage->Code); + + menuData = GTMENUITEM_USERDATA(targetMenu); + + switch (menuData->id) { + // I originally tried to use MENU_QUIT.id here + // https://stackoverflow.com/questions/14069737/switch-case-error-case-label-does-not-reduce-to-an-integer-constant + case MENU_QUIT_ID: + terminated = TRUE; + break; + } + break; + // We've moved the mouse. In slider gadget talk, we've changed + // the value of the slider. + case IDCMP_MOUSEMOVE: + targetGadget = (struct Gadget *)iMessage->IAddress; + + switch (targetGadget->GadgetID) { + case HOURS_SLIDER_ID: + uiHours = iMessage->Code; + rerenderTimer = TRUE; + break; + case MINUTES_SLIDER_ID: + uiMinutes = iMessage->Code; + rerenderTimer = TRUE; + break; + case SECONDS_SLIDER_ID: + uiSeconds = iMessage->Code; + rerenderTimer = TRUE; + break; + } + + if (rerenderTimer) setTimerText(); + break; + + // bye bye + case IDCMP_CLOSEWINDOW: + terminated = TRUE; + break; + + // this is required if your window is refreshable and you're using gadtools. + // http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node026F.html + case IDCMP_REFRESHWINDOW: + GT_BeginRefresh(window); + GT_EndRefresh(window, TRUE); + break; + } +} + +void endTimer(void) { + // TODO: play an included IFF 8SVX sound + DisplayBeep(screen); + + uiHours = uiMinutes = uiSeconds = 0; + setTimerText(); + + timerIsRunning = FALSE; + timerStarted = FALSE; + + clearUI(); + renderUI(); +} + +void handleTimerMessage(void) { + GetSysTime(¤tSystemTime); + + timerDistance = ((currentSystemTime.tv_secs * 1000000 + currentSystemTime.tv_micro) - timerStartTime) / 1000000; + timerBuild = (originalUiHours * 3600 + originalUiMinutes * 60 + originalUiSeconds) - timerDistance; + + if (timerBuild <= 0) { + endTimer(); + } else { + uiHours = timerBuild / 3600; + uiMinutes = timerBuild / 60 % 60; + uiSeconds = timerBuild % 60; + setTimerText(); + + // this is like a setTimeout loop in JavaScript. keep re-running + // the same timer until we don't need it anymore. + startTimer(); + } +} + +// https://wiki.amigaos.net/wiki/GadTools_Menus +void buildMenu(void) { + menu = CreateMenus( + appMenu, TAG_END ); + + LayoutMenus(menu, visualInfo, TAG_END); + SetMenuStrip(window, menu); +} + +void clearMenu(void) { + ClearMenuStrip(window); + FreeMenus(menu); } int main() { - struct Gadget *gad; - - BOOL terminated = FALSE; - BOOL rerenderTimer = FALSE; - + // TODO: Menu bar with about menu and change the title to match the time struct IntuiMessage *iMessage; - struct Gadget *targetGadget; - struct timeval currentSystemTime; - - unsigned int timerRerenderCountdown = 0; - - // timeval tv_secs is ULONG and that will let us have accurate - // time counting via timer.device. - ULONG timerStartTime, timerDistance, timerBuild; - - timerIsRunning = FALSE; + struct MsgPort *TimerPort; + ULONG windowSignal, timerSignal, foundSignals; if (0 == setup()) { teardown(); return 1; } - GetSysTime(¤tSystemTime); - timerStartTime = currentSystemTime.tv_secs; + // get our timer message port so we can await on timer events + TimerPort = TimerIO->tr_node.io_Message.mn_ReplyPort; + // business logic setup + timerIsRunning = FALSE; + + // open the empty window window = OpenWindow(&winlayout); + if (!window) { + teardown(); + return 1; + } - gad = buildUI(); + // these create the bit mask for Wait() to listen to events on + windowSignal = 1L << window->UserPort->mp_SigBit; + timerSignal = 1L << TimerPort->mp_SigBit; - // use -1 for working with all gadgets - AddGList(window, gad, -1, -1, NULL); - RefreshGList(gad, window, NULL, -1); - GT_RefreshWindow(window, NULL); + renderUI(); - setTimerText(); + buildMenu(); - // what the fuck, you have to set the labels afterwards? - // you know what, it's better if it's explicit rather than the - // pointer shit it was trying to do before. - //GT_SetGadgetAttrs(one, window, NULL, GTTX_Text, "wow", TAG_DONE); - - // after doing anything with gadgets, you need to refresh them - //RefreshGList(gad, window, NULL, 3); - - // you son of a bitch, this is what you need - printf("sig signal %d\n", window->UserPort->mp_SigBit); while (!terminated) { - // what is a userport on a window // http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node02EB.html // http://www.amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node038A.html - //WaitPort(window->UserPort); - // but we need a timer + foundSignals = Wait(windowSignal | timerSignal); - // this shoud be responsive enoug - Delay(4); - - if (timerIsRunning) { - timerRerenderCountdown += 1; - if (timerRerenderCountdown > TIMER_COUNTDOWN_COUNT) { - timerRerenderCountdown = 0; - - GetSysTime(¤tSystemTime); - - timerDistance = currentSystemTime.tv_secs - timerStartTime; - - timerBuild = (uiHours * 3600 + uiMinutes * 60 + uiSeconds) - timerDistance; - - activeHours = timerBuild / 3600; - activeMinutes = timerBuild / 60 % 60; - activeSeconds = timerBuild % 60; - - setTimerText(); + if (foundSignals & windowSignal) { + // drain the window event queue + while ((!terminated) && (iMessage = GT_GetIMsg(window->UserPort))) { + handleIntuitionMessage(iMessage); } } - while ((!terminated) && (iMessage = GT_GetIMsg(window->UserPort))) { - switch (iMessage->Class) { - case IDCMP_GADGETUP: - targetGadget = (struct Gadget *)iMessage->IAddress; - - switch (targetGadget->GadgetID) { - case START_STOP_BUTTON_ID: - timerIsRunning = !timerIsRunning; - - if (timerIsRunning) { - GetSysTime(¤tSystemTime); - - timerStartTime = currentSystemTime.tv_secs; - timerRerenderCountdown = 0; - rerenderTimer = TRUE; - } - - RemoveGList(window, gad, -1); - FreeGadgets(gad); - - gad = buildUI(); - AddGList(window, gad, -1, -1, NULL); - RefreshGList(gad, window, NULL, -1); - GT_RefreshWindow(window, NULL); - - break; - case RESET_BUTTON_ID: - rerenderTimer = TRUE; - activeHours = uiHours; - activeMinutes = uiMinutes; - activeSeconds = uiSeconds; - break; - } - - break; - case IDCMP_MOUSEMOVE: - targetGadget = (struct Gadget *)iMessage->IAddress; - - switch (targetGadget->GadgetID) { - case HOURS_SLIDER_ID: - activeHours = uiHours = iMessage->Code; - rerenderTimer = TRUE; - break; - case MINUTES_SLIDER_ID: - activeMinutes = uiMinutes = iMessage->Code; - rerenderTimer = TRUE; - break; - case SECONDS_SLIDER_ID: - activeSeconds = uiSeconds = iMessage->Code; - rerenderTimer = TRUE; - break; - } - break; - - case IDCMP_CLOSEWINDOW: - terminated = TRUE; - break; - case IDCMP_REFRESHWINDOW: - GT_BeginRefresh(window); - GT_EndRefresh(window, TRUE); - break; - } - - if (rerenderTimer) { - setTimerText(); - rerenderTimer = FALSE; - } + if ((foundSignals & timerSignal) && timerIsRunning) { + handleTimerMessage(); } } - RemoveGList(window, gad, -1); - FreeGadgets(gad); - - if (window) { - CloseWindow(window); - } + clearUI(); + clearMenu(); + CloseWindow(window); teardown(); diff --git a/main.h b/main.h new file mode 100644 index 0000000..e69de29