/*
 *  wKeys-Handler.c   Input Handler for wKeyw, which moves and activates 
 *                    windows and screensin response to keystrokes
 *
 *              Copyright (c) 1987,1988 by Davide P. Cervone
 *  You may use this code provided this copyright notice is left intact.
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <devices/inputevent.h>
#include <intuition/intuitionbase.h>

#include "wKeys.h"

static char *program = "wKeys-Handler";
static int   version = 3;
static char *date    = "January 1988";
static char *author  = "Copyright (c) 1987,1988 by Davide P. Cervone";

extern struct Layer *WhichLayer();
extern struct IntuiMessage *GetMsg();
extern struct IntuiMessage *AllocMem();
extern void myHandlerStub();

#define WINDOW(layer)   ((struct Window *)((layer)->Window))

#define SCREENTOP\
   (theScreen->TopEdge << ((theScreen->ViewPort.Modes & LACE)? 0: 1))

#define IMSIZE          ((ULONG)sizeof(struct IntuiMessage))
#define NEWMESSAGE(m)   (m = AllocMem(IMSIZE,MEMF_PUBLIC | MEMF_CLEAR))
#define FREEMESSAGE(m)  FreeMem(m,IMSIZE);

struct IntuitionBase *IntuitionBase = NULL;
struct LayersBase    *LayersBase    = NULL;
struct SysBase       *SysBase       = NULL;

static struct HotKey *Key = NULL;
static int KeyCount = 0;
static struct MsgPort *ReplyPort = NULL;

/*
 *  Setup()
 *
 *  wKeys calls LoadSeg() to get this handler into memory.  The segment
 *  that it gets points to this routine.  wKeys calls Setup() and 
 *  passes the IntuitionBase, LayersBase and SysBase pointers that it
 *  has initialized (with OpenLibrary()).  wKeys also passes the KeyArray
 *  and KeyCount which hold the key bindings.  Setup returns a pointer to
 *  the actual input handler, which wKeys installs, and sets the version
 *  number so that hotKey can report the handler's version.
 */

long Setup(Ibase,Lbase,Sbase,theKeys,Count,Port,VersionPtr)
struct IntuitionBase *Ibase;
struct LayersBase *Lbase;
struct SysBase *Sbase;
struct HotKey *theKeys;
struct MsgPort *Port;
int *VersionPtr;
int Count;
{
   IntuitionBase = Ibase;
   LayersBase = Lbase;
   SysBase = Sbase;
   Key = theKeys;
   KeyCount = Count;
   ReplyPort = Port;
   *VersionPtr = version;
   return((long) &myHandlerStub);
}

/*
 *  TopWindow()
 *
 *  Find the top window of the specified screen.  Start at the top layer of
 *  the screen and move backward as long as the layer exists and has no
 *  window connected to it.  Return the window associated with the final 
 *  layer, if any.
 */

static struct Window *TopWindow(theScreen)
struct Screen *theScreen;
{
   struct Window *theWindow = NULL;
   struct Layer *theLayer;
   
   theLayer = theScreen->LayerInfo.top_layer;
   while (theLayer && WINDOW(theLayer) == NULL) theLayer = theLayer->back;
   if (theLayer) theWindow = WINDOW(theLayer);
   return(theWindow);
}

/*
 *  BottomWindow()
 *
 *  Find the bottom window of the specified screen.  Start at the top layer
 *  and as long as the layer exists, go to the next layer back.  If the
 *  layer has a window attached, consider that to be the bottom window until
 *  a lower one is found.
 */

static struct Window *BottomWindow(theScreen)
struct Screen *theScreen;
{
   struct Window *theWindow = NULL;
   struct Layer *theLayer = theScreen->LayerInfo.top_layer;
   
   while (theLayer)
   {
      if (WINDOW(theLayer) && isIconified(WINDOW(theLayer)) == FALSE)
         theWindow = WINDOW(theLayer);
      theLayer = theLayer->back;
   }
   return(theWindow);
}

/*
 *  NextWindow()
 *
 *  Find the next window below the specified window (wrap arround to the top
 *  if the window is the bottom one).  Start with the window's layer and go
 *  back until a layer with a window is found, or no more layers exist.  If
 *  a window was found, return it, otherwise, use the top window.
 */

static struct Window *NextWindow(theWindow)
struct Window *theWindow;
{
   struct Layer  *theLayer  = theWindow->WLayer;
   
   do
      theLayer = theLayer->back;
   while (theLayer && (WINDOW(theLayer) == NULL ||
          isIconified(WINDOW(theLayer))));
   if (theLayer)
      theWindow = WINDOW(theLayer);
     else
      theWindow = TopWindow(theWindow->WScreen);
   return(theWindow);
}

/*
 *  PreviousWindow()
 *
 *  Find the window that is on top of the specified window (or NULL if there
 *  are no windows above it).  Start with the window's layer, and move to
 *  the layer in front until a layer with a (different) window is found, or
 *  until no more layers exist.  If a window was found, return it, otherwise
 *  return NULL.
 */
 
static struct Window *PreviousWindow(theWindow)
struct Window *theWindow;
{
   struct Layer  *theLayer  = theWindow->WLayer;
   
   do
      theLayer = theLayer->front;
   while (theLayer && (WINDOW(theLayer) == NULL ||
                       WINDOW(theLayer) == theWindow));
   if (theLayer)
      theWindow = WINDOW(theLayer);
     else
      theWindow = NULL;
   return(theWindow);
}

/*
 *  BackScreenToFront()
 *
 *  Bring the bottom-most screen to the top, and activate its top window.
 *  While there is a screen following the current one, move the the next screen.
 *  Bring that screen to the front and find its top window.  If one was found,
 *  activate the window.
 */

static void BackScreenToFront()
{
   struct Screen *theScreen = IntuitionBase->FirstScreen;
   struct Window *theWindow;
   
   if (theScreen)
   {
      while (theScreen->NextScreen) theScreen = theScreen->NextScreen;
      ScreenToFront(theScreen);
      theWindow = TopWindow(theScreen);
      if (theWindow) ActivateWindow(theWindow);
   }
}

/*
 *  FrontScreenToBack()
 *
 *  Move the top screen to the back and activate the top window on the new
 *  top screen.
 */

static void FrontScreenToBack()
{
   struct Screen *theScreen = IntuitionBase->FirstScreen;
   struct Window *theWindow;

   if (theScreen)
   {
      ScreenToBack(theScreen);
      theScreen = IntuitionBase->FirstScreen;
      if (theScreen)
      {
         theWindow = TopWindow(theScreen);
         if (theWindow) ActivateWindow(theWindow);
      }
   }
}

/*
 *  ActivatePreviousWindow()
 *
 *  Get the window previous to the current window (if none, then get the
 *  bottom window in the active screen), and activate that window.
 */

static void ActivatePreviousWindow()
{
   struct Window *theWindow = PreviousWindow(IntuitionBase->ActiveWindow);
   
   if (theWindow == NULL) theWindow = BottomWindow(IntuitionBase->ActiveScreen);
   if (theWindow) ActivateWindow(theWindow);
}

/*
 *  ActivateNextWindow()
 *
 *  Get the window below the current window and activate it.
 */

static void ActivateNextWindow()
{
   struct Window *theWindow = NextWindow(IntuitionBase->ActiveWindow);
   
   if (theWindow) ActivateWindow(theWindow);
}

/*
 *  CurrentWindowToBack()
 *
 *  Send the current window to the back of the list.
 */

static void CurrentWindowToBack()
{
   struct Window *theWindow = IntuitionBase->ActiveWindow;
   
   if (theWindow && (theWindow->Flags & BACKDROP) == 0)
      WindowToBack(theWindow);
}

/*
 *  CurrentWindowToFront()
 *
 *  Send the current window to the top of the list.
 */

static void CurrentWindowToFront()
{
   struct Window *theWindow = IntuitionBase->ActiveWindow;
   
   if (theWindow) WindowToFront(theWindow);
}

/*
 *  BackWindowToFront()
 *
 *  Move the bottom window to the top and activate it.  Get the bottom window,
 *  skipping over backdrop windows.  If one is found, bring it to the front,
 *  and activate it.
 */

static void BackWindowToFront()
{
   struct Window *theWindow = BottomWindow(IntuitionBase->ActiveScreen);
   
   if (theWindow)
   {
      while (theWindow && (theWindow->Flags & BACKDROP))
         theWindow = PreviousWindow(theWindow);
      if (theWindow)
      {
         WindowToFront(theWindow);
         ActivateWindow(theWindow);
      }
   }
}

/*
 *  FrontWindowToBack()
 *
 *  Move the top window to the back, and activate the new top window.
 *  Get the top window, and then the window following it.  Send the top window
 *  to the back, and activate the next window.
 */

static void FrontWindowToBack()
{
   struct Window *theWindow  = TopWindow(IntuitionBase->ActiveScreen);
   struct Window *nextWindow = NextWindow(theWindow);
   
   if (theWindow && (theWindow->Flags & BACKDROP) == 0)
   {
      WindowToBack(theWindow);
      if (nextWindow) ActivateWindow(nextWindow);
   }
}

/*
 *  CloseCurrentWindow()
 *
 *  If the current window has the CLOSEWINDOW IDCMP flag, then 
 *  send it a CLOSEWINDOW IDCMP message.  Unfortunately, the workbench
 *  has the CLOSWINDOW flag set, and it crashes if you send it a close
 *  message; therefore, CloseCurrentWindow will not sent a CLOSWINDOW
 *  event to a BACKDROP window.  This seemed the easiest way to rule out
 *  the workbench without too much overhead.
 *
 *  The message will be replied to the ReplyPort, which is checked in the 
 *  input handler.  Any messages that get returned are freed.
 */

static void CloseCurrentWindow()
{
   struct Window *theWindow = IntuitionBase->ActiveWindow;
   struct IntuiMessage *theMessage;
   
   if (theWindow && (theWindow->IDCMPFlags & CLOSEWINDOW) && 
      (theWindow->Flags & BACKDROP) == 0)
   {
      if (NEWMESSAGE(theMessage))
      {
         theMessage->Class = CLOSEWINDOW;
         theMessage->IDCMPWindow = theWindow;
         theMessage->ExecMessage.mn_ReplyPort = ReplyPort;
         PutMsg(theWindow->UserPort,theMessage);
      }
   }
}

/*
 *  WindowToIcon()
 *
 *  Iconify the current window if wIconify is running.
 */

static void WindowToIcon()
{
   struct Window *theWindow = IntuitionBase->ActiveWindow;
   
   if (theWindow) wIconifyWindow(theWindow);
}

/*
 *  IconToWindow()
 *
 *  Restore the selected wIconify icon to a window.  The workbench must
 *  be the active window, and wIconify must be running, and a window icon
 *  must be selected.
 */
 
static void IconToWindow()
{
   wRestoreWindow(NULL);
}

/*
 *  SelectNextIcon()
 *
 *  Tells wIconify to select the next window icon on the workbench.  If no
 *  icon is selected, then select the first icon.
 */

static void SelectNextIcon()
{
   wNextIcon();
}

/*
 *  Array of functions that perform the different actions associated with 
 *  the hot-keys.  These are called by the handler routine.
 */

typedef void (*FUNCTION)();

static FUNCTION Action[] =
{
   NULL,
   &BackScreenToFront,
   &FrontScreenToBack,
   &ActivatePreviousWindow,
   &ActivateNextWindow,
   &CurrentWindowToBack,
   &CurrentWindowToFront,
   &BackWindowToFront,
   &FrontWindowToBack,
   &CloseCurrentWindow,
   &WindowToIcon,
   &IconToWindow,
   &SelectNextIcon
};

/*
 *  myHandler()
 *
 *  This is the input handler.  
 *  First check if there are any returned CLOSEWINDOW events, then,
 *  For each event in the event list:
 *  If the event is a raw key event, then
 *    make the KeyCode longword for that event's code and qualifier,
 *    binary search the Key[] array for a matching entry (only consider
 *      the qualifiers specified by the KeyMask).  Since most keys pressed
 *      will NOT match a hot-key, we want the search to be as fast as 
 *      possible, so we use a binary search rather than a linear search.
 *    set NoHotKey if the key is not a hot key.
 *  if the key was not a hot key,
 *    go on to the next key
 *   otherwise,
 *    perform the function for the specified hot key,
 *    remove the hot key from the event list.
 *
 *  When all the events have been checked, return the event list so that
 *  Intuition can do its thing.
 */

struct InputEvent *myHandler(EventList,data)
struct InputEvent *EventList;
APTR data;
{
   register struct InputEvent **EventPtr = &EventList;
   register struct InputEvent *theEvent;
   register long   theKey;
   register short  Num,Min,Max;
   register long   NoHotKey;
   struct IntuiMessage *theMessage;

   while (theMessage = GetMsg(ReplyPort)) FREEMESSAGE(theMessage);

   Forbid();
   while((theEvent = *EventPtr) != NULL)
   {
      NoHotKey = TRUE;
      if (theEvent->ie_Class == IECLASS_RAWKEY)
      {
         theKey = KEY(theEvent);
         Max = KeyCount; Min = -1;
         while ((Num = (Min + Max) >> 1) != Min &&
                (NoHotKey = (theKey & Key[Num].hk_KeyMask) -
                             Key[Num].hk_KeyCode) != 0)
            if (NoHotKey > 0) Min = Num; else Max = Num;
      }
      if (NoHotKey)
      {
         EventPtr = &(theEvent->ie_NextEvent);
      } else {
         (*(Action[Key[Num].hk_Action]))();
         *EventPtr = theEvent->ie_NextEvent;
      }
   }
   Permit();
   return(EventList);
}
