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