/*
 *  WICONSETTER     A companion utility to wIconify.  wIconSetter allows
 *                  you to specify custom icons for windows ans screens
 *                  that normally use the default icons.
 *
 *  wIconHandler.c      The main Handler code.
 *
 *  Copyright 1990 by Davide P. Cervone, all rights reserved.
 *  You may use this code, provided this copyright notice is kept intact.
 */

#include "wIconHandler.h"

/*
 *  Variables and routines needed for traps via SetFunction()
 */

long OldOpenWindow;
long OldSetWindowTitles;
long OldOpenScreen;
extern void cOpenWindow();
extern void aOpenWindow();
extern void cSetWindowTitles();
extern void aSetWindowTitles();
extern void cOpenScreen();
extern void aOpenScreen();

/*
 *  Routines passed to the loader
 */

extern void GetProgramName();
extern int  PrefixMatch();
extern ICONWINDOW *FindIcon(); 

extern WICONREF *wIconOf();             /* Part of wIconCalls.o */


static ICONPROGRAM *FirstProgram;       /* The list of icons */
static ICONPROGRAM *ProgramAny;         /* the special ANY program */
static ICONDEFINE  *FirstDefine;        /* the list of defined icons */

struct SysBase *SysBase;

static char *IconProcess = ICONPROCNAME;    /* Our process name */
#define NOTICONIFY(p)       (stricmp(p,IconProcess) != 0)

#define TOUPPER(c)          (((c)>='a'&&(c)<='z')?(c)-'a'+'A':c)
#define MATCH(n1,n2)        (stricmp(n1,n2))
#define PREFIXMATCH(n1,n2)  ((n1) == NAME_ANY || PrefixMatch(n1,n2) == 0)
#define WPREFIXMATCH(n1,n2)\
   (((n1) == NAME_ANY && (n2) != SCREENICON) || PrefixMatch(n1,n2) == 0)


/*
 *  IconHandlerData is passed to the loader on startup.
 *  It is used to pass values to the loader and receive initialized
 *  values from the loader.
 */

struct IconHandlerInfo IconHandlerData =
{
   {                                            /* the MsgPort is pre-setup */
      {NULL,NULL, NT_MSGPORT, 0, PORTNAME},     /*  to include the name and */
      PA_IGNORE, 0, NULL,                       /*  type so that it can just */
      {NULL,NULL,NULL, 0,0}                     /*  be added to the port list */
   },                                           /*  so it can be found later */
   MAJVERS,MINVERS, MINLOADVERS,            /* version numbers */
   NULL,                                    /* the loaded segment code */
   
   /*
    *  The libraries needed by the handler
    */

   &IntuitionBase,
   &SysBase,
   
   /*
    *  The routines and variables trapped by the loader
    */

   &aOpenWindow,
   &OldOpenWindow,
   &aSetWindowTitles,
   &OldSetWindowTitles,
   &aOpenScreen,
   &OldOpenScreen,
   
   /*
    *  Data needed by the loader
    */
   &GetProgramName,
   &PrefixMatch,
   &FindIcon,
   &FirstDefine,
   &FirstProgram,
   &ProgramAny,
};


/*
 *  Setup()
 *
 *  This routine MUST be linked into the wIconSetter-Handler executable 
 *  as the first routine, so that the loader can find it.
 *  It should check the version number for compatibility with the loader,
 *  and should return NULL for an error, or the pointer to the shared
 *  data structure if everything is OK.
 */

struct IconHandlerInfo *Setup(version)
int version;
{
   if (version < MINLOADVERS) return(NULL);
   return(&IconHandlerData);
}


/*
 *  PrefixMatch()
 *
 *  Case-insensitive prefix match.  NULL pointers and empty strings
 *  only match themselves, and are not considered prefixes to any string.
 *  The special SCREENICON value only matches itself.
 *
 *  Return <0 if the first is smaller than the second,
 *         =0 if the first is a prefix of the second,
 *         >1 if the first is larger than the second.
 */

int PrefixMatch(s1,s2)
char *s1,*s2;
{
   int match = -1;
   
   if (s1 == SCREENICON || s2 == SCREENICON) match = (s1 == s2)? 0: 1;
   else if (s1)
   {
      if (s2)
      {
         if (*s1)
         {
            while (TOUPPER(*s1) == TOUPPER(*s2) && *s2 != 0) s1++,s2++;
            if (*s1 == 0) match = 0; else match = *s1 - *s2;
         } else if (*s2 == 0) match = 0;
      } else match = 1;
   } else if (s2 == NULL) match = 0;
   return(match);
}



/*
 *  FindIconWindow()
 *
 *  Start with the first window of the given screen
 *  While there are more windows to look at
 *    If the window matches the name, return the window pointer
 *    Otherwise go on to the next one
 *  Return NULL if no match
 */

static ICONWINDOW *FindIconWindow(IScreen,Name)
ICONSCREEN *IScreen;
char *Name;
{
   ICONWINDOW *IWindow = IScreen->Window;

   while (IWindow)
   {
      if (WPREFIXMATCH(IWindow->Name,Name)) return(IWindow);
      IWindow = IWindow->Next;
   }
   return(NULL);
}


/*
 *  FindIconScreen()
 *
 *  While there are more screen to look at
 *    If the screen matches the name, return the screen pointer
 *    Otherwise go on to the next one
 *  Return NULL if no match
 */

static ICONSCREEN *FindIconScreen(IScreen,Name)
ICONSCREEN *IScreen;
char *Name;
{
   while (IScreen)
   {
      if (PREFIXMATCH(IScreen->Name,Name)) return(IScreen);
      IScreen = IScreen->Next;
   }
   return(NULL);
}


/*
 *  FindIconProgram()
 *
 *  While there are more programs to look at
 *    If the program name is ANY, always match, otherwise
 *      compare the program against the given name
 *    If there was a match, return the program
 *    Otherwise move down to the next level of the tree
 *  Return ProgramAny if nothing else matched
 */

static ICONPROGRAM *FindIconProgram(IProgram,Name)
ICONPROGRAM *IProgram;
char *Name;
{
   int result;

   while (IProgram)
   {
      if (IProgram->Name == NAME_ANY) result = 0;
         else result = MATCH(IProgram->Name,Name);
      if (result == 0) return(IProgram); else
      if (result < 0) IProgram = IProgram->Prev; else IProgram = IProgram->Next;
   }
   return(ProgramAny);
}



/*
 *  FindIcon()
 *
 *  Find the program structure for the given program name
 *  If found,
 *    Look through the program's list of screens
 *    While there are more screens to check and we haven't found a window
 *      Find the next matching screen
 *      If found,
 *        Look for the window on that screen and go on to the next screen
 *  return the window (or NULL if none found)
 */

ICONWINDOW *FindIcon(Program,theScreen,wTitle)
char *Program;
struct Screen *theScreen;
char *wTitle;
{
   ICONPROGRAM *IProgram;
   ICONSCREEN  *IScreen;
   ICONWINDOW  *IWindow = NULL;
   
   IProgram = FindIconProgram(FirstProgram,Program);
   if (IProgram)
   {
      IScreen = IProgram->Screen;
      while (IScreen && IWindow == NULL)
      {
         IScreen = FindIconScreen(IScreen,theScreen->DefaultTitle);
         if (IScreen)
            IWindow = FindIconWindow(IScreen,wTitle),
            IScreen = IScreen->Next;
      }
   }
   return(IWindow);
}


/*
 *  StripPath()
 *
 *  Start at the end of the string
 *  While there are more characters to look at,
 *    Get the preceding character
 *    If it is '/' or ':' cancel the loop, otherwise back up a character
 *  Clip the length of the name, if necessary
 *  Terminate the Name string
 *  Copy the name portion of the string into the Name buffer
 */

static void StripPath(Name,s,len)
char Name[MAXNAME];
char *s;
short len;
{
   short i = 0;
   char c;

   s += len;
   while (len)
   {
      c = *(s-1);
      if (c == '/' || c == ':') len = 0;
         else len--, s--, i++;
   }
   if (i >= MAXNAME) i = MAXNAME-1;
   Name[i] = 0;
   while (i--) Name[i] = s[i];
}


/*
 *  GetProgramName()
 *
 *  If we have a task to look at,
 *    If the task is a process
 *      If the process has a task number and a CLI structure
 *        Get the CLI structure, and use its current command name and length
 *    If we haven't found the name yet,
 *      Get the name from the task structure, and find its length
 *    Strip the path portion off the name
 *  Otherwise clear the name buffer
 */

void GetProgramName(Name,theTask)
char Name[MAXNAME];
struct Process *theTask;
{
   struct CommandLineInterface *theCLI;
   char *s = NULL;
   short len;

   if (theTask)
   {
      if (theTask->pr_Task.tc_Node.ln_Type == NT_PROCESS)
      {
         if (theTask->pr_TaskNum && theTask->pr_CLI)
         {
            theCLI = (struct CommandLineInterface *)BADDR(theTask->pr_CLI);
            if (theCLI->cli_CommandName)
               s = (char *)(BADDR(theCLI->cli_CommandName)), len = *s++;
         }
      }
      if (s == NULL)
      {
         s = theTask->pr_Task.tc_Node.ln_Name;
         len = strlen(s);
      }
      StripPath(Name,s,len);
   } else {
      Name[0] = 0;
   }
}


/*
 *  HandleWindow()
 *
 *  Get the name of the current process
 *  If it is not the wIconify-Handler process
 *    Look through the icon list for the given program/screen/window
 *    If found
 *      Get a copy of the icon (so we can modify it)
 *      Remove the Autoiconify flag, if any (it is wIconSetter private)
 *      Get the current icon position (if it exists)
 *      Set the window to have the (modified) icon
 *      If the original icon had the AUTOICONIFY flag and we are supposed
 *        to do autoiconify, then iconify the window
 */

static void HandleWindow(theWindow,DoAuto)
struct Window *theWindow;
int DoAuto;
{
   char Program[MAXNAME];
   ICONWINDOW *theIcon;
   WICON tmpIcon;

   GetProgramName(Program,FindTask(NULL));
   if (NOTICONIFY(Program))
   {
      theIcon = FindIcon(Program,theWindow->WScreen,theWindow->Title);
      if (theIcon)
      {
         tmpIcon = theIcon->Icon;
         tmpIcon.Flags &= ~WI_AUTOICONIFY;
         wIconXY(wIconOf(theWindow),&(tmpIcon.x),&(tmpIcon.y));
         wSetIcon(theWindow,&tmpIcon);
         if ((theIcon->Icon.Flags & WI_AUTOICONIFY) && DoAuto)
            wIconify(theWindow);
      }
   }
}


/*
 *  cOpenWindow()
 *
 *  If the window openned successfully,
 *    Check to see if it needs an icon.
 */

void cOpenWindow(theWindow)
struct Window *theWindow;
{
   if (theWindow) HandleWindow(theWindow,TRUE);
}


/*
 *  cSetWindowTitles()
 *
 *  If a window is specified and it is getting a new title,
 *    Check if the window with the new title should get an icon.
 */

void cSetWindowTitles(theWindow,wTitle,sTitle)
struct Window *theWindow;
UBYTE *wTitle,*sTitle;
{
   if (theWindow && wTitle != (UBYTE *)-ONE) HandleWindow(theWindow,FALSE);
}


/*
 *  cOpenScreen()
 *
 *  If the screen has a window (ie, wIconify added its backdrop window
 *    to it already)
 *    Get the name of the current task
 *    If it is not the wIconify-Handler process itself
 *      Look through the icon list for this program/screen
 *      If it needs an icon
 *        Get a copy of the screen icon for modification
 *        Clear the AUTOICONIFY flag (it is wIconSetter private)
 *        Set the screen to have the given icon
 *        If the original icon has the AUTOICONIFY flag,
 *          Iconify the screen
 */

void cOpenScreen(theScreen)
struct Screen *theScreen;
{
   char Program[MAXNAME];
   ICONWINDOW *theIcon;
   WICON tmpIcon;

   if (theScreen && theScreen->FirstWindow)
   {
      GetProgramName(Program,FindTask(NULL));
      if (NOTICONIFY(Program))
      {
         theIcon = FindIcon(Program,theScreen,SCREENICON);
         if (theIcon)
         {
            tmpIcon = theIcon->Icon;
            tmpIcon.Flags &= ~WI_AUTOICONIFY;
            wSetScreenIcon(theScreen,&tmpIcon);
            if ((theIcon->Icon.Flags & WI_AUTOICONIFY))
               wIconifyScreen(theScreen);
         }
      }
   }
}
