/*
 *  WICONIFY    A utility that allows you to iconify any Intuition window
 *              on any screen, and to open WB windows on any screen.
 *
 *  wEvent.c    Main event loop routines.
 *
 *  Copyright 1990 by Davide P. Cervone, all rights reserved.
 *  You may use this code, provided this copyright notice is kept intact.
 */

#include "wHandler.h"
#include "wMenu.h"

static char *IconAbout = COPYRIGHT;     /* ABOUT... message */

static WORD MouseX,MouseY;              /* Saved mouse position */
static WSCREEN *MouseScreen = NULL;     /* Screen where mouse move occured */
static WICONREF *PressedIcon;           /* Icon to report a release event to */

static WICON NullIcon = {NULL, NULL,NULL,NULL, 0,0,0, 0,NULL};
                                        /* an empty icon structure */

extern struct Window *WindowToActivate;


/*
 *  DoVanillaKey()
 *
 *  Then, if the right amiga key was pressed, and no shift is pressed,
 *    Attempt to create a new screen of the required depth
 *    If successful, activate the wIconify window on the screen
 */

static void DoVanillaKey(Code,Qualifier,theWindow)
USHORT Code;
USHORT Qualifier;
struct Window *theWindow;
{
   WSCREEN *theScreen;

   if (Code >= '1' && Code <= '5')
   {
      if ((Qualifier & (AMIGARIGHT | SHIFTKEYS)) == AMIGARIGHT)
      {
         Code -= '0';
         theScreen = DoNewScreen(Code,theWindow->WScreen);
         if (theScreen && theScreen->BackDrop)
            ActivateWindow(theScreen->BackDrop);
      }
   }
}


/*
 *  DoMouseButtons()
 *
 *  Do the right thing for each of the event types:
 *  SELECTDOWN:
 *    Cancel any drag in progress (never should happen)
 *    If there are any icons selected, deselect them
 *
 *  SELECTUP:
 *    If there is an icon to report a release event to, do it
 *    Complete any dragging that is in progress
 *    If there is a window to activate (due to a double-click), do it
 *      (we wait until now so that the window does not get an unexpected
 *      SELECTUP message when the mouse is released)
 *
 *  MENUDOWN:
 *    Cancel any dragging in progress
 *
 *  MENUUP:
 *    Cancel any dragging and close any selected icons (the only time we will
 *    get menu buttons rather than menu events is when an icon is being 
 *    dragged)
 */

static void DoMouseButtons(theMessage,theScreen)
struct IntuiMessage *theMessage;
WSCREEN *theScreen;
{
   switch(theMessage->Code)
   {
      case SELECTDOWN:
         CancelDragging(theScreen);
         if (theScreen->Selected) DeselectAll(theScreen);
         break;

      case SELECTUP:
         if (PressedIcon) ReportEvent(WI_REPORTRELEASE,PressedIcon);
         EndDragging(theScreen,theMessage->MouseX,theMessage->MouseY);
         if (WindowToActivate)
            ActivateWindow(WindowToActivate),
            WindowToActivate = NULL;
         break;

      case MENUDOWN:
         CancelDragging(theScreen);
         break;

      case MENUUP:
         CancelDragging(theScreen);
         CloseSelected(theScreen);
         break;
   }
   PressedIcon = NULL;
}



/*
 *  DoMouseMove()
 *
 *  If mouse moves have occured,
 *    If icons are being dragged on the given screen,
 *      Move them to their new location
 *    Reset for next move
 */

void DoMouseMove()
{
   if (MouseScreen)
   {
      if (MouseScreen->Flags & (WI_STARTDRAG| WI_DRAGGING))
         MoveGels(MouseScreen,MouseX,MouseY);
      MouseScreen = NULL;
   }
}


/*
 *  EARLIERTIME checks if one time value is earlier than another.
 */
#define EARLIERTIME(s1,m1,s2,m2)    ((s1)<(s2)||((s1)==(s2)&&(m1)<(m2)))


/*
 *  DoGadgetDown()
 *
 *  If the current message time is earlier than the previous time (ie, the
 *    clock has been reset), then if the time is not zero (ie, it is not a
 *    bogus time stamp from some other input handler) then clear the old times
 *  If we have a double click
 *    Cancel dragging in progress (never should happen)
 *    Open the list of selected gadgets, but wait to activate the windows
 *      until we get a mouse up event
 *  Otherwise
 *    If the gadget wants press events reported,
 *      Report the event and save the pressed gadget for a release event
 *    Select the pressed gadget
 *    If the gadget has been selected, set up for dragging it
 *    Record the time for double click checking.
 */

static void DoGadgetDown(theGadget,theScreen,theMessage)
struct wGadget *theGadget;
WSCREEN *theScreen;
struct IntuiMessage *theMessage;
{
   static long Secs = 0, Mics = 0;

   if (EARLIERTIME(theMessage->Seconds,theMessage->Micros,Secs,Mics))
      if (theMessage->Seconds || theMessage->Micros) Secs = Mics = 0;
   if (DoubleClick(Secs,Mics,theMessage->Seconds,theMessage->Micros))
   {
      CancelDragging(theScreen);
      OpenSelected(theScreen,FALSE);
   } else {
      if (GADGETICON->Icon.Report & WI_REPORTPRESS)
      {
         ReportEvent(WI_REPORTPRESS,GADGETICON);
         PressedIcon = GADGETICON;
      }
      SelectIcon(GADGETICON,theMessage->Qualifier & SHIFTKEYS);
      if (theGadget->Gadget.Flags & SELECTED)
         StartDragging(GADGETICON,theMessage->MouseX,theMessage->MouseY);
      Secs = theMessage->Seconds;
      Mics = theMessage->Micros;
   }
}


/*
 *  DoIconMenu()
 *
 *  Do the right thing for each of the menu items (the routine names are
 *  pretty self-explanitory).
 */

static int DoIconMenu(theCode,theScreen,EndItAll)
USHORT theCode;
WSCREEN *theScreen;
int EndItAll;
{
   switch(ITEMNUM(theCode))
   {
      case IM_OPEN:
         OpenSelected(theScreen,TRUE);
         break;

      case IM_CLOSE:
         CloseSelected(theScreen);
         break;

      case IM_LOCK:
         LockSelected(theScreen);
         UpdateActiveMenu();
         break;

      case IM_CLEANUP:
         CleanUpIcons(theScreen);
         RefreshIcons(theScreen,TRUE);
         break;

      case IM_ORGANIZE:
         OrganizeIcons(theScreen);
         RefreshIcons(theScreen,TRUE);
         break;

      case IM_OPENALL:
         OpenAllIcons(theScreen);
         break;

      case IM_ABOUT:
         if (ActiveWindow)
         {
            SetWindowTitles(ActiveWindow,-ONE,IconAbout);
            ShowTitle(ActiveWindow->WScreen,TRUE);
         }
         break;

      case IM_END:
         DoEndIconify();
         break;
   }
   return(EndItAll);
}


/*
 *  DoMenu()
 *
 *  While there are more menu items to do
 *    Get the menu item pointer
 *    If it is an ICON menu item, do the ICON menu code
 *    Otherwise do the SCREEN menu code
 *    Go on to the next item
 */

static int DoMenu(theCode,theScreen,EndItAll)
USHORT theCode;
WSCREEN *theScreen;
int EndItAll;
{
   struct MenuItem *theItem;

   while (theCode != MENUNULL)
   {
      theItem = ItemAddress(wMenu,theCode);
      switch(MENUNUM(theCode))
      {
         case ICON_MENU:
            EndItAll = DoIconMenu(theCode,theScreen,EndItAll);
            break;

         case SCREEN_MENU:
            EndItAll = DoScreenMenu(theCode,theScreen,EndItAll);
            break;
      }
      theCode = theItem->NextSelect;
   }
   return(EndItAll);
}


/*
 *  DoEvent()
 *
 *  Clear the ABOUT or error message from the screen title, if necessary
 *  Get the screen of the wIconify window were the message occured
 *  If the screen exists
 *    If there are pending mouse moves and this is not a mouse move
 *      Do the buffered mouse moves
 *    Call the correct routine for each class of events
 */

int DoEvent(theMessage,EndItAll)
struct IntuiMessage *theMessage;
int EndItAll;
{
   WSCREEN *theScreen;

   if (ActiveWindow && ActiveWindow->ScreenTitle != wIconifyTitle)
      SetWindowTitles(ActiveWindow,-ONE,wIconifyTitle);
   theScreen = (WSCREEN *)theMessage->IDCMPWindow->UserData;
   if (theScreen)
   {
      if (MouseScreen && theMessage->Class != MOUSEMOVE) DoMouseMove();
      switch(theMessage->Class)
      {
         case MOUSEBUTTONS:
            DoMouseButtons(theMessage,theScreen);
            break;

         case GADGETDOWN:
            DoGadgetDown(theMessage->IAddress,theScreen,theMessage);
            break;

         case MENUPICK:
            CancelDragging(theScreen);
            EndItAll = DoMenu(theMessage->Code,theScreen,EndItAll);
            break;

         case MOUSEMOVE:
            MouseX = theMessage->MouseX;
            MouseY = theMessage->MouseY;
            MouseScreen = theScreen;
            break;

         case VANILLAKEY:
            DoVanillaKey(theMessage->Code,theMessage->Qualifier,
                         theMessage->IDCMPWindow);
            break;

         case ACTIVEWINDOW:
            SetActiveWindow(theMessage->IDCMPWindow);
            WindowToActivate = NULL;
            break;

         case INACTIVEWINDOW:
            SetActiveWindow(NULL);
            WindowToActivate = NULL;
            break;
      }
   }
   return(EndItAll);
}


/*
 *  DoIconify()
 *
 *  Get the icon and screen of the given window
 *  If the screen was located
 *    but there was no icon for the window
 *       Create a new icon structure, if possible
 *          Make it point to the given window
 *          Set its default flags and mark it as system-created
 *          And link it into the icon list for its screen
 *    If an icon exists or was allocated
 *      If the icon is for a screen and dragging is in progress (ie, the
 *          left button is down over an icon), and the message is a NOREPLY
 *          message (ie, it came from the handler), then
 *        Close the selected icons on the screen
 *      Otherwise
 *        If the icon wants to verify iconifications, report ICONVERIFY
 *        Otherwise iconify the window
 */

void DoIconify(theMessage)
struct wIconMessage *theMessage;
{
   WICONREF *theIcon;
   WSCREEN *theScreen;

   theIcon = FindIcon(theMessage->Window,&theScreen);
   if (theScreen)
   {
      if (theIcon == NULL)
      {
         if (NEWSTRUCT(wIconRef,theIcon))
         {
            theIcon->Window = theMessage->Window;
            theIcon->Icon.Flags |= WI_SYSICON | DefaultFlags;
            LinkIcon(theIcon,theScreen);
         }
      }
      if (theIcon)
      {
         if ((theIcon->Icon.Flags & WI_SCREENICON) &&
             (theIcon->Screen->Flags & (WI_STARTDRAG | WI_DRAGGING)) &&
             (theMessage->Flags & WI_NOREPLY))
         {
            CloseSelected(theIcon->Screen);
         } else {
            if (theIcon->Icon.Report & WI_REPORTICONVERIFY)
               ReportEvent(WI_REPORTICONVERIFY,theIcon);
              else
               Iconify(theIcon,theMessage->Flags & WI_CHANGE);
         }
      }
   }
}


/*
 *  UpdateIcon()
 *
 *  If there was no icon given, use the blank icon
 *  Get the icon's screen and system flags
 *  Copy the icon template to the existing icon
 *  And replace the system flags from before
 *  If the icon is currently iconified (ie, it has a gadget)
 *    Get the gadget pointer
 *    Remove the gadget temprarily so we can work on it
 *    If no new position was given, retrieve the old one
 *    Save the SELECTED flag
 *    Setup the gadget to use the current icon data
 *    If it used to be selected, re-select it
 *    Replace the gadget on the screen and refresh the screen's icons
 */

static void UpdateIcon(theIcon,WIcon)
WICONREF *theIcon;
WICON *WIcon;
{
   WSCREEN *theScreen;
   ULONG Flags;
   int pos;
   struct Gadget *theGadget;
   
   if (WIcon == NULL) WIcon = &NullIcon;
   theScreen = theIcon->Screen;
   Flags = theIcon->Icon.Flags & WI_SYSTEMFLAGS;
   theIcon->Icon = *WIcon;
   theIcon->Icon.Flags = (WIcon->Flags & ~WI_SYSTEMFLAGS) | Flags;
   if (theIcon->Gadget)
   {
      theGadget = &(theIcon->Gadget->Gadget);
      if (theScreen->BackDrop)
          pos = RemoveGadget(theScreen->BackDrop,theGadget);
      if (WIcon->x == 0 && WIcon->y == 0)
         theIcon->Icon.x = theGadget->LeftEdge,
         theIcon->Icon.y = theGadget->TopEdge;
      Flags = theGadget->Flags & SELECTED;
      SetupGadget(theIcon);
      if (Flags) MakeSelected(theGadget);
      if (theScreen->BackDrop) AddGadget(theScreen->BackDrop,theGadget,pos);
      RefreshIcons(theScreen,TRUE);
   }
}


/*
 *  DoSetIcon()
 *
 *  Find the icon and screen of the given window
 *  If the icon already exists, update it to the new values,
 *  Otherwise, if the screen was found
 *    Get a new Icon structure, if possible
 *      Link it to the window
 *      Copy the data from the template
 *      Clear the system flags
 *      If no template was given, add the default flags
 *      Link the icon to the screen's icon list
 *  Return the pointer to the IconRef structure
 */

static void DoSetIcon(theWindow,WIcon,theMessage)
struct Window *theWindow;
WICON *WIcon;
struct wIconMessage *theMessage;
{
   WICONREF *theIcon;
   WSCREEN *theScreen;
   
   theIcon = FindIcon(theWindow,&theScreen);
   if (theIcon) UpdateIcon(theIcon,WIcon);
   else if (theScreen)
   {
      if (NEWSTRUCT(wIconRef,theIcon))
      {
         theIcon->Window = theWindow;
         theIcon->Icon = (WIcon)? *WIcon: NullIcon;
         theIcon->Icon.Flags &= ~WI_SYSTEMFLAGS;
         if (WIcon == NULL) theIcon->Icon.Flags |= DefaultFlags;
         LinkIcon(theIcon,theScreen);
      }
   }
   theMessage->Icon = theIcon;
}


/*
 *  DoUnSetIcon()
 *
 *  Find the icon and screen of the given window
 *  If the icon was found
 *    Restore the icon's window and remove the icon from the screen
 *    Delete the icon from memory
 *  Return the IconRef (which is no longer valid)
 */

static void DoUnSetIcon(theWindow,theMessage)
struct Window *theWindow;
struct wIconMessage *theMessage;
{
   WICONREF *theIcon;

   theIcon = FindIcon(theMessage->Window,&(theMessage->Data.wScreen));
   if (theIcon)
   {
      Restore(theIcon,TRUE);
      DeleteIcon(theIcon);
   }
   theMessage->Icon = theIcon;
}


/*
 *  DoAddIcon()
 *
 *  If a screen and an icon are given
 *    Create a new Icon structure
 *      Mark it as having no window associated with it
 *      Copy the template icon
 *      Clear the system flags
 *      Link the icon into the screen's icon list
 *      Iconify the icon (ie, make it show up on the screen
 *  Return the newly created IconRef
 */

static WICONREF *DoAddIcon(WIcon,theScreen)
WICON *WIcon;
WSCREEN *theScreen;
{
   WICONREF *theIcon = NULL;

   if (theScreen && WIcon)
   {
      if (NEWSTRUCT(wIconRef,theIcon))
      {
         theIcon->Window = NULL;
         theIcon->Icon = *WIcon;
         theIcon->Icon.Flags &= ~WI_SYSTEMFLAGS;
         LinkIcon(theIcon,theScreen);
         Iconify(theIcon,FALSE);
      }
   }
   return(theIcon);
}


/*
 *  DoSelectNext()
 *
 *  If the screen has selected icons, start with the next one,
 *  Otherwise start with the first icon on the screen
 *  Skip over any non-iconified icons
 *  If we hit the end of the list of icons
 *    Start over at the begining of the list
 *    Skip over any non-iconified icons (until we get back to were we started)
 *  If we found a new icon to select, select it
 */

static void DoSelectNext(theScreen)
WSCREEN *theScreen;
{
   WICONREF *theIcon,*InitialIcon;

   if (theScreen->Selected)
      InitialIcon = ((WICONREF *)(theScreen->Selected->Gadget.UserData))->Next;
     else
      InitialIcon = theScreen->IconRef;
   theIcon = InitialIcon;
   while (theIcon && (theIcon->Icon.Flags & WI_ICONIFIED) == FALSE)
      theIcon = theIcon->Next;
   if (theIcon == NULL)
   {
      theIcon = theScreen->IconRef;
      while (theIcon && (theIcon->Icon.Flags & WI_ICONIFIED) == FALSE &&
         theIcon != InitialIcon) theIcon = theIcon->Next;
   }
   if (theIcon) SelectIcon(theIcon,FALSE);
}


/*
 *  DoIconEvent()
 *
 *  Do the right thing for each type of message
 *  ICONIFY:    Do the iconification routine
 *  RESTORE:    Restore the given icon
 *  ICONOF:     Find the icon and screen of the given window
 *  SETICON:    Set the window's icon
 *  UNSETICON:  Remove the window's icon
 *  SELECTICON: Attempt to select the indicated icon
 *  UNSELECT:   Remove the icon from the selection
 *  MOVEICON:
 *    If an icon was specified
 *      Get the icon's screen
 *      If the icon has a gadget and the screen has a wIconify window
 *         Remove the gadget from the screen
 *         Set the gadget's position to the one given in the message
 *         Check the new position for validity
 *         Put the gadget back on the screen
 *         Refresh the icons on the screen
 *  ADDICON:    Add the given icon to the indicated screen
 *  REMOVEICON: Delete the icon from memory
 *  REDRAW:     Refresh the icons on the given screen
 *  UPDATEICON: Update the given icon with new values
 *  BACKDROPOF:
 *    Find the screen structure if the given screen
 *    If found, return the backdrop window of the screen
 *    Otherwise return NULL
 *  REMSCREEN:  Remove the given screen from the list of screens
 *  CLOSEICON:  Close the given icon
 *  SELECTNEXT: Select the next icon on the given screen
 *  NEWSCREEN:
 *    If the real WB screen is open (to use as a template)
 *      Change the active window (unsetting the menus)
 *      Set the NewScreen menu to the given modes
 *      Create the new screen and return its pointer
 *  MAKEWB:
 *    Find the indicated screen
 *    If it is found and it has a wIconify window
 *      Make this the current WB screen
 *      Set the OpenWindow submenu to make CURRENT_WB checked
 *      Activate the new WB screen's wIconify backdrop window
 *  OPENSELECT:    Open the selected icons on the given screen
 *  CLOSESELECT:   Close the selected icons on the given screen
 *  OPENON:        Set the OpenOn menu items
 *  REPORTVERIFY:  (returned ICONVERIFY message)
 *    If the message was OKed, iconify the window
 *  MAKECONTACT:   (Initial message from the loader)
 *    Check the loader's version and report OK or BAD
 *  ENDICONIFY:
 *    Try to end the process
 *    Report to any programs that are interested
 *    Try to exit when we have cleaned out all message from the ports
 */

int DoIconEvent(theMessage,EndItAll)
struct wIconMessage *theMessage;
int EndItAll;
{
   WSCREEN *theScreen;
   int pos;

   switch(theMessage->Action)
   {
      case WI_ICONIFY:
         DoIconify(theMessage);
         break;

      case WI_RESTORE:
         Restore(theMessage->Icon,TRUE);
         break;

      case WI_ICONOF:
         theMessage->Icon =
             FindIcon(theMessage->Window,&(theMessage->Data.wScreen));
         break;

      case WI_SETICON:
         DoSetIcon(theMessage->Window,theMessage->Icon,theMessage);
         break;

      case WI_UNSETICON:
         DoUnSetIcon(theMessage->Window,theMessage);
         break;

      case WI_SELECTICON:
         SelectIcon(theMessage->Icon,theMessage->Flags & WI_ADDTOSELECT);
         break;

      case WI_UNSELECT:
         DeselectIcon(theMessage->Icon);
         break;

      case WI_MOVEICON:
         if (theMessage->Icon)
         {
            theScreen = theMessage->Icon->Screen;
            if (theMessage->Icon->Gadget && theScreen->BackDrop)
            {
               pos = RemoveGadget(theScreen->BackDrop,theMessage->Icon->Gadget);
               theMessage->Icon->Gadget->Gadget.LeftEdge =
                   theMessage->Data.Position.x;
               theMessage->Icon->Gadget->Gadget.TopEdge =
                   theMessage->Data.Position.y;
               InitPosition(theMessage->Icon->Gadget);
               AddGadget(theScreen->BackDrop,theMessage->Icon->Gadget,pos);
               RefreshIcons(theMessage->Icon->Screen,TRUE);
            }
         }
         break;

      case WI_ADDICON:
         if (theMessage->Icon) theMessage->Icon =
            DoAddIcon(theMessage->Icon,FindScreen(theMessage->Data.Screen));
         break;

      case WI_REMOVEICON:
         if (theMessage->Icon) DeleteIcon(theMessage->Icon);
         break;

      case WI_REDRAW:
         RefreshIcons(theMessage->Data.wScreen,TRUE);
         break;

      case WI_UPDATEICON:
         if (theMessage->Icon)
            UpdateIcon(theMessage->Icon,theMessage->Data.Icon);
         break;

      case WI_BACKDROPOF:
         theScreen = FindScreen(theMessage->Data.Screen);
         if (theScreen)
            theMessage->Window = theScreen->BackDrop;
           else
            theMessage->Window = NULL;
         break;

      case WI_REMSCREEN:
         UnLinkScreen(theMessage->Data.wScreen);
         break;

      case WI_CLOSEICON:
         CloseIcon(theMessage->Icon);
         break;

      case WI_SELECTNEXT:
         theScreen = FindScreen(theMessage->Data.Screen);
         if (theScreen) DoSelectNext(theScreen);
         break;

      case WI_NEWSCREEN:
         if (RealWB)
         {
            SetActiveWindow(NULL);
            SetScreenMenu(theMessage->Data.NewScreen.Modes);
            theScreen = 
               DoNewScreen(theMessage->Data.NewScreen.Depth,RealWB->Screen);
            theMessage->Data.Screen = theScreen->Screen;
         }
         break;

      case WI_MAKEWB:
         theScreen = FindScreen(theMessage->Data.Screen);
         if (theScreen && theScreen->BackDrop)
         {
            NewWBScreen(theScreen);
            SetWindowMenu(OW_CURRENTWB,NOCHANGE);
            ActivateWindow(theScreen->BackDrop);
         }
         break;

      case WI_OPENSELECT:
         OpenSelected(FindScreen(theMessage->Data.Screen),TRUE);
         break;

      case WI_CLOSESELECT:
         CloseSelected(FindScreen(theMessage->Data.Screen));
         break;

      case WI_OPENON:
         SetWindowMenu(theMessage->Data.OpenOn.ScreenType,
                       theMessage->Data.OpenOn.SizeToFit);
         break;

      case WI_REPORTICONVERIFY:
         if (theMessage->Flags & WI_ICONIFYOK)
            Iconify(theMessage->Icon,theMessage->Flags & WI_CHANGE);
         break;

      case WI_MAKECONTACT:
         if (theMessage->Data.Version.Maj > MAJLIBVERS ||
            (theMessage->Data.Version.Maj == MAJLIBVERS &&
             theMessage->Data.Version.Min >= MINLIBVERS))
         {
            theMessage->Action = WI_VERSIONOK;
            theMessage->Data.Version.Maj = MAJVERSION;
            theMessage->Data.Version.Min = MINVERSION;
         } else {
            theMessage->Action = WI_VERSIONBAD;
         }
         break;

      case WI_ENDICONIFY:
         AttemptEnd(theMessage);
         ReportMulti(WI_REPORTICONEND,NULL,NULL);
         EndItAll = TRUE;
         break;
   }
   return(EndItAll);
}
