* Copyright 2006-2010 Stephan Aßmus <superstippi@gmx.de>
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "TransportControlGroup.h"
#include <stdio.h>
#include <string.h>
#include <Shape.h>
#include <SpaceLayoutItem.h>
#include <String.h>
#include <ToolTipManager.h>
#include <Window.h>
#include "DurationView.h"
#include "PeakView.h"
#include "PlaybackState.h"
#include "PlayPauseButton.h"
#include "PositionToolTip.h"
#include "SeekSlider.h"
#include "SymbolButton.h"
#include "VolumeSlider.h"
enum {
MSG_SEEK = 'seek',
MSG_SEEK_HOVER = 'hovr',
MSG_PLAY = 'play',
MSG_STOP = 'stop',
MSG_REWIND = 'rwnd',
MSG_FORWARD = 'frwd',
MSG_SKIP_BACKWARDS = 'skpb',
MSG_SKIP_FORWARD = 'skpf',
MSG_SET_VOLUME = 'stvl',
MSG_SET_MUTE = 'stmt',
MSG_DURATION_TOOLTIP = 'msdt',
};
#define kVolumeDbMax 6.0
#define kVolumeDbMin -60.0
#define kVolumeDbExpPositive 1.4 // for dB values > 0
#define kVolumeDbExpNegative 1.9 // for dB values < 0
#define kVolumeFactor 100
#define kPositionFactor 3000
TransportControlGroup::TransportControlGroup(BRect frame, bool useSkipButtons,
bool usePeakView, bool useWindButtons)
:
BGroupView(B_VERTICAL, 0),
fSeekSlider(NULL),
fDurationView(NULL),
fPositionToolTip(NULL),
fPeakView(NULL),
fVolumeSlider(NULL),
fSkipBack(NULL),
fSkipForward(NULL),
fRewind(NULL),
fForward(NULL),
fPlayPause(NULL),
fStop(NULL),
fMute(NULL),
fSymbolScale(1.0f),
fLastEnabledButtons(0)
{
float symbolHeight = int(be_plain_font->Size() / 1.33) | 1;
BGroupView* seekGroup = new BGroupView(B_HORIZONTAL, 0);
fSeekLayout = seekGroup->GroupLayout();
GroupLayout()->AddView(seekGroup);
fSeekSlider = new SeekSlider("seek slider", new BMessage(MSG_SEEK),
new BMessage(MSG_SEEK_HOVER), 0, kPositionFactor);
fSeekLayout->AddView(fSeekSlider);
fPositionToolTip = new PositionToolTip();
fPositionToolTip->SetAlignment(BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP));
fPositionToolTip->SetMouseRelativeLocation(BPoint(0, 0));
fSeekSlider->SetToolTip(fPositionToolTip);
fDurationView = new DurationView("duration view");
fSeekLayout->AddView(fDurationView);
uint32 topBottomBorder = BControlLook::B_TOP_BORDER
| BControlLook::B_BOTTOM_BORDER;
if (useSkipButtons) {
fSkipBack = new SymbolButton(B_EMPTY_STRING,
_CreateSkipBackwardsShape(symbolHeight),
new BMessage(MSG_SKIP_BACKWARDS),
BControlLook::B_LEFT_BORDER | topBottomBorder);
fSkipForward = new SymbolButton(B_EMPTY_STRING,
_CreateSkipForwardShape(symbolHeight),
new BMessage(MSG_SKIP_FORWARD),
BControlLook::B_RIGHT_BORDER | topBottomBorder);
}
if (useWindButtons) {
fRewind = new SymbolButton(B_EMPTY_STRING,
_CreateRewindShape(symbolHeight), new BMessage(MSG_REWIND),
useSkipButtons ? topBottomBorder
: BControlLook::B_LEFT_BORDER | topBottomBorder);
fForward = new SymbolButton(B_EMPTY_STRING,
_CreateForwardShape(symbolHeight), new BMessage(MSG_FORWARD),
useSkipButtons ? topBottomBorder
: BControlLook::B_RIGHT_BORDER | topBottomBorder);
}
fPlayPause = new PlayPauseButton(B_EMPTY_STRING,
_CreatePlayShape(symbolHeight), _CreatePauseShape(symbolHeight),
new BMessage(MSG_PLAY), useWindButtons || useSkipButtons
? topBottomBorder
: topBottomBorder | BControlLook::B_LEFT_BORDER);
fStop = new SymbolButton(B_EMPTY_STRING,
_CreateStopShape(symbolHeight), new BMessage(MSG_STOP),
useWindButtons || useSkipButtons ? topBottomBorder
: topBottomBorder | BControlLook::B_RIGHT_BORDER);
fMute = new SymbolButton(B_EMPTY_STRING,
_CreateSpeakerShape(floorf(symbolHeight * 0.9)),
new BMessage(MSG_SET_MUTE), 0);
fVolumeSlider = new VolumeSlider("volume slider",
_DbToGain(_ExponentialToLinear(kVolumeDbMin)) * kVolumeFactor,
_DbToGain(_ExponentialToLinear(kVolumeDbMax)) * kVolumeFactor,
kVolumeFactor, new BMessage(MSG_SET_VOLUME));
fVolumeSlider->SetValue(_DbToGain(_ExponentialToLinear(0.0))
* kVolumeFactor);
if (usePeakView)
fPeakView = new PeakView("peak view", false, false);
BGroupView* buttonGroup = new BGroupView(B_HORIZONTAL, 0);
BGroupLayout* buttonLayout = buttonGroup->GroupLayout();
if (fSkipBack != NULL)
buttonLayout->AddView(fSkipBack);
if (fRewind != NULL)
buttonLayout->AddView(fRewind);
buttonLayout->AddView(fPlayPause);
buttonLayout->AddView(fStop);
if (fForward != NULL)
buttonLayout->AddView(fForward);
if (fSkipForward != NULL)
buttonLayout->AddView(fSkipForward);
BGroupView* controlGroup = new BGroupView(B_HORIZONTAL, 0);
GroupLayout()->AddView(controlGroup);
fControlLayout = controlGroup->GroupLayout();
fControlLayout->AddView(buttonGroup, 0.6f);
fControlLayout->AddItem(BSpaceLayoutItem::CreateHorizontalStrut(5));
fControlLayout->AddView(fMute);
fControlLayout->AddView(fVolumeSlider);
if (fPeakView != NULL)
fControlLayout->AddView(fPeakView, 0.6f);
float inset = fSeekSlider->BarFrame().left;
float hInset = inset - fSeekSlider->BarFrame().top;
if (hInset < 0.0f)
hInset = 0.0f;
fSeekLayout->SetInsets(0, hInset, 5, 0);
fControlLayout->SetInsets(inset, hInset, inset, inset);
BSize size = fControlLayout->MinSize();
size.width *= 3;
size.height = B_SIZE_UNSET;
fControlLayout->SetExplicitMaxSize(size);
fControlLayout->SetExplicitAlignment(BAlignment(B_ALIGN_CENTER,
B_ALIGN_TOP));
}
TransportControlGroup::~TransportControlGroup()
{
if (!fSeekSlider->IsEnabled())
fPositionToolTip->ReleaseReference();
}
void
TransportControlGroup::AttachedToWindow()
{
SetEnabled(EnabledButtons());
fSeekSlider->SetTarget(this);
fVolumeSlider->SetTarget(this);
if (fSkipBack)
fSkipBack->SetTarget(this);
if (fSkipForward)
fSkipForward->SetTarget(this);
if (fRewind)
fRewind->SetTarget(this);
if (fForward)
fForward->SetTarget(this);
fPlayPause->SetTarget(this);
fStop->SetTarget(this);
fMute->SetTarget(this);
}
void
TransportControlGroup::GetPreferredSize(float* _width, float* _height)
{
BSize size = GroupLayout()->MinSize();
if (_width != NULL)
*_width = size.width;
if (_height != NULL)
*_height = size.height;
}
void
TransportControlGroup::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_PLAY:
_TogglePlaying();
break;
case MSG_STOP:
_Stop();
break;
case MSG_REWIND:
_Rewind();
break;
case MSG_FORWARD:
_Forward();
break;
case MSG_SKIP_BACKWARDS:
_SkipBackward();
break;
case MSG_SKIP_FORWARD:
_SkipForward();
break;
case MSG_SET_VOLUME:
_UpdateVolume();
break;
case MSG_SET_MUTE:
_ToggleMute();
break;
case MSG_SEEK:
_UpdatePosition();
break;
case MSG_DURATION_TOOLTIP:
{
BPoint tipPoint;
fSeekSlider->GetMouse(&tipPoint, NULL, false);
tipPoint.y = 0;
fSeekSlider->ConvertToScreen(&tipPoint);
BToolTipManager::Manager()->ShowTip(fPositionToolTip, tipPoint, this);
break;
}
case MSG_SEEK_HOVER:
{
int32 value;
if (message->FindInt32("value", &value) == B_OK) {
bigtime_t position = TimePositionFor(value / (float)kPositionFactor);
fPositionToolTip->Update(position, fDurationView->TimeDuration());
Looper()->PostMessage(MSG_DURATION_TOOLTIP, this);
}
break;
}
default:
BView::MessageReceived(message);
break;
}
}
uint32
TransportControlGroup::EnabledButtons()
{
return fLastEnabledButtons;
}
void TransportControlGroup::TogglePlaying() {}
void TransportControlGroup::Stop() {}
void TransportControlGroup::Rewind() {}
void TransportControlGroup::Forward() {}
void TransportControlGroup::SkipBackward() {}
void TransportControlGroup::SkipForward() {}
void TransportControlGroup::VolumeChanged(float value) {}
void TransportControlGroup::ToggleMute() {}
void TransportControlGroup::PositionChanged(float value) {}
bigtime_t TransportControlGroup::TimePositionFor(float value) { return 0; }
float
TransportControlGroup::_LinearToExponential(float dbIn)
{
float db = dbIn;
if (db >= 0) {
db = db * (pow(fabs(kVolumeDbMax), (1.0 / kVolumeDbExpPositive))
/ fabs(kVolumeDbMax));
db = pow(db, kVolumeDbExpPositive);
} else {
db = -db;
db = db * (pow(fabs(kVolumeDbMin), (1.0 / kVolumeDbExpNegative))
/ fabs(kVolumeDbMin));
db = pow(db, kVolumeDbExpNegative);
db = -db;
}
return db;
}
float
TransportControlGroup::_ExponentialToLinear(float dbIn)
{
float db = dbIn;
if (db >= 0) {
db = pow(db, (1.0 / kVolumeDbExpPositive));
db = db * (fabs(kVolumeDbMax) / pow(fabs(kVolumeDbMax),
(1.0 / kVolumeDbExpPositive)));
} else {
db = -db;
db = pow(db, (1.0 / kVolumeDbExpNegative));
db = db * (fabs(kVolumeDbMin) / pow(fabs(kVolumeDbMin),
(1.0 / kVolumeDbExpNegative)));
db = -db;
}
return db;
}
float
TransportControlGroup::_DbToGain(float db)
{
return pow(10.0, db / 20.0);
}
float
TransportControlGroup::_GainToDb(float gain)
{
return 20.0 * log10(gain);
}
void
TransportControlGroup::SetEnabled(uint32 buttons)
{
if (!LockLooper())
return;
fLastEnabledButtons = buttons;
fSeekSlider->SetEnabled(buttons & SEEK_ENABLED);
fSeekSlider->SetToolTip((buttons & SEEK_ENABLED) != 0
? fPositionToolTip : NULL);
fVolumeSlider->SetEnabled(buttons & VOLUME_ENABLED);
fMute->SetEnabled(buttons & VOLUME_ENABLED);
if (fSkipBack)
fSkipBack->SetEnabled(buttons & SKIP_BACK_ENABLED);
if (fSkipForward)
fSkipForward->SetEnabled(buttons & SKIP_FORWARD_ENABLED);
if (fRewind)
fRewind->SetEnabled(buttons & SEEK_BACK_ENABLED);
if (fForward)
fForward->SetEnabled(buttons & SEEK_FORWARD_ENABLED);
fPlayPause->SetEnabled(buttons & PLAYBACK_ENABLED);
fStop->SetEnabled(buttons & PLAYBACK_ENABLED);
UnlockLooper();
}
void
TransportControlGroup::SetPlaybackState(uint32 state)
{
if (!LockLooper())
return;
switch (state) {
case PLAYBACK_STATE_PLAYING:
fPlayPause->SetPlaying();
break;
case PLAYBACK_STATE_PAUSED:
fPlayPause->SetPaused();
break;
case PLAYBACK_STATE_STOPPED:
fPlayPause->SetStopped();
break;
}
UnlockLooper();
}
void
TransportControlGroup::SetSkippable(bool backward, bool forward)
{
if (!LockLooper())
return;
if (fSkipBack)
fSkipBack->SetEnabled(backward);
if (fSkipForward)
fSkipForward->SetEnabled(forward);
UnlockLooper();
}
void
TransportControlGroup::SetAudioEnabled(bool enabled)
{
if (!LockLooper())
return;
fMute->SetEnabled(enabled);
fVolumeSlider->SetEnabled(enabled);
UnlockLooper();
}
void
TransportControlGroup::SetMuted(bool mute)
{
if (!LockLooper())
return;
fVolumeSlider->SetMuted(mute);
UnlockLooper();
}
void
TransportControlGroup::SetVolume(float value)
{
float db = _GainToDb(value);
float exponential = _LinearToExponential(db);
float gain = _DbToGain(exponential);
int32 pos = (int32)(floorf(gain * kVolumeFactor + 0.5));
fVolumeSlider->SetValue(pos);
}
void
TransportControlGroup::SetAudioChannelCount(int32 count)
{
fPeakView->SetChannelCount(count);
}
void
TransportControlGroup::SetPosition(float value, bigtime_t position,
bigtime_t duration)
{
fDurationView->Update(position, duration);
if (fSeekSlider->IsTracking())
return;
fSeekSlider->SetPosition(value);
}
float
TransportControlGroup::Position() const
{
return fSeekSlider->Position();
}
void
TransportControlGroup::SetDisabledString(const char* string)
{
fSeekSlider->SetDisabledString(string);
}
void
TransportControlGroup::SetSymbolScale(float scale)
{
if (scale == fSymbolScale)
return;
fSymbolScale = scale;
if (fSeekSlider != NULL)
fSeekSlider->SetSymbolScale(scale);
if (fVolumeSlider != NULL) {
fVolumeSlider->SetBarThickness(fVolumeSlider->PreferredBarThickness()
* scale);
}
if (fDurationView != NULL)
fDurationView->SetSymbolScale(scale);
float symbolHeight = int(scale * be_plain_font->Size() / 1.33) | 1;
if (fSkipBack != NULL)
fSkipBack->SetSymbol(_CreateSkipBackwardsShape(symbolHeight));
if (fSkipForward != NULL)
fSkipForward->SetSymbol(_CreateSkipForwardShape(symbolHeight));
if (fRewind != NULL)
fRewind->SetSymbol(_CreateRewindShape(symbolHeight));
if (fForward != NULL)
fForward->SetSymbol(_CreateForwardShape(symbolHeight));
if (fPlayPause != NULL) {
fPlayPause->SetSymbols(_CreatePlayShape(symbolHeight),
_CreatePauseShape(symbolHeight));
}
if (fStop != NULL)
fStop->SetSymbol(_CreateStopShape(symbolHeight));
if (fMute != NULL)
fMute->SetSymbol(_CreateSpeakerShape(floorf(symbolHeight * 0.9)));
float barInset = fSeekSlider->BarFrame().left;
float inset = barInset * scale;
float hInset = inset - fSeekSlider->BarFrame().top;
if (hInset < 0.0f)
hInset = 0.0f;
fSeekLayout->SetInsets(inset - barInset, hInset, inset, 0);
fSeekLayout->SetSpacing(inset - barInset);
fControlLayout->SetInsets(inset, hInset, inset, inset);
fControlLayout->SetSpacing(inset - barInset);
ResizeTo(Bounds().Width(), GroupLayout()->MinSize().height);
}
void
TransportControlGroup::_TogglePlaying()
{
TogglePlaying();
}
void
TransportControlGroup::_Stop()
{
fPlayPause->SetStopped();
Stop();
}
void
TransportControlGroup::_Rewind()
{
Rewind();
}
void
TransportControlGroup::_Forward()
{
Forward();
}
void
TransportControlGroup::_SkipBackward()
{
SkipBackward();
}
void
TransportControlGroup::_SkipForward()
{
SkipForward();
}
void
TransportControlGroup::_UpdateVolume()
{
float pos = fVolumeSlider->Value() / (float)kVolumeFactor;
float db = _ExponentialToLinear(_GainToDb(pos));
float gain = _DbToGain(db);
VolumeChanged(gain);
}
void
TransportControlGroup::_ToggleMute()
{
fVolumeSlider->SetMuted(!fVolumeSlider->IsMuted());
ToggleMute();
}
void
TransportControlGroup::_UpdatePosition()
{
PositionChanged(fSeekSlider->Value() / (float)kPositionFactor);
BMessage msg(MSG_DURATION_TOOLTIP);
Window()->PostMessage(&msg, this);
}
BShape*
TransportControlGroup::_CreateSkipBackwardsShape(float height) const
{
BShape* shape = new BShape();
float stopWidth = ceilf(height / 6);
shape->MoveTo(BPoint(-stopWidth, height));
shape->LineTo(BPoint(0, height));
shape->LineTo(BPoint(0, 0));
shape->LineTo(BPoint(-stopWidth, 0));
shape->Close();
shape->MoveTo(BPoint(0, height / 2));
shape->LineTo(BPoint(height, height));
shape->LineTo(BPoint(height, 0));
shape->Close();
shape->MoveTo(BPoint(height, height / 2));
shape->LineTo(BPoint(height * 2, height));
shape->LineTo(BPoint(height * 2, 0));
shape->Close();
return shape;
}
BShape*
TransportControlGroup::_CreateSkipForwardShape(float height) const
{
BShape* shape = new BShape();
shape->MoveTo(BPoint(height, height / 2));
shape->LineTo(BPoint(0, height));
shape->LineTo(BPoint(0, 0));
shape->Close();
shape->MoveTo(BPoint(height * 2, height / 2));
shape->LineTo(BPoint(height, height));
shape->LineTo(BPoint(height, 0));
shape->Close();
float stopWidth = ceilf(height / 6);
shape->MoveTo(BPoint(height * 2, height));
shape->LineTo(BPoint(height * 2 + stopWidth, height));
shape->LineTo(BPoint(height * 2 + stopWidth, 0));
shape->LineTo(BPoint(height * 2, 0));
shape->Close();
return shape;
}
BShape*
TransportControlGroup::_CreateRewindShape(float height) const
{
BShape* shape = new BShape();
shape->MoveTo(BPoint(0, height / 2));
shape->LineTo(BPoint(height, height));
shape->LineTo(BPoint(height, 0));
shape->Close();
shape->MoveTo(BPoint(height, height / 2));
shape->LineTo(BPoint(height * 2, height));
shape->LineTo(BPoint(height * 2, 0));
shape->Close();
return shape;
}
BShape*
TransportControlGroup::_CreateForwardShape(float height) const
{
BShape* shape = new BShape();
shape->MoveTo(BPoint(height, height / 2));
shape->LineTo(BPoint(0, height));
shape->LineTo(BPoint(0, 0));
shape->Close();
shape->MoveTo(BPoint(height * 2, height / 2));
shape->LineTo(BPoint(height, height));
shape->LineTo(BPoint(height, 0));
shape->Close();
return shape;
}
BShape*
TransportControlGroup::_CreatePlayShape(float height) const
{
BShape* shape = new BShape();
float step = floorf(height / 8);
shape->MoveTo(BPoint(height + step, height / 2));
shape->LineTo(BPoint(-step, height + step));
shape->LineTo(BPoint(-step, 0 - step));
shape->Close();
return shape;
}
BShape*
TransportControlGroup::_CreatePauseShape(float height) const
{
BShape* shape = new BShape();
float stemWidth = floorf(height / 3);
shape->MoveTo(BPoint(0, height));
shape->LineTo(BPoint(stemWidth, height));
shape->LineTo(BPoint(stemWidth, 0));
shape->LineTo(BPoint(0, 0));
shape->Close();
shape->MoveTo(BPoint(height - stemWidth, height));
shape->LineTo(BPoint(height, height));
shape->LineTo(BPoint(height, 0));
shape->LineTo(BPoint(height - stemWidth, 0));
shape->Close();
return shape;
}
BShape*
TransportControlGroup::_CreateStopShape(float height) const
{
BShape* shape = new BShape();
shape->MoveTo(BPoint(0, height));
shape->LineTo(BPoint(height, height));
shape->LineTo(BPoint(height, 0));
shape->LineTo(BPoint(0, 0));
shape->Close();
return shape;
}
static void
add_bow(BShape* shape, float offset, float size, float height, float step)
{
float width = floorf(size * 2 / 3);
float outerControlHeight = size * 2 / 3;
float outerControlWidth = size / 4;
float innerControlHeight = size / 2;
float innerControlWidth = size / 5;
shape->MoveTo(BPoint(offset, height / 2 + size));
shape->BezierTo(
BPoint(offset + outerControlWidth, height / 2 + size),
BPoint(offset + width, height / 2 + outerControlHeight),
BPoint(offset + width, height / 2)
);
shape->BezierTo(
BPoint(offset + width, height / 2 - outerControlHeight),
BPoint(offset + outerControlWidth, height / 2 - size),
BPoint(offset, height / 2 - size)
);
shape->BezierTo(
BPoint(offset + innerControlWidth, height / 2 - size),
BPoint(offset + width - step, height / 2 - innerControlHeight),
BPoint(offset + width - step, height / 2)
);
shape->BezierTo(
BPoint(offset + width - step, height / 2 + innerControlHeight),
BPoint(offset + innerControlWidth, height / 2 + size),
BPoint(offset, height / 2 + size)
);
shape->Close();
}
BShape*
TransportControlGroup::_CreateSpeakerShape(float height) const
{
BShape* shape = new BShape();
float step = floorf(height / 8);
float magnetWidth = floorf(height / 5);
float chassieWidth = floorf(height / 1.5);
float chassieHeight = floorf(height / 4);
shape->MoveTo(BPoint(0, height - step));
shape->LineTo(BPoint(magnetWidth, height - step));
shape->LineTo(BPoint(magnetWidth, height / 2 + chassieHeight));
shape->LineTo(BPoint(magnetWidth + chassieWidth - step, height + step));
shape->LineTo(BPoint(magnetWidth + chassieWidth, height + step));
shape->LineTo(BPoint(magnetWidth + chassieWidth, -step));
shape->LineTo(BPoint(magnetWidth + chassieWidth - step, -step));
shape->LineTo(BPoint(magnetWidth, height / 2 - chassieHeight));
shape->LineTo(BPoint(magnetWidth, step));
shape->LineTo(BPoint(0, step));
shape->Close();
float offset = magnetWidth + chassieWidth + step * 2;
add_bow(shape, offset, 3 * step, height, step * 2);
offset += step * 2;
add_bow(shape, offset, 5 * step, height, step * 2);
offset += step * 2;
add_bow(shape, offset, 7 * step, height, step * 2);
return shape;
}