/*
 * 	"My Chart" application: Stage 4
 *
 * 		This application is the fourth stage of a sample
 *		application which is built up by stages in the
 *		SDK Tutorial.  It isn't meant to illustrate any
 *		particular point, nor should it be graced with
 *		comments explaining the workings of system components
 *		it uses.  It's meant to be an example of a "real app".
 *
 *		That said, you should probably omit this paragraph
 *		whenever including this file in the docs.
 *
 *	$Id: mchrt.goc,v 1.1.4.3 94/01/23 10:33:31 jenny Exp $
 */
/**************************************************************
 *	Copyright (c) Geoworks 1993 -- All Rights Reserved
 *
 * MChrt is a charting application.  It maintains a list of
 * numbers and constructs a bar chart to display them.
 *
 * Our process object is in charge of maintaining the data
 * structure which holds the list of numbers.
 *
 **************************************************************/

@include <stdapp.goh>

/* CONSTANTS */

/* In the list gadget which represents our data, the first item
 * isn't going to represent anything; it's just a place holder.
 * The FAKE_LIST_ITEM constant will be used when checking for this item 
 */
#define FAKE_LIST_ITEM 	0

/* When drawing the pieces of the chart, we'll use the following 
 * constants to compute the coordinates at which to draw.
 */
#define VIEW_RIGHT	360	/* Width of the Chart View */
#define VIEW_BOTTOM	288	/* Height of Chart View */
#define CHART_BOTTOM 	268	/* y = 0 line of Chart */
#define CHART_LEFT      20	/* Left bound of Leftmost bar */
#define BAR_WIDTH	30	/* Width of each bar */
#define BAR_WIDTH_ALLOW	36	/* Distance between left edges of bars */
#define BORDER_MARGIN	10	/* Arbitrary margin width at edges */

/* The following constants are the document protocol.  If we ever change
 * our document format, we should increment either the major or minor
 * protocol number. 
 */
#define MC_DOCUMENT_PROTOCOL_MAJOR 0
#define MC_DOCUMENT_PROTOCOL_MINOR 1

@class	MCProcessClass, GenProcessClass;
@endc   /* end of MCProcessClass definition */

@class MCDocumentClass, GenDocumentClass;
/* For information about the messages listed below, see the
 * headers for their handlers, later in this file. 
 */
	@message (GEN_DYNAMIC_LIST_QUERY_MSG) MSG_MCD_SET_DATA_ITEM_MONIKER;
	@message void 	MSG_MCD_DELETE_DATA_ITEM();
	@message void   MSG_MCD_INSERT_DATA_ITEM();
	@message void	MSG_MCD_SET_DATA_ITEM();
/* MCDI_dataFileBlock: handle of map block, where we store a linked list. */
	@instance 	VMBlockHandle 	MCDI_dataFileBlock;
/* MCDI_dataListHead: handle of head of linked list. */
	@instance 	ChunkHandle 	MCDI_dataListHead;
/* MCDI_chartMemBlock: Object block holding our MCChart object. */
	@instance 	MemHandle 	MCDI_chartMemBlock;
@endc /* end of MCDocumentClass definition */

@class MCChartClass, VisClass;
/* For information about the messages listed below, see the
 * headers for their handlers, later in this file. 
 */
	@message void 	MSG_MCC_INSERT_BAR(word ordinal, word value);
	@message void 	MSG_MCC_DELETE_BAR(word ordinal);
	@message void 	MSG_MCC_RESIZE_BAR(word ordinal, word value);
/* MCCI_numBars:	The number of bars in the chart. Internal. */
	@instance word MCCI_numBars = 0;
/* MCCI_barArray:	Chunk handle of array to hold bar info. */
	@instance ChunkHandle MCCI_barArray;
@endc /* end of MCChartClass definition */

@classdecl	MCProcessClass, neverSaved;
@classdecl 	MCChartClass;
@classdecl 	MCDocumentClass;

/* Global STRUCTURES and VARIABLES */

/* This structure will hold information about our document, and will form
 * the header of a block in our data file.  The first item of this structure
 * MUST be an LMemBlockHeader. 
 */
typedef struct {
	LMemBlockHeader	DBH_standardHeader;
	word 		DBH_numItems;	/* Number of data items */
	ChunkHandle	DBH_listHead; 	/* Head of linked list */
	VMBlockHandle 	DBH_chartBlock; /* Block where MCChart is stored */
} DataBlockHeader;

/* The data points which are to be charted are stored in
 * a linked list of chunks, all of which are contained within
 * a single block of memory. Each element of the list will be
 * stored in a ListNode structure. 
 */
typedef struct {
	word		LN_data;
	ChunkHandle     LN_next;
} ListNode;

/* OBJECT Resources */

/* APPRESOURCE will hold the application object and other information
   which the system will want to load when it wants to find out about
   the application but doesn't need to run the application. */

@start	AppResource;

@object GenApplicationClass MCApp = {
    GI_visMoniker = list { @MCTextMoniker }
    GI_comp = @MCPrimary;
    gcnList(MANUFACTURER_ID_GEOWORKS,GAGCNLT_WINDOWS) = @MCPrimary;
    gcnList(MANUFACTURER_ID_GEOWORKS,MGCNLT_ACTIVE_LIST) =
	@MCDocumentControl, @MCDisplayControl;
    gcnList(MANUFACTURER_ID_GEOWORKS,MGCNLT_APP_STARTUP) =
	@MCDocumentControl;
    gcnList(MANUFACTURER_ID_GEOWORKS,GAGCNLT_SELF_LOAD_OPTIONS) =
	@MCDocumentControl, @MCDisplayControl;
}

@visMoniker MCTextMoniker = "MyChart Application";

@end	AppResource;

/* The INTERFACE resource holds the bulk of our Generic UI gadgetry. */
@start  Interface;

@object GenPrimaryClass MCPrimary = {
	GI_comp = @MCFileGroup, @MCWindowGroup, @MCDisplayGroup;

	/*
	 * When the specific UI permits, let's not show the menu bar on
	 * startup; this can be useful on the small screen of a pen-based
	 * system since it gives some extra space.
	 */
	ATTR_GEN_DISPLAY_MENU_BAR_POPPED_OUT;
	HINT_DISPLAY_MENU_BAR_HIDDEN_ON_STARTUP;
}

@object GenInteractionClass MCFileGroup = {
	GI_comp = @MCDocumentControl;
	GII_visibility = GIV_POPUP;
	ATTR_GEN_INTERACTION_GROUP_TYPE = GIGT_FILE_MENU;
}

@object GenDocumentControlClass MCDocumentControl = {
	GDCI_documentToken = {"MCht", MANUFACTURER_ID_GEOWORKS } ;
	GDCI_noNameText = "No Document";
	GDCI_documentGroup = MCDocumentGroup;
	GDCI_attrs = @default | GDCA_MULTIPLE_OPEN_FILES;
}

@object GenInteractionClass MCWindowGroup = {
	GI_comp = @MCDisplayControl;
	GII_visibility = GIV_POPUP;
	ATTR_GEN_INTERACTION_GROUP_TYPE = GIGT_WINDOW_MENU;
}

@object GenDisplayControlClass MCDisplayControl = {}

@object GenDisplayGroupClass MCDisplayGroup = {
	ATTR_GEN_DESTINATION_CLASS = {(ClassStruct *) &MCDocumentClass};
	HINT_DEFAULT_TARGET;
}

@end 	Interface;

/* The DISPLAY resource is a template which will be duplicated for
 * each open document. It contains all generic UI associated with
 * a document.  
 */
@start	Display;

@object GenDisplayClass MCDisplay  = {
	GI_comp = @MCLeftClump, @MCChartView;
	GI_states = @default & ~GS_USABLE;
	GI_attrs = @default | GDA_USER_DISMISSABLE;
	ATTR_GEN_DISPLAY_NOT_MINIMIZABLE;
	HINT_ORIENT_CHILDREN_HORIZONTALLY;
	ATTR_GEN_WINDOW_KBD_OVERRIDE = (KO_NO_KEYBOARD);
}

@object GenInteractionClass MCLeftClump = {
	GI_comp = @MCDataList, @MCAddTrigger, @MCDeleteTrigger,
		  @MCChangeTrigger, @MCValue;
}

@object GenViewClass MCChartView = {
	GVI_horizAttrs = @default | GVDA_NO_SMALLER_THAN_CONTENT |
			 GVDA_NO_LARGER_THAN_CONTENT;
	GVI_vertAttrs = @default | GVDA_NO_SMALLER_THAN_CONTENT |
			GVDA_NO_LARGER_THAN_CONTENT;
}

@end	Display;

/* The CONTENT resource is a template resource, and a duplicate of this
 * resource will be made for each newly created document.
 */ 
@start  Content, notDetachable;

@object MCChartClass MCChart = {
	VI_bounds = { 0, 0, VIEW_RIGHT, VIEW_BOTTOM };
	MCCI_barArray = BarDataChunk;
}

@chunk word BarDataChunk[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

@end 	Content;

@start 	Display;

@object GenDynamicListClass MCDataList = {
	GIGI_selection = FAKE_LIST_ITEM;
	GIGI_numSelections = 1;
	GIGI_applyMsg = 0;
	GIGI_destination = (TO_APP_MODEL);
	GDLI_numItems = 1;
	GDLI_queryMsg = MSG_MCD_SET_DATA_ITEM_MONIKER;
	HINT_ITEM_GROUP_SCROLLABLE;
}

@visMoniker FakeItemMoniker = "Data:";


@object GenTriggerClass MCAddTrigger = {
	GI_visMoniker = "Add";
	GTI_destination = (TO_APP_MODEL);
	GTI_actionMsg = MSG_MCD_INSERT_DATA_ITEM;
	ATTR_GEN_DESTINATION_CLASS = (ClassStruct *) &MCDocumentClass;
}

@object GenTriggerClass MCChangeTrigger = {
	GI_visMoniker = "Change";
	GTI_destination = (TO_APP_MODEL);
	GTI_actionMsg = MSG_MCD_SET_DATA_ITEM;
	ATTR_GEN_DESTINATION_CLASS = (ClassStruct *) &MCDocumentClass;
}

@object GenTriggerClass MCDeleteTrigger = {
	GI_visMoniker = "Delete";
	GTI_destination = (TO_APP_MODEL);
	GTI_actionMsg = MSG_MCD_DELETE_DATA_ITEM;
	ATTR_GEN_DESTINATION_CLASS = (ClassStruct *) &MCDocumentClass;
}


@object GenValueClass MCValue = {
	GVLI_minimum = MakeWWFixed(0);
	GVLI_maximum = MakeWWFixed(0x7ffe);
	GVLI_value   = MakeWWFixed(123);
	HINT_VALUE_NOT_DIGITALLY_EDITABLE;
}

@end 	Display;

/* The DOCGROUP resource contains our GenDocumentGroup object, and
 * will contain any GenDocument objects created by the GenDocumentGroup.
 */ 
@start 	DocGroup;

@object GenDocumentGroupClass MCDocumentGroup = {
	GDGI_attrs = (@default | GDGA_VM_FILE_CONTAINS_OBJECTS );
	GDGI_untitledName = "UntitledChart";
	GDGI_documentClass = (ClassStruct *) &MCDocumentClass;
	GDGI_documentControl = MCDocumentControl;
	GDGI_genDisplayGroup = MCDisplayGroup;
	GDGI_genView = MCChartView;
	GDGI_genDisplay = MCDisplay;
	GDGI_protocolMajor = MC_DOCUMENT_PROTOCOL_MAJOR;
	GDGI_protocolMinor = MC_DOCUMENT_PROTOCOL_MINOR;
}

@end 	DocGroup;

/* CODE for MCDocumentClass */

/* MSG_MCD_SET_DATA_ITEM_MONIKER for MCDocumentClass

	SYNOPSIS: Set the moniker for one of our Data List's items.
	CONTEXT: The Data List will send this message to the process
		 whenever it needs to display the moniker of a given
		 item.  We should respond with one of the
		 MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_... messages.
	PARAMS: void (optr list, word item)
*/
@method MCDocumentClass, MSG_MCD_SET_DATA_ITEM_MONIKER {

/* If we're looking for the moniker of the "Data:" item,
 * just return that moniker.  Otherwise, look up the
 * numerical value of the item as stored in the linked list. 
 */
	if (item==FAKE_LIST_ITEM) {
		optr 	ourMoniker;

		ourMoniker = ConstructOptr(OptrToHandle(list),
					   OptrToChunk(@FakeItemMoniker));
		@call list::MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_MONIKER_OPTR(
				FAKE_LIST_ITEM, ourMoniker);}
	else /* item > FAKE_LIST_ITEM */ {
		ChunkHandle 	tempListItem;
		ListNode 	*tempNode;
		char 		monikerString[LOCAL_DISTANCE_BUFFER_SIZE];
		word 		data;
		MemHandle	dataListBlock;
		MemHandle	*dataListBlockPtr = &dataListBlock;
		word 		ordinal = item;

		VMLock(pself->GDI_fileHandle,
		       pself->MCDI_dataFileBlock,
		       dataListBlockPtr);

		for(tempNode = LMemDerefHandles(dataListBlock,
						pself->MCDI_dataListHead);
		    ordinal > 1;
		    --ordinal)
			{
			 tempListItem = tempNode->LN_next;
			 tempNode = LMemDerefHandles(dataListBlock, tempListItem);
			}
		data = tempNode->LN_data;
		VMUnlock(dataListBlock);

		LocalFixedToAscii(monikerString, MakeWWFixed(data), 0);
		@call list::MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_TEXT(
						   item, monikerString);
	}
} /* end of MSG_MCD_SET_DATA_ITEM_MONIKER */

/* MSG_MCD_INSERT_DATA_ITEM for MCDocumentClass
 *
 *	SYNOPSIS: Add a new number to our list of data.
 *	CONTEXT: User wants to add a new piece of data.
 *	PARAMS: void(void)
 */
@method MCDocumentClass, MSG_MCD_INSERT_DATA_ITEM {
	ChunkHandle 	newListItem;
	ListNode 	*newNode;
	WWFixedAsDWord	value;
	word 		ordinal;
	MemHandle 	dataListBlock;
	MemHandle 	*dataListBlockPtr = &dataListBlock;
	optr 		ourList;
	optr 		ourValue;
	optr 		ourChart;
	DataBlockHeader *dataBlockHeader;

/* Query list and data objects to find out where to insert item
 * and what value to insert there. 
 */
	ourList = ConstructOptr(pself->GDI_display,
				OptrToChunk(@MCDataList));
	ordinal = @call ourList::MSG_GEN_ITEM_GROUP_GET_SELECTION();
	ourValue = ConstructOptr(pself->GDI_display,
				 OptrToChunk(@MCValue));
	value = @call ourValue::MSG_GEN_VALUE_GET_VALUE();

/* Lock the data block so we can insert data into the linked list. */
	VMLock(pself->GDI_fileHandle,
	       pself->MCDI_dataFileBlock,
	       dataListBlockPtr);

/* Create a new linked list element. */
	newListItem = LMemAlloc(dataListBlock, sizeof(ListNode));
	newNode = LMemDerefHandles(dataListBlock, newListItem);
	newNode->LN_data = WWFixedToInt(value);

/* Check to see if the item we're adding will be the
 * new head of the data list and handle that case. 
 */
	if (ordinal==0)
	 {
		newNode->LN_next = pself->MCDI_dataListHead;
		pself->MCDI_dataListHead = newListItem;
	 }
	else
/* We're not adding to the head of the list.  Traverse the
 * list using the tempListItem and tempNode variables, then
 * insert the new item. 
 */
	 {
		ListNode 	*tempNode;
		ChunkHandle 	tempListItem;
		word 		count = ordinal;

		for (tempNode = LMemDerefHandles(dataListBlock,
						 pself->MCDI_dataListHead);
		     count > 1;
		     --count)
		 {
			tempListItem = tempNode->LN_next;
			tempNode = LMemDerefHandles(dataListBlock,
					  	    tempListItem);
		 }
		newNode->LN_next = tempNode->LN_next;
		tempNode->LN_next = newListItem;
	 }

	dataBlockHeader = MemDeref(dataListBlock);
	dataBlockHeader->DBH_listHead = pself->MCDI_dataListHead;
	dataBlockHeader->DBH_numItems++;

/* We've changed the data, so before we unlock the block, we mark
 * it dirty. 
 */
	VMDirty(dataListBlock);
	VMUnlock(dataListBlock);

/* Update the data list gadget. */
	@send ourList::MSG_GEN_DYNAMIC_LIST_ADD_ITEMS(ordinal+1, 1);
	@send ourList::MSG_GEN_ITEM_GROUP_SET_SINGLE_SELECTION(ordinal+1,
								  FALSE);

/* Update the chart */
	ourChart = ConstructOptr(pself->MCDI_chartMemBlock,
				 OptrToChunk(@MCChart));
	@send ourChart::MSG_MCC_INSERT_BAR(ordinal, WWFixedToInt(value));
} /* end of MSG_MCD_INSERT_DATA_ITEM */

/* MSG_MCD_DELETE_DATA_ITEM for MCDocumentClass
 *
 *	SYNOPSIS: Destroys one data item.
 *	CONTEXT: User has just clicked on the "Delete" trigger.
 *	PARAMS: void (void)
 */
@method MCDocumentClass, MSG_MCD_DELETE_DATA_ITEM {

	word 		ordinal;
	word 		count;
	ChunkHandle  	oldItem;
	ListNode	*oldNode;
	MemHandle 	dataListBlock;
	MemHandle 	*dataListBlockPtr = &dataListBlock;
	ChunkHandle 	tempListItem;
	ListNode 	*tempNode;
	optr 		ourList;
	optr 		ourChart;
	DataBlockHeader *dataBlockHeader;

	ourList = ConstructOptr(pself->GDI_display,
				OptrToChunk(MCDataList));

	ordinal = @call ourList::MSG_GEN_ITEM_GROUP_GET_SELECTION();
	if (ordinal==FAKE_LIST_ITEM) return;
	count = ordinal;

	VMLock(pself->GDI_fileHandle,
	       pself->MCDI_dataFileBlock,
	       dataListBlockPtr);

	if (ordinal == 1) {
		oldNode = LMemDerefHandles(dataListBlock,
					   pself->MCDI_dataListHead);
		tempListItem = oldNode->LN_next;
		LMemFreeHandles(dataListBlock, pself->MCDI_dataListHead);
		pself->MCDI_dataListHead = tempListItem;
	 }
	else {
		for (tempNode=LMemDerefHandles(dataListBlock,
					       pself->MCDI_dataListHead);
		     count > 2;
		     --count)
		 {
			tempListItem = tempNode->LN_next;
			tempNode = LMemDerefHandles(dataListBlock,
						    tempListItem);
		 }
		oldItem = tempNode->LN_next;
		oldNode = LMemDerefHandles(dataListBlock, oldItem);

		tempNode->LN_next = oldNode->LN_next;
		LMemFreeHandles(dataListBlock, oldItem);
	 }

	dataBlockHeader = MemDeref(dataListBlock);
	dataBlockHeader->DBH_listHead = pself->MCDI_dataListHead;
	dataBlockHeader->DBH_numItems--;

	VMDirty(dataListBlock);
	VMUnlock(dataListBlock);

	@send ourList::MSG_GEN_DYNAMIC_LIST_REMOVE_ITEMS(ordinal, 1);
	@send ourList::MSG_GEN_ITEM_GROUP_SET_SINGLE_SELECTION(ordinal-1,
								  FALSE);

	ourChart = ConstructOptr(pself->MCDI_chartMemBlock,
				 OptrToChunk(@MCChart));

	@send ourChart::MSG_MCC_DELETE_BAR(ordinal-1);
} /* end of end of MSG_MCD_DELETE_DATA_ITEM */ 


/* MSG_MCD_SET_DATA_ITEM for MCDocumentClass
 *
 *	SYNOPSIS: Change the data number of one item in the data list.
 *	CONTEXT: User has clicked the "Change" button.
 *	PARAMS: void(void)
 */
@method MCDocumentClass, MSG_MCD_SET_DATA_ITEM {
	word 		ordinal;
	WWFixedAsDWord 	value;
	char 		monikerString[LOCAL_DISTANCE_BUFFER_SIZE];
	word 		count;
	MemHandle 	dataListBlock;
	MemHandle	*dataListBlockPtr = &dataListBlock;
	ChunkHandle 	tempListItem;
	ListNode 	*tempNode;
	optr 		ourList;
	optr 		ourValue;
	optr 		ourChart;

/* Find out which item we're changing. */
	ourList = ConstructOptr(pself->GDI_display,
				OptrToChunk(MCDataList));
	ordinal = @call ourList::MSG_GEN_ITEM_GROUP_GET_SELECTION();
	if (ordinal == FAKE_LIST_ITEM) return;

/* Find out what the item's new value should be. */
	ourValue = ConstructOptr(pself->GDI_display,
				 OptrToChunk(MCValue));
	value = @call ourValue::MSG_GEN_VALUE_GET_VALUE();

/* Lock the data block so that we can change the data. */
	VMLock(pself->GDI_fileHandle,
	       pself->MCDI_dataFileBlock,
	       dataListBlockPtr);
/* Find the appropriate item in the linked list and change its value. */
	for (tempNode = LMemDerefHandles(dataListBlock,
					 pself->MCDI_dataListHead),
	     count = ordinal-1;
	     count > 0;
	     --count)
	 {
		tempListItem = tempNode->LN_next;
		tempNode = LMemDerefHandles(dataListBlock, tempListItem);
	 }
	tempNode->LN_data = WWFixedToInt(value);

/* We changed the data so mark it dirty before unlocking it. */
	VMDirty(dataListBlock);
	VMUnlock(dataListBlock);

/* Update the data list gadget. */
	LocalFixedToAscii(monikerString, value, 0);
	@call ourList::MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_TEXT(
							ordinal,
							monikerString);

/* Update the chart. */
	ourChart = ConstructOptr(pself->MCDI_chartMemBlock,
				 OptrToChunk(@MCChart));
	@send ourChart::MSG_MCC_RESIZE_BAR(ordinal-1, WWFixedToInt(value));
} /* end of MSG_MCD_SET_DATA_ITEM */

/* MSG_GEN_DOCUMENT_INITIALIZE_DOCUMENT_FILE for MCDocumentClass
 *
 *	SYNOPSIS: Duplicate blocks and create a blank header.
 *	CONTEXT: Creating a new document.
 * 	PARAMS: Boolean(void);
 */
@method MCDocumentClass, MSG_GEN_DOCUMENT_INITIALIZE_DOCUMENT_FILE {
	VMBlockHandle 		chartFileBlock;
	MemHandle 		dataListBlock;
	MemHandle 		*dataListBlockPtr = &dataListBlock;
	DataBlockHeader 	*dataBlockHeader;

/* If superclass hits an error while trying to create the file, return
 * immediately, signalling that we hit an error. 
 */
	if (@callsuper()) return(TRUE); 
	pself = ObjDerefGen(oself);

/* Create the block which will hold our MCChart. */
	pself->MCDI_chartMemBlock =
			ObjDuplicateResource(OptrToHandle(@MCChart), 0, 0);
	chartFileBlock = VMAttach(pself->GDI_fileHandle,
				  0,
				  pself->MCDI_chartMemBlock);
	VMPreserveBlocksHandle(pself->GDI_fileHandle,
			       chartFileBlock);

/* Create the block which will hold our linked list */
	pself->MCDI_dataFileBlock = VMAllocLMem(pself->GDI_fileHandle,
						LMEM_TYPE_GENERAL,
						sizeof(DataBlockHeader));
	VMSetMapBlock(pself->GDI_fileHandle, pself->MCDI_dataFileBlock);
	VMLock(pself->GDI_fileHandle,
	       pself->MCDI_dataFileBlock,
	       dataListBlockPtr);
	dataBlockHeader = MemDeref(dataListBlock);
	dataBlockHeader->DBH_listHead = NULL;
	dataBlockHeader->DBH_numItems = 1;
	dataBlockHeader->DBH_chartBlock = chartFileBlock;
	VMDirty(dataListBlock);
	VMUnlock(dataListBlock);

/* Assume no error encountered */
	return(FALSE); 
} /* end of MSG_GEN_DOCUMENT_INITALIZE_DOCUMENT_FILE */

/* MSG_GEN_DOCUMENT_ATTACH_UI_TO_DOCUMENT for MCDocumentClass
 *
 * 	SYNOPSIS: Load in chart block, initialize data list gadget.
 *	CONTEXT: Opening a file.
 *	PARAMS: void(void) 
 */
@method MCDocumentClass, MSG_GEN_DOCUMENT_ATTACH_UI_TO_DOCUMENT {
	MemHandle 		dataListBlock;
	MemHandle 		*dataListBlockPtr = &dataListBlock;
	DataBlockHeader         *dataBlockHeader;
	word 			numItems;
	optr 			ourList;
	VMBlockHandle		chartFileBlock;
	optr 			ourChart;

	@callsuper();
	pself = ObjDerefGen(oself);

/* Get the block that contains our linked list and header info */
	pself->MCDI_dataFileBlock = VMGetMapBlock(pself->GDI_fileHandle);
	dataBlockHeader = VMLock(pself->GDI_fileHandle,
				 pself->MCDI_dataFileBlock,
				 dataListBlockPtr);

/* Extract the header info. */
	pself->MCDI_dataListHead = dataBlockHeader->DBH_listHead;
	numItems = dataBlockHeader->DBH_numItems;
	chartFileBlock = dataBlockHeader->DBH_chartBlock;
	VMUnlock(dataListBlock);

/* Made the MCChart a child of the document object. */
	pself->MCDI_chartMemBlock = VMVMBlockToMemBlock(pself->GDI_fileHandle,
							chartFileBlock);
	ourChart = ConstructOptr(pself->MCDI_chartMemBlock,
				 OptrToChunk(@MCChart));
	@send self::MSG_VIS_ADD_NON_DISCARDABLE_VM_CHILD(ourChart, CCO_FIRST);

/* Initialize the data list gadget. */
	ourList = ConstructOptr(pself->GDI_display,
				OptrToChunk(@MCDataList));
	@send ourList::MSG_GEN_DYNAMIC_LIST_INITIALIZE(numItems);
	@send ourList::MSG_GEN_ITEM_GROUP_SET_SINGLE_SELECTION(0, FALSE);

} /* end of MSG_GEN_DOCUMENT_ATTACH_UI_TO_DOCUMENT */

/* MSG_GEN_DOCUMENT_DETACH_UI_FROM_DOCUMENT
 *
 *	SYNOPSIS: Remove the chart object.
 * 	CONTEXT: Closing the document.
 *	PARAMS: void(void)
 */
@method MCDocumentClass, MSG_GEN_DOCUMENT_DETACH_UI_FROM_DOCUMENT {
	optr 			ourChart;

	@callsuper();

/* Remove the chart from the document. */
	ourChart = ConstructOptr(pself->MCDI_chartMemBlock,
				 OptrToChunk(@MCChart));
	@call ourChart::MSG_VIS_REMOVE_NON_DISCARDABLE( VUM_MANUAL );
}

/* CODE for MCChartClass */

/* MSG_VIS_DRAW for MCChartClass
 *
 *	SYNOPSIS: Draw the chart.
 *	CONTEXT: System has asked the chart object to draw itself.
 *	PARAMS: void(DrawFlags drawFlags, GStateHandle gstate)
 */
@method MCChartClass, MSG_VIS_DRAW {
	word 	count;
	word 	*barArray;

/* Draw the axis markers */
	GrDrawVLine(gstate,
		    CHART_LEFT-BORDER_MARGIN,
		    BORDER_MARGIN,
		    CHART_BOTTOM);
	GrDrawHLine(gstate,
		    CHART_LEFT,
		    CHART_BOTTOM+BORDER_MARGIN,
		    VIEW_RIGHT - BORDER_MARGIN);
	barArray = LMemDerefHandles(OptrToHandle(oself),
				    pself->MCCI_barArray);

/* Draw the bars */
	for (count = 0; count < pself->MCCI_numBars; count++)
	 {
		word 	top, bottom, left, right;

		bottom = CHART_BOTTOM;
		top = bottom - barArray[count];
		left = CHART_LEFT + (count * BAR_WIDTH_ALLOW);
		right = left + BAR_WIDTH;
		GrFillRect(gstate, left, top, right, bottom);
	 }
} /* end of MSG_VIS_DRAW */

/* MSG_MCC_INSERT_BAR
 *
 *	SYNOPSIS: Add another bar to bar chart.
 *	CONTEXT: The user has added another data item to the list.
 *	PARAMS: void(word ordinal, word value);
 */
@method MCChartClass, MSG_MCC_INSERT_BAR {
	word 		count;
	word 		*barArray;

/* Insert new bar into array, shifting other bars over */
	barArray = LMemDerefHandles(OptrToHandle(oself), pself->MCCI_barArray);
	for(count=pself->MCCI_numBars; count > ordinal; --count)
	 {
		barArray[count] = barArray[count-1];
	 }
	 barArray[ordinal] = value;

	pself->MCCI_numBars++;

/* Mark ourself as in need of a redraw. */
	@call self::MSG_VIS_MARK_INVALID(VOF_IMAGE_INVALID,
					 VUM_DELAYED_VIA_APP_QUEUE);
} /* end of MSG_MCC_INSERT_BAR */

/* MSG_MCC_DELETE_BAR
 *
 *	SYNOPSIS: Remove a bar from the bar chart.
 *	CONTEXT: User has deleted a data item from the list.
 *	PARAMS: void(word ordinal);
 */
@method MCChartClass, MSG_MCC_DELETE_BAR {
	word 		count;
	word 		*barArray;

/* Update our instance data and data array */
	pself->MCCI_numBars -=1;
	barArray = LMemDerefHandles(OptrToHandle(oself),
				    pself->MCCI_barArray);
	for(count=ordinal; count < pself->MCCI_numBars; count++)
	 {
		barArray[count] = barArray[count+1];
	 }

/* Mark ourselves as in need of a redraw. */
	@call self::MSG_VIS_MARK_INVALID(VOF_IMAGE_INVALID,
					 VUM_DELAYED_VIA_APP_QUEUE);
} /* end of MSG_MCC_DELETE_BAR */

/* MSG_MCC_RESIZE_BAR
 *
 *	SYNOPSIS: Resize a bar.
 *	CONTEXT: User has changed the value of a data item.
 *	PARAMS: void(word ordinal, word value);
 */
@method MCChartClass, MSG_MCC_RESIZE_BAR {
	word 		*barArray;

/* Update the array */
	barArray = LMemDerefHandles(OptrToHandle(oself),
				    pself->MCCI_barArray);
	barArray[ordinal] = value;

/* Mark ourself as in need of a redraw. */
	@call self::MSG_VIS_MARK_INVALID(VOF_IMAGE_INVALID,
					 VUM_DELAYED_VIA_APP_QUEUE);
} /* end of MSG_MCC_RESIZE_BAR */
