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