xref: /haiku/src/apps/mediaplayer/interface/TransportControlGroup.cpp (revision 83b1a68c52ba3e0e8796282759f694b7fdddf06d)
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 	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 
199 TransportControlGroup::~TransportControlGroup()
200 {
201 	if (!fSeekSlider->IsEnabled())
202 		fPositionToolTip->ReleaseReference();
203 }
204 
205 
206 void
207 TransportControlGroup::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 
228 void
229 TransportControlGroup::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 
239 void
240 TransportControlGroup::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 
285 uint32
286 TransportControlGroup::EnabledButtons()
287 {
288 	return fLastEnabledButtons;
289 }
290 
291 
292 void TransportControlGroup::TogglePlaying() {}
293 void TransportControlGroup::Stop() {}
294 void TransportControlGroup::Rewind() {}
295 void TransportControlGroup::Forward() {}
296 void TransportControlGroup::SkipBackward() {}
297 void TransportControlGroup::SkipForward() {}
298 void TransportControlGroup::VolumeChanged(float value) {}
299 void TransportControlGroup::ToggleMute() {}
300 void TransportControlGroup::PositionChanged(float value) {}
301 
302 
303 // #pragma mark -
304 
305 
306 float
307 TransportControlGroup::_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 
325 float
326 TransportControlGroup::_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 
344 float
345 TransportControlGroup::_DbToGain(float db)
346 {
347 	return pow(10.0, db / 20.0);
348 }
349 
350 
351 float
352 TransportControlGroup::_GainToDb(float gain)
353 {
354 	return 20.0 * log10(gain);
355 }
356 
357 
358 // #pragma mark -
359 
360 
361 void
362 TransportControlGroup::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 
395 void
396 TransportControlGroup::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 
417 void
418 TransportControlGroup::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 
435 void
436 TransportControlGroup::SetAudioEnabled(bool enabled)
437 {
438 	if (!LockLooper())
439 		return;
440 
441 	fMute->SetEnabled(enabled);
442 	fVolumeSlider->SetEnabled(enabled);
443 
444 	UnlockLooper();
445 }
446 
447 
448 void
449 TransportControlGroup::SetMuted(bool mute)
450 {
451 	if (!LockLooper())
452 		return;
453 
454 	fVolumeSlider->SetMuted(mute);
455 
456 	UnlockLooper();
457 }
458 
459 
460 void
461 TransportControlGroup::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 
472 void
473 TransportControlGroup::SetAudioChannelCount(int32 count)
474 {
475 	fPeakView->SetChannelCount(count);
476 }
477 
478 
479 void
480 TransportControlGroup::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 
493 float
494 TransportControlGroup::Position() const
495 {
496 	return fSeekSlider->Position();
497 }
498 
499 
500 void
501 TransportControlGroup::SetDisabledString(const char* string)
502 {
503 	fSeekSlider->SetDisabledString(string);
504 }
505 
506 
507 void
508 TransportControlGroup::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 
562 void
563 TransportControlGroup::_TogglePlaying()
564 {
565 	TogglePlaying();
566 }
567 
568 
569 void
570 TransportControlGroup::_Stop()
571 {
572 	fPlayPause->SetStopped();
573 	Stop();
574 }
575 
576 
577 void
578 TransportControlGroup::_Rewind()
579 {
580 	Rewind();
581 }
582 
583 
584 void
585 TransportControlGroup::_Forward()
586 {
587 	Forward();
588 }
589 
590 
591 void
592 TransportControlGroup::_SkipBackward()
593 {
594 	SkipBackward();
595 }
596 
597 
598 void
599 TransportControlGroup::_SkipForward()
600 {
601 	SkipForward();
602 }
603 
604 
605 void
606 TransportControlGroup::_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 
615 void
616 TransportControlGroup::_ToggleMute()
617 {
618 	fVolumeSlider->SetMuted(!fVolumeSlider->IsMuted());
619 	ToggleMute();
620 }
621 
622 
623 void
624 TransportControlGroup::_UpdatePosition()
625 {
626 	PositionChanged(fSeekSlider->Value() / (float)kPositionFactor);
627 }
628 
629 
630 // #pragma mark -
631 
632 
633 BShape*
634 TransportControlGroup::_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 
660 BShape*
661 TransportControlGroup::_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 
687 BShape*
688 TransportControlGroup::_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 
706 BShape*
707 TransportControlGroup::_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 
725 BShape*
726 TransportControlGroup::_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 
741 BShape*
742 TransportControlGroup::_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 
764 BShape*
765 TransportControlGroup::_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 
779 static void
780 add_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 
817 BShape*
818 TransportControlGroup::_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