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