xref: /haiku/src/bin/desklink/VolumeControl.cpp (revision 9157e52c88f73dbf36ac564941303ac95256e654)
1 /*
2  * Copyright 2003-2010, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Jérôme Duval
7  *		François Revol
8  *		Axel Dörfler, axeld@pinc-software.de.
9  */
10 
11 
12 #include "VolumeControl.h"
13 
14 #include <string.h>
15 #include <stdio.h>
16 
17 #include <Alert.h>
18 #include <Application.h>
19 #include <Beep.h>
20 #include <ControlLook.h>
21 #include <Dragger.h>
22 #include <MessageRunner.h>
23 #include <Roster.h>
24 
25 #include <AppMisc.h>
26 
27 #include "desklink.h"
28 #include "MixerControl.h"
29 #include "VolumeWindow.h"
30 
31 
32 static const char* kMediaServerSignature = "application/x-vnd.Be.media-server";
33 static const char* kAddOnServerSignature = "application/x-vnd.Be.addon-host";
34 
35 static const uint32 kMsgReconnectVolume = 'rcms';
36 
37 
38 VolumeControl::VolumeControl(int32 volumeWhich, bool beep, BMessage* message)
39 	:
40 	BSlider("VolumeControl", "Volume", message, 0, 1, B_HORIZONTAL),
41 	fMixerControl(new MixerControl(volumeWhich)),
42 	fBeep(beep),
43 	fSnapping(false),
44 	fConnectRetries(0)
45 {
46 	font_height fontHeight;
47 	GetFontHeight(&fontHeight);
48 	SetBarThickness(ceilf((fontHeight.ascent + fontHeight.descent) * 0.7));
49 
50 	BRect rect(Bounds());
51 	rect.top = rect.bottom - 7;
52 	rect.left = rect.right - 7;
53 	BDragger* dragger = new BDragger(rect, this,
54 		B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
55 	AddChild(dragger);
56 }
57 
58 
59 VolumeControl::VolumeControl(BMessage* archive)
60 	:
61 	BSlider(archive),
62 	fMixerControl(NULL),
63 	fSnapping(false),
64 	fConnectRetries(0)
65 {
66 	if (archive->FindBool("beep", &fBeep) != B_OK)
67 		fBeep = false;
68 
69 	int32 volumeWhich;
70 	if (archive->FindInt32("volume which", &volumeWhich) != B_OK)
71 		volumeWhich = VOLUME_USE_MIXER;
72 
73 	fMixerControl = new MixerControl(volumeWhich);
74 }
75 
76 
77 VolumeControl::~VolumeControl()
78 {
79 	delete fMixerControl;
80 }
81 
82 
83 status_t
84 VolumeControl::Archive(BMessage* into, bool deep) const
85 {
86 	status_t status;
87 
88 	status = BView::Archive(into, deep);
89 	if (status < B_OK)
90 		return status;
91 
92 	status = into->AddString("add_on", kAppSignature);
93 	if (status < B_OK)
94 		return status;
95 
96 	status = into->AddBool("beep", fBeep);
97 	if (status != B_OK)
98 		return status;
99 
100 	return into->AddInt32("volume which", fMixerControl->VolumeWhich());
101 }
102 
103 
104 VolumeControl*
105 VolumeControl::Instantiate(BMessage* archive)
106 {
107 	if (!validate_instantiation(archive, "VolumeControl"))
108 		return NULL;
109 
110 	return new VolumeControl(archive);
111 }
112 
113 
114 void
115 VolumeControl::AttachedToWindow()
116 {
117 	BSlider::AttachedToWindow();
118 
119 	if (_IsReplicant())
120 		SetEventMask(0, 0);
121 	else
122 		SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
123 
124 	be_roster->StartWatching(this, B_REQUEST_LAUNCHED | B_REQUEST_QUIT);
125 
126 	_ConnectVolume();
127 
128 	if (!fMixerControl->Connected()) {
129 		// Wait a bit, and try again - the media server might not have been
130 		// ready yet
131 		BMessage reconnect(kMsgReconnectVolume);
132 		BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1);
133 		fConnectRetries = 3;
134 	}
135 }
136 
137 
138 void
139 VolumeControl::DetachedFromWindow()
140 {
141 	_DisconnectVolume();
142 
143 	be_roster->StopWatching(this);
144 }
145 
146 
147 /*!	Since we have set a mouse event mask, we don't want to forward all
148 	mouse downs to the slider - instead, we only invoke it, which causes a
149 	message to our target. Within the VolumeWindow, this will actually
150 	cause the window to close.
151 	Also, we need to mask out the dragger in this case, or else dragging
152 	us will also cause a volume update.
153 */
154 void
155 VolumeControl::MouseDown(BPoint where)
156 {
157 	// Ignore clicks on the dragger
158 	int32 viewToken;
159 	if (Bounds().Contains(where) && Looper()->CurrentMessage() != NULL
160 		&& Looper()->CurrentMessage()->FindInt32("_view_token",
161 				&viewToken) == B_OK
162 		&& viewToken != _get_object_token_(this))
163 		return;
164 
165 	// TODO: investigate why this does not work as expected (the dragger
166 	// frame seems to be off)
167 #if 0
168 	if (BView* dragger = ChildAt(0)) {
169 		if (!dragger->IsHidden() && dragger->Frame().Contains(where))
170 			return;
171 	}
172 #endif
173 
174 	if (!IsEnabled() || !Bounds().Contains(where)) {
175 		Invoke();
176 		return;
177 	}
178 
179 	BSlider::MouseDown(where);
180 }
181 
182 
183 void
184 VolumeControl::MouseUp(BPoint where)
185 {
186 	fSnapping = false;
187 	BSlider::MouseUp(where);
188 }
189 
190 
191 /*!	Override the BSlider functionality to be able to grab the knob when
192 	it's over 0 dB for some pixels.
193 */
194 void
195 VolumeControl::MouseMoved(BPoint where, uint32 transit,
196 	const BMessage* dragMessage)
197 {
198 	if (!IsTracking()) {
199 		BSlider::MouseMoved(where, transit, dragMessage);
200 		return;
201 	}
202 
203 	float cursorPosition = Orientation() == B_HORIZONTAL ? where.x : where.y;
204 
205 	if (fSnapping && cursorPosition >= fMinSnap && cursorPosition <= fMaxSnap) {
206 		// Don't move the slider, keep the current value for a few
207 		// more pixels
208 		return;
209 	}
210 
211 	fSnapping = false;
212 
213 	int32 oldValue = Value();
214 	int32 newValue = ValueForPoint(where);
215 	if (oldValue == newValue) {
216 		BSlider::MouseMoved(where, transit, dragMessage);
217 		return;
218 	}
219 
220 	// Check if there is a 0 dB transition at all
221 	if ((oldValue < 0 && newValue >= 0) || (oldValue > 0 && newValue <= 0)) {
222 		SetValue(0);
223 		if (ModificationMessage() != NULL)
224 			Messenger().SendMessage(ModificationMessage());
225 
226 		float snapPoint = _PointForValue(0);
227 		const float kMinSnapOffset = 6;
228 
229 		if (oldValue > newValue) {
230 			// movement from right to left
231 			fMinSnap = _PointForValue(-4);
232 			if (fabs(snapPoint - fMinSnap) < kMinSnapOffset)
233 				fMinSnap = snapPoint - kMinSnapOffset;
234 
235 			fMaxSnap = _PointForValue(1);
236 		} else {
237 			// movement from left to right
238 			fMinSnap = _PointForValue(-1);
239 			fMaxSnap = _PointForValue(4);
240 			if (fabs(snapPoint - fMaxSnap) < kMinSnapOffset)
241 				fMaxSnap = snapPoint + kMinSnapOffset;
242 		}
243 
244 		fSnapping = true;
245 		return;
246 	}
247 
248 	BSlider::MouseMoved(where, transit, dragMessage);
249 }
250 
251 
252 void
253 VolumeControl::MessageReceived(BMessage* msg)
254 {
255 	switch (msg->what) {
256 		case B_MOUSE_WHEEL_CHANGED:
257 		{
258 			if (!fMixerControl->Connected())
259 				return;
260 
261 			// Even though the volume bar is horizontal, we use the more common
262 			// vertical mouse wheel change
263 			float deltaY = 0.0f;
264 
265 			msg->FindFloat("be:wheel_delta_y", &deltaY);
266 
267 			if (deltaY == 0.0f)
268 				return;
269 
270 			int32 currentValue = Value();
271 			int32 newValue = currentValue - int32(deltaY) * 3;
272 
273 			if (newValue != currentValue) {
274 				SetValue(newValue);
275 				InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED);
276 			}
277 			break;
278 		}
279 
280 		case B_MEDIA_NEW_PARAMETER_VALUE:
281 			if (IsTracking())
282 				break;
283 
284 			SetValue((int32)fMixerControl->Volume());
285 			break;
286 
287 		case B_ABOUT_REQUESTED:
288 			(new BAlert("About Volume Control", "Volume Control\n"
289 					"  Written by Jérôme DUVAL, and Axel Dörfler.\n\n"
290 					"Copyright " B_UTF8_COPYRIGHT "2003-2009, Haiku",
291 				"OK"))->Go(NULL);
292 			break;
293 
294 		case B_SOME_APP_LAUNCHED:
295 		case B_SOME_APP_QUIT:
296 		{
297 			const char* signature;
298 			if (msg->FindString("be:signature", &signature) != B_OK)
299 				break;
300 
301             bool isMediaServer = !strcmp(signature, kMediaServerSignature);
302             bool isAddOnServer = !strcmp(signature, kAddOnServerSignature);
303             if (isMediaServer)
304                 fMediaServerRunning = msg->what == B_SOME_APP_LAUNCHED;
305             if (isAddOnServer)
306                 fAddOnServerRunning = msg->what == B_SOME_APP_LAUNCHED;
307 
308            if (isMediaServer || isAddOnServer) {
309                 if (!fMediaServerRunning && !fAddOnServerRunning) {
310 					// No media server around
311 					SetLabel("No media server running");
312 					SetEnabled(false);
313                 } else if (fMediaServerRunning && fAddOnServerRunning) {
314                     // HACK!
315                     // quit our now invalid instance of the media roster
316                     // so that before new nodes are created,
317                     // we get a new roster
318                     BMediaRoster* roster = BMediaRoster::CurrentRoster();
319                     if (roster != NULL) {
320                         roster->Lock();
321                         roster->Quit();
322                     }
323 
324 					BMessage reconnect(kMsgReconnectVolume);
325 					BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1);
326 					fConnectRetries = 3;
327                 }
328 			}
329 			break;
330 		}
331 
332 		case kMsgReconnectVolume:
333 			_ConnectVolume();
334 			if (!fMixerControl->Connected() && --fConnectRetries > 1) {
335 				BMessage reconnect(kMsgReconnectVolume);
336 				BMessageRunner::StartSending(this, &reconnect,
337 					6000000LL / fConnectRetries, 1);
338 			}
339 			break;
340 
341 		default:
342 			return BView::MessageReceived(msg);
343 	}
344 }
345 
346 
347 status_t
348 VolumeControl::Invoke(BMessage* message)
349 {
350 	if (fBeep && fOriginalValue != Value() && message == NULL) {
351 		beep();
352 		fOriginalValue = Value();
353 	}
354 
355 	fMixerControl->SetVolume(Value());
356 
357 	return BSlider::Invoke(message);
358 }
359 
360 
361 void
362 VolumeControl::DrawBar()
363 {
364 	BRect frame = BarFrame();
365 	BView* view = OffscreenView();
366 
367 	if (be_control_look != NULL) {
368 		uint32 flags = be_control_look->Flags(this);
369 		rgb_color base = LowColor();
370 		rgb_color rightFillColor = (rgb_color){255, 109, 38, 255};
371 		rgb_color leftFillColor = (rgb_color){116, 224, 0, 255};
372 
373 		int32 min, max;
374 		GetLimits(&min, &max);
375 		float position = (float)min / (min - max);
376 
377 		be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor,
378 			rightFillColor, position, flags, Orientation());
379 		return;
380 	}
381 
382 	BSlider::DrawBar();
383 }
384 
385 
386 const char*
387 VolumeControl::UpdateText() const
388 {
389 	if (!IsEnabled())
390 		return NULL;
391 
392 	snprintf(fText, sizeof(fText), "%ld dB", Value());
393 	return fText;
394 }
395 
396 
397 void
398 VolumeControl::_DisconnectVolume()
399 {
400 	BMediaRoster* roster = BMediaRoster::CurrentRoster();
401 	if (roster != NULL && fMixerControl->GainNode() != media_node::null) {
402 		roster->StopWatching(this, fMixerControl->GainNode(),
403 			B_MEDIA_NEW_PARAMETER_VALUE);
404 	}
405 }
406 
407 
408 void
409 VolumeControl::_ConnectVolume()
410 {
411 	_DisconnectVolume();
412 
413 	const char* errorString = NULL;
414 	float volume = 0.0;
415 	fMixerControl->Connect(fMixerControl->VolumeWhich(), &volume, &errorString);
416 
417 	if (errorString != NULL) {
418 		SetLabel(errorString);
419 		SetLimits(-60, 18);
420 	} else {
421 		SetLabel("Volume");
422 		SetLimits((int32)floorf(fMixerControl->Minimum()),
423 			(int32)ceilf(fMixerControl->Maximum()));
424 
425 		BMediaRoster* roster = BMediaRoster::CurrentRoster();
426 		if (roster != NULL && fMixerControl->GainNode() != media_node::null) {
427 			roster->StartWatching(this, fMixerControl->GainNode(),
428 				B_MEDIA_NEW_PARAMETER_VALUE);
429 		}
430 	}
431 
432 	SetEnabled(errorString == NULL);
433 
434 	fOriginalValue = (int32)volume;
435 	SetValue((int32)volume);
436 }
437 
438 
439 float
440 VolumeControl::_PointForValue(int32 value) const
441 {
442 	int32 min, max;
443 	GetLimits(&min, &max);
444 
445 	if (Orientation() == B_HORIZONTAL) {
446 		return ceilf(1.0f * (value - min) / (max - min)
447 			* (BarFrame().Width() - 2) + BarFrame().left + 1);
448 	}
449 
450 	return ceilf(BarFrame().top - 1.0f * (value - min) / (max - min)
451 		* BarFrame().Height());
452 }
453 
454 
455 bool
456 VolumeControl::_IsReplicant() const
457 {
458 	return dynamic_cast<VolumeWindow*>(Window()) == NULL;
459 }
460