2022-10-04 00:09:48 +00:00
/**
* Topaz ' s Pizza Timer
* Built for a future video
* https : //theindustriousrabbit.video
*
* Copyright 2022 John Bintz
* MIT License
*/
2022-10-02 20:59:06 +00:00
# include <stdio.h>
2022-10-04 00:09:48 +00:00
// basic amiga stuff
2022-10-02 20:59:06 +00:00
# include <proto/dos.h>
# include <proto/exec.h>
2022-10-04 00:09:48 +00:00
// allow us to load and scale fonts
2022-10-02 20:59:06 +00:00
# include <proto/diskfont.h>
2022-10-04 01:14:18 +00:00
// handle images and sound
2022-11-25 14:22:57 +00:00
# include <proto/datatypes.h>
2022-10-04 01:14:18 +00:00
# include <datatypes/datatypesclass.h>
2022-10-10 18:54:54 +00:00
# include <datatypes/soundclass.h>
2022-10-04 01:14:18 +00:00
2022-11-25 14:22:57 +00:00
# include <proto/timer.h>
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
// 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
2022-10-02 20:59:06 +00:00
// gadtools gives a proper set of components for building UIs.
// http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0278.html
2022-10-04 00:09:48 +00:00
# include <proto/intuition.h>
2022-10-02 20:59:06 +00:00
# include <proto/gadtools.h>
2022-10-04 00:09:48 +00:00
# include <intuition/gadgetclass.h>
// drawin' stuff
// This is where we get font handling
2022-10-02 20:59:06 +00:00
# include <proto/graphics.h>
2022-10-04 00:09:48 +00:00
// the main app window
2022-10-02 20:59:06 +00:00
# define WINDOW_WIDTH (240)
2022-10-04 00:09:48 +00:00
# define WINDOW_HEIGHT (80)
# define WINDOW_TITLE "Topaz's Pizza Timer"
2022-10-02 20:59:06 +00:00
# define WINDOW_CHROME_WIDTH (4)
2022-10-04 00:09:48 +00:00
struct Screen * screen ;
struct Window * window ;
struct Gadget * windowGadgets ;
2022-10-02 20:59:06 +00:00
2022-10-07 11:35:49 +00:00
# define ABOUT_WINDOW_WIDTH (350)
# define ABOUT_WINDOW_HEIGHT (75)
2022-10-04 12:08:15 +00:00
2022-10-04 00:09:48 +00:00
// Fonts!
2022-10-02 20:59:06 +00:00
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 } ;
2022-10-04 00:09:48 +00:00
struct TextFont * topaz80Font ;
struct TextFont * topaz160Font ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
// Gadtools stuff
2022-10-02 20:59:06 +00:00
# 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 ;
2022-10-04 00:09:48 +00:00
struct Gadget * timerGadget , * hourSlider , * minuteSlider , * secondSlider ;
// gadtools menus
// https://wiki.amigaos.net/wiki/GadTools_Menus
2022-10-04 12:08:15 +00:00
// 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.
2022-10-04 00:09:48 +00:00
struct MenuData {
int id ;
} ;
2022-11-25 14:22:57 +00:00
// 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
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
struct MenuData MENU_ABOUT = { MENU_ABOUT_ID } ;
struct MenuData MENU_QUIT = { MENU_QUIT_ID } ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
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 ;
2022-10-05 21:59:05 +00:00
struct MsgPort * timerPort ;
2022-10-04 00:09:48 +00:00
2022-10-04 01:14:18 +00:00
// sound stuff
// get that bell
# define BELL_FILENAME "bell.8svx"
2022-10-04 12:08:15 +00:00
APTR bellSound = NULL ;
2022-10-10 18:54:54 +00:00
struct Library * DataTypesBase ;
2022-11-05 14:52:01 +00:00
BYTE waitForSoundSignalNumber ;
2022-10-04 12:08:15 +00:00
2022-10-04 00:09:48 +00:00
// our business logic
// for how long should I cook this pizza?
2022-10-02 20:59:06 +00:00
unsigned int uiHours = 0 ;
unsigned int uiMinutes = 12 ;
unsigned int uiSeconds = 0 ;
2022-10-04 00:09:48 +00:00
unsigned int priorHours , originalUiHours ;
unsigned int priorMinutes , originalUiMinutes ;
unsigned int priorSeconds , originalUiSeconds ;
2022-10-02 20:59:06 +00:00
char timerText [ 9 ] ;
2022-10-04 00:09:48 +00:00
ULONG timerStartTime = 0 , timerDistance , timerBuild ;
BOOL timerIsRunning = FALSE ;
BOOL timerStarted = FALSE ;
BOOL terminated = FALSE ;
2022-10-05 21:59:05 +00:00
BOOL openAboutWindowNext = FALSE ;
2022-10-02 20:59:06 +00:00
/**
* Initialize system stuff .
*/
2022-10-04 00:09:48 +00:00
// 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 ) {
2022-10-02 20:59:06 +00:00
// make sure the font exists on the computer
2022-10-04 00:09:48 +00:00
// http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node0308.html
2022-10-02 20:59:06 +00:00
if ( NULL = = ( topaz80Font = OpenFont ( & Topaz80 ) ) ) {
return 0 ;
}
2022-10-04 00:09:48 +00:00
// 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
2022-10-02 20:59:06 +00:00
if ( NULL = = ( topaz160Font = OpenDiskFont ( & Topaz160 ) ) ) {
return 0 ;
}
2022-10-04 00:09:48 +00:00
// get a handle on the Workbench screen
2022-10-02 20:59:06 +00:00
if ( NULL = = ( screen = LockPubScreen ( NULL ) ) ) {
return 0 ;
}
2022-10-04 00:09:48 +00:00
// 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.
2022-10-02 20:59:06 +00:00
if ( NULL = = ( visualInfo = GetVisualInfo ( screen , TAG_END ) ) ) {
return 0 ;
}
2022-10-04 00:09:48 +00:00
// 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 ;
}
2022-10-02 20:59:06 +00:00
2022-10-10 18:54:54 +00:00
if ( NULL = = ( DataTypesBase = OpenLibrary ( " datatypes.library " , 40 ) ) ) {
// this is ok, we just won't play sound
printf ( " no sound! " ) ;
2022-11-05 14:52:01 +00:00
} else {
waitForSoundSignalNumber = AllocSignal ( - 1 ) ;
2022-10-10 18:54:54 +00:00
}
2022-10-02 20:59:06 +00:00
return 1 ;
}
/**
* Tear down system stuff .
*/
2022-10-04 00:09:48 +00:00
void teardown ( void ) {
// http://amigadev.elowar.com/read/ADCD_2.1/Devices_Manual_guide/node0196.html
2022-10-10 18:54:54 +00:00
if ( DataTypesBase ) {
CloseLibrary ( DataTypesBase ) ;
2022-11-05 14:52:01 +00:00
FreeSignal ( waitForSoundSignalNumber ) ;
2022-10-10 18:54:54 +00:00
}
2022-10-04 00:09:48 +00:00
if ( TimerIO ) {
2022-11-25 19:33:31 +00:00
// if the timer's still running, wait for it to be done
if ( ! CheckIO ( ( struct IORequest * ) TimerIO ) ) {
WaitIO ( ( struct IORequest * ) TimerIO ) ;
}
2022-11-25 19:00:23 +00:00
2022-10-05 21:59:05 +00:00
DeleteExtIO ( ( struct IORequest * ) TimerIO ) ;
}
if ( timerPort ) {
DeletePort ( timerPort ) ;
2022-10-04 00:09:48 +00:00
}
// all right, then. keep your secrets.
2022-10-02 20:59:06 +00:00
if ( visualInfo ) FreeVisualInfo ( visualInfo ) ;
if ( screen ) UnlockPubScreen ( NULL , screen ) ;
if ( topaz80Font ) CloseFont ( topaz80Font ) ;
if ( topaz160Font ) CloseFont ( topaz160Font ) ;
}
2022-10-04 00:09:48 +00:00
/**
* 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 ) {
2022-10-02 20:59:06 +00:00
struct NewGadget ng ;
struct Gadget * currentGadget ;
struct Gadget * glist ;
2022-10-04 00:09:48 +00:00
// 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
2022-10-02 20:59:06 +00:00
currentGadget = CreateContext ( & glist ) ;
2022-10-07 11:35:49 +00:00
ng . ng_LeftEdge = window - > BorderLeft ;
ng . ng_TopEdge = window - > BorderTop ;
2022-10-02 20:59:06 +00:00
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
2022-10-07 11:35:49 +00:00
ng . ng_Width = window - > Width - window - > BorderLeft - window - > BorderRight ;
2022-10-02 20:59:06 +00:00
ng . ng_TextAttr = & Topaz160 ;
ng . ng_Height = 20 ;
timerGadget = currentGadget = CreateGadget (
TEXT_KIND ,
currentGadget ,
& ng ,
2022-10-04 00:09:48 +00:00
GTTX_Text , & timerText ,
2022-10-02 20:59:06 +00:00
GTTX_Justification , GTJ_CENTER ,
TAG_END
) ;
ng . ng_TextAttr = & Topaz80 ;
ng . ng_Height = 12 ;
2022-10-04 12:08:15 +00:00
// start/stop button
2022-10-07 11:35:49 +00:00
ng . ng_Width = ( window - > Width - window - > BorderLeft - window - > BorderRight ) / 2 ;
2022-10-02 20:59:06 +00:00
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
) ;
2022-10-04 12:08:15 +00:00
// reset button
2022-10-07 11:35:49 +00:00
ng . ng_LeftEdge + = ng . ng_Width ; // lol reuse it
2022-10-02 20:59:06 +00:00
ng . ng_GadgetText = " _Reset " ;
ng . ng_GadgetID = RESET_BUTTON_ID ;
currentGadget = CreateGadget (
BUTTON_KIND ,
currentGadget ,
& ng ,
GT_Underscore , ' _ ' ,
2022-10-04 00:09:48 +00:00
GA_Disabled , timerIsRunning | | ! timerStarted ,
2022-10-02 20:59:06 +00:00
TAG_END
) ;
2022-10-04 12:08:15 +00:00
// hours slider
2022-10-02 20:59:06 +00:00
ng . ng_LeftEdge = 85 ;
2022-10-07 11:35:49 +00:00
ng . ng_Width = window - > Width - window - > BorderLeft - window - > BorderRight - ng . ng_LeftEdge + 4 ;
2022-10-02 20:59:06 +00:00
ng . ng_TopEdge + = 12 ;
2022-10-04 00:09:48 +00:00
// 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.
2022-10-02 20:59:06 +00:00
ng . ng_GadgetText = " Hours: " ;
ng . ng_Flags = PLACETEXT_LEFT ;
ng . ng_GadgetID = HOURS_SLIDER_ID ;
2022-10-04 00:09:48 +00:00
hourSlider = currentGadget = CreateGadget (
2022-10-02 20:59:06 +00:00
SLIDER_KIND ,
currentGadget ,
& ng ,
GTSL_Min , 0 ,
GTSL_Max , 23 ,
GTSL_Level , uiHours ,
2022-10-04 00:09:48 +00:00
// You need both MaxLevelLen and LevelFormat for the label to display.
2022-10-02 20:59:06 +00:00
GTSL_MaxLevelLen , 2 ,
2022-10-04 00:09:48 +00:00
// The level is also a long, so use long number formattting.
2022-10-02 20:59:06 +00:00
GTSL_LevelFormat , " %2ld " ,
GA_Disabled , timerIsRunning ,
TAG_END
) ;
2022-10-04 12:08:15 +00:00
// minutes slider
2022-10-02 20:59:06 +00:00
ng . ng_TopEdge + = 12 ;
ng . ng_GadgetText = " Mins: " ;
ng . ng_GadgetID = MINUTES_SLIDER_ID ;
2022-10-04 00:09:48 +00:00
minuteSlider = currentGadget = CreateGadget (
2022-10-02 20:59:06 +00:00
SLIDER_KIND ,
currentGadget ,
& ng ,
GTSL_Min , 0 ,
GTSL_Max , 59 ,
GTSL_Level , uiMinutes ,
GTSL_MaxLevelLen , 2 ,
GTSL_LevelFormat , " %2ld " ,
GA_Disabled , timerIsRunning ,
TAG_END
) ;
2022-10-04 12:08:15 +00:00
// seconds slider
2022-10-02 20:59:06 +00:00
ng . ng_TopEdge + = 12 ;
ng . ng_GadgetText = " Secs: " ;
ng . ng_GadgetID = SECONDS_SLIDER_ID ;
2022-10-04 00:09:48 +00:00
secondSlider = currentGadget = CreateGadget (
2022-10-02 20:59:06 +00:00
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 ;
}
2022-10-04 12:08:15 +00:00
/**
* Set the text of the timer and redraw the necessary gadgets .
*/
2022-10-04 00:09:48 +00:00
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 ;
}
2022-10-02 20:59:06 +00:00
}
2022-10-04 00:09:48 +00:00
/**
* Ask the timer to send a message to our message port
* after a defined number of milliseconds .
*/
2022-11-25 14:22:57 +00:00
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 ;
}
2022-10-04 00:09:48 +00:00
TimerIO - > tr_node . io_Command = TR_ADDREQUEST ;
TimerIO - > tr_time . tv_secs = 0 ;
TimerIO - > tr_time . tv_micro = TIMER_INTERVAL ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
// SendIO is async, it doesn't wait for the IO to complete before continuing
SendIO ( ( struct IORequest * ) TimerIO ) ;
2022-11-25 14:22:57 +00:00
CloseDevice ( ( struct IORequest * ) TimerIO ) ;
2022-10-04 00:09:48 +00:00
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
/**
* 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 ) ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
/**
* Clear the current UI .
*/
void clearUI ( void ) {
RemoveGList ( window , windowGadgets , - 1 ) ;
FreeGadgets ( windowGadgets ) ;
}
2022-10-02 20:59:06 +00:00
2022-11-25 14:22:57 +00:00
/**
* 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 ;
}
2022-10-04 00:09:48 +00:00
/**
* Toggle the timer between started and stopped .
*/
void handleToggleTimer ( void ) {
timerIsRunning = ! timerIsRunning ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
if ( timerIsRunning ) {
2022-11-25 14:22:57 +00:00
// TODO: handle timer.device not being available
getSysTime ( ) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
// timers are microsecond resolution
timerStartTime = currentSystemTime . tv_secs * 1000000 + currentSystemTime . tv_micro ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
priorHours = NULL ;
priorMinutes = NULL ;
priorSeconds = NULL ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
originalUiHours = uiHours ;
originalUiMinutes = uiMinutes ;
originalUiSeconds = uiSeconds ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
timerStarted = TRUE ;
2022-10-02 20:59:06 +00:00
2022-10-04 12:08:15 +00:00
// start the timer
2022-10-04 00:09:48 +00:00
startTimer ( ) ;
} else {
2022-10-04 12:08:15 +00:00
// 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.
2022-10-04 00:09:48 +00:00
GT_SetGadgetAttrs (
hourSlider ,
window ,
NULL ,
GTSL_Level , uiHours ,
TAG_DONE
) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
GT_SetGadgetAttrs (
minuteSlider ,
window ,
NULL ,
GTSL_Level , uiMinutes ,
TAG_DONE
) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
GT_SetGadgetAttrs (
secondSlider ,
window ,
NULL ,
GTSL_Level , uiSeconds ,
TAG_DONE
) ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
clearUI ( ) ;
renderUI ( ) ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
/**
* Reset the timer .
*/
void handleResetTimer ( void ) {
uiHours = originalUiHours ;
uiMinutes = originalUiMinutes ;
uiSeconds = originalUiSeconds ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
timerStarted = FALSE ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
setTimerText ( ) ;
clearUI ( ) ;
renderUI ( ) ;
}
2022-10-04 12:08:15 +00:00
//
// 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 ;
2022-10-05 21:59:05 +00:00
// 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
2022-10-08 17:03:36 +00:00
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 ,
2022-10-10 18:54:54 +00:00
WA_Title , " About Pizza Timer " ,
2022-10-08 17:03:36 +00:00
TAG_END
) ;
2022-10-04 12:08:15 +00:00
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
) ;
2022-10-07 11:35:49 +00:00
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
) ;
2022-10-04 12:08:15 +00:00
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 ) ;
2022-10-05 21:59:05 +00:00
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 ;
}
2022-10-04 12:08:15 +00:00
}
2022-10-05 21:59:05 +00:00
// ensure messages are garbage collected
GT_ReplyIMsg ( iMessage ) ;
2022-10-04 12:08:15 +00:00
}
}
RemoveGList ( aboutWindow , glist , - 1 ) ;
FreeGadgets ( glist ) ;
CloseWindow ( aboutWindow ) ;
return 0 ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
/**
* Process a single Intuition message .
*/
void handleIntuitionMessage ( struct IntuiMessage * iMessage ) {
struct Gadget * targetGadget ;
struct Menu * targetMenu ;
struct MenuData * menuData ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
BOOL rerenderTimer = FALSE ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
switch ( iMessage - > Class ) {
// We've released a button.
case IDCMP_GADGETUP :
targetGadget = ( struct Gadget * ) iMessage - > IAddress ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
switch ( targetGadget - > GadgetID ) {
case START_STOP_BUTTON_ID :
handleToggleTimer ( ) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
break ;
case RESET_BUTTON_ID :
handleResetTimer ( ) ;
rerenderTimer = TRUE ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
break ;
}
break ;
2022-10-04 12:08:15 +00:00
2022-10-04 00:09:48 +00:00
// 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 ) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
menuData = GTMENUITEM_USERDATA ( targetMenu ) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
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
2022-10-04 12:08:15 +00:00
// This is why there's a bunch more overhead for building
// the menu userdata.
2022-10-04 00:09:48 +00:00
case MENU_QUIT_ID :
terminated = TRUE ;
2022-10-02 20:59:06 +00:00
break ;
2022-10-04 12:08:15 +00:00
case MENU_ABOUT_ID :
2022-10-05 21:59:05 +00:00
openAboutWindowNext = TRUE ;
2022-10-04 12:08:15 +00:00
break ;
2022-10-04 00:09:48 +00:00
}
break ;
2022-10-04 12:08:15 +00:00
2022-10-04 00:09:48 +00:00
// 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 ;
2022-10-02 20:59:06 +00:00
break ;
2022-10-04 00:09:48 +00:00
case MINUTES_SLIDER_ID :
uiMinutes = iMessage - > Code ;
rerenderTimer = TRUE ;
2022-10-02 20:59:06 +00:00
break ;
2022-10-04 00:09:48 +00:00
case SECONDS_SLIDER_ID :
uiSeconds = iMessage - > Code ;
rerenderTimer = TRUE ;
2022-10-02 20:59:06 +00:00
break ;
}
2022-10-04 00:09:48 +00:00
if ( rerenderTimer ) setTimerText ( ) ;
break ;
case IDCMP_CLOSEWINDOW :
2022-10-04 12:08:15 +00:00
// bye bye
2022-10-04 00:09:48 +00:00
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 ;
2022-10-02 20:59:06 +00:00
}
2022-10-04 00:09:48 +00:00
}
2022-11-25 18:08:36 +00:00
struct dtTrigger myTrigger ;
2022-10-04 12:08:15 +00:00
/**
2022-10-10 18:54:54 +00:00
* Start playing a bell sound , then wait for it to finish .
2022-10-04 12:08:15 +00:00
*/
void startBellSound ( void ) {
ULONG soundPlayResult ;
2022-10-04 01:14:18 +00:00
2022-11-05 14:52:01 +00:00
if ( ! DataTypesBase ) return ;
2022-10-10 18:54:54 +00:00
// https://amigaworld.net/modules/newbb/viewtopic.php?forum=15&topic_id=39094&post_id=735120&viewmode=thread&order=0#735120
2022-11-25 18:08:36 +00:00
// 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.
2022-10-10 18:54:54 +00:00
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 ) ) {
2022-10-04 01:14:18 +00:00
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 ) ;
}
2022-10-04 12:08:15 +00:00
}
/**
* Tear down the bell once the timer runs out .
*/
void waitForBellToFinish ( void ) {
if ( bellSound ) {
2022-10-10 18:54:54 +00:00
Wait ( 1 < < waitForSoundSignalNumber ) ;
2022-10-04 12:08:15 +00:00
DisposeDTObject ( bellSound ) ;
}
}
2022-10-04 01:14:18 +00:00
2022-10-04 12:08:15 +00:00
/**
* Handle the timer running out of time .
*/
void endTimer ( void ) {
startBellSound ( ) ;
2022-10-04 00:09:48 +00:00
DisplayBeep ( screen ) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
uiHours = uiMinutes = uiSeconds = 0 ;
setTimerText ( ) ;
2022-10-08 17:03:36 +00:00
waitForBellToFinish ( ) ;
2022-10-04 00:09:48 +00:00
timerIsRunning = FALSE ;
timerStarted = FALSE ;
clearUI ( ) ;
renderUI ( ) ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
void handleTimerMessage ( void ) {
2022-11-25 14:22:57 +00:00
// TODO: handle timer.device not being available
getSysTime ( ) ;
2022-10-04 00:09:48 +00:00
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 ( ) ;
2022-10-02 20:59:06 +00:00
}
2022-10-04 00:09:48 +00:00
}
// 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
2022-10-08 17:03:36 +00:00
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
) ;
2022-10-04 00:09:48 +00:00
if ( ! window ) {
teardown ( ) ;
return 1 ;
}
2022-10-04 12:08:15 +00:00
// 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 ( ) ;
2022-10-04 00:09:48 +00:00
// 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
2022-10-05 21:59:05 +00:00
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 ) ;
2022-10-04 00:09:48 +00:00
}
}
if ( ( foundSignals & timerSignal ) & & timerIsRunning ) {
handleTimerMessage ( ) ;
}
2022-10-05 21:59:05 +00:00
2022-10-07 11:35:49 +00:00
// open up the about menu outside of event handling
2022-10-05 21:59:05 +00:00
if ( openAboutWindowNext ) {
if ( openAboutWindow ( ) ) terminated = TRUE ;
openAboutWindowNext = FALSE ;
}
2022-10-04 00:09:48 +00:00
}
clearUI ( ) ;
clearMenu ( ) ;
CloseWindow ( window ) ;
2022-10-02 20:59:06 +00:00
teardown ( ) ;
return 0 ;
}