1/*
2 * Copyright 2006-2010 Stephan Aßmus <superstippi@gmx.de>
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6
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
10
11
12#include "TransportControlGroup.h"
13
14#include <stdio.h>
15#include <string.h>
16
17#include <Shape.h>
18#include <SpaceLayoutItem.h>
19#include <String.h>
20
21#include "DurationView.h"
22#include "PeakView.h"
23#include "PlaybackState.h"
24#include "PlayPauseButton.h"
25#include "PositionToolTip.h"
26#include "SeekSlider.h"
27#include "SymbolButton.h"
28#include "VolumeSlider.h"
29
30enum {
31	MSG_SEEK				= 'seek',
32	MSG_PLAY				= 'play',
33	MSG_STOP				= 'stop',
34	MSG_REWIND				= 'rwnd',
35	MSG_FORWARD				= 'frwd',
36	MSG_SKIP_BACKWARDS		= 'skpb',
37	MSG_SKIP_FORWARD		= 'skpf',
38	MSG_SET_VOLUME			= 'stvl',
39	MSG_SET_MUTE			= 'stmt',
40};
41
42// the range of the volume sliders (in dB)
43#define kVolumeDbMax	6.0
44#define kVolumeDbMin	-60.0
45// a power function for non linear sliders
46#define kVolumeDbExpPositive 1.4	// for dB values > 0
47#define kVolumeDbExpNegative 1.9	// for dB values < 0
48
49#define kVolumeFactor	100
50#define kPositionFactor	3000
51
52
53TransportControlGroup::TransportControlGroup(BRect frame, bool useSkipButtons,
54		bool usePeakView, bool useWindButtons)
55	:
56	BGroupView(B_VERTICAL, 0),
57	fSeekSlider(NULL),
58	fDurationView(NULL),
59	fPositionToolTip(NULL),
60	fPeakView(NULL),
61	fVolumeSlider(NULL),
62	fSkipBack(NULL),
63	fSkipForward(NULL),
64	fRewind(NULL),
65	fForward(NULL),
66	fPlayPause(NULL),
67	fStop(NULL),
68	fMute(NULL),
69	fSymbolScale(1.0f),
70	fLastEnabledButtons(0)
71{
72	// Pick a symbol size based on the current system font size, but make
73	// sure the size is uneven, so the pointy shapes have their middle on
74	// a pixel instead of between two pixels.
75	float symbolHeight = int(be_plain_font->Size() / 1.33) | 1;
76
77	BGroupView* seekGroup = new BGroupView(B_HORIZONTAL, 0);
78	fSeekLayout = seekGroup->GroupLayout();
79	GroupLayout()->AddView(seekGroup);
80
81    // Seek slider
82	fSeekSlider = new SeekSlider("seek slider", new BMessage(MSG_SEEK),
83		0, kPositionFactor);
84	fSeekLayout->AddView(fSeekSlider);
85
86	fPositionToolTip = new PositionToolTip();
87	fSeekSlider->SetToolTip(fPositionToolTip);
88
89    // Duration view
90	fDurationView = new DurationView("duration view");
91	fSeekLayout->AddView(fDurationView);
92
93    // Buttons
94
95	uint32 topBottomBorder = BControlLook::B_TOP_BORDER
96		| BControlLook::B_BOTTOM_BORDER;
97
98	if (useSkipButtons) {
99		// Skip Back
100		fSkipBack = new SymbolButton(B_EMPTY_STRING,
101			_CreateSkipBackwardsShape(symbolHeight),
102			new BMessage(MSG_SKIP_BACKWARDS),
103			BControlLook::B_LEFT_BORDER | topBottomBorder);
104		// Skip Foward
105		fSkipForward = new SymbolButton(B_EMPTY_STRING,
106			_CreateSkipForwardShape(symbolHeight),
107			new BMessage(MSG_SKIP_FORWARD),
108			BControlLook::B_RIGHT_BORDER | topBottomBorder);
109	}
110
111	if (useWindButtons) {
112		// Rewind
113		fRewind = new SymbolButton(B_EMPTY_STRING,
114			_CreateRewindShape(symbolHeight), new BMessage(MSG_REWIND),
115			useSkipButtons ? topBottomBorder
116				: BControlLook::B_LEFT_BORDER | topBottomBorder);
117		// Forward
118		fForward = new SymbolButton(B_EMPTY_STRING,
119			_CreateForwardShape(symbolHeight), new BMessage(MSG_FORWARD),
120			useSkipButtons ? topBottomBorder
121				: BControlLook::B_RIGHT_BORDER | topBottomBorder);
122	}
123
124	// Play Pause
125	fPlayPause = new PlayPauseButton(B_EMPTY_STRING,
126		_CreatePlayShape(symbolHeight), _CreatePauseShape(symbolHeight),
127		new BMessage(MSG_PLAY), useWindButtons || useSkipButtons
128			? topBottomBorder
129			: topBottomBorder | BControlLook::B_LEFT_BORDER);
130
131	// Stop
132	fStop = new SymbolButton(B_EMPTY_STRING,
133		_CreateStopShape(symbolHeight), new BMessage(MSG_STOP),
134		useWindButtons || useSkipButtons ? topBottomBorder
135			: topBottomBorder | BControlLook::B_RIGHT_BORDER);
136
137	// Mute
138	fMute = new SymbolButton(B_EMPTY_STRING,
139		_CreateSpeakerShape(floorf(symbolHeight * 0.9)),
140		new BMessage(MSG_SET_MUTE), 0);
141
142	// Volume Slider
143	fVolumeSlider = new VolumeSlider("volume slider",
144		_DbToGain(_ExponentialToLinear(kVolumeDbMin)) * kVolumeFactor,
145		_DbToGain(_ExponentialToLinear(kVolumeDbMax)) * kVolumeFactor,
146		kVolumeFactor, new BMessage(MSG_SET_VOLUME));
147	fVolumeSlider->SetValue(_DbToGain(_ExponentialToLinear(0.0))
148		* kVolumeFactor);
149
150	// Peak view
151	if (usePeakView)
152		fPeakView = new PeakView("peak view", false, false);
153
154	// Layout the controls
155
156	BGroupView* buttonGroup = new BGroupView(B_HORIZONTAL, 0);
157	BGroupLayout* buttonLayout = buttonGroup->GroupLayout();
158
159	if (fSkipBack != NULL)
160		buttonLayout->AddView(fSkipBack);
161	if (fRewind != NULL)
162		buttonLayout->AddView(fRewind);
163	buttonLayout->AddView(fPlayPause);
164	buttonLayout->AddView(fStop);
165	if (fForward != NULL)
166		buttonLayout->AddView(fForward);
167	if (fSkipForward != NULL)
168		buttonLayout->AddView(fSkipForward);
169
170	BGroupView* controlGroup = new BGroupView(B_HORIZONTAL, 0);
171	GroupLayout()->AddView(controlGroup);
172	fControlLayout = controlGroup->GroupLayout();
173	fControlLayout->AddView(buttonGroup, 0.6f);
174	fControlLayout->AddItem(BSpaceLayoutItem::CreateHorizontalStrut(5));
175	fControlLayout->AddView(fMute);
176	fControlLayout->AddView(fVolumeSlider);
177	if (fPeakView != NULL)
178		fControlLayout->AddView(fPeakView, 0.6f);
179
180	// Figure out the visual insets of the slider bounds towards the slider
181	// bar, and use that as insets for the rest of the layout.
182	float inset = fSeekSlider->BarFrame().left;
183	float hInset = inset - fSeekSlider->BarFrame().top;
184	if (hInset < 0.0f)
185		hInset = 0.0f;
186
187	fSeekLayout->SetInsets(0, hInset, 5, 0);
188	fControlLayout->SetInsets(inset, hInset, inset, inset);
189
190	BSize size = fControlLayout->MinSize();
191	size.width *= 3;
192	size.height = B_SIZE_UNSET;
193	fControlLayout->SetExplicitMaxSize(size);
194	fControlLayout->SetExplicitAlignment(BAlignment(B_ALIGN_CENTER,
195		B_ALIGN_TOP));
196}
197
198
199TransportControlGroup::~TransportControlGroup()
200{
201	if (!fSeekSlider->IsEnabled())
202		fPositionToolTip->ReleaseReference();
203}
204
205
206void
207TransportControlGroup::AttachedToWindow()
208{
209	SetEnabled(EnabledButtons());
210
211	// we are now a valid BHandler
212	fSeekSlider->SetTarget(this);
213	fVolumeSlider->SetTarget(this);
214	if (fSkipBack)
215		fSkipBack->SetTarget(this);
216	if (fSkipForward)
217		fSkipForward->SetTarget(this);
218	if (fRewind)
219		fRewind->SetTarget(this);
220	if (fForward)
221		fForward->SetTarget(this);
222	fPlayPause->SetTarget(this);
223	fStop->SetTarget(this);
224	fMute->SetTarget(this);
225}
226
227
228void
229TransportControlGroup::GetPreferredSize(float* _width, float* _height)
230{
231	BSize size = GroupLayout()->MinSize();
232	if (_width != NULL)
233		*_width = size.width;
234	if (_height != NULL)
235		*_height = size.height;
236}
237
238
239void
240TransportControlGroup::MessageReceived(BMessage* message)
241{
242	switch (message->what) {
243		case MSG_PLAY:
244			_TogglePlaying();
245			break;
246		case MSG_STOP:
247			_Stop();
248			break;
249
250		case MSG_REWIND:
251			_Rewind();
252			break;
253		case MSG_FORWARD:
254			_Forward();
255			break;
256
257		case MSG_SKIP_BACKWARDS:
258			_SkipBackward();
259			break;
260		case MSG_SKIP_FORWARD:
261			_SkipForward();
262			break;
263
264		case MSG_SET_VOLUME:
265			_UpdateVolume();
266			break;
267		case MSG_SET_MUTE:
268			_ToggleMute();
269			break;
270
271		case MSG_SEEK:
272			_UpdatePosition();
273			break;
274
275		default:
276		    BView::MessageReceived(message);
277		    break;
278	}
279}
280
281
282// #pragma mark - default implementation for the virtuals
283
284
285uint32
286TransportControlGroup::EnabledButtons()
287{
288	return fLastEnabledButtons;
289}
290
291
292void TransportControlGroup::TogglePlaying() {}
293void TransportControlGroup::Stop() {}
294void TransportControlGroup::Rewind() {}
295void TransportControlGroup::Forward() {}
296void TransportControlGroup::SkipBackward() {}
297void TransportControlGroup::SkipForward() {}
298void TransportControlGroup::VolumeChanged(float value) {}
299void TransportControlGroup::ToggleMute() {}
300void TransportControlGroup::PositionChanged(float value) {}
301
302
303// #pragma mark -
304
305
306float
307TransportControlGroup::_LinearToExponential(float dbIn)
308{
309	float db = dbIn;
310	if (db >= 0) {
311		db = db * (pow(fabs(kVolumeDbMax), (1.0 / kVolumeDbExpPositive))
312			/ fabs(kVolumeDbMax));
313		db = pow(db, kVolumeDbExpPositive);
314	} else {
315		db = -db;
316		db = db * (pow(fabs(kVolumeDbMin), (1.0 / kVolumeDbExpNegative))
317			/ fabs(kVolumeDbMin));
318		db = pow(db, kVolumeDbExpNegative);
319		db = -db;
320	}
321	return db;
322}
323
324
325float
326TransportControlGroup::_ExponentialToLinear(float dbIn)
327{
328	float db = dbIn;
329	if (db >= 0) {
330		db = pow(db, (1.0 / kVolumeDbExpPositive));
331		db = db * (fabs(kVolumeDbMax) / pow(fabs(kVolumeDbMax),
332			(1.0 / kVolumeDbExpPositive)));
333	} else {
334		db = -db;
335		db = pow(db, (1.0 / kVolumeDbExpNegative));
336		db = db * (fabs(kVolumeDbMin) / pow(fabs(kVolumeDbMin),
337			(1.0 / kVolumeDbExpNegative)));
338		db = -db;
339	}
340	return db;
341}
342
343
344float
345TransportControlGroup::_DbToGain(float db)
346{
347	return pow(10.0, db / 20.0);
348}
349
350
351float
352TransportControlGroup::_GainToDb(float gain)
353{
354	return 20.0 * log10(gain);
355}
356
357
358// #pragma mark -
359
360
361void
362TransportControlGroup::SetEnabled(uint32 buttons)
363{
364	if (!LockLooper())
365		return;
366
367	fLastEnabledButtons = buttons;
368
369	fSeekSlider->SetEnabled(buttons & SEEK_ENABLED);
370	fSeekSlider->SetToolTip((buttons & SEEK_ENABLED) != 0
371		? fPositionToolTip : NULL);
372
373	fVolumeSlider->SetEnabled(buttons & VOLUME_ENABLED);
374	fMute->SetEnabled(buttons & VOLUME_ENABLED);
375
376	if (fSkipBack)
377		fSkipBack->SetEnabled(buttons & SKIP_BACK_ENABLED);
378	if (fSkipForward)
379		fSkipForward->SetEnabled(buttons & SKIP_FORWARD_ENABLED);
380	if (fRewind)
381		fRewind->SetEnabled(buttons & SEEK_BACK_ENABLED);
382	if (fForward)
383		fForward->SetEnabled(buttons & SEEK_FORWARD_ENABLED);
384
385	fPlayPause->SetEnabled(buttons & PLAYBACK_ENABLED);
386	fStop->SetEnabled(buttons & PLAYBACK_ENABLED);
387
388	UnlockLooper();
389}
390
391
392// #pragma mark -
393
394
395void
396TransportControlGroup::SetPlaybackState(uint32 state)
397{
398	if (!LockLooper())
399		return;
400
401	switch (state) {
402		case PLAYBACK_STATE_PLAYING:
403			fPlayPause->SetPlaying();
404			break;
405		case PLAYBACK_STATE_PAUSED:
406			fPlayPause->SetPaused();
407			break;
408		case PLAYBACK_STATE_STOPPED:
409			fPlayPause->SetStopped();
410			break;
411	}
412
413	UnlockLooper();
414}
415
416
417void
418TransportControlGroup::SetSkippable(bool backward, bool forward)
419{
420	if (!LockLooper())
421		return;
422
423	if (fSkipBack)
424		fSkipBack->SetEnabled(backward);
425	if (fSkipForward)
426		fSkipForward->SetEnabled(forward);
427
428	UnlockLooper();
429}
430
431
432// #pragma mark -
433
434
435void
436TransportControlGroup::SetAudioEnabled(bool enabled)
437{
438	if (!LockLooper())
439		return;
440
441	fMute->SetEnabled(enabled);
442	fVolumeSlider->SetEnabled(enabled);
443
444	UnlockLooper();
445}
446
447
448void
449TransportControlGroup::SetMuted(bool mute)
450{
451	if (!LockLooper())
452		return;
453
454	fVolumeSlider->SetMuted(mute);
455
456	UnlockLooper();
457}
458
459
460void
461TransportControlGroup::SetVolume(float value)
462{
463	float db = _GainToDb(value);
464	float exponential = _LinearToExponential(db);
465	float gain = _DbToGain(exponential);
466	int32 pos = (int32)(floorf(gain * kVolumeFactor + 0.5));
467
468	fVolumeSlider->SetValue(pos);
469}
470
471
472void
473TransportControlGroup::SetAudioChannelCount(int32 count)
474{
475	fPeakView->SetChannelCount(count);
476}
477
478
479void
480TransportControlGroup::SetPosition(float value, bigtime_t position,
481	bigtime_t duration)
482{
483	fPositionToolTip->Update(position, duration);
484	fDurationView->Update(position, duration);
485
486	if (fSeekSlider->IsTracking())
487		return;
488
489	fSeekSlider->SetPosition(value);
490}
491
492
493float
494TransportControlGroup::Position() const
495{
496	return fSeekSlider->Position();
497}
498
499
500void
501TransportControlGroup::SetDisabledString(const char* string)
502{
503	fSeekSlider->SetDisabledString(string);
504}
505
506
507void
508TransportControlGroup::SetSymbolScale(float scale)
509{
510	if (scale == fSymbolScale)
511		return;
512
513	fSymbolScale = scale;
514
515	if (fSeekSlider != NULL)
516		fSeekSlider->SetSymbolScale(scale);
517	if (fVolumeSlider != NULL) {
518		fVolumeSlider->SetBarThickness(fVolumeSlider->PreferredBarThickness()
519			* scale);
520	}
521	if (fDurationView != NULL)
522		fDurationView->SetSymbolScale(scale);
523
524	float symbolHeight = int(scale * be_plain_font->Size() / 1.33) | 1;
525
526	if (fSkipBack != NULL)
527		fSkipBack->SetSymbol(_CreateSkipBackwardsShape(symbolHeight));
528	if (fSkipForward != NULL)
529		fSkipForward->SetSymbol(_CreateSkipForwardShape(symbolHeight));
530	if (fRewind != NULL)
531		fRewind->SetSymbol(_CreateRewindShape(symbolHeight));
532	if (fForward != NULL)
533		fForward->SetSymbol(_CreateForwardShape(symbolHeight));
534	if (fPlayPause != NULL) {
535		fPlayPause->SetSymbols(_CreatePlayShape(symbolHeight),
536			_CreatePauseShape(symbolHeight));
537	}
538	if (fStop != NULL)
539		fStop->SetSymbol(_CreateStopShape(symbolHeight));
540	if (fMute != NULL)
541		fMute->SetSymbol(_CreateSpeakerShape(floorf(symbolHeight * 0.9)));
542
543	// Figure out the visual insets of the slider bounds towards the slider
544	// bar, and use that as insets for the rest of the layout.
545	float barInset = fSeekSlider->BarFrame().left;
546	float inset = barInset * scale;
547	float hInset = inset - fSeekSlider->BarFrame().top;
548	if (hInset < 0.0f)
549		hInset = 0.0f;
550
551	fSeekLayout->SetInsets(inset - barInset, hInset, inset, 0);
552	fSeekLayout->SetSpacing(inset - barInset);
553	fControlLayout->SetInsets(inset, hInset, inset, inset);
554	fControlLayout->SetSpacing(inset - barInset);
555
556	ResizeTo(Bounds().Width(), GroupLayout()->MinSize().height);
557}
558
559// #pragma mark -
560
561
562void
563TransportControlGroup::_TogglePlaying()
564{
565	TogglePlaying();
566}
567
568
569void
570TransportControlGroup::_Stop()
571{
572	fPlayPause->SetStopped();
573	Stop();
574}
575
576
577void
578TransportControlGroup::_Rewind()
579{
580	Rewind();
581}
582
583
584void
585TransportControlGroup::_Forward()
586{
587	Forward();
588}
589
590
591void
592TransportControlGroup::_SkipBackward()
593{
594	SkipBackward();
595}
596
597
598void
599TransportControlGroup::_SkipForward()
600{
601	SkipForward();
602}
603
604
605void
606TransportControlGroup::_UpdateVolume()
607{
608	float pos = fVolumeSlider->Value() / (float)kVolumeFactor;
609	float db = _ExponentialToLinear(_GainToDb(pos));
610	float gain = _DbToGain(db);
611	VolumeChanged(gain);
612}
613
614
615void
616TransportControlGroup::_ToggleMute()
617{
618	fVolumeSlider->SetMuted(!fVolumeSlider->IsMuted());
619	ToggleMute();
620}
621
622
623void
624TransportControlGroup::_UpdatePosition()
625{
626	PositionChanged(fSeekSlider->Value() / (float)kPositionFactor);
627}
628
629
630// #pragma mark -
631
632
633BShape*
634TransportControlGroup::_CreateSkipBackwardsShape(float height) const
635{
636	BShape* shape = new BShape();
637
638	float stopWidth = ceilf(height / 6);
639
640	shape->MoveTo(BPoint(-stopWidth, height));
641	shape->LineTo(BPoint(0, height));
642	shape->LineTo(BPoint(0, 0));
643	shape->LineTo(BPoint(-stopWidth, 0));
644	shape->Close();
645
646	shape->MoveTo(BPoint(0, height / 2));
647	shape->LineTo(BPoint(height, height));
648	shape->LineTo(BPoint(height, 0));
649	shape->Close();
650
651	shape->MoveTo(BPoint(height, height / 2));
652	shape->LineTo(BPoint(height * 2, height));
653	shape->LineTo(BPoint(height * 2, 0));
654	shape->Close();
655
656	return shape;
657}
658
659
660BShape*
661TransportControlGroup::_CreateSkipForwardShape(float height) const
662{
663	BShape* shape = new BShape();
664
665	shape->MoveTo(BPoint(height, height / 2));
666	shape->LineTo(BPoint(0, height));
667	shape->LineTo(BPoint(0, 0));
668	shape->Close();
669
670	shape->MoveTo(BPoint(height * 2, height / 2));
671	shape->LineTo(BPoint(height, height));
672	shape->LineTo(BPoint(height, 0));
673	shape->Close();
674
675	float stopWidth = ceilf(height / 6);
676
677	shape->MoveTo(BPoint(height * 2, height));
678	shape->LineTo(BPoint(height * 2 + stopWidth, height));
679	shape->LineTo(BPoint(height * 2 + stopWidth, 0));
680	shape->LineTo(BPoint(height * 2, 0));
681	shape->Close();
682
683	return shape;
684}
685
686
687BShape*
688TransportControlGroup::_CreateRewindShape(float height) const
689{
690	BShape* shape = new BShape();
691
692	shape->MoveTo(BPoint(0, height / 2));
693	shape->LineTo(BPoint(height, height));
694	shape->LineTo(BPoint(height, 0));
695	shape->Close();
696
697	shape->MoveTo(BPoint(height, height / 2));
698	shape->LineTo(BPoint(height * 2, height));
699	shape->LineTo(BPoint(height * 2, 0));
700	shape->Close();
701
702	return shape;
703}
704
705
706BShape*
707TransportControlGroup::_CreateForwardShape(float height) const
708{
709	BShape* shape = new BShape();
710
711	shape->MoveTo(BPoint(height, height / 2));
712	shape->LineTo(BPoint(0, height));
713	shape->LineTo(BPoint(0, 0));
714	shape->Close();
715
716	shape->MoveTo(BPoint(height * 2, height / 2));
717	shape->LineTo(BPoint(height, height));
718	shape->LineTo(BPoint(height, 0));
719	shape->Close();
720
721	return shape;
722}
723
724
725BShape*
726TransportControlGroup::_CreatePlayShape(float height) const
727{
728	BShape* shape = new BShape();
729
730	float step = floorf(height / 8);
731
732	shape->MoveTo(BPoint(height + step, height / 2));
733	shape->LineTo(BPoint(-step, height + step));
734	shape->LineTo(BPoint(-step, 0 - step));
735	shape->Close();
736
737	return shape;
738}
739
740
741BShape*
742TransportControlGroup::_CreatePauseShape(float height) const
743{
744	BShape* shape = new BShape();
745
746	float stemWidth = floorf(height / 3);
747
748	shape->MoveTo(BPoint(0, height));
749	shape->LineTo(BPoint(stemWidth, height));
750	shape->LineTo(BPoint(stemWidth, 0));
751	shape->LineTo(BPoint(0, 0));
752	shape->Close();
753
754	shape->MoveTo(BPoint(height - stemWidth, height));
755	shape->LineTo(BPoint(height, height));
756	shape->LineTo(BPoint(height, 0));
757	shape->LineTo(BPoint(height - stemWidth, 0));
758	shape->Close();
759
760	return shape;
761}
762
763
764BShape*
765TransportControlGroup::_CreateStopShape(float height) const
766{
767	BShape* shape = new BShape();
768
769	shape->MoveTo(BPoint(0, height));
770	shape->LineTo(BPoint(height, height));
771	shape->LineTo(BPoint(height, 0));
772	shape->LineTo(BPoint(0, 0));
773	shape->Close();
774
775	return shape;
776}
777
778
779static void
780add_bow(BShape* shape, float offset, float size, float height, float step)
781{
782	float width = floorf(size * 2 / 3);
783	float outerControlHeight = size * 2 / 3;
784	float outerControlWidth = size / 4;
785	float innerControlHeight = size / 2;
786	float innerControlWidth = size / 5;
787	// left/bottom
788	shape->MoveTo(BPoint(offset, height / 2 + size));
789	// outer bow, to middle
790	shape->BezierTo(
791		BPoint(offset + outerControlWidth, height / 2 + size),
792		BPoint(offset + width, height / 2 + outerControlHeight),
793		BPoint(offset + width, height / 2)
794	);
795	// outer bow, to left/top
796	shape->BezierTo(
797		BPoint(offset + width, height / 2 - outerControlHeight),
798		BPoint(offset + outerControlWidth, height / 2 - size),
799		BPoint(offset, height / 2 - size)
800	);
801	// inner bow, to middle
802	shape->BezierTo(
803		BPoint(offset + innerControlWidth, height / 2 - size),
804		BPoint(offset + width - step, height / 2 - innerControlHeight),
805		BPoint(offset + width - step, height / 2)
806	);
807	// inner bow, back to left/bottom
808	shape->BezierTo(
809		BPoint(offset + width - step, height / 2 + innerControlHeight),
810		BPoint(offset + innerControlWidth, height / 2 + size),
811		BPoint(offset, height / 2 + size)
812	);
813	shape->Close();
814}
815
816
817BShape*
818TransportControlGroup::_CreateSpeakerShape(float height) const
819{
820	BShape* shape = new BShape();
821
822	float step = floorf(height / 8);
823	float magnetWidth = floorf(height / 5);
824	float chassieWidth = floorf(height / 1.5);
825	float chassieHeight = floorf(height / 4);
826
827	shape->MoveTo(BPoint(0, height - step));
828	shape->LineTo(BPoint(magnetWidth, height - step));
829	shape->LineTo(BPoint(magnetWidth, height / 2 + chassieHeight));
830	shape->LineTo(BPoint(magnetWidth + chassieWidth - step, height + step));
831	shape->LineTo(BPoint(magnetWidth + chassieWidth, height + step));
832	shape->LineTo(BPoint(magnetWidth + chassieWidth, -step));
833	shape->LineTo(BPoint(magnetWidth + chassieWidth - step, -step));
834	shape->LineTo(BPoint(magnetWidth, height / 2 - chassieHeight));
835	shape->LineTo(BPoint(magnetWidth, step));
836	shape->LineTo(BPoint(0, step));
837	shape->Close();
838
839	float offset = magnetWidth + chassieWidth + step * 2;
840	add_bow(shape, offset, 3 * step, height, step * 2);
841	offset += step * 2;
842	add_bow(shape, offset, 5 * step, height, step * 2);
843	offset += step * 2;
844	add_bow(shape, offset, 7 * step, height, step * 2);
845
846	return shape;
847}
848
849