/*

File IKBrowserManager.m

Release 1.2, 7 June 1994
Copyright (C) 1994 by H. Scott Roy

This code is part of IconKit, a general toolbox for drag-and-drop applications.  IconKit is free for noncommercial use, but costs money for a commercial license.  You should have received a copy of the license agreement with this file.  If not, a copy of the license and the complete source of IconKit can be obtained from the author:

		H. Scott Roy
		2573 Stowe Ct.
		Northbrook, IL  60062-8103
		iconkit@cs.stanford.edu

For your editing convenience, this file is best viewed using an editor that automatically wraps long lines, in a fixed point font at 80 columns, with tabs every 4 spaces.

*/


/* ========================================================================== */


/*

Browsing and editing graph is one of the most natural uses for an IKBrowser.  This class defines a graph manager that automates the generic operations.  End applications only need to specify the graph structure.  The IKBrowserManager takes care of filling in a browser with the appropriate information, responding to editing and dragging, selecting paths, and so forth.

*/

#import <appkit/appkit.h>
#import <apps/InterfaceBuilder.h>
#import "iconkit.h"

@implementation IKBrowserManager


/* ========================================================================== */


/*

We use a pretty picture to represent the browser manager in InterfaceBuilder.

*/


- (NXImage *) getIBImage
{
	char
		buf [MAXPATHLEN + 1];
	id
		bundle;
	NXImage
		* image = [NXImage  findImageNamed: "BrowserManager"];
	
	if (image == nil)
	{
		bundle = [NXBundle  bundleForClass: [self  class]];
		if ([bundle  getPath: buf forResource: "BrowserManager" ofType: "tiff"])
		{
    		image = [[NXImage  alloc]  initFromFile: buf];
			[image  setName: "BrowserManager"];
		}
	}
	
	return image;
}


/* ========================================================================== */


/*

The initialization given here should work correctly whenever either unarchiving from a typed stream or initializing from a nib.  The initBrowser:root: method is the designated initializer, and should be used to initialize the graph manager programmatically.

*/


- init;
{
	return [self  initBrowser: nil  root: nil];
}


- initBrowser: (id) theBrowser  root: (id) theRoot;
{
	if ((self = [super  init]) != nil)
	{
		browser = theBrowser;
		root = theRoot;
		[self  awake];
	}
	
	return self;
}


- free
{
	int
		i;
	id
		cells = [[browser  iconPath]  cellList];
	
	IKdprintf ("freeing browser manager: %p\n", self);
	for (i = 0;  i < [cells  count];  i++)
			[[[cells  objectAt: i]  delegate]  removeUser: self];
	
	[browser  setDelegate: nil];
	if ([[browser  window]  delegate] == self)
			[[browser  window]  setDelegate: nil];
	
	return [super  free];
}


- awakeFromNib
{
	[self  awake];
	return self;
}


- awake
{
	NXAtom
		* pasteTypes;
	int
		n = 0;
	
	pasteTypes = [[root  class]  respondsTo: @selector(pasteTypes)] ?
			[[root  class]  pasteTypes]:
			NULL;
	
	if (pasteTypes != NULL) while (pasteTypes[n] != NULL) n++;
	[[browser  iconPath]  registerForDraggedTypes: pasteTypes  count: n];
	[browser  setDelegate: self];
	
	return self;
}


/* ========================================================================== */


/*

Here are the various set and get methods.  Note that the set methods must conform to the InterfaceBuilder specifications, and so cannot coordinate the browser and root.  Programmers should send an explicity awake message if they need to set the browser or root after initialization.

*/


- browser				{	return browser;			}
- root					{	return root;			}

- setBrowser: theBrowser;
{
	[browser  setDelegate: nil];
	browser = theBrowser;
	[self  awake];
	
	return self;
}


- setRoot: theRoot;
{
	root = theRoot;
	[self  awake];
	
	return self;
}


/* ========================================================================== */


/*

Filling the browser columns is straightforward.  The selected cell in the previous column is used to find the graph node that should be displayed.

*/


- (int) browser: sender  fillMatrix: matrix  inColumn: (int) column;
{
	id
		node,
		selection,
		children;
	int
		k = 0,
		n,
		i;
	
	if (column == 0)
			node = root;
	
	else
	{
		selection = [sender  getSelectionInColumn: column - 1];
		node = ([selection  count] == 1) ?
					[selection  objectAt: 0]:
					nil;
		
		[selection  free];
	}
	
	if (column == 0  ||  ![node  isLeaf])
	{
		children = [node  children];
		n = [children  count];
		
		{
			id
				child [n];
	
			for (i = 0;  i < n;  i++)
				if (![child[k] = [children  objectAt: i]  isHidden])
					k++;
			
			[self  sort: child  count: k  for: column];
			
			for (i = 0;  i < k;  i++)
					[[[matrix	addRow]
								cellAt: i  : 0]
								setDelegate: child[i]];
		}
	}
	
	return k;
}


/* ========================================================================== */


/*

Setting the column icon is as easy as filling the column.  The only trick part is checking for a multiple selection.  When the IKBrowserManager installs a new object at the head of a column, it registers for notification messages so that it can correctly update the browser when the object's contents change.

The getSelectedCells method is broken when running in InterfaceBuilder test mode.  Accordingly, we need to jump through some hoops to get things to work correctly in that particular case.

*/

- browser: sender  setColumnIcon: cell  for: (int) column;
{
	id
		node = nil,
		selection;
	
	if (column == 0)
			node = root;
	
	else
	{
		selection = [sender  getSelectionInColumn: column - 1];
		if ([selection  count] == 1)
				node = [selection  objectAt: 0];
		
		else if ([selection  count] > 1)
		{
			node =
				[[root  class]  respondsTo: @selector(multipleSelectionClass)] ?
						[[[[root class]  multipleSelectionClass]  alloc]  init]:
						[[IKList alloc]  init];
			
			[node  appendList: selection];
		}
		
		[selection  free];
	}
	
	[node  addUser: self];
	[[cell  setDelegate: node]  removeUser: self];
	
	return self;
}


/* ========================================================================== */


/*

An IKBrowser sends out messages when it deletes columns, as well as when it adds them, so that the browser delegate can clear out the cells.

*/


- browser: sender  emptyMatrix: matrix  inColumn: (int) column
{
	[[matrix	cellList]
				makeObjectsPerform: @selector(setDelegate:)  with: nil];
	
	return self;
}


- browser: sender  removeColumnIcon: cell  for: (int) column
{
	[[cell  setDelegate: nil]  removeUser: self];
	return self;
}


/* ========================================================================== */


/*

An IKBrowser alerts its delegate at two points in the update process: just before the window is flushed to show any changes, and just after.  IKBrowserManager uses the first message to select the current cell in the icon path for editing.  This behavior mirrors Workspace and makes it easier to read long titles.

The browserDidChange method is the perfect place to update any inspectors attached to the browser.

*/


- browserWillFinishChange: sender
{
	[[sender  iconPath]  editCellAt: 0  : [sender  lastColumn]];
	return self;
}


- browserDidChange: sender
{
	return self;
}


/* ========================================================================== */


/*

The provided sorting method does nothing.  The FileViewer example shows how to call qsort to do sorting by name, and can easily be adapated to other sorting keys.

*/


- sort: (id *) folders  count: (int) n  for: (int) column
{
	return self;
}


/* ========================================================================== */


/*

Here is an action method to select an icon in the browser.  It is intended to be sent by an IKCell or an IKShelf--something with a delegate that the IKBrowserManager can retrieve.  The IKBrowserManager finds a path to the delegate and sets it in the browser.  If the delegate cannot be found, the browser is reset to the root node.

*/

- takePathFrom: sender
{
	id
		node,
		path = nil;
	char
		pathName [IKMAXPATHLENGTH];
	int
		i;
	
	if ([sender  respondsTo: @selector(delegate)] &&
		[node = [sender  delegate]  respondsTo: @selector(pathFromNode:)])
	{
		path = [node  pathFromNode: root];
		pathName[0] = '/';
		pathName[1] = '\0';
		
		if (path != nil)
			for (i = 1;  i < [path  count];  i++)
			{
				strcat (pathName, [[path  objectAt: i]  name]);
				strcat (pathName, "/");
			}
		
		[browser  setPath: pathName];
	}
	
	return path;
}


/* ========================================================================== */


/*

Here are action messages that let one edit the graph.  A deleteLink: message erases the link between the current selection and its parent.  A delete: message erases the selection, and a duplicate: message copies it.

*/


- deleteLink: sender
{
	int
		n = [browser  selectedColumn];
	id
		parent,
		selection;
	
	if (n != -1)
	{
		parent = [[browser  iconPath]  objectInColumn: n];
		if ([parent  respondsTo: @selector(removeChildren:)])
		{
			selection = [browser  getSelectionInColumn: n];
			[parent  removeChildren: selection];
			[selection  free];
		}	
	}
	
	return self;
}


- delete: sender
{
	int
		n = [browser  selectedColumn];
	id
		selection;
	
	if (n != -1)
	{
		selection = [browser  getSelectionInColumn: n];
		[selection  makeObjectsPerform: @selector(addUser:)  with: self];
		[self  deleteLink: self];
		[selection  freeObjects];
		[selection  free];
	}
	
	return self;
}


- duplicate: sender
{
	int
		n = [browser  selectedColumn],
		i;
	id
		parent,
		selection;
	
	if (n != -1)
	{
		parent = [[browser  iconPath]  objectInColumn: n];
		if ([parent  respondsTo: @selector(addChildren:)])
		{
			selection = [browser  getSelectionInColumn: n];
		
			for (i = 0;  i < [selection  count];  i++)
					[selection  replaceObjectAt: i
										with: [[selection  objectAt: i]  copy]];
		
			[parent  addChildren: selection];
			[selection  free];
		}
	}
	
	return self;
}


- newFolder: sender
{
	static int
		count = 1;
	char
		name [100];
	id
		delegate = [[browser  iconPath]
							objectInColumn: [browser  selectedColumn] + 1];
	
	if ([delegate  respondsTo: @selector(addChild:)])
	{
		sprintf (name, "NewFolder%d", count++);
		[delegate  addChild: [[[root  class]  alloc]  init: name]];
	}
	
	return self;
}


- toggleLeaf: sender
{
	id
		selection = [browser  getSelectionInColumn: [browser  selectedColumn]],
		node;
	int
		i;
	
	for (i = 0; i < [selection  count]; i++)
	{
		node = [selection  objectAt: i];
		if ([node  respondsTo: @selector(setLeaf:)])
				[node  setLeaf: ![node  isLeaf]];
	}
	
	[selection  free];
	
	return self;
}


/* ========================================================================== */


/*

The IKBrowserManager is responsible for updating the browser whenever one of the columns changes.  It does this by scanning the icon path for the changed column, then reloading it.

*/


- didChangeProperties: sender			{	return [self  changed: sender];	}
- didAddChild: sender					{	return [self  changed: sender];	}
- didAddChildren: sender				{	return [self  changed: sender];	}
- didRemoveChild: sender				{	return [self  changed: sender];	}
- didRemoveChildren: sender				{	return [self  changed: sender];	}


- willFree: sender
{
	if (sender == root) root = nil;
	return self;
}


- changed: who
{
	int
		r,
		n;
	char
		path [IKMAXPATHLENGTH];
	id
		iconPath = [browser  iconPath];
	
	[iconPath  getRow: &r andCol: &n ofCell: [iconPath  cellWithDelegate: who]];
	if (n != -1 )
	{
		[browser  getPath: path  toColumn: [browser  lastColumn]];
		[browser  resetColumn: n  usingPath: path];
	}
	
	return self;
}


@end