xref: /haiku/src/bin/desklink/VolumeControl.cpp (revision 9829800d2c60d6aba146af7fde09601929161730)
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_TRANSLATE_CONTEXT
35 #define B_TRANSLATE_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 
83 
84 VolumeControl::~VolumeControl()
85 {
86 	delete fMixerControl;
87 }
88 
89 
90 status_t
91 VolumeControl::Archive(BMessage* into, bool deep) const
92 {
93 	status_t status;
94 
95 	status = BView::Archive(into, deep);
96 	if (status < B_OK)
97 		return status;
98 
99 	status = into->AddString("add_on", kAppSignature);
100 	if (status < B_OK)
101 		return status;
102 
103 	status = into->AddBool("beep", fBeep);
104 	if (status != B_OK)
105 		return status;
106 
107 	return into->AddInt32("volume which", fMixerControl->VolumeWhich());
108 }
109 
110 
111 VolumeControl*
112 VolumeControl::Instantiate(BMessage* archive)
113 {
114 	if (!validate_instantiation(archive, "VolumeControl"))
115 		return NULL;
116 
117 	return new VolumeControl(archive);
118 }
119 
120 
121 void
122 VolumeControl::AttachedToWindow()
123 {
124 	BSlider::AttachedToWindow();
125 
126 	if (_IsReplicant())
127 		SetEventMask(0, 0);
128 	else
129 		SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
130 
131 	be_roster->StartWatching(this, B_REQUEST_LAUNCHED | B_REQUEST_QUIT);
132 
133 	_ConnectVolume();
134 
135 	if (!fMixerControl->Connected()) {
136 		// Wait a bit, and try again - the media server might not have been
137 		// ready yet
138 		BMessage reconnect(kMsgReconnectVolume);
139 		BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1);
140 		fConnectRetries = 3;
141 	}
142 }
143 
144 
145 void
146 VolumeControl::DetachedFromWindow()
147 {
148 	_DisconnectVolume();
149 
150 	be_roster->StopWatching(this);
151 }
152 
153 
154 /*!	Since we have set a mouse event mask, we don't want to forward all
155 	mouse downs to the slider - instead, we only invoke it, which causes a
156 	message to our target. Within the VolumeWindow, this will actually
157 	cause the window to close.
158 	Also, we need to mask out the dragger in this case, or else dragging
159 	us will also cause a volume update.
160 */
161 void
162 VolumeControl::MouseDown(BPoint where)
163 {
164 	// Ignore clicks on the dragger
165 	int32 viewToken;
166 	if (Bounds().Contains(where) && Looper()->CurrentMessage() != NULL
167 		&& Looper()->CurrentMessage()->FindInt32("_view_token",
168 				&viewToken) == B_OK
169 		&& viewToken != _get_object_token_(this))
170 		return;
171 
172 	// TODO: investigate why this does not work as expected (the dragger
173 	// frame seems to be off)
174 #if 0
175 	if (BView* dragger = ChildAt(0)) {
176 		if (!dragger->IsHidden() && dragger->Frame().Contains(where))
177 			return;
178 	}
179 #endif
180 
181 	if (!IsEnabled() || !Bounds().Contains(where)) {
182 		Invoke();
183 		return;
184 	}
185 
186 	BSlider::MouseDown(where);
187 }
188 
189 
190 void
191 VolumeControl::MouseUp(BPoint where)
192 {
193 	fSnapping = false;
194 	BSlider::MouseUp(where);
195 }
196 
197 
198 /*!	Override the BSlider functionality to be able to grab the knob when
199 	it's over 0 dB for some pixels.
200 */
201 void
202 VolumeControl::MouseMoved(BPoint where, uint32 transit,
203 	const BMessage* dragMessage)
204 {
205 	if (!IsTracking()) {
206 		BSlider::MouseMoved(where, transit, dragMessage);
207 		return;
208 	}
209 
210 	float cursorPosition = Orientation() == B_HORIZONTAL ? where.x : where.y;
211 
212 	if (fSnapping && cursorPosition >= fMinSnap && cursorPosition <= fMaxSnap) {
213 		// Don't move the slider, keep the current value for a few
214 		// more pixels
215 		return;
216 	}
217 
218 	fSnapping = false;
219 
220 	int32 oldValue = Value();
221 	int32 newValue = ValueForPoint(where);
222 	if (oldValue == newValue) {
223 		BSlider::MouseMoved(where, transit, dragMessage);
224 		return;
225 	}
226 
227 	// Check if there is a 0 dB transition at all
228 	if ((oldValue < 0 && newValue >= 0) || (oldValue > 0 && newValue <= 0)) {
229 		SetValue(0);
230 		if (ModificationMessage() != NULL)
231 			Messenger().SendMessage(ModificationMessage());
232 
233 		float snapPoint = _PointForValue(0);
234 		const float kMinSnapOffset = 6;
235 
236 		if (oldValue > newValue) {
237 			// movement from right to left
238 			fMinSnap = _PointForValue(-4);
239 			if (fabs(snapPoint - fMinSnap) < kMinSnapOffset)
240 				fMinSnap = snapPoint - kMinSnapOffset;
241 
242 			fMaxSnap = _PointForValue(1);
243 		} else {
244 			// movement from left to right
245 			fMinSnap = _PointForValue(-1);
246 			fMaxSnap = _PointForValue(4);
247 			if (fabs(snapPoint - fMaxSnap) < kMinSnapOffset)
248 				fMaxSnap = snapPoint + kMinSnapOffset;
249 		}
250 
251 		fSnapping = true;
252 		return;
253 	}
254 
255 	BSlider::MouseMoved(where, transit, dragMessage);
256 }
257 
258 
259 void
260 VolumeControl::MessageReceived(BMessage* msg)
261 {
262 	switch (msg->what) {
263 		case B_MOUSE_WHEEL_CHANGED:
264 		{
265 			if (!fMixerControl->Connected())
266 				return;
267 
268 			// Even though the volume bar is horizontal, we use the more common
269 			// vertical mouse wheel change
270 			float deltaY = 0.0f;
271 
272 			msg->FindFloat("be:wheel_delta_y", &deltaY);
273 
274 			if (deltaY == 0.0f)
275 				return;
276 
277 			int32 currentValue = Value();
278 			int32 newValue = currentValue - int32(deltaY) * 3;
279 
280 			if (newValue != currentValue) {
281 				SetValue(newValue);
282 				InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED);
283 			}
284 			break;
285 		}
286 
287 		case B_MEDIA_NEW_PARAMETER_VALUE:
288 			if (IsTracking())
289 				break;
290 
291 			SetValue((int32)fMixerControl->Volume());
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(B_TRANSLATE("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 	fText.SetToFormat(B_TRANSLATE("%ld dB"), Value());
393 	return fText.String();
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(B_TRANSLATE("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