// LiveNodeView.cpp
// e.moon 6may99

#include "LiveNodeView.h"

#include "LayoutDefs.h"
#include "MediaRoutingView.h"
#include "MediaRoutingContainerView.h"

#include "RouteWindow.h"
#include "TipManager.h"

#include "NodeRef.h"

#include <Entry.h>
#include <MediaRoster.h>
#include <MenuItem.h>
#include <cstring>

#include <Region.h>

#include "debug_tools.h"

__USE_CORTEX_NAMESPACE


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

//const BPoint LiveNodeView::s_jackTipOffset(2.0, -4.0);

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

LiveNodeView::~LiveNodeView() {
//	PRINT((
//		"~LiveNodeView[%s]: %p\n", ref->name(), ref));
//		
//	// +++++ 11aug99: in what circumstances would the ref outlive the
//	//                view?
//	if(ref)
//		ref->removeListener(this);
}

LiveNodeView::LiveNodeView(
	MediaRoutingView* 					pRoutingView,
	NodeRef*										_ref) :
	
	MouseTrackingSourceView(
		BRect(0,0,0,0),
		"liveNode",
		B_FOLLOW_LEFT|B_FOLLOW_TOP,
		B_WILL_DRAW|B_FRAME_EVENTS),

	ref(_ref),
	m_routingView(pRoutingView),
	m_pMenu(0),
	m_bSelected(false),
	m_dragOp(DRAG_NONE),
	m_lastClickTime(0LL) {

	ASSERT(m_routingView);
	ASSERT(ref);

	// build connection-point sets
	if(ref->kind() & B_BUFFER_CONSUMER)
		_scanInputs();
	if(ref->kind() & B_BUFFER_PRODUCER)
		_scanOutputs();
		
	// handle background painting manually
	SetViewColor(B_TRANSPARENT_COLOR);
}

// ---------------------------------------------------------------- //
// hooks
// ---------------------------------------------------------------- //

// extend to add items to the pop-up menu
void LiveNodeView::populateMenu() {
/*
	[8jun99] no more transport window
	
	BMessage* pMsg;
	
	pMsg = new BMessage(MediaRoutingView::M_SHOW_TRANSPORT);
	pMsg->AddInt32("nodeID", m_node.node);
	m_pMenu->AddItem(new BMenuItem("Transport...", pMsg));
*/
}

// draw the cell onto the given view
void LiveNodeView::drawCell(BView* v, BRect updateRect,
	bool bDrawBackground) {

	BRect b = Bounds();
	BRegion backRegion;
	backRegion.Include(updateRect);
	
	// figure cell bounds
	BRect cellBounds(_cellBounds(b));
	
	// figure cell contour
	BRegion cellRegion;
	cellRegion.Include(cellBounds);
	
	// build jack shapes to cut out of cell
	BRect jackL, jackR;
	BPoint vInc;	
	_getJackPositions(jackL, jackR, vInc);
	
	// keep track of the areas to color as free inputs/outputs
	BRegion freeJackRegion;

	// left == inputs	
	for(int n = 0;
		n < m_connectedInputs.size();
		n++) {
		cellRegion.Exclude(jackL);
		jackL.OffsetBy(vInc);
	}
	// figure free jack areas (drawn w/ different background,
	// but only in the notch cut out of the cell)
	jackL.left = cellBounds.left;
	for(int n = 0; n < m_freeInputs.size(); n++) {
		cellRegion.Exclude(jackL);
		freeJackRegion.Include(jackL);
		jackL.OffsetBy(vInc);
	}		

	// right == outputs
	for(int n = 0;
		n < m_connectedOutputs.size();
		n++) {
		cellRegion.Exclude(jackR);
		jackR.OffsetBy(vInc);
	}
	jackR.right = cellBounds.right;
	for(int n = 0; n < m_freeOutputs.size(); n++) {
		cellRegion.Exclude(jackR);
		freeJackRegion.Include(jackR);
		jackR.OffsetBy(vInc);
	}		
	
	// stamp the cell out of the background...
	backRegion.Exclude(&cellRegion);
	
	// draw the background
	v->SetHighColor(m_backColor);
	v->FillRegion(&backRegion);
	
	// draw the cell
	v->SetHighColor(m_cellColor);
	v->FillRegion(&cellRegion);
	
	// draw the free jacks
	v->SetHighColor(m_freeJackColor);
	v->FillRegion(&freeJackRegion);
	
	// draw a simple frame
	v->SetHighColor(m_cellColorD2);
	v->StrokeRect(cellBounds);
	
	// draw the node name
	v->SetFont(&m_font);
	v->SetHighColor(m_textColor);
	v->SetLowColor(m_cellColor);
	
	// (center horizontally, at bottom of cell)
	BPoint namePos(
//		(b.Width() -
//			(m_font.StringWidth(m_displayName.String()) + NAME_PAD_X*2)) / 2,
		NAME_PAD_X + JACK_WIDTH,
		b.bottom - (m_fh.descent + 2));

	v->DrawString(m_displayName.String(), namePos);

	// add a slight 3D highlight
	rgb_color delta = {32, 32, 32, 255};

	cellBounds.InsetBy(1.0, 1.0);
	v->SetDrawingMode(B_OP_ADD);
	v->SetHighColor(delta);
	v->StrokeLine(cellBounds.LeftTop(), cellBounds.RightTop() - BPoint(1.0, 0.0));
	v->StrokeLine(cellBounds.LeftTop(), cellBounds.LeftBottom() - BPoint(0.0, 1.0));
	v->SetDrawingMode(B_OP_SUBTRACT);
	v->StrokeLine(cellBounds.LeftBottom() + BPoint(1.0, 0.0), cellBounds.RightBottom());
	v->StrokeLine(cellBounds.RightTop() + BPoint(0.0, 1.0), cellBounds.RightBottom());
	v->SetDrawingMode(B_OP_COPY);
	
	// ++++++ 23may99 selection hack
	if(isSelected()) {
		rgb_color select = {48, 48, 24, 255};
		v->SetDrawingMode(B_OP_ADD);
		v->SetHighColor(select);
		/*
		// select above t.icon
		if(updateRect.top < m_transportIconFrame.top)
			v->FillRect(BRect(updateRect.left, updateRect.top,
				updateRect.right, m_transportIconFrame.top-1));
		// select left of t.icon
		if(updateRect.left < m_transportIconFrame.left)
			v->FillRect(BRect(updateRect.left, m_transportIconFrame.top,
				m_transportIconFrame.left-1, m_transportIconFrame.bottom));
		// select below t.icon
		if(updateRect.bottom > m_transportIconFrame.bottom)
			v->FillRect(BRect(updateRect.left, m_transportIconFrame.bottom+1,
				updateRect.right, updateRect.bottom));
		// select right of t.icon
		if(updateRect.right > m_transportIconFrame.right)
			v->FillRect(BRect(m_transportIconFrame.right+1, m_transportIconFrame.top,
				updateRect.right, m_transportIconFrame.bottom));
		*/
		v->FillRect(updateRect);
		v->SetDrawingMode(B_OP_COPY);
	}

	// 12aug99: moved from (now-defunct) ChildNodeView::drawCell()	
	// released? gray it out:
	if(ref->isReleased()) {
		v->SetHighColor(64, 64, 64, 255);
		v->SetDrawingMode(B_OP_SUBTRACT);
		v->FillRect(updateRect);
	}
}

// ---------------------------------------------------------------- //
// routing-grid operations
// ---------------------------------------------------------------- //

// fetches the bounding rectangle of the indicated connection's
// icon; returns B_OK on success, or B_BAD_INDEX if the connection
// doesn't exist
status_t LiveNodeView::getJackFrame(
	WireVertex::x_offset_t			jackType,
	uint16											index,
	BRect&											oFrame) {
	
	BRect jackL, jackR;
	BPoint vInc;
	_getJackPositions(jackL, jackR, vInc);
	
	switch(jackType) {
		case WireVertex::NODE_INPUT_JACK:
			if(index >= m_freeInputs.size() + m_connectedInputs.size())
				return B_BAD_INDEX;
			oFrame = jackL;
			oFrame.OffsetBy(0.0, vInc.y * index);
			break;
		
		case WireVertex::NODE_OUTPUT_JACK:
			if(index >= m_freeOutputs.size() + m_connectedOutputs.size())
				return B_BAD_INDEX;
			oFrame = jackR;
			oFrame.OffsetBy(0.0, vInc.y * index);
			break;
		
		default:
			return B_ERROR; // +++++ care to be a little less descriptive? :P
	}
	
	return B_OK;
}

void LiveNodeView::setFont(const BFont& font) {	

	MediaRoutingContainerView* pParent = dynamic_cast<MediaRoutingContainerView*>(Parent());

	// keep the old font height for comparison...
	m_font.GetHeight(&m_fh);
	float height = m_fh.ascent+m_fh.descent+m_fh.leading;
	
	// ...with the new
	m_font = font;
	m_font.GetHeight(&m_fh);
	float newHeight = m_fh.ascent+m_fh.descent+m_fh.leading;
	if(newHeight > height)
		height = newHeight;

	// figure name string (which may need to be truncated; if so,
	// generate a pop-up tip with the whole text)		
	_prepareName();
	if(pParent)
		_updateTips(TipManager::Instance());
	
	// redraw the name area
	BRect b = Bounds();
	b.top = b.bottom - height;
	Invalidate(b);
}

// notify view that input/output connections may have changed
void LiveNodeView::updateInputs() {
	if(!(ref->kind() & B_BUFFER_CONSUMER))
		return;
	
	_scanInputs();
	Invalidate();
}

void LiveNodeView::updateOutputs() {
	if(!(ref->kind() & B_BUFFER_PRODUCER))
		return;

	_scanOutputs();
	Invalidate();
}

// [e.moon 29sep99]
// notify view that the displayed name may have been changed
// (nodes can't be renamed, but the cell may display the name of
//  a file being played, for example.)
void LiveNodeView::updateName() {

	_prepareName();
	Invalidate();
}
	

// required-size calculation
// - figure the number of grid rows needed to display the cell
//   frame & all connection points
// - the value returned must be odd (the cell can't butt against a grid row)
uint32 LiveNodeView::gridRowsNeeded() const {
	uint32 maxJackCount = max_c(
		m_freeInputs.size() + m_connectedInputs.size(),
		m_freeOutputs.size() + m_connectedOutputs.size());
	
	// make odd
	if(!(maxJackCount%2)) maxJackCount++;
	
	// connection-point layout
	// 24may99
	//
	// connection icon height == 2 rows
	// 1 row needed above & below each connection icon
	
	return max_c(1, maxJackCount);
}

// create new Vertex if the given point lies over a
// viable connection jack.
// +++++ accept media_format from the origin jack (the other side of
//       the connection)
bool LiveNodeView::matchJackVertex(BPoint point, WireVertex& outVertex) {
	BRect inputJackR, outputJackR;
	BPoint jackOffset;
	_getJackPositions(inputJackR, outputJackR, jackOffset);
	
//	PRINT((
//		"LiveNodeView::matchJackVertex()\n"));
	
	if(point.x <= inputJackR.right) {
		// look for a matching input connection point
		uint32 inputs = m_connectedInputs.size() + m_freeInputs.size();
		int16 n = 0;
		while(inputs--) {
			if(inputJackR.Contains(point)) {
			
				// found a match
				outVertex.columnOffsetType = WireVertex::NODE_INPUT_JACK;
				outVertex.columnOffsetIndex = 0;
				outVertex.rowOffsetType = WireVertex::NODE_JACK;
				outVertex.rowOffsetIndex = n;				
				
				// * exit
				return true;
			}
			inputJackR.OffsetBy(jackOffset);
			++n;
		}
	} else if(point.x >= outputJackR.left) {
		// look for a matching output connection point
		uint32 outputs = m_connectedOutputs.size() + m_freeOutputs.size();
		int16 n = 0;
		while(outputs--) {
			if(outputJackR.Contains(point)) {

				// found a match
				outVertex.columnOffsetType = WireVertex::NODE_OUTPUT_JACK;
				outVertex.columnOffsetIndex = 0;
				outVertex.rowOffsetType = WireVertex::NODE_JACK;
				outVertex.rowOffsetIndex = n;				
				
				// * exit
				return true;
			}
			outputJackR.OffsetBy(jackOffset);
			++n;
		}
	}
	
	// * no match
	return false;
}

// returns media_input corresponding to the given vertex, or
// bool if none exists
bool LiveNodeView::findMediaInput(
	const WireVertex& vertex, media_input& outInput, bool& isConnected) {

	// sanity checks
	if(vertex.columnOffsetType != WireVertex::NODE_INPUT_JACK ||
		vertex.rowOffsetType != WireVertex::NODE_JACK) {
		PRINT((
			"LiveNodeView::findMediaInput(): invalid vertex:\n%s\n",
			vertex.toString().String()));
		return false;
	}
	
	// in range?
	uint32 connected = m_connectedInputs.size();
	uint32 free = m_freeInputs.size();
	uint32 rowOffset = vertex.rowOffsetIndex;
	if(rowOffset >= connected+free)
		return false;
	
	// found
	if(rowOffset < connected) {
		isConnected = true;
		outInput = m_connectedInputs[rowOffset];
	} else {
		isConnected = false;
		outInput = m_freeInputs[rowOffset-connected];
	}
	return true;

}

// returns media_output corresponding to the given vertex, or
// bool if none exists
bool LiveNodeView::findMediaOutput(
	const WireVertex& vertex, media_output& outOutput, bool& isConnected) {

	// sanity checks
	if(vertex.columnOffsetType != WireVertex::NODE_OUTPUT_JACK ||
		vertex.rowOffsetType != WireVertex::NODE_JACK) {
		PRINT((
			"LiveNodeView::findMediaOutput(): invalid vertex:\n%s\n",
			vertex.toString().String()));
		return false;
	}
	
	// in range?
	uint32 connected = m_connectedOutputs.size();
	uint32 free = m_freeOutputs.size();
	uint32 rowOffset = vertex.rowOffsetIndex;
	if(rowOffset >= connected+free)
		return false;
	
	// found
	if(rowOffset < connected) {
		isConnected = true;
		outOutput = m_connectedOutputs[rowOffset];
	} else {
		isConnected = false;
		outOutput = m_freeOutputs[rowOffset-connected];
	}
	return true;
}

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

void LiveNodeView::AttachedToWindow() {
	_inherited::AttachedToWindow();

	// force initial bounds check
	// [8jun99] e.moon
	FrameResized(Bounds().Width(), Bounds().Height());
	
	// set up pop-up tips for connection jacks
	MediaRoutingContainerView* pParent = dynamic_cast<MediaRoutingContainerView*>(Parent());
	ASSERT(pParent);
	
	// prepare to draw
	_prepareColors();
	_prepareName();

	_updateTips(TipManager::Instance());			

	// build pop-up menu
	m_pMenu = new BPopUpMenu("contextMenu", false, false);
	populateMenu();

	// init menu to target routing view
	// echk.  the cell view really shouldn't know this.
	// 7jun99 e.moon	
	m_pMenu->SetTargetForItems(m_routingView);
}

void LiveNodeView::DetachedFromWindow() {
	_inherited::DetachedFromWindow();
	
	MediaRoutingContainerView* pParent = dynamic_cast<MediaRoutingContainerView*>(Parent());
	ASSERT(pParent);
	
	// remove all tips associated with this view
	// [e.moon 17oct99] now tolerates a missing TipManager
	TipManager::Instance()->removeAll(this);
}	


// draw the cell
void LiveNodeView::Draw(BRect updateRect) {

	drawCell(this, updateRect, true);
}

// when connection info for the node changes, the routing
// grid will first ask the node to scan its inputs and/or
// outputs, then call GetPreferredSize() to determine whether
// the node will be taking up more (or fewer) grid slots.
// width is ignored; it's okay to return 0.

void LiveNodeView::GetPreferredSize(float* poWidth, float* poHeight) {
	// 24may99: for connection points, each row is the average of
	// (node-row-height, gutter-row-height)
	*poWidth = 0;
	*poHeight = gridRowsNeeded() * ((NODE_ROW_HEIGHT+GUTTER_ROW_HEIGHT)/2);
}

void LiveNodeView::MouseDown(BPoint point) {
//	PRINT((
//		"LiveNodeView::MouseDown(%.1f,%.1f)\n", point.x, point.y));

	ASSERT(m_dragOp == DRAG_NONE);
	
	// right-button launches context menu
	// 7jun99
	uint32 buttons;
	GetMouse(&point, &buttons, false);
	if(buttons & B_SECONDARY_MOUSE_BUTTON) {
		m_pMenu->Go(ConvertToScreen(point), true, false, true);
		return;
	}

	// extracted jack-vertex tests to 'matchJackVertex()'
	// 4jun99

	if(matchJackVertex(point, m_dragVertex)) {
		// connected vertex?
		media_input in;
		media_output out;
		bool isConnected;
		if(!(
			(m_dragVertex.columnOffsetType == WireVertex::NODE_INPUT_JACK) ?
				findMediaInput(m_dragVertex, in, isConnected) :
				findMediaOutput(m_dragVertex, out, isConnected))) {
			// no media input/output found!
			PRINT((
				"LiveNodeView::MouseDown(): expected connection is missing:\n%s\n",
				m_dragVertex.toString().String()));
			ASSERT(!"LiveNodeView::MouseDown(): out of date or broken");
		}

		// connected-wire gesture support, wire-selection, etc
		// go here! +++++

		if(isConnected) {
			// wire already exists; select it

			m_routingView->deselectAll();
			Wire* pWire = 0;
			// +++++ slow search for wire
			if(m_dragVertex.columnOffsetType == WireVertex::NODE_INPUT_JACK)
				pWire = m_routingView->findWire(in.source, in.destination);
			else
				pWire = m_routingView->findWire(out.source, out.destination);

//			ASSERT(pWire); crashes on 'zombie connections' [16sep99
			if(pWire)
				m_routingView->selectWire(pWire);
			
			// bail out (don't initiate mouse tracking!)
			return;
			
		} else
			m_dragOp = DRAG_WIRE;
						
	} else {
		m_dragOp = DRAG_CELL;
	}
	

	// hand off to generic mouse-tracking implementation
	_inherited::MouseDown(point);
	
	// double click?
	bigtime_t doubleClickInterval;
	if(get_click_speed(&doubleClickInterval) < B_OK) {
		PRINT((
			"* LiveNodeView::MouseDown():\n"
			"  get_click_speed() failed."));
		return;
	}
	
	bigtime_t now = system_time();
	if(now - m_lastClickTime < doubleClickInterval)
		_handleDoubleClick();
	else
		m_lastClickTime = now;
}

void LiveNodeView::MouseMoved(BPoint point, uint32 transit,
	const BMessage* pMsg) {

//	PRINT((
//		"LiveNodeView::MouseMoved()\n"));

	// hand off to generic mouse-tracking implementation
	_inherited::MouseMoved(point, transit, pMsg);
}

void LiveNodeView::MouseUp(BPoint point) {

	// hand off to generic mouse-tracking implementation
	_inherited::MouseUp(point);

	// mark drag operation as ended
	m_dragOp = DRAG_NONE;
}

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

void LiveNodeView::MessageReceived(
	BMessage*										msg) {

	switch(msg->what) {
//		case NodeRef::M_RELEASED:
//			// +++++ delete me!
//
//			// clear ref pointer; that marks this view officially defunct
//			const_cast<NodeRef*&>(ref) = 0;			
//
//			// M_DELETED must be responded to
//			msg->SendReply('Okay');
//			break;
	
		case NodeRef::M_GROUP_CHANGED:
			// +++++
			break;
			
		case NodeRef::M_INPUTS_CHANGED:
			// +++++
			break;
			
		case NodeRef::M_OUTPUTS_CHANGED:
			// +++++
			break;
			
		default:
			_inherited::MessageReceived(msg);
			break;
	}	
}

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

void LiveNodeView::_scanInputs() {
//	PRINT(( "LiveNodeView('%s')::_scanInputs()\n",
//		ref->name()));
//	
	ASSERT(ref->kind() & B_BUFFER_CONSUMER);

	// 11aug99: hand off to NodeRef
	m_freeInputs.clear();
	ref->getFreeInputs(m_freeInputs);
	
	m_connectedInputs.clear();
	ref->getConnectedInputs(m_connectedInputs);
	
//	BMediaRoster* r = BMediaRoster::Roster();
//
//	status_t err;
//	int32 count;
//	int32 bufferInc = 16;
//	int32 inputBufferSize = 16;
//	media_input* pInputBuffer = new media_input[inputBufferSize];
//
//	// find free inputs
//	m_freeInputs.clear();
//	while(true) {
//		err = r->GetFreeInputsFor(
//			m_node, pInputBuffer, inputBufferSize, &count);
//		if(err < B_OK)
//			throw runtime_error("LiveNodeView::scanInputs(): GetFreeInputsFor() failed.");
//
//		if(count == inputBufferSize) {
//			// buffer too small (yikes!)
//			inputBufferSize += bufferInc;
//			delete [] pInputBuffer;
//			pInputBuffer = new media_input[inputBufferSize];
//			continue;
//		}
//			
//		// got all the inputs; copy into vector
//		if(count) {
//			m_freeInputs.assign(pInputBuffer, pInputBuffer + count);
//			// do a little sanity check
//			// [14may99]
//			for(int n = 0; n < count; n++) {
//				if(m_freeInputs[n].node != m_node) {
//					PRINT(( "* bad node member for input '%s' of '%s' fixed\n",
//						m_freeInputs[n].name,
//						m_nodeInfo.name));
//					m_freeInputs[n].node = m_node;
//				}
//			}
//		}
//
//		break;
//	}
//	
//	// find connected inputs
//	m_connectedInputs.clear();
//	while(true) {
//		err = r->GetConnectedInputsFor(
//			m_node, pInputBuffer, inputBufferSize, &count);
//		if(err < B_OK)
//			throw runtime_error("LiveNodeView::scanInputs(): GetConnectedInputsFor() failed.");
//
//		if(count == inputBufferSize) {
//			// buffer too small (yikes!)
//			inputBufferSize += bufferInc;
//			delete [] pInputBuffer;
//			pInputBuffer = new media_input[inputBufferSize];
//			continue;
//		}
//			
//		// got all the inputs; copy into vector
//		if(count) {
//			m_connectedInputs.assign(pInputBuffer, pInputBuffer + count);
//			// do a little sanity check
//			// [14may99]
//			for(int n = 0; n < count; n++) {
//				if(m_connectedInputs[n].node != m_node) {
//					PRINT(( "* bad node member for input '%s' of '%s' fixed\n",
//						m_connectedInputs[n].name,
//						m_nodeInfo.name));
//					m_connectedInputs[n].node = m_node;
//				}
//			}
//		}
//
//		break;
//	}
//
//	delete [] pInputBuffer;

	// update pop-up connection tips if possible	
	MediaRoutingContainerView* pParent = dynamic_cast<MediaRoutingContainerView*>(Parent());
	if(pParent)
		_updateTips(TipManager::Instance());
}

void LiveNodeView::_scanOutputs() {
//	PRINT(( "LiveNodeView('%s')::_scanOutputs()\n",
//		ref->name()));
	ASSERT(ref->kind() & B_BUFFER_PRODUCER);

	// 11aug99: hand off to NodeRef
	m_freeOutputs.clear();
	ref->getFreeOutputs(m_freeOutputs);
	
	m_connectedOutputs.clear();
	ref->getConnectedOutputs(m_connectedOutputs);

//	BMediaRoster* r = BMediaRoster::Roster();
//
//	status_t err;
//	int32 count;
//	int32 bufferInc = 16;
//	int32 outputBufferSize = 16;
//	media_output* pOutputBuffer = new media_output[outputBufferSize];
//	
//	// find free outputs
//	m_freeOutputs.clear();
//
//	while(true) {
//		err = r->GetFreeOutputsFor(
//			m_node, pOutputBuffer, outputBufferSize, &count);
//		if(err < B_OK)
//			throw runtime_error("LiveNodeView::scanOutputs(): GetFreeOutputsFor() failed.");
//		if(count == outputBufferSize) {
//			// buffer too small (yikes!)
//			outputBufferSize += bufferInc;
//			delete [] pOutputBuffer;
//			pOutputBuffer = new media_output[outputBufferSize];
//			continue;
//		}
//			
//		// got all the outputs; copy into vector
//		if(count) {
//			m_freeOutputs.assign(pOutputBuffer, pOutputBuffer + count);
//			// do a little sanity check
//			// [14may99]
//			for(int n = 0; n < count; n++) {
//				if(m_freeOutputs[n].node != m_node) {
//					PRINT(( "* bad node member for output '%s' of '%s' fixed\n",
//						m_freeOutputs[n].name,
//						m_nodeInfo.name));
//					m_freeOutputs[n].node = m_node;
//				}
//			}
//		}
//
//		break;
//	}
//
//	// get connected outputs
//	m_connectedOutputs.clear();
//
//	while(true) {
//		err = r->GetConnectedOutputsFor(
//			m_node, pOutputBuffer, outputBufferSize, &count);
//		if(err < B_OK)
//			throw runtime_error("LiveNodeView::scanOutputs(): GetConnectedOutputsFor() failed.");
//		if(count == outputBufferSize) {
//			// buffer too small (yikes!)
//			outputBufferSize += bufferInc;
//			delete [] pOutputBuffer;
//			pOutputBuffer = new media_output[outputBufferSize];
//			continue;
//		}
//			
//		// got all the outputs; copy into vector
//		if(count) {
//			m_connectedOutputs.assign(pOutputBuffer, pOutputBuffer + count);
//			// do a little sanity check
//			// [14may99]
//			for(int n = 0; n < count; n++) {
//				if(m_connectedOutputs[n].node != m_node) {
//					PRINT(( "* bad node member for output '%s' of '%s' fixed\n",
//						m_connectedOutputs[n].name,
//						m_nodeInfo.name));
//					m_connectedOutputs[n].node = m_node;
//				}
//			}
//		}
//
//		break;
//	}
//	
//	delete [] pOutputBuffer;

	// update pop-up connection tips if possible	
	MediaRoutingContainerView* pParent = dynamic_cast<MediaRoutingContainerView*>(Parent());
	if(pParent)
		_updateTips(TipManager::Instance());
}

// +++++ launch node inspector
//+++++ finish me
void LiveNodeView::_handleDoubleClick() {
////	PRINT(( "LiveNodeView('%s')::_handleDoubleClick()\n",
////		m_nodeInfo.name));
////
//
//	if(m_controlPanel.IsValid()) {
//		// already got one
//		// +++++ bring to front
//		return;
//	}
//
//	BMediaRoster* r = BMediaRoster::Roster();
//	status_t err = r->StartControlPanel(ref->node(), &m_controlPanel);
//	if(err < B_OK) {
//		PRINT((
//			"LiveNodeView('%s')::_handleDoubleClick():\n"
//			"  Error starting node control panel: %s\n",
//			ref->name(),
//			strerror(err)));
//	}	
//	if(!m_controlPanel.IsValid()) {
//		PRINT((
//			"\tno valid control panel messenger\n"));
//	}

	BMessage m(RouteWindow::M_SHOW_NODE_INSPECTOR);
	m.AddInt32("nodeID", ref->id());
	BWindow* w = Window();
	ASSERT(w);
	BMessenger(w).SendMessage(&m);
}

// ---------------------------------------------------------------- //
// presentation methods
// ---------------------------------------------------------------- //

rgb_color make_color(uint8 r, uint8 g, uint8 b, uint8 a=255);

rgb_color make_color(uint8 r, uint8 g, uint8 b, uint8 a) {
	rgb_color c;
	c.red = r;
	c.green = g;
	c.blue = b;
	c.alpha = a;
	return c;
}

// figure colors
void LiveNodeView::_prepareColors() {
	// decide on some colors
	ASSERT(Parent());
	m_backColor = Parent()->ViewColor();
	
	m_cellColor = tint_color(
		ui_color(B_PANEL_BACKGROUND_COLOR),
		B_LIGHTEN_1_TINT);
	
	m_cellColorL1 = tint_color(m_cellColor, B_LIGHTEN_1_TINT);
	m_cellColorL2 = tint_color(m_cellColor, B_LIGHTEN_2_TINT);
	m_cellColorD1 = tint_color(m_cellColor, B_DARKEN_2_TINT);
	m_cellColorD2 = tint_color(m_cellColor, B_DARKEN_4_TINT);
	
	m_freeJackColor = make_color(48, 192, 128);
	m_textColor = make_color(0, 0, 0);
}

// fetch node name (shortening as necessary to fit)
void LiveNodeView::_prepareName() {

	status_t err;
	
	char idBuffer[16];
	sprintf(idBuffer, "%ld", ref->id());
	m_displayName = idBuffer;
	m_displayName += ": ";

	// [e.moon 29sep99] use name of file being played, if available
	entry_ref nodeFile;

	if(ref->kind() & B_FILE_INTERFACE) {
		err = BMediaRoster::Roster()->GetRefFor(
			ref->node(),
			&nodeFile);

		if(err < B_OK) {
			m_displayName += ref->name();
			m_displayName += " (no file)";
		}
		else {
			BEntry e(&nodeFile);
			char n[B_FILE_NAME_LENGTH];
			e.GetName(n);
			m_displayName += n;
		}
	} 
	else
		m_displayName += ref->name();

	const char* dots = "...";
	const float dotsWidth = m_font.StringWidth(dots);
	float dotsUsed = 0.0;
	BRect cellR = _cellBounds(Bounds());
	
	// figure maximum width of the string
	float max =
		cellR.Width() -
		(JACK_WIDTH*2 + NAME_PAD_X*2 + TRANSPORT_ICON_WIDTH);
	if(max < dotsWidth * 1.5) {
		// too little room? give up
		m_displayName = "";
		return;
	}
	
	while(m_displayName.CountChars() > 0) {
		float width = m_font.StringWidth(m_displayName.String()) + dotsUsed;
		if(width <= max)
			// short enough
			break;
		
		// shrink a bit
		m_displayName.Remove(m_displayName.CountChars()-1, 1);
		dotsUsed = dotsWidth;
	}
	
	if(dotsUsed != 0.0) {
		// if name shortened, prepare a tip
		m_nameTipRect = cellR;
		m_nameTipRect.top = m_nameTipRect.bottom -
			(m_fh.ascent + m_fh.descent + 3.0);			
		m_nameTipRect.left += JACK_WIDTH;
		m_nameTipRect.right -= JACK_WIDTH;
			
		m_displayName += dots;
	}
	else
		m_nameTipRect = BRect(); // init to invalid rect
}

// figure bounds of the actual cell view (leaving room
// for connection icons on either side)
BRect LiveNodeView::_cellBounds(const BRect& viewBounds) const {
	BRect cellBounds = viewBounds;

// 24may99: cells currently extend to view edges
//
//	float margin = JACK_WIDTH/2 + JACK_PAD_X;
//	cellBounds.left += margin;
//	cellBounds.right -= margin;

	return cellBounds;
}

	// figure jack positioning info
void LiveNodeView::_getJackPositions(
	BRect&		outLeftTopJack,
	BRect&		outRightTopJack,
	BPoint&		outJackOffset) {

	BRect b = Bounds();
	
	// figure first input jack frame rect

// 24may99: cells now extend to all view edges
//	outLeftTopJack.left = JACK_PAD_X;
	outLeftTopJack.left = 0;
	outLeftTopJack.top = (V_UNIT * 2) - JACK_PAD_Y;
	outLeftTopJack.right = outLeftTopJack.left + JACK_WIDTH;
	outLeftTopJack.bottom = (V_UNIT * 4) + JACK_PAD_Y - 1;

	// derive first output jack rect	
	outRightTopJack = outLeftTopJack;
//	outRightTopJack.right = b.right - JACK_PAD_X;
	outRightTopJack.right = b.right;
	outRightTopJack.left = outRightTopJack.right - JACK_WIDTH;
	
	// distance between jacks
	outJackOffset.x = 0.0;
	outJackOffset.y = V_UNIT*4;
}

// add pop-up tips for all the input/output jacks
void LiveNodeView::_updateTips(TipManager* pTipManager) {

	ASSERT(pTipManager);
	pTipManager->removeAll(this);
	char formatStr[256];
	status_t err;
	
	// * NAME *
	
	if(m_nameTipRect.IsValid()) {
	
		BString fullName;
		entry_ref nodeFile;
		
		if(ref->kind() & B_FILE_INTERFACE) {
			err = BMediaRoster::Roster()->GetRefFor(
				ref->node(),
				&nodeFile);
	
			if(err < B_OK) {
				fullName += ref->name();
				fullName += " (no file)";
			}
			else {
				BEntry e(&nodeFile);
				char n[B_FILE_NAME_LENGTH];
				e.GetName(n);
				fullName += n;
			}
		}
		else
			fullName += ref->name();

		pTipManager->setTip(m_nameTipRect, fullName.String(), this,
			TipManager::LEFT_OFFSET_FROM_POINTER, BPoint(-8.0, -24.0));
	}
	
	// * INPUTS *

	uint32 inputs = m_connectedInputs.size() + m_freeInputs.size();
	
	for(int n = 0; n < inputs; n++) {
		BRect jackRect;
		err = getJackFrame(
			WireVertex::NODE_INPUT_JACK, n, jackRect);
		ASSERT(err == B_OK);
		
		// +++++ format info
		BString tip;
		media_format format;
		if(n < m_connectedInputs.size()) {
			tip = m_connectedInputs[n].name;
			format = m_connectedInputs[n].format;
		} else {
			tip = m_freeInputs[n-m_connectedInputs.size()].name;
			format = m_freeInputs[n-m_connectedInputs.size()].format;
		}
		
		if(!tip.Length())
			tip = "(Anonymous Input)";
		
		if(format.type > B_MEDIA_UNKNOWN_TYPE) {
			string_for_format(format, formatStr, 255);
			tip << ": " << formatStr;
		}
		else
			tip << " (no format specified)";

//		PRINT(( "adding tip '%s' at (%.1f,%.1f,%.1f,%.1f)\n",
//			name.String(),
//			jackRect.left, jackRect.top, jackRect.right, jackRect.bottom));

		err = pTipManager->setTip(jackRect, tip.String(), this,
			TipManager::RIGHT_OFFSET_FROM_RECT, BPoint(JACK_TIP_OFFSET_X, JACK_TIP_OFFSET_Y));
		if(err < B_OK) {
			PRINT(( "LiveNodeView::updateTips(): setTip() failed on input %s\n",
				tip.String()));
		}
	}
	
	// * OUTPUTS *
	
	uint32 outputs = m_connectedOutputs.size() + m_freeOutputs.size();
	
	for(int n = 0; n < outputs; n++) {
		BRect jackRect;
		err = getJackFrame(
			WireVertex::NODE_OUTPUT_JACK, n, jackRect);
		ASSERT(err == B_OK);
		
		// +++++ format info
		BString tip;
		media_format format;
		if(n < m_connectedOutputs.size()) {
			tip = m_connectedOutputs[n].name;
			format = m_connectedOutputs[n].format;
		} else {
			tip = m_freeOutputs[n-m_connectedOutputs.size()].name;
			format = m_freeOutputs[n-m_connectedOutputs.size()].format;
		}

		if(!tip.Length())
			tip = "(Anonymous Output)";
			
		if(format.type > B_MEDIA_UNKNOWN_TYPE) {
			string_for_format(format, formatStr, 255);
			tip << ": " << formatStr;
		}
		else
			tip << " (no format specified)";
	
//		PRINT(( "adding tip '%s' at (%.1f,%.1f,%.1f,%.1f)\n",
//			name.String(),
//			jackRect.left, jackRect.top, jackRect.right, jackRect.bottom));
		
		err = pTipManager->setTip(jackRect, tip.String(), this,
			TipManager::LEFT_OFFSET_FROM_RECT, BPoint(JACK_TIP_OFFSET_X, JACK_TIP_OFFSET_Y));
		if(err < B_OK) {
			PRINT(( "LiveNodeView::updateTips(): setTip() failed on output %s\n",
				tip.String()));
		}
	}
}

// END -- LiveNodeView.cpp --
