it's working, now to add the last parts
This commit is contained in:
parent
3c6cc29c52
commit
e4a570b247
603
main.c
603
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 <stdio.h>
|
#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>
|
||||||
|
|
||||||
|
// timer stuff. using proto/timer.h did not work well, likely
|
||||||
|
// because of how you have to open the device.
|
||||||
|
#include <clib/timer_protos.h>
|
||||||
|
|
||||||
// this is the manual for the original intuition stuff.
|
// 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
|
// 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 <proto/intuition.h>
|
|
||||||
#include <proto/dos.h>
|
|
||||||
#include <proto/exec.h>
|
|
||||||
#include <proto/diskfont.h>
|
|
||||||
|
|
||||||
#include <clib/timer_protos.h>
|
|
||||||
#include <intuition/gadgetclass.h>
|
|
||||||
|
|
||||||
// gadtools gives a proper set of components for building UIs.
|
// gadtools gives a proper set of components for building UIs.
|
||||||
// http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0278.html
|
// http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0278.html
|
||||||
|
#include <proto/intuition.h>
|
||||||
#include <proto/gadtools.h>
|
#include <proto/gadtools.h>
|
||||||
|
#include <intuition/gadgetclass.h>
|
||||||
|
|
||||||
|
// drawin' stuff
|
||||||
|
// This is where we get font handling
|
||||||
#include <proto/graphics.h>
|
#include <proto/graphics.h>
|
||||||
|
|
||||||
|
// the main app window
|
||||||
#define WINDOW_WIDTH (240)
|
#define WINDOW_WIDTH (240)
|
||||||
#define WINDOW_HEIGHT (100)
|
#define WINDOW_HEIGHT (80)
|
||||||
#define WINDOW_TITLE "Topaz Timer"
|
#define WINDOW_TITLE "Topaz's Pizza Timer"
|
||||||
|
|
||||||
#define WINDOW_CHROME_WIDTH (4)
|
#define WINDOW_CHROME_WIDTH (4)
|
||||||
|
|
||||||
struct NewWindow winlayout = {
|
struct NewWindow winlayout = {
|
||||||
|
@ -27,7 +44,7 @@ struct NewWindow winlayout = {
|
||||||
0, 1, // detailpen, blockpen,
|
0, 1, // detailpen, blockpen,
|
||||||
// you have to add the different gadget types you're looking for
|
// 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
|
// 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
|
WFLG_SMART_REFRESH | WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_CLOSEGADGET | WFLG_ACTIVATE, // window flag from Window struct
|
||||||
NULL, // FirstGadget
|
NULL, // FirstGadget
|
||||||
NULL, // menu checkmark
|
NULL, // menu checkmark
|
||||||
|
@ -38,75 +55,130 @@ struct NewWindow winlayout = {
|
||||||
WINDOW_WIDTH, WINDOW_HEIGHT, // max size,
|
WINDOW_WIDTH, WINDOW_HEIGHT, // max size,
|
||||||
WBENCHSCREEN // screen where you want the window to open
|
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 };
|
struct TextAttr Topaz80 = { "topaz.font", 8, 0, 0 };
|
||||||
|
|
||||||
// There's no native 16 pixel Topaz font so we'll use
|
// There's no native 16 pixel Topaz font so we'll use
|
||||||
// diskfont.library to make one.
|
// diskfont.library to make one.
|
||||||
struct TextAttr Topaz160 = { "topaz.font", 16, 0, 0 };
|
struct TextAttr Topaz160 = { "topaz.font", 16, 0, 0 };
|
||||||
|
struct TextFont *topaz80Font;
|
||||||
|
struct TextFont *topaz160Font;
|
||||||
|
|
||||||
|
// Gadtools stuff
|
||||||
#define START_STOP_BUTTON_ID 0
|
#define START_STOP_BUTTON_ID 0
|
||||||
#define RESET_BUTTON_ID 1
|
#define RESET_BUTTON_ID 1
|
||||||
#define HOURS_SLIDER_ID 2
|
#define HOURS_SLIDER_ID 2
|
||||||
#define MINUTES_SLIDER_ID 3
|
#define MINUTES_SLIDER_ID 3
|
||||||
#define SECONDS_SLIDER_ID 5
|
#define SECONDS_SLIDER_ID 5
|
||||||
|
|
||||||
#define TIMER_COUNTDOWN_COUNT 3
|
|
||||||
|
|
||||||
struct Window *window;
|
|
||||||
struct TextFont *topaz80Font;
|
|
||||||
struct TextFont *topaz160Font;
|
|
||||||
struct Screen *screen;
|
|
||||||
void *visualInfo;
|
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 uiHours = 0;
|
||||||
unsigned int uiMinutes = 12;
|
unsigned int uiMinutes = 12;
|
||||||
unsigned int uiSeconds = 0;
|
unsigned int uiSeconds = 0;
|
||||||
|
|
||||||
unsigned int activeHours;
|
unsigned int priorHours, originalUiHours;
|
||||||
unsigned int activeMinutes;
|
unsigned int priorMinutes, originalUiMinutes;
|
||||||
unsigned int activeSeconds;
|
unsigned int priorSeconds, originalUiSeconds;
|
||||||
|
|
||||||
char timerText[9];
|
char timerText[9];
|
||||||
|
ULONG timerStartTime = 0, timerDistance, timerBuild;
|
||||||
BOOL timerIsRunning;
|
BOOL timerIsRunning = FALSE;
|
||||||
|
BOOL timerStarted = FALSE;
|
||||||
struct Device *TimerBase;
|
BOOL terminated = FALSE;
|
||||||
static struct IORequest timereq;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize system stuff.
|
* Initialize system stuff.
|
||||||
*/
|
*/
|
||||||
int setup() {
|
// This function, despite taking no parameters, still needs void as a
|
||||||
// http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node0308.html
|
// 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
|
// 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))) {
|
if (NULL == (topaz80Font = OpenFont(&Topaz80))) {
|
||||||
return 0;
|
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))) {
|
if (NULL == (topaz160Font = OpenDiskFont(&Topaz160))) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get a handle on the Workbench screen
|
||||||
if (NULL == (screen = LockPubScreen(NULL))) {
|
if (NULL == (screen = LockPubScreen(NULL))) {
|
||||||
return 0;
|
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))) {
|
if (NULL == (visualInfo = GetVisualInfo(screen, TAG_END))) {
|
||||||
return 0;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimerBase = timereq.io_Device;
|
// this allows us to use GetSysTime, rather than performing an IO operation
|
||||||
|
// to get the current time.
|
||||||
activeHours = uiHours;
|
TimerBase = TimerIO->tr_node.io_Device;
|
||||||
activeMinutes = uiMinutes;
|
|
||||||
activeSeconds = uiSeconds;
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -114,19 +186,38 @@ int setup() {
|
||||||
/**
|
/**
|
||||||
* Tear down system stuff.
|
* 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 (visualInfo) FreeVisualInfo(visualInfo);
|
||||||
if (screen) UnlockPubScreen(NULL, screen);
|
if (screen) UnlockPubScreen(NULL, screen);
|
||||||
if (topaz80Font) CloseFont(topaz80Font);
|
if (topaz80Font) CloseFont(topaz80Font);
|
||||||
if (topaz160Font) CloseFont(topaz160Font);
|
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 NewGadget ng;
|
||||||
struct Gadget *currentGadget;
|
struct Gadget *currentGadget;
|
||||||
struct Gadget *glist;
|
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);
|
currentGadget = CreateContext(&glist);
|
||||||
|
|
||||||
ng.ng_LeftEdge = WINDOW_CHROME_WIDTH;
|
ng.ng_LeftEdge = WINDOW_CHROME_WIDTH;
|
||||||
|
@ -139,7 +230,6 @@ struct Gadget *buildUI() {
|
||||||
ng.ng_TextAttr = &Topaz80;
|
ng.ng_TextAttr = &Topaz80;
|
||||||
ng.ng_VisualInfo = visualInfo;
|
ng.ng_VisualInfo = visualInfo;
|
||||||
|
|
||||||
// TODO: use constants to indicate which gadget is which
|
|
||||||
ng.ng_GadgetID = NULL;
|
ng.ng_GadgetID = NULL;
|
||||||
ng.ng_Flags = PLACETEXT_IN;
|
ng.ng_Flags = PLACETEXT_IN;
|
||||||
|
|
||||||
|
@ -152,9 +242,8 @@ struct Gadget *buildUI() {
|
||||||
TEXT_KIND,
|
TEXT_KIND,
|
||||||
currentGadget,
|
currentGadget,
|
||||||
&ng,
|
&ng,
|
||||||
GTTX_Text, "",
|
GTTX_Text, &timerText,
|
||||||
GTTX_Justification, GTJ_CENTER,
|
GTTX_Justification, GTJ_CENTER,
|
||||||
GTTX_Border, TRUE,
|
|
||||||
TAG_END
|
TAG_END
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -188,26 +277,30 @@ struct Gadget *buildUI() {
|
||||||
currentGadget,
|
currentGadget,
|
||||||
&ng,
|
&ng,
|
||||||
GT_Underscore, '_',
|
GT_Underscore, '_',
|
||||||
GA_Disabled, timerIsRunning,
|
GA_Disabled, timerIsRunning || !timerStarted,
|
||||||
TAG_END
|
TAG_END
|
||||||
);
|
);
|
||||||
|
|
||||||
ng.ng_LeftEdge = 85;
|
ng.ng_LeftEdge = 85;
|
||||||
ng.ng_Width = (WINDOW_WIDTH - WINDOW_CHROME_WIDTH * 2) - 85 + 4;
|
ng.ng_Width = (WINDOW_WIDTH - WINDOW_CHROME_WIDTH * 2) - 85 + 4;
|
||||||
ng.ng_TopEdge += 12;
|
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_GadgetText = "Hours: ";
|
||||||
ng.ng_Flags = PLACETEXT_LEFT;
|
ng.ng_Flags = PLACETEXT_LEFT;
|
||||||
ng.ng_GadgetID = HOURS_SLIDER_ID;
|
ng.ng_GadgetID = HOURS_SLIDER_ID;
|
||||||
|
|
||||||
currentGadget = CreateGadget(
|
hourSlider = currentGadget = CreateGadget(
|
||||||
SLIDER_KIND,
|
SLIDER_KIND,
|
||||||
currentGadget,
|
currentGadget,
|
||||||
&ng,
|
&ng,
|
||||||
GT_Underscore, '_',
|
|
||||||
GTSL_Min, 0,
|
GTSL_Min, 0,
|
||||||
GTSL_Max, 23,
|
GTSL_Max, 23,
|
||||||
GTSL_Level, uiHours,
|
GTSL_Level, uiHours,
|
||||||
|
// You need both MaxLevelLen and LevelFormat for the label to display.
|
||||||
GTSL_MaxLevelLen, 2,
|
GTSL_MaxLevelLen, 2,
|
||||||
|
// The level is also a long, so use long number formattting.
|
||||||
GTSL_LevelFormat, "%2ld",
|
GTSL_LevelFormat, "%2ld",
|
||||||
GA_Disabled, timerIsRunning,
|
GA_Disabled, timerIsRunning,
|
||||||
TAG_END
|
TAG_END
|
||||||
|
@ -217,11 +310,10 @@ struct Gadget *buildUI() {
|
||||||
ng.ng_GadgetText = "Mins: ";
|
ng.ng_GadgetText = "Mins: ";
|
||||||
ng.ng_GadgetID = MINUTES_SLIDER_ID;
|
ng.ng_GadgetID = MINUTES_SLIDER_ID;
|
||||||
|
|
||||||
currentGadget = CreateGadget(
|
minuteSlider = currentGadget = CreateGadget(
|
||||||
SLIDER_KIND,
|
SLIDER_KIND,
|
||||||
currentGadget,
|
currentGadget,
|
||||||
&ng,
|
&ng,
|
||||||
GT_Underscore, '_',
|
|
||||||
GTSL_Min, 0,
|
GTSL_Min, 0,
|
||||||
GTSL_Max, 59,
|
GTSL_Max, 59,
|
||||||
GTSL_Level, uiMinutes,
|
GTSL_Level, uiMinutes,
|
||||||
|
@ -235,11 +327,10 @@ struct Gadget *buildUI() {
|
||||||
ng.ng_GadgetText = "Secs: ";
|
ng.ng_GadgetText = "Secs: ";
|
||||||
ng.ng_GadgetID = SECONDS_SLIDER_ID;
|
ng.ng_GadgetID = SECONDS_SLIDER_ID;
|
||||||
|
|
||||||
currentGadget = CreateGadget(
|
secondSlider = currentGadget = CreateGadget(
|
||||||
SLIDER_KIND,
|
SLIDER_KIND,
|
||||||
currentGadget,
|
currentGadget,
|
||||||
&ng,
|
&ng,
|
||||||
GT_Underscore, '_',
|
|
||||||
GTSL_Min, 0,
|
GTSL_Min, 0,
|
||||||
GTSL_Max, 59,
|
GTSL_Max, 59,
|
||||||
GTSL_Level, uiSeconds,
|
GTSL_Level, uiSeconds,
|
||||||
|
@ -252,9 +343,20 @@ struct Gadget *buildUI() {
|
||||||
return glist;
|
return glist;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTimerText() {
|
void setTimerText(void) {
|
||||||
// TODO: don't rerender if the time hasn't changed
|
// Only change the timer widget text if the time is different
|
||||||
sprintf(timerText, "%02d:%02d:%02d", activeHours, activeMinutes, activeSeconds);
|
// 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(
|
GT_SetGadgetAttrs(
|
||||||
timerGadget,
|
timerGadget,
|
||||||
window,
|
window,
|
||||||
|
@ -262,160 +364,303 @@ void setTimerText() {
|
||||||
GTTX_Text, &timerText,
|
GTTX_Text, &timerText,
|
||||||
TAG_DONE
|
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() {
|
int main() {
|
||||||
struct Gadget *gad;
|
// TODO: Menu bar with about menu and change the title to match the time
|
||||||
|
|
||||||
BOOL terminated = FALSE;
|
|
||||||
BOOL rerenderTimer = FALSE;
|
|
||||||
|
|
||||||
struct IntuiMessage *iMessage;
|
struct IntuiMessage *iMessage;
|
||||||
struct Gadget *targetGadget;
|
struct MsgPort *TimerPort;
|
||||||
struct timeval currentSystemTime;
|
ULONG windowSignal, timerSignal, foundSignals;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (0 == setup()) {
|
if (0 == setup()) {
|
||||||
teardown();
|
teardown();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
GetSysTime(¤tSystemTime);
|
// get our timer message port so we can await on timer events
|
||||||
timerStartTime = currentSystemTime.tv_secs;
|
TimerPort = TimerIO->tr_node.io_Message.mn_ReplyPort;
|
||||||
|
|
||||||
|
// business logic setup
|
||||||
|
timerIsRunning = FALSE;
|
||||||
|
|
||||||
|
// open the empty window
|
||||||
window = OpenWindow(&winlayout);
|
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
|
renderUI();
|
||||||
AddGList(window, gad, -1, -1, NULL);
|
|
||||||
RefreshGList(gad, window, NULL, -1);
|
|
||||||
GT_RefreshWindow(window, NULL);
|
|
||||||
|
|
||||||
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) {
|
while (!terminated) {
|
||||||
// what is a userport on a window
|
|
||||||
// http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node02EB.html
|
// 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
|
// http://www.amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node038A.html
|
||||||
//WaitPort(window->UserPort);
|
foundSignals = Wait(windowSignal | timerSignal);
|
||||||
// but we need a timer
|
|
||||||
|
|
||||||
// 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))) {
|
while ((!terminated) && (iMessage = GT_GetIMsg(window->UserPort))) {
|
||||||
switch (iMessage->Class) {
|
handleIntuitionMessage(iMessage);
|
||||||
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);
|
clearUI();
|
||||||
FreeGadgets(gad);
|
clearMenu();
|
||||||
|
|
||||||
if (window) {
|
|
||||||
CloseWindow(window);
|
CloseWindow(window);
|
||||||
}
|
|
||||||
|
|
||||||
teardown();
|
teardown();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue