/***************************************************************************/
/*									   */
/* SmartIcon, release 1.0 - An Intuition object iconifier for the Amiga    */
/* Copyright (c) 1988 Gauthier H. Groult                                   */
/*									   */
/* Written in January 1988 by Gauthier H. Groult			   */
/*			      33, Boulevard Saint Denis,		   */
/*			      92400 Courbevoie				   */
/*			      France - Europe				   */
/*			      Tel: (16) 1 47 89 09 54                      */
/*			      email: mcvax!inria!litp!germinal!groult	   */
/*									   */
/* This source code is not in public domain, please do not distribute.	   */
/* The binary program is shareaware. Read docs files for details.	   */
/*									   */
/***************************************************************************/


/*									   */
/* SmartIcon Tech Notes.						   */
/*									   */
/* The general techniques used in the program are the following:	   */
/*									   */
/*    ° The program keeps track of what as been patched trouch a linked    */
/*	list of "IconGadgets". The structure IconGadget is a superset      */
/*	of the Gadget structure. The added fields contains the information */
/*	needed to reset things when cleaning up.			   */
/*									   */
/*    ° The windows that are already opened when the program starts are    */
/*	patched by scanning the Intuition window list.			   */
/*									   */
/*    ° The windows which open *after* the program as been laoded are	   */
/*	patched trough a rom call trap to OpenWindow().                    */
/*									   */
/*    ° The windows that close *while* the program is running are	   */
/*	un-patched trough a rom call trap to CloseWindow().                */
/*									   */
/*    ° The icon gadgets selection for any window is detected to a rom	   */
/*	call trap to PutMsg().                                             */
/*									   */
/*    ° For every icon created, a program capable of un-iconifying the	   */
/*	window is written to the ram disk. This program is contained in    */
/*	this code, under its binary form.				   */
/*									   */
/*    ° The revealing program needs to have a pointer on the window it	   */
/*	must reveal. This is given to it trough the "comment" field of     */
/*	the window's icon.                                                 */
/*									   */
/*    ° The revealing program self-destroys it's ram-disk file before      */
/*	exiting to keep things clean.					   */
/*									   */
/*    ° The windows are hidden only by resizing and moving their layers.   */
/*									   */

#include "exec/types.h"
#include "graphics/gfx.h"
#include "hardware/dmabits.h"
#include "hardware/custom.h"
#include "hardware/blit.h"
#include "graphics/gfxmacros.h"
#include "graphics/copper.h"
#include "graphics/view.h"
#include "graphics/gels.h"
#include "graphics/regions.h"
#include "graphics/clip.h"
#include "exec/exec.h"
#include "graphics/text.h"
#include "graphics/gfxbase.h"
#include "graphics/layers.h"
#include "graphics/clip.h"
#include "intuition/intuition.h"
#include "intuition/intuitionbase.h"
#include "libraries/dos.h"
#include "workbench/workbench.h"
#include "string.h"
/*
#include "proto/exec.h"
#include "proto/intuition.h"
#include "proto/dos.h"
*/
#define  ICONGADGET  11366
#define  MAXFILELEN  13

struct	 IconGadget
   {
   struct   IconGadget	*NextIGadget;
   struct   Gadget	*IconGadget, *DepthGadget;
   struct   Window	*Window;
   struct   MsgPort	*ReelPort;
   ULONG    Flags;
   };

BOOL	 HooksSet;
ULONG	 IconEvent;
ULONG	 IconBase;
struct	 GfxBase	*GfxBase;
struct	 IntuitionBase	*IntuitionBase;
struct	 LayersBase	*LayersBase;
struct	 MsgPort	*IconPort;
struct	 IconGadget	AddedGadgets;
struct	 Task		*IconTask;
struct	 Window 	*theSelectedWindow;
struct	 FileHandle	*file;

extern	 UBYTE	  RevealCode[];
extern	 USHORT   RevealCodeSize;
extern	 USHORT   IconImageData[], theWindowIData[];
extern	 VOID	  SetHooks(), CleanHooks(), DisplayWindow();

struct Image IconImage =
   {
   -14,0,
   51,10,
   2,
   IconImageData,
   0x0003,0x0000,
   NULL
   };

struct	 Gadget   theIconGadget =
   {
   NULL,
   -36, 0, 17, 10,
   GADGIMAGE | GRELRIGHT,
   RELVERIFY,
   BOOLGADGET,
   (APTR)&IconImage,
   NULL,
   NULL,
   NULL,
   NULL,
   ICONGADGET,
   NULL
   };

struct Image theWindowImage = {
   0,0,
   55,20,
   2,
   &theWindowIData[0],
   3,0
   };

struct DiskObject theWindowIcon = {
   WB_DISKMAGIC,
   WB_DISKVERSION,

   /* Gadget Structure */
   NULL,		    /* Ptr to next gadget */
   0,0, 		    /* Leftedge, Topedge */
   55,20,		    /* Width, Height */
   GADGHBOX|GADGIMAGE,	    /* Flags */
   RELVERIFY|GADGIMMEDIATE, /* Activation */
   BOOLGADGET,		    /* Type */
   (APTR)&theWindowImage,   /* Render */
   NULL,		    /* Select Render */
   NULL,		    /* Text */
   NULL,NULL,NULL,NULL,     /* Exclude, Special, ID, UserData */

   WBTOOL,		    /* WBObject type */
   NULL,		    /* Default tool */
   NULL,		    /* Tool Types */
   NO_ICON_POSITION,	    /* Current X */
   NO_ICON_POSITION,	    /* Current Y */
   NULL,NULL,NULL,	    /* Drawer, ToolWindow, Stack */
   };

VOID	 main(), Cleanup(), DepthGadgetsHNone(), Expurge(),
	 AddIconGadget(), HideWindow(),  RevealWindow(), Recover();
VOID	 MyOpenWindow(), MyCloseWindow(), MyPutMsg();
struct	 IconGadget  *AllocIGadget(), *FindIGadget();

/* Main() calls the initialisation functions (the startup window and the   */
/* library hooks); scans the Intuition window list to patch all depth	   */
/* gadgets for already opened windows; and wait forever for icon messages. */

VOID
main(argc, argv)
UBYTE argc, **argv;
{
   UBYTE    mystring[80], filename[MAXFILELEN+10], comment[20];
   ULONG    mask, IconPortMsg;
   struct   Screen	   *screen;
   struct   Window	   *window, *iwindow;
   struct   IntuiMessage   *message;

   GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0);
   if (!GfxBase) Cleanup(10);

   IntuitionBase = (struct IntuitionBase *)
		   OpenLibrary("intuition.library", 0);
   if (!IntuitionBase) Cleanup(11);

   LayersBase = (struct LayersBase *)OpenLibrary("layers.library", 0);
   if (!LayersBase) Cleanup(12);

   IconBase = OpenLibrary("icon.library",0);
   if (!IconBase) Cleanup(13);

   if (strcmp(argv[1],"-f")) DisplayWindow();

   IconTask = (struct Task *)FindTask(0);

   IconEvent = AllocSignal(-1);
   if (IconEvent ==  -1) Cleanup(21);

   IconPort = (struct MsgPort *)CreatePort(0, 0); /* The message port for */
   if (!IconPort) Cleanup(22);                    /* where icon msgs are  */
   IconPortMsg = 1 << IconPort->mp_SigBit;	  /* posted.		  */

   SetHooks();
   HooksSet = TRUE;


   screen = IntuitionBase->FirstScreen; 	  /* First system screen  */
   while (screen)
	 {
	 window = screen->FirstWindow;		  /* First system window  */
	 while (window)
	       {
	       if (window->Title && (window->Flags & WINDOWDEPTH))
		  {
		  AddIconGadget(window);
		  }
	       window = window->NextWindow;
	       }
	 screen = screen->NextScreen;
	 }

   for(;;)
   {
   mask = Wait(SIGBREAKF_CTRL_C | IconPortMsg | IconEvent);

   if (mask & SIGBREAKF_CTRL_C)
      Cleanup(0);

   if (mask & IconEvent)
      while (message=(struct IntuiMessage *)GetMsg(IconPort))
	    ReplyMsg(message);

   if (mask & IconEvent)
      {
      Forbid();
      iwindow = theSelectedWindow;
      HideWindow(iwindow->WScreen, iwindow);

      strcpy(mystring, iwindow->Title);
      mystring[MAXFILELEN]=0;
      Expurge(mystring);
      sprintf(filename, "ram:%s", mystring);

      if (!(file = (struct FileHandle *)Open(filename, MODE_NEWFILE)))
	 {
	 DisplayBeep(NULL);
	 RevealWindow(iwindow->WScreen, iwindow);
	 }
      else
	 {
	 Write(file, RevealCode, RevealCodeSize);
	 Close(file);
	 sprintf(comment, "%ld", iwindow);
	 if (!SetComment(filename, comment)) Recover(filename, iwindow);
	 else if (!PutDiskObject(filename, &theWindowIcon))
		 Recover(filename, iwindow);
	 }
      Permit();
      }
   }
}

/* Expurge() retrieves all special caracters used by AmigaDos from      */
/* a string. This is used to purge window titles, for they are used as	*/
/* the file name for the icon.						*/

VOID
Expurge(text)
UBYTE *text;
{
   while (*text)
	 {
	 if (*text == ':' || *text == '/' || *text == ' ')
	    strcpy(text, text+1);
	 else text++;
	 }
}

/* This function is called when attempting to iconify fails. It just	*/
/* undoes what as already be done. Its sole purpose as a function is	*/
/* to evitate a "goto".                                                 */

VOID
Recover(fname, window)
UBYTE	 *fname;
struct	 Window   *window;
{
   DeleteFile(fname);
   DisplayBeep(NULL);
   RevealWindow(window->WScreen, window);
}

/* This function adds the new depth gadgets to a window. Dynamic     */
/* allocation is required as we use a linked list (nodes) for the    */
/* modified windows. Instead of just allocating and filling the      */
/* gadget structyre, we CopyMemQuick() it for speed.                 */

VOID
AddIconGadget(window)
struct	 Window   *window;
{
   struct   IconGadget	   *node;

   Forbid();
   node =  AllocIGadget();
   node->Window = window;
   DepthGadgetsHNone(node);

   CopyMemQuick(&theIconGadget, node->IconGadget, sizeof(struct Gadget));
   if (window->Flags & GIMMEZEROZERO)
      node->IconGadget->GadgetType |= GZZGADGET;

   AddGadget(window, node->IconGadget, 0);
   RefreshGList(window->FirstGadget, window, NULL, 1);
   if (!window->UserPort) window->UserPort = IconPort;
   ModifyIDCMP(window, window->IDCMPFlags);
   Permit();
}

/* AllocIGadget(): allocates an IconGadget. IconGadgets are actually a  */
/* superset of the gadget structure. The linked list of allocated	*/
/* IconGadgets is the only way we know about which windows have been	*/
/* modified.								*/

struct	 IconGadget
*AllocIGadget()
{
   struct   IconGadget	*node;

   node = (struct IconGadget *)
	  AllocMem(sizeof(struct IconGadget), MEMF_CLEAR);

   node->NextIGadget = AddedGadgets.NextIGadget;
   AddedGadgets.NextIGadget = node;

   node->IconGadget = (struct Gadget *)
		      AllocMem(sizeof(struct Gadget), MEMF_CLEAR);
   return(node);
}

/* DepthGadgetsHNone() changes the position and size of the original depth */
/* gadgets so they can match the new imagery. The name of this function    */
/* doesn't discribe it's meaning, it just there for historical reasons.    */

VOID
DepthGadgetsHNone(node)
struct	 IconGadget  *node;
{
   struct   Gadget   *gadget;
   struct   Window   *window;

   window = node->Window;
   gadget = window->FirstGadget;
   while (gadget)
	 {
	 if (!gadget->TopEdge)
	    if (gadget->LeftEdge == -28)
	       {
	       node->DepthGadget = gadget;
	       gadget->LeftEdge = -20;
	       gadget->Width = 18;
	       }
	    else if (gadget->LeftEdge == -52) gadget->Width = 17;
	 gadget = gadget->NextGadget;
	 }
}

/* HideWindow().
/* This is the key to window iconification. This call removes the window   */
/* from the display without releasing any of it's attributes. This is      */
/* sort of a visual trick: the layer of the window is made small (one      */
/* pixel), and is moved to the lower rightmost position of the display.    */
/* Thus the window is invisible, but it keeps it's rastport, bitmap,       */
/* gadgets, etc... The only way the trick can be seen is by checking	   */
/* Intuition's window list, or by using a program such as DropShadow       */
/* which highlights window levels.					   */
/* This screws up with programs messing with layers, of course, but	   */
/* they are pretty rare.						   */
/*									   */
/* One good question is: does this work the same way with screens??	   */

VOID
HideWindow(screen, window)
struct	 Screen   *screen;
struct	 Window   *window;
{
   struct   Layer	*layer;
   struct   Layer_Info	*linfo;

   layer = window->RPort->Layer;
   linfo = layer->LayerInfo;

   SizeLayer(linfo, layer, 1-window->Width, 1-window->Height);
   MoveLayer(linfo, layer,
	     screen->Width  - window->LeftEdge - 1,
	     screen->Height - window->TopEdge  - 1);
   ShowTitle(screen, TRUE);
   MakeScreen(screen);
}

/* RevealWindow() is the exact opposition of HideWindow(). It resizes and  */
/* puts back to its original position the window's layer. The nice thing   */
/* about it is that we DO NOT need to give it the position where to move   */
/* to: this is described by the window itself!				   */
/*									   */
/* This function is not actually called within THIS program. It's in the   */
/* code for it's a good place for it to be. It is called by the "Reveal"   */
/* program which is encoded in and written to disk by this program.	   */

VOID
RevealWindow(screen, window)
struct	 Screen   *screen;
struct	 Window   *window;
{
   struct   Layer	*layer;
   struct   Layer_Info	*linfo;

   layer = window->RPort->Layer;
   linfo = layer->LayerInfo;

   MoveLayer(linfo, layer,
	     window->LeftEdge - screen->Width  + 1,
	     window->TopEdge  - screen->Height + 1);
   SizeLayer(linfo, layer, window->Width-1, window->Height-1);
   WindowToFront(window);
}

/* This where a rom call to OpenWindow() ends to. If the new window has */
/* depth gadgets, than add an icon to it.				*/

VOID
MyOpenWindow(window)
struct	 Window   *window;
{
   if (window && window->Title && (window->Flags & WINDOWDEPTH))
      AddIconGadget(window);
}

/* This is where a rom call to CloseWindow() comes before actually         */
/* closing the window. The jump to the rom code is done when returning	   */
/* from this function (see hook.asm).                                      */
/* If the window has been patched, then deallocate what was ealier	   */
/* given to it. 							   */

VOID
MyCloseWindow(window)
struct	 Window  *window;
{
   struct   IconGadget	*node, *next;

   Forbid();
   if (node = FindIGadget(window))
      {
      next = node->NextIGadget;
      RemoveGadget(window, next->IconGadget);
      FreeMem(next->IconGadget, sizeof(struct Gadget));
      if (window->UserPort==IconPort) window->UserPort = NULL;
      node->NextIGadget = next->NextIGadget;
      FreeMem(next, sizeof(struct IconGadget));
      }
   Permit();
}

/* MyPutMsg(). This is another trick.                                      */
/* How can we get the messages from the icon gadgets? The only way I	   */
/* figured out is by trapping all the PutMsg() calls. Thus every time      */
/* there's a PutMsg(), you first come here.                                */
/* Now we had to signal our main() that we recognized an icon gadget       */
/* message. Doing this by a PutMsg wasn't possible: as the function is     */
/* trapped, using it in the trap leads to an infinite loop. So we	   */
/* use a signal.							   */

VOID
MyPutMsg(port, msg)
struct	 MsgPort	*port;
struct	 IntuiMessage	*msg;
{
   if ( msg->Class == GADGETUP &&
	((struct Gadget *)(msg->IAddress))->GadgetID == ICONGADGET )
      {
      theSelectedWindow = msg->IDCMPWindow;
      Signal(IconTask, IconEvent);
      }
}

/* FindIGadget() returns an IconGadget given a window pointer, i.e.     */
/* finds back the allocated memory from an IntuiMessage IDCMPWindow	*/
/* field.								*/

struct	 IconGadget
*FindIGadget(window)
struct	 Window   *window;
{
   struct   IconGadget	*ig;

   ig = &AddedGadgets;

   while (ig->NextIGadget)
	 {
	 if (ig->NextIGadget->Window == window) return(ig);
	 ig = ig->NextIGadget;
	 }
   return(NULL);
}

/* Cleanup() de-patches all patched windows, closes everything and leaves. */
/* Currently called only when a "Break C" is sent to the task.             */

VOID
Cleanup(code)
UBYTE code;
{
   struct   IconGadget	   *node, *next;
   struct   IntuiMessage   *msg;

   if (code>11) DisplayBeep(NULL);

   if (HooksSet) CleanHooks();

   Forbid();
   node = AddedGadgets.NextIGadget;
   while (node)
	 {
	 next = node->NextIGadget;
	 RemoveGadget(node->Window, node->IconGadget);
	 node->DepthGadget->LeftEdge = -28;
	 node->DepthGadget->Width    =	26;
	 node->DepthGadget->NextGadget->Width = 24;
	 RefreshGList(node->DepthGadget, node->Window, NULL, 2);
	 FreeMem(node->IconGadget, sizeof(struct Gadget));
	 FreeMem(node, sizeof(struct IconGadget));
	 node = next;
	 }
   Permit();

   if (IconPort)
      {
      while(msg = (struct IntuiMessage *)GetMsg(IconPort)) ReplyMsg(msg);
      DeletePort(IconPort);
      }
   FreeSignal(IconEvent);

   if (IconBase)        CloseLibrary(IconBase);
   if (GfxBase)         CloseLibrary(GfxBase);
   if (IntuitionBase)   CloseLibrary(IntuitionBase);
   if (LayersBase)      CloseLibrary(LayersBase);

   exit(code);
}

