/* This file, along with several ancillary functions in another */
/* function file, lets you set the date and time using requesters. */
/* The actual setting takes place via the AmigaDos call Date, */
/* accessed by the AmigaDos function Execute(). */

/* If someone knows how to write the date and time 'directly' */
/* to the machine, I'd like to know.  The date and time info */
/* must be stored at some memory location, after all!! */

/* Some include files... */
#include <exec/types.h>
#include <exec/exec.h>
#include <intuition/intuition.h>
#include <libraries/dos.h>
#include <lattice/stdio.h> 

/* Gadget ID numbers... */
#define IDMINUTE 0
#define IDHOUR 1
#define IDYEAR 2
#define IDDAY 3
#define IDMONTH 4
#define IDSETTIME 5

/* Some time zero values... */
#define BASEYEAR 1986

/* Maximum of number years spanned by the year gadget. */
#define MAXYEARS 30

/* The library base addresses for Intuition's magic... */
int *GfxBase;
struct IntuitionBase *IntuitionBase;

/* A lookup table of month names... */
static char *MName[]={"January","February","March","April","May","June",
  "July","August","September","October","November","December"};

/* Intuition functions used elsewhere, declared external here to */
/* keep the compiler happy about returned types. */
struct Window *SetWindow;
struct IntuiMessage *GetMsg(),*IDCMPmesg;
struct Window *OpenWindow(),*CloseWindow();
struct MenuItem *ItemAddress();

/* User functions defined in Date.C and used here. */
void FromDosDate(),ToDosDate();
int LegalDays();

/* Some AmigaDos functions... */
int Execute(); 
void DateStamp();

/* Text used to identify the various gadgets in the requester. */
static struct IntuiText MinuteText = {0,1,JAM2,0,-8,NULL,"Minute Select",NULL};
static struct IntuiText HourText = {0,1,JAM2,0,-8,NULL,"Hour Select",NULL};
static struct IntuiText YearText = {0,1,JAM2,0,-8,NULL,"Year Select",NULL};
static struct IntuiText DayText = {0,1,JAM2,0,-8,NULL,"Day Select",NULL};
static struct IntuiText MonthText = {0,1,JAM2,0,-8,NULL,"Month Select",NULL};
static struct IntuiText SetTimeText = {0,1,JAM2,0,-8,NULL,"Set Time and Date",NULL};

/* When using the Pot(entiometer) gadgets (I call them sliders) */
/* you need to give Intuition data areas in which to draw the   */
/* default knobs.  You could define your own image to be used   */
/* as a knob, but I'm too lazy for that. */
static struct Image Knob1,Knob2,Knob3,Knob4,Knob5;

/* The set requester uses one boolean gadget (to actually SET the */
/* time).  Here are the relative x,y coordinate pairs that define */
/* the size and shape of the box enclosing the select region.     */
static USHORT BoxData[] = {
   0,0,
   0,10,
   30,10,
   30,0,
   0,0
  };

/* Here is Border structure that makes the border implied by BoxData */
/* meaningful to Intuition.  COMPLEMENT is a flag which forces the   */
/* border to be drawn by complementing the color at each point the   */
/* border passes through. */  
static struct Border Box1 = {
   0,0,
   2,2,COMPLEMENT,
   5,
   &BoxData,
   NULL
  };

/* These next structures define the visual characteristics of the */
/* various sliders.  The '0,0' datapair is, as I recall, where the */
/* initial position and percent of full scale occupied by the slider */
/* is specified.  The actual values are set later on in the program. */
static struct PropInfo MinuteInfo = {
   AUTOKNOB | FREEHORIZ ,
   0,0,
   NULL,NULL,
   NULL,NULL,NULL,NULL,NULL,NULL
  };

static struct PropInfo HourInfo = {
   AUTOKNOB | FREEHORIZ ,
   0,0,
   NULL,NULL,
   NULL,NULL,NULL,NULL,NULL,NULL
  };

static struct PropInfo YearInfo = {
   AUTOKNOB | FREEHORIZ ,
   0,0,
   NULL,NULL,
   NULL,NULL,NULL,NULL,NULL,NULL
  };

static struct PropInfo DayInfo = {
   AUTOKNOB | FREEHORIZ ,
   0,0,
   NULL,NULL,
   NULL,NULL,NULL,NULL,NULL,NULL
  };

static struct PropInfo MonthInfo = {
   AUTOKNOB | FREEHORIZ ,
   0,0,
   NULL,NULL,
   NULL,NULL,NULL,NULL,NULL,NULL
  };

/* These next structures define the various gadgets.  Note that all */
/* of the sliders -- but not the boolean gadget -- are set to       */
/* follow mouse events and to immediately notify the program when   */
/* they are selected by the user.  The follow function is needed    */
/* because we want to continuously update the displayed date and    */
/* time to give feedback.  Please note that the gadgets are linked */
/* together as a list. */
static struct Gadget MinuteGadget = {
   NULL,
   5,20,360,10,
   GADGHNONE | GADGIMAGE,
   GADGIMMEDIATE | FOLLOWMOUSE ,
   PROPGADGET | REQGADGET,
   (APTR)&Knob1 ,
   NULL,
   &MinuteText,
   NULL,
   (APTR)&MinuteInfo,
   IDMINUTE,
   NULL
  };

static struct Gadget HourGadget = {
   &MinuteGadget,
   5,40,360,10,
   GADGHNONE | GADGIMAGE,
   GADGIMMEDIATE | FOLLOWMOUSE ,
   PROPGADGET | REQGADGET,
   (APTR)&Knob2 ,
   NULL,
   &HourText,
   NULL,
   (APTR)&HourInfo,
   IDHOUR,
   NULL
  };

static struct Gadget YearGadget = {
   &HourGadget,
   5,60,360,10,
   GADGHNONE | GADGIMAGE,
   GADGIMMEDIATE | FOLLOWMOUSE ,
   PROPGADGET | REQGADGET,
   (APTR)&Knob3 ,
   NULL,
   &YearText,
   NULL,
   (APTR)&YearInfo,
   IDYEAR,
   NULL
  };

static struct Gadget DayGadget = {
   &YearGadget,
   5,80,372,10,
   GADGHNONE | GADGIMAGE,
   GADGIMMEDIATE | FOLLOWMOUSE ,
   PROPGADGET | REQGADGET,
   (APTR)&Knob4 ,
   NULL,
   &DayText,
   NULL,
   (APTR)&DayInfo,
   IDDAY,
   NULL
  };

static struct Gadget MonthGadget = {
   &DayGadget,
   5,100,360,10,
   GADGHNONE | GADGIMAGE,
   GADGIMMEDIATE | FOLLOWMOUSE ,
   PROPGADGET | REQGADGET,
   (APTR)&Knob5 ,
   NULL,
   &MonthText,
   NULL,
   (APTR)&MonthInfo,
   IDMONTH,
   NULL
  };

/* This gadget differs from the above because it is a boolean */
/* gadget and not a slider.  Note that it has a flag which    */
/* tells Intuition to get rid of the requester. */
static struct Gadget SetTime = {
   &MonthGadget,
   5,120,30,10,
   GADGHCOMP ,
   GADGIMMEDIATE | ENDGADGET ,
   BOOLGADGET | REQGADGET,
   (APTR)&Box1 ,
   NULL,
   &SetTimeText,
   NULL,
   NULL,
   IDSETTIME,
   NULL
  };

/* Here is the requester data structure.  Note that it 'stores' the */
/* gadgets defined above by pointing to the start of the linked list */
/* of gadgets. */
static struct Requester SetReq= {
   NULL,
   5,40,
   390,140,
   NULL,NULL,
   &SetTime,
   NULL,
   NULL,
   NULL,
   0,
   NULL,
   {NULL},
   {NULL},
   NULL,
   {NULL}
  };

/* This function takes the current settings of the date/time */
/* variables and formats/displays the information at the top */
/* of the window in which the requester appears. */
void DisplayDateTime(Month,Day,Year,Hour,Minute)
  int Month,Day,Year,Hour,Minute;
  {
   /* We need to reserve space for the sprintf() function to */
   /* write into. */

   static char DispString[]="                                ";
   char *DayModifier="th",*HalfDay="a.m.";

   /* This section of code assigns the correct ending to the */
   /* day number (e.g., '1st', '2nd', etc.). */

   if(Day>=10 && Day<20)
     DayModifier="th";
   else
     {
      switch(Day%10)
        {
         case 1:
           DayModifier="st";
           break;
         case 2:
           DayModifier="nd";
           break;
         case 3:
           DayModifier="rd";
           break;
         default:
           DayModifier="th";
           break;
       }
     }

   /* Here we set the a.m./p.m. 'flag'.  Note that we adjust */
   /* the value of Hour, which arrives in 24 hour military   */
   /* format, to be consistent with a 12 hour a.m./p.m. format. */

   if((Hour>=0 && Hour<12) || Hour==24)
     {
      HalfDay="a.m.";
      if(Hour==0 || Hour==24)
        Hour=12;
     }
   else
     {
      HalfDay="p.m.";
      if(Hour!=12)
        Hour-=12;
     }

   /* Here's where we glom all the info together to form a string. */
   /* Note that the string is then padded to always make it the same */
   /* length.  This makes sure you always completely write-over */
   /* whatever stuff you wrote previously. */

   sprintf(&DispString,"%s %d%s, %d  %d:%02d %s",MName[Month],Day,DayModifier,Year,Hour,Minute,HalfDay);
   while(strlen(&DispString)<32)
     strcat(&DispString," ");

   /* Here's where we actually write the string to the window. */
   Move(SetWindow->RPort,5,20);
   Text(SetWindow->RPort,&DispString,32);
  }

/* This function returns a value meaningful to humans from whatever */
/* unsigned 16 bit value is sent by Intuition to describe the current */
/* location of the slider. */
int GetPotVal(PickedGadget)
  struct Gadget *PickedGadget;
  {
   static int PotMult[]={60,24,MAXYEARS,31,12};
   int temp;
   USHORT PotVal;
   struct PropInfo *PickedProp;
   
   /* Here we get a pointer to the currently selected gadget. */
   /* I tried to do this all in one step but for some reason  */
   /* it didn't work... */
   PickedProp=(struct PropInfo *)PickedGadget->SpecialInfo;
   
   /* The unsigned value representing the gadget's location. */
   PotVal=(PickedProp->HorizPot);
   
   /* This converts the unsigned value to meaningful data. */
   temp=(PotVal*PotMult[PickedGadget->GadgetID])>>16;
   return(temp);
  }

/* This function lets us exit gracefully, with some indication */
/* of what went wrong (if anything did).  Normally I wouldn't  */
/* include this, since printf's expand a program file a LOT,   */
/* but I used sprintf() up above so why not...                 */
void Cleanup(Level)
  int Level;
  {
   /* Level is an error code.  See main() for context. */
   if(Level>3)
     CloseWindow(SetWindow);
   if(Level>2)
     CloseLibrary(IntuitionBase);
   if(Level>1)
     CloseLibrary(GfxBase);
   switch(Level)
     {
      case 1:
        printf("Graphics library would not open.\n");
        break;
      case 2:
        printf("Intuition library would not open.\n");
        break;
      case 3:
        printf("Window would not open.\n");
        break;
      case 4:
        printf("Requester would not open.\n");
        break;
     }
   exit(0);
  }
  
/* The main banana. */
main()
  {
   int temp,*ChgVal,Minute,Hour,Year,Day,Month;
   static char SetCmd[]="                ";
   ULONG class;
   struct Gadget *CurGadget;
   
   /* Information needed by Intuition to open the window in which */
   /* the requester appears.  Note that you also need to set the  */
   /* window to pass mouse motion and gadget selection reports.   */
   /* REQCLEAR means 'Hey, Intuition, tell us when the requester  */
   /* got sent away', i.e., when user wants to set date/time.*/

   static struct NewWindow newSetWindow = {
      10,10,
      500,180,
      0,1,
      GADGETDOWN | MOUSEMOVE | REQCLEAR,
      WINDOWDRAG | SMART_REFRESH | NOCAREREFRESH | ACTIVATE, 
      NULL,
      NULL,
      "AmigaDOS Time and Date Setting Screen",
      NULL,
      NULL,
      NULL,NULL,
      NULL,NULL,
      WBENCHSCREEN
     };

   /* A data structure needed by the AmigaDos date/time function */

   struct DateStamp DosDate;

   /* Here we open the various libraries.  Note error codes passed */
   /* to Cleanup() if something goes wrong. */

   if((GfxBase=(int *)OpenLibrary("graphics.library",1))==NULL)
     Cleanup(1);
   if((IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",1))==NULL)
     Cleanup(2);
   if((SetWindow=OpenWindow(&newSetWindow))==NULL)
     Cleanup(3);

   /* Get the current date and time from AmigaDos.  Unfortunately, */
   /* it comes back in a very unuseful form.  So we pass it through */
   /* the FromDosDate function, which converts it into a more useful */
   /* format.  Afterwards, we subtract one from the month number */
   /* because the MName array expects a value range of 0..11 */

   DateStamp(&DosDate);
   FromDosDate(&DosDate,&Month,&Day,&Year,&Hour,&Minute,&temp);
   --Month;

   /* Bump the minute counter if we are more than 30 seconds into */
   /* the current minute. */
  
   if(temp>=30)
     ++Minute;
   
   /* Here we set the size of the slider controls. */

   MinuteInfo.HorizBody=0xFFFF/60;
   HourInfo.HorizBody=0xFFFF/24;
   YearInfo.HorizBody=0xFFFF/30;
   DayInfo.HorizBody=0xFFFF/31;
   MonthInfo.HorizBody=0xFFFF/12;

   /* Here we set the current date and time into the sliders so */   
   /* that they will start out in the 'correct' positions. */

   MinuteInfo.HorizPot=MinuteInfo.HorizBody*Minute;
   HourInfo.HorizPot=HourInfo.HorizBody*Hour;
   YearInfo.HorizPot=YearInfo.HorizBody*(Year-BASEYEAR);
   DayInfo.HorizPot=DayInfo.HorizBody*Day;
   MonthInfo.HorizPot=MonthInfo.HorizBody*Month;

   /* Before going into the event processing loop, show the */   
   /* date and time as returned by AmigaDos */

   DisplayDateTime(Month,Day,Year,Hour,Minute);

   /* Here we try and set up the requester. */

   if(Request(&SetReq,SetWindow)==FALSE)
     Cleanup(4);

   /* The event processing loop.  We run it forever.  Actually, */
   /* only until the requester goes away. */

   for(;;)
     {

      /* Put the program to bed until Intuition sends us a message. */
      /* That funny argument in the function says 'wait until a     */
      /* certain bit in the message area turns on' (which signifies */
      /* a message).  The particular bit -- the Intuition Direct    */
      /* Communication Message Port bit (I did NOT make up that name)*/
      /* -- is mp_SigBit's into the (32?) bit message area.  You    */
      /* get the value of mp_SigBit, which is set by Intuition when */
      /* it opens your window, from a data structure pointed to by  */
      /* the window data structure. */
      
      Wait(1<<SetWindow->UserPort->mp_SigBit);
      
      /* Here we process messages so long as they continue to arrive. */
      /* After that, we go back to sleep. */

      while(IDCMPmesg=GetMsg(SetWindow->UserPort))
        {
        
         /* Messages come in different classes -- and you need to */ 
         /* reply to them ASAP, particularly mouse movement       */
         /* messages, because Intuition holds onto all messages   */
         /* until you reply to them.  Which can burn up a LOT of  */
         /* memory.  Actually, I think there are some occasions   */
         /* where you can not reply -- but I KNOW there are also  */
         /* some where not replying calls in the Guru! */

         class=IDCMPmesg->Class;
         ReplyMsg(IDCMPmesg);
         
         /* We have only 2 classes of events -- but Intuition views */
         /* them as three.  The 2 which are one so far as this      */
         /* program is concerned are 'gadget picked' and 'mouse     */
         /* moved'.  The reason these are the 'same' class here is  */
         /* that whenever the mouse moves we want to see if we need */
         /* to update the displayed date/time (i.e., the user may   */
         /* have dragged the slider. */
        
         if(class==GADGETDOWN || class==MOUSEMOVE)
           {
            if(class==GADGETDOWN)
               CurGadget=(struct Gadget *)(IDCMPmesg->IAddress);
               
            /* Ignore the message that the user wants to set date */
            /* and time, since it is also heralded by a 'requester */
            /* went away' message. */
           
            if(CurGadget->GadgetID<IDSETTIME || class==MOUSEMOVE)
              {
              
               /* Otherwise, get the current setting and do whatever */
               /* special processing is needed.  We store the setting */
               /* in ChgVal so that we can compare it to the last    */
               /* similar setting -- thus avoiding writing to the    */
               /* screen except when absolutely necessary.  This     */
               /* reduces flicker. 'ChgVal' is the base value which */
               /* we will be comparing to.value.  'Temp' is the     */
               /* <possibly> changed value just returned. */

               temp=GetPotVal(CurGadget);
               switch(CurGadget->GadgetID)
                 {
                  case IDMINUTE:
                    ChgVal=&Minute;
                    break;
                  case IDHOUR:
                    ChgVal=&Hour;
                    break;
                  case IDYEAR:
                    ChgVal=&Year;
                    
                    /* We need to add BASEYEAR to the returned value. */

                    temp+=BASEYEAR;
                    
                    /* If the year changed, get the current day setting */
                    /* and see if we need to adjust for leap years. */

                    if(temp!=Year)
                      {
                       Day=GetPotVal(&DayGadget)+1;
                       Day=LegalDays(Month,Day,temp);
                      }
                    break;
                  case IDDAY:
                    ChgVal=&Day;
                    temp+=1;
                    
                    /* Make sure we 'max out' in 28/29/30 day months. */

                    temp=LegalDays(Month,temp,Year);
                    break;
                  case IDMONTH:
                    ChgVal=&Month;
                    
                    /* If the month changed, we need to make sure we */
                    /* don't have to change the displayed day due to */
                    /* the different lengths of months. */

                    if(temp!=Month)
                      {
                       Day=GetPotVal(&DayGadget)+1;
                       Day=LegalDays(temp,Day,Year);
                      }
                    break;
                 }
                 
               /* Only display date and time if something changed. */

               if(*ChgVal!=temp)
                 {
                  *ChgVal=temp;
                  DisplayDateTime(Month,Day,Year,Hour,Minute);
                 }
              }
           }
           
         /* If the requester went away, set the date and time and */
         /* end the program. */

         if(class==REQCLEAR)
           {

            /* Build the command string... */

            sprintf(&SetCmd,"date %.2d-%.3s-%d %d:%02d",Day,MName[Month],Year%100,Hour,Minute); 

            /* and Execute() it! */

            Execute(&SetCmd,0,0);
            Cleanup(5);
           }
        } /* End of handling IDCMP message */
     } /* End of set event processing */               
  }
