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
36static const uint32 kMsgReconnectVolume = 'rcms';
37
38
39VolumeControl::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
61VolumeControl::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
82VolumeControl::~VolumeControl()
83{
84	delete fMixerControl;
85}
86
87
88status_t
89VolumeControl::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
109VolumeControl*
110VolumeControl::Instantiate(BMessage* archive)
111{
112	if (!validate_instantiation(archive, "VolumeControl"))
113		return NULL;
114
115	return new VolumeControl(archive);
116}
117
118
119void
120VolumeControl::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
145void
146VolumeControl::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*/
163void
164VolumeControl::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
192void
193VolumeControl::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*/
203void
204VolumeControl::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
261void
262VolumeControl::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
331status_t
332VolumeControl::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
345void
346VolumeControl::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
370const char*
371VolumeControl::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
381void
382VolumeControl::_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
392void
393VolumeControl::_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
423float
424VolumeControl::_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
439bool
440VolumeControl::_IsReplicant() const
441{
442	return dynamic_cast<VolumeWindow*>(Window()) == NULL;
443}
444