xref: /haiku/src/bin/desklink/VolumeControl.cpp (revision b6b0567fbd186f8ce8a0c90bdc7a7b5b4c649678)
1 /*
2  * Copyright 2003-2009, 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 		if (oldValue > newValue) {
227 			// movement from right to left
228 			fMinSnap = _PointForValue(-4);
229 			fMaxSnap = _PointForValue(1);
230 		} else {
231 			// movement from left to right
232 			fMinSnap = _PointForValue(-1);
233 			fMaxSnap = _PointForValue(4);
234 		}
235 
236 		fSnapping = true;
237 		return;
238 	}
239 
240 	BSlider::MouseMoved(where, transit, dragMessage);
241 }
242 
243 
244 void
245 VolumeControl::MessageReceived(BMessage* msg)
246 {
247 	switch (msg->what) {
248 		case B_MOUSE_WHEEL_CHANGED:
249 		{
250 			if (!fMixerControl->Connected())
251 				return;
252 
253 			// Even though the volume bar is horizontal, we use the more common
254 			// vertical mouse wheel change
255 			float deltaY = 0.0f;
256 
257 			msg->FindFloat("be:wheel_delta_y", &deltaY);
258 
259 			if (deltaY == 0.0f)
260 				return;
261 
262 			int32 currentValue = Value();
263 			int32 newValue = currentValue - int32(deltaY) * 3;
264 
265 			if (newValue != currentValue) {
266 				SetValue(newValue);
267 				InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED);
268 			}
269 			break;
270 		}
271 
272 		case B_MEDIA_NEW_PARAMETER_VALUE:
273 			if (IsTracking())
274 				break;
275 
276 			SetValue((int32)fMixerControl->Volume());
277 			break;
278 
279 		case B_ABOUT_REQUESTED:
280 			(new BAlert("About Volume Control", "Volume Control\n"
281 					"  Written by Jérôme DUVAL, and Axel Dörfler.\n\n"
282 					"Copyright " B_UTF8_COPYRIGHT "2003-2009, Haiku",
283 				"OK"))->Go(NULL);
284 			break;
285 
286 		case B_SOME_APP_LAUNCHED:
287 		case B_SOME_APP_QUIT:
288 		{
289 			const char* signature;
290 			if (msg->FindString("be:signature", &signature) != B_OK)
291 				break;
292 
293             bool isMediaServer = !strcmp(signature, kMediaServerSignature);
294             bool isAddOnServer = !strcmp(signature, kAddOnServerSignature);
295             if (isMediaServer)
296                 fMediaServerRunning = msg->what == B_SOME_APP_LAUNCHED;
297             if (isAddOnServer)
298                 fAddOnServerRunning = msg->what == B_SOME_APP_LAUNCHED;
299 
300            if (isMediaServer || isAddOnServer) {
301                 if (!fMediaServerRunning && !fAddOnServerRunning) {
302 					// No media server around
303 					SetLabel("No media server running");
304 					SetEnabled(false);
305                 } else if (fMediaServerRunning && fAddOnServerRunning) {
306                     // HACK!
307                     // quit our now invalid instance of the media roster
308                     // so that before new nodes are created,
309                     // we get a new roster
310                     BMediaRoster* roster = BMediaRoster::CurrentRoster();
311                     if (roster != NULL) {
312                         roster->Lock();
313                         roster->Quit();
314                     }
315 
316 					BMessage reconnect(kMsgReconnectVolume);
317 					BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1);
318 					fConnectRetries = 3;
319                 }
320 			}
321 			break;
322 		}
323 
324 		case kMsgReconnectVolume:
325 			_ConnectVolume();
326 			if (!fMixerControl->Connected() && --fConnectRetries > 1) {
327 				BMessage reconnect(kMsgReconnectVolume);
328 				BMessageRunner::StartSending(this, &reconnect,
329 					6000000LL / fConnectRetries, 1);
330 			}
331 			break;
332 
333 		default:
334 			return BView::MessageReceived(msg);
335 	}
336 }
337 
338 
339 status_t
340 VolumeControl::Invoke(BMessage* message)
341 {
342 	if (fBeep && fOriginalValue != Value() && message == NULL) {
343 		beep();
344 		fOriginalValue = Value();
345 	}
346 
347 	fMixerControl->SetVolume(Value());
348 
349 	return BSlider::Invoke(message);
350 }
351 
352 
353 void
354 VolumeControl::DrawBar()
355 {
356 	BRect frame = BarFrame();
357 	BView* view = OffscreenView();
358 
359 	if (be_control_look != NULL) {
360 		uint32 flags = be_control_look->Flags(this);
361 		rgb_color base = LowColor();
362 		rgb_color rightFillColor = (rgb_color){255, 109, 38, 255};
363 		rgb_color leftFillColor = (rgb_color){116, 224, 0, 255};
364 
365 		int32 min, max;
366 		GetLimits(&min, &max);
367 		float position = (float)min / (min - max);
368 
369 		be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor,
370 			rightFillColor, position, flags, Orientation());
371 		return;
372 	}
373 
374 	BSlider::DrawBar();
375 }
376 
377 
378 const char*
379 VolumeControl::UpdateText() const
380 {
381 	if (!IsEnabled())
382 		return NULL;
383 
384 	snprintf(fText, sizeof(fText), "%ld dB", Value());
385 	return fText;
386 }
387 
388 
389 void
390 VolumeControl::_DisconnectVolume()
391 {
392 	BMediaRoster* roster = BMediaRoster::CurrentRoster();
393 	if (roster != NULL && fMixerControl->GainNode() != media_node::null) {
394 		roster->StopWatching(this, fMixerControl->GainNode(),
395 			B_MEDIA_NEW_PARAMETER_VALUE);
396 	}
397 }
398 
399 
400 void
401 VolumeControl::_ConnectVolume()
402 {
403 	_DisconnectVolume();
404 
405 	const char* errorString = NULL;
406 	float volume = 0.0;
407 	fMixerControl->Connect(fMixerControl->VolumeWhich(), &volume, &errorString);
408 
409 	if (errorString != NULL) {
410 		SetLabel(errorString);
411 		SetLimits(-60, 18);
412 	} else {
413 		SetLabel("Volume");
414 		SetLimits((int32)floorf(fMixerControl->Minimum()),
415 			(int32)ceilf(fMixerControl->Maximum()));
416 
417 		BMediaRoster* roster = BMediaRoster::CurrentRoster();
418 		if (roster != NULL && fMixerControl->GainNode() != media_node::null) {
419 			roster->StartWatching(this, fMixerControl->GainNode(),
420 				B_MEDIA_NEW_PARAMETER_VALUE);
421 		}
422 	}
423 
424 	SetEnabled(errorString == NULL);
425 
426 	fOriginalValue = (int32)volume;
427 	SetValue((int32)volume);
428 }
429 
430 
431 float
432 VolumeControl::_PointForValue(int32 value) const
433 {
434 	int32 min, max;
435 	GetLimits(&min, &max);
436 
437 	if (Orientation() == B_HORIZONTAL) {
438 		return ceilf(1.0f * (value - min) / (max - min)
439 			* (BarFrame().Width() - 2) + BarFrame().left + 1);
440 	}
441 
442 	return ceilf(BarFrame().top - 1.0f * (value - min) / (max - min)
443 		* BarFrame().Height());
444 }
445 
446 
447 bool
448 VolumeControl::_IsReplicant() const
449 {
450 	return dynamic_cast<VolumeWindow*>(Window()) == NULL;
451 }
452