/*************************************************************************
 * ClassAct Comprehensive Demo Program
 * Copyright © 1995 Osma Ahvenlampi
 *
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <intuition/icclass.h>
#include <libraries/gadtools.h>
#include <graphics/gfxbase.h>
#include <graphics/text.h>
#include <graphics/gfxmacros.h>
#include <utility/tagitem.h>
#include <workbench/startup.h>
#include <workbench/workbench.h>

#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/diskfont.h>
#include <proto/utility.h>
#include <proto/wb.h>
#include <proto/icon.h>

#include <classact.h>
#include <classact_lib.h>
#include <clib/classact_lib_protos.h>

void __stdargs kprintf(const char *, ...);

/* a simple button */
#define Button(a) ButtonObject, GA_Text, a, ButtonEnd
#define DButton(a) ButtonObject, GA_Text, a, GA_Disabled, TRUE, ButtonEnd

/**************************************************************************
 * Some label arrays for the gadgets in this demo.
 */
 
static STRPTR objtypes[] = 
{
	"Exec",
	"Image",
	"Sound",
	"Menu",
	"Icon",
	"Dock",
	"Access",
	NULL 	
};

static STRPTR objnames[] = 
{
	"ToolManager",
	"ScreenMode",
	"WBPattern",
	NULL
};

/*************************************************************************
 * Gadget list
 * This wouldn't be strictly necessary, but it's an easy way of keeping
 * the gadget pointers for when we need to access the gadgets.
 */
typedef enum { G_ObjType = 1, G_ObjList, G_Top, G_Up, G_Down, G_Bottom,
			G_Sort, G_New, G_Edit, G_Copy, G_Remove, G_Help, G_Save, G_Use,
			G_Test, G_Cancel, G_MAX } GadgetIDs;
 
struct Gadget *GL[G_MAX+1];

/*************************************************************************
 * ReadArgs
 */
 
#define TEMPLATE "S=SIMPLEREFRESH/S,NC=NOCAREREFRESH/S,ND=NDEFER/S"
LONG ARG[3];
typedef enum { A_Simple, A_NoCare, A_NoDefer } Args;

/*************************************************************************
 * App message hook.
 * Workbench App messages can be caught with a callback hook such as this.
 * We'll not worry about the app message type in this hook. Objects dropped
 * on the window or on the icon (while iconified) will be added to the 
 * listview.
 */

void __asm __saveds AppMsgFunc(	register __a0 struct Hook *Hook,
								register __a2 Object *Window,
								register __a1 struct AppMessage *Msg )
{
	struct Window *Win;
	struct WBArg *arg = Msg->am_ArgList;
	LONG i = Msg->am_NumArgs;
	struct List *l = Hook->h_Data;
	struct Node *n;
	UBYTE name[256];
	
	GetAttr( WINDOW_Window, Window, (ULONG *)&Win );

	/* Detach the list for modifications.
	 */
	SetGadgetAttrs( GL[G_ObjList], Win, NULL, LISTBROWSER_Labels, ~0, TAG_END );
		
	while (i--)
	{
		/* Add the name of the icon to the listview. ListBrowser can copy the
		 * text into an internal buffer and thus let us not worry about the
		 * pointer validity.
		 */
		
		NameFromLock( arg->wa_Lock, name, sizeof(name) );
		AddPart( name, arg->wa_Name, sizeof(name) );
		
		if (n = AllocListBrowserNode( 1, LBNCA_CopyText, TRUE, LBNCA_Text, name, TAG_END ))
			AddTail( l, n );
			
		arg++;
	}
	
	/* Reattach the list */
	SetGadgetAttrs( GL[G_ObjList], Win, NULL, LISTBROWSER_Labels, l, TAG_END );
}

/*************************************************************************
 * Main Program
 */
int 
main(void)
{
	struct List *objlist; 
	struct List *typelist; 
	struct RDArgs *args;
	struct MsgPort *appport;

	if (!ButtonBase) /* force it open */
		return 30;
		
	if (!(args = ReadArgs(TEMPLATE, ARG, NULL)))
		return 20;
		
	Printf("%seferred %s refresh %s\n", ARG[A_NoDefer] ? "Non-d" : "D", ARG[A_Simple] ? "Simple" : "Smart", ARG[A_NoCare] ? "(NoCare)" : "");

	objlist = BrowserNodesA( objnames );
	typelist = ChooserLabelsA( objtypes );

	/* By providing a message port you enable windowclass to handle iconification
	 * and appwindows. This port can shared by all the windows of your application.
	 */
	appport = CreateMsgPort();

	if (objlist && typelist && appport)
	{
		struct Gadget *MainLayout;
		Object *Window;
		
		struct Hook apphook;
		apphook.h_Entry = (ULONG (* )())AppMsgFunc;
		apphook.h_SubEntry = NULL;
		apphook.h_Data = objlist;
		
		/* Create a Window object with a Layout. When Window is asked to open itself,
		 * it will calculate how much space the Layout needs and size itself accordingly.
		 */
	
		Window = WindowObject,
				
				/* these tags describe the window 
				 */
		
				WA_IDCMP, IDCMP_RAWKEY,
				WA_Top, 20,
				WA_Left, 20,
				WA_SizeGadget, TRUE,
				WA_DepthGadget, TRUE,
				WA_DragBar, TRUE,
				WA_CloseGadget, TRUE,
				WA_Activate, TRUE,
				
				/* About window refreshes:
				 * Because WindowClass and LayoutClass can, when used together, change the
				 * normal Intuition practise of refreshing gadgets in the input.device context,
				 * some rules about the refresh system change.
				 * Deferred refresh works in both smart and simple refresh windows, but
				 * if nocarerefresh is used, Intuition does not retain the damage regions
				 * and any window damage will force the whole window to be refreshed.
				 * This demo allows you to try combinations of refresh types.
				 * In the normal case you can ignore this and let WindowClass and the user
				 * decide what kind of refreshes they want. Nocare refresh can be
				 * combined with smart refresh to provide a fast, but somewhat more memory
				 * hungry refresh method. Simple refresh can save some memory but it's
				 * slower.
				 */
				
				WA_SimpleRefresh, ARG[A_Simple],
				WA_NoCareRefresh, ARG[A_NoCare],	
				WA_SmartRefresh, !ARG[A_Simple],
				
				WA_Title, "ClassAct layout.gadget Example (ToolManager preferences mockup)",
				WA_ScreenTitle, "ClassAct Copyright 1995 Phantom Development LLC.",
				
				/* Turn on gadget help in the window 
				 */
				
				WINDOW_GadgetHelp, TRUE,
				
				/* Add an iconification gadget. If you have this, you must listen to
				 * WMHI_ICONIFY.
				 */
				 
				WINDOW_IconifyGadget, TRUE,
				
				/* This message port lets windowclass handle the icon and appwindow.
				 */
				 
				WINDOW_AppPort, appport,
				WINDOW_AppWindow, TRUE,
				WINDOW_AppMsgHook, &apphook,
				
				/* The windowclass will automatically free the DiskObject used when
				 * iconifying the window. If you do not provide a valid DiskObject,
				 * windowclass will try to use env:sys/def_window.info or the default
				 * project icon.
				 */
				
				WINDOW_Icon, GetDiskObject( "LayoutExample" ),
				WINDOW_IconTitle, "ClassAct Example",
				
				/* Below is the layout of the window 
				 */
				
				WINDOW_ParentGroup,	MainLayout = VGroupObject,
					LAYOUT_SpaceOuter, TRUE,
					LAYOUT_BevelStyle, BVS_THIN,
					
					/* this tag instructs layout.gadget to defer GM_LAYOUT and GM_RENDER and ask
					 * the windowclass to do them. This lessens the load on input.device 
					 */
					LAYOUT_DeferLayout, !ARG[A_NoDefer],

					/* A 1-of-n chooser using the labels list we made from the label array earlier 
					 */

					StartMember, GL[G_ObjType] = ChooserObject,
						CHOOSER_Labels, typelist,
					EndMember,
					MemberLabel("_Object Type"),
					
					/* Objects can be given arbitary weights within groups, and layout.gadget
					 * will distribute space relative to the total weight of the group.
					 * Here we set the button column to 0 weight which means minimum space.
					 * Thus the listview gets all available extra space.
					 */
					
					StartHGroup, BAligned,
						StartMember, GL[G_ObjList] = ListBrowserObject,
							LISTBROWSER_Labels, objlist,
							LISTBROWSER_ShowSelected, TRUE,
						EndMember,
						
						StartVGroup,
							StartMember, GL[G_Top] = DButton("Top"),
							StartMember, GL[G_Up] = DButton("Up"),
							StartMember, GL[G_Down] = DButton("Down"),
							StartMember, GL[G_Bottom] = DButton("Bottom"),
							StartMember, GL[G_Sort] = Button("So_rt"),
						EndGroup,
						CHILD_WeightedWidth, 0,
						
						/* One way to keep the buttons constant size is to set the
						 * group to stay at minimum size with a weight of 0. We could
						 * also set the weight of each of the buttons to 0. That way
						 * extra space would be distributed between the buttons
						 * instead of all below. This looks better.
						 */
						
						CHILD_WeightedHeight, 0,
					EndGroup,
					
					/* two rows of buttons. EvenSized instructs layout.gadget that it
					 * should make sure the minimum size of each matches, so that we
					 * get four neat columns.
					 * Again the weight is set to 0. When the window is resized, all
					 * space is given to the listview.
					 */
					
					StartHGroup, EvenSized,
						StartMember, GL[G_New] = Button("_New..."),
						StartMember, GL[G_Edit] = DButton("_Edit..."),
						StartMember, GL[G_Copy] = DButton("Co_py"),
						StartMember, GL[G_Remove] = DButton("Remove"),
					EndGroup,
					CHILD_WeightedHeight, 0,
					
					StartHGroup, EvenSized,
						StartMember, GL[G_Save] = Button("_Save"),
						StartMember, GL[G_Use] = Button("_Use"),
						StartMember, GL[G_Test] = Button("_Test"),
						StartMember, GL[G_Cancel] = Button("_Cancel"),
					EndGroup,
					CHILD_WeightedHeight, 0,
					
					StartMember, GL[G_Help] = ButtonObject,
						GA_ReadOnly, TRUE,
						GA_Text, "Welcome to ClassAct demo!",
					EndMember,
					CHILD_WeightedHeight, 0,
				EndGroup,
			EndWindow;
	
		if (Window)
		{
			/* Window pointer cache.
			 */
		
			struct Window *Win;
		
			/* Finish the gadgetarray initialisation. Set gadget IDs and release verify. 
			 * This is one way of avoiding boring repetition in the layout description
			 * taglist itself.
			 */
			
			{
				LONG i = 1;
				do SetAttrs(GL[i], GA_ID, i, GA_RelVerify, TRUE, TAG_END);
				while (GL[++i]);
			}
			
			if (Win = CA_OpenWindow( Window ))
			{
				ULONG wsig, asig = 1L << appport->mp_SigBit;
				BOOL done = FALSE;
				
				/* Now that the window has been opened, we can get the signal mask
				 * of its user port. If the program supported iconification and didn't
				 * use a shared IDCMP port between all windows, this signal bit
				 * would have to be re-queried before each Wait().
				 */
				
				GetAttr( WINDOW_SigMask, Window, &wsig );
	
				while (done == FALSE)
				{
					ULONG sig = Wait(wsig | asig | SIGBREAKF_CTRL_C);
					ULONG result;
					UWORD code;

					if (sig & (wsig | asig))
					{
						/* Messages waiting at the window's IDCMP port. Loop at WM_HANDLEINPUT
						 * until all have been processed.
						 */
					
						while ((result = CA_HandleInput(Window,&code)) != WMHI_LASTMSG)
						{
							/* The return code of this method is two-part. The upper word describes the
							 * class of the message (gadgetup, menupick, closewindow, iconify, etc),
							 * and the lower word is a class-defined ID, currently in use in the
							 * gadgetup and menupick return codes.
							 * Switch on the class, then on the ID.
							 */
						
							switch(result & WMHI_CLASSMASK)
							{
							case WMHI_GADGETUP:
							
								/* OK, got a gadgetup from something. Lets find out what the something is.
								 * The code WORD to which a pointer was passed to WM_HANDLEINPUT has been
								 * set to the Code value from the IDCMP_GADGETUP, in case we need it.
								 */
							
								switch(result & WMHI_GADGETMASK)
								{
								case G_ObjList:
									/* User clicked on the listview 
									 */
									{
										static ULONG ids[] = { G_Top, G_Up, G_Down, G_Bottom, G_Edit, G_Copy, G_Remove, 0 };
										ULONG i, dis = FALSE;
										
										if (code == ~0) /* no node was selected */
											dis = TRUE;
										
										for ( i = 0 ; ids[i] ; i++ )
										{
											SetGadgetAttrs( GL[ids[i]], Win, NULL, GA_Disabled, dis, TAG_END );
											RefreshGList( GL[ids[i]], Win, NULL, 1 );
										}
										
										break;
									}
								}
								break;
								
							case WMHI_GADGETHELP:
								{
									STRPTR helptext;
									
									/* A gadget help message informs the application about the gadget
									 * under the mouse pointer. The code WORD is set to the value the
									 * gadget returned. Result code contains the ID of the gadget, 
									 * or NULL (not in the window) or WMHI_GADGETMASK (not over a gadget).
									 */
								
									switch(result & WMHI_GADGETMASK)
									{
									case G_ObjType:
										helptext = "Choose object type";
										break;
									case G_ObjList:
										helptext = "Choose object to modify";
										break;
									case G_Top:
										helptext = "Move object to top";
										break;
									case G_Up:
										helptext = "Move object upwards";
										break;
									case G_Down:
										helptext = "Move object downwards";
										break;
									case G_Bottom:
										helptext = "Move object to bottom";
										break;
									case G_Sort:
										helptext = "Sort object list";
										break;
									case G_New:
										helptext = "Create new object";
										break;
									case G_Edit:
										helptext = "Edit object";
										break;
									case G_Copy:
										helptext = "Make a new copy of object";
										break;
									case G_Remove:
										helptext = "Delete the object";
										break;
									case G_Help:
										helptext = "Hey there ;)";
										break;
									case G_Save:
										helptext = "Save settings";
										break;
									case G_Use:
										helptext = "Use these settings";
										break;
									case G_Test:
										helptext = "Test these settings";
										break;
									case G_Cancel:
										helptext = "Cancel changes";
										break;
									default:
										helptext = "";
										break;
									}
									if (SetGadgetAttrs( GL[G_Help], Win, NULL, GA_Text, helptext, TAG_END ))
										RefreshGList(GL[G_Help], Win, NULL, 1);
								}
								break;

							case WMHI_CLOSEWINDOW:
								/* The window close gadget was hit. Time to die...
								 */
								done = TRUE;
								break;
								
							case WMHI_ICONIFY:
								/* Window requests that it be iconified. Handle this event as
								 * soon as possible. The window is not iconified automatically to
								 * give you a chance to make note that the window pointer will be 
								 * invalid before the window closes. It also allows you to free
								 * resources only needed when the window is open, if you wish to.
								 */
								if (CA_Iconify( Window ))
									Win = NULL;
								break;
								 
							case WMHI_UNICONIFY:
								/* The window should be reopened. If you had free'd something
								 * on iconify, now is the time to re-allocate it, before calling
								 * CA_OpenWindow.
								 */
								Win = CA_OpenWindow( Window );
								break;
							}
						}
					}
					else if (sig & SIGBREAKF_CTRL_C)
					{
						done = TRUE;
					}
				}
				/* Close the window and dispose of all attached gadgets 
				 */
				DisposeObject( Window );
			}
		}
	}
	
	if (appport)
		DeleteMsgPort(appport);
	
	/* NULL is valid input for these helper functions, so no need to check.
	 */
	FreeChooserLabels( typelist );
	FreeBrowserNodes( objlist );
	
	FreeArgs(args);
}
