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// RouteWindow.cpp
33// e.moon 14may99
34
35#include "RouteApp.h"
36#include "RouteWindow.h"
37#include "MediaRoutingView.h"
38#include "StatusView.h"
39
40#include "DormantNodeWindow.h"
41#include "TransportWindow.h"
42
43#include "RouteAppNodeManager.h"
44#include "NodeGroup.h"
45#include "TipManager.h"
46
47#include <Alert.h>
48#include <Autolock.h>
49#include <Debug.h>
50#include <Font.h>
51#include <MenuBar.h>
52#include <Menu.h>
53#include <MenuItem.h>
54#include <Message.h>
55#include <Messenger.h>
56#include <Roster.h>
57#include <Screen.h>
58#include <ScrollView.h>
59#include <StringView.h>
60
61#include <algorithm>
62
63#include "debug_tools.h"
64#define D_HOOK(x) //PRINT (x)
65#define D_INTERNAL(x) //PRINT (x)
66
67__USE_CORTEX_NAMESPACE
68
69
70const char* const RouteWindow::s_windowName = "Cortex";
71
72const BRect RouteWindow::s_initFrame(100,100,700,550);
73
74const char* const g_aboutText =
75	"Cortex/Route 2.1.2\n\n"
76	"Copyright 1999-2000 Eric Moon\n"
77	"All rights reserved.\n\n"
78	"The Cortex Team:\n\n"
79	"Christopher Lenz: UI\n"
80	"Eric Moon: UI, back-end\n\n"
81	"Thanks to:\nJohn Ashmun\nJon Watte\nDoug Wright\n<your name here>\n\n"
82	"Certain icons used herein are the property of\n"
83	"Be, Inc. and are used by permission.";
84
85
86RouteWindow::~RouteWindow()
87{
88}
89
90
91RouteWindow::RouteWindow(RouteAppNodeManager* manager)
92	:
93	BWindow(s_initFrame, s_windowName, B_DOCUMENT_WINDOW, 0),
94	m_hScrollBar(0),
95	m_vScrollBar(0),
96	m_transportWindow(0),
97	m_dormantNodeWindow(0),
98	m_selectedGroupID(0),
99	m_zoomed(false),
100	m_zooming(false)
101{
102	BRect b = Bounds();
103
104	// initialize the menu bar: add all menus that target this window
105	BMenuBar* pMenuBar = new BMenuBar(b, "menuBar");
106	BMenu* pFileMenu = new BMenu("File");
107	BMenuItem* item = new BMenuItem("Open" B_UTF8_ELLIPSIS,
108		new BMessage(RouteApp::M_SHOW_OPEN_PANEL), 'O');
109	item->SetTarget(be_app);
110	pFileMenu->AddItem(item);
111	pFileMenu->AddItem(new BSeparatorItem());
112	item = new BMenuItem("Save nodes" B_UTF8_ELLIPSIS,
113		new BMessage(RouteApp::M_SHOW_SAVE_PANEL), 'S');
114	item->SetTarget(be_app);
115	pFileMenu->AddItem(item);
116	pFileMenu->AddItem(new BSeparatorItem());
117	pFileMenu->AddItem(new BMenuItem("About Cortex/Route" B_UTF8_ELLIPSIS,
118		new BMessage(B_ABOUT_REQUESTED)));
119	pFileMenu->AddItem(new BSeparatorItem());
120	pFileMenu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED)));
121	pMenuBar->AddItem(pFileMenu);
122	AddChild(pMenuBar);
123
124	// build the routing view
125	BRect rvBounds = b;
126	rvBounds.top = pMenuBar->Frame().bottom+1;
127	rvBounds.right -= B_V_SCROLL_BAR_WIDTH;
128	rvBounds.bottom -= B_H_SCROLL_BAR_HEIGHT;
129	m_routingView = new MediaRoutingView(manager, rvBounds, "routingView");
130
131	BRect hsBounds = rvBounds;
132	hsBounds.left = rvBounds.left + 199;
133	hsBounds.top = hsBounds.bottom + 1;
134	hsBounds.right++;
135	hsBounds.bottom = b.bottom + 1;
136
137	m_hScrollBar = new BScrollBar(hsBounds, "hScrollBar", m_routingView,
138		0, 0, B_HORIZONTAL);
139	AddChild(m_hScrollBar);
140
141	BRect vsBounds = rvBounds;
142	vsBounds.left = vsBounds.right + 1;
143	vsBounds.top--;
144	vsBounds.right = b.right + 1;
145	vsBounds.bottom++;
146
147	m_vScrollBar = new BScrollBar(vsBounds, "vScrollBar", m_routingView,
148		0, 0, B_VERTICAL);
149	AddChild(m_vScrollBar);
150
151	BRect svBounds = rvBounds;
152	svBounds.left -= 1;
153	svBounds.right = hsBounds.left - 1;
154	svBounds.top = svBounds.bottom + 1;
155	svBounds.bottom = b.bottom + 1;
156
157	m_statusView = new StatusView(svBounds, manager, m_hScrollBar);
158	AddChild(m_statusView);
159
160	AddChild(m_routingView);
161
162	float minWidth, maxWidth, minHeight, maxHeight;
163	GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
164	minWidth = m_statusView->Frame().Width() + 6 * B_V_SCROLL_BAR_WIDTH;
165	minHeight = 6 * B_H_SCROLL_BAR_HEIGHT;
166	SetSizeLimits(minWidth, maxWidth, minHeight, maxHeight);
167
168	// construct the Window menu
169	BMenu* windowMenu = new BMenu("Window");
170	m_transportWindowItem = new BMenuItem(
171		"Show transport",
172		new BMessage(M_TOGGLE_TRANSPORT_WINDOW));
173	windowMenu->AddItem(m_transportWindowItem);
174
175	m_dormantNodeWindowItem = new BMenuItem(
176		"Show add-ons",
177		new BMessage(M_TOGGLE_DORMANT_NODE_WINDOW));
178	windowMenu->AddItem(m_dormantNodeWindowItem);
179
180	windowMenu->AddItem(new BSeparatorItem());
181
182	m_pullPalettesItem = new BMenuItem(
183		"Pull palettes",
184		new BMessage(M_TOGGLE_PULLING_PALETTES));
185	windowMenu->AddItem(m_pullPalettesItem);
186
187	pMenuBar->AddItem(windowMenu);
188
189	// create the dormant-nodes palette
190	_toggleDormantNodeWindow();
191
192	// display group inspector
193	_toggleTransportWindow();
194}
195
196
197//	#pragma mark - operations
198
199
200/*!	Enable/disable palette position-locking (when the main
201	window is moved, all palettes follow).
202*/
203bool
204RouteWindow::isPullPalettes() const
205{
206	return m_pullPalettesItem->IsMarked();
207}
208
209
210void
211RouteWindow::setPullPalettes(bool enabled)
212{
213	m_pullPalettesItem->SetMarked(enabled);
214}
215
216
217void
218RouteWindow::constrainToScreen()
219{
220	BScreen screen(this);
221
222	const BRect sr = screen.Frame();
223
224	// [c.lenz 1mar2000] this should be handled by every window
225	// itself. will probably change soon ;-)
226	_constrainToScreen();
227/*	// main window
228	BRect r = Frame();
229	BPoint offset(0.0, 0.0);
230	if(r.left < 0.0)
231		offset.x = -r.left;
232	if(r.top < 0.0)
233		offset.y = -r.top;
234	if(r.left >= (sr.right - 20.0))
235		offset.x -= (r.left - (sr.Width()/2));
236	if(r.top >= (sr.bottom - 20.0))
237		offset.y -= (r.top - (sr.Height()/2));
238	if(offset.x != 0.0 || offset.y != 0.0) {
239		setPullPalettes(false);
240		MoveBy(offset.x, offset.y);
241	}*/
242
243	// transport window
244	BPoint offset = BPoint(0.0, 0.0);
245	BRect r = (m_transportWindow) ?
246			   m_transportWindow->Frame() :
247			   m_transportWindowFrame;
248	if(r.left < 0.0)
249		offset.x = (sr.Width()*.75) - r.left;
250	if(r.top < 0.0)
251		offset.y = (sr.Height()*.25) - r.top;
252	if(r.left >= (sr.right - 20.0))
253		offset.x -= (r.left - (sr.Width()/2));
254	if(r.top >= (sr.bottom - 20.0))
255		offset.y -= (r.top - (sr.Height()/2));
256
257	if(offset.x != 0.0 || offset.y != 0.0) {
258		if(m_transportWindow)
259			m_transportWindow->MoveBy(offset.x, offset.y);
260		else
261			m_transportWindowFrame.OffsetBy(offset.x, offset.y);
262	}
263
264	// addon palette
265	offset = BPoint(0.0, 0.0);
266	r = (m_dormantNodeWindow) ?
267		m_dormantNodeWindow->Frame() :
268		m_dormantNodeWindowFrame;
269	if(r.left < 0.0)
270		offset.x = (sr.Width()*.25) - r.left;
271	if(r.top < 0.0)
272		offset.y = (sr.Height()*.125) - r.top;
273	if(r.left >= (sr.right - 20.0))
274		offset.x -= (r.left - (sr.Width()/2));
275	if(r.top >= (sr.bottom - 20.0))
276		offset.y -= (r.top - (sr.Height()/2));
277
278	if(offset.x != 0.0 || offset.y != 0.0) {
279		if(m_dormantNodeWindow)
280			m_dormantNodeWindow->MoveBy(offset.x, offset.y);
281		else
282			m_dormantNodeWindowFrame.OffsetBy(offset.x, offset.y);
283	}
284
285}
286
287
288//	#pragma mark - BWindow implementation
289
290
291void
292RouteWindow::FrameMoved(BPoint point)
293{
294	// ignore notification if the window isn't yet visible
295	if(IsHidden())
296		return;
297
298	BPoint delta = point - m_lastFramePosition;
299	m_lastFramePosition = point;
300
301
302	if (m_pullPalettesItem->IsMarked())
303		_movePalettesBy(delta.x, delta.y);
304}
305
306
307void
308RouteWindow::FrameResized(float width, float height)
309{
310	D_HOOK(("RouteWindow::FrameResized()\n"));
311
312	if (!m_zooming) {
313		m_zoomed = false;
314	}
315	else {
316		m_zooming = false;
317	}
318}
319
320
321bool
322RouteWindow::QuitRequested()
323{
324	be_app->PostMessage(B_QUIT_REQUESTED);
325	return false; // [e.moon 20oct99] app now quits window
326}
327
328
329void
330RouteWindow::Zoom(BPoint origin, float width, float height)
331{
332	D_HOOK(("RouteWindow::Zoom()\n"));
333
334	m_zooming = true;
335
336	BScreen screen(this);
337	if (!screen.Frame().Contains(Frame())) {
338		m_zoomed = false;
339	}
340
341	if (!m_zoomed) {
342		// resize to the ideal size
343		m_manualSize = Bounds();
344		float width, height;
345		m_routingView->GetPreferredSize(&width, &height);
346		width += B_V_SCROLL_BAR_WIDTH;
347		height += B_H_SCROLL_BAR_HEIGHT;
348		if (KeyMenuBar()) {
349			height += KeyMenuBar()->Frame().Height();
350		}
351		ResizeTo(width, height);
352		_constrainToScreen();
353		m_zoomed = true;
354	}
355	else {
356		// resize to the most recent manual size
357		ResizeTo(m_manualSize.Width(), m_manualSize.Height());
358		m_zoomed = false;
359	}
360}
361
362
363//	#pragma mark - BHandler implemenation
364
365
366void
367RouteWindow::MessageReceived(BMessage* pMsg)
368{
369//	PRINT((
370//		"RouteWindow::MessageReceived()\n"));
371//	pMsg->PrintToStream();
372//
373	switch (pMsg->what) {
374		case B_ABOUT_REQUESTED:
375		{
376			BAlert* alert = new BAlert("About", g_aboutText, "OK");
377			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
378			alert->Go();
379			break;
380		}
381		case MediaRoutingView::M_GROUP_SELECTED:
382			_handleGroupSelected(pMsg);
383			break;
384
385		case MediaRoutingView::M_SHOW_ERROR_MESSAGE:
386			_handleShowErrorMessage(pMsg);
387			break;
388
389		case M_TOGGLE_TRANSPORT_WINDOW:
390			_toggleTransportWindow();
391			break;
392
393		case M_REFRESH_TRANSPORT_SETTINGS:
394			_refreshTransportSettings(pMsg);
395			break;
396
397		case M_TOGGLE_PULLING_PALETTES:
398			_togglePullPalettes();
399			break;
400
401		case M_TOGGLE_DORMANT_NODE_WINDOW:
402			_toggleDormantNodeWindow();
403			break;
404
405		case M_TOGGLE_GROUP_ROLLING:
406			_toggleGroupRolling();
407			break;
408
409		default:
410			_inherited::MessageReceived(pMsg);
411			break;
412	}
413}
414
415
416//	#pragma mark - IStateArchivable
417
418
419status_t
420RouteWindow::importState(const BMessage* archive)
421{
422	status_t err;
423
424	// frame rect
425	BRect r;
426	err = archive->FindRect("frame", &r);
427	if(err == B_OK) {
428		MoveTo(r.LeftTop());
429		ResizeTo(r.Width(), r.Height());
430		m_lastFramePosition = r.LeftTop();
431	}
432
433	// status view width
434	int32 i;
435	err = archive->FindInt32("statusViewWidth", &i);
436	if (err == B_OK) {
437		float diff = i - m_statusView->Bounds().IntegerWidth();
438		m_statusView->ResizeBy(diff, 0.0);
439		m_hScrollBar->ResizeBy(-diff, 0.0);
440		m_hScrollBar->MoveBy(diff, 0.0);
441	}
442
443	// settings
444	bool b;
445	err = archive->FindBool("pullPalettes", &b);
446	if(err == B_OK)
447		m_pullPalettesItem->SetMarked(b);
448
449//	const char* p;
450//	err = archive->FindString("saveDir", &p);
451//	if(err == B_OK) {
452//		m_openPanel.SetPanelDirectory(p);
453//		m_savePanel.SetPanelDirectory(p);
454//	}
455//
456	// dormant-node window
457	err = archive->FindRect("addonPaletteFrame", &r);
458	if (err == B_OK)
459		m_dormantNodeWindowFrame = r;
460	err = archive->FindBool("addonPaletteVisible", &b);
461	if (err == B_OK && (b != (m_dormantNodeWindow != 0))) {
462		_toggleDormantNodeWindow();
463		if(!m_dormantNodeWindow)
464			m_dormantNodeWindowFrame = r;
465	}
466
467	if (m_dormantNodeWindow) {
468		m_dormantNodeWindow->MoveTo(m_dormantNodeWindowFrame.LeftTop());
469		m_dormantNodeWindow->ResizeTo(
470			m_dormantNodeWindowFrame.Width(),
471			m_dormantNodeWindowFrame.Height());
472	}
473
474	// transport window
475	err = archive->FindRect("transportFrame", &r);
476	if (err == B_OK)
477		m_transportWindowFrame = r;
478	err = archive->FindBool("transportVisible", &b);
479	if (err == B_OK && (b != (m_transportWindow != 0))) {
480		_toggleTransportWindow();
481		if (!m_transportWindow)
482			m_transportWindowFrame = r;
483	}
484
485	if (m_transportWindow) {
486		m_transportWindow->MoveTo(m_transportWindowFrame.LeftTop());
487		m_transportWindow->ResizeTo(
488			m_transportWindowFrame.Width(),
489			m_transportWindowFrame.Height());
490	}
491
492	return B_OK;
493}
494
495
496status_t
497RouteWindow::exportState(BMessage* archive) const
498{
499	BRect r = Frame();
500	archive->AddRect("frame", r);
501	archive->AddBool("pullPalettes", m_pullPalettesItem->IsMarked());
502
503	bool b = (m_dormantNodeWindow != 0);
504	r = b ? m_dormantNodeWindow->Frame() : m_dormantNodeWindowFrame;
505	archive->AddRect("addonPaletteFrame", r);
506	archive->AddBool("addonPaletteVisible", b);
507
508	b = (m_transportWindow != 0);
509	r = b ? m_transportWindow->Frame() : m_transportWindowFrame;
510
511	archive->AddRect("transportFrame", r);
512	archive->AddBool("transportVisible", b);
513
514	// [c.lenz 23may00] remember status view width
515	int i = m_statusView->Bounds().IntegerWidth();
516	archive->AddInt32("statusViewWidth", i);
517
518//	entry_ref saveRef;
519//	m_savePanel.GetPanelDirectory(&saveRef);
520//	BEntry saveEntry(&saveRef);
521//	if(saveEntry.InitCheck() == B_OK) {
522//		BPath p;
523//		saveEntry.GetPath(&p);
524//		archive->AddString("saveDir", p.Path());
525//	}
526
527	return B_OK;
528}
529
530
531//	#pragma mark - implementation
532
533
534void
535RouteWindow::_constrainToScreen()
536{
537	D_INTERNAL(("RouteWindow::_constrainToScreen()\n"));
538
539	BScreen screen(this);
540	BRect screenRect = screen.Frame();
541	BRect windowRect = Frame();
542
543	// if the window is outside the screen rect
544	// move it to the default position
545	if (!screenRect.Intersects(windowRect)) {
546		windowRect.OffsetTo(screenRect.LeftTop());
547		MoveTo(windowRect.LeftTop());
548		windowRect = Frame();
549	}
550
551	// if the window is larger than the screen rect
552	// resize it to fit at each side
553	if (!screenRect.Contains(windowRect)) {
554		if (windowRect.left < screenRect.left) {
555			windowRect.left = screenRect.left + 5.0;
556			MoveTo(windowRect.LeftTop());
557			windowRect = Frame();
558		}
559		if (windowRect.top < screenRect.top) {
560			windowRect.top = screenRect.top + 5.0;
561			MoveTo(windowRect.LeftTop());
562			windowRect = Frame();
563		}
564		if (windowRect.right > screenRect.right) {
565			windowRect.right = screenRect.right - 5.0;
566		}
567		if (windowRect.bottom > screenRect.bottom) {
568			windowRect.bottom = screenRect.bottom - 5.0;
569		}
570		ResizeTo(windowRect.Width(), windowRect.Height());
571	}
572}
573
574
575void
576RouteWindow::_toggleTransportWindow()
577{
578	if (m_transportWindow) {
579		m_transportWindowFrame = m_transportWindow->Frame();
580		m_transportWindow->Lock();
581		m_transportWindow->Quit();
582		m_transportWindow = 0;
583		m_transportWindowItem->SetMarked(false);
584	} else {
585		m_transportWindow = new TransportWindow(m_routingView->manager,
586			this, "Transport");
587
588		// ask for a selection update
589		BMessenger(m_routingView).SendMessage(
590			MediaRoutingView::M_BROADCAST_SELECTION);
591
592		// place & display the window
593		if (m_transportWindowFrame.IsValid()) {
594			m_transportWindow->MoveTo(m_transportWindowFrame.LeftTop());
595			m_transportWindow->ResizeTo(m_transportWindowFrame.Width(),
596				m_transportWindowFrame.Height());
597		}
598
599		m_transportWindow->Show();
600		m_transportWindowItem->SetMarked(true);
601	}
602}
603
604
605void
606RouteWindow::_togglePullPalettes()
607{
608	m_pullPalettesItem->SetMarked(!m_pullPalettesItem->IsMarked());
609}
610
611
612void
613RouteWindow::_toggleDormantNodeWindow()
614{
615	if (m_dormantNodeWindow) {
616		m_dormantNodeWindowFrame = m_dormantNodeWindow->Frame();
617		m_dormantNodeWindow->Lock();
618		m_dormantNodeWindow->Quit();
619		m_dormantNodeWindow = 0;
620		m_dormantNodeWindowItem->SetMarked(false);
621	} else {
622		m_dormantNodeWindow = new DormantNodeWindow(this);
623		if (m_dormantNodeWindowFrame.IsValid()) {
624			m_dormantNodeWindow->MoveTo(m_dormantNodeWindowFrame.LeftTop());
625			m_dormantNodeWindow->ResizeTo(m_dormantNodeWindowFrame.Width(),
626				m_dormantNodeWindowFrame.Height());
627		}
628		m_dormantNodeWindow->Show();
629		m_dormantNodeWindowItem->SetMarked(true);
630	}
631}
632
633
634void
635RouteWindow::_handleGroupSelected(BMessage* message)
636{
637	status_t err;
638	uint32 groupID;
639
640	err = message->FindInt32("groupID", (int32*)&groupID);
641	if (err < B_OK) {
642		PRINT((
643			"! RouteWindow::_handleGroupSelected(): no groupID in message!\n"));
644		return;
645	}
646
647	if (!m_transportWindow)
648		return;
649
650	BMessage m(TransportWindow::M_SELECT_GROUP);
651	m.AddInt32("groupID", groupID);
652	BMessenger(m_transportWindow).SendMessage(&m);
653
654	m_selectedGroupID = groupID;
655}
656
657
658void
659RouteWindow::_handleShowErrorMessage(BMessage* message)
660{
661	status_t err;
662	BString text;
663
664	err = message->FindString("text", &text);
665	if (err < B_OK) {
666		PRINT((
667			"! RouteWindow::_handleShowErrorMessage(): no text in message!\n"));
668		return;
669	}
670
671	m_statusView->setErrorMessage(text.String(), message->HasBool("error"));
672}
673
674
675//! Refresh the transport window for the given group, if any
676void
677RouteWindow::_refreshTransportSettings(BMessage* message)
678{
679	status_t err;
680	uint32 groupID;
681
682	err = message->FindInt32("groupID", (int32*)&groupID);
683	if (err < B_OK) {
684		PRINT((
685			"! RouteWindow::_refreshTransportSettings(): no groupID in message!\n"));
686		return;
687	}
688
689	if(m_transportWindow) {
690		// relay the message
691		BMessenger(m_transportWindow).SendMessage(message);
692	}
693}
694
695
696void
697RouteWindow::_closePalettes()
698{
699	BAutolock _l(this);
700
701	if (m_transportWindow) {
702		m_transportWindow->Lock();
703		m_transportWindow->Quit();
704		m_transportWindow = 0;
705	}
706}
707
708
709//!	Move all palette windows by the specified amounts
710void RouteWindow::_movePalettesBy(float xDelta, float yDelta)
711{
712	if (m_transportWindow)
713		m_transportWindow->MoveBy(xDelta, yDelta);
714
715	if (m_dormantNodeWindow)
716		m_dormantNodeWindow->MoveBy(xDelta, yDelta);
717}
718
719
720//!	Toggle group playback
721void
722RouteWindow::_toggleGroupRolling()
723{
724	if (!m_selectedGroupID)
725		return;
726
727	NodeGroup* g;
728	status_t err = m_routingView->manager->findGroup(m_selectedGroupID, &g);
729	if (err < B_OK)
730		return;
731
732	Autolock _l(g);
733	uint32 startAction = (g->runMode() == BMediaNode::B_OFFLINE)
734		? NodeGroup::M_ROLL : NodeGroup::M_START;
735
736	BMessenger m(g);
737	switch (g->transportState()) {
738		case NodeGroup::TRANSPORT_STOPPED:
739			m.SendMessage(startAction);
740			break;
741
742		case NodeGroup::TRANSPORT_RUNNING:
743		case NodeGroup::TRANSPORT_ROLLING:
744			m.SendMessage(NodeGroup::M_STOP);
745			break;
746
747		default:
748			break;
749	}
750}
751