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