1/*
2 * Copyright 2003-2011, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Sikosis
7 *		J��r��me Duval
8 */
9
10
11#include "MediaViews.h"
12
13#include <AutoDeleter.h>
14#include <Box.h>
15#include <Button.h>
16#include <Catalog.h>
17#include <CheckBox.h>
18#include <Deskbar.h>
19#include <Entry.h>
20#include <LayoutBuilder.h>
21#include <Locale.h>
22#include <MediaAddOn.h>
23#include <MediaRoster.h>
24#include <MenuField.h>
25#include <PopUpMenu.h>
26#include <String.h>
27#include <StringView.h>
28#include <TextView.h>
29
30#include <assert.h>
31#include <stdio.h>
32
33#include "MediaWindow.h"
34
35
36#undef B_TRANSLATION_CONTEXT
37#define B_TRANSLATION_CONTEXT "Media views"
38
39#define MEDIA_DEFAULT_INPUT_CHANGE 'dich'
40#define MEDIA_DEFAULT_OUTPUT_CHANGE 'doch'
41#define MEDIA_SHOW_HIDE_VOLUME_CONTROL 'shvc'
42
43
44SettingsView::SettingsView()
45	:
46	BGroupView(B_VERTICAL, B_USE_DEFAULT_SPACING),
47	fInputMenu(NULL),
48	fOutputMenu(NULL)
49{
50	// input menu
51	fInputMenu = new BPopUpMenu(B_TRANSLATE_ALL("<none>",
52		"VideoInputMenu", "Used when no video input is available"));
53	fInputMenu->SetLabelFromMarked(true);
54
55	// output menu
56	fOutputMenu = new BPopUpMenu(B_TRANSLATE_ALL("<none>",
57		"VideoOutputMenu", "Used when no video output is available"));
58	fOutputMenu->SetLabelFromMarked(true);
59}
60
61
62BButton*
63SettingsView::MakeRestartButton()
64{
65	return new BButton("restartButton",
66		B_TRANSLATE("Restart media services"),
67		new BMessage(ML_RESTART_MEDIA_SERVER));
68}
69
70
71
72void
73SettingsView::AddInputNodes(NodeList& list)
74{
75	_EmptyMenu(fInputMenu);
76
77	BMessage message(MEDIA_DEFAULT_INPUT_CHANGE);
78	_PopulateMenu(fInputMenu, list, message);
79}
80
81
82void
83SettingsView::AddOutputNodes(NodeList& list)
84{
85	_EmptyMenu(fOutputMenu);
86
87	BMessage message(MEDIA_DEFAULT_OUTPUT_CHANGE);
88	_PopulateMenu(fOutputMenu, list, message);
89}
90
91
92void
93SettingsView::SetDefaultInput(const dormant_node_info* info)
94{
95	_ClearMenuSelection(fInputMenu);
96	NodeMenuItem* item = _FindNodeItem(fInputMenu, info);
97	if (item)
98		item->SetMarked(true);
99}
100
101
102void
103SettingsView::SetDefaultOutput(const dormant_node_info* info)
104{
105	_ClearMenuSelection(fOutputMenu);
106	NodeMenuItem* item = _FindNodeItem(fOutputMenu, info);
107	if (item)
108		item->SetMarked(true);
109}
110
111
112void
113SettingsView::MessageReceived(BMessage* message)
114{
115	switch (message->what) {
116		case MEDIA_DEFAULT_INPUT_CHANGE:
117		{
118			int32 index;
119			if (message->FindInt32("index", &index)!=B_OK)
120				break;
121			NodeMenuItem* item
122				= static_cast<NodeMenuItem*>(fInputMenu->ItemAt(index));
123			SetDefaultInput(item->NodeInfo());
124			break;
125		}
126		case MEDIA_DEFAULT_OUTPUT_CHANGE:
127		{
128			int32 index;
129			if (message->FindInt32("index", &index)!=B_OK)
130				break;
131			NodeMenuItem* item
132				= static_cast<NodeMenuItem*>(fOutputMenu->ItemAt(index));
133			SetDefaultOutput(item->NodeInfo());
134			break;
135		}
136		default:
137			BGroupView::MessageReceived(message);
138	}
139}
140
141
142void
143SettingsView::AttachedToWindow()
144{
145	BMessenger thisMessenger(this);
146	fInputMenu->SetTargetForItems(thisMessenger);
147	fOutputMenu->SetTargetForItems(thisMessenger);
148}
149
150
151MediaWindow*
152SettingsView::_MediaWindow() const
153{
154	return static_cast<MediaWindow*>(Window());
155}
156
157
158void
159SettingsView::_EmptyMenu(BMenu* menu)
160{
161	while (menu->CountItems() > 0)
162		delete menu->RemoveItem((int32)0);
163}
164
165
166void
167SettingsView::_PopulateMenu(BMenu* menu, NodeList& nodes,
168	const BMessage& message)
169{
170	for (int32 i = 0; i < nodes.CountItems(); i++) {
171		dormant_node_info* info = nodes.ItemAt(i);
172		menu->AddItem(new NodeMenuItem(info, new BMessage(message)));
173	}
174
175	if (Window() != NULL)
176		menu->SetTargetForItems(BMessenger(this));
177}
178
179
180NodeMenuItem*
181SettingsView::_FindNodeItem(BMenu* menu, const dormant_node_info* nodeInfo)
182{
183	for (int32 i = 0; i < menu->CountItems(); i++) {
184		NodeMenuItem* item = static_cast<NodeMenuItem*>(menu->ItemAt(i));
185		const dormant_node_info* itemInfo = item->NodeInfo();
186		if (itemInfo && itemInfo->addon == nodeInfo->addon
187			&& itemInfo->flavor_id == nodeInfo->flavor_id) {
188			return item;
189		}
190	}
191	return NULL;
192}
193
194
195void
196SettingsView::_ClearMenuSelection(BMenu* menu)
197{
198	for (int32 i = 0; i < menu->CountItems(); i++) {
199		BMenuItem* item = menu->ItemAt(i);
200		item->SetMarked(false);
201	}
202}
203
204
205NodeMenuItem::NodeMenuItem(const dormant_node_info* info, BMessage* message,
206	char shortcut, uint32 modifiers)
207	:
208	BMenuItem(info->name, message, shortcut, modifiers),
209	fInfo(info)
210{
211
212}
213
214
215status_t
216NodeMenuItem::Invoke(BMessage* message)
217{
218	if (IsMarked())
219		return B_OK;
220	return BMenuItem::Invoke(message);
221}
222
223
224ChannelMenuItem::ChannelMenuItem(media_input* input, BMessage* message,
225	char shortcut, uint32 modifiers)
226	:
227	BMenuItem(input->name, message, shortcut, modifiers),
228	fInput(input)
229{
230}
231
232
233ChannelMenuItem::~ChannelMenuItem()
234{
235	delete fInput;
236}
237
238
239int32
240ChannelMenuItem::DestinationID()
241{
242	return fInput->destination.id;
243}
244
245
246media_input*
247ChannelMenuItem::Input()
248{
249	return fInput;
250}
251
252
253status_t
254ChannelMenuItem::Invoke(BMessage* message)
255{
256	if (IsMarked())
257		return B_OK;
258	return BMenuItem::Invoke(message);
259}
260
261
262AudioSettingsView::AudioSettingsView()
263{
264	BBox* defaultsBox = new BBox("defaults");
265	defaultsBox->SetLabel(B_TRANSLATE("Defaults"));
266	BGridView* defaultsGridView = new BGridView();
267
268	BMenuField* inputMenuField = new BMenuField("inputMenuField",
269		B_TRANSLATE("Audio input:"), InputMenu());
270
271	BMenuField* outputMenuField = new BMenuField("outputMenuField",
272		B_TRANSLATE("Audio output:"), OutputMenu());
273
274	BLayoutBuilder::Grid<>(defaultsGridView)
275		.SetInsets(B_USE_DEFAULT_SPACING, 0, B_USE_DEFAULT_SPACING,
276			B_USE_DEFAULT_SPACING)
277		.AddMenuField(inputMenuField, 0, 0, B_ALIGN_HORIZONTAL_UNSET, 1, 3, 1)
278		.AddMenuField(outputMenuField, 0, 1)
279		.AddMenuField(_MakeChannelMenu(), 2, 1);
280
281	defaultsBox->AddChild(defaultsGridView);
282
283	BLayoutBuilder::Group<>(this)
284		.SetInsets(0, 0, 0, 0)
285		.Add(defaultsBox)
286		.AddGroup(B_HORIZONTAL)
287			.Add(_MakeVolumeCheckBox())
288			.AddGlue()
289			.Add(MakeRestartButton())
290			.End()
291		.AddGlue();
292}
293
294
295void
296AudioSettingsView::SetDefaultChannel(int32 channelID)
297{
298	for (int32 i = 0; i < fChannelMenu->CountItems(); i++) {
299		ChannelMenuItem* item = _ChannelMenuItemAt(i);
300		item->SetMarked(item->DestinationID() == channelID);
301	}
302}
303
304
305void
306AudioSettingsView::AttachedToWindow()
307{
308	SettingsView::AttachedToWindow();
309
310	BMessenger thisMessenger(this);
311	fChannelMenu->SetTargetForItems(thisMessenger);
312	fVolumeCheckBox->SetTarget(thisMessenger);
313}
314
315
316void
317AudioSettingsView::MessageReceived(BMessage* message)
318{
319	switch (message->what) {
320		case ML_DEFAULT_CHANNEL_CHANGED:
321			{
322				int32 index;
323				if (message->FindInt32("index", &index) != B_OK)
324					break;
325				ChannelMenuItem* item = _ChannelMenuItemAt(index);
326
327				if (item) {
328					BMediaRoster* roster = BMediaRoster::Roster();
329					roster->SetAudioOutput(*item->Input());
330				} else
331					fprintf(stderr, "ChannelMenuItem not found\n");
332			}
333			break;
334		case MEDIA_SHOW_HIDE_VOLUME_CONTROL:
335		{
336			if (fVolumeCheckBox->Value() == B_CONTROL_ON)
337				_ShowDeskbarVolumeControl();
338			else
339				_HideDeskbarVolumeControl();
340			break;
341		}
342
343		default:
344			SettingsView::MessageReceived(message);
345	}
346}
347
348
349void
350AudioSettingsView::SetDefaultInput(const dormant_node_info* info)
351{
352	SettingsView::SetDefaultInput(info);
353	_MediaWindow()->UpdateInputListItem(MediaListItem::AUDIO_TYPE, info);
354	BMediaRoster::Roster()->SetAudioInput(*info);
355}
356
357
358void
359AudioSettingsView::SetDefaultOutput(const dormant_node_info* info)
360{
361	SettingsView::SetDefaultOutput(info);
362	_MediaWindow()->UpdateOutputListItem(MediaListItem::AUDIO_TYPE, info);
363	_FillChannelMenu(info);
364	BMediaRoster::Roster()->SetAudioOutput(*info);
365}
366
367
368BMenuField*
369AudioSettingsView::_MakeChannelMenu()
370{
371	fChannelMenu = new BPopUpMenu(B_TRANSLATE("<none>"));
372	fChannelMenu->SetLabelFromMarked(true);
373	BMenuField* channelMenuField = new BMenuField("channelMenuField",
374		B_TRANSLATE("Channel:"), fChannelMenu);
375	return channelMenuField;
376}
377
378
379BCheckBox*
380AudioSettingsView::_MakeVolumeCheckBox()
381{
382	fVolumeCheckBox = new BCheckBox("volumeCheckBox",
383		B_TRANSLATE("Show volume control on Deskbar"),
384		new BMessage(MEDIA_SHOW_HIDE_VOLUME_CONTROL));
385
386	if (BDeskbar().HasItem("MediaReplicant"))
387		fVolumeCheckBox->SetValue(B_CONTROL_ON);
388
389	return fVolumeCheckBox;
390}
391
392
393void
394AudioSettingsView::_FillChannelMenu(const dormant_node_info* nodeInfo)
395{
396	_EmptyMenu(fChannelMenu);
397
398	BMediaRoster* roster = BMediaRoster::Roster();
399	media_node node;
400	media_node_id node_id;
401
402	status_t err = roster->GetInstancesFor(nodeInfo->addon,
403		nodeInfo->flavor_id, &node_id);
404	if (err != B_OK) {
405		err = roster->InstantiateDormantNode(*nodeInfo, &node,
406			B_FLAVOR_IS_GLOBAL);
407	} else {
408		err = roster->GetNodeFor(node_id, &node);
409	}
410
411	if (err == B_OK) {
412		int32 inputCount = 4;
413		media_input* inputs = new media_input[inputCount];
414		BPrivate::ArrayDeleter<media_input> inputDeleter(inputs);
415
416		while (true) {
417			int32 realInputCount = 0;
418			err = roster->GetAllInputsFor(node, inputs,
419				inputCount, &realInputCount);
420			if (realInputCount > inputCount) {
421				inputCount *= 2;
422				inputs = new media_input[inputCount];
423				inputDeleter.SetTo(inputs);
424			} else {
425				inputCount = realInputCount;
426				break;
427			}
428		}
429
430		if (err == B_OK) {
431			BMessage message(ML_DEFAULT_CHANNEL_CHANGED);
432
433			for (int32 i = 0; i < inputCount; i++) {
434				media_input* input = new media_input();
435				*input = inputs[i];
436				ChannelMenuItem* channelItem = new ChannelMenuItem(input,
437					new BMessage(message));
438				fChannelMenu->AddItem(channelItem);
439
440				if (channelItem->DestinationID() == 0)
441					channelItem->SetMarked(true);
442			}
443		}
444	}
445
446	if (Window())
447		fChannelMenu->SetTargetForItems(BMessenger(this));
448}
449
450
451void
452AudioSettingsView::_ShowDeskbarVolumeControl()
453{
454	BDeskbar deskbar;
455	BEntry entry("/bin/desklink", true);
456	int32 id;
457	entry_ref ref;
458	status_t status = entry.GetRef(&ref);
459	if (status == B_OK)
460		status = deskbar.AddItem(&ref, &id);
461
462	if (status != B_OK) {
463		fprintf(stderr, B_TRANSLATE(
464			"Couldn't add volume control in Deskbar: %s\n"),
465			strerror(status));
466	}
467}
468
469
470void
471AudioSettingsView::_HideDeskbarVolumeControl()
472{
473	BDeskbar deskbar;
474	status_t status = deskbar.RemoveItem("MediaReplicant");
475	if (status != B_OK) {
476		fprintf(stderr, B_TRANSLATE(
477			"Couldn't remove volume control in Deskbar: %s\n"),
478			strerror(status));
479	}
480}
481
482
483ChannelMenuItem*
484AudioSettingsView::_ChannelMenuItemAt(int32 index)
485{
486	return static_cast<ChannelMenuItem*>(fChannelMenu->ItemAt(index));
487}
488
489
490VideoSettingsView::VideoSettingsView()
491{
492	BBox* defaultsBox = new BBox("defaults");
493	defaultsBox->SetLabel(B_TRANSLATE("Defaults"));
494	BGridView* defaultsGridView = new BGridView();
495
496	BMenuField* inputMenuField = new BMenuField("inputMenuField",
497		B_TRANSLATE("Video input:"), InputMenu());
498
499	BMenuField* outputMenuField = new BMenuField("outputMenuField",
500		B_TRANSLATE("Video output:"), OutputMenu());
501
502	BLayoutBuilder::Grid<>(defaultsGridView)
503		.SetInsets(B_USE_DEFAULT_SPACING, 0, B_USE_DEFAULT_SPACING,
504			B_USE_DEFAULT_SPACING)
505		.AddMenuField(inputMenuField, 0, 0)
506		.AddMenuField(outputMenuField, 0, 1);
507
508	defaultsBox->AddChild(defaultsGridView);
509
510	BLayoutBuilder::Group<>(this)
511		.SetInsets(0, 0, 0, 0)
512		.Add(defaultsBox)
513		.AddGroup(B_HORIZONTAL)
514			.AddGlue()
515			.Add(MakeRestartButton())
516			.End()
517		.AddGlue();
518}
519
520
521void
522VideoSettingsView::SetDefaultInput(const dormant_node_info* info)
523{
524	SettingsView::SetDefaultInput(info);
525	_MediaWindow()->UpdateInputListItem(MediaListItem::VIDEO_TYPE, info);
526	BMediaRoster::Roster()->SetVideoInput(*info);
527}
528
529
530void
531VideoSettingsView::SetDefaultOutput(const dormant_node_info* info)
532{
533	SettingsView::SetDefaultOutput(info);
534	_MediaWindow()->UpdateOutputListItem(MediaListItem::VIDEO_TYPE, info);
535	BMediaRoster::Roster()->SetVideoOutput(*info);
536}
537