// NodeInspector.cpp

#include "NodeInspector.h"

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

#include "InspectorToggleButton.h"
#include "NumericValControl.h"
#include "TextControlFloater.h"

#include "InspectorWindow.h"
#include "RouteWindow.h"

#include "TipManager.h"

#include <Button.h>
#include <CheckBox.h>
#include <Debug.h>
#include <Font.h>
#include <Invoker.h>
#include <StringView.h>
#include <MediaRoster.h>
#include <MediaTheme.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <ParameterWeb.h>
#include <PopUpMenu.h>
#include <String.h>
#include <TextControl.h>
#include <TimeCode.h>

#include <algorithm>
#include <functional>

#include "inspector_layout.h"

__USE_CORTEX_NAMESPACE

// -------------------------------------------------------- //
// _NodeInfoView
// -------------------------------------------------------- //

__BEGIN_CORTEX_NAMESPACE
class _NodeInfoView :
	public	BView {
	typedef	BView _inherited;
	
public:												// ctor/dtor
	~_NodeInfoView() {
		if(m_backBitmap)
			delete m_backBitmap;
	}

	_NodeInfoView(
		BRect											frame,
		NodeInspector*						parent,
		const char*								name,
		uint32										resizeMode =B_FOLLOW_LEFT|B_FOLLOW_TOP,
		uint32										flags =B_WILL_DRAW|B_FRAME_EVENTS) :
		
		BView(frame, name, resizeMode, flags),
		m_parent(parent),
		m_plainFont(be_plain_font),
		m_boldFont(be_bold_font),
		m_statusText("No errors."),
		m_backBitmap(0),
		m_backBitmapView(0) {
		
		_initViews();
		_initColors();
		_updateLayout();
		_updateBackBitmap();
	}
	
public:												// operations

	void updatePosition(
		bigtime_t									position) {
	
		int hours, minutes, seconds, frames;
		us_to_timecode(position, &hours, &minutes, &seconds, &frames);
		char buffer[64];
		sprintf(buffer, "%02d:%02d:%02d:%02d",
			hours, minutes, seconds, frames);
		m_statusText = buffer;
		Invalidate();
	}
		
	void clearPosition() {
		m_statusText = "No errors.";
		Invalidate();
	}

public:												// BView
	virtual void FrameResized(
		float											width,
		float											height) {

		_inherited::FrameResized(width, height);
		_updateLayout();
		_updateBackBitmap();
		Invalidate();
	}
	
	virtual void GetPreferredSize(
		float*										width,
		float*										height) {
		font_height fh;
		m_plainFont.GetHeight(&fh);
		
		*width = 0.0;
		*height = (fh.ascent+fh.descent+fh.leading) * 2;
		*height += 4.0; //+++++
	}
	
	
	virtual void Draw(
		BRect											updateRect) {

		BView* view = (m_backBitmapView) ? m_backBitmapView : this;
		if(view == m_backBitmapView) {
			ASSERT(m_backBitmap);
			m_backBitmap->Lock();
		}

		NodeRef* ref = m_parent->m_ref;
		BRect b = Bounds();
		
		// background
		view->SetLowColor(LowColor());
		view->FillRect(updateRect, B_SOLID_LOW);
		
		// border
		rgb_color hi = tint_color(ViewColor(), B_LIGHTEN_2_TINT);
		rgb_color lo = tint_color(ViewColor(), B_DARKEN_2_TINT);
		view->SetHighColor(lo);
		view->StrokeLine(
			b.LeftTop(), b.RightTop());
		view->StrokeLine(
			b.LeftTop(), b.LeftBottom());

		view->SetHighColor(hi);
		view->StrokeLine(
			b.LeftBottom(), b.RightBottom());
		view->StrokeLine(
			b.RightTop(), b.RightBottom());
			
		view->SetHighColor(HighColor());
		
		// name
		BString name;
		if(ref) {
			name << ref->id() << ": " << ref->name();
		} else {
			name << "(no node)";
		}
		
		// +++++ constrain width
		view->SetFont(&m_boldFont);
		view->DrawString(name.String(), m_namePosition);
		
		view->SetFont(&m_plainFont);
		
		// status
		// +++++ constrain width
		view->DrawString(m_statusText.String(), m_statusPosition);

		if(view == m_backBitmapView) {
			view->Sync();
			m_backBitmap->Unlock();

			// update display
			DrawBitmap(m_backBitmap, BPoint(0,0));
		}
	}
	
public:												// implementation
	void _initViews() {
		// +++++
	}

	void _initColors() {
		// +++++ these colors need to be centrally defined
		SetViewColor(B_TRANSPARENT_COLOR);
		SetLowColor(16, 64, 96, 255);
		SetHighColor(255,255,255,255);
	}

	void _updateLayout() {
		float _edge_pad_x = 3.0;
		float _edge_pad_y = 1.0;
		
		BRect b = Bounds();
		font_height fh;
		m_plainFont.GetHeight(&fh);
		
		float realWidth = b.Width() - (_edge_pad_x * 2);

		m_maxNameWidth = realWidth;
		m_namePosition.x = _edge_pad_x;
		m_namePosition.y = _edge_pad_x + fh.ascent - 2.0;
		
		m_maxStatusWidth = realWidth;
		m_statusPosition.x = _edge_pad_x;
		m_statusPosition.y = b.Height() - (fh.descent + fh.leading + _edge_pad_y);
	}
	
	void _updateBackBitmap() {
		BRect b = Bounds();
		
		if(!b.IsValid())
			return;
		
		if(m_backBitmap) {
			// big enough already?
			if(b.Width() <= m_backBitmap->Bounds().Width() &&
				b.Height() <= m_backBitmap->Bounds().Height())
				return;

			// make room for a new bitmap			
			delete m_backBitmap;
			m_backBitmap = 0;
			m_backBitmapView = 0;
		}
		
		m_backBitmap = new BBitmap(b, B_RGB32, true);
		m_backBitmapView = new BView(
			b,
			"_NodeInfoView::m_backBitmapView",
			B_FOLLOW_NONE,
			B_WILL_DRAW);
		m_backBitmap->AddChild(m_backBitmapView);
	}

private:
	NodeInspector*							m_parent;
	
	BFont												m_plainFont;
	BFont												m_boldFont;
	
	BPoint											m_namePosition;
	float												m_maxNameWidth;
	
	BString											m_statusText;
	BPoint											m_statusPosition;
	float												m_maxStatusWidth;
	
	BBitmap*										m_backBitmap;
	BView*											m_backBitmapView;
};
__END_CORTEX_NAMESPACE

// -------------------------------------------------------- //
// *** ctors
// -------------------------------------------------------- //
	
NodeInspector::NodeInspector(
	NodeManager*						manager,
	const char*							name) :
	
	BView(
		BRect(),
		name,
		B_FOLLOW_ALL_SIDES,
		B_WILL_DRAW|B_FRAME_EVENTS),
	m_manager(manager),
	m_ref(0),
	m_lockTarget(true),
	m_controlView(0) {
	
	// initialize
	_initLayout();
	_constructControls();
//	_updateLayout(); deferred until AttachedToWindow(): 24aug99
	_disableControls();
	
	SetViewColor(
		tint_color(
			ui_color(B_PANEL_BACKGROUND_COLOR),
			B_LIGHTEN_1_TINT));
}

// -------------------------------------------------------- //
// *** accessors
// -------------------------------------------------------- //

bool NodeInspector::canAcceptNewTarget() const {
	return !m_lockTarget;
}
	

// -------------------------------------------------------- //
// *** BView
// -------------------------------------------------------- //

void NodeInspector::AttachedToWindow() {
	_inherited::AttachedToWindow();
	
	// finish layout
	_updateLayout();
	
	TipManager* t = TipManager::Instance();
	t->setTip(
		"Toggles cycle (loop) for this node.",
		m_cycleCheckBox);
	t->setTip(
		"When Lock is enabled, this inspector will show\n"
		"information for the current node only.  If Lock\n"
		"is turned off, this window will update whenever\n"
		"you select a new node in the Routing Window.",
		m_lockButton);
	t->setTip(
		"Toggles display of parameters (controls) for this node.",
		m_parametersButton);
}

void NodeInspector::AllAttached() {
	_inherited::AllAttached();
	
	// set message targets for view-configuation controls
	for(target_set::iterator it = m_localTargets.begin();
		it != m_localTargets.end(); ++it) {
		ASSERT(*it);
		(*it)->SetTarget(this);
	}
}

void NodeInspector::AllDetached() {
	_inherited::AllDetached();
	
	// [e.moon 28sep99]
	// release the target node, if any
	if(m_ref)
		_releaseNode();
}

void NodeInspector::FrameResized(
	float										width,
	float										height) {

	_inherited::FrameResized(width, height);
//	_updateLayout();
}

void NodeInspector::Draw(
	BRect										updateRect) {
	
	// draw dividing mark for parameter area
	if(m_controlView) {
		BRect controlFrame = m_controlView->Frame();
		controlFrame.InsetBy(-3.0, -3.0);
		SetHighColor(0, 0, 0, 255);
		StrokeRect(controlFrame);
	}
}

// -------------------------------------------------------- //
// *** BHandler
// -------------------------------------------------------- //

void NodeInspector::MessageReceived(
	BMessage*								message) {
	status_t err;
	uint32 nodeID;
	
//	PRINT((
//		"NodeInspector::MessageReceived()\n"));
//	message->PrintToStream();
	
	switch(message->what) {
		case M_SELECT_NODE:
			if(!m_lockTarget || !m_ref)
				_handleSelectNode(message);
			break;
		
		case NodeRef::M_RELEASED:
			{
				err = message->FindInt32("nodeID", (int32*)&nodeID);
				if(err < B_OK) {
					PRINT((
						"* NodeInspector::MessageReceived(NodeRef::M_RELEASED)\n"
						"  no nodeID!\n"));
				}
				if(!m_ref || nodeID != m_ref->id()) {
					PRINT((
						"* NodeInspector::MessageReceived(NodeRef::M_RELEASED)\n"
						"  mismatched nodeID.\n"));
					break;
				}
				
				_releaseNode();
								
				if(m_lockTarget) {
					// I was too attached to that node to go on... <sob>
					BMessenger(Window()).SendMessage(B_QUIT_REQUESTED);
				}
			}
			break;
			
		case NodeRef::M_OBSERVER_ADDED:
			err = message->FindInt32("nodeID", (int32*)&nodeID);
			if(err < B_OK) {
				PRINT((
					"* NodeInspector::MessageReceived(NodeRef::M_OBSERVER_ADDED)\n"
					"  no nodeID!\n"));
				break;
			}
			if(!m_ref || nodeID != m_ref->id()) {
				PRINT((
					"* NodeInspector::MessageReceived(NodeRef::M_OBSERVER_ADDED)\n"
					"  mismatched nodeID; ignoring.\n"));
				break;
			}
			
			_enableControls();
			break;
			
		case NodeRef::M_POSITION: {
			//PRINT(("### NodeRef::M_POSITION\n"));
			
			bigtime_t position;
			err = message->FindInt64("position", &position);
			if(err < B_OK) {
				PRINT((
					"!!! NodeInspector::MessageReceived(NodeRef::M_POSITION):\n"
					"    position missing\n"));
				break;
			}
			
			m_infoView->updatePosition(position);
			break;
		}
			
		case M_TOGGLE_LOCK:
			m_lockTarget = (m_lockButton->Value() == 1);
			break;
			
		case M_TOGGLE_PARAMETERS:
			m_controlView ?
				_hideControlView() :
				_showControlView();
			
			_updateLayout();
			break;
		
		// * outbound node-control messages
		
		case M_SET_NODE_RUN_MODE:
			{					
				int32 runMode;
				err = message->FindInt32("runMode", &runMode);
				ASSERT(err == B_OK);
				
				BMessage m(NodeRef::M_SET_RUN_MODE);
				m.AddInt32("runMode", runMode);
				
				if(runMode == BMediaNode::B_RECORDING) {
					// figure an appropriate delay
					ASSERT(m_ref);
					bigtime_t delay = m_ref->calculateRecordingModeDelay();

					m.AddInt64("delay", delay);
				}
				
				// forward new message to node
				BMessenger(m_ref).SendMessage(&m);
			}
			break;

		case NodeRef::M_CYCLING_CHANGED: {
//			PRINT(("### NodeRef::M_CYCLING_CHANGED\n"));
//			
			if(!m_ref)
				break;

			// update transport inspector if the node is grouped
			NodeGroup* group = m_ref->group();
			if(group) {
				BMessage m(RouteWindow::M_REFRESH_TRANSPORT_SETTINGS);
				m.AddInt32("groupID", group->id());
				InspectorWindow* window = dynamic_cast<InspectorWindow*>(Window());
				ASSERT(window);
				window->routeWindow().SendMessage(&m);
			}
			break;
		}
			
		default:
			_inherited::MessageReceived(message);
			break;
	}
}

// -------------------------------------------------------- //
// *** BHandler impl.
// -------------------------------------------------------- //

void NodeInspector::_handleSelectNode(
	BMessage*								message) {

	uint32 nodeID;
	status_t err = message->FindInt32("nodeID", (int32*)&nodeID);
	if(err < B_OK) {
		PRINT((
			"* NodeInspector::_handleSelectNode(): no nodeID\n"));
		return;
	}
	
//	PRINT((
//		"* NodeInspector::_handleSelectNode(): %ld\n", nodeID));

	if(m_ref && nodeID != m_ref->id())
		_releaseNode();

	_selectNode(nodeID);
	
	Invalidate();
}

// -------------------------------------------------------- //
// *** internal operations
// -------------------------------------------------------- //

// select the given target node; initialize controls
// (if 0, gray out all controls)
void NodeInspector::_selectNode(
	uint32									nodeID) {
	status_t err;
	
	if(m_ref)
		_releaseNode();

	if(!nodeID) {
		_disableControls();
		return;
	}

	err = m_manager->getNodeRef(nodeID, &m_ref);
	if(err < B_OK) {
		PRINT((
			"* NodeInspector::_selectNode(%ld): getNodeRef() failed:\n"
			"  %s\n",
			nodeID,
			strerror(err)));
		return;
	}

	_observeNode();
}

void NodeInspector::_observeNode() {
	ASSERT(m_ref);
	
	status_t err = add_observer(this, m_ref);
	if(err < B_OK) {
		PRINT((
			"!!! NodeInspector::_observeNode(): add_observer() failed:\n"
			"    %s\n",
			strerror(err)));
		return;
	}
	
	err = m_ref->addPositionObserver(this);
	if(err < B_OK) {
		PRINT((
			"!!! NodeInspector::_observeNode(): NodeRef::addPositionObserver() failed:\n"
			"    %s\n",
			strerror(err)));
		return;
	}
	m_infoView->clearPosition();
}

void NodeInspector::_releaseNode() {
	ASSERT(m_ref);
	
//	PRINT((
//		"NodeInspector::_releaseNode(%ld)\n", m_ref->id()));

	m_infoView->clearPosition();
	
	status_t err = m_ref->removePositionObserver(this);
	if(err < B_OK) {
		PRINT((
			"!!! NodeInspector::_releaseNode(): NodeRef::removePositionObserver() failed:\n"
			"    %s\n",
			strerror(err)));
	}

	err = remove_observer(this, m_ref);
	m_ref = 0;

	if(err < B_OK) {
		PRINT((
			"!!! NodeInspector::_releaseNode(): remove_observer() failed:\n"
			"    %s\n",
			strerror(err)));
	}
}
// -------------------------------------------------------- //
// *** CONTROLS
// -------------------------------------------------------- //

const char _run_mode_strings[][20] = {
	"(same as group)",
	"Offline",
	"Decrease Precision",
	"Increase Latency",
	"Drop Data",
	"Recording"
};
const int _run_modes = 6;

void NodeInspector::_constructControls() {

	BMessage* m;

	// * create and populate, but don't position, the views:


	m_infoView = new _NodeInfoView(
		BRect(),
		this,
		"infoView");
	AddChild(m_infoView);
	
	m_runModeView = new BMenuField(
		BRect(),
		"runModeView",
		"Run Mode:",
		new BPopUpMenu("runModeMenu"));
	_populateRunModeMenu(
		m_runModeView->Menu());
	AddChild(m_runModeView);


	m = new BMessage(NodeRef::M_SET_CYCLING);
	m_cycleCheckBox = new BCheckBox(
		BRect(),
		"cycleCheckBox",
		"Cycle",
		m);
	_addNodeRefTarget(m_cycleCheckBox);
	AddChild(m_cycleCheckBox);

	m = new BMessage(NodeRef::M_PREROLL);
	m_prerollButton = new BButton(
		BRect(),
		"prerollButton",
		"Preroll",
		m);
	_addNodeRefTarget(m_prerollButton);
	AddChild(m_prerollButton);
	
	m_lockButton = new InspectorToggleButton(
		BRect(),
		"lockButton",
		"Lock",
		new BMessage(M_TOGGLE_LOCK));
	_addLocalTarget(m_lockButton);
	m_lockButton->SetValue(m_lockTarget ? 1 : 0);
	AddChild(m_lockButton);
		
	m_parametersButton = new InspectorToggleButton(
		BRect(),
		"lockButton",
		"Parameters",
		new BMessage(M_TOGGLE_PARAMETERS));
	_addLocalTarget(m_parametersButton);
	m_parametersButton->SetEnabled(false);
	AddChild(m_parametersButton);
}

void NodeInspector::_populateRunModeMenu(
	BMenu*									menu) {

	BMessage* m;
	for(int n = 0; n < _run_modes; ++n) {
		m = new BMessage(M_SET_NODE_RUN_MODE);
		m->AddInt32("runMode", n);

		BMenuItem* i = new BMenuItem(
			_run_mode_strings[n], m);
		menu->AddItem(i);
		_addLocalTarget(i);
	}
}

// add the given invoker to be retargeted to this
// view (used for controls whose messages need a bit more
// processing before being forwarded to the NodeManager.)

void NodeInspector::_addLocalTarget(
	BInvoker*								invoker) {
	m_localTargets.push_back(invoker);
}

void NodeInspector::_addNodeRefTarget(
	BInvoker*								invoker) {
	m_nodeTargets.push_back(invoker);
}


void NodeInspector::_disableControls() {

	BWindow* w = Window();

	if(w)
		w->BeginViewTransaction();

//	m_nameView->SetText("(no group)");
	m_infoView->Invalidate();
	
	m_runModeView->SetEnabled(false);
	m_runModeView->Menu()->Superitem()->SetLabel("(none)");

	m_cycleCheckBox->SetValue(0);
	m_cycleCheckBox->SetEnabled(false);
	
	m_prerollButton->SetEnabled(false);

	m_lockButton->SetEnabled(false);
	m_parametersButton->SetEnabled(false);

	if(m_controlView) {
		_hideControlView();
		_updateLayout(false);
	}

	if(w)
		w->EndViewTransaction();
}

void NodeInspector::_enableControls() {

	ASSERT(m_ref);
	//Autolock _l(m_ref);
	BWindow* w = Window();
	if(w)
		w->BeginViewTransaction();

//	BString nameViewText = m_group->name();
//	nameViewText << ": " << m_group->countNodes() << " nodes.";
//	m_nameView->SetText(nameViewText.String());
	
	m_infoView->Invalidate();
	
	m_runModeView->SetEnabled(true);
	_updateRunMode();

	m_cycleCheckBox->SetValue(m_ref->isCycling());
	m_cycleCheckBox->SetEnabled(true);

	_updateTransportButtons();

	m_lockButton->SetEnabled(true);

	// only enable parameter button if node is marked controllable
	// [8sep99: AND it has one or more parameters]
	
	bool enableParameters = false;
	if(m_ref->kind() & B_CONTROLLABLE) {
		BParameterWeb* web;
		status_t err = m_manager->roster->GetParameterWebFor(m_ref->node(), &web);
		if(err == B_OK && web->CountParameters())
			enableParameters = true;
	}
	m_parametersButton->SetEnabled(enableParameters);
	
	if(enableParameters && m_parametersButton->Value()) {
		_showControlView();
		_updateLayout(false);
	}
	else if(m_controlView) {
		_hideControlView();
		_updateLayout(false);
	}
	
	// target controls on NodeRef
	for(target_set::iterator it = m_nodeTargets.begin();
		it != m_nodeTargets.end(); ++it) {
		ASSERT(*it);
		(*it)->SetTarget(m_ref);
	}

	if(w) {
		w->EndViewTransaction();
	}	
}

void NodeInspector::_updateTransportButtons() {
//	switch(m_group->transportState()) {
//		case NodeGroup::TRANSPORT_INVALID:
//		case NodeGroup::TRANSPORT_STARTING:
//		case NodeGroup::TRANSPORT_STOPPING:
//			m_startButton->SetEnabled(false);
//			m_stopButton->SetEnabled(false);
//			m_prerollButton->SetEnabled(false);
//			break;
//			
//		case NodeGroup::TRANSPORT_STOPPED:
//			m_startButton->SetEnabled(true);
//			m_stopButton->SetEnabled(false);
//			m_prerollButton->SetEnabled(true);
//			break;
//
//		case NodeGroup::TRANSPORT_RUNNING:
//			m_startButton->SetEnabled(false);
//			m_stopButton->SetEnabled(true);
//			m_prerollButton->SetEnabled(false);
//			break;
//	}
}

void NodeInspector::_updateRunMode() {
	ASSERT(m_ref);
	
	BMenu* menu = m_runModeView->Menu();
	uint32 runMode = m_ref->runMode(); // 0 allowed (means "use group's run mode")
	ASSERT(menu->CountItems() > runMode);
	menu->ItemAt(runMode)->SetMarked(true);
}

// -------------------------------------------------------- //
// *** LAYOUT ***
// -------------------------------------------------------- //

const float _edge_pad_x 								= 3.0;
const float _edge_pad_y 								= 3.0;

const float _column_pad_x 							= 4.0;

const float _field_pad_x 								= 20.0;

const float _text_view_pad_x						= 10.0;

const float _control_pad_x 							= 5.0;
const float _control_pad_y 							= 10.0;
const float _menu_field_pad_x 					= 30.0;

const float _label_pad_x 								= 8.0;

const float _transport_pad_y 						= 4.0;
const float _transport_button_width			= 60.0;
const float _transport_button_height		= 22.0;
const float _transport_button_pad_x			= 4.0;

const float _toggle_button_pad_x				= 4.0;
const float _toggle_button_pad_y				= 2.0;
const float _toggle_button_height				= 16.0;

const float _lock_button_width					= 42.0;
const float _param_button_width					= 82.0;

const float _parameter_pad_top					= 6.0;
const float _parameter_pad_left					= 6.0;
const float _parameter_pad_right				= 6.0;
const float _parameter_pad_bottom				= 5.0;

class NodeInspector::_layout_state {
public:
	_layout_state() {}

	// +++++	
};

// -------------------------------------------------------- //

void NodeInspector::_initLayout() {
	m_layout = new _layout_state();
}


void NodeInspector::_updateLayout(
	bool										asSingleTransaction) {
	
	BWindow* window = Window();
	if(asSingleTransaction && window)
		window->BeginViewTransaction();

	// calculate the ideal size of the view
	// * max label width
	float maxLabelWidth = 0.0;
//	float w;
	
	maxLabelWidth = be_bold_font->StringWidth(
		m_runModeView->Label());
		
	// * max field width
	float maxFieldWidth = 0.0;
	maxFieldWidth = _menu_width(
		m_runModeView->Menu(), be_plain_font);

	// * column width
	float columnWidth =
		maxLabelWidth +
		maxFieldWidth + _label_pad_x + _field_pad_x;

	// figure columns
	float column1_x = _edge_pad_x;
	float column2_x = column1_x + columnWidth + _column_pad_x;

	// * sum to figure view width
	float viewWidth =
		column2_x + columnWidth + _edge_pad_x;

	// make room for buttons
	float buttonSpan =
		(_transport_button_width*3) +
		(_transport_button_pad_x*2);
	if(columnWidth < buttonSpan)
		viewWidth += (buttonSpan - columnWidth);

//	float insideWidth = viewWidth - (_edge_pad_x*2);
		
	// * figure view height a row at a time
	font_height fh;
	be_plain_font->GetHeight(&fh);
	float lineHeight = fh.ascent + fh.descent + fh.leading;

	float prefInfoWidth, prefInfoHeight;
	m_infoView->GetPreferredSize(&prefInfoWidth, &prefInfoHeight);
	float row_1_height = max(prefInfoHeight, _transport_button_height);
	
	float row1_y = _edge_pad_y;
	float row2_y = row1_y + row_1_height + _transport_pad_y;
	float row3_y = row2_y + lineHeight + _control_pad_y;
//	float row4_y = row3_y + lineHeight + _control_pad_y + _transport_pad_y;
//	float row5_y = row4_y + lineHeight + _control_pad_y;
	float viewHeight = row3_y + lineHeight + _edge_pad_y + 7.0;
	
	// Place controls
	m_infoView->MoveTo(
		column1_x+1.0, row1_y+1.0);
	m_infoView->ResizeTo(
		columnWidth, prefInfoHeight);
		
	BRect br(
		column2_x, row1_y,
		column2_x+_transport_button_width,
		row1_y+_transport_button_height);
	if(prefInfoHeight > _transport_button_height)
		br.OffsetBy(0.0, (prefInfoHeight - _transport_button_height)/2);

	m_cycleCheckBox->MoveTo(br.LeftTop() + BPoint(
		_transport_button_pad_x, 3.0));
	m_cycleCheckBox->ResizeTo(br.Width(), br.Height());
	br.OffsetBy(_transport_button_width, 0.0);
//
//	m_stopButton->MoveTo(br.LeftTop());
//	m_stopButton->ResizeTo(br.Width(), br.Height());
//	br.OffsetBy(_transport_button_width + _transport_button_pad_x, 0.0);

	m_prerollButton->MoveTo(br.LeftTop());
	m_prerollButton->ResizeTo(br.Width(), br.Height());

	m_runModeView->MoveTo(
		column2_x, row3_y);
		
	// only size m_runModeView once
	if(!m_runModeView->Bounds().IsValid())
		m_runModeView->ResizeTo(
			columnWidth, lineHeight);

	m_runModeView->SetDivider(
		maxLabelWidth+_label_pad_x);
	m_runModeView->SetAlignment(
		B_ALIGN_LEFT);
		
	// lock button at bottom-left
	
	m_lockButton->ResizeTo(
		_lock_button_width,
		_toggle_button_height);
	float lockButton_y = row3_y + _toggle_button_pad_y;
	m_lockButton->MoveTo(
		column1_x,
		lockButton_y);
		
	// parameter button to the right of lock button
	m_parametersButton->ResizeTo(
		_param_button_width,
		_toggle_button_height);
	m_parametersButton->MoveTo(
		column1_x + _lock_button_width + _toggle_button_pad_x,
		lockButton_y);

	// handle control-panel view
	if(m_controlView) {
		m_controlView->SetResizingMode(B_FOLLOW_NONE);
		m_controlView->MoveTo(_parameter_pad_left, viewHeight + _parameter_pad_top);
		
		BRect controlBounds = m_controlView->Bounds();
		float w = controlBounds.Width() + _parameter_pad_left + _parameter_pad_right;
		float h = controlBounds.Height() + _parameter_pad_top + _parameter_pad_bottom;
		if(w > viewWidth)
			viewWidth = w;
		
		viewHeight += h;
	}

//	BRect b = Bounds();
//	float targetWidth = (b.Width() < viewWidth) ?
//		viewWidth :
//		b.Width();
//	float targetHeight = (b.Height() < viewHeight) ?
//		viewHeight :
//		b.Height();

	float targetWidth = viewWidth;
	float targetHeight = viewHeight;
	
	// Resize view to fit contents
	ResizeTo(targetWidth, targetHeight);
	
	// resize window to fit view
	if(window) {
		uint32 resizeMode = ResizingMode();
		SetResizingMode(B_FOLLOW_NONE);
		window->ResizeTo(targetWidth, targetHeight);
		SetResizingMode(resizeMode);
	}

	if(asSingleTransaction && window)
		window->EndViewTransaction();
}

void NodeInspector::_hideControlView() {
	if(!m_controlView)
		return;

	RemoveChild(m_controlView);
	delete m_controlView;
	m_controlView = 0;
}

void NodeInspector::_showControlView() {
	status_t err;
	if(m_controlView)
		_hideControlView();
	
	// check node
	ASSERT(m_ref);
	ASSERT(m_ref->kind() & B_CONTROLLABLE);
			
	// fetch parameters
	BParameterWeb* web;
	err = m_manager->roster->GetParameterWebFor(m_ref->node(), &web);
	if(err < B_OK) {
		PRINT((
			"! NodeInspector::_showControlView(): GetParameterWebFor() failed:\n"
			"  %s\n",
			strerror(err)));
		m_parametersButton->SetValue(0);
		m_parametersButton->SetEnabled(false);
		return;
	}
		
	m_controlView = BMediaTheme::ViewFor(web);
	ASSERT(m_controlView);
		
	AddChild(m_controlView);
}

// -------------------------------------------------------- //
// *** dtor
// -------------------------------------------------------- //

NodeInspector::~NodeInspector() {
	if(m_ref)
		_releaseNode();
	if(m_layout)
		delete m_layout;
}
	

// END -- NodeInspector.cpp --
