// MediaRoutingView.cpp
// e.moon 6may99

#include "MediaRoutingView.h"
//
//// transport control
//// [7jun99]
//#include "Transport.h"

// Inspector views
#include "GroupInspector.h"

// NodeManager
#include "NodeManager.h"
#include "NodeGroup.h"
#include "NodeRef.h"
#include "Connection.h"

// layout classes
#include "LayoutDefs.h"

#include "RoutingColumn.h"
#include "RoutingRow.h"
#include "GutterColumn.h"
#include "GutterRow.h"
#include "NodeColumn.h"
#include "NodeRow.h"

// node cell view
#include "LiveNodeView.h"
//#include "ChildNodeView.h"

// backdrop view
#include "MediaRoutingContainerView.h"

// connection entry
#include "Wire.h"

// add-on node palette view
#include "DormantNodeView.h"

// Be API
#include <Alert.h>
#include <MediaRoster.h>
#include <MediaAddOn.h>
#include <Region.h>
#include <Window.h>
#include <String.h>

// STL
#include <list>
#include <algorithm>
#include <functional>
#include <iterator>

// internal utilities
#include "debug_tools.h"
#include "set_tools.h"

#include "ProfileBlock.h"

//#include "DebugClient.h"

__USE_CORTEX_NAMESPACE

// ---------------------------------------------------------------- //
// constants
// ---------------------------------------------------------------- //

const rgb_color MediaRoutingView::s_wireColor 					= {48, 128, 192, 255};
const rgb_color MediaRoutingView::s_selectedWireColor	= {224, 224, 160, 255};
const rgb_color MediaRoutingView::s_jackOutlineColor		= {255, 32, 32, 64};
const rgb_color MediaRoutingView::s_workspaceColor			= {16, 64, 96, 255};

// ---------------------------------------------------------------- //
// ctor/dtor/accessors
// ---------------------------------------------------------------- //

MediaRoutingView::~MediaRoutingView() {
//	PRINT((
//		"MediaRoutingView::~MediaRoutingView()\n"));
	status_t err;
			
	// deallocate wires
	ptr_map_delete(
		m_wireMap.begin(),
		m_wireMap.end());

	if(manager) {
		err = remove_observer(this, manager);
		ASSERT(err == B_OK);
	}
}

MediaRoutingView::MediaRoutingView(
	NodeManager*								_manager,
	BRect												frame,
	const char*									name,
	uint32											resizeMode) :
		
	GridView(frame, name, resizeMode,
		B_WILL_DRAW|B_FRAME_EVENTS|B_DRAW_ON_CHILDREN|B_PULSE_NEEDED,
		GridView::NONE),

	manager(_manager),
	m_containerView(0),
	m_bDeferSegmentRedraw(false),
	m_selectedCell(0),
	m_dragCell(0),
	m_dragFromVertex(0),
	m_dragToVertex(0),
	m_dragVertexFlashOn(false),
	m_dragVertexFlashState(false),
	m_nextGroupNumber(1) {
	
	ASSERT(manager);
	
//	m_dragUpdateThread(0),
//	m_dragUpdateLock("MediaRoutingView::m_dragUpdateLock")

	// replace stock cell container with my backdrop view
	m_containerView = new MediaRoutingContainerView(this);
	setContainerView(m_containerView);

	// create default rows & columns
	addGridRows(0, s_defNodeRows);
	addGridColumns(0, s_defNodeColumns);

	// init display stuff	
	SetViewColor(m_containerView->ViewColor());
	setScrollMargins(
		GUTTER_COLUMN_WIDTH + NODE_COLUMN_WIDTH,
		GUTTER_ROW_HEIGHT + NODE_ROW_HEIGHT);
}

// ---------------------------------------------------------------- //
// IMouseTrackingDestination impl.
// ---------------------------------------------------------------- //

//ProfileTarget g_prof;

// a MouseTrackingSourceView has started tracking the mouse
// (point is in pSource view coordinates)
void MediaRoutingView::mouseTrackingBegin(
	MouseTrackingSourceView* pSource,
	uint32 buttons,
	BPoint point) {
	
//	PRINT(("MediaRoutingView::mouseTrackingBegin()\n"));
	
	if(m_dragCell) {
		PRINT((
			"* MediaRoutingView::mouseTrackingBegin():\n"
			"  drag operation still in progress!\n"));
		return;
	}
		
	m_dragCell = dynamic_cast<LiveNodeView*>(pSource);
	if(!m_dragCell) {
		PRINT((
			"* MediaRoutingView::mouseTrackingBegin():\n"
			"  bad view type!\n"));
		return;
	}
	
	// * figure target cell position relative to the clicked
	//   point:
	// figure cell's outline rectangle in my view coordinates
	BRect cellFrameR = m_dragCell->Frame();
	BRect outlineR = m_containerView->ConvertToParent(cellFrameR);
		
	// figure distance from mouse pointer to top-left corner
	// of cell rectangle
	m_dragOffset = ConvertToScreen(outlineR.LeftTop()) - point;

	// figure which row of the cell was clicked
	BPoint clickP = m_dragCell->ConvertToParent(point);
	int32 rowClicked = rowIndexFor(clickP.y);
	ASSERT(rowClicked > 0);
	if(!(rowClicked%2))
		rowClicked++;
	m_dragCellRowOffset = (rowClicked - rowIndexFor(cellFrameR.top)) / 2;

	// get current cell position in node rows/columns
	m_dragCellColumn = gridToNodeColumnIndex(columnIndexFor(cellFrameR.left));
	m_dragCellRow = gridToNodeRowIndex(rowIndexFor(cellFrameR.top));

	m_dragCellRowSpan = nodeRowSpanFor(cellFrameR.Height());
	
//	PRINT((
//		"MediaRoutingView::mouseTrackingBegin(): node at %ld, %ld [+%ld]\n",
//		m_dragCellColumn, m_dragCellRow, m_dragCellRowOffset));

	// operation-specific branch
	switch(m_dragCell->dragOp()) {
		case LiveNodeView::DRAG_CELL:
			beginCellDrag(buttons, point);
			break;
			
		case LiveNodeView::DRAG_WIRE:
			beginWireDrag(buttons, point);
			break;
			
		default:
			PRINT(("!!! mismatched call to mouseTrackingBegin()\n"));
	}
//	g_prof.clear();
}			
	
// mouse-tracking update from child view
// (point is in screen coordinates)
void MediaRoutingView::mouseTrackingUpdate(
	uint32 buttons,
	float xDelta,
	float yDelta,
	BPoint point) {

//	PRINT(("MediaRoutingView::mouseTrackingUpdate()\n"));
	ASSERT(m_dragCell);
	
	switch(m_dragCell->dragOp()) {
		case LiveNodeView::DRAG_CELL:
			updateCellDrag(buttons, xDelta, yDelta, point);
			break;
			
		case LiveNodeView::DRAG_WIRE:
			updateWireDrag(buttons, xDelta, yDelta, point);
			break;
			
		default:
			EndRectTracking();
			PRINT(("!!! mismatched call to mouseTrackingUpdate()\n"));
	}
}
	
// mouse-tracking done
void MediaRoutingView::mouseTrackingEnd() {
	if(!m_dragCell)
		return;
		
	switch(m_dragCell->dragOp()) {
		case LiveNodeView::DRAG_CELL:
			endCellDrag();
			break;
			
		case LiveNodeView::DRAG_WIRE:
			endWireDrag();
			break;
			
		default:
			EndRectTracking();
			PRINT(("!!! mismatched call to mouseTrackingEnd()\n"));
	}

	m_dragCell = 0;
//	g_prof.dump();
}


// ---------------------------------------------------------------- //
// mouse-tracking impl. methods
// ---------------------------------------------------------------- //

void MediaRoutingView::beginCellDrag(
	uint32 buttons,
	BPoint point) {
		
//	deselectAllCells();
	selectCell(m_dragCell);

	// figure cell's outline rectangle in my view coordinates
	BRect cellFrameR = m_dragCell->Frame();
	BRect outlineR = m_containerView->ConvertToParent(cellFrameR);

	// display drag rectangle
	// +++++ why is the rect-tracking mechanism off by 2 pixels?
	outlineR.OffsetBy(-2.0, -2.0);
	BeginRectTracking(outlineR);
}

void MediaRoutingView::updateCellDrag(
	uint32 buttons,
	float xDelta,
	float yDelta,
	BPoint point) {
	
	const bool bRerouteWires = true;
	
	BWindow* pWnd = Window();
	if(pWnd)
		pWnd->BeginViewTransaction();
	
	// figure whether the mouse has been dragged into a valid node
	// row/column (that differs from the cell's previous location)
	
	m_containerView->ConvertFromScreen(&point);
	int32 mouseColumn = columnIndexFor(point.x);
	int32 mouseRow = rowIndexFor(point.y);
	
	uint32 mouseNodeColumn, mouseNodeRow;
	
	if(mouseColumn < 0 || mouseRow < 0) {
//		PRINT(("MediaRoutingView::updateCellDrag(): not in grid\n"));
		goto release_window;
	}
	
	// if dragged into either a horizontal or vertical gutter,
	// clamp the column or row back to the current cell position.
	
	if(!(mouseColumn%2))
		mouseColumn = nodeToGridColumnIndex(m_dragCellColumn);
	if(!(mouseRow%2))
		mouseRow = nodeToGridRowIndex(m_dragCellRow+m_dragCellRowOffset);
	
	// figure new node slot, if any:
	
	mouseNodeColumn = gridToNodeRowIndex(mouseColumn);
	mouseNodeRow = gridToNodeColumnIndex(mouseRow) - m_dragCellRowOffset;
	
	if(mouseNodeColumn == m_dragCellColumn &&
		mouseNodeRow == m_dragCellRow) {
//		PRINT(("MediaRoutingView::updateCellDrag(): same cell\n"));
		goto release_window;
	}
	
	if(mouseNodeRow < 0) {
		// can't drag off top of grid
		goto release_window;
	}
	
	if(mouseNodeRow >= nodeRows()) {
		// can't drag completely off the bottom, either
		goto release_window;
	}

//	PRINT(("MediaRoutingView::updateCellDrag(): drag to %ld, %ld\n",
//		mouseNodeColumn, mouseNodeRow));


	// 21may99: defer wire-segment redraws
	m_bDeferSegmentRedraw = true;

	// * attempt to displace cell(s) at target location, if any;
	//   bail if impossible

	// move the target view via displaceCells()
	
	if(mouseNodeRow < m_dragCellRow) {
		uint32 displaced = displaceCells(
			m_dragCellColumn, m_dragCellRow+m_dragCellRowSpan-1,
			1, DISPLACE_UP,
			m_dragCellRow-mouseNodeRow,
			true, bRerouteWires);

		mouseNodeRow = m_dragCellRow - displaced;
/* 18may99:
   deleteNodeRow() broken +++++
   
		// remove empty rows if any
		uint32 n = nodeRows()-1;
		while(n >= s_defNodeRows) {
			uint32 emptyRow = n;
			n--;
			if(!nodeRowEmpty(emptyRow))
				break;
			deleteNodeRow(emptyRow);
		}
*/
	}
	else if(mouseNodeRow > m_dragCellRow) {
		uint32 displaced = displaceCells(
			m_dragCellColumn, m_dragCellRow,
			1, DISPLACE_DOWN,
			mouseNodeRow-m_dragCellRow,
			true, bRerouteWires);
	
		ASSERT(displaced == mouseNodeRow-m_dragCellRow);
	}
	m_dragCellRow = mouseNodeRow;
	
	if(mouseNodeColumn < m_dragCellColumn) {
		uint32 displaced = displaceCells(
			m_dragCellColumn, m_dragCellRow,
			m_dragCellRowSpan, DISPLACE_LEFT,
			m_dragCellColumn-mouseNodeColumn,
			true, bRerouteWires);
			
		mouseNodeColumn = m_dragCellColumn - displaced;
	}
	else {
		uint32 displaced = displaceCells(
			m_dragCellColumn, m_dragCellRow,
			m_dragCellRowSpan, DISPLACE_RIGHT,
			mouseNodeColumn-m_dragCellColumn,
			true, bRerouteWires);
			
		ASSERT(displaced == mouseNodeColumn-m_dragCellColumn);
	}
	m_dragCellColumn = mouseNodeColumn;

	// 21may99: reroute displaced wires
	if(!bRerouteWires)
		rerouteDisplacedWires();

	// 21may99: redraw segments added/removed by the above call
	m_bDeferSegmentRedraw = false;
	redrawTouchedSegments();

release_window:	
	if(pWnd)
		pWnd->EndViewTransaction();
}

void MediaRoutingView::endCellDrag() {
		
	EndRectTracking();
}

void MediaRoutingView::beginWireDrag(
	uint32 buttons,
	BPoint point) {
	
	// fetch origin vertex
	ASSERT(m_dragCell);
	ASSERT(!m_dragFromVertex);
	ASSERT(!m_dragToVertex);
	
	m_dragFromVertex = new WireVertex();
	*m_dragFromVertex = m_dragCell->dragVertex();
	m_dragFromVertex->nodeColumn = m_dragCellColumn;
	m_dragFromVertex->nodeRow = m_dragCellRow;
	
	// set up animation
	// [4jun99]
	m_dragVertexFlashTimer = s_dragVertexFlashPeriod;
	m_dragVertexFlashState = true;
	m_dragVertexFlashOn = true;
	
	// highlight it
//	Invalidate(jackOutlineBounds(m_dragFromVertex));
}

void MediaRoutingView::updateWireDrag(
	uint32 buttons,
	float xDelta,
	float yDelta,
	BPoint point) {

	// find node cell under cursor, if any
	// figure whether the mouse has been dragged into a valid node
	
	WireVertex* pNewToVertex = new WireVertex();
	if(findAvailableVertex(point, *pNewToVertex)) {
		// * bail if same vertex
		if(m_dragToVertex &&
			*pNewToVertex == *m_dragToVertex) {
			delete pNewToVertex;
			return;
		}

		// set new target vertex
		if(m_dragToVertex) {
			Invalidate(jackOutlineBounds(m_dragToVertex));
			delete m_dragToVertex;
		}
		m_dragToVertex = pNewToVertex;
	}
	else if(m_dragToVertex) {
		Invalidate(jackOutlineBounds(m_dragToVertex));
		delete m_dragToVertex;
		m_dragToVertex = 0;
	}
}

// point is in screen coordinates
bool MediaRoutingView::findAvailableVertex(
	BPoint point,	WireVertex& outVertex) {
	ASSERT(m_dragFromVertex);
	
//	PRINT((
//		"MediaRoutingView::findAvailableVertex(%.1f,%.1f)\n",
//		point.x, point.y));

	// locate in grid	
	BPoint containerPoint = m_containerView->ConvertFromScreen(point);
	int32 column = columnIndexFor(containerPoint.x);
	int32 row = rowIndexFor(containerPoint.y);
	
	// valid cell coordinate?
	if(column < 0 || row < 0 || !(column%2)) {
//		PRINT((
//			"- not a cell slot.\n"));
		return false;
	}
	// round row up to nearest node-cell row (cells never occupy
	// gutters above their origin row)
	if(!(row%2))
		--row;

	// look it up using the GridView::getCell()		
	BView* pView;
	uint32 oColumn, oRow, cSpan, rSpan;
	if(!getCell(column, row, &pView,
		&oColumn, &oRow, &cSpan, &rSpan)) {
//		PRINT((
//			"- no cell found.\n"));
		return false;
	}
			
	ASSERT(oColumn == column);
	
	// fetch cell
	LiveNodeView* pCell = dynamic_cast<LiveNodeView*>(pView);
	ASSERT(pCell);	

	// look for jack
	WireVertex foundVertex;
	if(!pCell->matchJackVertex(
		pCell->ConvertFromScreen(point),
		foundVertex))
		return false;

	// fail if both jacks 'face the same direction'
	if(m_dragFromVertex->columnOffsetType ==
		foundVertex.columnOffsetType)
		return false;
	
	// fail if already connected
	media_input in;
	media_output out;
	bool isConnected;
	if(!(
		(foundVertex.columnOffsetType == WireVertex::NODE_INPUT_JACK) ?
			pCell->findMediaInput(foundVertex, in, isConnected) :
			pCell->findMediaOutput(foundVertex, out, isConnected))) {
		// no media input/output found!
		PRINT((
			"MediaRoutingView::findAvailableVertex(): expected connection is missing:\n%s\n",
			foundVertex.toString().String()));
		ASSERT(!"LiveNodeView is out of date or broken");
		return false;
	}
	if(isConnected)
		return false;
		
	// +++++ further format checks go here!

	// fill in row/column (unknown to cell)
	outVertex = foundVertex;
	outVertex.nodeColumn = gridToNodeColumnIndex(oColumn);
	outVertex.nodeRow = gridToNodeRowIndex(oRow);
	return true;
}

void MediaRoutingView::endWireDrag() {

	ASSERT(m_dragFromVertex);
	status_t err;

	if(m_dragToVertex) {
		// * fetch input & output
		media_input input;
		media_output output;
		bool isConnected;

		// ORIGIN VERTEX
		if(!(
			(m_dragFromVertex->columnOffsetType == WireVertex::NODE_INPUT_JACK) ?
				m_dragCell->findMediaInput(
					*m_dragFromVertex, input, isConnected) :
				m_dragCell->findMediaOutput(
					*m_dragFromVertex, output, isConnected))) {
			(new BAlert("connection failed",
				"origin vertex is no longer valid:\n"
				"this is a bug; please report it to\n"
				"+++++", "Ok"))->Go();

			// bail
			goto end_drag;
		}
		if(isConnected) {
			(new BAlert("connection failed",
				"origin jack already connected:\n"
				"this is a bug; please report it to\n"
				"+++++", "Ok"))->Go();

			// bail
			goto end_drag;
		}

		// the cell containing m_dragToVertex
		LiveNodeView* pDragToCell = nodeViewAt(
			m_dragToVertex->nodeColumn, m_dragToVertex->nodeRow);
		ASSERT(pDragToCell);

		// TARGET VERTEX
		if(!(
			(m_dragToVertex->columnOffsetType == WireVertex::NODE_INPUT_JACK) ?
				pDragToCell->findMediaInput(
					*m_dragToVertex, input, isConnected) :
				pDragToCell->findMediaOutput(
					*m_dragToVertex, output, isConnected))) {
			(new BAlert("connection failed",
				"target vertex is no longer valid:\n"
				"this is a bug; please report it to\n"
				"+++++", "Ok"))->Go();

			// bail
			goto end_drag;
		}
		if(isConnected) {
			(new BAlert("connection failed",
				"origin jack already connected:\n"
				"this is a bug; please report it to\n"
				"+++++", "Ok"))->Go();

			// bail
			goto end_drag;
		}

//		// attempt a connection
//		media_format templateFormat;
//
//		if(input.format.type != B_MEDIA_UNKNOWN_TYPE) {
//			PRINT(("\tbasing connection on consumer format\n"));
//			templateFormat = input.format;
//		}
//		else if(output.format.type != B_MEDIA_UNKNOWN_TYPE) {
//			PRINT(("\tbasing connection on producer format\n"));
//			templateFormat = output.format;
//		}
//		else {
//			// +++++ what's the proper behavior when neither the producer
//			//       nor the consumer cares what type of media it's getting?
//			templateFormat.type = B_MEDIA_RAW_AUDIO;
//			templateFormat.u.raw_audio = media_raw_audio_format::wildcard;
//		}
//		
		char formatStr[256];
//		string_for_format(templateFormat, formatStr, 255);
//		PRINT(("*** connecting: suggested format '%s'\n", formatStr));
//
//		err = m_roster->Connect(
//			output.source,
//			input.destination,
//			&connectionFormat,
//			&output,
//			&input);
		Connection con;
		err = manager->connect(
			output,
			input,
//			templateFormat,
			&con);
		if(err < B_OK) {
			(new BAlert("Connection Failed", strerror(err), "Ok"))->Go();
			goto end_drag;
		}
		
		string_for_format(con.format(), formatStr, 255);
		PRINT(("*** connected w/ format '%s'\n", formatStr));
		
		// [15jun99] check format for completeness; display diagnostics
		bool formatIncomplete = false;
		string formatMissingText;
		if(con.format().type == B_MEDIA_RAW_AUDIO) {
			const media_raw_audio_format& f = con.format().u.raw_audio;
			formatMissingText = "Missing fields (media_raw_audio_format):\n\n";
			if(f.frame_rate == media_raw_audio_format::wildcard.frame_rate) {
				formatIncomplete = true;
				formatMissingText += "frame rate\n";
			}
			
			if(f.channel_count == media_raw_audio_format::wildcard.channel_count) {
				formatIncomplete = true;
				formatMissingText += "channel count\n";
			}
			
			if(f.format == media_raw_audio_format::wildcard.format) {
				formatIncomplete = true;
				formatMissingText += "format\n";
			}
			
			if(f.byte_order == media_raw_audio_format::wildcard.byte_order) {
				formatIncomplete = true;
				formatMissingText += "byte order\n";
			}
			
			if(f.buffer_size == media_raw_audio_format::wildcard.buffer_size) {
				formatIncomplete = true;
				formatMissingText += "buffer size\n";
			}
		}
		else if(con.format().type == B_MEDIA_RAW_VIDEO) {
			// +++++ UNTESTED +++++
			const media_raw_video_format& f = con.format().u.raw_video;
			formatMissingText = "Missing fields (media_raw_video_format):\n\n";
			if(f.field_rate == media_raw_video_format::wildcard.field_rate) {
				formatIncomplete = true;
				formatMissingText += "field rate\n";
			}
			
			if(f.interlace == media_raw_video_format::wildcard.interlace) {
				formatIncomplete = true;
				formatMissingText += "interlace\n";
			}
			
//			// jon watte sez: this is wrong
//			// +++++ is media_raw_video_format::wildcard.first_active 0?
//			//
//			if(f.first_active == media_raw_video_format::wildcard.first_active) {
//				formatIncomplete = true;
//				formatMissingText += "first_active\n";
//			}

			if(f.last_active == media_raw_video_format::wildcard.last_active) {
				formatIncomplete = true;
				formatMissingText += "last_active\n";
			}
		
			if(f.orientation == media_raw_video_format::wildcard.orientation) {
				formatIncomplete = true;
				formatMissingText += "orientation\n";
			}
		
			if(f.pixel_width_aspect == media_raw_video_format::wildcard.pixel_width_aspect) {
				formatIncomplete = true;
				formatMissingText += "pixel_width_aspect\n";
			}

			if(f.pixel_height_aspect == media_raw_video_format::wildcard.pixel_height_aspect) {
				formatIncomplete = true;
				formatMissingText += "pixel_height_aspect\n";
			}
		}
		
		if(formatIncomplete) {
			string message(
				"Warning: a connection has been established with an incomplete "
				"format.  Attempting to start the nodes involved in this connection "
				"may result in this application -- or, in some cases, the BeOS -- "
				"crashing or locking up.\n\n"
				"Format summary: ");
			message += formatStr;
			message += "\n\n";
			message += formatMissingText;
			
			(new BAlert("Incomplete Connection Format", message.c_str(), "Ok"))->Go();
			
			// +++++ auto-disconnect?
		}

		// +++++ 13aug99: may as well make the wire here... +++++

//		// report success
//		char buffer[512];
//		string_for_format(connectionFormat, buffer, 511);
//		(new BAlert("Connected", buffer, "Ok"))->Go();
	}
			
end_drag:
	// end animation
	m_dragVertexFlashOn = false;
	
	// redraw one last time
	BRect b = jackOutlineBounds(m_dragFromVertex);
	delete m_dragFromVertex;
	m_dragFromVertex = 0;
	Invalidate(b);
	
	if(m_dragToVertex) {
		b = jackOutlineBounds(m_dragToVertex);
		delete m_dragToVertex;
		m_dragToVertex = 0;
		Invalidate(b);
	}
}


// calculate bounds of the outline drawn around a jack that's
// participating in a drag'n'drop connection

BRect MediaRoutingView::jackOutlineBounds(const WireVertex* pVertex) {
	ASSERT(pVertex);
//	PRINT((
//		"MediaRoutingView::jackOutlineBounds()\n%s\n",
//		pVertex->toString().String()));
		
	BPoint center = vertexToPoint(*pVertex);
//	PRINT((
//		"- center (%.1f,%.1f)\n", center.x, center.y));

	return BRect(
		center.x-JACK_DRAG_OUTLINE_LEFT_OFFSET,
		center.y-JACK_DRAG_OUTLINE_TOP_OFFSET,
		center.x+JACK_DRAG_OUTLINE_RIGHT_OFFSET,
		center.y+JACK_DRAG_OUTLINE_BOTTOM_OFFSET);
}
 
void MediaRoutingView::drawJackOutline(const WireVertex* pVertex) {
	BRect outline = jackOutlineBounds(pVertex);

	SetHighColor(s_jackOutlineColor);
	SetDrawingMode(B_OP_OVER);
	FillRect(outline);
	/*
	FillRect(
		BRect(outline.left, outline.top, outline.right,
			outline.top + JACK_DRAG_OUTLINE_THICKNESS));
	FillRect(
		BRect(outline.left,
			outline.bottom - JACK_DRAG_OUTLINE_THICKNESS,
			outline.right, outline.bottom));
	FillRect(
		BRect(outline.left, outline.top,
			outline.left + JACK_DRAG_OUTLINE_THICKNESS,
			outline.bottom));
	FillRect(
		BRect(outline.right - JACK_DRAG_OUTLINE_THICKNESS,
			outline.top, outline.right, outline.bottom));
	*/
	SetDrawingMode(B_OP_COPY);
}

/*
// ---------------------------------------------------------------- //
// the display-update thread that runs during drag ops
// ---------------------------------------------------------------- //

status_t MediaRoutingView::DragUpdateThread(void* pUser) {
	((MediaRoutingView*)pUser)->dragUpdateThread();
	return B_OK;
}

void MediaRoutingView::dragUpdateThread() {
//	ProfileBlock _b(g_prof, "dragUpdateThread");

	while(m_dragCell) {
		// do the update
		m_dragUpdateLock.Lock();
		for(set<Wire*>::iterator it = m_dragUpdateWires.begin();
			it != m_dragUpdateWires.end(); it++) {
			// +++++
		}
		m_dragUpdateLock.Unlock();
		
		snooze(s_dragUpdatePeriod);
	}
	
	// signal that I've stopped
	m_dragUpdateThread = 0;
}
*/

// ---------------------------------------------------------------- //
// BView impl.
// ---------------------------------------------------------------- //

void MediaRoutingView::AttachedToWindow() {
	_inherited::AttachedToWindow();
	
//  11aug99: now listens to the NodeManager instead
//	// now that I can receive messages, establish a connection with
//	// the media roster
//	ASSERT(m_roster);
//	status_t err = m_roster->StartWatching(BMessenger(this));
//	if(err < B_OK) {
//		PRINT((
//			"* MediaRoutingView::AttachedToWindow():\n"
//			"StartWatching() failed:\n%s\n",
//			strerror(err)));
//	}

	// attach to manager
	ASSERT(manager);
	status_t err = add_observer(this, manager);
	ASSERT(err == B_OK);
	
	// 4jun99: set pulse rate for animation
	BWindow* pWnd = Window();
	ASSERT(pWnd);
	pWnd->SetPulseRate(s_pulseRate);
}

void MediaRoutingView::AllAttached() {
	_inherited::AllAttached();
	
	// populate with existing nodes & connections
	// [e.moon 17oct99] moved here after changing MediaRoutingContainerView's
	// TipManager usage (the TipManager must exist before cells are created.)
	populateInit();
	
	// grab keyboard events
	MakeFocus();
}	


void MediaRoutingView::DetachedFromWindow() {
	_inherited::DetachedFromWindow();

//	PRINT((
//		"MediaRoutingView::DetachedFromWindow()\n"));
	status_t err;
		
//	// break connection with media roster
//	ASSERT(m_roster);
//	status_t err = m_roster->StopWatching(BMessenger(this));
//	if(err < B_OK) {
//		PRINT((
//			"* MediaRoutingView::DetachedFromWindow():\n"
//			"StopWatching() failed:\n%s\n",
//			strerror(err)));
//	}

	// detach from manager
	if(manager) {

		err = remove_observer(this, manager);
		ASSERT(err == B_OK);
				
		// stop listening to each ref
		for(node_location_map::iterator it = m_nodeLocationMap.begin();
			it != m_nodeLocationMap.end(); ++it) {
			NodeRef* ref;
			if(manager->getNodeRef((*it).first, &ref) == B_OK) {

				err = remove_observer(this, ref);
				ASSERT(err == B_OK);
			}
		}

		const_cast<NodeManager*&>(manager) = 0;
	}
}

void MediaRoutingView::Draw(BRect updateRect) {
//	// draw any portions of the view to the right or below the
//	// grid extents
//
//	BRegion bgRegion;
//	bgRegion.Include(updateRect);
//	bgRegion.Exclude(m_containerView->Frame());
//	SetHighColor(s_workspaceColor);
//	FillRegion(&bgRegion);
}

void MediaRoutingView::DrawAfterChildren(BRect updateRect) {

//	PRINT(("MediaRoutingView::DrawAfterChildren()\n"));

	// * draw wires
	
	// draw nonselected wires
	// +++++ lazy; restrict to wires crossing the update rect
	for(wire_map::iterator it = m_wireMap.begin();
		it != m_wireMap.end(); it++) {
		Wire* pWire = (*it).second;
		if(pWire->isSelected())
			// draw on next pass
			continue;
		drawWire(pWire);
	}

	// draw selected wires
	// +++++ lazy; restrict to wires crossing the update rect
	for(int wire = 0; wire < m_selectedWires.size(); wire++) {
		Wire* pWire = m_selectedWires[wire];
		ASSERT(pWire->isSelected());
		drawWire(pWire);
	}
	
	// * draw jack outlines (for drag'n'drop wiring)

	// origin vertex has blinking highlight
	if(m_dragFromVertex && m_dragVertexFlashState)
		drawJackOutline(m_dragFromVertex);
	
	// target vertex has solid highlight
	if(m_dragToVertex)
		drawJackOutline(m_dragToVertex);

//	PRINT(("*DONE: MediaRoutingView::DrawAfterChildren()\n"));
}

void MediaRoutingView::KeyDown(const char* pBytes, int32 bytes) {
	switch(*pBytes) {
		case B_DELETE:
			handleDeleteKey();
			break;
			
		default:
			break;
	}
}

// clear selection [e.moon 11oct99]
void MediaRoutingView::MouseDown(
	BPoint											where) {
	
	deselectAll();
}

void MediaRoutingView::Pulse() {
//	PRINT((
//		"MediaRoutingView::Pulse()\n"));
	if(!m_dragVertexFlashOn)
		return;
	
	if(m_dragVertexFlashTimer-- == 0) {
//		PRINT((
//			"MediaRoutingView::Pulse(): jack flash tick (%s)\n",
//			m_dragVertexFlashState ? "on" : "off"));

		// tick animation
		m_dragVertexFlashTimer = s_dragVertexFlashPeriod;
		m_dragVertexFlashState = !m_dragVertexFlashState;
		
		ASSERT(m_dragFromVertex);
		Invalidate(jackOutlineBounds(m_dragFromVertex));
		if(m_dragToVertex)
			Invalidate(jackOutlineBounds(m_dragToVertex));
			
		// 'flush' redraw request
		BWindow* pWnd = Window();
		ASSERT(pWnd);
		pWnd->UpdateIfNeeded();
	}
}

// ---------------------------------------------------------------- //
// keypress handlers
// ---------------------------------------------------------------- //

void MediaRoutingView::handleDeleteKey() {
	PRINT(("MediaRoutingView::handleDeleteKey()\n"));
	status_t err;
	
	if(m_selectedCell) {
		// try to release it
		// (+++++ if the node is unreleasable, shouldn't the user be informed?)

		m_selectedCell->ref->releaseNode();
		m_selectedCell->Invalidate();
		return;
	}
	
	for(int n = 0; n < m_selectedWires.size(); ++n) {
		err = manager->disconnect(
			m_selectedWires[n]->connection);
		if(err < B_OK) {
			PRINT((
				" - manager->disconnect('%s'->'%s') failed:\n"
				"   %s\n",
				m_selectedWires[n]->connection.outputName(),
				m_selectedWires[n]->connection.inputName(),
				strerror(err)));
			continue;
		}
	}
}
	

// ---------------------------------------------------------------- //
// BHandler impl
// ---------------------------------------------------------------- //

void MediaRoutingView::MessageReceived(BMessage* message) {
//	PRINT((
//		"* MediaRoutingView::MessageReceived()\n"));
//	message->PrintToStream();
		
	switch(message->what) {
	
		// *** Media Roster messages (forwarded by NodeManager) ***
		
		case B_MEDIA_NODE_CREATED:
			nodesCreated(message);
			break;

		case B_MEDIA_NODE_DELETED:
			nodesDeleted(message);
			break;

		case B_MEDIA_CONNECTION_MADE:
			connectionMade(message);
			break;
			
		case B_MEDIA_CONNECTION_BROKEN:
			connectionBroken(message);
			break;
		
		// *** NodeManager messages ***
		case NodeManager::M_RELEASED:
//			// 11aug99
//			// The NodeManager got deleted before I did!
//			PRINT((
//				"*** MediaRoutingView::MessageReceived():\n"
//				"    M_DELETED received.\n"));
//			const_cast<NodeManager*&>(manager) = 0;
//			
//			// +++++ explode?
//			
			// release it
			remove_observer(this, manager);
			const_cast<NodeManager*&>(manager) = 0;	

			// +++++ disable view!

			break;
			
		
		// *** NodeRef messages ***
		case NodeRef::M_RELEASED:
			releaseNodeRef(message);
			break;

		// *** DormantNodeView messages ***
		
		case DormantNodeView::M_INSTANTIATE_NODE:
			instantiateNode(message);
			break;
			
		// *** view-configuration messages ***
		
		case M_SET_FONT:
			setFont(message);
			break;
			
		case M_BROADCAST_SELECTION:
			if(m_selectedCell) {
				notifyNodeSelected(m_selectedCell->ref->id());
				notifyGroupSelected(
					m_selectedCell->ref->group() ? 
						m_selectedCell->ref->group()->id() : 0);
			} else {
				notifyNodeSelected(0);
				notifyGroupSelected(0);
			}
			break;

		// *** Tracker messages ***
		
		case 'DATA': // is this kosher?
			handleTrackerDrop(message);
			break;
			
// 11aug99	
//		case M_RELEASE_NODE:
//			releaseNode(message);
//			break;

		default:
			_inherited::MessageReceived(message);
	}
}

// ---------------------------------------------------------------- //
// internal message-handling helpers
// ---------------------------------------------------------------- //

void MediaRoutingView::setFont(BMessage* message) {
	uint32 code;
	if(message->FindInt32("code", (int32*)&code) == B_OK) {
		// the easy, though possibly risky way (?)
		m_cellFont.SetFamilyAndStyle(code);
	} else {
		font_family family;
		font_style style;
		const char* pCur;

		if(message->FindString("family", &pCur) == B_OK) {
			strncpy(family, pCur, B_FONT_FAMILY_LENGTH);

			if(message->FindString("style", &pCur) == B_OK) {
				strncpy(style, pCur, B_FONT_STYLE_LENGTH);
				m_cellFont.SetFamilyAndStyle(family, style);		
			}
		}
	}
	
	float size;
	if(message->FindFloat("size", &size) == B_OK)
		m_cellFont.SetSize(size);
	
	uint16 face;
	if(message->FindInt16("face", (int16*)&face) == B_OK)
		m_cellFont.SetFace(face);
		
	// send font to cell views:

	BView* pChild = m_containerView->ChildAt(0);
	while(pChild) {
		LiveNodeView* nodeView = dynamic_cast<LiveNodeView*>(pChild);
		if(nodeView)
			nodeView->setFont(m_cellFont);

		pChild = pChild->NextSibling();
	}
}

void MediaRoutingView::releaseNode(BMessage* message) {
	// +++++
	
//	media_node_id id;
//	if(message->FindInt32("nodeID", (int32*)&id) < B_OK) {
//		PRINT((
//			"MediaRoutingView::releaseNode(): no nodeID\n"));
//		return;
//	}
//	
//	// look it up
//	ChildNodeView* pChildView = dynamic_cast<ChildNodeView*>(
//		nodeViewFor(id));
//	if(!pChildView) {
//		PRINT((
//			"MediaRoutingView::releaseNode(): no matching child-node view\n"));
//		return;
//	}
//		
//	// release
//	pChildView->releaseNode();
//	pChildView->Invalidate();
}

// ---------------------------------------------------------------- //
// BMediaRoster message-handling helpers
// ---------------------------------------------------------------- //

void MediaRoutingView::nodesCreated(
	BMessage*										message) {
	
//	PRINT((
//		"MediaRoutingView::nodesCreated()\n"));

	BWindow* pWnd = Window();
	if(pWnd)
		pWnd->BeginViewTransaction();

	// walk node IDs in message; find slots for new nodes
	// and add them to the grid:

	type_code type;
	int32 count;
	status_t err = message->GetInfo("media_node_id", &type, &count);
	if(err < B_OK) {
		PRINT((
			"MediaRoutingView::nodesCreated(): GetInfo() failed.\n"));
		goto release_window;
	}
	if(!count) {
		PRINT((
			"MediaRoutingView::nodesCreated(): no node IDs in message.\n"));
		goto release_window;
	}

	for(int32 n = 0; n < count; n++) {
		int32 id;
		err = message->FindInt32("media_node_id", n, &id);
		if(err < B_OK) {
			PRINT((
				"* MediaRoutingView::nodesCreated(): FindInt32() failed\n"));
			continue;
		}

		// already got a cell for this node? bail [e.moon 11oct99]
		if(nodeViewFor(id)) {
//			PRINT((
//				"* MediaRoutingView::nodesCreated(): already have a cell for %ld\n", id));
			continue;
		}
		
		// 11aug99: find NodeRef; add self as observer
		manager->lock();
		NodeRef* ref;
		err = manager->getNodeRef(id, &ref);
		if(err < B_OK) {
			PRINT((
				"* MediaRoutingView::nodesCreated(): manager->getNodeRef(%ld) failed:\n"
				"  %s\n",
				id, strerror(err)));
			manager->unlock();
			continue;
		}
		err = add_observer(this, ref);
		ASSERT(err == B_OK);
		manager->unlock();
	
		// create & configure the cell view (+++++ defer until observer status confirmed?)
		LiveNodeView* nodeView = new LiveNodeView(this, ref);

		uint32 destRow = 0;
		uint32 destColumn = 0;
		bool makeSelected = false;

		// manually dropped node?
		if(m_extDroppedNode == ref) {
					
			m_extDroppedNode = 0;
			makeSelected = true;

			// place at last known position, translated to node column/row
			// (shift down/right if dropped in a gutter)
				
			// convert point to container-view coords
			BPoint p = m_containerView->ConvertFromScreen(m_extDropPoint);

			// convert to GridView coords, then to node row/column
			int32 gridColumn = columnIndexFor(p.x);
			if(gridColumn < 0) {
				// dropped outside of grid
				// +++++ estimate target column
				gridColumn = columns();
			}
			else {
				if(!(gridColumn%2)) ++gridColumn;
				destColumn = gridToNodeColumnIndex(gridColumn);
			}

			int32 gridRow = rowIndexFor(p.y);
			if(gridRow < 0) {
				// dropped outside of grid
				// +++++ estimate real row
				gridRow = rows();
			}
			else {
				if(!(gridRow%2)) ++gridRow;

				// check for overlap with cell beginning above the
				// target position
				uint32 oColumn, oRow, cSpan, rSpan;
				BView* pCell;
				if(getCell(gridColumn, gridRow,
					&pCell, &oColumn, &oRow, &cSpan, &rSpan) &&
					oRow < gridRow) {
					
					// +++++ hack 3jun99:
					//   place at top of cell (workaround for
					//   displaceCells() bug.)
					gridRow = oRow;
				}

				destRow = gridToNodeRowIndex(gridRow);
			}

//			PRINT((
//				"- adding dropped node at (%ld,%ld) (from point %.1f,%.1f)\n",
//				destColumn, destRow, m_extDropPoint.x, m_extDropPoint.y));
			
		} else {		

			// place cell based on node type
			destColumn = externalNodeColumn(ref);
			// placed in first row; pushes existing cells downward
		}

		nodeView->setFont(m_cellFont);
		addNodeView(nodeView, destColumn, destRow);
		if(makeSelected)
			selectCell(nodeView);
	}
	
release_window:
	if(pWnd)
		pWnd->EndViewTransaction();
		
//	PRINT((
//		"*DONE: MediaRoutingView::nodesCreated()\n"));
}

// +++++ listen to NodeRef instead -- there's a potential sync bug
//       here (MediaRoutingView may try to access a NodeRef that's already
//       been deleted)

void MediaRoutingView::nodesDeleted(BMessage* message) {
//	PRINT((
//		"MediaRoutingView::nodesDeleted()\n"));
		
	BWindow* pWnd = Window();
	if(pWnd)
		pWnd->BeginViewTransaction();

	// walk the set of deleted nodes, removing their cells from the
	// grid:
	type_code type;
	int32 count;
	status_t err = message->GetInfo("media_node_id", &type, &count);
	if(err < B_OK) {
		PRINT((
			"MediaRoutingView::nodeDeleted(): GetInfo() failed.\n"));
		goto release_window;
	}
	if(!count)
		goto release_window;

	for(int32 n = 0; n < count; n++) {
		int32 id;
		err = message->FindInt32("media_node_id", n, &id);
		if(err < B_OK) {
			PRINT((
				"MediaRoutingView::nodeDeleted(): FindInt32() failed\n"));
			continue;
		}

		// find cell
		node_location_map::iterator it = m_nodeLocationMap.find(id);
		if(it == m_nodeLocationMap.end()) {
			PRINT((
				"MediaRoutingView::nodeDeleted(): node %ld not in map.\n",
				id));
			continue;
		}

		// remove it
		deleteNodeView((*it).second.first, (*it).second.second);
		
		// remove any associated wires
		
	}

release_window:
	if(pWnd)
		pWnd->EndViewTransaction();
}

void MediaRoutingView::connectionMade(BMessage* message) {
		
//	PRINT((
//		"MediaRoutingView::connectionMade()\n"));
	status_t err;
	
	// extract each connection from message; fetch Connection
	// instances from NodeManager and create associated Wire objects.
	
	for(int32 n = 0;; ++n) {
		media_output output;
		const void* data;
		ssize_t dataSize;
		
		// fetch output
		err = message->FindData("output", B_RAW_TYPE, n, &data, &dataSize);
		if(err < B_OK) {
			if(!n) {
				PRINT((
					"* MediaRoutingView::connectionMade(): no entries in message.\n"));
			}
			break;
		}
		if(dataSize < sizeof(media_output)) {
			PRINT((
				"* MediaRoutingView::connectionMade(): not enough data for output.\n"));
			break;
		}
		output = *(media_output*)data;

		// find connection
		Connection con;
		err = manager->findConnection(
			output.node.node,
			output.source,
			&con);
		if(err < B_OK) {
			PRINT((
				"* MediaRoutingView::connectionMade(): CONNECTION NOT FOUND:\n"
				"  output == %ld:%s\n",
				output.node.node, output.name));
			continue;
		}
		
		// find cell view for the producer node
		uint32 producerNodeColumn, producerNodeRow;
		LiveNodeView* producerCell = nodeViewFor(
			con.sourceNode(), &producerNodeColumn, &producerNodeRow);
		if(!producerCell) {
			// +++++
			PRINT((
				"*** MediaRoutingView::connectionMade(): no cell for output node %ld\n",
				con.sourceNode()));
			continue;
		}

		// fetch current (supposed) cell height
		uint32 prevProducerNodeRowSpan = gridToNodeRowSpan(
			producerCell->gridRowsNeeded());

		// rescan the producer-node's inputs/outputs
		displaceOutputWiresFor(producerCell);		
		producerCell->updateInputs();
		producerCell->updateOutputs();
		
		// resize cell if needed
		uint32 producerNodeRowSpan = gridToNodeRowSpan(
			producerCell->gridRowsNeeded());
		if(producerNodeRowSpan != prevProducerNodeRowSpan)
			resizeNodeView(producerNodeColumn, producerNodeRow, producerNodeRowSpan);

		// find cell view for the consumer node		
		uint32 consumerNodeColumn, consumerNodeRow;
		LiveNodeView* consumerCell = nodeViewFor(
			con.destinationNode(),
			&consumerNodeColumn, &consumerNodeRow);
		if(!consumerCell) {
			// +++++
			PRINT((
				"!!!!! MediaRoutingView::connectionMade(): no cell for input node %ld\n",
				con.destinationNode()));
			continue;
		}

		// fetch current (supposed) cell height
		uint32 prevConsumerNodeRowSpan = gridToNodeRowSpan(
			consumerCell->gridRowsNeeded());
			
		// rescan the consumer-node's inputs/outputs
		displaceInputWiresFor(consumerCell);
		consumerCell->updateInputs();
		consumerCell->updateOutputs();
		
		// resize cell if needed
		uint32 consumerNodeRowSpan = gridToNodeRowSpan(
			consumerCell->gridRowsNeeded());
		if(consumerNodeRowSpan != prevConsumerNodeRowSpan)
			resizeNodeView(consumerNodeColumn, consumerNodeRow, consumerNodeRowSpan);

		// create & add wire for this connection
		Wire* pWire = new Wire(con);
		if(routeWire(pWire) == B_OK) {
			addWire(pWire);
		}	else
			delete pWire;

		// create or merge groups
		NodeRef* producer = producerCell->ref;
		NodeRef* consumer = consumerCell->ref;
		NodeGroup* group = 0;
		
		BString nameBuffer = "Untitled Group ";
		nameBuffer << m_nextGroupNumber++;
		
		if(producer->isInternal()) {
			if(producer->group()) {
				// * USE / REPLACE PRODUCER'S GROUP
				if(consumer->group() && consumer->isInternal()) {
					// merge into consumer's group
					manager->mergeGroups(
						consumer->group(),
						producer->group());
				}
				else if(consumer->isInternal()) {
					// add consumer to my group
					producer->group()->addNode(consumer);
				}
				
				group = producer->group();
			}
			else if(consumer->group() && consumer->isInternal()) {
				// * USE CONSUMER'S GROUP
				// add to consumer's group
				group = consumer->group();
				group->addNode(producer);
			}
			else {
				// * MAKE NEW GROUP
				group = manager->createGroup(nameBuffer.String());
				ASSERT(group);
				group->addNode(producer);
				if(consumer->isInternal())
					group->addNode(consumer);			
			}
		}
		else if(consumer->isInternal() && !consumer->group()) {
			// * MAKE GROUP FOR LONE CONSUMER
			group = manager->createGroup(nameBuffer.String());
			ASSERT(group);
			group->addNode(consumer);
		}

		if(group)
			notifyGroupSelected(group->id());
			
		// reroute wires that may have been displaced
		rerouteDisplacedWires();
	}
}


// +++++ split groups when necessary

void MediaRoutingView::connectionBroken(BMessage* message) {
//	PRINT((
//		"MediaRoutingView::connectionBroken()\n"));

	// extract connection info from message, find & remove wires
	status_t err;
	for(int32 n=0;;n++) {		
		// fetch connection id
		uint32 conID;

		err = message->FindInt32("__connection_id", n, (int32*)&conID);
		if(err < B_OK) {
			PRINT((
				"* MediaRoutingView::connectionBroken(): no __connection_id in entry.\n"));
			break;
		}
		if(!conID) {
			PRINT((
				"* MediaRoutingView::connectionBroken(): invalid __connection_id in entry.\n"));
			continue;
		}
		
//		// find the represented Connection
//		// (failure's okay -- if I disconnected it, the Connection may well
//		//  be dead & gone by now.)
//		Connection con;
//		err = manager->findConnection(
//			sourceNodeID,
//			source,
//			&con);
//		
//		if(err < B_OK) {
//			// +++++ no whining
//			PRINT((
//				"  - connection already released\n"));
//			continue;
//		}

		// look up wire
		wire_map::iterator it = m_wireMap.find(conID);
		if(it == m_wireMap.end()) {
			PRINT((
				"* MediaRoutingView::connectionBroken(): no wire for connection '%ld'\n",
				conID));
			continue;
		}
		Wire* wire = (*it).second;
		ASSERT(wire);
				
		// remove & delete the wire representing the broken connection
		removeAllWireSegments(wire);
		removeWire(wire);

		// rescan the producer-node's outputs
		uint32 producerNodeColumn, producerNodeRow;
		LiveNodeView* producerCell = nodeViewFor(
			wire->connection.sourceNode(),
			&producerNodeColumn, &producerNodeRow);
		
		if(!producerCell) {
			// +++++
			PRINT((
				"!!!!! MediaRoutingView::connectionBroken(): no cell for output node %ld\n",
				wire->connection.sourceNode()));
		}
		else if(!producerCell->ref->isReleased()){
		
			// fetch current (supposed) cell height
			uint32 prevProducerNodeRowSpan = gridToNodeRowSpan(
				producerCell->gridRowsNeeded());

			displaceOutputWiresFor(producerCell);		
			producerCell->updateOutputs();

			// resize cell if needed
			uint32 producerNodeRowSpan = gridToNodeRowSpan(
				producerCell->gridRowsNeeded());
			if(producerNodeRowSpan != prevProducerNodeRowSpan)
				resizeNodeView(producerNodeColumn, producerNodeRow, producerNodeRowSpan);
		}

		// rescan the consumer-node's inputs
		uint32 consumerNodeColumn, consumerNodeRow;
		LiveNodeView* consumerCell = nodeViewFor(
			wire->connection.destinationNode(),
			&consumerNodeColumn, &consumerNodeRow);
		if(!consumerCell) {
			// +++++
			PRINT((
				"!!!!! MediaRoutingView::connectionBroken(): no cell for input node %ld\n",
				wire->connection.destinationNode()));
		}
		else if(!consumerCell->ref->isReleased()) {

			// fetch current (supposed) cell height
			uint32 prevConsumerNodeRowSpan = gridToNodeRowSpan(
				consumerCell->gridRowsNeeded());

			displaceInputWiresFor(consumerCell);
			consumerCell->updateInputs();

			// resize cell if needed
			uint32 consumerNodeRowSpan = gridToNodeRowSpan(
				consumerCell->gridRowsNeeded());
			if(consumerNodeRowSpan != prevConsumerNodeRowSpan)
				resizeNodeView(consumerNodeColumn, consumerNodeRow, consumerNodeRowSpan);
		}

		delete wire;

		// [e.moon 28sep99]
		// if the source and destination nodes belong to the same group,
		// and if no direct or indirect connection remains between the
		// source and destination nodes, split groups +++++

		if(
			producerCell->ref->group() != 0 &&
			producerCell->ref->group() == consumerCell->ref->group() &&
			!manager->findRoute(
				producerCell->ref->id(),
				consumerCell->ref->id())) {
			
			NodeGroup* newGroup;
			manager->splitGroup(
				producerCell->ref,
				consumerCell->ref,
				&newGroup);
				
			PRINT(("##### split to '%s'\n", newGroup->name()));
		}

		// reroute wires that may have been displaced
		rerouteDisplacedWires();
	}

	// +++++
	dumpWires();
//	PRINT((
//		"*DONE: MediaRoutingView::connectionBroken()\n"));
}

// ---------------------------------------------------------------- //
// NodeRef message handlers
// ---------------------------------------------------------------- //

void MediaRoutingView::releaseNodeRef(
	BMessage*										message) {

//	PRINT((
//		"MediaRoutingView::releaseNodeRef()\n"));

	// fetch id
	media_node_id id;
	status_t err = message->FindInt32("nodeID", (int32*)&id);
	if(err < B_OK) {
		PRINT((
			"* MediaRoutingView::nodeRefDeleted(): nodeID not found.\n"));
		return;
	}
	
	// find cell
	node_location_map::iterator it = m_nodeLocationMap.find(id);
	if(it == m_nodeLocationMap.end()) {
		PRINT((
			"* MediaRoutingView::nodeRefDeleted(): node %ld not in map.\n",
			id));
		return;
	}

	// send acknowledgement
	LiveNodeView* v = nodeViewAt((*it).second.first, (*it).second.second);
	ASSERT(v);
	remove_observer(this, v->ref);

	// remove it
	deleteNodeView((*it).second.first, (*it).second.second);
}



// ---------------------------------------------------------------- //
// DormantNodeView message handlers
// e.moon 3jun99
// ---------------------------------------------------------------- //

void MediaRoutingView::instantiateNode(BMessage* message) {

	// fetch node info from message
	dormant_node_info info;
	const void* pData;
	ssize_t length;
	status_t err = message->FindData(
		"dormantNodeInfo",
		B_RAW_TYPE,	&pData, &length);
	if(err < B_OK) {	
		PRINT((
			"! MediaRoutingView::instantiateNode(): couldn't find dormantNodeInfo.\n"));
		return;
	}
	if(length < sizeof(dormant_node_info)) {
		PRINT((
			"! MediaRoutingView::instantiateNode(): dormantNodeInfo short.\n"));
		return;
	}
	memcpy((void*)&info, pData, length);

	// fetch drop point
	err = message->FindPoint("_drop_point_", &m_extDropPoint);
	if(err < B_OK) {
		PRINT((
			"! MediaRoutingView::instantiateNode(): couldn't find _drop_point_\n"));
		return;
	}
	
	// instantiate
	uint32 flags = 0;
	
	NodeRef* droppedNode;
	err = manager->instantiate(
		info,
		&droppedNode,
		flags);
		
	if(err < B_OK) {
		BString buffer(
			"instantiateNode()\n"
			"An error occurred adding the node '");
		buffer << info.name << "':\n" << strerror(err) << "\n";
		(new BAlert("Error creating node", buffer.String(), "Ok"))->Go();
		return;
	}
	
//	// group it +++++ add group merging, etc.
//	char buffer[64];
//	strncpy(buffer, droppedNode->name(), 55);
//	strcat(buffer, ":default");
//
//	NodeGroup* g = manager->createGroup(buffer);
//	ASSERT(g);
//	err = g->addNode(droppedNode);
//	ASSERT(err == B_OK);
//	
//	PRINT((
//		"# created group '%s': %ld\n",
//		g->name(), g->id()));
	
	// prepare to handle resultant 'node created' message from NodeManager
	m_extDroppedNode = droppedNode;
}

void MediaRoutingView::handleTrackerDrop(
	BMessage*										message) {

	status_t err;
	
	// fetch drop point
	err = message->FindPoint("_drop_point_", &m_extDropPoint);
	if(err < B_OK) {
		PRINT((
			"! MediaRoutingView::handleTrackerDrop(): couldn't find _drop_point_\n"));
		return;
	}

	// fetch ref
	entry_ref ref;
	err = message->FindRef("refs", &ref);
	if(err < B_OK) {
		PRINT((
			"! MediaRoutingView::handleTrackerDrop(): no refs!\n"));
		return;
	}	

	// instantiate
	NodeRef* droppedNode;
	err = manager->instantiate(
		ref,
		B_BUFFER_PRODUCER,
		&droppedNode);
		
	if(err < B_OK) {
		BString buffer(
			"handleTrackerDrop()\n"
			"No node was found to play the file, or there\n"
			"was an error creating the node ('");
		buffer << strerror(err) << "')\n";
		(new BAlert("Error creating node", buffer.String(), "Ok"))->Go();
		return;
	}
	
	// [e.moon 14oct99]
	// disable position reporting for file-players that produce
	// encoded-video output.
	// ICK. +++++
	media_output encVideoOutput;
	err = droppedNode->findFreeOutput(
		&encVideoOutput,
		B_MEDIA_ENCODED_VIDEO);
	if(err == B_OK) {
		PRINT((
			"### disabling position reporting for '%s'\n",
			droppedNode->name()));

		droppedNode->setFlags(
			droppedNode->flags() | NodeRef::NO_POSITION_REPORTING);
	}

//	// [e.moon 14oct99] disable position reporting for encoded video-file players
//	PRINT((
//		"### investigating file-player formats\n"));
//	if(
//		(droppedNode->kind() & B_FILE_INTERFACE)/* &&
//		(droppedNode->kind() & B_BUFFER_PRODUCER)*/) {
//
//		PRINT((
//			"### investigating file-player formats\n"));
//
//		bool foundVideoFormat = false;
//
//		// fetch list of all formats supported by the node
//		size_t formatBufferSize = 8;
//		int32 count;
//		media_file_format* formats = new media_file_format[formatBufferSize];
//		
//		while(formatBufferSize < 64) {
//			count = formatBufferSize;
//			err = manager->roster->GetFileFormatsFor(
//				droppedNode->node(),
//				formats,
//				&count);
//			if(err < B_OK) {
//				PRINT((
//					"! MediaRoutingView::instantiateNode(): roster->GetFileFormatsFor():\n"
//					"  %s\n",
//					strerror(err)));
//				count = 0;
//				break;
//			}
//			
//			// got them all
//			if(count < formatBufferSize)
//				break;
//				
//			// buffer filled; double its capacity & try again
//			formatBufferSize *= 2;
//			delete [] formats;
//			formats = new media_file_format[formatBufferSize];
//		}
//	
//		// walk formats
//		for(int32 n = 0; n < count; ++n) {
//			PRINT((
//				"### format %ld: %s\n", n, formats[n].pretty_name));
//			if(formats[n].capabilities & media_file_format::B_KNOWS_ENCODED_VIDEO) {
//				foundVideoFormat = true;
//				break;
//			}
//		}
//		
//		// clean up	
//		delete [] formats;
//		
//		if(foundVideoFormat) {
//			PRINT((
//				"### disabling position reports for video-playback node\n"));
//			droppedNode->setFlags(
//				droppedNode->flags() & NodeRef::NO_POSITION_REPORTING);
//		}
//	}			

//	// group it +++++ add group merging, etc.
//	char buffer[64];
//	strncpy(buffer, droppedNode->name(), 55);
//	strcat(buffer, ":default");
//
//	NodeGroup* g = manager->createGroup(buffer);
//	ASSERT(g);
//	err = g->addNode(droppedNode);
//	ASSERT(err == B_OK);
//	
//	PRINT((
//		"# created group '%s': %ld\n",
//		g->name(), g->id()));
	
	// prepare to handle resultant 'node created' message from NodeManager
	m_extDroppedNode = droppedNode;
}


//// ---------------------------------------------------------------- //
//// connection/transport helpers
//// ---------------------------------------------------------------- //
//
//// add child-cell references for every node in the given transport
//void MediaRoutingView::addTransportRefs(Transport* pTransport) {
//	PRINT(("MediaRoutingView::addTransportRefs(%p)\n", pTransport));
//	ASSERT(pTransport);
//
//	vector<media_node_id> nodesInTransport;
//	pTransport->listNodes(nodesInTransport);
//		
//	for(int n = 0; n < nodesInTransport.size(); ++n) {
//#ifdef DEBUG
//		// +++++ check ?
//#endif
//
//		// get cell
//		ChildNodeView* pChild = dynamic_cast<ChildNodeView*>(
//			nodeViewFor(nodesInTransport[n]));
//		if(!pChild) {
//			PRINT((
//				"MediaRoutingView::addTransportRefs():\n"
//				"no child view for node (%ld) in new transport\n",
//				nodesInTransport[n]));
//			//assert(!"BOOM");
//			continue;
//		}
//
//		// reference transport from cell
//		pTransport->addRef();
//		pChild->setActiveTransport(pTransport);
//	}
//}
//	
//// remove child-cell referencess for every node in the given transport
//void MediaRoutingView::removeTransportRefs(Transport* pTransport) {
//	PRINT(("MediaRoutingView::addTransportRefs(%p)\n", pTransport));
//	ASSERT(pTransport);
//
//	// release transport from each node
//	vector<media_node_id> nodesInTransport;
//	pTransport->listNodes(nodesInTransport);
//			
//	for(int n = 0; n < nodesInTransport.size(); ++n) {
//		ChildNodeView* pChild = dynamic_cast<ChildNodeView*>(
//		nodeViewFor(nodesInTransport[n]));
//		if(!pChild) {
//			PRINT((
//				"MediaRoutingView::removeTransportRefs():\n"
//				"no child view for node (%ld) in transport\n",
//				nodesInTransport[n]));
//			continue;
//			//assert(!"BOOM");
//		}
//				
//		if(pChild->activeTransport() != pTransport) {
//			PRINT((
//				"MediaRoutingView::removeTransportRefs():\n"
//				"mismatched transport for child view for node (%ld)\n",
//				nodesInTransport[n]));
//			continue;
//			//assert(!"BOOM");
//		}
//				
//		pChild->activeTransport()->release();
//		pChild->setActiveTransport(0);
//	}
//}
//
//void MediaRoutingView::initTransports() {
//	PRINT((
//		"MediaRoutingView::initTransports()\n"));
//	
//	status_t err;
//	media_node mixerNode;
//	err = m_roster->GetAudioMixer(&mixerNode);
//	if(err < B_OK) {
//		PRINT((
//			"- couldn't find audio mixer: %s\n", strerror(err)));
//	} else {
//		// create transport for the system mixer (and every node
//		// downstream of it)
//		m_pAudioMixerTransport = Transport::GetTransportFor(
//			this, mixerNode.node,
//			Transport::SCAN_OUTPUTS,
//			Transport::NO_START_STOP | Transport::NO_SEEK);
//		ASSERT(m_pAudioMixerTransport);
//	}
//	
//	media_node inputNode;
//	err = m_roster->GetAudioInput(&inputNode);
//	if(err < B_OK) {
//		PRINT((
//			"- couldn't find audio input: %s\n", strerror(err)));
//	} else {
//		// create transport
//		m_pAudioInputTransport = Transport::GetTransportFor(
//			this, inputNode.node,
//			0,
//			Transport::DRIVEN_BY_OUTPUTS);
//		ASSERT(m_pAudioInputTransport);
//	}
//	
//	// find video window node
//	media_node voutNode;
//	err = m_roster->GetVideoOutput(&voutNode);
//	if(err < B_OK) {
//		PRINT(("- couldn't find video output: %s\n", strerror(err)));
//	} else {
//		// create transport
//		m_pVideoOutputTransport = Transport::GetTransportFor(
//			this, voutNode.node,
//			0,
//			Transport::DRIVEN_BY_INPUTS);
//		ASSERT(m_pVideoOutputTransport);
//	}
//	
//	Transport::DumpAll();
//}

// ---------------------------------------------------------------- //
// outbound messaging (notification)
// ---------------------------------------------------------------- //

void MediaRoutingView::notifyNodeSelected(
	uint32											nodeID) {
	BLooper* l = Looper();

	if(!l)
		return;
		
	BMessage m(M_NODE_SELECTED);
	m.AddInt32("nodeID", nodeID);
	BMessenger(l).SendMessage(&m);
}

void MediaRoutingView::notifyGroupSelected(
	uint32											groupID) {
	BLooper* l = Looper();

	if(!l)
		return;
		
	BMessage m(M_GROUP_SELECTED);
	m.AddInt32("groupID", groupID);
	BMessenger(l).SendMessage(&m);
}
	

// ---------------------------------------------------------------- //
// node & gutter row/column access
// ---------------------------------------------------------------- //

// convert node-row index to grid row index
inline uint32 MediaRoutingView::nodeToGridRowIndex(uint32 index) const {
	return (index*2) + 1;
}

// convert grid row index to node row index
// assertion fails if row isn't odd!
inline uint32 MediaRoutingView::gridToNodeRowIndex(uint32 row) const {
	ASSERT(row%2);
	return (row-1) / 2;
}

inline NodeRow* MediaRoutingView::nodeRow(uint32 index) const {
	GridRow* pRow = rowAt(nodeToGridRowIndex(index));
	return dynamic_cast<NodeRow*>(pRow);
}

inline uint32 MediaRoutingView::nodeRows() const {
	return (rows()-1)/2;
}

// figure a row-span from the given height in pixels
inline uint32 MediaRoutingView::nodeRowSpanFor(float height) const {
	uint32 nodeRowSpan = 1;
	if(height > NODE_ROW_HEIGHT)
		nodeRowSpan += (uint32)ceil(
			(height-NODE_ROW_HEIGHT) / (NODE_ROW_HEIGHT + GUTTER_ROW_HEIGHT));
	return nodeRowSpan;
}

// convert node-column index to grid column index
inline uint32 MediaRoutingView::nodeToGridColumnIndex(uint32 index) const {
	return (index*2) + 1;
}

// convert grid column index to node column
// assertion fails if column isn't odd!
inline uint32 MediaRoutingView::gridToNodeColumnIndex(uint32 column) const {
	ASSERT(column%2);
	return (column-1)/2;
}

inline NodeColumn* MediaRoutingView::nodeColumn(uint32 index) const {
	GridColumn* pColumn = columnAt(nodeToGridColumnIndex(index));
	return dynamic_cast<NodeColumn*>(pColumn);
}

inline uint32 MediaRoutingView::nodeColumns() const {
	return (columns()-1)/2;
}

// * gutter row/column access
uint32 MediaRoutingView::gutterRowIndex(
	uint32 index, row_gutter_t pos) const {
	return (index*2) + ((pos == BOTTOM_GUTTER) ? 2 : 0);
}

GutterRow* MediaRoutingView::gutterRow(
	uint32 index, row_gutter_t pos) const {
	GridRow* pRow = rowAt(gutterRowIndex(index, pos));
	return dynamic_cast<GutterRow*>(pRow);	
}

uint32 MediaRoutingView::gutterColumnIndex(
	uint32 index, column_gutter_t pos) const {
	return (index*2) + ((pos == RIGHT_GUTTER) ? 2 : 0);	
}
GutterColumn* MediaRoutingView::gutterColumn(
	uint32 index, column_gutter_t pos) const {
	GridColumn* pColumn = columnAt(gutterColumnIndex(index, pos));
	return dynamic_cast<GutterColumn*>(pColumn);	
}

// * vertex-based row/column access

uint32 MediaRoutingView::gridRowIndexFor(const WireVertex& vertex) const {
	switch(vertex.rowOffsetType) {
		case WireVertex::TOP_GUTTER_ROW:
			return gutterRowIndex(vertex.nodeRow, TOP_GUTTER);
		
		case WireVertex::BOTTOM_GUTTER_ROW:
			return gutterRowIndex(vertex.nodeRow, BOTTOM_GUTTER);
			
		case WireVertex::NODE_JACK:
			// was wrong [15jun99] - didn't take offset into account
			return nodeToGridRowIndex(vertex.nodeRow) + vertex.rowOffsetIndex;
	}
	
	ASSERT(!"MediaRoutingView::gridRowIndexFor(): invalid column offset in vertex");
	return 0;
}

RoutingRow* MediaRoutingView::rowFor(const WireVertex& vertex) const {
	return dynamic_cast<RoutingRow*>(rowAt(gridRowIndexFor(vertex)));
}

uint32 MediaRoutingView::gridColumnIndexFor(const WireVertex& vertex) const {
	switch(vertex.columnOffsetType) {
		case WireVertex::NODE_INPUT_JACK:
		case WireVertex::NODE_OUTPUT_JACK:
			return nodeToGridColumnIndex(vertex.nodeColumn);
		
		case WireVertex::LEFT_GUTTER_COLUMN:
			return gutterColumnIndex(vertex.nodeColumn, LEFT_GUTTER);
		
		case WireVertex::RIGHT_GUTTER_COLUMN:
			return gutterColumnIndex(vertex.nodeColumn, RIGHT_GUTTER);
	}
	
	ASSERT(!"MediaRoutingView::gridColumnIndexFor(): invalid row offset in vertex");
	return 0;
}

RoutingColumn* MediaRoutingView::columnFor(const WireVertex& vertex) const {
	return dynamic_cast<RoutingColumn*>(columnAt(gridColumnIndexFor(vertex)));
}

// convert node-row span to grid row span
uint32 MediaRoutingView::nodeToGridRowSpan(uint32 nodeRowSpan) const {
	return (nodeRowSpan*2) - 1;
}

// convert grid row span to node row span
// (an assertion will fail given an invalid node-row span!)
uint32 MediaRoutingView::gridToNodeRowSpan(uint32 gridRowSpan) const {
	// must be an odd number
	ASSERT(gridRowSpan%2);
	return (gridRowSpan+1)/2;
}

// ---------------------------------------------------------------- //
// impl. operations
// ---------------------------------------------------------------- //

// add/remove node row (+ manage wires)
// (index specifies node row)

void MediaRoutingView::addNodeRow(uint32 index, bool bRerouteWires) {

	if(index < nodeRows()) {
		// any wire touching the given node row or either adjacent
		// gutter row may need to be rerouted:
		displaceWiresInNodeRow(index);
	}

	// add the row objects to the grid
	addGridRows(index, 1);

	if(bRerouteWires)
		rerouteDisplacedWires();
}

bool MediaRoutingView::nodeRowEmpty(uint32 index) const {

	uint32 nodeRow = nodeToGridRowIndex(index);

	// make sure there are no nodes in the row
	BView* pView;
	uint32 oColumn, oRow, cSpan, rSpan;
	
	for(uint32 nodeColumn = nodeToGridColumnIndex(0);
		nodeColumn < nodeColumns();
		nodeColumn += 2) {

		if(!getCell(nodeColumn, nodeRow, &pView, &oColumn, &oRow, &cSpan, &rSpan))
			continue;
			
		// found a node view
		return false;
	}
	return true;
}

// row must be empty
void MediaRoutingView::deleteNodeRow(uint32 index, bool bRerouteWires) {

	ASSERT(index < nodeRows());
	ASSERT(nodeRowEmpty(index));

	// pull out all wires crossing the given row
	displaceWiresInNodeRow(index);

	// remove the grid rows
	deleteGridRows(index, 1);

	if(bRerouteWires)
		// recreate the wires
		rerouteDisplacedWires();	
}
	
// add/remove column (+ manage wires)
// (index specifies node column)
void MediaRoutingView::addNodeColumn(uint32 index, bool bRerouteWires) {

	displaceWiresInNodeColumn(index);

	// add the column objects to the grid
	addGridColumns(index, 1);

	if(bRerouteWires)	
		// recreate the wires
		rerouteDisplacedWires();	
}

bool MediaRoutingView::nodeColumnEmpty(uint32 index) const {
	uint32 nodeColumn = nodeToGridColumnIndex(index);

	// make sure there are no nodes in the row
	BView* pView;
	uint32 oColumn, oRow, cSpan, rSpan;
	
	for(uint32 nodeRow = nodeToGridRowIndex(0); nodeRow < nodeRows(); nodeRow += 2) {
		if(!getCell(nodeColumn, nodeRow, &pView, &oColumn, &oRow, &cSpan, &rSpan))
			continue;
			
		// found a node view
		return false;
	}
	return true;
}

// column must be empty
void MediaRoutingView::deleteNodeColumn(uint32 index, bool bRerouteWires) {

	ASSERT(nodeColumnEmpty(index));

	displaceWiresInNodeColumn(index);

	// remove the grid column
	deleteGridColumns(index, 1);
	
	if(bRerouteWires)	
		// recreate the wires
		rerouteDisplacedWires();	
}

// ---------------------------------------------------------------- //
// node operations
// ---------------------------------------------------------------- //

// fetch given node view
LiveNodeView* MediaRoutingView::nodeViewAt(
	uint32 nodeColumn, uint32 nodeRow,
	uint32* poNodeRows) const {
	
	BView* pView;
	uint32 cSpan, rSpan;
	if(!getCell(
		nodeToGridColumnIndex(nodeColumn), nodeToGridRowIndex(nodeRow),
		&pView, &cSpan, &rSpan))
		return 0;
	
	LiveNodeView* nodeView = dynamic_cast<LiveNodeView*>(pView);
	if(!nodeView)
		return 0;

	// found occupied slot; return # of rows and view pointer
	if(poNodeRows)
		*poNodeRows = gridToNodeRowSpan(rSpan);
	return nodeView;
}

LiveNodeView* MediaRoutingView::nodeViewFor(media_node_id id,
	uint32* poNodeColumn, uint32* poNodeRow) const {
	node_location_map::const_iterator it = m_nodeLocationMap.find(id);
	if(it == m_nodeLocationMap.end())
		return 0;
		
	// aren't pairs of pairs fun?
	if(poNodeColumn) *poNodeColumn = (*it).second.first;
	if(poNodeRow) *poNodeRow = (*it).second.second;
	return nodeViewAt((*it).second.first, (*it).second.second);
}


// fetch node rowspan of given node view
// (an assertion fails if the cell couldn't be found)
uint32 MediaRoutingView::nodeViewRows(uint32 nodeColumn, uint32 nodeRow) const {
	uint32 nodeRowSpan;
	LiveNodeView* nodeView = nodeViewAt(nodeColumn, nodeRow, &nodeRowSpan);
	ASSERT(nodeView);
	return nodeRowSpan;
}	

// add/remove node (+ associated wires)
// - new columns & rows are created, and blocking cells moved, as
//   necessary to place the node in the requested position.

void MediaRoutingView::addNodeView(
	LiveNodeView* nodeView, uint32 nodeColumn, uint32 nodeRow) {

	// * figure needed row span
	float width, height;
	nodeView->GetPreferredSize(&width, &height);
	uint32 nodeRowSpan = nodeRowSpanFor(height);
//+++++broken 27may99
//	uint32 nodeRowSpan = gridToNodeRowSpan(nodeView->gridRowsNeeded());

/*
	PRINT((
		"MediaRoutingView::addNodeView(%ld,%ld): '%s', %ld rows needed; CURRENT STRUCTURE\n",
			nodeColumn, nodeRow,
			nodeView->nodeInfo().name, nodeRowSpan));
			
	dumpStructure();
*/

	// * displace blocking cells
	//   18may99: new displace mechanism

	if(displaceCells(nodeColumn, nodeRow, 1, DISPLACE_DOWN, nodeRowSpan) < nodeRowSpan) {
		ASSERT(!"MediaRoutingView::addNodeView(): displaceCells() failed\n");
	}

	// make certain there's room to place the cell; there
	// should always be a free rightmost column & bottom-
	// most row.
	// 27may99
	
	uint32 endNodeRow = nodeRow + nodeRowSpan;
	if(endNodeRow >= nodeRows())
		addGridRows(
			nodeRows(), // at bottom
			(endNodeRow+1)-nodeRows());

	uint32 endNodeColumn = nodeColumn + 1;
	if(endNodeColumn >= nodeColumns())
		addGridColumns(
			nodeColumns(), // at bottom
			(endNodeColumn+1)-nodeColumns());

	// *	make sure the target slot is free

	uint32 column = nodeToGridColumnIndex(nodeColumn);
	uint32 row = nodeToGridRowIndex(nodeRow);
	uint32 rowSpan = nodeToGridRowSpan(nodeRowSpan);
	
//	ASSERT(row+rowSpan <= rows());
	
	if(!canAddCell(column, row, 1, rowSpan)) {
		PRINT((
			"MediaRoutingView::addNodeView(): target slot blocked:\n"
			"(%ld, %ld) + %ld node row%s\n", column, row, nodeRowSpan,
			(nodeRowSpan==1) ? "" : "s"));
		dumpStructure();
		ASSERT(!"canAddCell() failed");
	}
	
	// * add node view to the grid

	status_t err = addCell(
		nodeView,
		column, row,
		1, rowSpan);
	ASSERT(err == B_OK);
	
	// * add to the node-location map

	m_nodeLocationMap.insert(
		node_location_map::value_type(
			nodeView->ref->id(),
			make_pair(nodeColumn, nodeRow)));
		
//	tracex("addNodeView", dbgMAJOR, "done");
}

void MediaRoutingView::deleteNodeView(
	uint32 nodeColumn, uint32 nodeRow) {

	PRINT((
		"MediaRoutingView::deleteNodeView(%ld,%ld)\n",
			nodeColumn, nodeRow));

	// * find the view: get cell position in grid units
	uint32 column = nodeToGridColumnIndex(nodeColumn);
	uint32 row = nodeToGridRowIndex(nodeRow);

	// fetch from grid	
	LiveNodeView* nodeView;
	BView* pView;
	uint32 columnSpan, rowSpan;
	bool bRet = getCell(column, row, &pView, &columnSpan, &rowSpan);
	ASSERT(bRet);

	nodeView = dynamic_cast<LiveNodeView*>(pView);
	ASSERT(nodeView);

	// deselect
	if(m_selectedCell == nodeView)
		deselectCell(nodeView);

	// remove from node-location map
	node_location_map::iterator it = m_nodeLocationMap.find(
		nodeView->ref->id());
		
	// ++++++
	// [e.moon 27sep99] this assertion just failed after deleting several
	// nodes in quick succession -- haven't reproduced the bug yet.
	ASSERT(it != m_nodeLocationMap.end());
	m_nodeLocationMap.erase(it);
	
	// remove from grid, then delete the view
	BView* pCellView = removeCell(column, row);
	ASSERT(pCellView == nodeView);
	delete pCellView;
}

// move node (+ associated wires)
//       no longer tries to displace cells that are in the way;
//       that's the caller's responsibility
// * the destination column/row MUST be free, or an assert will fail
// +++++ 17may99: if a displaced-wire list is given, move any wires
//                touching the node view into the list; these wires
//                are only recreated if no list is provided.

void MediaRoutingView::moveNodeView(
	uint32 fromNodeColumn, uint32 fromNodeRow,
	uint32 toNodeColumn, uint32 toNodeRow,
	bool bRerouteWires) {

//	ProfileBlock _b(g_prof, "moveNodeView");

//	tracex("moveNodeView", dbgMAJOR, "call ("<<fromNodeColumn<<','<<fromNodeRow<<") -> ("<<
//		toNodeColumn<<','<<toNodeRow<<')');
		
//	PRINT((
//		"MediaRoutingView::moveNodeView(): (%ld,%ld) -> (%ld,%ld)\n",
//		fromNodeColumn, fromNodeRow, toNodeColumn, toNodeRow));

	// * null case?

	if(fromNodeColumn == toNodeColumn &&
		fromNodeRow == toNodeRow) {
//		tracex("moveNodeView", dbgMAJOR, "nothing to do");
		return;
	}

	// * find the node view

	LiveNodeView* nodeView;
	BView* pView;
	uint32 columnSpan, rowSpan;
	uint32 fromColumn = nodeToGridColumnIndex(fromNodeColumn);
	uint32 fromRow = nodeToGridRowIndex(fromNodeRow);
	bool bRet = getCell(fromColumn, fromRow, &pView, &columnSpan, &rowSpan);
	if(!bRet) {
		PRINT(( "MediaRoutingView::moveNodeView(): source cell empty:\n"));
		dumpStructure();
		ASSERT(bRet);
	}
	
	nodeView = dynamic_cast<LiveNodeView*>(pView);
	ASSERT(nodeView);
	
	// * find & tuck away all wires connected to this node

	displaceAllWiresFor(nodeView);
	
	// mark soon-to-be-previously occupied rect as changed
	invalidateContainerRect(nodeView->Frame());

	// * remove the node view from the grid structure
	//   (11may99: leaves the view itself attached to the container)
	pView = removeCellEntry(fromColumn, fromRow);
	ASSERT(pView == nodeView);

	// * displace wires crossing the target slot(s)
	//   19may99
	displaceWiresInSlot(toNodeColumn, toNodeRow, gridToNodeRowSpan(rowSpan));

	// * find & check the target slot(s) for stray cells
	
	uint32 toColumn = nodeToGridColumnIndex(toNodeColumn);
	uint32 toRow = nodeToGridRowIndex(toNodeRow);

	if(!canAddCell(toColumn, toRow, 1, rowSpan)) {
		PRINT((
			"MediaRoutingView::moveNodeView(): target slot blocked:\n"
			"(%ld, %ld) + %ld grid rows\n", toColumn, toRow, rowSpan));
		dumpStructure();
		ASSERT(!"canAddCell() failed");
	}	
	
	// * replace node view in grid at target slot
	status_t err = addCell(nodeView, toColumn, toRow, 1, rowSpan);
	ASSERT(err == B_OK);

	// * mark new rect as changed	
	invalidateContainerRect(nodeView->Frame());
	
	// * update node-location map entry
	media_node_id id = nodeView->ref->id();
	m_nodeLocationMap[id].first = toNodeColumn;
	m_nodeLocationMap[id].second = toNodeRow;

	if(bRerouteWires)
		rerouteDisplacedWires();

//	tracex("moveNodeView", dbgMAJOR, "done");
}

// resize node view to given number of rows: this should Just Work(tm),
// which means that nodes occupying slots below the given row may need
// to be pushed downwards, and that new rows may need to be created
// at the bottom of the grid.

void MediaRoutingView::resizeNodeView(
		uint32 nodeColumn, uint32 nodeRow, uint32 toNodeRowSpan) {

	PRINT((
		"resizeNodeView(): %ld, %ld +%ld\n",
		nodeColumn, nodeRow, toNodeRowSpan));

	ASSERT(toNodeRowSpan > 0);
	status_t err;

	// * fetch original node-view
	uint32 prevNodeRowSpan;
	LiveNodeView* nodeView = nodeViewAt(nodeColumn, nodeRow, &prevNodeRowSpan);
	ASSERT(nodeView);

	uint32 column = nodeToGridColumnIndex(nodeColumn);
	uint32 row = nodeToGridRowIndex(nodeRow);
	
	if(toNodeRowSpan > prevNodeRowSpan) {
		// make room (displace following node views if necessary)
		// [18may99]
		uint32 displaceCount = toNodeRowSpan - prevNodeRowSpan;
		uint32 free = displaceCells(nodeColumn, nodeRow + prevNodeRowSpan, 1,
			DISPLACE_DOWN, displaceCount);
		ASSERT(free == displaceCount);
		
		PRINT((
			" - displaced %ld node rows\n", displaceCount));
			
		// resize the cell
		err = resizeCell(
			column, row, 1, nodeToGridRowSpan(toNodeRowSpan));
		ASSERT(err == B_OK);
	}
	else {
		status_t err = resizeCell(
			column, row, 1, nodeToGridRowSpan(toNodeRowSpan));
		ASSERT(err == B_OK);
	}
}


// select/deselect node

void MediaRoutingView::selectCell(
	LiveNodeView*								cell,
	bool												notify) {
	
//	PRINT(("* MediaRoutingView::selectCell(): %ld, %s\n",
//		cell->ref->id(),
//		notify?"notify":"quiet"));
		
	ASSERT(cell);
	if(m_selectedCell) {
		// already selected?
		if(m_selectedCell == cell)
			return;

		deselectCell(m_selectedCell, false);	
	}
	
	m_selectedCell = cell;
	m_selectedCell->select();
	selectWiresBySource(m_selectedCell);
	
	if(notify) {
		// notify node inspector
		notifyNodeSelected(cell->ref->id());
	
		// notify group inspector
		notifyGroupSelected(
			cell->ref->group() ?
				cell->ref->group()->id() :
				0);
	}
}

void MediaRoutingView::deselectCell(
	LiveNodeView*								cell,
	bool												notify) {

//	PRINT(("* MediaRoutingView::deselectCell(): %ld, %s\n",
//		cell->ref->id(),
//		notify?"notify":"quiet"));
	
	ASSERT(cell);
	if(m_selectedCell != cell) {
		// not selected
		PRINT((
			"MediaRoutingView::deselectCell(%x): not selected!\n", cell));
		return;
	}
	
	if(notify) {
		// notify node inspector
		notifyNodeSelected(0);
	
		// notify group inspector
		notifyGroupSelected(0);
	}

	m_selectedCell->deselect();
	deselectWiresBySource(m_selectedCell);
	m_selectedCell = 0;
}

void MediaRoutingView::deselectAllCells() {
	if(m_selectedCell)
		deselectCell(m_selectedCell);
}

void MediaRoutingView::selectWiresBySource(LiveNodeView* pCell) {
	ASSERT(pCell);
	
	int startIndex = m_selectedWires.size();
	findWiresBySourceNode(pCell->ref->id(), m_selectedWires);
	//findWiresByDestinationNode(pCell->node().node, m_selectedWires);
	
	for(int n = startIndex; n < m_selectedWires.size(); n++) {
		m_selectedWires[n]->select();
//		touchWire(m_selectedWires[n]);
		drawWire(m_selectedWires[n]);
	}
}

// [14jun99]
void MediaRoutingView::deselectWiresBySource(LiveNodeView* pCell) {
	// build new selected-wire vector
	vector<Wire*> newSelectedWires;
	ASSERT(pCell);
	for(int n = 0; n < m_selectedWires.size(); n++) {
		if(m_selectedWires[n]->connection.sourceNode() == pCell->ref->id()) {
			// found a wire; deselect it & redraw
			m_selectedWires[n]->deselect();
			drawWire(m_selectedWires[n]);
		}
		else
			// wire doesn't stem from pCell; leave it in the new set
			newSelectedWires.push_back(m_selectedWires[n]);
	}
	
	// swap new vector into place, then redraw all currently selected
	// wires
	m_selectedWires.swap(newSelectedWires);
	for(int n = 0; n < m_selectedWires.size(); n++)
		drawWire(m_selectedWires[n]);
}

// [14jun99]
void MediaRoutingView::selectWire(Wire* pWire) {
	ASSERT(pWire);
//	describeWire(pWire); // +++++ testing
	if(pWire->isSelected())
		return;
	
	pWire->select();
	m_selectedWires.push_back(pWire);
	drawWire(pWire);
}

// [14jun99]
void MediaRoutingView::deselectWire(Wire* pWire) {
	ASSERT(pWire);
	if(!pWire->isSelected())
		return;
	
	// deselect & redraw wire	
	pWire->deselect();
	drawWire(pWire);
	
	// remove from set
	vector<Wire*>::iterator itEnd =
		remove(
			m_selectedWires.begin(),
			m_selectedWires.end(),
			pWire);
	ASSERT(itEnd - m_selectedWires.end() == 1);
	m_selectedWires.resize(m_selectedWires.size()-1);

	// redraw all currently selected wires
	for(int n = 0; n < m_selectedWires.size(); n++)
		drawWire(m_selectedWires[n]);
}

// [14jun99]
void MediaRoutingView::deselectAllWires() {
	for(int n = 0; n < m_selectedWires.size(); n++) {
		m_selectedWires[n]->deselect();
//		touchWire(m_selectedWires[n]);
		drawWire(m_selectedWires[n]);
	}
	m_selectedWires.clear();
}

// [14jun99]
void MediaRoutingView::deselectAll() {
	// +++++ probably does a bit of unnecessary redrawing

	deselectAllCells();
	deselectAllWires();
}

// ---------------------------------------------------------------- //
// node operation helpers
// ---------------------------------------------------------------- //

// * Populates the grid with all nodes currently in the NodeManager.

void MediaRoutingView::populateInit() {
	Autolock _l(manager);
//	PRINT((
//		"MediaRoutingView::populateInit()\n"));

	status_t err;		
	void* cookie=0;
	NodeRef* ref;
	while(manager->getNextRef(&ref, &cookie) == B_OK) {
//		PRINT((
//			"  ref: '%s'\n", ref->name()));

		// add self as observer
		err = add_observer(this, ref);
		ASSERT(err == B_OK);
				
		// create & place node view (+++++ defer until observer status confirmed!)
		LiveNodeView* cell = new LiveNodeView(this, ref);
		cell->setFont(m_cellFont);
		addNodeView(cell, externalNodeColumn(ref), 0);		
	}
	
	cookie=0;
	Connection con;
	while(manager->getNextConnection(&con, &cookie) == B_OK) {
		// create wire
		Wire* wire = new Wire(con);
		if(routeWire(wire) != B_OK) {
			// +++++ why would this ever happen?
			delete wire;
			continue;
		}
		
		addWire(wire);	
	}
	
	// create built-in groups
	// [extended 25sep99 e.moon]

	NodeGroup* group;

	NodeRef* videoIn = manager->videoInputNode();
	if(videoIn) {
		group = manager->createGroup("Video Input");
		group->setRunMode(BMediaNode::B_RECORDING);
		group->addNode(videoIn);
	}
	
	NodeRef* audioIn = manager->audioInputNode();
	if(audioIn) {
		group = manager->createGroup("Audio Input");
		group->setRunMode(BMediaNode::B_RECORDING);
		group->addNode(audioIn);
	}

	NodeRef* videoOut = manager->videoOutputNode();
	if(videoOut) {
		group = manager->createGroup("Video Output");
		group->addNode(videoOut);
	}
	
// crashes the Media Addon Server and eventually the OS
// +++++ [e.moon 28sep99
//
//	NodeRef* mixer = manager->audioMixerNode();
//	if(mixer) {
//		group = manager->createGroup("Audio Mixer");
//		group->addNode(mixer);
//
//		NodeRef* audioOut = manager->audioOutputNode();
//		if(audioOut) {
//			group->addNode(audioOut);
//		} else {
//			PRINT((
//				"!!! mangled audio configuration: mixer but no output node\n"));
//		}
//	}
}

// * Returns ideal position (column) for the given
//   (externally created) node
uint32 MediaRoutingView::externalNodeColumn(
	NodeRef*										ref) {

	if(ref->kind() & B_PHYSICAL_OUTPUT)
		return 3;
	else if(ref->kind() & B_SYSTEM_MIXER)
		return 2;
	else if(ref->kind() & B_BUFFER_CONSUMER)
		return (ref->kind() & B_BUFFER_PRODUCER) ? 1 : 2;
	else
		return 0;
}


// Ensures that a cell spanning <nodeRowSpan> rows can be added
// at the given position:
//
// - Any cells blocking the destination column/row[s] are shifted in
//   the direction specified if possible. (+)
//
// - Creates additional rows and columns as necessary to make room,
//   and to leave an empty row and column available at the bottom/right
//   of the grid.
//
// (+) If the destination cell[s] can't be freed up, returns false
// (this may only happen when displacing leftwards or upwards);
// otherwise returns true.

// *** displace mechanism reworked 17may99	

// +++++ BUG 3jun99: doesn't seem to properly handle vertical displace
//                   in the middle of a multi-row cell.
uint32 MediaRoutingView::displaceCells(
	int32 nodeColumn, int32 nodeRow, uint32 nodeRowSpan,
	displace_t direction, uint32 distance,
	bool bMoveCells, bool bRerouteWires, BView* pIgnoreCell) {

//	ProfileBlock _b(g_prof, "displaceCells");

//	tracex("displaceCells", dbgMAJOR,
//		"begin ("<<nodeColumn<<','<<nodeRow<<") +"<<nodeRowSpan<<" ["<<
//		((direction == DISPLACE_LEFT) ? "left" :
//			(direction == DISPLACE_RIGHT) ? "right" :
//				(direction == DISPLACE_UP) ? "up" : "down")<<' '<< distance<<']');
		
	uint32 ret;		
	if(direction == DISPLACE_LEFT || direction == DISPLACE_RIGHT) {
		if(bMoveCells) {
			// first figure max distance cells can be moved
			distance = h_displaceCells(
				nodeColumn, nodeRow, nodeRowSpan,
				direction, distance,
				false, pIgnoreCell);
		}
				
		ret = h_displaceCells(
			nodeColumn, nodeRow, nodeRowSpan,
			direction, distance,
			bMoveCells, pIgnoreCell);
	}
	else
		ret = v_displaceCells(
			nodeColumn, nodeRow, nodeRowSpan,
			direction, distance,
			bMoveCells, pIgnoreCell);
	
	if(bRerouteWires)
		rerouteDisplacedWires();
		
	return ret;
}

// 18may99
// split into separate horizontal & vertical displace methods

uint32 MediaRoutingView::v_displaceCells(
	int32 nodeColumn, int32 nodeRow, uint32 nodeRowSpan,
	displace_t direction, uint32 distance,
	bool bMoveCells, BView* pIgnoreCell) {

	ASSERT(direction == DISPLACE_UP || direction == DISPLACE_DOWN);
	
//	tracex("v_displaceCells", dbgMAJOR,
//		"begin ("<<nodeColumn<<','<<nodeRow<<") +"<<nodeRowSpan<<" ["<<
//		((direction == DISPLACE_LEFT) ? "left" :
//			(direction == DISPLACE_RIGHT) ? "right" :
//				(direction == DISPLACE_UP) ? "up" : "down")<<' '<< distance<<']');

	// sanity check
	if(nodeColumn < 0 || nodeRow < 0 || distance == 0) {
//		tracex("v_displaceCells", dbgMAJOR,
//			"out of bounds");
		return 0;
	}

	if(nodeRowSpan > 1) {
		// when displacing vertically, row span is ignored
//		tracex("v_displaceCells", dbgMAJOR,
//			"vertical displace: ignoring nodeRowSpan > 1");
		nodeRowSpan = 1;
	}
		
	// scan for a blocking cell:
	int32 rowDelta = (direction == DISPLACE_UP) ? -1 : 1;
		
	uint32 free = 0;
	for(
		;
		nodeRow >= 0 && distance;
		nodeRow += rowDelta, distance--, free++) {
			
		BView* pCell;
		uint32 oColumn, oRow, cSpan, rSpan;
		if(getCell(
			nodeToGridColumnIndex(nodeColumn),
			nodeToGridRowIndex(nodeRow),
			&pCell, &oColumn, &oRow, &cSpan, &rSpan) &&
			pCell != pIgnoreCell) {

							
			int32 displaceAtRow = (direction == DISPLACE_UP) ?
				gridToNodeRowIndex(oRow)-1 :
				gridToNodeRowIndex(oRow+rSpan+1);

			// * recurse *
			// found a blocking cell; make room for it to be shifted
			uint32 available = v_displaceCells(
				nodeColumn, displaceAtRow, 1,
				direction, distance,
				bMoveCells, pCell);

			// shift it if possible/requested:
			if(available && bMoveCells) {
				// at least some room was made; move the blocking cell
				// as far as it'll go
				int32 fromColumn = nodeColumn;
				ASSERT(fromColumn >= 0);
				int32 fromRow = gridToNodeRowIndex(oRow);
				ASSERT(fromRow >= 0);
				int32 toRow = (direction == DISPLACE_UP) ?
					fromRow - available :
					fromRow + available;
				ASSERT(toRow >= 0);
				int32 endRow = toRow + gridToNodeRowSpan(rSpan);

				if(endRow >= nodeRows()) {
					// create one more row than needed
					ASSERT(direction == DISPLACE_DOWN);
					addGridRows(
						nodeRows(), // at bottom
						(endRow+1)-nodeRows());
				}

				// move blocking cell
				moveNodeView(
					fromColumn, fromRow,
					fromColumn, toRow,
					false);
			}

			if(available < distance) {
				// ran out of room; get final count of available 
				// slots and bail out of loop
				free += available;
					
				// this should only happen when pushing upwards
				ASSERT(direction == DISPLACE_UP);
					
				break;
			}
		} //if(getCell( ...
	} //for( ...

	return free;
}

uint32 MediaRoutingView::h_displaceCells(
	int32 nodeColumn, int32 nodeRow, uint32 nodeRowSpan,
	displace_t direction, uint32 distance,
	bool bMoveCells, BView* pIgnoreCell) {

	ASSERT(direction == DISPLACE_LEFT || direction == DISPLACE_RIGHT);
	
//	tracex("h_displaceCells", dbgMAJOR,
//		"begin ("<<nodeColumn<<','<<nodeRow<<") +"<<nodeRowSpan<<" ["<<
//		((direction == DISPLACE_LEFT) ? "left" :
//			(direction == DISPLACE_RIGHT) ? "right" :
//				(direction == DISPLACE_UP) ? "up" : "down")<<' '<< distance<<
//				"] " << (bMoveCells ? "move" : "don't move"));

	// sanity check
	if(nodeColumn < 0 || nodeRow < 0 || distance == 0) {
//		tracex("h_displaceCells", dbgMAJOR,
//			"out of bounds");
		return 0;
	}

	// 18may99:
	// - walk rows first.
	// - if the top-level displaceCells() call was given (bMoveCells == true),
	//   h_displaceCells() is called twice: once with
	//   bMoveCells false to clamp the requested distance down to the maximum
	//   possible, then once with the new distance.  If bMoveCells is true,
	//   assert that the requested distance is available.

	uint32 minFree = distance;

	int32 columnDelta = (direction == DISPLACE_LEFT) ? -1 : 1;

	// walk rows; scan each for blocking cells and update minFree with the
	// minimum number of free slots
	
	for(uint32 curNodeRow = nodeRow;
		curNodeRow < nodeRow + nodeRowSpan;
		curNodeRow++) {

		// for each row, figure the number of columns that the cell can be
		// shifted (if less than the requested distance.)
		
		uint32 free = distance;

		uint32 curDistance = distance;
		uint32 curNodeColumn = nodeColumn;
		for(
			;
			curNodeColumn >= 0 && curDistance;
			curNodeColumn += columnDelta, curDistance--) {

			BView* pCell;
			uint32 oColumn, oRow, cSpan, rSpan;
			if(getCell(
				nodeToGridColumnIndex(curNodeColumn),
				nodeToGridRowIndex(curNodeRow),
				&pCell, &oColumn, &oRow, &cSpan, &rSpan) &&
				pCell != pIgnoreCell) {
							
				// found a blocking cell; make room for it to be shifted

				int32 displaceAtColumn = (direction == DISPLACE_LEFT) ?
					curNodeColumn - 1 :
					curNodeColumn + 1;

				int32 displaceAtRow = gridToNodeRowIndex(oRow);
				
				// * recurse *
				uint32 available = h_displaceCells(
					displaceAtColumn, displaceAtRow, gridToNodeRowSpan(rSpan),
					direction, curDistance,
					bMoveCells, pCell);
				
				if(bMoveCells) {
					// sanity check: the proper distance should have been calculated
					// by a previous h_displaceCells() call
					ASSERT(available == curDistance);
				}

				if(available && bMoveCells) {
					// at least some room was made; move the blocking cell
					// as far as it'll go
						
					int32 fromColumn = curNodeColumn;
					ASSERT(fromColumn >= 0);
					int32 fromRow = gridToNodeRowIndex(oRow);
					ASSERT(fromRow >= 0);
					int32 toColumn = (direction == DISPLACE_LEFT) ?
						fromColumn - available :
						fromColumn + available;
					ASSERT(toColumn >= 0);

//					tracex("h_displaceCells", dbgMAJOR, "moving cell from " <<fromColumn <<','<<
//						fromRow<<" to "<<toColumn<<','<<fromRow);

					// add columns if necessary
					if(toColumn >= nodeColumns()-1) {
						ASSERT(direction == DISPLACE_RIGHT);
						// create one more column than needed
						addGridColumns(
							nodeColumns(), // at bottom
							(toColumn+2)-nodeColumns());
					}

					// move blocking cell
					moveNodeView(
						fromColumn, fromRow,
						toColumn, fromRow,
						false);
				}

				if(available < curDistance) {
					// update distance returned
					free -= (curDistance-available);
					
					// this should only happen when pushing leftwards
					ASSERT(direction == DISPLACE_LEFT);
					
					// break out
					break;
				}
					
			}//if(getCell ...
		}//for(curNodeColumn ...

		// if number the number of columns the cell can be shifted is less
		// than the current minimum, update the minimum.
		if(minFree > free)
			minFree = free;

	}//for(curNodeRow ...
		
	return minFree;
}

// ---------------------------------------------------------------- //
// wire operations
// ---------------------------------------------------------------- //

// add/remove wire
void MediaRoutingView::addWire(Wire* wire) {
	ASSERT(wire);

	// precondition: wire isn't already in map
	ASSERT(
		m_wireMap.find(wire->connection.id()) == m_wireMap.end());

	// add the wire
	m_wireMap.insert(
		wire_map::value_type(
			wire->connection.id(),
			wire));
}

void MediaRoutingView::removeWire(Wire* wire) {
	ASSERT(wire);

	// precondition: wire is in the map
	wire_map::iterator it = m_wireMap.find(wire->connection.id());
	ASSERT(it != m_wireMap.end());

	// remove
	m_wireMap.erase(it);
	
	// 9jun99: remove from selected-wire set
	//        (duh)
	vector<Wire*>::iterator itNewEnd = remove(
		m_selectedWires.begin(), m_selectedWires.end(), wire);
	m_selectedWires.resize(itNewEnd - m_selectedWires.begin());
	PRINT(("%ld wires now selected\n", m_selectedWires.size()));
}


// doesn't delete wires
void MediaRoutingView::removeWires(
	const vector<Wire*>& wires) {
	
	for(vector<Wire*>::const_iterator it = wires.begin();
		it != wires.end();
		it++) {
		ASSERT(*it);
		removeWire(*it);
	}
}

// dump all wires to trace output
void MediaRoutingView::dumpWires() {
	PRINT((
		"dumpWires: %ld map entries\n",
		m_wireMap.size()));
	
	for(wire_map::iterator it = m_wireMap.begin();
		it != m_wireMap.end(); it++) {
		PRINT((
			"  %p: %ld -> %ld\n",
			(*it).second,
			(*it).second->connection.sourceNode(),
			(*it).second->connection.destinationNode()));
	}
}

// list all segments in the given wire
void MediaRoutingView::describeWire(Wire* wire) {
	PRINT((
		"MediaRoutingView::describeWire(%p)\n"
		"\t'%s' -> '%s'\n\n",
		wire,
		wire->connection.outputName(),
		wire->connection.inputName()));
		
	ASSERT(wire);
	for(int n = 0; n < wire->m_segments.size(); ++n) {
		PRINT((
			"\t%02d %s ->\n"
			"\t   %s\n",
			n+1,
			wire->m_segments[n].from.toString().String(),
			wire->m_segments[n].to.toString().String()));
	}
}


void MediaRoutingView::addWireSegment(
	const WireSegment& segment) {

//	ProfileBlock _b(g_prof, "addWireSegment");


	// add to columns
	uint32 fromGridColumn = gridColumnIndexFor(segment.from);
	uint32 toGridColumn = gridColumnIndexFor(segment.to);
	if(toGridColumn < fromGridColumn) {
		uint32 t = toGridColumn; toGridColumn = fromGridColumn; fromGridColumn = t;
	}	
	for(uint32 c = fromGridColumn; c <= toGridColumn; c++) {
		RoutingColumn* pColumn = dynamic_cast<RoutingColumn*>(columnAt(c));
		ASSERT(pColumn);
		pColumn->m_segments.push_back(segment);
	}
	
	// add to rows
	uint32 fromGridRow = gridRowIndexFor(segment.from);
	uint32 toGridRow = gridRowIndexFor(segment.to);
	if(toGridRow < fromGridRow) {
		uint32 t = toGridRow; toGridRow = fromGridRow; fromGridRow = t;
	}
	for(uint32 r = fromGridRow; r <= toGridRow; r++) {
		RoutingRow* pRow = dynamic_cast<RoutingRow*>(rowAt(r));
		ASSERT(pRow);
		pRow->m_segments.push_back(segment);
	}
	
	// schedule redraw
	touchSegment(segment);
}

void MediaRoutingView::removeWireSegment(
	const WireSegment& segment) {

//	ProfileBlock _b(g_prof, "removeWireSegment");

	uint32 fromGridColumn = gridColumnIndexFor(segment.from);
	uint32 toGridColumn = gridColumnIndexFor(segment.to);
	if(toGridColumn < fromGridColumn) {
		uint32 t = toGridColumn; toGridColumn = fromGridColumn; fromGridColumn = t;
	}
	for(uint32 c = fromGridColumn; c <= toGridColumn; c++) {
		RoutingColumn* pColumn = dynamic_cast<RoutingColumn*>(columnAt(c));
		ASSERT(pColumn);
		bool bRet = pColumn->removeSegment(segment);
		ASSERT(bRet);
	}
	
	// add to rows
	uint32 fromGridRow = gridRowIndexFor(segment.from);
	uint32 toGridRow = gridRowIndexFor(segment.to);
	if(toGridRow < fromGridRow) {
		uint32 t = toGridRow; toGridRow = fromGridRow; fromGridRow = t;
	}
	for(uint32 r = fromGridRow; r <= toGridRow; r++) {
		RoutingRow* pRow = dynamic_cast<RoutingRow*>(rowAt(r));
		ASSERT(pRow);
		bool bRet = pRow->removeSegment(segment);
		ASSERT(bRet);
	}

//	// remove from columns' sets
//	RoutingColumn* pFromColumn = columnFor(segment.from);
//	ASSERT(pFromColumn);
//	bool bRet = pFromColumn->removeSegment(segment);
//	ASSERT(bRet);
//	
//	RoutingColumn* pToColumn = columnFor(segment.to);
//	ASSERT(pToColumn);
//	if(pToColumn != pFromColumn) {
//		bRet = pToColumn->removeSegment(segment);
//		ASSERT(bRet);
//	}
//
//	// remove from rows' sets
//	RoutingRow* pFromRow = rowFor(segment.from);
//	ASSERT(pFromRow);
//	bRet = pFromRow->removeSegment(segment);
//	ASSERT(bRet);
//
//	RoutingRow* pToRow = rowFor(segment.to);
//	ASSERT(pToRow);
//	if(pToRow != pFromRow) {
//		bRet = pToRow->removeSegment(segment);
//		ASSERT(bRet);
//	}
	
	// schedule redraw
	touchSegment(segment);
}

// add/remove all segments for the given wire
void MediaRoutingView::addAllWireSegments(const Wire* pWire) {

	// add wire segments to each row and column they touch
	for(vector<WireSegment>::const_iterator it = pWire->m_segments.begin();
		it != pWire->m_segments.end(); it++)
		addWireSegment(*it);
}

void MediaRoutingView::removeAllWireSegments(const Wire* pWire) {

	// remove wire segments from each row and column they touch
	for(vector<WireSegment>::const_iterator it = pWire->m_segments.begin();
		it != pWire->m_segments.end(); it++)
		removeWireSegment(*it);
}

// ---------------------------------------------------------------- //
// drawing
// ---------------------------------------------------------------- //

// redraw the given rectangular region, specified in
// container-view units

void MediaRoutingView::invalidateContainerRect(BRect rect) {
//	ProfileBlock _b(g_prof, "invalidateContainerRect");

//	{ProfileBlock _b(g_prof, "invalidateContainerRect:1");
//	m_containerView->ConvertToParent(&rect);
//	}
//	{ProfileBlock _b(g_prof, "invalidateContainerRect:2");
	
	m_containerView->Invalidate(rect);

//		Invalidate(rect);
//	}
}

//// redraw all rectangles in the given region (which is specified
//// in container-view units)
//void MediaRoutingView::invalidateContainerRegion(BRegion* pRegion) {
//	ASSERT(pRegion);
//	ASSERT(m_containerView);
//	
//	for(int32 n = pRegion->CountRects(); n; --n) {
//		BRect r = m_containerView->ConvertToParent(pRegion->RectAt(n-1));
//		Invalidate(r);
//	}
//}

// draw wire
inline void MediaRoutingView::drawWire(Wire* pWire) {

	SetHighColor(pWire->isSelected() ? s_selectedWireColor : s_wireColor);

	for(int n = 0; n < pWire->m_segments.size(); n++)
//		FillRect(segmentToRect(pWire->m_segments[n]));
		drawWireSegment(pWire->m_segments[n]);
}

// draw wire segment
inline void MediaRoutingView::drawWireSegment(
	const WireSegment& segment) {

	FillRect(segmentToRect(segment));
	
	// draw filled rectangles around connection points
	// 24may99
	
	if(segment.to.columnOffsetType == WireVertex::NODE_INPUT_JACK) {
		BPoint endPoint = vertexToPoint(segment.to);
		endPoint.x--;
		endPoint.y--;
		BRect cRect(endPoint.x, endPoint.y, endPoint.x + 2, endPoint.y + 3);
		FillRect(cRect);
	}

	if(segment.from.columnOffsetType == WireVertex::NODE_OUTPUT_JACK) {
		BPoint endPoint = vertexToPoint(segment.from);
		endPoint.x -= 2;
		endPoint.y--;
		BRect cRect(endPoint.x, endPoint.y, endPoint.x + 2, endPoint.y + 3);
		FillRect(cRect);
	}
}


// ---------------------------------------------------------------- //
// grid-management
// ---------------------------------------------------------------- //

// add node row(s) (+gutter(s))
void MediaRoutingView::addGridRows(uint32 nodeRow, uint32 count) {

//	PRINT((
//		"MediaRoutingView::addGridRows(%ld, %ld)\n", nodeRow, count));
//	dumpStructure();

	ASSERT(nodeRow <= nodeRows());
	uint32 row = gutterRowIndex(nodeRow, TOP_GUTTER);
	
	status_t err;
	if(!rows()) {
		// insert topmost gutter row
		err = addRow(row, new GutterRow(GUTTER_ROW_HEIGHT));
		ASSERT(err == B_OK);
	}
	++row;
	
	while(count--) {
		// insert the node row
		err = addRow(row++, new NodeRow(NODE_ROW_HEIGHT));
		ASSERT(err == B_OK);
		
		// insert bottom gutter
		err = addRow(row++, new GutterRow(GUTTER_ROW_HEIGHT));
		ASSERT(err == B_OK);
	}

//	PRINT(( "*** NEW STRUCTURE\n"));
//	dumpStructure();
}
	
// remove node row(s) (+gutter(s))
void MediaRoutingView::deleteGridRows(uint32 nodeRow, uint32 count) {
	ASSERT((nodeRow+count)*2 < rows());
	
	status_t err;
	uint32 row = nodeToGridRowIndex(nodeRow);
	
	while(count--) {
		// remove node row (and, implicitly, any views it contains)
		err = deleteRow(row);
		ASSERT(err == B_OK);
		
		// remove gutter row
		err = deleteRow(row);
		ASSERT(err == B_OK);
	}
	
	if(rows() == 1) {
		// remove last gutter row
		err = deleteRow(0);
		ASSERT(err == B_OK);
	}
}

// add node column(s) (+gutter(s))
void MediaRoutingView::addGridColumns(uint32 nodeColumn, uint32 count) {
//	trace("begin ("<<nodeColumn<<","<<count<<")");
	ASSERT(nodeColumn <= nodeColumns());
	uint32 column = gutterColumnIndex(nodeColumn, LEFT_GUTTER);
	
	status_t err;
	if(!columns()) {
		// insert leftmost gutter column
		err = addColumn(column, new GutterColumn(GUTTER_COLUMN_WIDTH));
		ASSERT(err == B_OK);
	}
	++column;
	
	while(count--) {
		// insert the node column
		err = addColumn(column++, new NodeColumn(NODE_COLUMN_WIDTH));
		ASSERT(err == B_OK);

		// insert bottom gutter
		err = addColumn(column++, new GutterColumn(GUTTER_COLUMN_WIDTH));
		ASSERT(err == B_OK);
	}
}
	
// remove node column(s) (+gutter(s))
void MediaRoutingView::deleteGridColumns(uint32 nodeColumn, uint32 count) {
	ASSERT((nodeColumn+count)*2 < columns());
	
	status_t err;
	uint32 column = nodeToGridColumnIndex(nodeColumn);
	
	while(count--) {
		// remove node column (and, implicitly, any views it contains)
		err = deleteColumn(column);
		ASSERT(err == B_OK);
		
		// remove gutter column
		err = deleteColumn(column);
		ASSERT(err == B_OK);
	}
	
	if(columns() == 1) {
		// remove last gutter column
		err = deleteColumn(0);
		ASSERT(err == B_OK);
	}
}

// ---------------------------------------------------------------- //
// wire-management tools
// ---------------------------------------------------------------- //

// removes all the wires touching the given node row or column
// (including gutters), and adds them to m_displacedWires
// 21may99: no longer removes wires; just adds them to
//          m_displacedWires

uint32 MediaRoutingView::displaceWiresInNodeColumn(uint32 nodeColumn) {

	uint32 s = m_displacedWires.size();
	
	for(uint32 column = gutterColumnIndex(nodeColumn, LEFT_GUTTER);
		column <= gutterColumnIndex(nodeColumn, RIGHT_GUTTER);
		column++) {
		RoutingColumn* pColumn = dynamic_cast<RoutingColumn*>(columnAt(column));
		ASSERT(pColumn);
		
		for(list<WireSegment>::iterator itSeg = pColumn->m_segments.begin();
			itSeg != pColumn->m_segments.end(); itSeg++) {

			Wire* pWire = (*itSeg).pWire;
			
			// if the current wire isn't in the internal displace set, add it
			if(m_displacedWires.find(pWire) == m_displacedWires.end())
				m_displacedWires.insert(pWire);
		}
	}

	return m_displacedWires.size() - s;
}

uint32 MediaRoutingView::displaceWiresInNodeRow(uint32 nodeRow) {

	uint32 s = m_displacedWires.size();
	
	for(uint32 row = gutterRowIndex(nodeRow, TOP_GUTTER);
		row <= gutterRowIndex(nodeRow, BOTTOM_GUTTER);
		row++) {
		RoutingRow* pRow = dynamic_cast<RoutingRow*>(rowAt(row));
		ASSERT(pRow);
		
		for(list<WireSegment>::iterator itSeg = pRow->m_segments.begin();
			itSeg != pRow->m_segments.end(); itSeg++) {

			Wire* pWire = (*itSeg).pWire;
			
			// if the current wire isn't in the internal displace set, add it
			if(m_displacedWires.find(pWire) == m_displacedWires.end())
				m_displacedWires.insert(pWire);
		}
	}

	return m_displacedWires.size() - s;
}

// removes all wires touching the given, presumably empty node
// slot, and adds them to m_displacedWires
// +++++ should wires continue to be allowed to cross empty node slots?
//       (does this mean a new column-index type 'EMPTY_NODE_SLOT' or something?)

uint32 MediaRoutingView::displaceWiresInSlot(
	uint32 nColumn, uint32 nRow, uint32 nodeRowSpan) {

//	PRINT((
//		"MediaRoutingView::displaceWiresInSlot(%ld, %ld, +%ld)\n",
//		nColumn, nRow, nodeRowSpan));

	ASSERT(nColumn < nodeColumns());
	ASSERT(nRow < nodeRows());
	uint32 gridRow = nodeToGridRowIndex(nRow);
	uint32 endGridRow = gridRow + nodeToGridRowSpan(nodeRowSpan);
	
	NodeColumn* pColumn = nodeColumn(nColumn);
	ASSERT(pColumn);
	
	uint32 s = m_displacedWires.size();
	
	// inspect each segment in the column
	for(list<WireSegment>::const_iterator itCol = pColumn->m_segments.begin();
		itCol != pColumn->m_segments.end(); itCol++) {
		const WireSegment& colSeg = *itCol;
		
//		PRINT((
//			"column segment:\n%s ->\n%s\n",
//			colSeg.from.toString().String(), colSeg.to.toString().String()));

		// look for a matching segment in each row
		for(int curRow = gridRow; curRow < endGridRow; curRow++) {
			RoutingRow* pRow = dynamic_cast<RoutingRow*>(rowAt(curRow));
			ASSERT(pRow);

			for(list<WireSegment>::const_iterator itRow = pRow->m_segments.begin();
				itRow != pRow->m_segments.end(); itRow++) {
				const WireSegment& rowSeg = *itRow;

//				PRINT((
//					"row segment:\n%s ->\n%s\n",
//					rowSeg.from.toString().String(), rowSeg.to.toString().String()));

				if(rowSeg == colSeg) {
					// found an intersection
//					PRINT((
//						"intersecting segment:\n%s ->\n%s\n",
//						colSeg.from.toString().String(), colSeg.to.toString().String()));
				
					// if the current wire isn't in the internal displace set, add it
					Wire* pWire = rowSeg.pWire;
					if(m_displacedWires.find(pWire) == m_displacedWires.end())
						m_displacedWires.insert(pWire);
					break;
				}
			}
		}
	}

	return m_displacedWires.size() - s;
}
		
// displaces all wires touching (connected to input or output jacks
// of) the given cell view

uint32 MediaRoutingView::displaceAllWiresFor(LiveNodeView* nodeView) {
	ASSERT(nodeView);
	media_node_id id = nodeView->ref->id();

	uint32 s = m_displacedWires.size();
		
	// find touching wires
	vector<Wire*> displace;

	findWiresBySourceNode(id, displace);
	findWiresByDestinationNode(id, displace);
	
	for(int n = 0; n < displace.size(); n++) {
		Wire* pWire = displace[n];
		if(m_displacedWires.find(pWire) == m_displacedWires.end())
			m_displacedWires.insert(pWire);
	}
		
	return m_displacedWires.size() - s;
}

uint32 MediaRoutingView::displaceInputWiresFor(LiveNodeView* nodeView) {
	ASSERT(nodeView);
	media_node_id id = nodeView->ref->id();
	uint32 s = m_displacedWires.size();
		
	// find touching wires
	vector<Wire*> displace;

	findWiresByDestinationNode(id, displace);
	
	for(int n = 0; n < displace.size(); n++) {
		Wire* pWire = displace[n];
		if(m_displacedWires.find(pWire) == m_displacedWires.end())
			m_displacedWires.insert(pWire);
	}
		
	return m_displacedWires.size() - s;
}

uint32 MediaRoutingView::displaceOutputWiresFor(LiveNodeView* nodeView) {
	ASSERT(nodeView);
	media_node_id id = nodeView->ref->id();
	uint32 s = m_displacedWires.size();
		
	// find touching wires
	vector<Wire*> displace;

	findWiresBySourceNode(id, displace);
	
	for(int n = 0; n < displace.size(); n++) {
		Wire* pWire = displace[n];
		if(m_displacedWires.find(pWire) == m_displacedWires.end())
			m_displacedWires.insert(pWire);
	}
		
	return m_displacedWires.size() - s;
}

	
// reroutes all wires from m_displacedWires (which is then emptied)
void MediaRoutingView::rerouteDisplacedWires() {
//	ProfileBlock _b(g_prof, "rerouteDisplacedWires");
	
	for(wire_set::iterator it = m_displacedWires.begin();
		it != m_displacedWires.end(); it++) {
		routeWire(*it);
	}
	m_displacedWires.clear();
}

// finds all wires whose source is the given node
void MediaRoutingView::findWiresBySourceNode(
	media_node_id id, vector<Wire*>& wires) const {

 	// 11aug99: refers to the NodeManager wire map
	NodeRef* ref;
	status_t err = manager->getNodeRef(id, &ref);
	ASSERT(err == B_OK);
	
	vector<Connection> conSet;
	err = ref->getOutputConnections(conSet);
	ASSERT(err == B_OK);
	
	for(vector<Connection>::const_iterator it = conSet.begin();
		it != conSet.end(); ++it) {

		Wire* wire = findWire(*it);
		if(!wire) {
//			PRINT((
//				"* MediaRoutingView::findWiresBySourceNode():\n"
//				"  no wire for connection %ld\n",
//				(*it).id()));
			continue;
		}
		wires.push_back(wire);
	}
}

// find a wire whose destination is the given node
void MediaRoutingView::findWiresByDestinationNode(
	media_node_id id, vector<Wire*>& wires) const {

 	// 11aug99: refers to the NodeManager wire map
	NodeRef* ref;
	status_t err = manager->getNodeRef(id, &ref);
	ASSERT(err == B_OK);
	
	vector<Connection> conSet;
	err = ref->getInputConnections(conSet);
	ASSERT(err == B_OK);
	
	for(vector<Connection>::const_iterator it = conSet.begin();
		it != conSet.end(); ++it) {

		Wire* wire = findWire(*it);
		if(!wire) {
//			PRINT((
//				"* MediaRoutingView::findWiresByDestinationNode():\n"
//				"  no wire for connection %ld\n",
//				(*it).id()));
			continue;
		}
		wires.push_back(wire);
	}
}

// find wire matching the given connection: FAST
Wire* MediaRoutingView::findWire(
	const Connection&						connection) const {

	wire_map::const_iterator it = m_wireMap.find(connection.id());
	return (it == m_wireMap.end()) ? 0 : (*it).second;
}


// find wire for the given connection
Wire* MediaRoutingView::findWire(
	const media_source& source,
	const media_destination& destination) const {

	// 11aug99: uses NodeManager for match +++++UNTESTED+++++
	Connection con;
	status_t err = manager->findConnection(source, &con);
	if(err < B_OK) {

		err = manager->findConnection(destination, &con);
		if(err == B_OK) {
			// uh oh
			PRINT((
				"*** MediaRoutingView::findWire(): NodeManager integrity check failed;\n"
				"    Connection entry for destination but not source!\n"));
			ASSERT(!"boom");
		}
		
		// wire not found in either map
		return 0;
	}

	wire_map::const_iterator it = m_wireMap.find(con.id());
	if(it == m_wireMap.end()) {
		PRINT((
			"*** MediaRoutingView::findWire(): No wire for connection %ld\n",
			con.id()));
		return 0;
	}
	
	return (*it).second;
}

// find wire route for the connection described by the given Wire
// instance, and add segments to the wire.  (any pre-existing
// segments are cleared.)
// 19may99

// 21may99:
//     leaves existing wire segments untouched if possible; wire
//		 segments may added or removed from both the Wire object
//     and the grid.  New or removed segments are copied to the
//     m_touchedSegments set to ensure that they area they covered
//     is redrawn as efficiently as possible.


// from (now-defunct) Wire::addSegment():
// [20may99] adds the given segment to the given position in the wire
// (0..# of segments)
// If a matching segment already exists at that position, it's
// not touched; otherwise, it and any remaining segments are
// removed and the new segment is written.
// 
// 21may99: properly add and remove segments from grid
	
void MediaRoutingView::addWireSegmentAt(
	Wire* pWire, const WireSegment& segment, uint32 where) {
	if(where < pWire->m_segments.size()) {
		if(pWire->m_segments[where] == segment)
			return;
			
		// existing, and non-matching, segments exist at/after the given point;
		// remove them:
		
		for(uint32 n = where; n < pWire->m_segments.size(); n++)
			removeWireSegment(pWire->m_segments[n]);
		pWire->m_segments.erase(pWire->m_segments.begin() + where, pWire->m_segments.end());
	}
	
	// add the new segment (which is by definition going to the end of the list)
	pWire->m_segments.push_back(segment);
	addWireSegment(pWire->m_segments.back());
}


status_t MediaRoutingView::routeWire(Wire* pWire) {
//	ProfileBlock _b(g_prof, "routeWire");
//	PRINT(("MediaRoutingView::routeWire()\n"));
	
	ASSERT(pWire->isValid());

	// *** FIGURE SOURCE VERTEX ***
	
	// find source coordinates
	node_location_map::iterator itPos = m_nodeLocationMap.find(
		pWire->connection.sourceNode());
	if(itPos == m_nodeLocationMap.end()) {
		PRINT((
			"MediaRoutingView::routeWire(): source node (%ld) not found\n",
			pWire->connection.sourceNode()));
		return B_MEDIA_BAD_SOURCE;
	}
	column_row_pair sourceCoords = (*itPos).second;
	
	// fetch the source cell view
	LiveNodeView* pSourceCell = nodeViewAt(
		sourceCoords.first, sourceCoords.second);
	ASSERT(pSourceCell);
	
	// find the output jack
	int16 sourceRowOffset = -1;
	const vector<media_output>& sourceOutputs = pSourceCell->connectedOutputs();
	for(uint32 n = 0; n < sourceOutputs.size(); n++) {
		if(sourceOutputs[n].source == pWire->connection.source()) {
			sourceRowOffset = (int16)n;
			break;
		}
	}
	if(sourceRowOffset < 0) {
		PRINT((
			"MediaRoutingView::routeWire(): source node '%s' output not found\n",
			pSourceCell->ref->name()));
		return B_MEDIA_BAD_SOURCE;
	}
		
	// build a vertex
	WireVertex sourceV(
		sourceCoords.first, WireVertex::NODE_OUTPUT_JACK, 0,
		sourceCoords.second, WireVertex::NODE_JACK, sourceRowOffset);
		
	// *** FIGURE DESTINATION VERTEX ***
	
	// find dest coordinates
	itPos = m_nodeLocationMap.find(pWire->connection.destinationNode());
	if(itPos == m_nodeLocationMap.end()) {
		PRINT((
			"MediaRoutingView::routeWire(): dest node (%ld) not found\n",
			pWire->connection.destinationNode()));
		return B_MEDIA_BAD_DESTINATION;
	}
	column_row_pair destCoords = (*itPos).second;
	
	// fetch the source cell view
	LiveNodeView* pDestCell = nodeViewAt(destCoords.first, destCoords.second);
	ASSERT(pDestCell);
	
	// find the input jack
	int16 destRowOffset = -1;
	const vector<media_input>& destInputs = pDestCell->connectedInputs();
	for(uint32 n = 0; n < destInputs.size(); n++) {
		if(destInputs[n].destination == pWire->connection.destination()) {
			destRowOffset = (int16)n;
			break;
		}
	}
	if(destRowOffset < 0) {
		PRINT((
			"MediaRoutingView::routeWire(): dest node '%s' input not found\n",
			pDestCell->ref->name()));
		return B_MEDIA_BAD_DESTINATION;
	}
		
	// build a vertex
	WireVertex destV(
		destCoords.first, WireVertex::NODE_INPUT_JACK, 0,
		destCoords.second, WireVertex::NODE_JACK, destRowOffset);

	// * CONSTRUCT THE WIRE SEGMENTS
	//
	// 13may99: I'm making a few layout assumptions here.  jacks are
	//          currently spaced 4 vu (vertical units) apart, and node
	//          views are 6 vu apart.  That means three jacks for every
	//          two node rows.  Jacks are spaced so that the even ones
	//          are in the center of a node row, and the odd ones in
	//          the center of a gutter row.
	//
	//          This means that jack indices can be directly translated
	//          to grid rows:
	//
	//          gridRow = jackIndex + 1 + (nodeRow * 2)
	//
	// TO DO: +++++
	// - use nearest row gutter available rather than top/bottom
	//   * this involves scanning each slot in the gutter that may
	//     be potentially covered by a multi-row cell
	// - gutter wiring subcolumn support: group wires with common
	//   destinations
	//   * each gutter column keeps track of the groups of wires
	//     passing through it bound for a common destination node.
	//   * subcolumns should be allocated if:
	//     - wires bound for different destinations cross
	//     - (? any other reasons?)
	//   ? how should the column handle a subcolumn shortage?  do
	//     larger groups get priority?  (makes some sense -- this
	//     seems most likely to keep the clutter low.)
	
	uint32 sourceJackRow = sourceV.nodeRow*2 + sourceV.rowOffsetIndex + 1;
	uint32 destJackRow = destV.nodeRow*2 + destV.rowOffsetIndex + 1;

	// first & last grid rows lining up with the last row of the
	// source cell's connection points
// [14jun99] unused so far
//	uint32 sourceNodeTopRow = sourceV.nodeRow*2 + 1;
//	uint32 sourceNodeBottomRow = sourceV.nodeRow*2 + (pSourceCell->gridRowsNeeded()-1) + 1;

	// first & last grid rows lining up with connection points
	// on the destination cell
	uint32 destNodeTopRow = destV.nodeRow*2 + 1;
	uint32 destNodeBottomRow = destV.nodeRow*2 + (pDestCell->gridRowsNeeded()-1) + 1;

	// build wire segments between sourceV and destV.
	
	uint32 segment = 0;
	if(sourceJackRow == destJackRow &&
		gridRowSubsetClear(sourceJackRow,
			gutterColumnIndex(sourceV.nodeColumn, RIGHT_GUTTER),
			gutterColumnIndex(destV.nodeColumn, LEFT_GUTTER))) {

		// CASE ONE (straight shot)
		// 1) src jack/src jack row				->		dest jack/dest jack row
		
		addWireSegmentAt(pWire,
			WireSegment(pWire, sourceV, destV), segment++);
	}
	else if(destV.nodeColumn - sourceV.nodeColumn == 1) {

		// CASE TWO (crossing a single gutter column)
		// 1) src jack/src jack row				->		gutter column/src jack row

		WireVertex cur = sourceV;
		WireVertex next = sourceV;
		
		// +++++ subcolumn support goes here
		next.columnOffsetIndex = 0; 
		if(destJackRow > sourceJackRow) {
			next.columnOffsetType = WireVertex::RIGHT_GUTTER_COLUMN;
		} else {
			next.nodeColumn++;
			next.columnOffsetType = WireVertex::LEFT_GUTTER_COLUMN;
		}
		
		addWireSegmentAt(pWire,
			WireSegment(pWire, cur, next), segment++);
			
		// 2) gutter column/src jack row	->		gutter column/dest jack row

		cur = next;
		next.nodeRow = destV.nodeRow;
		next.rowOffsetIndex = destV.rowOffsetIndex;

		addWireSegmentAt(pWire,
			WireSegment(pWire, cur, next), segment++);
		
		// 3) gutter column/dest jack row	->		dest jack/dest jack row

		cur = next;

		addWireSegmentAt(pWire,
			WireSegment(pWire, cur, destV), segment++);		
	}
	else {	
//		PRINT((
//			"(.) full routing path:\n%s ->\n%s\n"
//			"    - sourceJackRow        = %ld\n"
//			"    - destJackRow          = %ld\n"
//			"    - sourceNodeTopRow     = %ld\n"
//			"    - sourceNodeBottomRow  = %ld\n"
//			"    - destNodeTopRow       = %ld\n"
//			"    - destNodeBottomRow    = %ld\n\n",
//			sourceV.toString().String(),
//			destV.toString().String(),
//			sourceJackRow,
//			destJackRow,
//			sourceNodeTopRow,
//			sourceNodeBottomRow,
//			destNodeTopRow,
//			destNodeBottomRow));
			
		// CASE THREE
		// 1) src jack/src jack row				->		src column/src jack row

		WireVertex cur = sourceV;
		WireVertex next = sourceV;
		next.columnOffsetType = WireVertex::RIGHT_GUTTER_COLUMN;
		next.columnOffsetIndex = 0; // +++++ subcolumn support goes here
		
		addWireSegmentAt(pWire,
			WireSegment(pWire, cur, next), segment++);
			
		// 2) src column/src jack row			->		src column/gutter row
		
		// find the closest row through which the wire can be routed
		// (node rows are okay, if not blocked)

		cur = next;
		
		// *** (slightly) better row-finding
		//     24may99
		
		uint16 crossRow;
		if(sourceJackRow <= destNodeBottomRow) {
			for(crossRow = destNodeTopRow; crossRow > 0; crossRow--) {
				if(gridRowSubsetClear(crossRow,
					gutterColumnIndex(sourceV.nodeColumn, RIGHT_GUTTER),
					gutterColumnIndex(destV.nodeColumn, LEFT_GUTTER))) {
					break;
				}
			}
		}
		else {
			for(crossRow = destNodeBottomRow; crossRow < rows(); crossRow++) {
				if(gridRowSubsetClear(crossRow,
					gutterColumnIndex(sourceV.nodeColumn, RIGHT_GUTTER),
					gutterColumnIndex(destV.nodeColumn, LEFT_GUTTER))) {
					break;
				}
			}
		}				
		
//		// +++++ look for a shorter path
//		
//		if(sourceV.nodeColumn < destV.nodeColumn) {
//			// wiring left->right: route through top gutter
//			next.nodeRow = 0;
//			next.rowOffsetType = WireVertex::TOP_GUTTER_ROW;
//			next.rowOffsetIndex = 0;
//		} else {
//			// wiring right->left: route through bottom gutter
//			// +++++ ick
//			next.nodeRow = nodeRows()-1;
//			next.rowOffsetType = WireVertex::BOTTOM_GUTTER_ROW;
//			next.rowOffsetIndex = 0;
//		}

		if(!(crossRow % 2)) {
			// actually a gutter row
			next.nodeRow = crossRow / 2;
			next.rowOffsetType =
				(crossRow == rows()-1) ?
					WireVertex::BOTTOM_GUTTER_ROW :
					WireVertex::TOP_GUTTER_ROW;
			next.rowOffsetIndex = 0;
		} else {
			next.nodeRow = destV.nodeRow;
			next.rowOffsetType = WireVertex::NODE_JACK;
			next.rowOffsetIndex = crossRow-destNodeTopRow;
		}
		
		addWireSegmentAt(pWire,
			WireSegment(pWire, cur, next), segment++);
			
		// 3) source column/gutter row		->		dest column/gutter row
		cur = next;
		
		next.nodeColumn = destV.nodeColumn;
		next.columnOffsetType = WireVertex::LEFT_GUTTER_COLUMN;
		next.columnOffsetIndex = 0; // +++++ subcolumn support goes here
		
		addWireSegmentAt(pWire,
			WireSegment(pWire, cur, next), segment++);		

		// 4) dest column/gutter row			->		dest column/dest jack row
		cur = next;
		
		next.nodeRow = destV.nodeRow;
		next.rowOffsetType = destV.rowOffsetType;
		next.rowOffsetIndex = destV.rowOffsetIndex;

		addWireSegmentAt(pWire,
			WireSegment(pWire, cur, next), segment++);		
		
		// 5) dest column/dest jack row		->		dest jack/dest jack row
		cur = next;
		
		addWireSegmentAt(pWire,
			WireSegment(pWire, cur, destV), segment++);	
	}

//	PRINT(("*DONE: MediaRoutingView::routeWire()\n"));
	return B_OK;
}

// redraw a wire (immediately or later, depending on the
// value of m_bDeferSegmentRedraw)
void MediaRoutingView::touchWire(Wire* pWire) {
	for(vector<WireSegment>::iterator it = pWire->m_segments.begin();
		it != pWire->m_segments.end(); it++) {
		touchSegment(*it);
	}
}

// redraw a segment (immediately or later, depending on the
// value of m_bDeferSegmentRedraw)
void MediaRoutingView::touchSegment(const WireSegment& segment) {

	if(m_bDeferSegmentRedraw) {
		// [e.moon 17oct99] duplicate segment
		m_touchedSegments.insert(new WireSegment(segment));
	} else {
		invalidateContainerRect(segmentToRect(segment));
	}
}

// redraw all deferred segments
void MediaRoutingView::redrawTouchedSegments() {
//	ProfileBlock _b(g_prof, "redrawTouchedSegments");
	for(segment_set::iterator it = m_touchedSegments.begin();
		it != m_touchedSegments.end(); it++) {
		invalidateContainerRect(segmentToRect(*(*it)));
		delete *it;
	}
	m_touchedSegments.clear();
}

// figure bounds rectangle for the given wire segment
BRect MediaRoutingView::segmentToRect(const WireSegment& segment) const {

//	ProfileBlock _b(g_prof, "segmentToRect");
	
	BPoint fromPoint = vertexToPoint(segment.from);
	BPoint toPoint = vertexToPoint(segment.to);

	float offset = WIRE_THICKNESS - 1;

	if(fromPoint.x == toPoint.x)
		// vertical
		if(fromPoint.y < toPoint.y)
			toPoint.x += offset;
		else
			fromPoint.x += offset;
	else {
		if(fromPoint.y != toPoint.y) {
			PRINT((
				"MediaRoutingView::segmentToRect(): not sane:\n"
				"(%.1f,%.1f)->(%.1f,%.1f)\n"
				"wire (%p) from '%s':%ld to '%s':%ld\n",
				fromPoint.x, fromPoint.y, toPoint.x, toPoint.y,
				segment.pWire,
				segment.pWire->connection.outputName(),
				segment.pWire->connection.sourceNode(),
				segment.pWire->connection.inputName(),
				segment.pWire->connection.destinationNode()));
			ASSERT(!"BANG");
		}
		
		// horizontal
		// +++++ ? 24may99
		if(fromPoint.x < toPoint.x)
			toPoint.y += offset;
		else
			fromPoint.y += offset;
	}

	if(fromPoint.x <= toPoint.x && fromPoint.y <= toPoint.y)
		return BRect(fromPoint, toPoint);
	else
		return BRect(toPoint, fromPoint);
}


//// get BRegion for wire
//void MediaRoutingView::addWireToRegion(const Wire* pWire, BRegion* pRegion) {
//
//	for(list<WireSegment>::const_iterator it = pWire->m_segments.begin();
//		it != pWire->m_segments.end(); it++)
//		pRegion->Include(segmentToRect(*it));
//}

// get BPoint for wire vertex
BPoint MediaRoutingView::vertexToPoint(const WireVertex& v) const {
//	ProfileBlock _b(g_prof, "vertexToPoint");

	// use cached point:
	if(v.point != WireVertex::s_noPointCached)
		return v.point;

//	ProfileBlock _b2(g_prof, "vertexToPoint:calc");

	// x coordinate
	// +++++ wire-column support
	
	v.point.x = (NODE_COLUMN_WIDTH+GUTTER_COLUMN_WIDTH) * v.nodeColumn;
	
	switch(v.columnOffsetType) {
		case WireVertex::RIGHT_GUTTER_COLUMN:
			v.point.x += (NODE_COLUMN_WIDTH + GUTTER_COLUMN_WIDTH + GUTTER_COLUMN_IN);
			break;
			
		case WireVertex::LEFT_GUTTER_COLUMN:
			v.point.x += (GUTTER_COLUMN_OUT);
			break;
			
		case WireVertex::NODE_INPUT_JACK:
			v.point.x += (GUTTER_COLUMN_WIDTH + JACK_WIDTH/2);
			break;
			
		case WireVertex::NODE_OUTPUT_JACK:
			v.point.x += (NODE_COLUMN_WIDTH + GUTTER_COLUMN_WIDTH - (JACK_WIDTH/2 + 1));
			break;
	}
	
	// y coordinate
	
	v.point.y = (NODE_ROW_HEIGHT+GUTTER_ROW_HEIGHT) * v.nodeRow;
	
	switch(v.rowOffsetType) {
		case WireVertex::BOTTOM_GUTTER_ROW:
			v.point.y += (NODE_ROW_HEIGHT + GUTTER_ROW_HEIGHT/2);
			break;
			
		case WireVertex::TOP_GUTTER_ROW:
			v.point.y += (GUTTER_ROW_HEIGHT/2);
			break;
			
		case WireVertex::NODE_JACK:
			v.point.y += (GUTTER_ROW_HEIGHT + v.rowOffsetIndex*V_UNIT*4 + V_UNIT*2 + JACK_HEIGHT/2);
			break;
	}
	
//	PRINT((
//		"vertexToPoint():\n%s\n(%.1f, %.1f)\n",
//		v.toString().String(),
//		v.point.x, v.point.y));
	
	v.point.y--;
	return v.point;
}


// returns false if the given subset of a grid row is blocked
// by a cell, or true if it's free.  (checks from 'fromColumn'
// up to and including 'toColumn')
bool MediaRoutingView::gridRowSubsetClear(
	uint32 row, uint32 fromColumn, uint32 toColumn) {
	if(toColumn < fromColumn) {
		uint32 t = fromColumn; fromColumn = toColumn; toColumn = t;
	}
	ASSERT(row < rows());
	ASSERT(toColumn < columns());
	
	BView* pCell;
	uint32 oColumn, oRow, cSpan, rSpan;
	for(uint32 column = fromColumn; column <= toColumn; column++) {
		if(getCell(column, row, &pCell, &oColumn, &oRow, &cSpan, &rSpan))
			// found a blocking cell
			return false;
	}
	
	// [14jun99] was missing return
	return true;
}


// END -- MediaRoutingView.cpp --
