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