* Copyright 2003-2010, Haiku, Inc.
* Distributed under the terms of the MIT license.
*
* Authors:
* Jérôme Duval
* François Revol
* Axel Dörfler, axeld@pinc-software.de.
*/
#include "VolumeControl.h"
#include <string.h>
#include <stdio.h>
#include <Application.h>
#include <Beep.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Dragger.h>
#include <MediaRoster.h>
#include <MessageRunner.h>
#include <AppMisc.h>
#include "desklink.h"
#include "MixerControl.h"
#include "VolumeWindow.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "VolumeControl"
static const uint32 kMsgReconnectVolume = 'rcms';
VolumeControl::VolumeControl(int32 volumeWhich, bool beep, BMessage* message)
:
BSlider("VolumeControl", B_TRANSLATE("Volume"), message, 0, 1, B_HORIZONTAL),
fMixerControl(new MixerControl(volumeWhich)),
fBeep(beep),
fSnapping(false),
fConnectRetries(0)
{
font_height fontHeight;
GetFontHeight(&fontHeight);
SetBarThickness(ceilf((fontHeight.ascent + fontHeight.descent) * 0.7));
BRect rect(Bounds());
rect.top = rect.bottom - 7;
rect.left = rect.right - 7;
BDragger* dragger = new BDragger(rect, this, B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
AddChild(dragger);
}
VolumeControl::VolumeControl(BMessage* archive)
:
BSlider(archive),
fMixerControl(NULL),
fSnapping(false),
fConnectRetries(0)
{
if (archive->FindBool("beep", &fBeep) != B_OK)
fBeep = false;
int32 volumeWhich;
if (archive->FindInt32("volume which", &volumeWhich) != B_OK)
volumeWhich = VOLUME_USE_MIXER;
fMixerControl = new MixerControl(volumeWhich);
BMessage msg(B_QUIT_REQUESTED);
archive->SendReply(&msg);
}
VolumeControl::~VolumeControl()
{
delete fMixerControl;
}
status_t
VolumeControl::Archive(BMessage* into, bool deep) const
{
status_t status;
status = BView::Archive(into, deep);
if (status < B_OK)
return status;
status = into->AddString("add_on", kAppSignature);
if (status < B_OK)
return status;
status = into->AddBool("beep", fBeep);
if (status != B_OK)
return status;
return into->AddInt32("volume which", fMixerControl->VolumeWhich());
}
VolumeControl*
VolumeControl::Instantiate(BMessage* archive)
{
if (!validate_instantiation(archive, "VolumeControl"))
return NULL;
return new VolumeControl(archive);
}
void
VolumeControl::AttachedToWindow()
{
BSlider::AttachedToWindow();
if (_IsReplicant()) {
SetEventMask(0, 0);
SetDrawingMode(B_OP_ALPHA);
SetFlags(Flags() | B_TRANSPARENT_BACKGROUND);
SetViewColor(B_TRANSPARENT_COLOR);
} else
SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
BMediaRoster* roster = BMediaRoster::Roster();
roster->StartWatching(BMessenger(this), B_MEDIA_SERVER_STARTED);
roster->StartWatching(BMessenger(this), B_MEDIA_SERVER_QUIT);
_ConnectVolume();
if (!fMixerControl->Connected()) {
BMessage reconnect(kMsgReconnectVolume);
BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1);
fConnectRetries = 3;
}
}
void
VolumeControl::DetachedFromWindow()
{
_DisconnectVolume();
BMediaRoster* roster = BMediaRoster::CurrentRoster();
roster->StopWatching(BMessenger(this), B_MEDIA_SERVER_STARTED);
roster->StopWatching(BMessenger(this), B_MEDIA_SERVER_QUIT);
}
mouse downs to the slider - instead, we only invoke it, which causes a
message to our target. Within the VolumeWindow, this will actually
cause the window to close.
Also, we need to mask out the dragger in this case, or else dragging
us will also cause a volume update.
*/
void
VolumeControl::MouseDown(BPoint where)
{
int32 viewToken;
if (Bounds().Contains(where) && Looper()->CurrentMessage() != NULL
&& Looper()->CurrentMessage()->FindInt32("_view_token",
&viewToken) == B_OK
&& viewToken != _get_object_token_(this))
return;
#if 0
if (BView* dragger = ChildAt(0)) {
if (!dragger->IsHidden() && dragger->Frame().Contains(where))
return;
}
#endif
if (!IsEnabled() || !Bounds().Contains(where)) {
Invoke();
return;
}
BSlider::MouseDown(where);
}
void
VolumeControl::MouseUp(BPoint where)
{
fSnapping = false;
BSlider::MouseUp(where);
}
it's over 0 dB for some pixels.
*/
void
VolumeControl::MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage)
{
if (!IsTracking()) {
BSlider::MouseMoved(where, transit, dragMessage);
return;
}
float cursorPosition = Orientation() == B_HORIZONTAL ? where.x : where.y;
if (fSnapping && cursorPosition >= fMinSnap && cursorPosition <= fMaxSnap) {
return;
}
fSnapping = false;
int32 oldValue = Value();
int32 newValue = ValueForPoint(where);
if (oldValue == newValue) {
BSlider::MouseMoved(where, transit, dragMessage);
return;
}
if ((oldValue < 0 && newValue >= 0) || (oldValue > 0 && newValue <= 0)) {
SetValue(0);
if (ModificationMessage() != NULL)
Messenger().SendMessage(ModificationMessage());
float snapPoint = _PointForValue(0);
const float kMinSnapOffset = 6;
if (oldValue > newValue) {
fMinSnap = _PointForValue(-4);
if (fabs(snapPoint - fMinSnap) < kMinSnapOffset)
fMinSnap = snapPoint - kMinSnapOffset;
fMaxSnap = _PointForValue(1);
} else {
fMinSnap = _PointForValue(-1);
fMaxSnap = _PointForValue(4);
if (fabs(snapPoint - fMaxSnap) < kMinSnapOffset)
fMaxSnap = snapPoint + kMinSnapOffset;
}
fSnapping = true;
return;
}
BSlider::MouseMoved(where, transit, dragMessage);
}
void
VolumeControl::MessageReceived(BMessage* msg)
{
switch (msg->what) {
case B_MOUSE_WHEEL_CHANGED:
{
if (!fMixerControl->Connected())
return;
float deltaY = 0.0f;
msg->FindFloat("be:wheel_delta_y", &deltaY);
if (deltaY == 0.0f)
return;
int32 currentValue = Value();
int32 newValue = currentValue - int32(deltaY) * 3;
if (newValue != currentValue) {
SetValue(newValue);
InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED);
}
break;
}
case B_MEDIA_NEW_PARAMETER_VALUE:
if (IsTracking())
break;
SetValue((int32)fMixerControl->Volume());
break;
case B_MEDIA_SERVER_STARTED:
{
BMessage reconnect(kMsgReconnectVolume);
BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1);
fConnectRetries = 3;
break;
}
case B_MEDIA_SERVER_QUIT:
{
SetLabel(B_TRANSLATE("No media server running"));
SetEnabled(false);
break;
}
case B_QUIT_REQUESTED:
Window()->MessageReceived(msg);
break;
case kMsgReconnectVolume:
_ConnectVolume();
if (!fMixerControl->Connected() && --fConnectRetries > 1) {
BMessage reconnect(kMsgReconnectVolume);
BMessageRunner::StartSending(this, &reconnect,
6000000LL / fConnectRetries, 1);
}
break;
case B_WORKSPACE_ACTIVATED:
if (_IsReplicant())
Invalidate();
break;
default:
return BView::MessageReceived(msg);
}
}
status_t
VolumeControl::Invoke(BMessage* message)
{
if (fBeep && fOriginalValue != Value() && message == NULL) {
beep();
fOriginalValue = Value();
}
fMixerControl->SetVolume(Value());
return BSlider::Invoke(message);
}
void
VolumeControl::DrawBar()
{
BRect frame = BarFrame();
BView* view = OffscreenView();
uint32 flags = be_control_look->Flags(this);
rgb_color base = LowColor();
rgb_color rightFillColor = make_color(255, 109, 38, 255);
rgb_color leftFillColor = make_color(116, 224, 0, 255);
int32 min, max;
GetLimits(&min, &max);
float position = (float)min / (min - max);
be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor,
rightFillColor, position, flags, Orientation());
}
const char*
VolumeControl::UpdateText() const
{
if (!IsEnabled())
return NULL;
fText.SetToFormat(B_TRANSLATE("%" B_PRId32 " dB"), Value());
return fText.String();
}
void
VolumeControl::_DisconnectVolume()
{
BMediaRoster* roster = BMediaRoster::CurrentRoster();
if (roster != NULL && fMixerControl->GainNode() != media_node::null) {
roster->StopWatching(this, fMixerControl->GainNode(),
B_MEDIA_NEW_PARAMETER_VALUE);
}
}
void
VolumeControl::_ConnectVolume()
{
_DisconnectVolume();
const char* errorString = NULL;
float volume = 0.0;
fMixerControl->Connect(fMixerControl->VolumeWhich(), &volume, &errorString);
if (errorString != NULL) {
SetLabel(errorString);
SetLimits(-60, 18);
} else {
SetLabel(B_TRANSLATE("Volume"));
SetLimits((int32)floorf(fMixerControl->Minimum()),
(int32)ceilf(fMixerControl->Maximum()));
BMediaRoster* roster = BMediaRoster::CurrentRoster();
if (roster != NULL && fMixerControl->GainNode() != media_node::null) {
roster->StartWatching(this, fMixerControl->GainNode(),
B_MEDIA_NEW_PARAMETER_VALUE);
}
}
SetEnabled(errorString == NULL);
fOriginalValue = (int32)volume;
SetValue((int32)volume);
}
float
VolumeControl::_PointForValue(int32 value) const
{
int32 min, max;
GetLimits(&min, &max);
if (Orientation() == B_HORIZONTAL) {
return ceilf(1.0f * (value - min) / (max - min)
* (BarFrame().Width() - 2) + BarFrame().left + 1);
}
return ceilf(BarFrame().top - 1.0f * (value - min) / (max - min)
* BarFrame().Height());
}
bool
VolumeControl::_IsReplicant() const
{
return dynamic_cast<VolumeWindow*>(Window()) == NULL;
}