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