1/*
2 * Copyright 2005, J��r��me Duval. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Inspired by SoundCapture from Be newsletter (Media Kit Basics: Consumers
6 * and Producers)
7 */
8
9#include <Bitmap.h>
10#include <Debug.h>
11#include <MessageFilter.h>
12#include <Window.h>
13
14#include <map>
15
16#include "TransportButton.h"
17#include "DrawingTidbits.h"
18
19using std::map;
20
21class BitmapStash {
22// Bitmap stash is a simple class to hold all the lazily-allocated
23// bitmaps that the TransportButton needs when rendering itself.
24// signature is a combination of the different enabled, pressed, playing, etc.
25// flavors of a bitmap. If the stash does not have a particular bitmap,
26// it turns around to ask the button to create one and stores it for next time.
27public:
28	BitmapStash(TransportButton *);
29	~BitmapStash();
30	BBitmap *GetBitmap(uint32 signature);
31
32private:
33	TransportButton *owner;
34	map<uint32, BBitmap *> stash;
35};
36
37
38BitmapStash::BitmapStash(TransportButton *owner)
39	:	owner(owner)
40{
41}
42
43
44BBitmap *
45BitmapStash::GetBitmap(uint32 signature)
46{
47	if (stash.find(signature) == stash.end()) {
48		BBitmap *newBits = owner->MakeBitmap(signature);
49		ASSERT(newBits);
50		stash[signature] = newBits;
51	}
52
53	return stash[signature];
54}
55
56
57BitmapStash::~BitmapStash()
58{
59	// delete all the bitmaps
60	for (map<uint32, BBitmap *>::iterator i = stash.begin();
61		i != stash.end(); i++)
62		delete (*i).second;
63}
64
65
66class PeriodicMessageSender {
67	// used to send a specified message repeatedly when holding down a button
68public:
69	static PeriodicMessageSender *Launch(BMessenger target,
70		const BMessage *message, bigtime_t period);
71	void Quit();
72
73private:
74	PeriodicMessageSender(BMessenger target, const BMessage *message,
75		bigtime_t period);
76	~PeriodicMessageSender() {}
77		// use quit
78
79	static status_t TrackBinder(void *);
80	void Run();
81
82	BMessenger target;
83	BMessage message;
84
85	bigtime_t period;
86
87	bool requestToQuit;
88};
89
90
91PeriodicMessageSender::PeriodicMessageSender(BMessenger target,
92	const BMessage *message, bigtime_t period)
93	:	target(target),
94		message(*message),
95		period(period),
96		requestToQuit(false)
97{
98}
99
100
101PeriodicMessageSender *
102PeriodicMessageSender::Launch(BMessenger target, const BMessage *message,
103	bigtime_t period)
104{
105	PeriodicMessageSender *result = new PeriodicMessageSender(target,
106		message, period);
107	thread_id thread = spawn_thread(&PeriodicMessageSender::TrackBinder,
108		"ButtonRepeatingThread", B_NORMAL_PRIORITY, result);
109
110	if (thread <= 0 || resume_thread(thread) != B_OK) {
111		// didn't start, don't leak self
112		delete result;
113		result = 0;
114	}
115
116	return result;
117}
118
119
120void
121PeriodicMessageSender::Quit()
122{
123	requestToQuit = true;
124}
125
126
127status_t
128PeriodicMessageSender::TrackBinder(void *castToThis)
129{
130	((PeriodicMessageSender *)castToThis)->Run();
131	return 0;
132}
133
134
135void
136PeriodicMessageSender::Run()
137{
138	for (;;) {
139		snooze(period);
140		if (requestToQuit)
141			break;
142		target.SendMessage(&message);
143	}
144	delete this;
145}
146
147
148class SkipButtonKeypressFilter : public BMessageFilter {
149public:
150	SkipButtonKeypressFilter(uint32 shortcutKey, uint32 shortcutModifier,
151		TransportButton *target);
152
153protected:
154	filter_result Filter(BMessage *message, BHandler **handler);
155
156private:
157	uint32 shortcutKey;
158	uint32 shortcutModifier;
159	TransportButton *target;
160};
161
162
163SkipButtonKeypressFilter::SkipButtonKeypressFilter(uint32 shortcutKey,
164	uint32 shortcutModifier, TransportButton *target)
165	:	BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE),
166		shortcutKey(shortcutKey),
167		shortcutModifier(shortcutModifier),
168		target(target)
169{
170}
171
172
173filter_result
174SkipButtonKeypressFilter::Filter(BMessage *message, BHandler **handler)
175{
176	if (target->IsEnabled()
177		&& (message->what == B_KEY_DOWN || message->what == B_KEY_UP)) {
178		uint32 modifiers;
179		uint32 rawKeyChar = 0;
180		uint8 byte = 0;
181		int32 key = 0;
182
183		if (message->FindInt32("modifiers", (int32 *)&modifiers) != B_OK
184			|| message->FindInt32("raw_char", (int32 *)&rawKeyChar) != B_OK
185			|| message->FindInt8("byte", (int8 *)&byte) != B_OK
186			|| message->FindInt32("key", &key) != B_OK)
187			return B_DISPATCH_MESSAGE;
188
189		modifiers &= B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY
190			| B_OPTION_KEY | B_MENU_KEY;
191			// strip caps lock, etc.
192
193		if (modifiers == shortcutModifier && rawKeyChar == shortcutKey) {
194			if (message->what == B_KEY_DOWN)
195				target->ShortcutKeyDown();
196			else
197				target->ShortcutKeyUp();
198
199			return B_SKIP_MESSAGE;
200		}
201	}
202
203	// let others deal with this
204	return B_DISPATCH_MESSAGE;
205}
206
207
208TransportButton::TransportButton(BRect frame, const char *name,
209	const unsigned char *normalBits,
210	const unsigned char *pressedBits,
211	const unsigned char *disabledBits,
212	BMessage *invokeMessage, BMessage *startPressingMessage,
213	BMessage *pressingMessage, BMessage *donePressingMessage, bigtime_t period,
214	uint32 key, uint32 modifiers, uint32 resizeFlags)
215	:	BControl(frame, name, "", invokeMessage, resizeFlags,
216			B_WILL_DRAW | B_NAVIGABLE),
217		bitmaps(new BitmapStash(this)),
218		normalBits(normalBits),
219		pressedBits(pressedBits),
220		disabledBits(disabledBits),
221		startPressingMessage(startPressingMessage),
222		pressingMessage(pressingMessage),
223		donePressingMessage(donePressingMessage),
224		pressingPeriod(period),
225		mouseDown(false),
226		keyDown(false),
227		messageSender(0),
228		keyPressFilter(0)
229{
230	if (key)
231		keyPressFilter = new SkipButtonKeypressFilter(key, modifiers, this);
232}
233
234
235void
236TransportButton::AttachedToWindow()
237{
238	_inherited::AttachedToWindow();
239	if (keyPressFilter)
240		Window()->AddCommonFilter(keyPressFilter);
241
242	// transparent to reduce flicker
243	SetViewColor(B_TRANSPARENT_COLOR);
244}
245
246
247void
248TransportButton::DetachedFromWindow()
249{
250	if (keyPressFilter) {
251		Window()->RemoveCommonFilter(keyPressFilter);
252		delete keyPressFilter;
253	}
254	_inherited::DetachedFromWindow();
255}
256
257
258TransportButton::~TransportButton()
259{
260	delete startPressingMessage;
261	delete pressingMessage;
262	delete donePressingMessage;
263	delete bitmaps;
264}
265
266
267void
268TransportButton::WindowActivated(bool state)
269{
270	if (!state)
271		ShortcutKeyUp();
272
273	_inherited::WindowActivated(state);
274}
275
276
277void
278TransportButton::SetEnabled(bool on)
279{
280	_inherited::SetEnabled(on);
281	if (!on)
282		ShortcutKeyUp();
283}
284
285
286const unsigned char *
287TransportButton::BitsForMask(uint32 mask) const
288{
289	switch (mask) {
290		case 0:
291			return normalBits;
292		case kDisabledMask:
293			return disabledBits;
294		case kPressedMask:
295			return pressedBits;
296		default:
297			break;
298	}
299	TRESPASS();
300	return 0;
301}
302
303
304BBitmap *
305TransportButton::MakeBitmap(uint32 mask)
306{
307	BBitmap *result = new BBitmap(Bounds(), B_CMAP8);
308	result->SetBits(BitsForMask(mask), (Bounds().Width() + 1)
309		* (Bounds().Height() + 1), 0, B_CMAP8);
310
311	ReplaceTransparentColor(result, Parent()->ViewColor());
312
313	return result;
314}
315
316
317uint32
318TransportButton::ModeMask() const
319{
320	return (IsEnabled() ? 0 : kDisabledMask)
321		| (Value() ? kPressedMask : 0);
322}
323
324
325void
326TransportButton::Draw(BRect)
327{
328	DrawBitmapAsync(bitmaps->GetBitmap(ModeMask()));
329}
330
331
332void
333TransportButton::StartPressing()
334{
335	SetValue(1);
336	if (startPressingMessage)
337		Invoke(startPressingMessage);
338
339	if (pressingMessage) {
340		ASSERT(pressingMessage);
341		messageSender = PeriodicMessageSender::Launch(Messenger(),
342			pressingMessage, pressingPeriod);
343	}
344}
345
346
347void
348TransportButton::MouseCancelPressing()
349{
350	if (!mouseDown || keyDown)
351		return;
352
353	mouseDown = false;
354
355	if (pressingMessage) {
356		ASSERT(messageSender);
357		PeriodicMessageSender *sender = messageSender;
358		messageSender = 0;
359		sender->Quit();
360	}
361
362	if (donePressingMessage)
363		Invoke(donePressingMessage);
364	SetValue(0);
365}
366
367
368void
369TransportButton::DonePressing()
370{
371	if (pressingMessage) {
372		ASSERT(messageSender);
373		PeriodicMessageSender *sender = messageSender;
374		messageSender = 0;
375		sender->Quit();
376	}
377
378	Invoke();
379	SetValue(0);
380}
381
382
383void
384TransportButton::MouseStartPressing()
385{
386	if (mouseDown)
387		return;
388
389	mouseDown = true;
390	if (!keyDown)
391		StartPressing();
392}
393
394
395void
396TransportButton::MouseDonePressing()
397{
398	if (!mouseDown)
399		return;
400
401	mouseDown = false;
402	if (!keyDown)
403		DonePressing();
404}
405
406
407void
408TransportButton::ShortcutKeyDown()
409{
410	if (!IsEnabled())
411		return;
412
413	if (keyDown)
414		return;
415
416	keyDown = true;
417	if (!mouseDown)
418		StartPressing();
419}
420
421
422void
423TransportButton::ShortcutKeyUp()
424{
425	if (!keyDown)
426		return;
427
428	keyDown = false;
429	if (!mouseDown)
430		DonePressing();
431}
432
433
434void
435TransportButton::MouseDown(BPoint)
436{
437	if (!IsEnabled())
438		return;
439
440	ASSERT(Window()->Flags() & B_ASYNCHRONOUS_CONTROLS);
441	SetTracking(true);
442	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
443	MouseStartPressing();
444}
445
446void
447TransportButton::MouseMoved(BPoint point, uint32 code, const BMessage *)
448{
449	if (IsTracking() && Bounds().Contains(point) != Value()) {
450		if (!Value())
451			MouseStartPressing();
452		else
453			MouseCancelPressing();
454	}
455}
456
457void
458TransportButton::MouseUp(BPoint point)
459{
460	if (IsTracking()) {
461		if (Bounds().Contains(point))
462			MouseDonePressing();
463		else
464			MouseCancelPressing();
465		SetTracking(false);
466	}
467}
468
469void
470TransportButton::SetStartPressingMessage(BMessage *message)
471{
472	delete startPressingMessage;
473	startPressingMessage = message;
474}
475
476void
477TransportButton::SetPressingMessage(BMessage *message)
478{
479	delete pressingMessage;
480	pressingMessage = message;
481}
482
483void
484TransportButton::SetDonePressingMessage(BMessage *message)
485{
486	delete donePressingMessage;
487	donePressingMessage = message;
488}
489
490void
491TransportButton::SetPressingPeriod(bigtime_t newTime)
492{
493	pressingPeriod = newTime;
494}
495
496
497PlayPauseButton::PlayPauseButton(BRect frame, const char *name,
498	BMessage *invokeMessage, BMessage *blinkMessage,
499	uint32 key, uint32 modifiers, uint32 resizeFlags)
500	:	TransportButton(frame, name, kPlayButtonBitmapBits,
501			kPressedPlayButtonBitmapBits,
502			kDisabledPlayButtonBitmapBits, invokeMessage, NULL,
503			NULL, NULL, 0, key, modifiers, resizeFlags),
504		fState(PlayPauseButton::kStopped),
505		fLastModeMask(0),
506		fRunner(NULL),
507		fBlinkMessage(blinkMessage)
508{
509}
510
511void
512PlayPauseButton::SetStopped()
513{
514	if (fState == kStopped || fState == kAboutToPlay)
515		return;
516
517	fState = kStopped;
518	delete fRunner;
519	fRunner = NULL;
520	Invalidate();
521}
522
523void
524PlayPauseButton::SetPlaying()
525{
526	if (fState == kAboutToPause)
527		return;
528
529	// in playing state blink the LED on and off
530	if (fState == kPlayingLedOn)
531		fState = kPlayingLedOff;
532	else
533		fState = kPlayingLedOn;
534
535	Invalidate();
536}
537
538const bigtime_t kPlayingBlinkPeriod = 600000;
539
540void
541PlayPauseButton::SetPaused()
542{
543	if (fState == kAboutToPlay)
544		return;
545
546	// in paused state blink the LED on and off
547	if (fState == kPausedLedOn)
548		fState = kPausedLedOff;
549	else
550		fState = kPausedLedOn;
551
552	Invalidate();
553}
554
555uint32
556PlayPauseButton::ModeMask() const
557{
558	if (!IsEnabled())
559		return kDisabledMask;
560
561	uint32 result = 0;
562
563	if (Value())
564		result = kPressedMask;
565
566	if (fState == kPlayingLedOn || fState == kAboutToPlay)
567		result |= kPlayingMask;
568	else if (fState == kAboutToPause || fState == kPausedLedOn)
569		result |= kPausedMask;
570
571	return result;
572}
573
574const unsigned char *
575PlayPauseButton::BitsForMask(uint32 mask) const
576{
577	switch (mask) {
578		case kPlayingMask:
579			return kPlayingPlayButtonBitmapBits;
580		case kPlayingMask | kPressedMask:
581			return kPressedPlayingPlayButtonBitmapBits;
582		case kPausedMask:
583			return kPausedPlayButtonBitmapBits;
584		case kPausedMask | kPressedMask:
585			return kPressedPausedPlayButtonBitmapBits;
586		default:
587			return _inherited::BitsForMask(mask);
588	}
589	TRESPASS();
590	return 0;
591}
592
593
594void
595PlayPauseButton::StartPressing()
596{
597	if (fState == kPlayingLedOn || fState == kPlayingLedOff)
598		fState = kAboutToPause;
599	else
600	 	fState = kAboutToPlay;
601
602	_inherited::StartPressing();
603}
604
605void
606PlayPauseButton::MouseCancelPressing()
607{
608	if (fState == kAboutToPause)
609	 	fState = kPlayingLedOn;
610	else
611		fState = kStopped;
612
613	_inherited::MouseCancelPressing();
614}
615
616void
617PlayPauseButton::DonePressing()
618{
619	if (fState == kAboutToPause) {
620	 	fState = kPausedLedOn;
621	} else if (fState == kAboutToPlay) {
622		fState = kPlayingLedOn;
623		if (!fRunner && fBlinkMessage)
624			fRunner = new BMessageRunner(Messenger(), fBlinkMessage,
625				kPlayingBlinkPeriod);
626	}
627
628	_inherited::DonePressing();
629}
630
631
632RecordButton::RecordButton(BRect frame, const char *name,
633	BMessage *invokeMessage, BMessage *blinkMessage,
634	uint32 key, uint32 modifiers, uint32 resizeFlags)
635	:	TransportButton(frame, name, kRecordButtonBitmapBits,
636			kPressedRecordButtonBitmapBits,
637			kDisabledRecordButtonBitmapBits, invokeMessage, NULL, NULL,
638			NULL, 0, key, modifiers, resizeFlags),
639		fState(RecordButton::kStopped),
640		fLastModeMask(0),
641		fRunner(NULL),
642		fBlinkMessage(blinkMessage)
643{
644}
645
646void
647RecordButton::SetStopped()
648{
649	if (fState == kStopped || fState == kAboutToRecord)
650		return;
651
652	fState = kStopped;
653	delete fRunner;
654	fRunner = NULL;
655	Invalidate();
656}
657
658const bigtime_t kRecordingBlinkPeriod = 600000;
659
660void
661RecordButton::SetRecording()
662{
663	if (fState == kAboutToStop)
664		return;
665
666	if (fState == kRecordingLedOff)
667		fState = kRecordingLedOn;
668	else
669		fState = kRecordingLedOff;
670
671	Invalidate();
672}
673
674uint32
675RecordButton::ModeMask() const
676{
677	if (!IsEnabled())
678		return kDisabledMask;
679
680	uint32 result = 0;
681
682	if (Value())
683		result = kPressedMask;
684
685	if (fState == kAboutToStop || fState == kRecordingLedOn)
686		result |= kRecordingMask;
687
688	return result;
689}
690
691const unsigned char *
692RecordButton::BitsForMask(uint32 mask) const
693{
694	switch (mask) {
695		case kRecordingMask:
696			return kRecordingRecordButtonBitmapBits;
697		case kRecordingMask | kPressedMask:
698			return kPressedRecordingRecordButtonBitmapBits;
699		default:
700			return _inherited::BitsForMask(mask);
701	}
702	TRESPASS();
703	return 0;
704}
705
706
707void
708RecordButton::StartPressing()
709{
710	if (fState == kRecordingLedOn || fState == kRecordingLedOff)
711		fState = kAboutToStop;
712	else
713	 	fState = kAboutToRecord;
714
715	_inherited::StartPressing();
716}
717
718void
719RecordButton::MouseCancelPressing()
720{
721	if (fState == kAboutToStop)
722	 	fState = kRecordingLedOn;
723	else
724		fState = kStopped;
725
726	_inherited::MouseCancelPressing();
727}
728
729void
730RecordButton::DonePressing()
731{
732	if (fState == kAboutToStop) {
733	 	fState = kStopped;
734	 	delete fRunner;
735	 	fRunner = NULL;
736	} else if (fState == kAboutToRecord) {
737		fState = kRecordingLedOn;
738		if (!fRunner && fBlinkMessage)
739			fRunner = new BMessageRunner(Messenger(), fBlinkMessage,
740				kRecordingBlinkPeriod);
741	}
742
743	_inherited::DonePressing();
744}
745