Milán Major

netscape-revival

1 branch
Code

lib/mac/VText/V_PowerPlant_Mods/LSelection.cp

//	===========================================================================
//	LSelection.cp					©1994 Metrowerks Inc. All rights reserved.
//	===========================================================================

#ifdef PowerPlant_PCH
#include PowerPlant_PCH
#endif

#include	"LSelection.h"
#include	<PP_Resources.h>
#include	<LListIterator.h>
#include	<LSelectableItem.h>
#include	<UDrawingState.h>
#include	<UAppleEventsMgr.h>
#include	<UAEGizmos.h>
#include	<LDataTube.h>
#include	<LTubeableItem.h>
#include	<URegions.h>
#include	<LStream.h>
#include	<LSelectionModelAEOM.h>
#include	<AERegistry.h>
#include	<AEPowerPlantSuite.h>
#include	<AEDebugSuite.h>

#ifndef	__AEOBJECTS__
#include <AEObjects.h>
#endif

#ifndef __AEREGISTRY__
#include <AERegistry.h>
#endif

#pragma	warn_unusedarg off

//	===========================================================================
//	Maintenance

LSelection::LSelection()
	:	LSelectableItem(NULL, cProperty)
{
	SetModelKind(pSelection);
	
	mRecordAllSelections = false;
	
//-	mRecordAllSelections = true;	//	True for debugging ONLY
	
	mAnchor = NULL;
	mSelectionID = 0;
}

LSelection::~LSelection(void)
{
	ListClear();
}

#ifndef	PP_No_ObjectStreaming

void	LSelection::WriteObject(LStream &inStream) const
{	
	//	for this
	inStream << mRecordAllSelections;

	//	for LSelectableItem
	LSelectionModelAEOM	*model = dynamic_cast<LSelectionModelAEOM *>(GetSuperModel());
	inStream << model;
}

void	LSelection::ReadObject(LStream &inStream)
{
	//	for this
	inStream >> mRecordAllSelections;
	
	SetSuperModel(ReadStreamable<LSelectionModelAEOM *>()(inStream));
}

#endif

//	===========================================================================
//	New features

void	LSelection::SelectSimple(LSelectableItem *inItem)
{
	SelectionAboutToChange();

	ListClear();
	ListAddItem(inItem);
	
	SelectionChanged();

	if (mRecordAllSelections)
		RecordPresentSelection();
	
	ReplaceSharable(mAnchor, inItem, this);
}

void	LSelection::SelectContinuous(LSelectableItem *inItem)
/*
	May need to override depending on implementation strategy for continuous
	selections.
*/
{
	if (!mAnchor) {
		SelectDiscontinuous(inItem);
	} else {
		StSharer		lockItem(inItem);
		LSelectableItem	*lastItem = ListNthItem(ListCount()),
						*newItem = mAnchor->FindExtensionItem(inItem),
						*p;
		StSharer		lockNew(newItem);

		ThrowIfNULL_(newItem);
		
		SelectionAboutToChange();
		
		if (lastItem)
			ListRemoveItem(lastItem);
		p = ListDependentItem(newItem);
		if (p)
			ListRemoveItem(p);
		ListAddItem(newItem);
		
		SelectionChanged();
		
		if (mRecordAllSelections)
			RecordPresentSelection();
	}
}

void	LSelection::SelectDiscontinuous(LSelectableItem *inItem)
{
	if (mRecordAllSelections) {
		StAEDescriptor	desc,
						temp;
		
		if (inItem) {
			StAEDescriptor	appleEvent;
			LModelObject	*super = GetSuperModel();
			if (super != NULL) {
			
				//	In an AE context
				StAEDescriptor	newValue;
				LAEStream		s;
				
				s.OpenList();
				s.WriteSpecifier(this);
				s.WriteSpecifier(inItem);
				s.CloseList();				
				s.Close(&newValue.mDesc);
				
				DoAESelection(newValue);
			}
		}
	} else {

		SelectionAboutToChange();

		LSelectableItem	*p;
		p = ListDependentItem(inItem);
		if (p) {
			ListRemoveItem(p);
		} else {
			ListAddItem(inItem);
		}

		SelectionChanged();
	}
	
	ReplaceSharable(mAnchor, inItem, this);
}

//	===========================================================================
//	Query

Boolean	LSelection::IsSubstantive(void) const
/*
	Does the selection contain data that can be copied & cut?
*/
{
	Boolean	rval = false;
	
	LListIterator	iter(((LSelection *)this)->mItems, iterate_FromStart);
	LSelectableItem	*p = NULL;
	
	while (iter.Next(&p)) {
		Assert_(p);
		if (p->IsSubstantive()) {
			rval = true;
			break;
		}
	}
	
	return rval;
}

Int32	LSelection::SelectionID(void) const
/*
	Client code can use SelectionID to return a "serial" number to tell if
	the selection has changed (the SelectionID return value will be different
	than the previous value).
	
	The msg_SelectionChanged broadcast indicates the selection did just change.
	The mechanism you use depends on what makes most sense.
*/
{
	return mSelectionID;
}

LSelectableItem *	LSelection::ListEquivalentItem(const LSelectableItem *inItem) const
/*
	Returns a pointer to the selection item that is equivalent to inItem.
	
	If no selection item is equivalent to inItem, NULL is returned.
	
	Equivalent means the model objects resolve to the same set of 
	actual objects.
	
	Notes:
			You may treat the return value as a boolean indicating whether
			the present selection includes the inItem.
			
			For non-lazy objects, the return value may be inItem.

			For lazy objects, the return value will be the pointer to the
				selection's copy of the equivalent inItem.  If inItem is
				one of the selection's lazy objects, the return value will
				be inItem.
				
			If a value is returned consider it an object owned by the
			selection.  Specifically, don't dispose or Finalize it.
			If you need it in a longer term scenario, follow the policies
			regarding objects of class LSharable.
*/
{
	LListIterator	iter(((LSelection *)this)->mItems, iterate_FromStart);
	LSelectableItem	*p = NULL;
	
	while (iter.Next(&p)) {
		Assert_(p);
		if (p->EquivalentTo(inItem))
			return p;
	}
	
	return NULL;
}

LSelectableItem *	LSelection::ListDependentItem(const LSelectableItem *inItem) const
/*
	Returns a pointer to a selection item that is not independent from inItem.
	
	If all selection items are independent from inItem, NULL is returned.
	
	Independent means none of the set of actual objects indicated by inItem
	are represented in the set indicated by the selection.
	
	Notes:
			You may treat the return value as a boolean indicating whether
			the present selection is dependent with inItem.
			
			For non-lazy objects, the return value may be inItem.

			For lazy objects, the return value will be the pointer to the
				selection's copy of the equivalent inItem.  If inItem is
				one of the selection's lazy objects, the return value will
				be inItem.
				
			If a value is returned consider it an object owned by the
			selection.  Specifically, don't dispose or Finalize it.
			If you need it in a longer term scenario, follow the policies
			regarding objects of class LSharable.
*/
{
	LListIterator	iter(((LSelection *)this)->mItems, iterate_FromStart);
	LSelectableItem	*p = NULL;
	
	while (iter.Next(&p)) {
		Assert_(p);
		if (!p->IndependentFrom(inItem))
			return p;
	}
	
	return NULL;
}

LSelectableItem *	LSelection::ListAdjacentItem(const LSelectableItem *inItem) const
/*
	Returns a pointer to the first selection item that is adjacent with inItem.
	
	If no selection items adjacent with inItem, NULL is returned.
	
	Adjacent means two objects share "boundaries."
	are represented in the set indicated by the selection.
	
	Notes:
			For non-lazy objects, the return value may be inItem.

			For lazy objects, the return value will be the pointer to the
				selection's copy of the equivalent inItem.  If inItem is
				one of the selection's lazy objects, the return value will
				be inItem.
				
			If a value is returned consider it an object owned by the
			selection.  Specifically, don't dispose or Finalize it.
			If you need it in a longer term scenario, follow the policies
			regarding objects of class LSharable.
*/
{
	LListIterator	iter(((LSelection *)this)->mItems, iterate_FromStart);
	LSelectableItem	*p = NULL;
	
	while (iter.Next(&p)) {
		Assert_(p);
		if (p->AdjacentWith(inItem))
			return p;
	}
	
	return NULL;
}

//	===========================================================================
//	Implementation -- manipulator

Boolean		LSelection::EquivalentTo(const LSelectableItem *inItem) const
{
	if (ListCount() == 1)
		return ListEquivalentItem(inItem) != NULL;
	else
		return false;
}

Boolean		LSelection::AdjacentWith(const LSelectableItem *inItem) const
{
	if (ListCount() == 1)
		return ListAdjacentItem(inItem) != NULL;
	else
		return false;
}

Boolean		LSelection::IndependentFrom(const LSelectableItem *inItem) const
{
	return ListDependentItem(inItem) == NULL;
}

Boolean		LSelection::PointInRepresentation(Point inWhere) const
{
	LListIterator	iter(((LSelection *)this)->mItems, iterate_FromStart);
	LSelectableItem	*p = NULL;
	
	if (!IsSubstantive())
		return false;
		
	while (iter.Next(&p)) {
		if (p->PointInRepresentation(inWhere))
			return true;
	}
	
	return false;
}

void	LSelection::DrawSelf(void)
{
	DrawLatent();
}

void	LSelection::UnDrawSelf(void)
{
}

void	LSelection::MakeRegion(Point inOrigin, RgnHandle *ioRgn) const
/*
	Resulting region will have it's origin as inOrigin --
	this means the region can be independent of coordinate
	system origin.
*/
{
	Boolean		createdRgn = false;
	OSErr		err = noErr;
	
	ThrowIfNULL_(ioRgn);

	if (*ioRgn == NULL) {
		*ioRgn = NewRgn();
		createdRgn = true;
	}
	ThrowIfNULL_(*ioRgn);
	
	Try_ {
		
		//	Get latent region
		{
			StRegionBuilder	buildRgn(*ioRgn);
			
//			DrawSelected();	//	doesn't work for text case.
			((LSelection *)this)->DrawLatent();	//	this works for any case
		}
		
		//	Set "origin" of region
		Point	offset;
		offset.h = -inOrigin.h;
		offset.v = -inOrigin.v;
		URegions::Translate(*ioRgn, offset);
	} Catch_(inErr) {

		if (createdRgn && (*ioRgn == NULL)) {
			DisposeRgn(*ioRgn);
			*ioRgn = NULL;
		}
		
		Throw_(inErr);
	} EndCatch_;
}

void	LSelection::DrawContent(void)
/*
	Draw selection content.
	
	This routine should be rarely called
		-- only for something like "print selection."
*/
{
	LListIterator		iter(mItems, iterate_FromStart);
	LSelectableItem		*p;
	
	while(iter.Next(&p)) {
		p->DrawSelf();
	}
}

//	===========================================================================
//	Implementation

void	LSelection::DrawSelected(void)
{
	LListIterator		iter(mItems, iterate_FromStart);
	LSelectableItem		*p;
	
	while(iter.Next(&p)) {
		p->DrawSelfSelected();
	}
}

void	LSelection::DrawLatent(void)
{
	LListIterator		iter(mItems, iterate_FromStart);
	LSelectableItem		*p;
	
	while(iter.Next(&p)) {
		p->DrawSelfLatent();
	}
}

void	LSelection::UnDrawSelected(void)
{
	LListIterator		iter(mItems, iterate_FromStart);
	LSelectableItem		*p;

	while(iter.Next(&p)) {
		p->UnDrawSelfSelected();
	}
}

void	LSelection::UnDrawLatent(void)
{
	LListIterator		iter(mItems, iterate_FromStart);
	LSelectableItem		*p;
	
	while(iter.Next(&p)) {
		p->UnDrawSelfLatent();
	}
}

//	===========================================================================
//	Data tubing

void	LSelection::AddFlavorTypesTo(LDataTube *inTube) const
{
	ThrowIfNULL_(inTube);
	
	inTube->AddFlavor(typeObjectSpecifier);	//	for selection
	
	//	items...
	LSelectableItem	*item;
	LListIterator	iter(((LSelection *)this)->mItems, iterate_FromStart);
	
	while (iter.Next(&item)) {
		inTube->OpenItem(item);
	
		item->AddFlavorTypesTo(inTube);
		
		inTube->CloseItem();
	}
}

Boolean	LSelection::SendFlavorTo(FlavorType inFlavor, LDataTube *inTube) const
{
	Boolean	rval = false;
	
	if (inFlavor == typeObjectSpecifier) {
		StAEDescriptor	ospec;
		MakeSpecifier(ospec.mDesc);
		inTube->SetFlavorHandle(typeObjectSpecifier, ospec.mDesc.dataHandle);
		rval = true;
	}
	
	return rval;
}

FlavorType	LSelection::PickFlavorFrom(const LDataTube *inTube) const
{
	ThrowIfNULL_(inTube);

	LSelectableItem	*selItem;
	LListIterator	iter(((LSelection *)this)->mItems, iterate_FromStart);
	FlavorType		flavor,
					rval = typeNull;
	
	while (iter.Next(&selItem)) {
		flavor = selItem->PickFlavorFrom(inTube);

		if (flavor == typeNull) {
			rval = typeNull;
			break;
		}
		
		if (rval == typeNull) {
			rval = flavor;
		} else {
			rval = typeWildCard;
		}
	}

	return rval;
}

void	LSelection::ReceiveDataFrom(LDataTube *inTube)
{
	SelectionAboutToChange();
	
	LSelectableItem	*selItem;
	LListIterator	iter(mItems, iterate_FromStart);
	
	Assert_(inTube);

	while (iter.Next(&selItem)) {
		selItem->ReceiveDataFrom(inTube);
	}

	SelectionChanged();
}

//	===========================================================================
//	AEOM support

void	LSelection::DoAESelection(const AEDesc &inValue, Boolean inExecute)
{
	LModelObject	*super = GetSuperModel();
	
	if (super == NULL)
		return;		//	not being used in an AE context.
	
	StAEDescriptor	appleEvent;
	{
		LAEStream		s(kAEMiscStandards, kAESelect);
		
		//	keyDirectObject
		if (!super->IsDefaultSubModel()) {
			s.WriteKey(keyDirectObject);
			s.WriteSpecifier(super);
		}
		
		//	keyAEData (what to select)
		s.WriteKey(keyAEData);
		s.WriteDesc(inValue);
		
		s.Close(&appleEvent.mDesc);
	}
	
	UAppleEventsMgr::SendAppleEvent(appleEvent.mDesc, inExecute);
}

void	LSelection::DoAESelectAsResult(
	const AEDesc	&inValue,
	DescType		inPositionModifier
)
{
#if	1
	//	Till we figure out a way of recording a reference to result in a script
	StAEDescriptor		modifiedSelection,
						rubbish;

	if (inPositionModifier != typeNull) {
		if (inValue.descriptorType != typeNull) {
			switch (inPositionModifier) {
			
				//	Unknown selection modifiers
				default:
					Assert_(false);
					
				//	Normal selection modifiers.
				case kAEBefore:
				case kAEAfter:
					//	fall through
	
				//	Possibly useful selection modifiers
				case kAEBeginning:
				case kAEEnd:
					UAEDesc::MakeInsertionLoc(inValue, inPositionModifier, &modifiedSelection.mDesc);
					break;

				case kAEReplace:
					//	Why make an insertion loc when we already have the needed
					//	ospec?
					break;
				
			}
			if (modifiedSelection.mDesc.descriptorType == typeNull)
				SetAEValue(inValue, rubbish.mDesc);
			else
				SetAEValue(modifiedSelection.mDesc, rubbish.mDesc);
		}
	}
#else
	//	This is what it should be for serialized selections...
		if (inValue.descriptorType != typeNull) {
		LAESubDesc		valSD(inValue);
		
		Assert_(inValue.descriptorType == typeObjectSpecifier);
		DescType		elemType = valSD.KeyedItem(keyAEDesiredClass).ToType();
		
		//	Adjust the actual selection
		{
			StAEDescriptor	bogus;
			StAEDescriptor	modifiedSelection;
			LAEStream		stream;
			
			switch (inPositionModifier) {
		
				//	Normal position modifiers.
				case pBeginning:
				case pEnding:
				case pBefore:
				case pAfter:
					stream.OpenRecord(typeObjectSpecifier);
						stream.WriteKey(keyAEDesiredClass);
						stream.WriteTypeDesc(elemType);
						stream.WriteKey(keyAEContainer);
						stream.WriteDesc(inValue);
						stream.WriteKey(keyAEKeyForm);
						stream.WriteEnumDesc(formPropertyID);
						stream.WriteKey(keyAEKeyData);
						stream.WriteTypeDesc(inPositionModifier);
					stream.CloseRecord();
					stream.Close(&modifiedSelection.mDesc);
					SetAEValue(modifiedSelection, bogus.mDesc);
					break;
		
				case pSame:
					SetAEValue(inValue, bogus.mDesc);
					break;
				
				//	Unknown position modifier
				default:
					Assert_(false);
					break;
			}
		}
		
		//	Record using "result"
		{
			StAEDescriptor	bogus,
							event;
			LAEStream		stream(kAEMiscStandards, kAESelect);
			LModelObject	*super = GetSuperModel();
			AEDesc			currentContainer = {typeNull, NULL};
			
			if (!super->IsDefaultSubModel()) {
				stream.WriteKey(keyDirectObject);
				stream.WriteSpecifier(super);
			}
			
			//	keyAEData (what to select)
			stream.WriteKey(keyAEData);
			stream.OpenRecord(typeObjectSpecifier);
	
			switch (inPositionModifier) {
	
				case pSame:
					stream.WriteKey(keyAEDesiredClass);
					stream.WriteTypeDesc(elemType);
					stream.WriteKey(keyAEContainer);
					stream.WriteDesc(currentContainer);
					stream.WriteKey(keyAEKeyForm);
					stream.WriteEnumDesc(formPropertyID);
					stream.WriteKey(keyAEKeyData);
					stream.WriteTypeDesc(pResult);
					break;
					
				case pBeginning:
				case pEnding:
				case pBefore:
				case pAfter:
					stream.WriteKey(keyAEDesiredClass);
					stream.WriteTypeDesc(elemType);
					
					stream.WriteKey(keyAEContainer);
					stream.OpenRecord(typeObjectSpecifier);
						stream.WriteKey(keyAEDesiredClass);
						stream.WriteTypeDesc(elemType);
						stream.WriteKey(keyAEContainer);
						stream.WriteDesc(currentContainer);
						stream.WriteKey(keyAEKeyForm);
						stream.WriteEnumDesc(formPropertyID);
						stream.WriteKey(keyAEKeyData);
						stream.WriteTypeDesc(pResult);
					stream.CloseRecord();
			
					stream.WriteKey(keyAEKeyForm);
					stream.WriteEnumDesc(formPropertyID);
					stream.WriteKey(keyAEKeyData);
					stream.WriteTypeDesc(inPositionModifier);
					break;
			}
			stream.CloseRecord();
			stream.Close(&event.mDesc);
	
			//	record select event
			UAppleEventsMgr::SendAppleEvent(event.mDesc, false);
		}
	}
#endif
}

void	LSelection::RecordPresentSelection(void)
{
	StAEDescriptor	requestedType,
					value;

	{
		LAEStream		typeStream;
		
		typeStream.OpenList();
		typeStream.WriteTypeDesc(typeObjectSpecifier);
		typeStream.CloseList();
		
		typeStream.Close(&requestedType.mDesc);
	}
	
	GetAEValue(requestedType.mDesc, value.mDesc);
	DoAESelection(value.mDesc, false);
}

void	LSelection::MakeSelfSpecifier(
	AEDesc&		inSuperSpecifier,
	AEDesc&		outSelfSpecifier) const
{
	LAEStream	stream;
	
	stream.OpenRecord(typeObjectSpecifier);		
		stream.WriteKey(keyAEDesiredClass);
		stream.WriteTypeDesc(cProperty);

		stream.WriteKey(keyAEKeyForm);
		stream.WriteEnumDesc(formPropertyID);
	
		stream.WriteKey(keyAEKeyData);
		stream.WriteTypeDesc(GetModelKind());	

		stream.WriteKey(keyAEContainer);
		stream.WriteSpecifier(mSuperModel);
	stream.CloseRecord();
	
	stream.Close(&outSelfSpecifier);
}

void	LSelection::GetAEValue(
	const AEDesc	&inRequestedType,
	AEDesc			&outPropertyDesc) const
{
	if (((LModelObject *)this)->GetSuperModel() == NULL)
		return;		//	not being used in an AE context.
		
//	inRequestedType is ignored in preference to typeObjectSpecifier
	OSErr	err;
	LListIterator	iter( ((LSelection *)this)->mItems, iterate_FromStart);
	LSelectableItem	*p = NULL;
	StAEDescriptor	tempDesc,
					typeDesc;
	
	{
		LAEStream	typeStream;
		typeStream.OpenList();
		typeStream.WriteTypeDesc(typeObjectSpecifier);
		typeStream.CloseList();
		typeStream.Close(&typeDesc.mDesc);
	}
	
	if (mItems.GetCount() == 0) {
		//	Force an empty list to be made
		err = AECreateList(NULL, 0, false, &outPropertyDesc);
		ThrowIfOSErr_(err);
	}

	while (iter.Next(&p)) {
		p->GetAEProperty(pContents, typeDesc, tempDesc.mDesc);
		UAEDesc::AddDesc(&outPropertyDesc, 0, tempDesc.mDesc);
		err = AEDisposeDesc(&tempDesc.mDesc);
		ThrowIfOSErr_(err);
	}
}

class StLockListItems
{
public:
						StLockListItems(LList &inList, LModelObject *inSuperModel);
						~StLockListItems();
protected:
	LList			*mList;
	LModelObject	*mSuperModel;	//	to scope incoming items
};

StLockListItems::StLockListItems(LList &inList, LModelObject *inSuperModel)
{
	mList = &inList;
	mSuperModel = inSuperModel;
	
	LModelObject		*model;
	LSelectableItem		*p;
	
	LListIterator	iter(*mList, iterate_FromStart);
	
	while(iter.Next(&model)) {
		if (!model->IsSubModelOf(mSuperModel))
			continue;

		p = (LSelectableItem *) model;	//	unsafe cast!
//		Assert_(member(p, LSelectableItem));

		p->AddUser(this);
	}
}

StLockListItems::~StLockListItems()
{
	LModelObject		*model;
	LSelectableItem		*p;
	
	LListIterator	iter(*mList, iterate_FromStart);
	
	while(iter.Next(&model)) {
		if (!model->IsSubModelOf(mSuperModel))
			continue;

		p = (LSelectableItem *) model;	//	unsafe cast!
//		Assert_(member(p, LSelectableItem));

		p->RemoveUser(this);
	}
}

void	LSelection::SetAEValue(
	const AEDesc		&inValue,
	AEDesc&				outAEReply)
{
	LAESubDesc		value(inValue);
	LList			newItems;
	Boolean			appending = false;
	
	//	build new items list
	switch(value.GetType()) {

		case typeNull:
			break;

		case typeObjectSpecifier:
		{
			LModelObject	*model = value.ToModelObject();
			newItems.InsertItemsAt(1, arrayIndex_Last, &model);
			break;
		}
		
		case typeAEList:
		{
			Int32	n = value.CountItems(),
					i;
			
			for (i = 1; i <= n; i++) {
				LModelObject	*model = value.NthItem(i).ToModelObject();
				if (model == this) {
					if (i != 1) {
						//	Can only add to end of the selection
						ThrowOSErr_(errAEEventNotHandled);
					}
					appending = true;
					continue;
				} else {
					newItems.InsertItemsAt(1, arrayIndex_Last, &model);
				}
			}
			break;
		}
		
		case typeInsertionLoc:
		{
			DescType		position = value.KeyedItem(keyAEPosition).ToEnum();
			LModelObject	*target = value.KeyedItem(keyAEObject).ToModelObjectOptional(),
							*model;

			if (target) {
				target = target->GetInsertionTarget(position);
			} else {
				target = GetSuperModel()->GetInsertionTarget(position);
			}
			model = target->GetInsertionElement(position);
			ThrowIfNULL_(model);
			newItems.InsertItemsAt(1, arrayIndex_Last, &model);
			break;
		}
		
		default:
			ThrowOSErr_(errAEBadKeyForm);
			break;
	}

	//	Make the change
	StSelectionChanger	changeMe(*this);
	StLockListItems		lockThem(newItems, GetSuperModel());	//	The clear below might delete lazies in the newItems list before they get added.
	{
		LModelObject		*model;
		LSelectableItem		*p,
							*q;
		
		//	The clear below might purge lazies in our newItems list -- so lock them down.
		if (!appending)
			ListClear();
		
		LListIterator	iter(newItems, iterate_FromStart);
		
		while(iter.Next(&model)) {

			//	Fix me!!!
			//	it should be IsSubModelOf(mTextLink || mTableModel);
			if (!model->IsSubModelOf(GetSuperModel()))
				Throw_(paramErr);	//	Should read "this object cant be used here."

			p = (LSelectableItem *) model;	//	unsafe cast!
//			Assert_(member(p, LSelectableItem));

			q = ListDependentItem(p);
			if (q) {
				ListRemoveItem(q);
			} else {
				ListAddItem(p);
			}
		}
	}
}

void	LSelection::GetImportantAEProperties(AERecord &outKeyDescList) const
/*
	Get all important AE property data for referred objects.
	
	"Important" includes things necessary for cloning.
*/
{
	const LSelectableItem	*item;
	OSErr					err = noErr;
	StAEDescriptor			contents,
							reqType;
	Int32					i;
	
	switch (ListCount()) {
		case 0:
			break;
		
		case 1:
			item = ListNthItem(1);
			item->GetImportantAEProperties(outKeyDescList);
			break;
			
		default:
		//	group the properties by item
		{
			LAEStream	stream;
			
			stream.OpenList();
			for (i = 1; i <= ListCount(); i++) {
				StAEDescriptor	itemProps;
				
				item = ListNthItem(i);
				item->GetImportantAEProperties(itemProps.mDesc);
				stream.WriteDesc(itemProps);
			}
			stream.CloseList();
			stream.Close(&outKeyDescList);
			break;
		}
	}
}

void	LSelection::GetContentAEKinds(AEDesc &outKindList)
{
	LSelectableItem	*item;
	OSErr			err = noErr;
	DescType		kind;
	Int32			i;
	
	for (i = 1; i <= ListCount(); i++) {
		item = ListNthItem(i);
		Assert_(item);
//		Assert_(member(item, LSelectableItem));
		kind = item->GetModelKind();
		UAEDesc::AddPtr(&outKindList, 0, typeType, &kind, sizeof(kind));
	}
}

void	LSelection::HandleDelete(
	AppleEvent			&outAEReply,
	AEDesc				&outResult)
/*
	It makes no sense to delete the selection object but it does make sense
	to delete the objects referenced by it.
	
	This routine will set the keyAEInsertHere parameter of the reply to
	the result of the object's delete or multiple object deletes.  In the 
	latter case, a descriptor list will be returned in keyAEInsertHere.
*/
{
	StAEDescriptor	cumulativeResult;
	LSelectableItem	*p;
	LListIterator	iter(mItems, iterate_FromStart);
	
	while(iter.Next(&p)) {
		StAEDescriptor	objectResult,
						objectReply,
						bogus;

		Assert_(p);
		p->HandleDelete(objectReply.mDesc, bogus.mDesc);

		//	accumulate replies
		objectResult.GetParamDesc(objectReply.mDesc, keyAEInsertHere, typeWildCard);
		if (objectResult.mDesc.descriptorType != typeNull)
			UAEDesc::AddDesc(&cumulativeResult.mDesc, 0, objectResult.mDesc);
	}

	SelectSimple(NULL);
	
	if (cumulativeResult.mDesc.descriptorType != typeNull)
		UAEDesc::AddKeyDesc(&outAEReply, keyAEInsertHere, cumulativeResult);
}

void	LSelection::GetAEProperty(
	DescType		inProperty,
	const AEDesc	&inRequestedType,
	AEDesc			&outPropertyDesc) const
{
	Assert_(outPropertyDesc.descriptorType == typeNull);
	
	switch (inProperty) {

		case pShowAllSelections:
			UAEDesc::MakeBooleanDesc(mRecordAllSelections, &outPropertyDesc);
			break;

		break;

	}
	
	if (outPropertyDesc.descriptorType == typeNull)
		LSelectableItem::GetAEProperty(inProperty, inRequestedType, outPropertyDesc);
}

void	LSelection::SetAEProperty(
	DescType		inProperty,
	const AEDesc	&inValue,
	AEDesc&			outAEReply)
{
	switch (inProperty) {

		case pShowAllSelections:
		{
			LAESubDesc	value(inValue);
			mRecordAllSelections = value.ToBoolean();
			break;
		}

		default:
			LSelectableItem::SetAEProperty(inProperty, inValue, outAEReply);
			break;
	}
}

//	===========================================================================
//	List maintenance

void	LSelection::SelectionAboutToChange(void)
{
	BroadcastMessage(msg_SelectionAboutToChange);
}

void	LSelection::SelectionChanged(void)
{
	mSelectionID++;
	if (mSelectionID <= 0)
		mSelectionID = 1;	//	this will rarely happen
		
	BroadcastMessage(msg_SelectionChanged);
}

void	LSelection::ListClear(void)
{
	LListIterator		iter(mItems, iterate_FromEnd);
	LSelectableItem		*p;
	
	while(iter.Previous(&p)) {
		ListRemoveItem(p);
	}
	
	ReplaceSharable(mAnchor, (LSelectableItem *)NULL, this);
}

Int32	LSelection::ListCount(void) const
{
	return (mItems.GetCount());
}

LSelectableItem *	LSelection::ListNthItem(Int32 inIndex)
{
	LSelectableItem	*rval = NULL;
	
	mItems.FetchItemAt(inIndex, &rval);
	return rval;
}

const LSelectableItem *	LSelection::ListNthItem(Int32 inIndex) const
{
	LSelectableItem	*rval = NULL;
	
	mItems.FetchItemAt(inIndex, &rval);
	return rval;
}

Boolean	LSelection::ListContains(const LSelectableItem *inItem) const
/*
	Make sure you shouldn't be using ListDependentWith
*/
{
	LListIterator		iter(((LSelection *)this)->mItems, iterate_FromStart);
	LSelectableItem		*p;
	
	while(iter.Next(&p)) {
		if (p == inItem)
			return true;
	}
	return NULL;
}
		
void	LSelection::ListAddItem(LSelectableItem *inItem)
/*
	Only call directly if you nest the call between calls to SelectionAboutToChange
	and SelectionChanged.
*/
{
	if (inItem != NULL && !ListContains(inItem)) {

		mItems.InsertItemsAt(1, arrayIndex_Last, &inItem);
		inItem->AddUser(this);
	}
}

void	LSelection::ListRemoveItem(LSelectableItem *inItem)
/*
	Only call directly if you nest the call between calls to SelectionAboutToChange
	and SelectionChanged.
*/
{
	Int32	index;
	
	Assert_(inItem);
	index = mItems.FetchIndexOf(&inItem);
	Assert_(index);
	
	mItems.RemoveItemsAt(1, index);
	inItem->RemoveUser(this);
}

//	===========================================================================
//	Clipboard support...
		
#include	<LAEAction.h>

LAction *	LSelection::MakeCutAction(void)
{
	StAEDescriptor		aeTarget,
						aeUndoData;
	LAEAction			*action;
	LModelObject		*super = GetSuperModel();
	
	Assert_(super);
	
	action = new LAEAction(STRx_RedoEdit, str_Cut);
	action->SetSelection(this);
	
	//	Redo
	action->SetRedoAE(kAEMiscStandards, kAECut);
	
	//	Undo
	UndoAddOldData(action);
	
	return action;
}

LAction *	LSelection::MakeCopyAction(void)
{
	StAEDescriptor		aeTarget,
						aeUndoData,
						temp;
	LAEAction			*action;
	LModelObject		*super = GetSuperModel();
	
	Assert_(super);
	
	action = new LAEAction(STRx_RedoEdit, str_Copy);
	action->SetSelection(this);
	
	//	Redo
	action->SetRedoAE(kAEMiscStandards, kAECopy);
	
	//	Undo -- nothing to undo
	return action;
}

LAction *	LSelection::MakePasteAction(void)
{
	StAEDescriptor		aeTarget;
	LAEAction			*action;
	LModelObject		*super = GetSuperModel();
	
	Assert_(super);
	
	action = new LAEAction(STRx_RedoEdit, str_Paste);
	action->SetSelection(this);

	//	Redo
	action->SetRedoAE(kAEMiscStandards, kAEPaste);

	//	Undo
	UndoAddOldData(action);

	return action;
}

LAction *	LSelection::MakeClearAction(void)
{
	StAEDescriptor		aeTarget,
						aeUndoData,
						temp;
	LAEAction			*action;
	
	this->GetAEValue(temp.mDesc, aeTarget.mDesc);
	
	action = new LAEAction(STRx_RedoEdit, str_Clear);
	action->SetSelection(this);

	//	Redo
	action->SetRedoAE(kAECoreSuite, kAEDelete);
	action->RedoAEAdd(keyDirectObject, aeTarget.mDesc);

	//	Undo
	UndoAddOldData(action);
	action->UndoAESetKeyFed(keyAEInsertHere, keyAEInsertHere);
	
	return action;
}

void	LSelection::UndoAddOldData(LAEAction *inAction)
{
	StAEDescriptor		aeTarget,
						aeUndoData,
						aeClass;
	OSErr				err;
	
	err = AECreateList(NULL, 0, true, &aeUndoData.mDesc);
	ThrowIfOSErr_(err);
	GetImportantAEProperties(aeUndoData.mDesc);
	GetContentAEKinds(aeClass.mDesc);
	
	inAction->SetUndoAE(kAECoreSuite, kAECreateElement);
	inAction->UndoAEAdd(keyAEObjectClass, aeClass.mDesc);
	inAction->UndoAEAdd(keyAEPropData, aeUndoData.mDesc);
	inAction->UndoAESetKeyFed(keyAEInsertHere);
}

StSelectionChanger::StSelectionChanger(LSelection &inSelection)
{
	mSelection = &inSelection;
	
	mSelection->SelectionAboutToChange();
}

StSelectionChanger::~StSelectionChanger()
{
	mSelection->SelectionChanged();
}