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

#define INTUITION_PREFERENCES_H             /* don't need 'em */
#include <intuition/intuitionbase.h>
#include "wHandler.h"
#include "wSetup.h"
#include "wMenu.h"
#include <exec/interrupts.h>
#include <devices/input.h>
#include <libraries/dos.h>

static int MessageCount;       /* number of messages still not returned */

extern void wHandlerStub();
static struct Interrupt wIconInterrupt = /* the Interrupt needed to add a */
{                                         /*  handler to the input chain */
   {NULL, NULL, 0, 51, NULL},             /*  ln_Pri = 51 (before Intuition) */
   NULL,
   &wHandlerStub                         /* the handler to add */
};


extern struct wMenuItem OpenWindowMenu[];
extern void cOpenScreen(),FreeAllLists();

extern void aOpenWindow(),aCloseWindow();
extern void aOpenScreen(),aCloseScreen();
extern void aSetWindowTitles(),aWindowToFront(),aWindowToBack();
extern void aActivateWindow();
extern void aBuildSysRequest(),aFreeSysRequest(),aAutoRequest();

/*
 *  Storage for the old library vectors that are replaced by SetFunction
 */
long OldOpenWindow,OldCloseWindow,OldOpenScreen,OldCloseScreen;
long OldSetWindowTitles,OldWindowToFront,OldWindowToBack;
long OldActivateWindow,OldBuildSysRequest,OldFreeSysRequest,OldAutoRequest;

/*
 *  The data needed by the loader to set up the handler
 */
static struct HandlerData HandlerData =
{
   MAJVERSION,MINVERSION,
   NULL, NULL,
   &IntuitionBase, &GfxBase, &LayersBase, &SysBase, &DOSBase,
   &wIconInterrupt,
   &wMenu[0],&OpenWindowMenu[0],
   &DefaultIcon, &DefaultScreenIcon,
   &HiResCLICommand, &LoResCLICommand, &StackSize,
   &DefaultImage, &DefaultSelect, &DefaultMask,
   &DefaultScreenImage, &DefaultScreenSelect, &DefaultScreenMask,
   &DefaultFlags,
   &DefaultScreenFlags,
   &IgnoreScreen,
   &IconifyKey,&ActivateKey,
   &IconifyQuals,&IconifyDisquals,&IconifyChange,
   &ActivateQuals,
   &Colors[0],
   &cOpenScreen,&FreeAllLists,
   &aOpenWindow,&OldOpenWindow,
   &aCloseWindow,&OldCloseWindow,
   &aOpenScreen,&OldOpenScreen,
   &aCloseScreen,&OldCloseScreen,
   &aSetWindowTitles,&OldSetWindowTitles,
   &aWindowToFront,&OldWindowToFront,
   &aWindowToBack,&OldWindowToBack,
   &aActivateWindow,&OldActivateWindow,
   &aBuildSysRequest,&OldBuildSysRequest,
   &aFreeSysRequest,&OldFreeSysRequest,
   &aAutoRequest,&OldAutoRequest
};


extern struct MsgPort *CreatePort();
extern void DeletePort();
extern void wMain();
extern void ReplyMessages();


/*
 *  Setup()
 *
 *  This routine MUST be linked into the handler as the first routine.
 *  It is called by the loader to check version numbers, and to get
 *  the handler data structure.  It should compare the version number
 *  and return NULL if a mismatch occurs or a pointer to HandlerData
 *  if all is OK.
 *  
 *  Since the handler is started as a process, it will also be called
 *  as the process first starts up.  FirstCall is used to tell which
 *  invocation this is.
 *  
 *  When it is called as part of the process starting up
 *    Get our task pointer for use by the handler routines
 *    Create a port for icon messages
 *    And one for the window's common UserPort
 *    Get a signal for when the last window on a screen is closed
 *    If all of these were allocated
 *      Clear the close signal
 *      If the Real WB is open and we have a backdrop window on it
 *        Set and clear the menu strip (to get the NL-Deamon to change
 *          its pen number, in case it is going to)
 *        Signal the loader that all is OK so far
 *        Wait for the loader to finish setting up
 *        If we were not cancelled, call the main loop routine
 *        Get ready to singal OK
 *      Otherwise (something could not be set up) Get ready to signal CANCEL
 *    Reply to any messages in the UserPort or IconPort
 *    Free the ports that were allocated
 *    Signal the loader (either OK or CANCEL)
 */

struct HandlerData *Setup(MajLoadVers,MinLoadVers)
int MajLoadVers,MinLoadVers;
{
   ULONG theSignal;
   static int FirstCall = TRUE;
   struct HandlerData *theData = &HandlerData;
   
   if (FirstCall)
   {
      if (MajLoadVers < MAJLOADVERS ||
         (MajLoadVers == MAJLOADVERS && MinLoadVers < MINLOADVERS))
            theData = NULL;
      FirstCall = FALSE;
   } else {
      theData = NULL;
      IconTask  = FindTask(NULL);
      wIconPort = CreatePort(WICONPORT);
      wUserPort = CreatePort(NULL);
      CloseSigBit = AllocSignal(-ONE);
      if (wIconPort && wUserPort && CloseSigBit != -ONE)
      {
         CloseSignal = (ONE << CloseSigBit);
         SetSignal(CloseSignal,0L);
         if (RealWB && RealWB->BackDrop)
         {
            SetMenuStrip(RealWB->BackDrop,wMenu); /* for NL-Deamon */
            ClearMenuStrip(RealWB->BackDrop);
         }
         Signal(HandlerData.ParentTask,OKSIGNAL);
         theSignal = Wait(OKSIGNAL| CANCELSIGNAL);
         if ((theSignal & CANCELSIGNAL) == FALSE) wMain();
         theSignal = OKSIGNAL;
      } else theSignal = CANCELSIGNAL;
      Forbid();
      ReplyMessages();
      if (wIconPort) DeletePort(wIconPort);
      if (wUserPort) DeletePort(wUserPort);
      Signal(HandlerData.ParentTask,theSignal);
   }
   return(theData);
}


/*
 *  CreatePort()
 *
 *  Get a new port structure
 *    Allocate a signal for it
 *    If none could be allocated
 *      Free the structure
 *    Otherwise
 *      Set the port's name and type
 *      Set its signal type and signal task
 *      Add it to the port list if it has a name, otheriwse
 *        initialize its message list
 */

struct MsgPort *CreatePort(theName)
char *theName;
{
   struct MsgPort *thePort;

   if (NEWSTRUCT(MsgPort,thePort))
   {
      thePort->mp_SigBit = AllocSignal(-ONE);
      if (thePort->mp_SigBit == -ONE)
      {
         FREESTRUCT(MsgPort,thePort);
         thePort = NULL;
      } else {
         thePort->mp_Node.ln_Name = theName;
         thePort->mp_Node.ln_Type = NT_MESSAGE;
         thePort->mp_Flags = PA_SIGNAL;
         thePort->mp_SigTask = (struct Task *)FindTask(NULL);
         if (theName) AddPort(thePort); else NewList(&(thePort->mp_MsgList));
      }
   }
   return(thePort);
}


/*
 *  DeletePort()
 *
 *  If the port exists
 *    If it has a name, remove it frlom the system port list
 *    Remove its type and message list
 *    Free the port's signal
 *    Free the ports memory
 */

void DeletePort(thePort)
struct MsgPort *thePort;
{
   if (thePort)
   {
      if (thePort->mp_Node.ln_Name) RemPort(thePort);
      thePort->mp_Node.ln_Type = 0xFF;
      thePort->mp_MsgList.lh_Head = (struct Node *)-1L;
      FreeSignal(thePort->mp_SigBit);
      FREESTRUCT(MsgPort,thePort);
   }
}


/*
 *  ReportEvent()
 *
 *  If the icon has an IconPort to send messages to
 *    Get a new message structure
 *    Increment the number of messages yet to be replied
 *    Set the message's Window, Icon, Action and Flags fields
 *    Set up the data field for special cases
 *    Set the reply port to the IconPort and send the message
 */

void ReportEvent(theEvent,theIcon,theData)
ULONG theEvent;
WICONREF *theIcon;
APTR theData;
{
   struct wIconMessage *theMessage;

   if (theIcon->Icon.IconPort)
   {
      if (NEWSTRUCT(wIconMessage,theMessage))
      {
         MessageCount++;
         theMessage->Window = theIcon->Window;
         theMessage->Icon   = theIcon;
         theMessage->Action = theEvent;
         theMessage->Flags  = WI_ICONMESSAGE| WI_REPORT| WI_NOREPLY;
         switch(theEvent) 
         {
            case WI_REPORTACTIVATE:
            case WI_REPORTINACTIVE:
            case WI_REPORTNEWSCREEN:
            case WI_REPORTSCREENCLOSE:
            case WI_REPORTICONEND:
               theMessage->Data.DataPtr = theData;
               break;

            case WI_REPORTICONVERIFY:
               theMessage->Flags &= ~WI_ICONMESSAGE;
               break;

            case WI_REPORTMOVED:
               theMessage->Data.Position.x = theIcon->Icon.x;
               theMessage->Data.Position.y = theIcon->Icon.y;
               break;
         }
         theMessage->Message.mn_ReplyPort = wIconPort;
         theMessage->Message.mn_Length = sizeof(struct wIconMessage);
         PutMsg(theIcon->Icon.IconPort,theMessage);
      }
   }
}


/*
 *  ReportMulti()
 *
 *  If a screen was specified, use it, otherwise start at the top of the list
 *  While there are more screens to check
 *    For each icon on the screen
 *      If the icon wants to hear about this type of event, report it
 *    If a screen was specified, we're done, otherwise go on to the next one
 */

void ReportMulti(theEvent,theData,IconScreen)
ULONG theEvent;
APTR theData;
WSCREEN *IconScreen;
{
   WSCREEN *theScreen;
   WICONREF *theIcon,*nextIcon;
   
   Forbid();
   if (IconScreen) theScreen = IconScreen; else theScreen = FirstScreen;
   while (theScreen)
   {
      theIcon = theScreen->IconRef;
      while (theIcon)
      {
         nextIcon = theIcon->Next;
         if (theIcon->Icon.Report & theEvent)
            ReportEvent(theEvent,theIcon,theData);
         theIcon = nextIcon;
      }
      if (IconScreen) theScreen = NULL; else theScreen = theScreen->Next;
   }
   Permit();
}


/*
 *  wMain()
 *
 *  Set the Wait signal bits to CANCEL, close and the bits used by the ports
 *  While there is more to do
 *    Don't try to end wIconify yet
 *    Wait for one of the signals we're interested in
 *    While there are messages in the common wIconify window UserPort
 *      If the message type is CLOSEWINDOW ro NEWSIZE (fake messages
 *        sent to Intuition windows by wIconify), then just free the message
 *      Otherwise
 *        Make a copy of the message data and reply the original
 *        Do the event, and record whether we are supposed to end wIconify
 *    While ther are messages in the IconPort (from the handler or IconCalls)
 *      If the message is not an IconMessage being returned to us
 *        Do what the message wants, and record if we are supposed to end
 *      If the message is not supposed to be replied (it came from us)
 *        If the message is a REPORT message we generated, decrement the count
 *        If we are trying to end and there are no more outstanding messages
 *          that need to be returned, go ahead and try to end
 *        Free the message
 *      Otherwise reply to the message (it's from IconCalls)
 *    If the last window on a screen was closed, update the menus
 *    If the loader wants us to end, try to quit
 *    Do any pending mouse moves (since we're about to Wait anyway)
 *    Refresh the screens that need it
 *    If we are supposed to end
 *      Try to close all screens and windows
 *        If all are closed, make sure there are no outstanding messages
 *          that need to be replied first; if not, go ahead and quit
 */

static void wMain()
{
   int NotDone = TRUE;
   int EndItAll;
   ULONG WaitSignal = CANCELSIGNAL| CloseSignal;
   ULONG theSignal;
   struct IntuiMessage *inMessage,tmpMessage;
   struct wIconMessage *wiMessage;
   extern struct Node *GetMsg();

   if (wUserPort) WaitSignal |= (ONE << wUserPort->mp_SigBit);
   if (wIconPort) WaitSignal |= (ONE << wIconPort->mp_SigBit);
   while (NotDone)
   {
      EndItAll = FALSE;
      theSignal = Wait(WaitSignal | EndSignal);
      while (inMessage = (struct IntuiMessage *)GetMsg(wUserPort))
      {
         if (inMessage->Class & FREECLASS)
         {
            FREESTRUCT(IntuiMessage,inMessage);
         } else {
            tmpMessage = *inMessage; ReplyMsg(inMessage);
            EndItAll = DoEvent(&tmpMessage,EndItAll);
         }
      }
      while (wiMessage = (struct wIconMessage *)GetMsg(wIconPort))
      {
         if ((wiMessage->Flags & WI_ICONMESSAGE) == 0)
            EndItAll = DoIconEvent(wiMessage,EndItAll);
         if (wiMessage->Flags & WI_NOREPLY)
         {
            if (wiMessage->Flags & WI_REPORT) MessageCount--;
            if (EndSignal && MessageCount == 0) EndItAll = TRUE;
            FREESTRUCT(wIconMessage,wiMessage);
         } else {
            ReplyMsg(wiMessage);
         }
      }
      if (theSignal & CloseSignal) UpdateActiveMenu();
      if (theSignal & EndSignal)   EndItAll = TRUE;
      DoMouseMove();
      RefreshScreens();

      if (EndItAll)
         if (AttemptQuit())
            if (MessageCount == 0) NotDone = FALSE;
   }
}


/*
 *  ReplyMessages()
 *
 *  While there are more IntuiMessages in the UserPort
 *    If the message is to be freed, do so, otherwise reply to it
 *  While there are more IconMessages in the IconPort
 *    If the message is not to be replied, free it, otherwise reply to it
 */

static void ReplyMessages()
{
   struct IntuiMessage *inMessage;
   struct wIconMessage *wiMessage;
   extern struct Node *GetMsg();

   while (inMessage = (struct IntuiMessage *)GetMsg(wUserPort))
   {
      if (inMessage->Class & FREECLASS) FREESTRUCT(IntuiMessage,inMessage);
        else ReplyMsg(inMessage);
   }
   while (wiMessage = (struct wIconMessage *)GetMsg(wIconPort))
   {
      if (wiMessage->Flags & WI_NOREPLY) FREESTRUCT(wIconMessage,wiMessage);
        else wiMessage->Icon = NULL, ReplyMsg(wiMessage);
   }
}


/*
 *  AttemptEnd()
 *
 *  This routine is called when an ENDICONIFY message arrives.
 *  Return a pointer to the HandlerData for use by the loader
 *  Get the loader's task pointer from the message data pointer
 */

void AttemptEnd(theMessage)
struct wIconMessage *theMessage;
{
   theMessage->Icon = (WICONREF *) &HandlerData;
   HandlerData.ParentTask = theMessage->Data.DataPtr;
}
