xref: /haiku/src/apps/mediaplayer/interface/TransportControlGroup.cpp (revision aa7f93c54ccd81db58dedaf78e6fed173364de41)
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 
32 enum {
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 
TransportControlGroup(BRect frame,bool useSkipButtons,bool usePeakView,bool useWindButtons)56 TransportControlGroup::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 
~TransportControlGroup()202 TransportControlGroup::~TransportControlGroup()
203 {
204 	if (!fSeekSlider->IsEnabled())
205 		fPositionToolTip->ReleaseReference();
206 }
207 
208 
209 void
AttachedToWindow()210 TransportControlGroup::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 
231 void
GetPreferredSize(float * _width,float * _height)232 TransportControlGroup::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 
242 void
MessageReceived(BMessage * message)243 TransportControlGroup::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 
297 uint32
EnabledButtons()298 TransportControlGroup::EnabledButtons()
299 {
300 	return fLastEnabledButtons;
301 }
302 
303 
TogglePlaying()304 void TransportControlGroup::TogglePlaying() {}
Stop()305 void TransportControlGroup::Stop() {}
Rewind()306 void TransportControlGroup::Rewind() {}
Forward()307 void TransportControlGroup::Forward() {}
SkipBackward()308 void TransportControlGroup::SkipBackward() {}
SkipForward()309 void TransportControlGroup::SkipForward() {}
VolumeChanged(float value)310 void TransportControlGroup::VolumeChanged(float value) {}
ToggleMute()311 void TransportControlGroup::ToggleMute() {}
PositionChanged(float value)312 void TransportControlGroup::PositionChanged(float value) {}
313 
314 
315 // #pragma mark -
316 
317 
318 float
_LinearToExponential(float dbIn)319 TransportControlGroup::_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 
337 float
_ExponentialToLinear(float dbIn)338 TransportControlGroup::_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 
356 float
_DbToGain(float db)357 TransportControlGroup::_DbToGain(float db)
358 {
359 	return pow(10.0, db / 20.0);
360 }
361 
362 
363 float
_GainToDb(float gain)364 TransportControlGroup::_GainToDb(float gain)
365 {
366 	return 20.0 * log10(gain);
367 }
368 
369 
370 // #pragma mark -
371 
372 
373 void
SetEnabled(uint32 buttons)374 TransportControlGroup::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 
407 void
SetPlaybackState(uint32 state)408 TransportControlGroup::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 
429 void
SetSkippable(bool backward,bool forward)430 TransportControlGroup::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 
447 void
SetAudioEnabled(bool enabled)448 TransportControlGroup::SetAudioEnabled(bool enabled)
449 {
450 	if (!LockLooper())
451 		return;
452 
453 	fMute->SetEnabled(enabled);
454 	fVolumeSlider->SetEnabled(enabled);
455 
456 	UnlockLooper();
457 }
458 
459 
460 void
SetMuted(bool mute)461 TransportControlGroup::SetMuted(bool mute)
462 {
463 	if (!LockLooper())
464 		return;
465 
466 	fVolumeSlider->SetMuted(mute);
467 
468 	UnlockLooper();
469 }
470 
471 
472 void
SetVolume(float value)473 TransportControlGroup::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 
484 void
SetAudioChannelCount(int32 count)485 TransportControlGroup::SetAudioChannelCount(int32 count)
486 {
487 	fPeakView->SetChannelCount(count);
488 }
489 
490 
491 void
SetPosition(float value,bigtime_t position,bigtime_t duration)492 TransportControlGroup::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 
505 float
Position() const506 TransportControlGroup::Position() const
507 {
508 	return fSeekSlider->Position();
509 }
510 
511 
512 void
SetDisabledString(const char * string)513 TransportControlGroup::SetDisabledString(const char* string)
514 {
515 	fSeekSlider->SetDisabledString(string);
516 }
517 
518 
519 void
SetSymbolScale(float scale)520 TransportControlGroup::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 
574 void
_TogglePlaying()575 TransportControlGroup::_TogglePlaying()
576 {
577 	TogglePlaying();
578 }
579 
580 
581 void
_Stop()582 TransportControlGroup::_Stop()
583 {
584 	fPlayPause->SetStopped();
585 	Stop();
586 }
587 
588 
589 void
_Rewind()590 TransportControlGroup::_Rewind()
591 {
592 	Rewind();
593 }
594 
595 
596 void
_Forward()597 TransportControlGroup::_Forward()
598 {
599 	Forward();
600 }
601 
602 
603 void
_SkipBackward()604 TransportControlGroup::_SkipBackward()
605 {
606 	SkipBackward();
607 }
608 
609 
610 void
_SkipForward()611 TransportControlGroup::_SkipForward()
612 {
613 	SkipForward();
614 }
615 
616 
617 void
_UpdateVolume()618 TransportControlGroup::_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 
627 void
_ToggleMute()628 TransportControlGroup::_ToggleMute()
629 {
630 	fVolumeSlider->SetMuted(!fVolumeSlider->IsMuted());
631 	ToggleMute();
632 }
633 
634 
635 void
_UpdatePosition()636 TransportControlGroup::_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 
648 BShape*
_CreateSkipBackwardsShape(float height) const649 TransportControlGroup::_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 
675 BShape*
_CreateSkipForwardShape(float height) const676 TransportControlGroup::_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 
702 BShape*
_CreateRewindShape(float height) const703 TransportControlGroup::_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 
721 BShape*
_CreateForwardShape(float height) const722 TransportControlGroup::_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 
740 BShape*
_CreatePlayShape(float height) const741 TransportControlGroup::_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 
756 BShape*
_CreatePauseShape(float height) const757 TransportControlGroup::_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 
779 BShape*
_CreateStopShape(float height) const780 TransportControlGroup::_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 
794 static void
add_bow(BShape * shape,float offset,float size,float height,float step)795 add_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 
832 BShape*
_CreateSpeakerShape(float height) const833 TransportControlGroup::_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