1/*
2 * Copyright (c) 1999-2000, Eric Moon.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions, and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions, and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * 3. The name of the author may not be used to endorse or promote products
17 *    derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31
32// MediaNodePanel.cpp
33// c.lenz 10oct99
34
35#include "MediaNodePanel.h"
36// InfoWindow
37#include "InfoWindowManager.h"
38// MediaRoutingView
39#include "MediaRoutingView.h"
40#include "MediaWire.h"
41#include "RouteAppNodeManager.h"
42// NodeManager
43#include "NodeRef.h"
44#include "NodeGroup.h"
45// ParameterWindow
46#include "ParameterWindow.h"
47// Support
48#include "cortex_ui.h"
49#include "MediaIcon.h"
50#include "MediaString.h"
51// RouteApp
52#include "RouteWindow.h"
53// TipManager
54#include "TipManager.h"
55
56// App Kit
57#include <Application.h>
58#include <Roster.h>
59// Interface Kit
60#include <MenuItem.h>
61#include <PopUpMenu.h>
62// Media Kit
63#include <MediaDefs.h>
64#include <MediaRoster.h>
65
66using namespace std;
67
68__USE_CORTEX_NAMESPACE
69
70#include <Debug.h>
71#define D_METHOD(x) //PRINT (x)
72#define D_MESSAGE(x) //PRINT (x)
73#define D_DRAW(x) //PRINT (x)
74
75// -------------------------------------------------------- //
76// constants
77// -------------------------------------------------------- //
78
79float	MediaNodePanel::M_DEFAULT_WIDTH		= 90.0;
80float	MediaNodePanel::M_DEFAULT_HEIGHT	= 60.0;
81float	MediaNodePanel::M_LABEL_H_MARGIN	= 3.0;
82float	MediaNodePanel::M_LABEL_V_MARGIN	= 3.0;
83float	MediaNodePanel::M_BODY_H_MARGIN		= 5.0;
84float	MediaNodePanel::M_BODY_V_MARGIN		= 5.0;
85
86// [e.moon 7dec99]
87const BPoint MediaNodePanel::s_invalidPosition(-200.0, -200.0);
88
89// -------------------------------------------------------- //
90// *** ctor/dtor
91// -------------------------------------------------------- //
92
93MediaNodePanel::MediaNodePanel(
94	BPoint position,
95	NodeRef *nodeRef)
96	: DiagramBox(BRect(position, position + BPoint(M_DEFAULT_WIDTH, M_DEFAULT_HEIGHT))),
97	  BHandler(nodeRef->name()),
98	  ref(nodeRef),
99	  m_bitmap(0),
100	  m_icon(0),
101	  m_alternatePosition(s_invalidPosition)
102{
103	D_METHOD(("MediaNodePanel::MediaNodePanel()\n"));
104	ASSERT(ref);
105}
106
107MediaNodePanel::~MediaNodePanel()
108{
109	D_METHOD(("MediaNodePanel::~MediaNodePanel()\n"));
110	if (m_icon)
111	{
112		delete m_icon;
113	}
114	if (m_bitmap)
115	{
116		delete m_bitmap;
117	}
118}
119
120// -------------------------------------------------------- //
121// *** derived from DiagramBox
122// -------------------------------------------------------- //
123
124void MediaNodePanel::attachedToDiagram()
125{
126	D_METHOD(("MediaNodePanel::attachedToDiagram()\n"));
127
128	resizeTo(M_DEFAULT_WIDTH, M_DEFAULT_HEIGHT);
129	_updateIcon(dynamic_cast<MediaRoutingView *>(view())->getLayout());
130	_prepareLabel();
131	populateInit();
132	arrangeIOJacks();
133
134	view()->Looper()->AddHandler(this);
135}
136
137void MediaNodePanel::detachedFromDiagram()
138{
139	D_METHOD(("MediaNodePanel::detachedFromDiagram()\n"));
140
141	BRect labelRect = m_labelRect.OffsetByCopy(Frame().LeftTop());
142	if (m_mouseOverLabel && m_labelTruncated)
143	{
144		TipManager *tips = TipManager::Instance();
145		tips->hideTip(view()->ConvertToScreen(labelRect));
146	}
147
148	view()->Looper()->RemoveHandler(this);
149}
150
151void MediaNodePanel::DrawBox()
152{
153	D_DRAW(("MediaNodePanel::DrawBox()\n"));
154	if (m_bitmap)
155	{
156		view()->DrawBitmap(m_bitmap, Frame().LeftTop());
157	}
158}
159
160void MediaNodePanel::MouseDown(
161	BPoint point,
162	uint32 buttons,
163	uint32 clicks)
164{
165	D_METHOD(("MediaNodePanel::MouseDown()\n"));
166
167	_inherited::MouseDown(point, buttons, clicks);
168
169	// +++ REALLY BAD WORKAROUND
170	MediaJack *jack = dynamic_cast<MediaJack *>(_LastItemUnder());
171	if (jack && jack->Frame().Contains(point))
172	{
173		return;
174	}
175
176	switch (buttons) {
177		case B_PRIMARY_MOUSE_BUTTON:
178		{
179			if (clicks == 2) {
180				if (ref->kind() & B_CONTROLLABLE) {
181					BMessage message(MediaRoutingView::M_NODE_TWEAK_PARAMETERS);
182					DiagramView* v = view();
183					BMessenger(v).SendMessage(&message);
184				}
185			}
186			break;
187		}
188		case B_SECONDARY_MOUSE_BUTTON:
189		{
190			if (clicks == 1) {
191				showContextMenu(point);
192			}
193			break;
194		}
195	}
196}
197
198void MediaNodePanel::MouseOver(
199	BPoint point,
200	uint32 transit)
201{
202	D_METHOD(("MediaNodePanel::MouseOver()\n"));
203	_inherited::MouseOver(point, transit);
204
205	switch (transit)
206	{
207		case B_ENTERED_VIEW:
208		{
209			break;
210		}
211		case B_INSIDE_VIEW:
212		{
213			BRect labelRect = m_labelRect.OffsetByCopy(Frame().LeftTop());
214			if (labelRect.Contains(point))
215			{
216				if (!m_mouseOverLabel && m_labelTruncated)
217				{
218					TipManager *tips = TipManager::Instance();
219					tips->showTip(m_fullLabel.String(), view()->ConvertToScreen(labelRect));
220					m_mouseOverLabel = true;
221				}
222			}
223			else
224			{
225				m_mouseOverLabel = false;
226			}
227			break;
228		}
229		case B_EXITED_VIEW:
230		{
231			m_mouseOverLabel = false;
232			break;
233		}
234	}
235}
236
237void MediaNodePanel::MessageDropped(
238	BPoint point,
239	BMessage *message)
240{
241	D_METHOD(("MediaNodePanel::MessageDropped()\n"));
242
243	// +++ REALLY BAD WORKAROUND
244	MediaJack *jack = dynamic_cast<MediaJack *>(ItemUnder(point));
245	if (jack)
246	{
247		jack->MessageDropped(point, message);
248		return;
249	}
250	else
251	{
252		be_app->SetCursor(B_HAND_CURSOR);
253	}
254}
255
256void MediaNodePanel::selected()
257{
258	D_METHOD(("MediaNodePanel::selected()\n"));
259	_updateBitmap();
260}
261
262void MediaNodePanel::deselected()
263{
264	D_METHOD(("MediaNodePanel::deselected()\n"));
265	_updateBitmap();
266}
267
268// ---------------------------------------------------------------- //
269// *** updating
270// ---------------------------------------------------------------- //
271
272void MediaNodePanel::layoutChanged(
273	int32 layout)
274{
275	D_METHOD(("MediaNodePanel::layoutChanged()\n"));
276
277	BPoint p = Frame().LeftTop();
278	if (m_alternatePosition == s_invalidPosition)
279	{
280		m_alternatePosition = dynamic_cast<MediaRoutingView *>
281							  (view())->findFreePositionFor(this);
282	}
283	moveTo(m_alternatePosition);
284	m_alternatePosition = p;
285
286	resizeTo(M_DEFAULT_WIDTH, M_DEFAULT_HEIGHT);
287	for (uint32 i = 0; i < CountItems(); i++)
288	{
289		MediaJack *jack = dynamic_cast<MediaJack *>(ItemAt(i));
290		jack->layoutChanged(layout);
291	}
292	_updateIcon(layout);
293	_prepareLabel();
294	arrangeIOJacks();
295	_updateBitmap();
296}
297
298void MediaNodePanel::populateInit()
299{
300	D_METHOD(("MediaNodePanel::populateInit()\n"));
301	if (ref->kind() & B_BUFFER_CONSUMER)
302	{
303		vector<media_input> freeInputs;
304		ref->getFreeInputs(freeInputs);
305		for (uint32 i = 0; i < freeInputs.size(); i++)
306		{
307			AddItem(new MediaJack(freeInputs[i]));
308		}
309	}
310	if (ref->kind() & B_BUFFER_PRODUCER)
311	{
312		vector<media_output> freeOutputs;
313		ref->getFreeOutputs(freeOutputs);
314		for (uint32 i = 0; i < freeOutputs.size(); i++)
315		{
316			AddItem(new MediaJack(freeOutputs[i]));
317		}
318	}
319}
320
321void MediaNodePanel::updateIOJacks()
322{
323	D_METHOD(("MediaNodePanel::updateIOJacks()\n"));
324
325	// remove all free inputs/outputs, they may be outdated
326	for (uint32 i = 0; i < CountItems(); i++)
327	{
328		MediaJack *jack = dynamic_cast<MediaJack *>(ItemAt(i));
329		if (jack && !jack->isConnected())
330		{
331			RemoveItem(jack);
332			delete jack;
333			i--; // account for reindexing in the BList
334		}
335	}
336
337	// add free inputs
338	if (ref->kind() & B_BUFFER_CONSUMER)
339	{
340		vector<media_input> freeInputs;
341		ref->getFreeInputs(freeInputs);
342		for (uint32 i = 0; i < freeInputs.size(); i++)
343		{
344			MediaJack *jack;
345			AddItem(jack = new MediaJack(freeInputs[i]));
346		}
347	}
348
349	// add free outputs
350	if (ref->kind() & B_BUFFER_PRODUCER)
351	{
352		vector<media_output> freeOutputs;
353		ref->getFreeOutputs(freeOutputs);
354		for (uint32 i = 0; i < freeOutputs.size(); i++)
355		{
356			MediaJack *jack;
357			AddItem(jack = new MediaJack(freeOutputs[i]));
358		}
359	}
360
361	// the supported media types might have changed -> this could
362	// require changing the icon
363	_updateIcon(dynamic_cast<MediaRoutingView *>(view())->getLayout());
364}
365
366void MediaNodePanel::arrangeIOJacks()
367{
368	D_METHOD(("MediaNodePanel::arrangeIOJacks()\n"));
369	SortItems(DiagramItem::M_ENDPOINT, &compareTypeAndID);
370
371	switch (dynamic_cast<MediaRoutingView *>(view())->getLayout())
372	{
373		case MediaRoutingView::M_ICON_VIEW:
374		{
375			BRegion updateRegion;
376			float align = 1.0;
377			view()->GetItemAlignment(0, &align);
378
379			// adjust this panel's size
380			int32 numInputs = 0, numOutputs = 0;
381			for (uint32 i = 0; i < CountItems(); i++)
382			{
383				MediaJack *jack = dynamic_cast<MediaJack *>(ItemAt(i));
384				if (jack)
385				{
386					if (jack->isInput())
387					{
388						numInputs++;
389					}
390					if (jack->isOutput())
391					{
392						numOutputs++;
393					}
394				}
395			}
396			float minHeight = MediaJack::M_DEFAULT_HEIGHT + MediaJack::M_DEFAULT_GAP;
397			minHeight *= numInputs > numOutputs ? numInputs : numOutputs;
398			minHeight += m_labelRect.Height();
399			minHeight += 2 * MediaJack::M_DEFAULT_GAP;
400			minHeight = ((int)minHeight / (int)align) * align + align;
401			if ((Frame().Height() < minHeight)
402			 || ((Frame().Height() > minHeight)
403			 && (minHeight >= MediaNodePanel::M_DEFAULT_HEIGHT)))
404			{
405				updateRegion.Include(Frame());
406				resizeTo(Frame().Width(), minHeight);
407				updateRegion.Include(Frame());
408				_prepareLabel();
409			}
410
411			// adjust the placement of the jacks
412			BRect r = m_bodyRect;
413			r.bottom -= M_BODY_V_MARGIN;
414			float inputOffset = 0.0, outputOffset = 0.0;
415			float center = Frame().top + r.top + (r.Height() / 2.0);
416			center += MediaJack::M_DEFAULT_GAP - (MediaJack::M_DEFAULT_HEIGHT / 2.0);
417			center = ((int)center / (int)align) * align;
418			if (numInputs)
419			{
420				if (numInputs % 2) // odd number of inputs
421				{
422					inputOffset = center - (numInputs / 2) * (MediaJack::M_DEFAULT_HEIGHT + MediaJack::M_DEFAULT_GAP);
423				}
424				else // even number of inputs
425				{
426					inputOffset = center - ((numInputs + 1) / 2) * (MediaJack::M_DEFAULT_HEIGHT + MediaJack::M_DEFAULT_GAP);
427				}
428			}
429			if (numOutputs)
430			{
431				if (numOutputs % 2) // odd number of outputs
432				{
433					outputOffset = center - (numOutputs / 2) * (MediaJack::M_DEFAULT_HEIGHT + MediaJack::M_DEFAULT_GAP);
434				}
435				else // even number of outputs
436				{
437					outputOffset = center - ((numOutputs + 1) / 2) * (MediaJack::M_DEFAULT_HEIGHT + MediaJack::M_DEFAULT_GAP);
438				}
439			}
440			for (uint32 i = 0; i < CountItems(); i++)
441			{
442				MediaJack *jack = dynamic_cast<MediaJack *>(ItemAt(i));
443				if (jack)
444				{
445					if (jack->isInput())
446					{
447						jack->setPosition(inputOffset, Frame().left, Frame().right, &updateRegion);
448						inputOffset += jack->Frame().Height() + MediaJack::M_DEFAULT_GAP;
449					}
450					if (jack->isOutput())
451					{
452						jack->setPosition(outputOffset, Frame().left, Frame().right, &updateRegion);
453						outputOffset += jack->Frame().Height() + MediaJack::M_DEFAULT_GAP;
454					}
455				}
456			}
457			for (int32 i = 0; i < updateRegion.CountRects(); i++)
458			{
459				view()->Invalidate(updateRegion.RectAt(i));
460			}
461			break;
462		}
463		case MediaRoutingView::M_MINI_ICON_VIEW:
464		{
465			BRegion updateRegion;
466			float align = 1.0;
467			view()->GetItemAlignment(&align, 0);
468
469			// adjust this panel's size
470			int32 numInputs = 0, numOutputs = 0;
471			for (uint32 i = 0; i < CountItems(); i++)
472			{
473				MediaJack *jack = dynamic_cast<MediaJack *>(ItemAt(i));
474				if (jack)
475				{
476					if (jack->isInput())
477					{
478						numInputs++;
479					}
480					if (jack->isOutput())
481					{
482						numOutputs++;
483					}
484				}
485			}
486			float minWidth = MediaJack::M_DEFAULT_WIDTH + MediaJack::M_DEFAULT_GAP;
487			minWidth *= numInputs > numOutputs ? numInputs : numOutputs;
488			minWidth += m_bodyRect.Width();
489			minWidth += 2 * MediaJack::M_DEFAULT_GAP;
490			minWidth = ((int)minWidth / (int)align) * align + align;
491			if ((Frame().Width() < minWidth)
492			 || ((Frame().Width() > minWidth)
493			 && (minWidth >= MediaNodePanel::M_DEFAULT_WIDTH)))
494			{
495				updateRegion.Include(Frame());
496				resizeTo(minWidth, Frame().Height());
497				updateRegion.Include(Frame());
498				_prepareLabel();
499			}
500			// adjust the placement of the jacks
501			float inputOffset = 0.0, outputOffset = 0.0;
502			float center = Frame().left + m_labelRect.left + (m_labelRect.Width() / 2.0);
503			center += MediaJack::M_DEFAULT_GAP - (MediaJack::M_DEFAULT_WIDTH / 2.0);
504			center = ((int)center / (int)align) * align;
505			if (numInputs)
506			{
507				if (numInputs % 2) // odd number of inputs
508				{
509					inputOffset = center - (numInputs / 2) * (MediaJack::M_DEFAULT_WIDTH + MediaJack::M_DEFAULT_GAP);
510				}
511				else // even number of inputs
512				{
513					inputOffset = center - ((numInputs + 1) / 2) * (MediaJack::M_DEFAULT_WIDTH + MediaJack::M_DEFAULT_GAP);
514				}
515			}
516			if (numOutputs)
517			{
518				if (numOutputs % 2) // odd number of outputs
519				{
520					outputOffset = center - (numOutputs / 2) * (MediaJack::M_DEFAULT_WIDTH + MediaJack::M_DEFAULT_GAP);
521				}
522				else // even number of outputs
523				{
524					outputOffset = center - ((numOutputs + 1) / 2) * (MediaJack::M_DEFAULT_WIDTH + MediaJack::M_DEFAULT_GAP);
525				}
526			}
527			for (uint32 i = 0; i < CountItems(); i++)
528			{
529				MediaJack *jack = dynamic_cast<MediaJack *>(ItemAt(i));
530				if (jack)
531				{
532					if (jack->isInput())
533					{
534						jack->setPosition(inputOffset, Frame().top, Frame().bottom, &updateRegion);
535						inputOffset += jack->Frame().Width() + MediaJack::M_DEFAULT_GAP;
536					}
537					if (jack->isOutput())
538					{
539						jack->setPosition(outputOffset, Frame().top, Frame().bottom, &updateRegion);
540						outputOffset += jack->Frame().Width() + MediaJack::M_DEFAULT_GAP;
541					}
542				}
543			}
544			for (int32 i = 0; i < updateRegion.CountRects(); i++)
545			{
546				view()->Invalidate(updateRegion.RectAt(i));
547			}
548			break;
549		}
550	}
551	_updateBitmap();
552}
553
554void MediaNodePanel::showContextMenu(
555	BPoint point)
556{
557	D_METHOD(("MediaNodePanel::showContextMenu()\n"));
558
559	BPopUpMenu *menu = new BPopUpMenu("MediaNodePanel PopUp", false, false, B_ITEMS_IN_COLUMN);
560	menu->SetFont(be_plain_font);
561
562	BMenuItem *item;
563	BMessage *message;
564
565	// add the "Tweak Parameters" item
566	message = new BMessage(MediaRoutingView::M_NODE_TWEAK_PARAMETERS);
567	menu->AddItem(item = new BMenuItem("Tweak parameters", message, 'P'));
568	if (!(ref->kind() & B_CONTROLLABLE))
569	{
570		item->SetEnabled(false);
571	}
572
573	message = new BMessage(InfoWindowManager::M_INFO_WINDOW_REQUESTED);
574	message->AddInt32("nodeID", ref->id());
575	menu->AddItem(new BMenuItem("Get info", message, 'I'));
576	menu->AddSeparatorItem();
577
578	menu->AddItem(item = new BMenuItem("Release", new BMessage(MediaRoutingView::M_DELETE_SELECTION), 'T'));
579	if (!ref->isInternal())
580	{
581		item->SetEnabled(false);
582	}
583	menu->AddSeparatorItem();
584
585	// add the "Cycle" item
586	message = new BMessage(MediaRoutingView::M_NODE_CHANGE_CYCLING);
587	message->AddBool("cycle", !ref->isCycling());
588	menu->AddItem(item = new BMenuItem("Cycle", message));
589	item->SetMarked(ref->isCycling());
590	if (ref->flags() & NodeRef::NO_SEEK)
591	{
592		item->SetEnabled(false);
593	}
594
595	// add the "Run Mode" sub menu
596	BMenu *subMenu = new BMenu("Run mode");
597	subMenu->SetFont(be_plain_font);
598	for (uint32 runMode = 1; runMode <= BMediaNode::B_RECORDING; runMode++)
599	{
600		BString itemName = MediaString::getStringFor(static_cast<BMediaNode::run_mode>
601													 (runMode));
602		message = new BMessage(MediaRoutingView::M_NODE_CHANGE_RUN_MODE);
603		message->AddInt32("run_mode", runMode);
604		subMenu->AddItem(item = new BMenuItem(itemName.String(), message));
605		if (ref->runMode() == runMode)
606		{
607			item->SetMarked(true);
608		}
609		else if ((ref->runMode() == 0)
610			  && (ref->group()) && (ref->group()->runMode() == BMediaNode::run_mode(runMode)))
611		{
612			item->SetMarked(true);
613		}
614	}
615	subMenu->AddSeparatorItem();
616	message = new BMessage(MediaRoutingView::M_NODE_CHANGE_RUN_MODE);
617	message->AddInt32("run_mode", 0);
618	subMenu->AddItem(item = new BMenuItem("(same as group)", message));
619	if (ref->group() == 0)
620	{
621		item->SetEnabled(false);
622	}
623	else if ((ref->runMode() < 1) && (ref->group()->runMode() > 0))
624	{
625		item->SetMarked(true);
626	}
627	menu->AddItem(subMenu);
628	subMenu->SetTargetForItems(view());
629
630	// [c.lenz 24dec99] hide rarely used commands in a 'Advanced' submenu
631	subMenu = new BMenu("Advanced");
632	subMenu->SetFont(be_plain_font);
633	// [e.moon 5dec99] ad-hoc timesource support
634	if(ref->kind() & B_TIME_SOURCE) {
635		message = new BMessage(MediaRoutingView::M_NODE_START_TIME_SOURCE);
636		message->AddInt32("nodeID", ref->id());
637		subMenu->AddItem(new BMenuItem(
638			"Start time source",
639			message));
640		message = new BMessage(MediaRoutingView::M_NODE_START_TIME_SOURCE);
641		message->AddInt32("nodeID", ref->id());
642		subMenu->AddItem(new BMenuItem(
643			"Stop time source",
644			message));
645	}
646	// [c.lenz 24dec99] support for BControllable::StartControlPanel()
647	if(ref->kind() & B_CONTROLLABLE) {
648		if (subMenu->CountItems() > 0)
649			subMenu->AddSeparatorItem();
650		message = new BMessage(MediaRoutingView::M_NODE_START_CONTROL_PANEL);
651		subMenu->AddItem(new BMenuItem("Start Control Panel", message,
652									   'P', B_COMMAND_KEY | B_SHIFT_KEY));
653	}
654	// [em 1feb00] group tweaks
655	if(ref->group())
656	{
657		message = new BMessage(MediaRoutingView::M_GROUP_SET_LOCKED);
658		message->AddInt32("groupID", ref->group()->id());
659		bool isLocked = (ref->group()->groupFlags() & NodeGroup::GROUP_LOCKED);
660		message->AddBool("locked", !isLocked);
661		if (subMenu->CountItems() > 0)
662			subMenu->AddSeparatorItem();
663		subMenu->AddItem(
664			new BMenuItem(
665				isLocked ? "Unlock group" : "Lock group", message));
666	}
667
668	if (subMenu->CountItems() > 0)
669	{
670		menu->AddItem(subMenu);
671		subMenu->SetTargetForItems(view());
672	}
673
674	menu->SetTargetForItems(view());
675	view()->ConvertToScreen(&point);
676	point -= BPoint(1.0, 1.0);
677	menu->Go(point, true, true, true);
678}
679
680// ---------------------------------------------------------------- //
681// BHandler impl
682// ---------------------------------------------------------------- //
683
684void MediaNodePanel::MessageReceived(
685	BMessage *message)
686{
687	D_METHOD(("MediaNodePanel::MessageReceived()\n"));
688	switch (message->what)
689	{
690		case NodeRef::M_INPUTS_CHANGED:
691		{
692			D_MESSAGE(("MediaNodePanel::MessageReceived(NodeRef::M_INPUTS_CHANGED)\n"));
693			_updateIcon(dynamic_cast<MediaRoutingView *>(view())->getLayout());
694			break;
695		}
696		case NodeRef::M_OUTPUTS_CHANGED:
697		{
698			D_MESSAGE(("MediaNodePanel::MessageReceived(NodeRef::M_OUTPUTS_CHANGED)\n"));
699			_updateIcon(dynamic_cast<MediaRoutingView *>(view())->getLayout());
700			break;
701		}
702		default:
703		{
704			BHandler::MessageReceived(message);
705			break;
706		}
707	}
708}
709
710// -------------------------------------------------------- //
711// *** IStateArchivable
712// -------------------------------------------------------- //
713
714status_t MediaNodePanel::importState(
715	const BMessage*						archive) {
716
717	BPoint iconPos(s_invalidPosition);
718	BPoint miniIconPos(s_invalidPosition);
719
720	MediaRoutingView* v = dynamic_cast<MediaRoutingView*>(view());
721	ASSERT(v);
722	MediaRoutingView::layout_t layoutMode = v->getLayout();
723	archive->FindPoint("iconPos", &iconPos);
724	archive->FindPoint("miniIconPos", &miniIconPos);
725
726	switch(layoutMode) {
727		case MediaRoutingView::M_ICON_VIEW:
728			if(iconPos != s_invalidPosition)
729				moveTo(iconPos);
730			m_alternatePosition = miniIconPos;
731			break;
732
733		case MediaRoutingView::M_MINI_ICON_VIEW:
734			if(miniIconPos != s_invalidPosition)
735				moveTo(miniIconPos);
736			m_alternatePosition = iconPos;
737			break;
738	}
739
740	return B_OK;
741}
742
743status_t MediaNodePanel::exportState(
744	BMessage*									archive) const {
745
746	BPoint iconPos, miniIconPos;
747
748	MediaRoutingView* v = dynamic_cast<MediaRoutingView*>(view());
749	ASSERT(v);
750	MediaRoutingView::layout_t layoutMode = v->getLayout();
751	switch(layoutMode) {
752		case MediaRoutingView::M_ICON_VIEW:
753			iconPos = Frame().LeftTop();
754			miniIconPos = m_alternatePosition;
755			break;
756
757		case MediaRoutingView::M_MINI_ICON_VIEW:
758			miniIconPos = Frame().LeftTop();
759			iconPos = m_alternatePosition;
760			break;
761	}
762
763	if(iconPos != s_invalidPosition)
764		archive->AddPoint("iconPos", iconPos);
765	if(miniIconPos != s_invalidPosition)
766		archive->AddPoint("miniIconPos", miniIconPos);
767
768	// determine if I'm a 'system' node
769	port_info portInfo;
770	app_info appInfo;
771
772	if ((get_port_info(ref->node().port, &portInfo) == B_OK)
773		&& (be_roster->GetRunningAppInfo(portInfo.team, &appInfo) == B_OK)) {
774		BEntry appEntry(&appInfo.ref);
775		char appName[B_FILE_NAME_LENGTH];
776		if(
777			appEntry.InitCheck() == B_OK &&
778			appEntry.GetName(appName) == B_OK &&
779			(!strcmp(appName, "media_addon_server") ||
780			 !strcmp(appName, "audio_server"))) {
781
782			archive->AddBool("sysOwned", true);
783		}
784	}
785
786	return B_OK;
787}
788
789// ---------------------------------------------------------------- //
790// *** internal operations
791// ---------------------------------------------------------------- //
792
793void MediaNodePanel::_prepareLabel()
794{
795	// find out if its a file node first
796	if (ref->kind() & B_FILE_INTERFACE)
797	{
798		entry_ref nodeFile;
799		status_t error = BMediaRoster::Roster()->GetRefFor(ref->node(),	&nodeFile);
800		if (error)
801		{
802			m_fullLabel = ref->name();
803			m_fullLabel += " (no file)";
804		}
805		else
806		{
807			BEntry entry(&nodeFile);
808			char fileName[B_FILE_NAME_LENGTH];
809			entry.GetName(fileName);
810			m_fullLabel = fileName;
811		}
812	}
813	else
814	{
815		m_fullLabel = ref->name();
816	}
817
818	int32 layout = dynamic_cast<MediaRoutingView *>(view())->getLayout();
819
820	// Construct labelRect
821	font_height fh;
822	be_plain_font->GetHeight(&fh);
823	switch (layout)
824	{
825		case MediaRoutingView::M_ICON_VIEW:
826		{
827			m_labelRect = Frame();
828			m_labelRect.OffsetTo(0.0, 0.0);
829			m_labelRect.bottom = 2 * M_LABEL_V_MARGIN + (fh.ascent + fh.descent + fh.leading) + 1.0;
830			break;
831		}
832		case MediaRoutingView::M_MINI_ICON_VIEW:
833		{
834			m_labelRect = Frame();
835			m_labelRect.OffsetTo(0.0, 0.0);
836			m_labelRect.left = M_BODY_H_MARGIN + B_MINI_ICON;
837			m_labelRect.top += MediaJack::M_DEFAULT_HEIGHT;
838			m_labelRect.bottom -= MediaJack::M_DEFAULT_HEIGHT;
839			break;
840		}
841	}
842
843	// truncate the label to fit in the panel
844	float maxWidth = m_labelRect.Width() - (2.0 * M_LABEL_H_MARGIN) - 2.0;
845	if (be_plain_font->StringWidth(m_fullLabel.String()) > maxWidth)
846	{
847		char *truncatedLabel[1];
848		truncatedLabel[0] = new char[B_MEDIA_NAME_LENGTH];
849		const char *originalLabel[1];
850		originalLabel[0] = new char[B_MEDIA_NAME_LENGTH];
851		m_fullLabel.CopyInto(const_cast<char *>(originalLabel[0]), 0, B_MEDIA_NAME_LENGTH);
852		be_plain_font->GetTruncatedStrings(originalLabel, 1, B_TRUNCATE_END, maxWidth, (char **) truncatedLabel);
853		m_label = truncatedLabel[0];
854		m_labelTruncated = true;
855		delete [] originalLabel[0];
856		delete [] truncatedLabel[0];
857	}
858	else
859	{
860		m_label = m_fullLabel;
861		m_labelTruncated = false;
862	}
863
864	// Construct labelOffset
865	float fw = be_plain_font->StringWidth(m_label.String());
866	m_labelOffset.x = m_labelRect.left + m_labelRect.Width() / 2.0 - fw / 2.0;
867	m_labelOffset.y = m_labelRect.bottom - M_LABEL_V_MARGIN - fh.descent - (fh.leading / 2.0) - 1.0;
868
869	// Construct bodyRect
870	switch (layout)
871	{
872		case MediaRoutingView::M_ICON_VIEW:
873		{
874			m_bodyRect = Frame();
875			m_bodyRect.OffsetTo(0.0, 0.0);
876			m_bodyRect.top = m_labelRect.bottom;
877			break;
878		}
879		case MediaRoutingView::M_MINI_ICON_VIEW:
880		{
881			m_bodyRect = Frame();
882			m_bodyRect.OffsetTo(0.0, 0.0);
883			m_bodyRect.right = m_labelRect.left;
884			break;
885		}
886	}
887}
888
889void MediaNodePanel::_updateBitmap()
890{
891	if (m_bitmap)
892	{
893		delete m_bitmap;
894	}
895	BBitmap *tempBitmap = new BBitmap(Frame().OffsetToCopy(0.0, 0.0), B_CMAP8, true);
896	tempBitmap->Lock();
897	{
898		BView *tempView = new BView(tempBitmap->Bounds(), "", B_FOLLOW_NONE, 0);
899		tempBitmap->AddChild(tempView);
900		tempView->SetOrigin(0.0, 0.0);
901
902		int32 layout = dynamic_cast<MediaRoutingView *>(view())->getLayout();
903		_drawInto(tempView, tempView->Bounds(), layout);
904
905		tempView->Sync();
906		tempBitmap->RemoveChild(tempView);
907		delete tempView;
908	}
909	tempBitmap->Unlock();
910	m_bitmap = new BBitmap(tempBitmap);
911	delete tempBitmap;
912}
913
914void MediaNodePanel::_drawInto(
915	BView *target,
916	BRect targetRect,
917	int32 layout)
918{
919	switch (layout)
920	{
921		case MediaRoutingView::M_ICON_VIEW:
922		{
923			BRect r;
924			BPoint p;
925
926			// Draw borders
927			r = targetRect;
928			target->BeginLineArray(16);
929				target->AddLine(r.LeftTop(), r.RightTop(), M_DARK_GRAY_COLOR);
930				target->AddLine(r.RightTop(), r.RightBottom(), M_DARK_GRAY_COLOR);
931				target->AddLine(r.RightBottom(), r.LeftBottom(), M_DARK_GRAY_COLOR);
932				target->AddLine(r.LeftBottom(), r.LeftTop(), M_DARK_GRAY_COLOR);
933				r.InsetBy(1.0, 1.0);
934				target->AddLine(r.LeftTop(), r.RightTop(), M_LIGHT_GRAY_COLOR);
935				target->AddLine(r.RightTop(), r.RightBottom(), M_MED_GRAY_COLOR);
936				target->AddLine(r.RightBottom(), r.LeftBottom(), M_MED_GRAY_COLOR);
937				target->AddLine(r.LeftBottom(), r.LeftTop(), M_LIGHT_GRAY_COLOR);
938			target->EndLineArray();
939
940			// Fill background
941			r.InsetBy(1.0, 1.0);
942			target->SetLowColor(M_GRAY_COLOR);
943			target->FillRect(r, B_SOLID_LOW);
944
945			// Draw icon
946			if (m_icon)
947			{
948				p.x = m_bodyRect.left + m_bodyRect.Width() / 2.0 - B_LARGE_ICON / 2.0;
949				p.y = m_labelRect.bottom + m_bodyRect.Height() / 2.0 - B_LARGE_ICON / 2.0;
950				if (isSelected())
951				{
952					target->SetDrawingMode(B_OP_INVERT);
953					target->DrawBitmapAsync(m_icon, p);
954					target->SetDrawingMode(B_OP_ALPHA);
955					target->SetHighColor(0, 0, 0, 180);
956					target->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
957					target->DrawBitmapAsync(m_icon, p);
958					target->SetDrawingMode(B_OP_OVER);
959				}
960				else
961				{
962					target->SetDrawingMode(B_OP_OVER);
963					target->DrawBitmapAsync(m_icon, p);
964				}
965			}
966
967			// Draw label
968			if (isSelected())
969			{
970				r = m_labelRect;
971				r.InsetBy(M_LABEL_H_MARGIN, M_LABEL_V_MARGIN);
972				target->BeginLineArray(4);
973					target->AddLine(r.LeftTop(), r.RightTop(), M_LIGHT_BLUE_COLOR);
974					target->AddLine(r.RightTop(), r.RightBottom(), M_LIGHT_BLUE_COLOR);
975					target->AddLine(r.RightBottom(), r.LeftBottom(), M_LIGHT_BLUE_COLOR);
976					target->AddLine(r.LeftBottom(), r.LeftTop(), M_LIGHT_BLUE_COLOR);
977				target->EndLineArray();
978				r.InsetBy(1.0, 1.0);
979				target->SetHighColor(M_DARK_BLUE_COLOR);
980				target->FillRect(r, B_SOLID_HIGH);
981			}
982			target->SetDrawingMode(B_OP_OVER);
983			target->SetHighColor(isSelected() ? M_WHITE_COLOR : M_BLACK_COLOR);
984			target->DrawString(m_label.String(), m_labelOffset);
985			break;
986		}
987		case MediaRoutingView::M_MINI_ICON_VIEW:
988		{
989			BRect r;
990			BPoint p;
991
992			// Draw borders
993			r = targetRect;
994			target->BeginLineArray(16);
995				target->AddLine(r.LeftTop(), r.RightTop(), M_DARK_GRAY_COLOR);
996				target->AddLine(r.RightTop(), r.RightBottom(), M_DARK_GRAY_COLOR);
997				target->AddLine(r.RightBottom(), r.LeftBottom(), M_DARK_GRAY_COLOR);
998				target->AddLine(r.LeftBottom(), r.LeftTop(), M_DARK_GRAY_COLOR);
999				r.InsetBy(1.0, 1.0);
1000				target->AddLine(r.LeftTop(), r.RightTop(), M_LIGHT_GRAY_COLOR);
1001				target->AddLine(r.RightTop(), r.RightBottom(), M_MED_GRAY_COLOR);
1002				target->AddLine(r.RightBottom(), r.LeftBottom(), M_MED_GRAY_COLOR);
1003				target->AddLine(r.LeftBottom(), r.LeftTop(), M_LIGHT_GRAY_COLOR);
1004			target->EndLineArray();
1005
1006			// Fill background
1007			r.InsetBy(1.0, 1.0);
1008			target->SetLowColor(M_GRAY_COLOR);
1009			target->FillRect(r, B_SOLID_LOW);
1010
1011			// Draw icon
1012			if (m_icon)
1013			{
1014				p.x = m_bodyRect.left + M_BODY_H_MARGIN;
1015				p.y = m_bodyRect.top + (m_bodyRect.Height() / 2.0) - (B_MINI_ICON / 2.0);
1016				if (isSelected())
1017				{
1018					target->SetDrawingMode(B_OP_INVERT);
1019					target->DrawBitmapAsync(m_icon, p);
1020					target->SetDrawingMode(B_OP_ALPHA);
1021					target->SetHighColor(0, 0, 0, 180);
1022					target->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
1023					target->DrawBitmapAsync(m_icon, p);
1024					target->SetDrawingMode(B_OP_OVER);
1025				}
1026				else
1027				{
1028					target->SetDrawingMode(B_OP_OVER);
1029					target->DrawBitmapAsync(m_icon, p);
1030				}
1031			}
1032
1033			// Draw label
1034			if (isSelected())
1035			{
1036				r = m_labelRect;
1037				r.InsetBy(M_LABEL_H_MARGIN, M_LABEL_V_MARGIN);
1038				target->BeginLineArray(4);
1039					target->AddLine(r.LeftTop(), r.RightTop(), M_LIGHT_BLUE_COLOR);
1040					target->AddLine(r.RightTop(), r.RightBottom(), M_LIGHT_BLUE_COLOR);
1041					target->AddLine(r.RightBottom(), r.LeftBottom(), M_LIGHT_BLUE_COLOR);
1042					target->AddLine(r.LeftBottom(), r.LeftTop(), M_LIGHT_BLUE_COLOR);
1043				target->EndLineArray();
1044				r.InsetBy(1.0, 1.0);
1045				target->SetHighColor(M_DARK_BLUE_COLOR);
1046				target->FillRect(r, B_SOLID_HIGH);
1047			}
1048			target->SetDrawingMode(B_OP_OVER);
1049			target->SetHighColor(isSelected() ? M_WHITE_COLOR : M_BLACK_COLOR);
1050			target->DrawString(m_label.String(), m_labelOffset);
1051			break;
1052		}
1053	}
1054}
1055
1056void MediaNodePanel::_updateIcon(
1057	int32 layout)
1058{
1059	D_METHOD(("MediaNodePanel::_updateIcon()\n"));
1060
1061	if (m_icon)
1062	{
1063		delete m_icon;
1064		m_icon = 0;
1065	}
1066	RouteAppNodeManager *manager;
1067	manager = dynamic_cast<MediaRoutingView *>(view())->manager;
1068	switch (layout)
1069	{
1070		case MediaRoutingView::M_ICON_VIEW:
1071		{
1072			const MediaIcon *icon = manager->mediaIconFor(ref->id(), B_LARGE_ICON);
1073			m_icon = new BBitmap(dynamic_cast<const BBitmap *>(icon));
1074			break;
1075		}
1076		case MediaRoutingView::M_MINI_ICON_VIEW:
1077		{
1078			const MediaIcon *icon = manager->mediaIconFor(ref->id(), B_MINI_ICON);
1079			m_icon = new BBitmap(dynamic_cast<const BBitmap *>(icon));
1080			break;
1081		}
1082	}
1083}
1084
1085// -------------------------------------------------------- //
1086// *** sorting methods (friend)
1087// -------------------------------------------------------- //
1088
1089int __CORTEX_NAMESPACE__ compareID(
1090	const void *lValue,
1091	const void *rValue)
1092{
1093	int retValue = 0;
1094	const MediaNodePanel *lPanel = *(reinterpret_cast<MediaNodePanel * const*>(reinterpret_cast<void * const*>(lValue)));
1095	const MediaNodePanel *rPanel = *(reinterpret_cast<MediaNodePanel * const*>(reinterpret_cast<void * const*>(rValue)));
1096	if (lPanel && rPanel)
1097	{
1098		if (lPanel->ref->id() < rPanel->ref->id())
1099		{
1100			retValue = -1;
1101		}
1102		else if (lPanel->ref->id() > rPanel->ref->id())
1103		{
1104			retValue = 1;
1105		}
1106	}
1107	return retValue;
1108}
1109
1110// END -- MediaNodePanel.cpp --
1111