/****************************************************************
** Execute.c : Little replacement for Workbench `Execute' menu **
**             Written by T.Pierron, startted on 30.8.2001     **
**-------------------------------------------------------------**
** Special Requirements: WorkBench 2.1, version 37 or above    **
****************************************************************/

#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <intuition/screens.h>
#include <intuition/sghooks.h>
#include <libraries/gadtools.h>
#include <exec/memory.h>
#include <utility/hooks.h>
#include <dos/dos.h>
#include "Execute.h"

#define  CATCOMP_NUMBERS		/* We will need the string id */
#define  CATCOMP_STRINGS		/* and the english string corresponding to the id */
#include "ExecuteStrs.h"

/* Shared libraries we'll need to open */
struct IntuitionBase *IntuitionBase = NULL;
struct GfxBase       *GfxBase       = NULL;
struct Library       *GadToolsBase  = NULL;
struct Library       *AslBase       = NULL;
struct Library       *LocaleBase    = NULL;
struct Library       *WorkbenchBase = NULL;
struct Library       *UtilityBase   = NULL;

/* Graphics area: */
struct RastPort *RP;
struct TextFont *font;

struct Menu    *menu   = NULL;			/* Menu attached to the window */
struct Screen  *screen = NULL;			/* Screen where window will be opened */
struct Window  *window = NULL;			/* Main window ptr */
struct Gadget  *glist  = NULL;  		   /* Gadget list pointer */
struct MsgPort *wbport = NULL;			/* Drag'n drop from workbench */
struct Gadget *gad, *strgad;				/* our running Gadget pointer */
void   *vi = NULL;							/* VisualInfo pointer */

WORD  ZoomData[4];							/* Position of iconified window */
WORD  Geometry[4]={0x7fff,0x7fff,400,0};	/* Default window width/position */
UBYTE Title[128];								/* Title of window */
UBYTE NoOutput[] = NIL_MARK;				/* Marker to show disabled output */
UWORD OutFlags, InfoFlags;

/* Review buffer of entered commands */
ReviewBuf rb;

/* For the "version" command */
const UBYTE VERSION[]= "$VER:" APPNAME " v" EXE_VERSION " " EXE_DATE " "
                       "for CBM-AmigaDOS-" ARCH "\n";

/* Tag list for opening the main window: */
ULONG WinTags[] = {
	WA_Zoom,(ULONG)ZoomData,
	WA_NewLookMenus,TRUE,
	TAG_DONE
};

/* Set the color of the menu to Newlook (only 3.0+) */
ULONG MenuTags[] = {
	GTMN_FrontPen, 1,
	TAG_DONE
};

/* Our gadtools gadget text */
STRPTR GadTxt[] = {
	MSG_EXEC_TITLE_STR,
	MSG_EXEC_GAD_STR,
	MSG_EXEC_STR,         MSG_CANCEL_STR,
	MSG_OUT_TITLE_STR,
	/* Error message list */
	MSG_BADOS_STR,        NO_ASLREQ_STR,
	NO_INTERFACE_STR,     DIR_NOT_FOUND_STR,
	FNC_WINDOW_TITLE_STR, DIR_ENTRY_STR,
	MSG_VOLUME_STR,       MSG_ASSIGN_STR,
	MSG_DEVICE_STR
};

ULONG sigwin = 0, sigappwnd = 0;

struct NewWindow NewWin = {
	50, 20, 510, 150,
	~0, ~0,						/* DetailPen, BlockPen */
	IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW | IDCMP_MENUPICK |
	IDCMP_GADGETUP    | IDCMP_VANILLAKEY,
	WFLG_DEPTHGADGET  | WFLG_SMART_REFRESH  | WFLG_ACTIVATE  |
	WFLG_CLOSEGADGET  | WFLG_DRAGBAR,
	NULL,							/* FirstGadget */
	NULL,							/* CheckMark */
	Title,						/* Title */
	NULL,							/* Screen */
	NULL,							/* BitMap */
	0, 0, 0, 0,					/* min/max sizes */
	CUSTOMSCREEN
};

/**********************************************************
*****					MENU DATA MANAGMENT:						*****
**********************************************************/

/* NewMenu menu declaration */
struct NewMenu newmenu[] =
{
	{NM_TITLE, MSG_MAINTITLE_STR, 0, 0, 0, 0},
	{  NM_ITEM, MSG_COMPLETE_FILE_STR,  "Tab", NM_COMMANDSTRING, 0, (APTR) 101},
	{  NM_ITEM, MSG_EXPAND_PATTERN_STR, "M",   NULL,             0, (APTR) 102},
	{  NM_ITEM, MSG_EXPAND_FILES_STR,   NULL,  NULL,             0, (APTR) 103},
	{  NM_ITEM, MSG_EXPAND_DIRS_STR,    "P",   NULL,             0, (APTR) 104},
	{  NM_ITEM, MSG_INSERT_FILES_STR,   "L",   NULL,             0, (APTR) 105},
	{	NM_ITEM, (STRPTR)NM_BARLABEL,	   NULL,  0,                0,  NULL },

	{  NM_ITEM, MSG_INCL_INFO_STR, ".",  CHECKIT|CHECKED|MENUTOGGLE, 0, (APTR) 106},
	{  NM_ITEM, MSG_OUT_WIN_STR,   "O",  CHECKIT|CHECKED|MENUTOGGLE, 0, (APTR) 107},

	{	NM_ITEM, (STRPTR)NM_BARLABEL,	NULL, 0, 0,  NULL     },
	{  NM_ITEM, MSG_QUIT_STR,        "Q",  0, 0, (APTR) 108},
	{NM_END, 0, 0, 0, 0, 0}
};

#ifdef	DEBUG
ULONG amem, bmem;
#endif

/**** Definition of the main loop: ****/
int main(int argc, char *argv[])
{
	extern UBYTE ShowGUI; /* In Events.c - TRUE if popup window */
#ifdef	DEBUG
	bmem = AvailMem( MEMF_PUBLIC );
#endif
	/* If no locale, use english as default */
	if(LocaleBase = (struct Library *)OpenLibrary("locale.library", 37))
		TranslateStrings();

	/* These libraries are located in ROM. If it failed to open, it's   **
	** because we are attempting to run this programme on an old system */
	if( !(GfxBase       = (void *) OpenLibrary("graphics.library",  37)) ||
	    !(IntuitionBase = (void *) OpenLibrary("intuition.library", 37)) ||
	    !(GadToolsBase  = (void *) OpenLibrary("gadtools.library",  37)) ||
	    !(WorkbenchBase = (void *) OpenLibrary("workbench.library", 37)) ||
	    !(UtilityBase   = (void *) OpenLibrary("utility.library",   37)) )
		puts(ErrMsg(MSG_BADOS)), cleanup(RETURN_FAIL);

	if(GadToolsBase->lib_Version < 39)
		puts("Warning: consider using OS v3.0+");

	/* If failed to open public port, that's because this tool is already **
	** running, therefore we should quit both copy of this program.       */
	if( init_args(argc, argv) == 0 )
		cleanup(RETURN_OK);

	/* Get access to the frontmost screen */
	screen = IntuitionBase->ActiveScreen;
	font   = screen->RastPort.Font;

	if( ShowGUI ) {
		if(setup() == 0)
			AvertUser( ErrMsg(NO_INTERFACE) ), cleanup(RETURN_FAIL);

		handle_input();
	}
	else ExecuteCommand( rb.Buffer + GetPrevNextBuffer(&rb, NULL, 0, -1) );
	cleanup(RETURN_OK);
}

/*** Build title string ***/
void setup_title( BOOL update )
{
	WORD len; APTR cwd;
	/* "Execute command" string */
	strcpy(Title, GadTxt[0]);
	strcat(Title, "  - ");
	/* Current working directory name */
	cwd = (APTR) CurrentDir( NULL ); len = strlen(Title);
	NameFromLock(cwd, Title+len, sizeof(Title)-sizeof(NoOutput)-len);
	CurrentDir( cwd );
	/* >NIL: redirection marker */
	if( (OutFlags & CHECKED) == 0 ) strcat(Title, NoOutput);
	if(update) SetWindowTitles(window, Title, -1);
}

/*** Set flags of output or info files menu entry ***/
void ToggleFlags( WORD MenuNum, ULONG Flags )
{
	struct NewMenu * nm = &newmenu[ NM_INFO_INDEX+1 ];
	UWORD          * fl = &InfoFlags;

	if( MenuNum == NM_OUT_INDEX ) nm++, fl = &OutFlags;
	*fl = nm->nm_Flags ^ Flags;
}

/*** Setup the window, return 0 if init failed ***/
UBYTE setup( void )
{
	struct DrawInfo *di;

	/* Get the screen's visual information data */
	if( vi = (void *) GetVisualInfoA( screen,NULL ) )
	{
		/* Adjust the pen numbers to what the screen uses */
		if( di = (void *)GetScreenDrawInfo(screen))
		{
			/* Get a copy of the correct pens for the screen */
			if(di->dri_Version >= 2)
				MenuTags[1] = di->dri_Pens[BARDETAILPEN];
			FreeScreenDrawInfo(screen,di);
		}

		/* Build the menu-strip and compute menu items size */
		if(NULL != (menu = (void *) CreateMenusA(newmenu, MenuTags)) &&
		   NULL != LayoutMenusA(menu, vi, NULL) )
		{
			/* Measure some text length */
			RP = &screen->RastPort;

			/* Setup normal and minimized position of main window */
			NewWin.TopEdge  = Adjust_pos(Geometry[1],screen->Height,
			NewWin.Height   = font->tf_YSize*3+28 );
			NewWin.LeftEdge = Adjust_pos(Geometry[0],screen->Width,
			NewWin.Width    = Adjust_pos(Geometry[2],screen->Width,0) );
			NewWin.Screen   = screen;

			setup_title( FALSE );

			/* Init the position and the size of the minimized window */
			ZoomData[0] = NewWin.TopEdge;
			ZoomData[1] = NewWin.LeftEdge;
			ZoomData[2] = TextLength(RP, GadTxt[0], strlen(GadTxt[0])) + 100;
			ZoomData[3] = screen->BarHeight+1;

			if( window = (void *) OpenWindowTagList(&NewWin, WinTags) )
			{
				sigwin = 1 << window->UserPort->mp_SigBit;
				SetMenuStrip(window, menu);
				create_gadgets();
				setup_appwindow( TRUE );
				return 1;
			}
		}
	}
	return 0;
}

/*** Setup workbench drop icon port ***/
void setup_appwindow( BOOL SetIt )
{
	static struct AppWindow * appwin = NULL;
	if( SetIt ) {
		if(wbport = (struct MsgPort *) CreateMsgPort())
			if(appwin = (void *) AddAppWindow(1, NULL, window, wbport, NULL))
				sigappwnd = 1 << wbport->mp_SigBit;
	} else {
		/* Remove AppWindow */
		if( appwin ) RemoveAppWindow(appwin); appwin = NULL;
		if( wbport ) DeleteMsgPort(wbport);   wbport = NULL;
	}
}

/*** Desallocate all ressources: ***/
void cleanup( WORD errcode )
{
	extern APTR FR;
	extern void *catalog;

	if (window)	{
		ClearMenuStrip(window);
		CloseWindow(window);
	}

	free_msgport(); change_cwd(NULL, 0);
	setup_appwindow( FALSE ); free_prefs();

	if (glist)				FreeGadgets(glist);
	if (vi)					FreeVisualInfo(vi);
	if (menu)				FreeMenus(menu);
	if (FR)					FreeAslRequest(FR);
	if (catalog)			CloseCatalog(catalog);
	if (AslBase)			CloseLibrary(AslBase);
	if (LocaleBase)		CloseLibrary(LocaleBase);
	if (GadToolsBase)		CloseLibrary(GadToolsBase);
	if (IntuitionBase)	CloseLibrary(IntuitionBase);
	if (WorkbenchBase)	CloseLibrary(WorkbenchBase);
	if (UtilityBase)		CloseLibrary(UtilityBase);
	if (GfxBase)			CloseLibrary(GfxBase);

#ifdef	DEBUG
	/* Here can be compared good programs with the other :-} */
	amem = AvailMem( MEMF_PUBLIC );
	if(amem < bmem)	printf("Possible memory lost of %d bytes\n", bmem-amem);
#endif

	exit(errcode);
}

/** Show output state into window title **/
void ShowOut( USHORT	Flags )
{
	if((Flags & CHECKED) == (OutFlags & CHECKED) ) return;
	if( Flags & CHECKED )
		Title[ strlen(Title) - sizeof(NoOutput) + 1 ] = 0;
	else
		strcat(Title, NoOutput);

	OutFlags = Flags;
	SetWindowTitles(window, Title, -1);
}

/** Search for a command matching beginning of string gadget **/
void SearchPrevNextBuffer(ReviewBuf *rb, struct SGWork *sgw, BYTE dir)
{
	WORD len;
	do {
		len = GetPrevNextBuffer(rb, NULL, 0, dir);
		if( !Strnicmp(sgw->WorkBuffer, rb->Buffer+len, sgw->BufferPos) ) {
			strcpy(sgw->WorkBuffer, rb->Buffer+len);
			sgw->Actions |= SGA_USE | SGA_REDISPLAY;
			sgw->NumChars = strlen(sgw->WorkBuffer);
			return;
		}
	} while( len > 0 );
	/* Nothing found, cut buffer */
	sgw->WorkBuffer[ sgw->NumChars = sgw->BufferPos ] = 0;
}

/** Special hook executed while editing a line **/
ULONG CheckConfirm(struct Hook *hook, struct SGWork *sgw, ULONG *msg)
{
	if(*msg == SGH_KEY)
	{
		if( sgw->Code == 27 )
			sgw->Code = CANCEL_OP,
			sgw->Actions |= SGA_END;
		else if( sgw->EditOp == EO_ENTER )
			if( sgw->Actions & (SGA_NEXTACTIVE | SGA_PREVACTIVE) )
				sgw->Code = (sgw->IEvent->ie_Qualifier & SHIFTKEYS ? COMPLETE_FILE :
				             sgw->IEvent->ie_Qualifier & CTRLKEYS  ? COMPLETE_DIR  :
				             COMPLETE_OBJ);
			else
				sgw->Code = (sgw->IEvent->ie_Qualifier & SHIFTKEYS ? SAVE_PREFS : EXECUTE_COM);
		else if(sgw->IEvent->ie_Qualifier & AMIGAKEYS)
			switch( sgw->Code )
			{
				/* Makes menu shortcut available in text entry */
				case 'o': case 'O': 
					/* User hit Amiga+O while editing : toggle Output state */
					sgw->Actions &= ~SGA_USE;
					ShowOut(MenuItem(MENUITEM(0,NM_OUT_INDEX,NOSUB))->Flags ^= CHECKED);
					break;
				case '.':
					/* Ditto with Amiga + . */
					sgw->Actions &= ~SGA_USE;
					InfoFlags = MenuItem(MENUITEM(0,NM_INFO_INDEX,NOSUB))->Flags ^= CHECKED;
					break;
				case EXPAND_PAT & ~32: sgw->Code = EXPAND_FILE;
				case EXPAND_PAT:
				case EXPAND_DIR:  case EXPAND_DIR  & ~32:
				case INSERT_PATH: case INSERT_PATH & ~32:
				case CANCEL_OP:   case CANCEL_OP   & ~32:
					/* Amiga + L => Insert filenames */
					sgw->Actions = (sgw->Actions & ~SGA_USE) | SGA_END;
					sgw->Code   |= 32;
			}
		else if(sgw->IEvent->ie_Class == IECLASS_RAWKEY) {
			BYTE dir;
			switch( sgw->IEvent->ie_Code )
			{
				case CURSORUP:   dir = -1; goto after;
				case CURSORDOWN: dir =  1; after:
					if( sgw->IEvent->ie_Qualifier & SHIFTKEYS )
						SearchPrevNextBuffer(&rb, sgw, dir);
					else
						/* Cursor up/down : scan through review buffer */
						sgw->BufferPos = sgw->NumChars = 
							GetPrevNextBuffer(&rb, sgw->WorkBuffer, MAX_COM, dir),
						sgw->Actions |= SGA_USE | SGA_REDISPLAY;
			}
		}
	}
	else return 0;
	return ~0;
}

/*** Create the gadgets associated with the window ***/
void create_gadgets( void )
{
	static struct Hook hook;
	static ULONG GadTags[] = {
		GTST_MaxChars, MAX_COM,
		GTST_EditHook, (ULONG) &hook,
		GT_Underscore, '_',
		TAG_DONE
	};
	extern ULONG HookEntry(void); /* This proc is defined in amiga.lib */
	extern UBYTE *StrBuf;
	struct NewGadget ng;
	UWORD  left, top, Fh;

	/* Init some variables */
	top  = window->BorderTop   + 5;
	left = window->BorderRight + 5;
	gad  = (void *) CreateContext(&glist);
	Fh   = font->tf_YSize+3;

	ng.ng_TextAttr   = screen->Font;
	ng.ng_VisualInfo = vi;

	/* String gadget for user command */
	ng.ng_Flags      = PLACETEXT_LEFT | NG_HIGHLABEL;
	ng.ng_GadgetText = GadTxt[1];
	ng.ng_Width      = window->Width - 10 - (
	ng.ng_LeftEdge   = left + TextLength(RP, GadTxt[1], strlen(GadTxt[1])) + 10);
	ng.ng_TopEdge    = top;
	ng.ng_Height     = Fh+3;
	ng.ng_GadgetID   = 1;

	/* Init strgad hook (v37+ only) */
	hook.h_Entry    = HookEntry;
	hook.h_SubEntry = CheckConfirm;

	strgad = gad = (APTR) CreateGadgetA(STRING_KIND, gad, &ng, GadTags);

	/* Fill buffer with the last command */
	StrBuf = sti(gad)->Buffer;
	sti(gad)->BufferPos = GetPrevNextBuffer(&rb, StrBuf, MAX_COM, -1);

	/* Bool gadget "Execute" */
	ng.ng_Flags      = PLACETEXT_IN;
	ng.ng_GadgetText = GadTxt[2];
	ng.ng_LeftEdge   = left;
	ng.ng_TopEdge    = top + 8 + (
	ng.ng_Height     = Fh+1);
	ng.ng_GadgetID   = 2;
	ng.ng_Width      = TextLength(RP, GadTxt[2], strlen(GadTxt[2]));
	if(ng.ng_Width < (left = TextLength(RP, GadTxt[3], strlen(GadTxt[3]))))
		ng.ng_Width =  left;
	ng.ng_Width += 40;

	gad = (void *) CreateGadgetA(BUTTON_KIND, gad, &ng, GadTags+4);

	/* Bool gadget "Cancel" */
	ng.ng_GadgetText = GadTxt[3];
	ng.ng_LeftEdge   = window->Width - 10 - ng.ng_Width;
	ng.ng_GadgetID   = 3;

	gad = (void *) CreateGadgetA(BUTTON_KIND, gad, &ng, GadTags+4);

	if (gad == NULL) cleanup( RETURN_FAIL );

	/** Refresh everything **/
	AddGList       (window,  glist, (UWORD)-1, (UWORD)-1, NULL);
	RefreshGList   (glist,  window, NULL,      (UWORD)-1);
	ActivateGadget (strgad, window, NULL);

	GT_RefreshWindow(window, NULL);
}
