xref: /haiku/src/bin/desklink/MediaReplicant.cpp (revision 3b07762c548ec4016dea480d1061577cd15ec614)
1 /*
2  * Copyright 2003-2013, Haiku. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Jérôme Duval
7  *		François Revol
8  *		Marcus Overhagen
9  *		Jonas Sundström
10  *		Axel Dörfler, axeld@pinc-software.de.
11  *		Stephan Aßmus <superstippi@gmx.de>
12  *		Puck Meerburg, puck@puckipedia.nl
13  */
14 
15 
16 //! Volume control, and media shortcuts in Deskbar
17 
18 
19 #include <new>
20 #include <stdio.h>
21 
22 #include <Alert.h>
23 #include <Bitmap.h>
24 #include <Catalog.h>
25 #include <Entry.h>
26 #include <File.h>
27 #include <FindDirectory.h>
28 #include <IconUtils.h>
29 #include <MenuItem.h>
30 #include <Path.h>
31 #include <PopUpMenu.h>
32 #include <Roster.h>
33 #include <String.h>
34 #include <StringView.h>
35 #include <ToolTip.h>
36 #include <ToolTipManager.h>
37 
38 #include "desklink.h"
39 #include "iconfile.h"
40 #include "MixerControl.h"
41 #include "VolumeWindow.h"
42 
43 
44 #undef B_TRANSLATION_CONTEXT
45 #define B_TRANSLATION_CONTEXT "MediaReplicant"
46 
47 
48 static const uint32 kMsgOpenMediaSettings = 'mese';
49 static const uint32 kMsgOpenSoundSettings = 'sose';
50 static const uint32 kMsgOpenMediaPlayer = 'omep';
51 static const uint32 kMsgToggleBeep = 'tdbp';
52 static const uint32 kMsgVolumeWhich = 'svwh';
53 
54 static const char* kReplicantName = "MediaReplicant";
55 	// R5 name needed, Media prefs manel removes by name
56 
57 static const char* kSettingsFile = "x-vnd.Haiku-desklink";
58 
59 
60 class VolumeToolTip : public BToolTip {
61 public:
62 	VolumeToolTip(int32 which = VOLUME_USE_MIXER)
63 		:
64 		fWhich(which)
65 	{
66 		fView = new BStringView("", "");
67 	}
68 
69 	virtual ~VolumeToolTip()
70 	{
71 		delete fView;
72 	}
73 
74 	virtual BView* View() const
75 	{
76 		return fView;
77 	}
78 
79 	virtual void AttachedToWindow()
80 	{
81 		Update();
82 	}
83 
84 	void SetWhich(int32 which)
85 	{
86 		fWhich = which;
87 	}
88 
89 	void Update()
90 	{
91 		if (!Lock())
92 			return;
93 
94 		if (fMuteMessage.Length() != 0)
95 			fView->SetText(fMuteMessage.String());
96 		else {
97 			MixerControl control;
98 			control.Connect(fWhich);
99 
100 			BString text;
101 			text.SetToFormat(B_TRANSLATE("%g dB"), control.Volume());
102 			fView->SetText(text.String());
103 		}
104 
105 		Unlock();
106 	}
107 
108 	void SetMuteMessage(const char* message)
109 	{
110 		fMuteMessage = message == NULL ? "" : message;
111 	}
112 
113 private:
114 	BStringView*	fView;
115 	int32			fWhich;
116 	BString			fMuteMessage;
117 };
118 
119 
120 class MediaReplicant : public BView {
121 public:
122 							MediaReplicant(BRect frame, const char* name,
123 								uint32 resizeMask = B_FOLLOW_ALL,
124 								uint32 flags = B_WILL_DRAW | B_NAVIGABLE
125 									| B_PULSE_NEEDED);
126 							MediaReplicant(BMessage* archive);
127 
128 	virtual					~MediaReplicant();
129 
130 	// archiving overrides
131 	static	MediaReplicant*	Instantiate(BMessage* data);
132 	virtual	status_t		Archive(BMessage* data, bool deep = true) const;
133 
134 	// BView overrides
135 	virtual void			AttachedToWindow();
136 	virtual void			MouseDown(BPoint point);
137 	virtual void			Draw(BRect updateRect);
138 	virtual void			MessageReceived(BMessage* message);
139 	virtual void			Pulse();
140 
141 private:
142 			status_t		_LaunchByPath(const char* path);
143 			status_t		_LaunchBySignature(const char* signature);
144 			void			_Launch(const char* prettyName,
145 								const char* signature, directory_which base,
146 								const char* fileName);
147 			void			_LoadSettings();
148 			void			_SaveSettings();
149 			void			_Init();
150 
151 			BBitmap*		fIcon;
152 			BBitmap*		fMutedIcon;
153 			VolumeWindow*	fVolumeSlider;
154 			bool 			fDontBeep;
155 				// don't beep on volume change
156 			int32 			fVolumeWhich;
157 				// which volume parameter to act on (Mixer/Phys.Output)
158 			bool				fMuted;
159 };
160 
161 
162 MediaReplicant::MediaReplicant(BRect frame, const char* name,
163 		uint32 resizeMask, uint32 flags)
164 	:
165 	BView(frame, name, resizeMask, flags),
166 	fVolumeSlider(NULL),
167 	fMuted(false)
168 {
169 	_Init();
170 }
171 
172 
173 MediaReplicant::MediaReplicant(BMessage* message)
174 	:
175 	BView(message),
176 	fVolumeSlider(NULL),
177 	fMuted(false)
178 {
179 	_Init();
180 }
181 
182 
183 MediaReplicant::~MediaReplicant()
184 {
185 	delete fIcon;
186 	_SaveSettings();
187 }
188 
189 
190 MediaReplicant*
191 MediaReplicant::Instantiate(BMessage* data)
192 {
193 	if (!validate_instantiation(data, kReplicantName))
194 		return NULL;
195 
196 	return new(std::nothrow) MediaReplicant(data);
197 }
198 
199 
200 status_t
201 MediaReplicant::Archive(BMessage* data, bool deep) const
202 {
203 	status_t status = BView::Archive(data, deep);
204 	if (status < B_OK)
205 		return status;
206 
207 	return data->AddString("add_on", kAppSignature);
208 }
209 
210 
211 void
212 MediaReplicant::AttachedToWindow()
213 {
214 	BView* parent = Parent();
215 	if (parent)
216 		SetViewColor(parent->ViewColor());
217 
218 	BView::AttachedToWindow();
219 }
220 
221 
222 void
223 MediaReplicant::Draw(BRect rect)
224 {
225 	SetDrawingMode(B_OP_OVER);
226 	DrawBitmap(fMuted ? fMutedIcon : fIcon);
227 }
228 
229 
230 void
231 MediaReplicant::MouseDown(BPoint point)
232 {
233 	int32 buttons = B_PRIMARY_MOUSE_BUTTON;
234 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
235 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
236 
237 	BPoint where = ConvertToScreen(point);
238 
239 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
240 		BPopUpMenu* menu = new BPopUpMenu("", false, false);
241 		menu->SetFont(be_plain_font);
242 
243 		menu->AddItem(new BMenuItem(
244 			B_TRANSLATE("Media preferences" B_UTF8_ELLIPSIS),
245 			new BMessage(kMsgOpenMediaSettings)));
246 		menu->AddItem(new BMenuItem(
247 			B_TRANSLATE("Sound preferences" B_UTF8_ELLIPSIS),
248 			new BMessage(kMsgOpenSoundSettings)));
249 
250 		menu->AddSeparatorItem();
251 
252 		menu->AddItem(new BMenuItem(B_TRANSLATE("Open MediaPlayer"),
253 			new BMessage(kMsgOpenMediaPlayer)));
254 
255 		menu->AddSeparatorItem();
256 
257 		BMenu* subMenu = new BMenu(B_TRANSLATE("Options"));
258 		menu->AddItem(subMenu);
259 
260 		BMenuItem* item = new BMenuItem(B_TRANSLATE("Control physical output"),
261 			new BMessage(kMsgVolumeWhich));
262 		item->SetMarked(fVolumeWhich == VOLUME_USE_PHYS_OUTPUT);
263 		subMenu->AddItem(item);
264 
265 		item = new BMenuItem(B_TRANSLATE("Beep"),
266 			new BMessage(kMsgToggleBeep));
267 		item->SetMarked(!fDontBeep);
268 		subMenu->AddItem(item);
269 
270 		menu->SetTargetForItems(this);
271 		subMenu->SetTargetForItems(this);
272 
273 		menu->Go(where, true, true, BRect(where - BPoint(4, 4),
274 			where + BPoint(4, 4)));
275 
276 	} else if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
277 		MixerControl mixerControl;
278 		if (mixerControl.Connect(fVolumeWhich)) {
279 			mixerControl.SetMute(!fMuted);
280 			fMuted = mixerControl.Mute();
281 			VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip());
282 			if (tip != NULL) {
283 				tip->SetMuteMessage(fMuted ? B_TRANSLATE("Muted"): NULL);
284 				ShowToolTip(tip);
285 			}
286 			Invalidate();
287 		}
288 
289 	} else {
290 		// Show VolumeWindow
291 		fVolumeSlider = new VolumeWindow(BRect(where.x, where.y,
292 			where.x + 207, where.y + 19), fDontBeep, fVolumeWhich);
293 		fVolumeSlider->Show();
294 	}
295 }
296 
297 
298 void
299 MediaReplicant::Pulse()
300 {
301 	bool setMuted = false;
302 	MixerControl mixerControl;
303 	const char* errorString = NULL;
304 	if (!mixerControl.Connect(fVolumeWhich, NULL, &errorString)) {
305 		fMuted = true;
306 		errorString = NULL;
307 	} else
308 		setMuted = mixerControl.Mute();
309 
310 	if (setMuted != fMuted) {
311 		fMuted = setMuted;
312 		VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip());
313 		if (tip != NULL)
314 			tip->SetMuteMessage(errorString);
315 		Invalidate();
316 	}
317 }
318 
319 
320 void
321 MediaReplicant::MessageReceived(BMessage* message)
322 {
323 	switch (message->what) {
324 		case kMsgOpenMediaPlayer:
325 			_Launch("MediaPlayer", "application/x-vnd.Haiku-MediaPlayer",
326 				B_SYSTEM_APPS_DIRECTORY, "MediaPlayer");
327 			break;
328 
329 		case kMsgOpenMediaSettings:
330 			_Launch("Media Preferences", "application/x-vnd.Haiku-Media",
331 				B_SYSTEM_PREFERENCES_DIRECTORY, "Media");
332 			break;
333 
334 		case kMsgOpenSoundSettings:
335 			_Launch("Sounds Preferences", "application/x-vnd.Haiku-Sounds",
336 				B_SYSTEM_PREFERENCES_DIRECTORY, "Sounds");
337 			break;
338 
339 		case kMsgToggleBeep:
340 		{
341 			BMenuItem* item;
342 			if (message->FindPointer("source", (void**)&item) != B_OK)
343 				return;
344 
345 			item->SetMarked(!item->IsMarked());
346 			fDontBeep = !item->IsMarked();
347 			break;
348 		}
349 
350 		case kMsgVolumeWhich:
351 		{
352 			BMenuItem* item;
353 			if (message->FindPointer("source", (void**)&item) != B_OK)
354 				return;
355 
356 			item->SetMarked(!item->IsMarked());
357 			fVolumeWhich = item->IsMarked()
358 				? VOLUME_USE_PHYS_OUTPUT : VOLUME_USE_MIXER;
359 
360 			if (VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip()))
361 				tip->SetWhich(fVolumeWhich);
362 			break;
363 		}
364 
365 		case B_MOUSE_WHEEL_CHANGED:
366 		{
367 			float deltaY;
368 			if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK
369 				&& deltaY != 0.0) {
370 				MixerControl mixerControl;
371 				mixerControl.Connect(fVolumeWhich);
372 				mixerControl.ChangeVolumeBy(deltaY < 0 ? 6 : -6);
373 
374 				VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip());
375 				if (tip != NULL) {
376 					tip->Update();
377 					ShowToolTip(tip);
378 				}
379 			}
380 			break;
381 		}
382 
383 		default:
384 			BView::MessageReceived(message);
385 			break;
386 	}
387 }
388 
389 
390 status_t
391 MediaReplicant::_LaunchByPath(const char* path)
392 {
393 	entry_ref ref;
394 	status_t status = get_ref_for_path(path, &ref);
395 	if (status != B_OK)
396 		return status;
397 
398 	status = be_roster->Launch(&ref);
399 	if (status != B_ALREADY_RUNNING)
400 		return status;
401 
402 	// The application runs already, bring it to front
403 
404 	app_info appInfo;
405 	status = be_roster->GetAppInfo(&ref, &appInfo);
406 	if (status != B_OK)
407 		return status;
408 
409 	return be_roster->ActivateApp(appInfo.team);
410 }
411 
412 
413 status_t
414 MediaReplicant::_LaunchBySignature(const char* signature)
415 {
416 	status_t status = be_roster->Launch(signature);
417 	if (status != B_ALREADY_RUNNING)
418 		return status;
419 
420 	// The application runs already, bring it to front
421 
422 	app_info appInfo;
423 	status = be_roster->GetAppInfo(signature, &appInfo);
424 	if (status != B_OK)
425 		return status;
426 
427 	return be_roster->ActivateApp(appInfo.team);
428 }
429 
430 
431 void
432 MediaReplicant::_Launch(const char* prettyName, const char* signature,
433 	directory_which base, const char* fileName)
434 {
435 	BPath path;
436 	status_t status = find_directory(base, &path);
437 	if (status == B_OK)
438 		path.Append(fileName);
439 
440 	// launch the application
441 	if (_LaunchBySignature(signature) != B_OK
442 		&& _LaunchByPath(path.Path()) != B_OK) {
443 		BString message = B_TRANSLATE("Couldn't launch ");
444 		message << prettyName;
445 
446 		BAlert* alert = new BAlert(B_TRANSLATE("desklink"), message.String(),
447 			B_TRANSLATE("OK"));
448 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
449 		alert->Go();
450 	}
451 }
452 
453 
454 void
455 MediaReplicant::_LoadSettings()
456 {
457 	fDontBeep = false;
458 	fVolumeWhich = VOLUME_USE_MIXER;
459 
460 	BPath path;
461 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK)
462 		return;
463 
464 	path.Append(kSettingsFile);
465 
466 	BFile settings(path.Path(), B_READ_ONLY);
467 	if (settings.InitCheck() < B_OK)
468 		return;
469 
470 	BMessage msg;
471 	if (msg.Unflatten(&settings) < B_OK)
472 		return;
473 
474 	msg.FindInt32("volwhich", &fVolumeWhich);
475 	msg.FindBool("dontbeep", &fDontBeep);
476 }
477 
478 
479 void
480 MediaReplicant::_SaveSettings()
481 {
482 	BPath path;
483 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK)
484 		return;
485 
486 	path.Append(kSettingsFile);
487 
488 	BFile settings(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
489 	if (settings.InitCheck() < B_OK)
490 		return;
491 
492 	BMessage msg('CNFG');
493 	msg.AddInt32("volwhich", fVolumeWhich);
494 	msg.AddBool("dontbeep", fDontBeep);
495 
496 	ssize_t size = 0;
497 	msg.Flatten(&settings, &size);
498 }
499 
500 
501 void
502 MediaReplicant::_Init()
503 {
504 	fIcon = new BBitmap(BRect(0, 0, kSpeakerWidth - 1, kSpeakerHeight - 1),
505 		B_RGBA32);
506 	BIconUtils::GetVectorIcon(kSpeakerIcon, sizeof(kSpeakerIcon), fIcon);
507 
508 	fMutedIcon = new BBitmap(BRect(0, 0, kSpeakerWidth - 1, kSpeakerHeight - 1),
509 		B_RGBA32);
510 	BIconUtils::GetVectorIcon(kMutedSpeakerIcon, sizeof(kMutedSpeakerIcon),
511 		fMutedIcon);
512 
513 	_LoadSettings();
514 
515 	SetToolTip(new VolumeToolTip(fVolumeWhich));
516 }
517 
518 
519 //	#pragma mark -
520 
521 
522 extern "C" BView*
523 instantiate_deskbar_item(void)
524 {
525 	return new MediaReplicant(BRect(0, 0, 16, 16), kReplicantName);
526 }
527 
528