xref: /haiku/src/bin/desklink/MediaReplicant.cpp (revision 62f5ba006a08b0df30631375878effaf67ae5dbc)
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 	SetDrawingMode(B_OP_OVER);
203 	DrawBitmap(fIcon);
204 }
205 
206 
207 void
208 MediaReplicant::MouseDown(BPoint point)
209 {
210 	int32 buttons = B_PRIMARY_MOUSE_BUTTON;
211 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
212 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
213 
214 	BPoint where = ConvertToScreen(point);
215 
216 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
217 		BPopUpMenu* menu = new BPopUpMenu("", false, false);
218 		menu->SetFont(be_plain_font);
219 
220 		menu->AddItem(new BMenuItem("Media preferences" B_UTF8_ELLIPSIS,
221 			new BMessage(kMsgOpenMediaSettings)));
222 		menu->AddItem(new BMenuItem("Sound preferences" B_UTF8_ELLIPSIS,
223 			new BMessage(kMsgOpenSoundSettings)));
224 
225 		menu->AddSeparatorItem();
226 
227 		menu->AddItem(new BMenuItem("Open MediaPlayer",
228 			new BMessage(kMsgOpenMediaPlayer)));
229 
230 		menu->AddSeparatorItem();
231 
232 		BMenu* subMenu = new BMenu("Options");
233 		menu->AddItem(subMenu);
234 
235 		BMenuItem* item = new BMenuItem("Control physical output",
236 			new BMessage(kMsgVolumeWhich));
237 		item->SetMarked(fVolumeWhich == VOLUME_USE_PHYS_OUTPUT);
238 		subMenu->AddItem(item);
239 
240 		item = new BMenuItem("Beep", new BMessage(kMsgToggleBeep));
241 		item->SetMarked(!fDontBeep);
242 		subMenu->AddItem(item);
243 
244 		menu->AddSeparatorItem();
245 
246 		menu->AddItem(new BMenuItem("About" B_UTF8_ELLIPSIS,
247 			new BMessage(B_ABOUT_REQUESTED)));
248 
249 		menu->SetTargetForItems(this);
250 		subMenu->SetTargetForItems(this);
251 
252 		menu->Go(where, true, true, BRect(where - BPoint(4, 4),
253 			where + BPoint(4, 4)));
254 	} else {
255 		// Show VolumeWindow
256 		fVolumeSlider = new VolumeWindow(BRect(where.x, where.y,
257 			where.x + 207, where.y + 19), fDontBeep, fVolumeWhich);
258 		fVolumeSlider->Show();
259 	}
260 }
261 
262 
263 void
264 MediaReplicant::MessageReceived(BMessage* message)
265 {
266 	switch (message->what) {
267 		case B_ABOUT_REQUESTED:
268 			(new BAlert("About Volume Control", "Volume Control\n"
269 					"  Brought to you by Jérôme DUVAL.\n\n"
270 					"Copyright " B_UTF8_COPYRIGHT "2003-2009, Haiku",
271 				"OK"))->Go(NULL);
272 			break;
273 
274 		case kMsgOpenMediaPlayer:
275 			_Launch("MediaPlayer", "application/x-vnd.Haiku-MediaPlayer",
276 				B_SYSTEM_APPS_DIRECTORY, "MediaPlayer");
277 			break;
278 
279 		case kMsgOpenMediaSettings:
280 			_Launch("Media Preferences", "application/x-vnd.Haiku-Media",
281 				B_SYSTEM_PREFERENCES_DIRECTORY, "Media");
282 			break;
283 
284 		case kMsgOpenSoundSettings:
285 			_Launch("Sounds Preferences", "application/x-vnd.Haiku-Sounds",
286 				B_SYSTEM_PREFERENCES_DIRECTORY, "Sounds");
287 			break;
288 
289 		case kMsgToggleBeep:
290 		{
291 			BMenuItem* item;
292 			if (message->FindPointer("source", (void**)&item) != B_OK)
293 				return;
294 
295 			item->SetMarked(!item->IsMarked());
296 			fDontBeep = !item->IsMarked();
297 			break;
298 		}
299 
300 		case kMsgVolumeWhich:
301 		{
302 			BMenuItem* item;
303 			if (message->FindPointer("source", (void**)&item) != B_OK)
304 				return;
305 
306 			item->SetMarked(!item->IsMarked());
307 			fVolumeWhich = item->IsMarked()
308 				? VOLUME_USE_PHYS_OUTPUT : VOLUME_USE_MIXER;
309 
310 			if (VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip()))
311 				tip->SetWhich(fVolumeWhich);
312 			break;
313 		}
314 
315 		case B_MOUSE_WHEEL_CHANGED:
316 		{
317 			float deltaY;
318 			if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK
319 				&& deltaY != 0.0) {
320 				MixerControl mixerControl;
321 				mixerControl.Connect(fVolumeWhich);
322 				mixerControl.ChangeVolumeBy(deltaY < 0 ? 6 : -6);
323 
324 				VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip());
325 				if (tip != NULL) {
326 					tip->Update();
327 					ShowToolTip(tip);
328 				}
329 			}
330 			break;
331 		}
332 
333 		default:
334 			BView::MessageReceived(message);
335 			break;
336 	}
337 }
338 
339 
340 status_t
341 MediaReplicant::_LaunchByPath(const char* path)
342 {
343 	entry_ref ref;
344 	status_t status = get_ref_for_path(path, &ref);
345 	if (status != B_OK)
346 		return status;
347 
348 	status = be_roster->Launch(&ref);
349 	if (status != B_ALREADY_RUNNING)
350 		return status;
351 
352 	// The application runs already, bring it to front
353 
354 	app_info appInfo;
355 	status = be_roster->GetAppInfo(&ref, &appInfo);
356 	if (status != B_OK)
357 		return status;
358 
359 	return be_roster->ActivateApp(appInfo.team);
360 }
361 
362 
363 status_t
364 MediaReplicant::_LaunchBySignature(const char* signature)
365 {
366 	status_t status = be_roster->Launch(signature);
367 	if (status != B_ALREADY_RUNNING)
368 		return status;
369 
370 	// The application runs already, bring it to front
371 
372 	app_info appInfo;
373 	status = be_roster->GetAppInfo(signature, &appInfo);
374 	if (status != B_OK)
375 		return status;
376 
377 	return be_roster->ActivateApp(appInfo.team);
378 }
379 
380 
381 void
382 MediaReplicant::_Launch(const char* prettyName, const char* signature,
383 	directory_which base, const char* fileName)
384 {
385 	BPath path;
386 	status_t status = find_directory(base, &path);
387 	if (status == B_OK)
388 		path.Append(fileName);
389 
390 	// launch the application
391 	if (_LaunchBySignature(signature) != B_OK
392 		&& _LaunchByPath(path.Path()) != B_OK) {
393 		BString message = "Couldn't launch ";
394 		message << prettyName;
395 
396 		(new BAlert("desklink", message.String(), "OK"))->Go();
397 	}
398 }
399 
400 
401 void
402 MediaReplicant::_LoadSettings()
403 {
404 	fDontBeep = false;
405 	fVolumeWhich = VOLUME_USE_MIXER;
406 
407 	BPath path;
408 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK)
409 		return;
410 
411 	path.Append(kSettingsFile);
412 
413 	BFile settings(path.Path(), B_READ_ONLY);
414 	if (settings.InitCheck() < B_OK)
415 		return;
416 
417 	BMessage msg;
418 	if (msg.Unflatten(&settings) < B_OK)
419 		return;
420 
421 	msg.FindInt32("volwhich", &fVolumeWhich);
422 	msg.FindBool("dontbeep", &fDontBeep);
423 }
424 
425 
426 void
427 MediaReplicant::_SaveSettings()
428 {
429 	BPath path;
430 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK)
431 		return;
432 
433 	path.Append(kSettingsFile);
434 
435 	BFile settings(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
436 	if (settings.InitCheck() < B_OK)
437 		return;
438 
439 	BMessage msg('CNFG');
440 	msg.AddInt32("volwhich", fVolumeWhich);
441 	msg.AddBool("dontbeep", fDontBeep);
442 
443 	ssize_t size = 0;
444 	msg.Flatten(&settings, &size);
445 }
446 
447 
448 void
449 MediaReplicant::_Init()
450 {
451 	fIcon = new BBitmap(BRect(0, 0, kSpeakerWidth - 1, kSpeakerHeight - 1),
452 		B_CMAP8);
453 	fIcon->SetBits(kSpeakerBits, kSpeakerWidth * kSpeakerHeight, 0, B_CMAP8);
454 
455 	_LoadSettings();
456 
457 	SetToolTip(new VolumeToolTip(fVolumeWhich));
458 }
459 
460 
461 //	#pragma mark -
462 
463 
464 extern "C" BView*
465 instantiate_deskbar_item(void)
466 {
467 	return new MediaReplicant(BRect(0, 0, 16, 16), kReplicantName);
468 }
469 
470