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