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