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// TransportView.cpp
33
34#include "TransportView.h"
35
36#include "RouteApp.h"
37#include "RouteWindow.h"
38
39#include "RouteAppNodeManager.h"
40#include "NodeGroup.h"
41
42#include "NumericValControl.h"
43#include "TextControlFloater.h"
44
45#include <Button.h>
46#include <Debug.h>
47#include <Font.h>
48#include <Invoker.h>
49#include <StringView.h>
50#include <MediaRoster.h>
51#include <MenuField.h>
52#include <MenuItem.h>
53#include <PopUpMenu.h>
54#include <String.h>
55#include <StringFormat.h>
56#include <TextControl.h>
57
58#include <algorithm>
59#include <functional>
60
61#undef B_CATALOG
62#define B_CATALOG (&sCatalog)
63
64#include <Catalog.h>
65
66#undef B_TRANSLATION_CONTEXT
67#define B_TRANSLATION_CONTEXT "TransportView"
68
69using namespace std;
70
71__USE_CORTEX_NAMESPACE
72
73static BCatalog sCatalog("x-vnd.Cortex.TransportView");
74
75// -------------------------------------------------------- //
76// _GroupInfoView
77// -------------------------------------------------------- //
78
79__BEGIN_CORTEX_NAMESPACE
80class _GroupInfoView :
81	public	BView {
82	typedef	BView _inherited;
83
84public:												// ctor/dtor
85	_GroupInfoView(
86		BRect											frame,
87		TransportView*						parent,
88		const char*								name,
89		uint32										resizeMode =B_FOLLOW_LEFT|B_FOLLOW_TOP,
90		uint32										flags =B_WILL_DRAW|B_FRAME_EVENTS) :
91
92		BView(frame, name, resizeMode, flags),
93		m_parent(parent),
94		m_plainFont(be_plain_font),
95		m_boldFont(be_bold_font) {
96
97		_initViews();
98		_initColors();
99		_updateLayout();
100	}
101
102public:												// BView
103	virtual void FrameResized(
104		float											width,
105		float											height) {
106
107		_inherited::FrameResized(width, height);
108		_updateLayout();
109		Invalidate();
110	}
111
112	virtual void GetPreferredSize(
113		float*										width,
114		float*										height) {
115		font_height fh;
116		m_plainFont.GetHeight(&fh);
117
118		*width = 0.0;
119		*height = (fh.ascent+fh.descent+fh.leading) * 2;
120		*height += 4.0; //+++++
121	}
122
123
124	virtual void Draw(
125		BRect											updateRect) {
126
127		NodeGroup* g = m_parent->m_group;
128		BRect b = Bounds();
129
130		// border
131		rgb_color hi = tint_color(ViewColor(), B_LIGHTEN_2_TINT);
132		rgb_color lo = tint_color(ViewColor(), B_DARKEN_2_TINT);
133		SetHighColor(lo);
134		StrokeLine(
135			b.LeftTop(), b.RightTop());
136		StrokeLine(
137			b.LeftTop(), b.LeftBottom());
138
139		SetHighColor(hi);
140		StrokeLine(
141			b.LeftBottom(), b.RightBottom());
142		StrokeLine(
143			b.RightTop(), b.RightBottom());
144
145		SetHighColor(255,255,255,255);
146
147		// background +++++
148
149		// name
150		BString name = g ? g->name() : B_TRANSLATE("(no group)");
151		// +++++ constrain width
152		SetFont(&m_boldFont);
153		DrawString(name.String(), m_namePosition);
154
155		SetFont(&m_plainFont);
156
157		// node count
158		uint32 count;
159		if (g != NULL)
160			count = g->countNodes();
161		else
162			count = 0;
163
164		BString nodeCount = "";
165		static BStringFormat format(
166			B_TRANSLATE("{0, plural, one{# node} other{# nodes}}"));
167		format.Format(nodeCount, count);
168
169		// +++++ constrain width
170		DrawString(nodeCount.String(), m_nodeCountPosition);
171
172		// status
173		BString status = B_TRANSLATE("No errors.");
174		// +++++ constrain width
175		DrawString(status.String(), m_statusPosition);
176	}
177
178	virtual void MouseDown(
179		BPoint										point) {
180
181		NodeGroup* g = m_parent->m_group;
182		if(!g)
183			return;
184
185		font_height fh;
186		m_boldFont.GetHeight(&fh);
187
188		BRect nameBounds(
189			m_namePosition.x,
190			m_namePosition.y - fh.ascent,
191			m_namePosition.x + m_maxNameWidth,
192			m_namePosition.y + (fh.ascent+fh.leading-4.0));
193		if(nameBounds.Contains(point)) {
194			ConvertToScreen(&nameBounds);
195			nameBounds.OffsetBy(-7.0, -3.0);
196			new TextControlFloater(
197				nameBounds,
198				B_ALIGN_LEFT,
199				&m_boldFont,
200				g->name(),
201				m_parent,
202				new BMessage(TransportView::M_SET_NAME));
203		}
204	}
205
206public:												// implementation
207	void _initViews() {
208		// +++++
209	}
210
211	void _initColors() {
212		// +++++ these colors need to be centrally defined
213		SetViewColor(16, 64, 96, 255);
214		SetLowColor(16, 64, 96, 255);
215		SetHighColor(255,255,255,255);
216	}
217
218	void _updateLayout() {
219		float _edge_pad_x = 3.0;
220		float _edge_pad_y = 1.0;
221
222		BRect b = Bounds();
223		font_height fh;
224		m_plainFont.GetHeight(&fh);
225
226		float realWidth = b.Width() - (_edge_pad_x * 2);
227
228		m_maxNameWidth = realWidth * 0.7;
229		m_maxNodeCountWidth = realWidth - m_maxNameWidth;
230		m_namePosition.x = _edge_pad_x;
231		m_namePosition.y = _edge_pad_x + fh.ascent - 2.0;
232		m_nodeCountPosition = m_namePosition;
233		m_nodeCountPosition.x = m_maxNameWidth;
234
235		m_maxStatusWidth = realWidth;
236		m_statusPosition.x = _edge_pad_x;
237		m_statusPosition.y = b.Height() - (fh.descent + fh.leading + _edge_pad_y);
238	}
239
240private:
241	TransportView*							m_parent;
242
243	BFont												m_plainFont;
244	BFont												m_boldFont;
245
246	BPoint											m_namePosition;
247	float												m_maxNameWidth;
248
249	BPoint											m_nodeCountPosition;
250	float												m_maxNodeCountWidth;
251
252	BPoint											m_statusPosition;
253	float												m_maxStatusWidth;
254};
255__END_CORTEX_NAMESPACE
256
257// -------------------------------------------------------- //
258// *** ctors
259// -------------------------------------------------------- //
260
261TransportView::TransportView(
262	NodeManager*						manager,
263	const char*							name) :
264
265	BView(
266		BRect(),
267		name,
268		B_FOLLOW_ALL_SIDES,
269		B_WILL_DRAW|B_FRAME_EVENTS),
270	m_manager(manager),
271	m_group(0) {
272
273	// initialize
274	_initLayout();
275	_constructControls();
276//	_updateLayout(); deferred until AttachedToWindow(): 24aug99
277	_disableControls();
278
279	SetViewColor(
280		tint_color(
281			ui_color(B_PANEL_BACKGROUND_COLOR),
282			B_LIGHTEN_1_TINT));
283}
284
285// -------------------------------------------------------- //
286// *** BView
287// -------------------------------------------------------- //
288
289void TransportView::AttachedToWindow() {
290	_inherited::AttachedToWindow();
291
292	// finish layout
293	_updateLayout();
294
295	// watch the node manager (for time-source create/delete notification)
296	RouteApp* app = dynamic_cast<RouteApp*>(be_app);
297	ASSERT(app);
298	add_observer(this, app->manager);
299}
300
301void TransportView::AllAttached() {
302	_inherited::AllAttached();
303
304	// set message targets for view-configuation controls
305	for(target_set::iterator it = m_localTargets.begin();
306		it != m_localTargets.end(); ++it) {
307		ASSERT(*it);
308		(*it)->SetTarget(this);
309	}
310}
311
312void TransportView::DetachedFromWindow() {
313	_inherited::DetachedFromWindow();
314
315	RouteApp* app = dynamic_cast<RouteApp*>(be_app);
316	ASSERT(app);
317	remove_observer(this, app->manager);
318}
319
320void TransportView::FrameResized(
321	float										width,
322	float										height) {
323
324	_inherited::FrameResized(width, height);
325//	_updateLayout();
326}
327
328void TransportView::KeyDown(
329	const char*							bytes,
330	int32										count) {
331
332	switch(bytes[0]) {
333		case B_SPACE: {
334			RouteApp* app = dynamic_cast<RouteApp*>(be_app);
335			ASSERT(app);
336			BMessenger(app->routeWindow).SendMessage(
337				RouteWindow::M_TOGGLE_GROUP_ROLLING);
338			break;
339		}
340
341		default:
342			_inherited::KeyDown(bytes, count);
343	}
344}
345
346
347void TransportView::MouseDown(
348	BPoint									where) {
349
350	MakeFocus(true);
351}
352
353
354// -------------------------------------------------------- //
355// *** BHandler
356// -------------------------------------------------------- //
357
358void TransportView::MessageReceived(
359	BMessage*								message) {
360	status_t err;
361	uint32 groupID;
362
363//	PRINT((
364//		"TransportView::MessageReceived()\n"));
365//	message->PrintToStream();
366
367	switch(message->what) {
368
369		case NodeGroup::M_RELEASED:
370			{
371				err = message->FindInt32("groupID", (int32*)&groupID);
372				if(err < B_OK) {
373					PRINT((
374						"* TransportView::MessageReceived(NodeGroup::M_RELEASED)\n"
375						"  no groupID!\n"));
376				}
377				if(!m_group || groupID != m_group->id()) {
378					PRINT((
379						"* TransportView::MessageReceived(NodeGroup::M_RELEASED)\n"
380						"  mismatched groupID.\n"));
381					break;
382				}
383
384				_releaseGroup();
385//
386//				BMessage m(M_REMOVE_OBSERVER);
387//				m.AddMessenger("observer", BMessenger(this));
388//				message->SendReply(&m);
389			}
390			break;
391
392		case NodeGroup::M_OBSERVER_ADDED:
393			err = message->FindInt32("groupID", (int32*)&groupID);
394			if(err < B_OK) {
395				PRINT((
396					"* TransportView::MessageReceived(NodeGroup::M_OBSERVER_ADDED)\n"
397					"  no groupID!\n"));
398				break;
399			}
400			if(!m_group || groupID != m_group->id()) {
401				PRINT((
402					"* TransportView::MessageReceived(NodeGroup::M_OBSERVER_ADDED)\n"
403					"  mismatched groupID; ignoring.\n"));
404				break;
405			}
406
407			_enableControls();
408			break;
409
410		case NodeGroup::M_TRANSPORT_STATE_CHANGED:
411			uint32 groupID;
412			err = message->FindInt32("groupID", (int32*)&groupID);
413			if(err < B_OK) {
414				PRINT((
415					"* TransportView::MessageReceived(NodeGroup::M_TRANSPORT_STATE_CHANGED)\n"
416					"  no groupID!\n"));
417				break;
418			}
419			if(!m_group || groupID != m_group->id()) {
420				PRINT((
421					"* TransportView::MessageReceived(NodeGroup::M_TRANSPORT_STATE_CHANGED)\n"
422					"  mismatched groupID; ignoring.\n"));
423				break;
424			}
425
426			_updateTransportButtons();
427			break;
428
429		case NodeGroup::M_TIME_SOURCE_CHANGED:
430			//_updateTimeSource(); +++++ check group?
431			break;
432
433		case NodeGroup::M_RUN_MODE_CHANGED:
434			//_updateRunMode(); +++++ check group?
435			break;
436
437		// * CONTROL PROCESSING
438
439		case NodeGroup::M_SET_START_POSITION: {
440
441			if(!m_group)
442				break;
443
444			bigtime_t position = _scalePosition(
445				m_regionStartView->value());
446			message->AddInt64("position", position);
447			BMessenger(m_group).SendMessage(message);
448
449			// update start-button duty/label [e.moon 11oct99]
450			if(m_group->transportState() == NodeGroup::TRANSPORT_STOPPED)
451				_updateTransportButtons();
452
453			break;
454		}
455
456		case NodeGroup::M_SET_END_POSITION: {
457
458			if(!m_group)
459				break;
460
461			bigtime_t position = _scalePosition(
462				m_regionEndView->value());
463			message->AddInt64("position", position);
464			BMessenger(m_group).SendMessage(message);
465
466			// update start-button duty/label [e.moon 11oct99]
467			if(m_group->transportState() == NodeGroup::TRANSPORT_STOPPED)
468				_updateTransportButtons();
469
470			break;
471		}
472
473		case M_SET_NAME:
474			{
475				const char* name;
476				err = message->FindString("_value", &name);
477				if(err < B_OK) {
478					PRINT((
479						"! TransportView::MessageReceived(M_SET_NAME): no _value!\n"));
480					break;
481				}
482				if(m_group) {
483					m_group->setName(name);
484					m_infoView->Invalidate();
485				}
486			}
487			break;
488
489		case RouteAppNodeManager::M_TIME_SOURCE_CREATED:
490			_timeSourceCreated(message);
491			break;
492
493		case RouteAppNodeManager::M_TIME_SOURCE_DELETED:
494			_timeSourceDeleted(message);
495			break;
496
497		default:
498			_inherited::MessageReceived(message);
499			break;
500	}
501}
502
503// -------------------------------------------------------- //
504// *** BHandler impl.
505// -------------------------------------------------------- //
506
507void TransportView::_handleSelectGroup(
508	BMessage*								message) {
509
510	uint32 groupID;
511	status_t err = message->FindInt32("groupID", (int32*)&groupID);
512	if(err < B_OK) {
513		PRINT((
514			"* TransportView::_handleSelectGroup(): no groupID\n"));
515		return;
516	}
517
518	if(m_group && groupID != m_group->id())
519		_releaseGroup();
520
521	_selectGroup(groupID);
522
523	Invalidate();
524}
525
526// -------------------------------------------------------- //
527// *** internal operations
528// -------------------------------------------------------- //
529
530// select the given group; initialize controls
531// (if 0, gray out all controls)
532void TransportView::_selectGroup(
533	uint32									groupID) {
534	status_t err;
535
536	if(m_group)
537		_releaseGroup();
538
539	if(!groupID) {
540		_disableControls();
541		return;
542	}
543
544	err = m_manager->findGroup(groupID, &m_group);
545	if(err < B_OK) {
546		PRINT((
547			"* TransportView::_selectGroup(%" B_PRId32 "): findGroup() failed:\n"
548			"  %s\n",
549			groupID,
550			strerror(err)));
551		return;
552	}
553
554	_observeGroup();
555}
556
557void TransportView::_observeGroup() {
558	ASSERT(m_group);
559
560	add_observer(this, m_group);
561}
562
563void TransportView::_releaseGroup() {
564	ASSERT(m_group);
565
566	remove_observer(this, m_group);
567	m_group = 0;
568}
569// -------------------------------------------------------- //
570// *** CONTROLS
571// -------------------------------------------------------- //
572
573const char _run_mode_strings[][20] = {
574	B_TRANSLATE_MARK("Offline"),
575	B_TRANSLATE_MARK("Decrease precision"),
576	B_TRANSLATE_MARK("Increase latency"),
577	B_TRANSLATE_MARK("Drop data"),
578	B_TRANSLATE_MARK("Recording")
579};
580const int _run_modes = 5;
581
582//const char _time_source_strings[][20] = {
583//	"DAC time source",
584//	"System clock"
585//};
586//const int _time_sources = 2;
587
588const char* _region_start_label = B_TRANSLATE("From:");
589const char* _region_end_label = B_TRANSLATE("To:");
590
591void TransportView::_constructControls() {
592
593	BMessage* m;
594
595	// * create and populate, but don't position, the views:
596
597//	// create and populate, but don't position, the views:
598//	m_nameView = new BStringView(
599//		BRect(),
600//		"nameView",
601//		"Group Name",
602//		B_FOLLOW_NONE);
603//	AddChild(m_nameView);
604
605	m_infoView = new _GroupInfoView(
606		BRect(),
607		this,
608		"infoView");
609	AddChild(m_infoView);
610
611	m_runModeView = new BMenuField(
612		BRect(),
613		"runModeView",
614		B_TRANSLATE("Run mode:"),
615		new BPopUpMenu("runModeMenu"));
616	_populateRunModeMenu(
617		m_runModeView->Menu());
618	AddChild(m_runModeView);
619
620	m_timeSourceView = new BMenuField(
621		BRect(),
622		"timeSourceView",
623		B_TRANSLATE("Time source:"),
624		new BPopUpMenu("timeSourceMenu"));
625	_populateTimeSourceMenu(
626		m_timeSourceView->Menu());
627	AddChild(m_timeSourceView);
628
629
630	m_fromLabel = new BStringView(BRect(), 0, B_TRANSLATE("Roll from"));
631	AddChild(m_fromLabel);
632
633
634	m = new BMessage(NodeGroup::M_SET_START_POSITION);
635	m_regionStartView = new NumericValControl(
636		BRect(),
637		"regionStartView",
638		m,
639		2, 4, // * DIGITS
640		false, ValControl::ALIGN_FLUSH_LEFT);
641
642	// redirect view back to self.  this gives me the chance to
643	// augment the message with the position before sending
644	_addLocalTarget(m_regionStartView);
645	AddChild(m_regionStartView);
646
647	m_toLabel = new BStringView(BRect(), 0, B_TRANSLATE("to"));
648	AddChild(m_toLabel);
649
650	m = new BMessage(NodeGroup::M_SET_END_POSITION);
651	m_regionEndView = new NumericValControl(
652		BRect(),
653		"regionEndView",
654		m,
655		2, 4, // * DIGITS
656		false, ValControl::ALIGN_FLUSH_LEFT);
657
658	// redirect view back to self.  this gives me the chance to
659	// augment the message with the position before sending
660	_addLocalTarget(m_regionEndView);
661	AddChild(m_regionEndView);
662
663//	m = new BMessage(NodeGroup::M_SET_START_POSITION);
664//	m_regionStartView = new BTextControl(
665//		BRect(),
666//		"regionStartView",
667//		_region_start_label,
668//		"0",
669//		m);
670//
671//	_addGroupTarget(m_regionStartView);
672////	m_regionStartView->TextView()->SetAlignment(B_ALIGN_RIGHT);
673//
674//	AddChild(m_regionStartView);
675//
676//	m = new BMessage(NodeGroup::M_SET_END_POSITION);
677//	m_regionEndView = new BTextControl(
678//		BRect(),
679//		"regionEndView",
680//		_region_end_label,
681//		"0",
682//		m);
683//
684//	_addGroupTarget(m_regionEndView);
685////	m_regionEndView->TextView()->SetAlignment(B_ALIGN_RIGHT);
686//	AddChild(m_regionEndView);
687
688	m = new BMessage(NodeGroup::M_START);
689	m_startButton = new BButton(
690		BRect(),
691		"startButton",
692		B_TRANSLATE("Start"),
693		m);
694	_addGroupTarget(m_startButton);
695	AddChild(m_startButton);
696
697	m = new BMessage(NodeGroup::M_STOP);
698	m_stopButton = new BButton(
699		BRect(),
700		"stopButton",
701		B_TRANSLATE("Stop"),
702		m);
703	_addGroupTarget(m_stopButton);
704	AddChild(m_stopButton);
705
706	m = new BMessage(NodeGroup::M_PREROLL);
707	m_prerollButton = new BButton(
708		BRect(),
709		"prerollButton",
710		B_TRANSLATE("Preroll"),
711		m);
712	_addGroupTarget(m_prerollButton);
713	AddChild(m_prerollButton);
714}
715
716// convert a position control's value to bigtime_t
717// [e.moon 11oct99]
718bigtime_t TransportView::_scalePosition(
719	double									value) {
720
721	return bigtime_t(value * 1000000.0);
722}
723
724void TransportView::_populateRunModeMenu(
725	BMenu*									menu) {
726
727	BMessage* m;
728	for(int n = 0; n < _run_modes; ++n) {
729		m = new BMessage(NodeGroup::M_SET_RUN_MODE);
730		m->AddInt32("runMode", n+1);
731
732		BMenuItem* i = new BMenuItem(
733			B_TRANSLATE(_run_mode_strings[n]), m);
734		menu->AddItem(i);
735		_addGroupTarget(i);
736	}
737}
738
739void TransportView::_populateTimeSourceMenu(
740	BMenu*									menu) {
741
742	// find the standard time sources
743	status_t err;
744	media_node dacTimeSource, systemTimeSource;
745	BMessage* m;
746	BMenuItem* i;
747	err = m_manager->roster->GetTimeSource(&dacTimeSource);
748	if(err == B_OK) {
749		m = new BMessage(NodeGroup::M_SET_TIME_SOURCE);
750		m->AddData(
751			"timeSourceNode",
752			B_RAW_TYPE,
753			&dacTimeSource,
754			sizeof(media_node));
755		i = new BMenuItem(
756			B_TRANSLATE("DAC time source"),
757			m);
758		menu->AddItem(i);
759		_addGroupTarget(i);
760	}
761
762	err = m_manager->roster->GetSystemTimeSource(&systemTimeSource);
763	if(err == B_OK) {
764		m = new BMessage(NodeGroup::M_SET_TIME_SOURCE);
765		m->AddData(
766			"timeSourceNode",
767			B_RAW_TYPE,
768			&systemTimeSource,
769			sizeof(media_node));
770		i = new BMenuItem(
771			B_TRANSLATE("System clock"),
772			m);
773		menu->AddItem(i);
774		_addGroupTarget(i);
775	}
776
777	// find other time source nodes
778
779	Autolock _l(m_manager);
780	void* cookie = 0;
781	NodeRef* r;
782	char nameBuffer[B_MEDIA_NAME_LENGTH+16];
783	bool needSeparator = (menu->CountItems() > 0);
784	while(m_manager->getNextRef(&r, &cookie) == B_OK) {
785		if(!(r->kind() & B_TIME_SOURCE))
786			continue;
787		if(r->id() == dacTimeSource.node)
788			continue;
789		if(r->id() == systemTimeSource.node)
790			continue;
791
792		if(needSeparator) {
793			needSeparator = false;
794			menu->AddItem(new BSeparatorItem());
795		}
796
797		m = new BMessage(NodeGroup::M_SET_TIME_SOURCE);
798		m->AddData(
799			"timeSourceNode",
800			B_RAW_TYPE,
801			&r->node(),
802			sizeof(media_node));
803		sprintf(nameBuffer, "%s: %" B_PRId32,
804			r->name(),
805			r->id());
806		i = new BMenuItem(
807			nameBuffer,
808			m);
809		menu->AddItem(i);
810		_addGroupTarget(i);
811	}
812
813//	BMessage* m;
814//	for(int n = 0; n < _time_sources; ++n) {
815//		m = new BMessage(NodeGroup::M_SET_TIME_SOURCE);
816//		m->AddData(
817//			"timeSourceNode",
818//			B_RAW_TYPE,
819//			&m_timeSourcePresets[n],
820//			sizeof(media_node));
821//		// +++++ copy media_node of associated time source!
822////		m->AddInt32("timeSource", n);
823//
824//		BMenuItem* i = new BMenuItem(
825//				_time_source_strings[n], m);
826//		menu->AddItem(i);
827//		_addGroupTarget(i);
828//	}
829}
830
831// add the given invoker to be retargeted to this
832// view (used for controls whose messages need a bit more
833// processing before being forwarded to the NodeManager.)
834
835void TransportView::_addLocalTarget(
836	BInvoker*								invoker) {
837
838	m_localTargets.push_back(invoker);
839	if(Window())
840		invoker->SetTarget(this);
841}
842
843void TransportView::_addGroupTarget(
844	BInvoker*								invoker) {
845
846	m_groupTargets.push_back(invoker);
847	if(m_group)
848		invoker->SetTarget(m_group);
849}
850
851void TransportView::_refreshTransportSettings() {
852	if(m_group)
853		_updateTransportButtons();
854}
855
856
857void TransportView::_disableControls() {
858
859//	PRINT((
860//		"*** _disableControls()\n"));
861
862	BWindow* w = Window();
863	if(w)
864		w->BeginViewTransaction();
865
866//	m_nameView->SetText("(no group)");
867	m_infoView->Invalidate();
868
869	m_runModeView->SetEnabled(false);
870	m_runModeView->Menu()->Superitem()->SetLabel(B_TRANSLATE("(none)"));
871
872	m_timeSourceView->SetEnabled(false);
873	m_timeSourceView->Menu()->Superitem()->SetLabel(B_TRANSLATE("(none)"));
874
875	m_regionStartView->SetEnabled(false);
876	m_regionStartView->setValue(0);
877
878	m_regionEndView->SetEnabled(false);
879	m_regionEndView->setValue(0);
880
881	m_startButton->SetEnabled(false);
882	m_stopButton->SetEnabled(false);
883	m_prerollButton->SetEnabled(false);
884
885	if(w)
886		w->EndViewTransaction();
887
888
889//	PRINT((
890//		"*** DONE: _disableControls()\n"));
891}
892
893void TransportView::_enableControls() {
894
895//	PRINT((
896//		"*** _enableControls()\n"));
897//
898	ASSERT(m_group);
899	Autolock _l(m_group); // +++++ why?
900	BWindow* w = Window();
901	if(w) {
902		w->BeginViewTransaction();
903
904		// clear focused view
905		// 19sep99: TransportWindow is currently a B_AVOID_FOCUS window; the
906		// only way views get focused is if they ask to be (which ValControl
907		// currently does.)
908		BView* focused = w->CurrentFocus();
909		if(focused)
910			focused->MakeFocus(false);
911	}
912
913//	BString nameViewText = m_group->name();
914//	nameViewText << ": " << m_group->countNodes() << " nodes.";
915//	m_nameView->SetText(nameViewText.String());
916
917	m_infoView->Invalidate();
918
919	m_runModeView->SetEnabled(true);
920	_updateRunMode();
921
922	m_timeSourceView->SetEnabled(true);
923	_updateTimeSource();
924
925	_updateTransportButtons();
926
927
928	// +++++ ick. hard-coded scaling factors make me queasy
929
930	m_regionStartView->SetEnabled(true);
931	m_regionStartView->setValue(
932		((double)m_group->startPosition()) / 1000000.0);
933
934	m_regionEndView->SetEnabled(true);
935	m_regionEndView->setValue(
936		((double)m_group->endPosition()) / 1000000.0);
937
938	// target controls on group
939	for(target_set::iterator it = m_groupTargets.begin();
940		it != m_groupTargets.end(); ++it) {
941		ASSERT(*it);
942		(*it)->SetTarget(m_group);
943	}
944
945	if(w) {
946		w->EndViewTransaction();
947	}
948
949//	PRINT((
950//		"*** DONE: _enableControls()\n"));
951}
952
953void TransportView::_updateTransportButtons() {
954
955	ASSERT(m_group);
956
957	switch(m_group->transportState()) {
958		case NodeGroup::TRANSPORT_INVALID:
959		case NodeGroup::TRANSPORT_STARTING:
960		case NodeGroup::TRANSPORT_STOPPING:
961			m_startButton->SetEnabled(false);
962			m_stopButton->SetEnabled(false);
963			m_prerollButton->SetEnabled(false);
964			break;
965
966		case NodeGroup::TRANSPORT_STOPPED:
967			m_startButton->SetEnabled(true);
968			m_stopButton->SetEnabled(false);
969			m_prerollButton->SetEnabled(true);
970			break;
971
972		case NodeGroup::TRANSPORT_RUNNING:
973		case NodeGroup::TRANSPORT_ROLLING:
974			m_startButton->SetEnabled(false);
975			m_stopButton->SetEnabled(true);
976			m_prerollButton->SetEnabled(false);
977			break;
978	}
979
980	// based on group settings, set the start button to do either
981	// a simple start or a roll (atomic start/stop.) [e.moon 11oct99]
982	ASSERT(m_startButton->Message());
983
984	// get current region settings
985	bigtime_t startPosition = _scalePosition(m_regionStartView->value());
986	bigtime_t endPosition = _scalePosition(m_regionEndView->value());
987
988	// get current run-mode setting
989	uint32 runMode = 0;
990	BMenuItem* i = m_runModeView->Menu()->FindMarked();
991	ASSERT(i);
992	runMode = m_runModeView->Menu()->IndexOf(i) + 1;
993
994	if(
995		endPosition > startPosition &&
996		(runMode == BMediaNode::B_OFFLINE ||
997			!m_group->canCycle())) {
998
999		m_startButton->SetLabel(B_TRANSLATE("Roll"));
1000		m_startButton->Message()->what = NodeGroup::M_ROLL;
1001
1002	} else {
1003		m_startButton->SetLabel(B_TRANSLATE("Start"));
1004		m_startButton->Message()->what = NodeGroup::M_START;
1005	}
1006}
1007
1008void TransportView::_updateTimeSource() {
1009	ASSERT(m_group);
1010
1011	media_node tsNode;
1012	status_t err = m_group->getTimeSource(&tsNode);
1013	if(err < B_OK) {
1014		PRINT((
1015			"! TransportView::_updateTimeSource(): m_group->getTimeSource():\n"
1016			"  %s\n",
1017			strerror(err)));
1018		return;
1019	}
1020
1021	BMenu* menu = m_timeSourceView->Menu();
1022	ASSERT(menu);
1023	if(tsNode == media_node::null) {
1024		menu->Superitem()->SetLabel(B_TRANSLATE("(none)"));
1025		return;
1026	}
1027
1028	// find menu item
1029	int32 n;
1030	for(n = menu->CountItems()-1; n >= 0; --n) {
1031		const void* data;
1032		media_node itemNode;
1033		BMessage* message = menu->ItemAt(n)->Message();
1034		if(!message)
1035			continue;
1036
1037		ssize_t size = sizeof(media_node);
1038		err = message->FindData(
1039			"timeSourceNode",
1040			B_RAW_TYPE,
1041			&data,
1042			&size);
1043		if(err < B_OK)
1044			continue;
1045
1046		itemNode = *((media_node*)data);
1047		if(itemNode != tsNode)
1048			continue;
1049
1050		// found it
1051		PRINT((
1052			"### _updateTimeSource: %s\n",
1053			menu->ItemAt(n)->Label()));
1054		menu->ItemAt(n)->SetMarked(true);
1055		break;
1056	}
1057//	ASSERT(m_timeSourcePresets);
1058//	int n;
1059//	for(n = 0; n < _time_sources; ++n) {
1060//		if(m_timeSourcePresets[n] == tsNode) {
1061//			BMenuItem* i = m_timeSourceView->Menu()->ItemAt(n);
1062//			ASSERT(i);
1063//			i->SetMarked(true);
1064//			break;
1065//		}
1066//	}
1067	if(n < 0)
1068		menu->Superitem()->SetLabel(B_TRANSLATE("(\?\?\?)"));
1069
1070}
1071void TransportView::_updateRunMode() {
1072	ASSERT(m_group);
1073
1074	BMenu* menu = m_runModeView->Menu();
1075	uint32 runMode = m_group->runMode() - 1; // real run mode starts at 1
1076	ASSERT((uint32)menu->CountItems() > runMode);
1077	menu->ItemAt(runMode)->SetMarked(true);
1078}
1079
1080//void TransportView::_initTimeSources() {
1081//
1082//	status_t err;
1083//	media_node node;
1084//	err = m_manager->roster->GetTimeSource(&node);
1085//	if(err == B_OK) {
1086//		m_timeSources.push_back(node.node);
1087//	}
1088//
1089//	err = m_manager->roster->GetSystemTimeSource(&m_timeSourcePresets[1]);
1090//	if(err < B_OK) {
1091//		PRINT((
1092//			"* TransportView::_initTimeSources():\n"
1093//			"  GetSystemTimeSource() failed: %s\n", strerror(err)));
1094//	}
1095//}
1096
1097// [e.moon 2dec99]
1098void TransportView::_timeSourceCreated(
1099	BMessage*								message) {
1100
1101	status_t err;
1102	media_node_id id;
1103	err = message->FindInt32("nodeID", &id);
1104	if(err < B_OK)
1105		return;
1106
1107//	PRINT(("### _timeSourceCreated(): %" B_PRId32 "\n", id));
1108	NodeRef* ref;
1109	err = m_manager->getNodeRef(id, &ref);
1110	if(err < B_OK) {
1111		PRINT((
1112			"!!! TransportView::_timeSourceCreated(): node %" B_PRId32 " doesn't exist\n",
1113			id));
1114		return;
1115	}
1116
1117	char nameBuffer[B_MEDIA_NAME_LENGTH+16];
1118	BMessage* m = new BMessage(NodeGroup::M_SET_TIME_SOURCE);
1119	m->AddData(
1120		"timeSourceNode",
1121		B_RAW_TYPE,
1122		&ref->node(),
1123		sizeof(media_node));
1124	sprintf(nameBuffer, "%s: %" B_PRId32,
1125		ref->name(),
1126		ref->id());
1127	BMenuItem* i = new BMenuItem(
1128		nameBuffer,
1129		m);
1130	BMenu* menu = m_timeSourceView->Menu();
1131	menu->AddItem(i);
1132	_addGroupTarget(i);
1133}
1134
1135// +++++
1136void TransportView::_timeSourceDeleted(
1137	BMessage*								message) {
1138
1139	status_t err;
1140	media_node_id id;
1141	err = message->FindInt32("nodeID", &id);
1142	if(err < B_OK)
1143		return;
1144
1145//	PRINT(("### _timeSourceDeleted(): %" B_PRId32 "\n", id));
1146
1147	BMenu* menu = m_timeSourceView->Menu();
1148	ASSERT(menu);
1149
1150	// find menu item
1151	int32 n;
1152	for(n = menu->CountItems()-1; n >= 0; --n) {
1153		const void* data;
1154		media_node itemNode;
1155		BMessage* message = menu->ItemAt(n)->Message();
1156		if(!message)
1157			continue;
1158
1159		ssize_t size = sizeof(media_node);
1160		err = message->FindData(
1161			"timeSourceNode",
1162			B_RAW_TYPE,
1163			&data,
1164			&size);
1165		if(err < B_OK)
1166			continue;
1167
1168		itemNode = *((media_node*)data);
1169		if(itemNode.node != id)
1170			continue;
1171
1172		// found it; remove the item
1173		menu->RemoveItem(n);
1174		break;
1175	}
1176}
1177
1178// -------------------------------------------------------- //
1179// *** LAYOUT ***
1180// -------------------------------------------------------- //
1181
1182const float _edge_pad_x 								= 3.0;
1183const float _edge_pad_y 								= 3.0;
1184
1185const float _column_pad_x 							= 4.0;
1186
1187const float _field_pad_x 								= 20.0;
1188
1189const float _text_view_pad_x						= 10.0;
1190
1191const float _control_pad_x 							= 5.0;
1192const float _control_pad_y 							= 10.0;
1193const float _menu_field_pad_x 					= 30.0;
1194
1195const float _label_pad_x 								= 8.0;
1196
1197const float _transport_pad_y 						= 4.0;
1198const float _transport_button_width			= 60.0;
1199const float _transport_button_height		= 22.0;
1200const float _transport_button_pad_x			= 4.0;
1201
1202const float _lock_button_width					= 42.0;
1203const float _lock_button_height					= 16.0;
1204
1205class TransportView::_layout_state {
1206public:
1207	_layout_state() {}
1208
1209	// +++++
1210};
1211
1212inline float _menu_width(
1213	const BMenu* menu,
1214	const BFont* font) {
1215
1216	float max = 0.0;
1217
1218	int total = menu->CountItems();
1219	for(int n = 0; n < total; ++n) {
1220		float w = font->StringWidth(
1221			menu->ItemAt(n)->Label());
1222		if(w > max)
1223			max = w;
1224	}
1225
1226	return max;
1227}
1228
1229// -------------------------------------------------------- //
1230
1231void TransportView::_initLayout() {
1232	m_layout = new _layout_state();
1233}
1234
1235
1236void TransportView::_updateLayout() // +++++
1237{
1238	BWindow* window = Window();
1239	if(window)
1240		window->BeginViewTransaction();
1241
1242	// calculate the ideal size of the view
1243	// * max label width
1244	float maxLabelWidth = 0.0;
1245	float w;
1246
1247	maxLabelWidth = be_bold_font->StringWidth(
1248		m_runModeView->Label());
1249
1250	w = be_bold_font->StringWidth(
1251		m_timeSourceView->Label());
1252	if(w > maxLabelWidth)
1253		maxLabelWidth = w;
1254
1255//	w = be_bold_font->StringWidth(
1256//		m_regionStartView->Label());
1257//	if(w > maxLabelWidth)
1258//		maxLabelWidth = w;
1259//
1260//	w = be_bold_font->StringWidth(
1261//		m_regionEndView->Label());
1262//	if(w > maxLabelWidth)
1263//		maxLabelWidth = w;
1264
1265	// * max field width
1266	float maxFieldWidth = 0.0;
1267	maxFieldWidth = _menu_width(
1268		m_runModeView->Menu(), be_plain_font);
1269
1270	w = _menu_width(
1271		m_timeSourceView->Menu(), be_plain_font);
1272	if(w > maxFieldWidth)
1273		maxFieldWidth = w;
1274
1275	// * column width
1276	float columnWidth =
1277		maxLabelWidth +
1278		maxFieldWidth + _label_pad_x + _field_pad_x;
1279
1280	// figure columns
1281	float column1_x = _edge_pad_x;
1282	float column2_x = column1_x + columnWidth + _column_pad_x;
1283
1284	// * sum to figure view width
1285	float viewWidth =
1286		column2_x + columnWidth + _edge_pad_x;
1287
1288	// make room for buttons
1289	float buttonSpan =
1290		(_transport_button_width*3) +
1291		(_transport_button_pad_x*2);
1292	if(columnWidth < buttonSpan)
1293		viewWidth += (buttonSpan - columnWidth);
1294
1295//	float insideWidth = viewWidth - (_edge_pad_x*2);
1296
1297	// * figure view height a row at a time
1298	font_height fh;
1299	be_plain_font->GetHeight(&fh);
1300	float lineHeight = fh.ascent + fh.descent + fh.leading;
1301
1302	float prefInfoWidth, prefInfoHeight;
1303	m_infoView->GetPreferredSize(&prefInfoWidth, &prefInfoHeight);
1304	float row_1_height = max(prefInfoHeight, _transport_button_height);
1305
1306	float row1_y = _edge_pad_y;
1307	float row2_y = row1_y + row_1_height + _transport_pad_y;
1308	float row3_y = row2_y + lineHeight + _control_pad_y;
1309//	float row4_y = row3_y + lineHeight + _control_pad_y + _transport_pad_y;
1310//	float row5_y = row4_y + lineHeight + _control_pad_y;
1311	float viewHeight = row3_y + lineHeight + _control_pad_y + _edge_pad_y;
1312
1313	// Place controls
1314	m_infoView->MoveTo(
1315		column1_x+1.0, row1_y+1.0);
1316	m_infoView->ResizeTo(
1317		columnWidth, prefInfoHeight);
1318
1319	BRect br(
1320		column2_x, row1_y,
1321		column2_x+_transport_button_width,
1322		row1_y+_transport_button_height);
1323	if(prefInfoHeight > _transport_button_height)
1324		br.OffsetBy(0.0, (prefInfoHeight - _transport_button_height)/2);
1325
1326	m_startButton->MoveTo(br.LeftTop());
1327	m_startButton->ResizeTo(br.Width(), br.Height());
1328	br.OffsetBy(_transport_button_width + _transport_button_pad_x, 0.0);
1329
1330	m_stopButton->MoveTo(br.LeftTop());
1331	m_stopButton->ResizeTo(br.Width(), br.Height());
1332	br.OffsetBy(_transport_button_width + _transport_button_pad_x, 0.0);
1333
1334	m_prerollButton->MoveTo(br.LeftTop());
1335	m_prerollButton->ResizeTo(br.Width(), br.Height());
1336
1337	m_runModeView->MoveTo(
1338		column2_x, row2_y);
1339	m_runModeView->ResizeTo(
1340		columnWidth, lineHeight);
1341	m_runModeView->SetDivider(
1342		maxLabelWidth+_label_pad_x);
1343	m_runModeView->SetAlignment(
1344		B_ALIGN_LEFT);
1345
1346	m_timeSourceView->MoveTo(
1347		column2_x, row3_y);
1348	m_timeSourceView->ResizeTo(
1349		columnWidth, lineHeight);
1350	m_timeSourceView->SetDivider(
1351		maxLabelWidth+_label_pad_x);
1352	m_timeSourceView->SetAlignment(
1353		B_ALIGN_LEFT);
1354
1355//	float regionControlWidth = columnWidth;
1356//	float regionControlHeight = lineHeight + 4.0;
1357
1358//	m_regionStartView->TextView()->SetResizingMode(
1359//		B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP);
1360
1361	// "FROM"
1362
1363	BPoint rtLeftTop(column1_x, row2_y + 5.0);
1364	BPoint rtRightBottom;
1365
1366	m_fromLabel->MoveTo(rtLeftTop);
1367	m_fromLabel->ResizeToPreferred();
1368	rtRightBottom = rtLeftTop + BPoint(
1369		m_fromLabel->Bounds().Width(),
1370		m_fromLabel->Bounds().Height());
1371
1372
1373	// (region-start)
1374
1375	rtLeftTop.x = rtRightBottom.x+4;
1376
1377	m_regionStartView->MoveTo(rtLeftTop + BPoint(0.0, 2.0));
1378	m_regionStartView->ResizeToPreferred();
1379	rtRightBottom = rtLeftTop + BPoint(
1380		m_regionStartView->Bounds().Width(),
1381		m_regionStartView->Bounds().Height());
1382
1383//	m_regionStartView->SetDivider(
1384//		maxLabelWidth);
1385//	m_regionStartView->TextView()->ResizeTo(
1386//		regionControlWidth-(maxLabelWidth+_text_view_pad_x),
1387//		regionControlHeight-4.0);
1388
1389	// "TO"
1390
1391	rtLeftTop.x = rtRightBottom.x + 6;
1392
1393	m_toLabel->MoveTo(rtLeftTop);
1394	m_toLabel->ResizeToPreferred();
1395	rtRightBottom = rtLeftTop + BPoint(
1396		m_toLabel->Bounds().Width(),
1397		m_toLabel->Bounds().Height());
1398
1399	// (region-end)
1400
1401	rtLeftTop.x = rtRightBottom.x + 4;
1402
1403	m_regionEndView->MoveTo(rtLeftTop + BPoint(0.0, 2.0));
1404	m_regionEndView->ResizeToPreferred();
1405//	m_regionEndView->SetDivider(
1406//		maxLabelWidth);
1407//	m_regionEndView->TextView()->ResizeTo(
1408//		regionControlWidth-(maxLabelWidth+_text_view_pad_x),
1409//		regionControlHeight-4.0);
1410
1411
1412	BRect b = Bounds();
1413	float targetWidth = (b.Width() < viewWidth) ?
1414		viewWidth :
1415		b.Width();
1416	float targetHeight = (b.Height() < viewHeight) ?
1417		viewHeight :
1418		b.Height();
1419
1420	// Resize view to fit contents
1421	ResizeTo(targetWidth, targetHeight);
1422
1423	if(window) {
1424		window->ResizeTo(targetWidth, targetHeight);
1425	}
1426
1427//	// +++++ testing NumericValControl [23aug99]
1428//	float valWidth, valHeight;
1429//	m_valView->GetPreferredSize(&valWidth, &valHeight);
1430//	PRINT((
1431//		"\n\nm_valView preferred size: %.1f x %.1f\n\n",
1432//		valWidth, valHeight));
1433//
1434	if(window)
1435		window->EndViewTransaction();
1436}
1437
1438// -------------------------------------------------------- //
1439// *** dtor
1440// -------------------------------------------------------- //
1441
1442TransportView::~TransportView() {
1443	if(m_group)
1444		_releaseGroup();
1445	if(m_layout)
1446		delete m_layout;
1447}
1448
1449
1450// END -- TransportView.cpp --
1451