1
0
Fork 0
topaz-pizza-timer-amigaos-204/main.c

926 lines
23 KiB
C

/**
* Topaz's Pizza Timer
* Built for a future video
* https://theindustriousrabbit.video
*
* Copyright 2022 John Bintz
* MIT License
*/
#include <stdio.h>
// basic amiga stuff
#include <proto/dos.h>
#include <proto/exec.h>
// allow us to load and scale fonts
#include <proto/diskfont.h>
// handle images and sound
#include <proto/datatypes.h>
#include <datatypes/datatypesclass.h>
#include <datatypes/soundclass.h>
#include <proto/timer.h>
// 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
// gadtools gives a proper set of components for building UIs.
// http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0278.html
#include <proto/intuition.h>
#include <proto/gadtools.h>
#include <intuition/gadgetclass.h>
// drawin' stuff
// This is where we get font handling
#include <proto/graphics.h>
// the main app window
#define WINDOW_WIDTH (240)
#define WINDOW_HEIGHT (80)
#define WINDOW_TITLE "Topaz's Pizza Timer"
#define WINDOW_CHROME_WIDTH (4)
struct Screen *screen;
struct Window *window;
struct Gadget *windowGadgets;
#define ABOUT_WINDOW_WIDTH (350)
#define ABOUT_WINDOW_HEIGHT (75)
// 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
void *visualInfo;
struct Gadget *timerGadget, *hourSlider, *minuteSlider, *secondSlider;
// gadtools menus
// https://wiki.amigaos.net/wiki/GadTools_Menus
// Why the special struct, constants and struct instantiation?
// Because this way, when we get a menu message from Intuition,
// we can look up the menu item selected by ID, rather than
// looking it up by position in the menu tree structure.
struct MenuData {
int id;
};
// Start this at 1, otherwise not selecting a menu item is the same as
// opening the menu and picking the item with ID 0
#define MENU_ABOUT_ID 1
#define MENU_QUIT_ID 2
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 timerequest *TimerIO;
struct timeval currentSystemTime;
struct MsgPort *timerPort;
// sound stuff
// get that bell
#define BELL_FILENAME "bell.8svx"
APTR bellSound = NULL;
struct Library *DataTypesBase;
BYTE waitForSoundSignalNumber;
// 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 priorHours, originalUiHours;
unsigned int priorMinutes, originalUiMinutes;
unsigned int priorSeconds, originalUiSeconds;
char timerText[9];
ULONG timerStartTime = 0, timerDistance, timerBuild;
BOOL timerIsRunning = FALSE;
BOOL timerStarted = FALSE;
BOOL terminated = FALSE;
BOOL openAboutWindowNext = FALSE;
/**
* Initialize system stuff.
*/
// 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) {
// 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;
}
// 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;
}
if (NULL == (DataTypesBase = OpenLibrary("datatypes.library", 40))) {
// this is ok, we just won't play sound
printf("no sound!");
} else {
waitForSoundSignalNumber = AllocSignal(-1);
}
return 1;
}
/**
* Tear down system stuff.
*/
void teardown(void) {
// http://amigadev.elowar.com/read/ADCD_2.1/Devices_Manual_guide/node0196.html
if (DataTypesBase) {
CloseLibrary(DataTypesBase);
FreeSignal(waitForSoundSignalNumber);
}
if (TimerIO) {
// if the timer's still running, wait for it to be done
if (!CheckIO((struct IORequest *)TimerIO)) {
WaitIO((struct IORequest *)TimerIO);
}
DeleteExtIO((struct IORequest *)TimerIO);
}
if (timerPort) {
DeletePort(timerPort);
}
// all right, then. keep your secrets.
if (visualInfo) FreeVisualInfo(visualInfo);
if (screen) UnlockPubScreen(NULL, screen);
if (topaz80Font) CloseFont(topaz80Font);
if (topaz160Font) CloseFont(topaz160Font);
}
/**
* 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->BorderLeft;
ng.ng_TopEdge = window->BorderTop;
ng.ng_Height = 12;
ng.ng_Width = 0;
ng.ng_GadgetText = NULL;
ng.ng_TextAttr = &Topaz80;
ng.ng_VisualInfo = visualInfo;
ng.ng_GadgetID = NULL;
ng.ng_Flags = PLACETEXT_IN;
// Timer display
ng.ng_Width = window->Width - window->BorderLeft - window->BorderRight;
ng.ng_TextAttr = &Topaz160;
ng.ng_Height = 20;
timerGadget = currentGadget = CreateGadget(
TEXT_KIND,
currentGadget,
&ng,
GTTX_Text, &timerText,
GTTX_Justification, GTJ_CENTER,
TAG_END
);
ng.ng_TextAttr = &Topaz80;
ng.ng_Height = 12;
// start/stop button
ng.ng_Width = (window->Width - window->BorderLeft - window->BorderRight) / 2;
ng.ng_TopEdge += 18;
if (timerIsRunning) {
ng.ng_GadgetText = "_Stop";
} else {
ng.ng_GadgetText = "_Start";
}
ng.ng_GadgetID = START_STOP_BUTTON_ID;
currentGadget = CreateGadget(
BUTTON_KIND,
currentGadget,
&ng,
GT_Underscore, '_',
TAG_END
);
// reset button
ng.ng_LeftEdge += ng.ng_Width; // lol reuse it
ng.ng_GadgetText = "_Reset";
ng.ng_GadgetID = RESET_BUTTON_ID;
currentGadget = CreateGadget(
BUTTON_KIND,
currentGadget,
&ng,
GT_Underscore, '_',
GA_Disabled, timerIsRunning || !timerStarted,
TAG_END
);
// hours slider
ng.ng_LeftEdge = 85;
ng.ng_Width = window->Width - window->BorderLeft - window->BorderRight - ng.ng_LeftEdge + 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;
hourSlider = currentGadget = CreateGadget(
SLIDER_KIND,
currentGadget,
&ng,
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
);
// minutes slider
ng.ng_TopEdge += 12;
ng.ng_GadgetText = "Mins: ";
ng.ng_GadgetID = MINUTES_SLIDER_ID;
minuteSlider = currentGadget = CreateGadget(
SLIDER_KIND,
currentGadget,
&ng,
GTSL_Min, 0,
GTSL_Max, 59,
GTSL_Level, uiMinutes,
GTSL_MaxLevelLen, 2,
GTSL_LevelFormat, "%2ld",
GA_Disabled, timerIsRunning,
TAG_END
);
// seconds slider
ng.ng_TopEdge += 12;
ng.ng_GadgetText = "Secs: ";
ng.ng_GadgetID = SECONDS_SLIDER_ID;
secondSlider = currentGadget = CreateGadget(
SLIDER_KIND,
currentGadget,
&ng,
GTSL_Min, 0,
GTSL_Max, 59,
GTSL_Level, uiSeconds,
GTSL_MaxLevelLen, 2,
GTSL_LevelFormat, "%2ld",
GA_Disabled, timerIsRunning,
TAG_END
);
return glist;
}
/**
* Set the text of the timer and redraw the necessary gadgets.
*/
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.
*/
int startTimer(void) {
// TODO: handle timer.device not being available
if (0 != (OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest *)TimerIO, 0L))) {
printf("Unable to open timer!");
return 1;
}
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);
CloseDevice((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);
}
/**
* Get the current system time.
*/
int getSysTime(void) {
struct Device* TimerBase;
if (0 != (OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest *)TimerIO, 0L))) {
printf("Unable to open timer!");
return 1;
}
TimerBase = TimerIO->tr_node.io_Device;
GetSysTime(&currentSystemTime);
CloseDevice((struct IORequest *)TimerIO);
return 0;
}
/**
* Toggle the timer between started and stopped.
*/
void handleToggleTimer(void) {
timerIsRunning = !timerIsRunning;
if (timerIsRunning) {
// TODO: handle timer.device not being available
getSysTime();
// 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 timer
startTimer();
} else {
// put the sliders back.
// it causes a lot of UI flashing to update these as the timer runs,
// so only do it once we stop the timer.
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();
}
//
// To keep this simple, the about window will block
int openAboutWindow(void) {
struct Window *aboutWindow;
struct NewGadget ng;
struct Gadget *currentGadget;
struct Gadget *glist;
struct IntuiMessage *iMessage;
ULONG windowSignal;
BOOL closeAbout = FALSE;
// Something is causing a small memory leak when I do this and I don't know
// what it is. CodeWatcher reports a bunch of 40 byte blocks, most likely
// Intuition messages.
//
// nope, false positive, checked with avail before and after running and
// the used/free counts are exactly the same:
// https://eab.abime.net/showthread.php?t=104360
aboutWindow = OpenWindowTags(NULL,
WA_Left, 40, WA_Top, 40,
WA_Width, ABOUT_WINDOW_WIDTH, WA_Height, ABOUT_WINDOW_HEIGHT,
WA_DetailPen, 0, WA_BlockPen, 1,
WA_IDCMP, IDCMP_CLOSEWINDOW,
WA_DragBar, TRUE,
WA_DepthGadget, TRUE,
WA_CloseGadget, TRUE,
WA_Activate, TRUE,
WA_Title, "About Pizza Timer",
TAG_END
);
if (!aboutWindow) {
return 1;
}
currentGadget = CreateContext(&glist);
ng.ng_LeftEdge = WINDOW_CHROME_WIDTH + 4;
ng.ng_TopEdge = 15;
ng.ng_Height = 10;
ng.ng_Width = 0;
ng.ng_GadgetText = NULL;
ng.ng_TextAttr = &Topaz80;
ng.ng_VisualInfo = visualInfo;
ng.ng_GadgetID = NULL;
ng.ng_Flags = PLACETEXT_IN;
currentGadget = CreateGadget(
TEXT_KIND,
currentGadget,
&ng,
GTTX_Text, "Topaz's Pizza Timer",
GTTX_CopyText, TRUE,
TAG_DONE
);
ng.ng_TopEdge += 10;
currentGadget = CreateGadget(
TEXT_KIND,
currentGadget,
&ng,
GTTX_Text, "By John Bintz",
GTTX_CopyText, TRUE,
TAG_DONE
);
ng.ng_TopEdge += 10;
currentGadget = CreateGadget(
TEXT_KIND,
currentGadget,
&ng,
GTTX_Text, "theindustriousrabbit.com",
GTTX_CopyText, TRUE,
TAG_DONE
);
ng.ng_TopEdge += 15;
currentGadget = CreateGadget(
TEXT_KIND,
currentGadget,
&ng,
GTTX_Text, "Sound: Reception bell by cdrk (CC-BY 4.0)",
GTTX_CopyText, TRUE,
TAG_DONE
);
ng.ng_TopEdge += 10;
currentGadget = CreateGadget(
TEXT_KIND,
currentGadget,
&ng,
GTTX_Text, "freesound.org/people/cdrk/sounds/264594/",
GTTX_CopyText, TRUE,
TAG_DONE
);
AddGList(aboutWindow, glist, -1, -1, NULL);
RefreshGList(glist, aboutWindow, NULL, -1);
GT_RefreshWindow(aboutWindow, NULL);
windowSignal = 1L << aboutWindow->UserPort->mp_SigBit;
while (!closeAbout) {
Wait(windowSignal);
while (iMessage = GT_GetIMsg(aboutWindow->UserPort)) {
if (!closeAbout) {
switch (iMessage->Class) {
case IDCMP_CLOSEWINDOW:
closeAbout = TRUE;
break;
case IDCMP_REFRESHWINDOW:
GT_BeginRefresh(window);
GT_EndRefresh(window, TRUE);
break;
}
}
// ensure messages are garbage collected
GT_ReplyIMsg(iMessage);
}
}
RemoveGList(aboutWindow, glist, -1);
FreeGadgets(glist);
CloseWindow(aboutWindow);
return 0;
}
/**
* 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
// This is why there's a bunch more overhead for building
// the menu userdata.
case MENU_QUIT_ID:
terminated = TRUE;
break;
case MENU_ABOUT_ID:
openAboutWindowNext = 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;
case IDCMP_CLOSEWINDOW:
// bye bye
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;
}
}
struct dtTrigger myTrigger;
/**
* Start playing a bell sound, then wait for it to finish.
*/
void startBellSound(void) {
ULONG soundPlayResult;
if (!DataTypesBase) return;
// https://amigaworld.net/modules/newbb/viewtopic.php?forum=15&topic_id=39094&post_id=735120&viewmode=thread&order=0#735120
// TODO: something in NewDTObject is causing a lock to be created that
// CodeWatcher declares was not freed. Commenting this down to just
// the GroupID tag does not make that warning go away. Each time this
// is called, a new lock is created.
//
// CodeWatcher also seems to think this is being kept around in memory,
// but running avail before and after getting the bell to ring indicate
// otherwise.
if (bellSound = NewDTObject(
BELL_FILENAME,
DTA_GroupID, GID_SOUND,
// ourselves
SDTA_SignalTask, (ULONG)FindTask(NULL),
// ideally we should use this, but my headers aren't new enough?
// SDTA_SignalBitNumber, waitForSoundSignalNumber,
// so we'll do it this way
SDTA_SignalBit, 1 << waitForSoundSignalNumber,
TAG_END)) {
myTrigger.MethodID = DTM_TRIGGER;
myTrigger.dtt_GInfo = NULL;
myTrigger.dtt_Function = STM_PLAY;
myTrigger.dtt_Data = NULL;
// https://github.com/khval/AmosKittens/blob/79f00ba3b81805b54fd4e437f667ea2eecab740d/OS/AmigaOS/animation.cpp#L128
soundPlayResult = DoDTMethodA(bellSound, NULL, NULL, (Msg) &myTrigger);
}
}
/**
* Tear down the bell once the timer runs out.
*/
void waitForBellToFinish(void) {
if (bellSound) {
Wait(1 << waitForSoundSignalNumber);
DisposeDTObject(bellSound);
}
}
/**
* Handle the timer running out of time.
*/
void endTimer(void) {
startBellSound();
DisplayBeep(screen);
uiHours = uiMinutes = uiSeconds = 0;
setTimerText();
waitForBellToFinish();
timerIsRunning = FALSE;
timerStarted = FALSE;
clearUI();
renderUI();
}
void handleTimerMessage(void) {
// TODO: handle timer.device not being available
getSysTime();
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 IntuiMessage *iMessage;
struct MsgPort *TimerPort;
ULONG windowSignal, timerSignal, foundSignals;
if (0 == setup()) {
teardown();
return 1;
}
// 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 = OpenWindowTags(NULL,
WA_Left, 20, WA_Top, 20,
WA_Width, WINDOW_WIDTH, WA_Height, WINDOW_HEIGHT,
WA_DetailPen, 0, WA_BlockPen, 1,
WA_IDCMP, IDCMP_REFRESHWINDOW | IDCMP_CLOSEWINDOW | BUTTONIDCMP | SLIDERIDCMP | IDCMP_MENUPICK, // IDCMP flags
WA_SmartRefresh, TRUE,
WA_DragBar, TRUE,
WA_DepthGadget, TRUE,
WA_CloseGadget, TRUE,
WA_Activate, TRUE,
WA_Title, WINDOW_TITLE,
TAG_END
);
if (!window) {
teardown();
return 1;
}
// http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node03F8.html
SetWindowTitles(window, (UBYTE *) ~0, "Topaz's Pizza Timer - theindustriousrabbit.com");
buildMenu();
// these create the bit mask for Wait() to listen to events on
windowSignal = 1L << window->UserPort->mp_SigBit;
timerSignal = 1L << TimerPort->mp_SigBit;
renderUI();
while (!terminated) {
// 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
foundSignals = Wait(windowSignal | timerSignal);
if (foundSignals & windowSignal) {
// drain the window event queue
while (iMessage = GT_GetIMsg(window->UserPort)) {
if (!terminated) {
handleIntuitionMessage(iMessage);
}
// if we don't reply, the event won't be garbage collected.
// we still have to drain any other messages that show up
GT_ReplyIMsg(iMessage);
}
}
if ((foundSignals & timerSignal) && timerIsRunning) {
handleTimerMessage();
}
// open up the about menu outside of event handling
if (openAboutWindowNext) {
if (openAboutWindow()) terminated = TRUE;
openAboutWindowNext = FALSE;
}
}
clearUI();
clearMenu();
CloseWindow(window);
teardown();
return 0;
}