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