2 * Copyright 2006-2010 Stephan Aßmus <superstippi@gmx.de>
3 * All rights reserved. Distributed under the terms of the MIT License.
7 // NOTE: Based on my code in the BeOS interface for the VLC media player
8 // that I did during the VLC 0.4.3 - 0.4.6 times. Code not written by me
9 // removed. -Stephan Aßmus
12 #include "TransportControlGroup.h"
18 #include <SpaceLayoutItem.h>
20 #include <ToolTipManager.h>
23 #include "DurationView.h"
25 #include "PlaybackState.h"
26 #include "PlayPauseButton.h"
27 #include "PositionToolTip.h"
28 #include "SeekSlider.h"
29 #include "SymbolButton.h"
30 #include "VolumeSlider.h"
38 MSG_SKIP_BACKWARDS
= 'skpb',
39 MSG_SKIP_FORWARD
= 'skpf',
40 MSG_SET_VOLUME
= 'stvl',
41 MSG_SET_MUTE
= 'stmt',
42 MSG_DURATION_TOOLTIP
= 'msdt'
45 // the range of the volume sliders (in dB)
46 #define kVolumeDbMax 6.0
47 #define kVolumeDbMin -60.0
48 // a power function for non linear sliders
49 #define kVolumeDbExpPositive 1.4 // for dB values > 0
50 #define kVolumeDbExpNegative 1.9 // for dB values < 0
52 #define kVolumeFactor 100
53 #define kPositionFactor 3000
56 TransportControlGroup::TransportControlGroup(BRect frame
, bool useSkipButtons
,
57 bool usePeakView
, bool useWindButtons
)
59 BGroupView(B_VERTICAL
, 0),
62 fPositionToolTip(NULL
),
73 fLastEnabledButtons(0)
75 // Pick a symbol size based on the current system font size, but make
76 // sure the size is uneven, so the pointy shapes have their middle on
77 // a pixel instead of between two pixels.
78 float symbolHeight
= int(be_plain_font
->Size() / 1.33) | 1;
80 BGroupView
* seekGroup
= new BGroupView(B_HORIZONTAL
, 0);
81 fSeekLayout
= seekGroup
->GroupLayout();
82 GroupLayout()->AddView(seekGroup
);
85 fSeekSlider
= new SeekSlider("seek slider", new BMessage(MSG_SEEK
),
87 fSeekLayout
->AddView(fSeekSlider
);
89 fPositionToolTip
= new PositionToolTip();
90 fSeekSlider
->SetToolTip(fPositionToolTip
);
93 fDurationView
= new DurationView("duration view");
94 fSeekLayout
->AddView(fDurationView
);
98 uint32 topBottomBorder
= BControlLook::B_TOP_BORDER
99 | BControlLook::B_BOTTOM_BORDER
;
101 if (useSkipButtons
) {
103 fSkipBack
= new SymbolButton(B_EMPTY_STRING
,
104 _CreateSkipBackwardsShape(symbolHeight
),
105 new BMessage(MSG_SKIP_BACKWARDS
),
106 BControlLook::B_LEFT_BORDER
| topBottomBorder
);
108 fSkipForward
= new SymbolButton(B_EMPTY_STRING
,
109 _CreateSkipForwardShape(symbolHeight
),
110 new BMessage(MSG_SKIP_FORWARD
),
111 BControlLook::B_RIGHT_BORDER
| topBottomBorder
);
114 if (useWindButtons
) {
116 fRewind
= new SymbolButton(B_EMPTY_STRING
,
117 _CreateRewindShape(symbolHeight
), new BMessage(MSG_REWIND
),
118 useSkipButtons
? topBottomBorder
119 : BControlLook::B_LEFT_BORDER
| topBottomBorder
);
121 fForward
= new SymbolButton(B_EMPTY_STRING
,
122 _CreateForwardShape(symbolHeight
), new BMessage(MSG_FORWARD
),
123 useSkipButtons
? topBottomBorder
124 : BControlLook::B_RIGHT_BORDER
| topBottomBorder
);
128 fPlayPause
= new PlayPauseButton(B_EMPTY_STRING
,
129 _CreatePlayShape(symbolHeight
), _CreatePauseShape(symbolHeight
),
130 new BMessage(MSG_PLAY
), useWindButtons
|| useSkipButtons
132 : topBottomBorder
| BControlLook::B_LEFT_BORDER
);
135 fStop
= new SymbolButton(B_EMPTY_STRING
,
136 _CreateStopShape(symbolHeight
), new BMessage(MSG_STOP
),
137 useWindButtons
|| useSkipButtons
? topBottomBorder
138 : topBottomBorder
| BControlLook::B_RIGHT_BORDER
);
141 fMute
= new SymbolButton(B_EMPTY_STRING
,
142 _CreateSpeakerShape(floorf(symbolHeight
* 0.9)),
143 new BMessage(MSG_SET_MUTE
), 0);
146 fVolumeSlider
= new VolumeSlider("volume slider",
147 _DbToGain(_ExponentialToLinear(kVolumeDbMin
)) * kVolumeFactor
,
148 _DbToGain(_ExponentialToLinear(kVolumeDbMax
)) * kVolumeFactor
,
149 kVolumeFactor
, new BMessage(MSG_SET_VOLUME
));
150 fVolumeSlider
->SetValue(_DbToGain(_ExponentialToLinear(0.0))
155 fPeakView
= new PeakView("peak view", false, false);
157 // Layout the controls
159 BGroupView
* buttonGroup
= new BGroupView(B_HORIZONTAL
, 0);
160 BGroupLayout
* buttonLayout
= buttonGroup
->GroupLayout();
162 if (fSkipBack
!= NULL
)
163 buttonLayout
->AddView(fSkipBack
);
165 buttonLayout
->AddView(fRewind
);
166 buttonLayout
->AddView(fPlayPause
);
167 buttonLayout
->AddView(fStop
);
168 if (fForward
!= NULL
)
169 buttonLayout
->AddView(fForward
);
170 if (fSkipForward
!= NULL
)
171 buttonLayout
->AddView(fSkipForward
);
173 BGroupView
* controlGroup
= new BGroupView(B_HORIZONTAL
, 0);
174 GroupLayout()->AddView(controlGroup
);
175 fControlLayout
= controlGroup
->GroupLayout();
176 fControlLayout
->AddView(buttonGroup
, 0.6f
);
177 fControlLayout
->AddItem(BSpaceLayoutItem::CreateHorizontalStrut(5));
178 fControlLayout
->AddView(fMute
);
179 fControlLayout
->AddView(fVolumeSlider
);
180 if (fPeakView
!= NULL
)
181 fControlLayout
->AddView(fPeakView
, 0.6f
);
183 // Figure out the visual insets of the slider bounds towards the slider
184 // bar, and use that as insets for the rest of the layout.
185 float inset
= fSeekSlider
->BarFrame().left
;
186 float hInset
= inset
- fSeekSlider
->BarFrame().top
;
190 fSeekLayout
->SetInsets(0, hInset
, 5, 0);
191 fControlLayout
->SetInsets(inset
, hInset
, inset
, inset
);
193 BSize size
= fControlLayout
->MinSize();
195 size
.height
= B_SIZE_UNSET
;
196 fControlLayout
->SetExplicitMaxSize(size
);
197 fControlLayout
->SetExplicitAlignment(BAlignment(B_ALIGN_CENTER
,
202 TransportControlGroup::~TransportControlGroup()
204 if (!fSeekSlider
->IsEnabled())
205 fPositionToolTip
->ReleaseReference();
210 TransportControlGroup::AttachedToWindow()
212 SetEnabled(EnabledButtons());
214 // we are now a valid BHandler
215 fSeekSlider
->SetTarget(this);
216 fVolumeSlider
->SetTarget(this);
218 fSkipBack
->SetTarget(this);
220 fSkipForward
->SetTarget(this);
222 fRewind
->SetTarget(this);
224 fForward
->SetTarget(this);
225 fPlayPause
->SetTarget(this);
226 fStop
->SetTarget(this);
227 fMute
->SetTarget(this);
232 TransportControlGroup::GetPreferredSize(float* _width
, float* _height
)
234 BSize size
= GroupLayout()->MinSize();
236 *_width
= size
.width
;
238 *_height
= size
.height
;
243 TransportControlGroup::MessageReceived(BMessage
* message
)
245 switch (message
->what
) {
260 case MSG_SKIP_BACKWARDS
:
263 case MSG_SKIP_FORWARD
:
278 case MSG_DURATION_TOOLTIP
:
280 BToolTipManager
* manager
= BToolTipManager::Manager();
282 GetMouse(&tipPoint
, NULL
, false);
283 manager
->ShowTip(fPositionToolTip
, tipPoint
, this);
288 BView::MessageReceived(message
);
294 // #pragma mark - default implementation for the virtuals
298 TransportControlGroup::EnabledButtons()
300 return fLastEnabledButtons
;
304 void TransportControlGroup::TogglePlaying() {}
305 void TransportControlGroup::Stop() {}
306 void TransportControlGroup::Rewind() {}
307 void TransportControlGroup::Forward() {}
308 void TransportControlGroup::SkipBackward() {}
309 void TransportControlGroup::SkipForward() {}
310 void TransportControlGroup::VolumeChanged(float value
) {}
311 void TransportControlGroup::ToggleMute() {}
312 void TransportControlGroup::PositionChanged(float value
) {}
319 TransportControlGroup::_LinearToExponential(float dbIn
)
323 db
= db
* (pow(fabs(kVolumeDbMax
), (1.0 / kVolumeDbExpPositive
))
324 / fabs(kVolumeDbMax
));
325 db
= pow(db
, kVolumeDbExpPositive
);
328 db
= db
* (pow(fabs(kVolumeDbMin
), (1.0 / kVolumeDbExpNegative
))
329 / fabs(kVolumeDbMin
));
330 db
= pow(db
, kVolumeDbExpNegative
);
338 TransportControlGroup::_ExponentialToLinear(float dbIn
)
342 db
= pow(db
, (1.0 / kVolumeDbExpPositive
));
343 db
= db
* (fabs(kVolumeDbMax
) / pow(fabs(kVolumeDbMax
),
344 (1.0 / kVolumeDbExpPositive
)));
347 db
= pow(db
, (1.0 / kVolumeDbExpNegative
));
348 db
= db
* (fabs(kVolumeDbMin
) / pow(fabs(kVolumeDbMin
),
349 (1.0 / kVolumeDbExpNegative
)));
357 TransportControlGroup::_DbToGain(float db
)
359 return pow(10.0, db
/ 20.0);
364 TransportControlGroup::_GainToDb(float gain
)
366 return 20.0 * log10(gain
);
374 TransportControlGroup::SetEnabled(uint32 buttons
)
379 fLastEnabledButtons
= buttons
;
381 fSeekSlider
->SetEnabled(buttons
& SEEK_ENABLED
);
382 fSeekSlider
->SetToolTip((buttons
& SEEK_ENABLED
) != 0
383 ? fPositionToolTip
: NULL
);
385 fVolumeSlider
->SetEnabled(buttons
& VOLUME_ENABLED
);
386 fMute
->SetEnabled(buttons
& VOLUME_ENABLED
);
389 fSkipBack
->SetEnabled(buttons
& SKIP_BACK_ENABLED
);
391 fSkipForward
->SetEnabled(buttons
& SKIP_FORWARD_ENABLED
);
393 fRewind
->SetEnabled(buttons
& SEEK_BACK_ENABLED
);
395 fForward
->SetEnabled(buttons
& SEEK_FORWARD_ENABLED
);
397 fPlayPause
->SetEnabled(buttons
& PLAYBACK_ENABLED
);
398 fStop
->SetEnabled(buttons
& PLAYBACK_ENABLED
);
408 TransportControlGroup::SetPlaybackState(uint32 state
)
414 case PLAYBACK_STATE_PLAYING
:
415 fPlayPause
->SetPlaying();
417 case PLAYBACK_STATE_PAUSED
:
418 fPlayPause
->SetPaused();
420 case PLAYBACK_STATE_STOPPED
:
421 fPlayPause
->SetStopped();
430 TransportControlGroup::SetSkippable(bool backward
, bool forward
)
436 fSkipBack
->SetEnabled(backward
);
438 fSkipForward
->SetEnabled(forward
);
448 TransportControlGroup::SetAudioEnabled(bool enabled
)
453 fMute
->SetEnabled(enabled
);
454 fVolumeSlider
->SetEnabled(enabled
);
461 TransportControlGroup::SetMuted(bool mute
)
466 fVolumeSlider
->SetMuted(mute
);
473 TransportControlGroup::SetVolume(float value
)
475 float db
= _GainToDb(value
);
476 float exponential
= _LinearToExponential(db
);
477 float gain
= _DbToGain(exponential
);
478 int32 pos
= (int32
)(floorf(gain
* kVolumeFactor
+ 0.5));
480 fVolumeSlider
->SetValue(pos
);
485 TransportControlGroup::SetAudioChannelCount(int32 count
)
487 fPeakView
->SetChannelCount(count
);
492 TransportControlGroup::SetPosition(float value
, bigtime_t position
,
495 fPositionToolTip
->Update(position
, duration
);
496 fDurationView
->Update(position
, duration
);
498 if (fSeekSlider
->IsTracking())
501 fSeekSlider
->SetPosition(value
);
506 TransportControlGroup::Position() const
508 return fSeekSlider
->Position();
513 TransportControlGroup::SetDisabledString(const char* string
)
515 fSeekSlider
->SetDisabledString(string
);
520 TransportControlGroup::SetSymbolScale(float scale
)
522 if (scale
== fSymbolScale
)
525 fSymbolScale
= scale
;
527 if (fSeekSlider
!= NULL
)
528 fSeekSlider
->SetSymbolScale(scale
);
529 if (fVolumeSlider
!= NULL
) {
530 fVolumeSlider
->SetBarThickness(fVolumeSlider
->PreferredBarThickness()
533 if (fDurationView
!= NULL
)
534 fDurationView
->SetSymbolScale(scale
);
536 float symbolHeight
= int(scale
* be_plain_font
->Size() / 1.33) | 1;
538 if (fSkipBack
!= NULL
)
539 fSkipBack
->SetSymbol(_CreateSkipBackwardsShape(symbolHeight
));
540 if (fSkipForward
!= NULL
)
541 fSkipForward
->SetSymbol(_CreateSkipForwardShape(symbolHeight
));
543 fRewind
->SetSymbol(_CreateRewindShape(symbolHeight
));
544 if (fForward
!= NULL
)
545 fForward
->SetSymbol(_CreateForwardShape(symbolHeight
));
546 if (fPlayPause
!= NULL
) {
547 fPlayPause
->SetSymbols(_CreatePlayShape(symbolHeight
),
548 _CreatePauseShape(symbolHeight
));
551 fStop
->SetSymbol(_CreateStopShape(symbolHeight
));
553 fMute
->SetSymbol(_CreateSpeakerShape(floorf(symbolHeight
* 0.9)));
555 // Figure out the visual insets of the slider bounds towards the slider
556 // bar, and use that as insets for the rest of the layout.
557 float barInset
= fSeekSlider
->BarFrame().left
;
558 float inset
= barInset
* scale
;
559 float hInset
= inset
- fSeekSlider
->BarFrame().top
;
563 fSeekLayout
->SetInsets(inset
- barInset
, hInset
, inset
, 0);
564 fSeekLayout
->SetSpacing(inset
- barInset
);
565 fControlLayout
->SetInsets(inset
, hInset
, inset
, inset
);
566 fControlLayout
->SetSpacing(inset
- barInset
);
568 ResizeTo(Bounds().Width(), GroupLayout()->MinSize().height
);
575 TransportControlGroup::_TogglePlaying()
582 TransportControlGroup::_Stop()
584 fPlayPause
->SetStopped();
590 TransportControlGroup::_Rewind()
597 TransportControlGroup::_Forward()
604 TransportControlGroup::_SkipBackward()
611 TransportControlGroup::_SkipForward()
618 TransportControlGroup::_UpdateVolume()
620 float pos
= fVolumeSlider
->Value() / (float)kVolumeFactor
;
621 float db
= _ExponentialToLinear(_GainToDb(pos
));
622 float gain
= _DbToGain(db
);
628 TransportControlGroup::_ToggleMute()
630 fVolumeSlider
->SetMuted(!fVolumeSlider
->IsMuted());
636 TransportControlGroup::_UpdatePosition()
638 PositionChanged(fSeekSlider
->Value() / (float)kPositionFactor
);
640 BMessage
msg(MSG_DURATION_TOOLTIP
);
641 Window()->PostMessage(&msg
, this);
649 TransportControlGroup::_CreateSkipBackwardsShape(float height
) const
651 BShape
* shape
= new BShape();
653 float stopWidth
= ceilf(height
/ 6);
655 shape
->MoveTo(BPoint(-stopWidth
, height
));
656 shape
->LineTo(BPoint(0, height
));
657 shape
->LineTo(BPoint(0, 0));
658 shape
->LineTo(BPoint(-stopWidth
, 0));
661 shape
->MoveTo(BPoint(0, height
/ 2));
662 shape
->LineTo(BPoint(height
, height
));
663 shape
->LineTo(BPoint(height
, 0));
666 shape
->MoveTo(BPoint(height
, height
/ 2));
667 shape
->LineTo(BPoint(height
* 2, height
));
668 shape
->LineTo(BPoint(height
* 2, 0));
676 TransportControlGroup::_CreateSkipForwardShape(float height
) const
678 BShape
* shape
= new BShape();
680 shape
->MoveTo(BPoint(height
, height
/ 2));
681 shape
->LineTo(BPoint(0, height
));
682 shape
->LineTo(BPoint(0, 0));
685 shape
->MoveTo(BPoint(height
* 2, height
/ 2));
686 shape
->LineTo(BPoint(height
, height
));
687 shape
->LineTo(BPoint(height
, 0));
690 float stopWidth
= ceilf(height
/ 6);
692 shape
->MoveTo(BPoint(height
* 2, height
));
693 shape
->LineTo(BPoint(height
* 2 + stopWidth
, height
));
694 shape
->LineTo(BPoint(height
* 2 + stopWidth
, 0));
695 shape
->LineTo(BPoint(height
* 2, 0));
703 TransportControlGroup::_CreateRewindShape(float height
) const
705 BShape
* shape
= new BShape();
707 shape
->MoveTo(BPoint(0, height
/ 2));
708 shape
->LineTo(BPoint(height
, height
));
709 shape
->LineTo(BPoint(height
, 0));
712 shape
->MoveTo(BPoint(height
, height
/ 2));
713 shape
->LineTo(BPoint(height
* 2, height
));
714 shape
->LineTo(BPoint(height
* 2, 0));
722 TransportControlGroup::_CreateForwardShape(float height
) const
724 BShape
* shape
= new BShape();
726 shape
->MoveTo(BPoint(height
, height
/ 2));
727 shape
->LineTo(BPoint(0, height
));
728 shape
->LineTo(BPoint(0, 0));
731 shape
->MoveTo(BPoint(height
* 2, height
/ 2));
732 shape
->LineTo(BPoint(height
, height
));
733 shape
->LineTo(BPoint(height
, 0));
741 TransportControlGroup::_CreatePlayShape(float height
) const
743 BShape
* shape
= new BShape();
745 float step
= floorf(height
/ 8);
747 shape
->MoveTo(BPoint(height
+ step
, height
/ 2));
748 shape
->LineTo(BPoint(-step
, height
+ step
));
749 shape
->LineTo(BPoint(-step
, 0 - step
));
757 TransportControlGroup::_CreatePauseShape(float height
) const
759 BShape
* shape
= new BShape();
761 float stemWidth
= floorf(height
/ 3);
763 shape
->MoveTo(BPoint(0, height
));
764 shape
->LineTo(BPoint(stemWidth
, height
));
765 shape
->LineTo(BPoint(stemWidth
, 0));
766 shape
->LineTo(BPoint(0, 0));
769 shape
->MoveTo(BPoint(height
- stemWidth
, height
));
770 shape
->LineTo(BPoint(height
, height
));
771 shape
->LineTo(BPoint(height
, 0));
772 shape
->LineTo(BPoint(height
- stemWidth
, 0));
780 TransportControlGroup::_CreateStopShape(float height
) const
782 BShape
* shape
= new BShape();
784 shape
->MoveTo(BPoint(0, height
));
785 shape
->LineTo(BPoint(height
, height
));
786 shape
->LineTo(BPoint(height
, 0));
787 shape
->LineTo(BPoint(0, 0));
795 add_bow(BShape
* shape
, float offset
, float size
, float height
, float step
)
797 float width
= floorf(size
* 2 / 3);
798 float outerControlHeight
= size
* 2 / 3;
799 float outerControlWidth
= size
/ 4;
800 float innerControlHeight
= size
/ 2;
801 float innerControlWidth
= size
/ 5;
803 shape
->MoveTo(BPoint(offset
, height
/ 2 + size
));
804 // outer bow, to middle
806 BPoint(offset
+ outerControlWidth
, height
/ 2 + size
),
807 BPoint(offset
+ width
, height
/ 2 + outerControlHeight
),
808 BPoint(offset
+ width
, height
/ 2)
810 // outer bow, to left/top
812 BPoint(offset
+ width
, height
/ 2 - outerControlHeight
),
813 BPoint(offset
+ outerControlWidth
, height
/ 2 - size
),
814 BPoint(offset
, height
/ 2 - size
)
816 // inner bow, to middle
818 BPoint(offset
+ innerControlWidth
, height
/ 2 - size
),
819 BPoint(offset
+ width
- step
, height
/ 2 - innerControlHeight
),
820 BPoint(offset
+ width
- step
, height
/ 2)
822 // inner bow, back to left/bottom
824 BPoint(offset
+ width
- step
, height
/ 2 + innerControlHeight
),
825 BPoint(offset
+ innerControlWidth
, height
/ 2 + size
),
826 BPoint(offset
, height
/ 2 + size
)
833 TransportControlGroup::_CreateSpeakerShape(float height
) const
835 BShape
* shape
= new BShape();
837 float step
= floorf(height
/ 8);
838 float magnetWidth
= floorf(height
/ 5);
839 float chassieWidth
= floorf(height
/ 1.5);
840 float chassieHeight
= floorf(height
/ 4);
842 shape
->MoveTo(BPoint(0, height
- step
));
843 shape
->LineTo(BPoint(magnetWidth
, height
- step
));
844 shape
->LineTo(BPoint(magnetWidth
, height
/ 2 + chassieHeight
));
845 shape
->LineTo(BPoint(magnetWidth
+ chassieWidth
- step
, height
+ step
));
846 shape
->LineTo(BPoint(magnetWidth
+ chassieWidth
, height
+ step
));
847 shape
->LineTo(BPoint(magnetWidth
+ chassieWidth
, -step
));
848 shape
->LineTo(BPoint(magnetWidth
+ chassieWidth
- step
, -step
));
849 shape
->LineTo(BPoint(magnetWidth
, height
/ 2 - chassieHeight
));
850 shape
->LineTo(BPoint(magnetWidth
, step
));
851 shape
->LineTo(BPoint(0, step
));
854 float offset
= magnetWidth
+ chassieWidth
+ step
* 2;
855 add_bow(shape
, offset
, 3 * step
, height
, step
* 2);
857 add_bow(shape
, offset
, 5 * step
, height
, step
* 2);
859 add_bow(shape
, offset
, 7 * step
, height
, step
* 2);