xref: /haiku/src/bin/desklink/VolumeControl.cpp (revision 9cfc4c2ff8117e74b8965d407edb24252dae1aa7)
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 				if (ModificationMessage() != NULL)
249 					Messenger().SendMessage(ModificationMessage());
250 			}
251 			break;
252 		}
253 
254 		case B_MEDIA_NEW_PARAMETER_VALUE:
255 			if (IsTracking())
256 				break;
257 
258 			SetValue((int32)fMixerControl->Volume());
259 			break;
260 
261 		case B_ABOUT_REQUESTED:
262 			(new BAlert("About Volume Control", "Volume Control\n"
263 					"  Written by Jérôme DUVAL, and Axel Dörfler.\n\n"
264 					"Copyright " B_UTF8_COPYRIGHT "2003-2009, Haiku",
265 				"OK"))->Go(NULL);
266 			break;
267 
268 		default:
269 			return BView::MessageReceived(msg);
270 	}
271 }
272 
273 
274 status_t
275 VolumeControl::Invoke(BMessage* message)
276 {
277 	if (fBeep && fOriginalValue != Value() && message == NULL) {
278 		beep();
279 		fOriginalValue = Value();
280 	}
281 
282 	fMixerControl->SetVolume(Value());
283 
284 	return BSlider::Invoke(message);
285 }
286 
287 
288 void
289 VolumeControl::DrawBar()
290 {
291 	BRect frame = BarFrame();
292 	BView* view = OffscreenView();
293 
294 	if (be_control_look != NULL) {
295 		uint32 flags = be_control_look->Flags(this);
296 		rgb_color base = LowColor();
297 		rgb_color rightFillColor = (rgb_color){255, 109, 38, 255};
298 		rgb_color leftFillColor = (rgb_color){116, 224, 0, 255};
299 
300 		int32 min, max;
301 		GetLimits(&min, &max);
302 		float position = 1.0f * (max - min - max) / (max - min);
303 
304 		be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor,
305 			rightFillColor, position, flags, Orientation());
306 		return;
307 	}
308 
309 	BSlider::DrawBar();
310 }
311 
312 
313 const char*
314 VolumeControl::UpdateText() const
315 {
316 	if (!IsEnabled())
317 		return NULL;
318 
319 	snprintf(fText, sizeof(fText), "%ld dB", Value());
320 	return fText;
321 }
322 
323 
324 void
325 VolumeControl::_InitVolume(int32 volumeWhich)
326 {
327 	const char* errorString = NULL;
328 	float volume = 0.0;
329 	fMixerControl = new MixerControl(volumeWhich, &volume, &errorString);
330 
331 	if (errorString != NULL) {
332 		SetLabel(errorString);
333 		SetLimits(-60, 18);
334 	} else {
335 		SetLabel("Volume");
336 		SetLimits((int32)floorf(fMixerControl->Minimum()),
337 			(int32)ceilf(fMixerControl->Maximum()));
338 	}
339 
340 	SetEnabled(errorString == NULL);
341 
342 	fOriginalValue = (int32)volume;
343 	SetValue((int32)volume);
344 }
345 
346 
347 float
348 VolumeControl::_PointForValue(int32 value) const
349 {
350 	int32 min, max;
351 	GetLimits(&min, &max);
352 
353 	if (Orientation() == B_HORIZONTAL) {
354 		return ceilf(1.0f * (value - min) / (max - min)
355 			* (BarFrame().Width() - 2) + BarFrame().left + 1);
356 	} else {
357 		return ceilf(BarFrame().top - 1.0f * (value - min) / (max - min)
358 			* BarFrame().Height());
359 	}
360 }
361 
362 
363 bool
364 VolumeControl::_IsReplicant() const
365 {
366 	return dynamic_cast<VolumeWindow*>(Window()) == NULL;
367 }
368