xref: /haiku/src/bin/desklink/VolumeControl.cpp (revision 17889a8c70dbb3d59c1412f6431968753c767bab)
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 <MediaRoster.h>
23 #include <MessageRunner.h>
24 
25 #include <AppMisc.h>
26 
27 #include "desklink.h"
28 #include "MixerControl.h"
29 #include "VolumeWindow.h"
30 
31 
32 #undef B_TRANSLATION_CONTEXT
33 #define B_TRANSLATION_CONTEXT "VolumeControl"
34 
35 
36 static const uint32 kMsgReconnectVolume = 'rcms';
37 
38 
39 VolumeControl::VolumeControl(int32 volumeWhich, bool beep, BMessage* message)
40 	:
41 	BSlider("VolumeControl", B_TRANSLATE("Volume"),
42 		message, 0, 1, B_HORIZONTAL),
43 	fMixerControl(new MixerControl(volumeWhich)),
44 	fBeep(beep),
45 	fSnapping(false),
46 	fConnectRetries(0)
47 {
48 	font_height fontHeight;
49 	GetFontHeight(&fontHeight);
50 	SetBarThickness(ceilf((fontHeight.ascent + fontHeight.descent) * 0.7));
51 
52 	BRect rect(Bounds());
53 	rect.top = rect.bottom - 7;
54 	rect.left = rect.right - 7;
55 	BDragger* dragger = new BDragger(rect, this,
56 		B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
57 	AddChild(dragger);
58 }
59 
60 
61 VolumeControl::VolumeControl(BMessage* archive)
62 	:
63 	BSlider(archive),
64 	fMixerControl(NULL),
65 	fSnapping(false),
66 	fConnectRetries(0)
67 {
68 	if (archive->FindBool("beep", &fBeep) != B_OK)
69 		fBeep = false;
70 
71 	int32 volumeWhich;
72 	if (archive->FindInt32("volume which", &volumeWhich) != B_OK)
73 		volumeWhich = VOLUME_USE_MIXER;
74 
75 	fMixerControl = new MixerControl(volumeWhich);
76 
77 	BMessage msg(B_QUIT_REQUESTED);
78 	archive->SendReply(&msg);
79 }
80 
81 
82 VolumeControl::~VolumeControl()
83 {
84 	delete fMixerControl;
85 }
86 
87 
88 status_t
89 VolumeControl::Archive(BMessage* into, bool deep) const
90 {
91 	status_t status;
92 
93 	status = BView::Archive(into, deep);
94 	if (status < B_OK)
95 		return status;
96 
97 	status = into->AddString("add_on", kAppSignature);
98 	if (status < B_OK)
99 		return status;
100 
101 	status = into->AddBool("beep", fBeep);
102 	if (status != B_OK)
103 		return status;
104 
105 	return into->AddInt32("volume which", fMixerControl->VolumeWhich());
106 }
107 
108 
109 VolumeControl*
110 VolumeControl::Instantiate(BMessage* archive)
111 {
112 	if (!validate_instantiation(archive, "VolumeControl"))
113 		return NULL;
114 
115 	return new VolumeControl(archive);
116 }
117 
118 
119 void
120 VolumeControl::AttachedToWindow()
121 {
122 	BSlider::AttachedToWindow();
123 
124 	if (_IsReplicant())
125 		SetEventMask(0, 0);
126 	else
127 		SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
128 
129 	BMediaRoster* roster = BMediaRoster::Roster();
130 	roster->StartWatching(BMessenger(this), B_MEDIA_SERVER_STARTED);
131 	roster->StartWatching(BMessenger(this), B_MEDIA_SERVER_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 	BMediaRoster* roster = BMediaRoster::CurrentRoster();
151 	roster->StopWatching(BMessenger(this), B_MEDIA_SERVER_STARTED);
152 	roster->StopWatching(BMessenger(this), B_MEDIA_SERVER_QUIT);
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_MEDIA_SERVER_STARTED:
297 		{
298 			BMessage reconnect(kMsgReconnectVolume);
299 			BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1);
300 			fConnectRetries = 3;
301 			break;
302 		}
303 
304 		case B_MEDIA_SERVER_QUIT:
305 		{
306 			// No media server around
307 			SetLabel(B_TRANSLATE("No media server running"));
308 			SetEnabled(false);
309 			break;
310 		}
311 
312 		case B_QUIT_REQUESTED:
313 			Window()->MessageReceived(msg);
314 			break;
315 
316 		case kMsgReconnectVolume:
317 			_ConnectVolume();
318 			if (!fMixerControl->Connected() && --fConnectRetries > 1) {
319 				BMessage reconnect(kMsgReconnectVolume);
320 				BMessageRunner::StartSending(this, &reconnect,
321 					6000000LL / fConnectRetries, 1);
322 			}
323 			break;
324 
325 		default:
326 			return BView::MessageReceived(msg);
327 	}
328 }
329 
330 
331 status_t
332 VolumeControl::Invoke(BMessage* message)
333 {
334 	if (fBeep && fOriginalValue != Value() && message == NULL) {
335 		beep();
336 		fOriginalValue = Value();
337 	}
338 
339 	fMixerControl->SetVolume(Value());
340 
341 	return BSlider::Invoke(message);
342 }
343 
344 
345 void
346 VolumeControl::DrawBar()
347 {
348 	BRect frame = BarFrame();
349 	BView* view = OffscreenView();
350 
351 	if (be_control_look != NULL) {
352 		uint32 flags = be_control_look->Flags(this);
353 		rgb_color base = LowColor();
354 		rgb_color rightFillColor = (rgb_color){255, 109, 38, 255};
355 		rgb_color leftFillColor = (rgb_color){116, 224, 0, 255};
356 
357 		int32 min, max;
358 		GetLimits(&min, &max);
359 		float position = (float)min / (min - max);
360 
361 		be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor,
362 			rightFillColor, position, flags, Orientation());
363 		return;
364 	}
365 
366 	BSlider::DrawBar();
367 }
368 
369 
370 const char*
371 VolumeControl::UpdateText() const
372 {
373 	if (!IsEnabled())
374 		return NULL;
375 
376 	fText.SetToFormat(B_TRANSLATE("%" B_PRId32 " dB"), Value());
377 	return fText.String();
378 }
379 
380 
381 void
382 VolumeControl::_DisconnectVolume()
383 {
384 	BMediaRoster* roster = BMediaRoster::CurrentRoster();
385 	if (roster != NULL && fMixerControl->GainNode() != media_node::null) {
386 		roster->StopWatching(this, fMixerControl->GainNode(),
387 			B_MEDIA_NEW_PARAMETER_VALUE);
388 	}
389 }
390 
391 
392 void
393 VolumeControl::_ConnectVolume()
394 {
395 	_DisconnectVolume();
396 
397 	const char* errorString = NULL;
398 	float volume = 0.0;
399 	fMixerControl->Connect(fMixerControl->VolumeWhich(), &volume, &errorString);
400 
401 	if (errorString != NULL) {
402 		SetLabel(errorString);
403 		SetLimits(-60, 18);
404 	} else {
405 		SetLabel(B_TRANSLATE("Volume"));
406 		SetLimits((int32)floorf(fMixerControl->Minimum()),
407 			(int32)ceilf(fMixerControl->Maximum()));
408 
409 		BMediaRoster* roster = BMediaRoster::CurrentRoster();
410 		if (roster != NULL && fMixerControl->GainNode() != media_node::null) {
411 			roster->StartWatching(this, fMixerControl->GainNode(),
412 				B_MEDIA_NEW_PARAMETER_VALUE);
413 		}
414 	}
415 
416 	SetEnabled(errorString == NULL);
417 
418 	fOriginalValue = (int32)volume;
419 	SetValue((int32)volume);
420 }
421 
422 
423 float
424 VolumeControl::_PointForValue(int32 value) const
425 {
426 	int32 min, max;
427 	GetLimits(&min, &max);
428 
429 	if (Orientation() == B_HORIZONTAL) {
430 		return ceilf(1.0f * (value - min) / (max - min)
431 			* (BarFrame().Width() - 2) + BarFrame().left + 1);
432 	}
433 
434 	return ceilf(BarFrame().top - 1.0f * (value - min) / (max - min)
435 		* BarFrame().Height());
436 }
437 
438 
439 bool
440 VolumeControl::_IsReplicant() const
441 {
442 	return dynamic_cast<VolumeWindow*>(Window()) == NULL;
443 }
444