xref: /haiku/src/apps/soundrecorder/TransportButton.cpp (revision 3ee964070b768aa3482c08fa82aa73e70cd175d1)
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 
19 using std::map;
20 
21 class 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.
27 public:
28 	BitmapStash(TransportButton *);
29 	~BitmapStash();
30 	BBitmap *GetBitmap(uint32 signature);
31 
32 private:
33 	TransportButton *owner;
34 	map<uint32, BBitmap *> stash;
35 };
36 
37 
BitmapStash(TransportButton * owner)38 BitmapStash::BitmapStash(TransportButton *owner)
39 	:	owner(owner)
40 {
41 }
42 
43 
44 BBitmap *
GetBitmap(uint32 signature)45 BitmapStash::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 
~BitmapStash()57 BitmapStash::~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 
66 class PeriodicMessageSender {
67 	// used to send a specified message repeatedly when holding down a button
68 public:
69 	static PeriodicMessageSender *Launch(BMessenger target,
70 		const BMessage *message, bigtime_t period);
71 	void Quit();
72 
73 private:
74 	PeriodicMessageSender(BMessenger target, const BMessage *message,
75 		bigtime_t period);
~PeriodicMessageSender()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 
PeriodicMessageSender(BMessenger target,const BMessage * message,bigtime_t period)91 PeriodicMessageSender::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 
101 PeriodicMessageSender *
Launch(BMessenger target,const BMessage * message,bigtime_t period)102 PeriodicMessageSender::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 
120 void
Quit()121 PeriodicMessageSender::Quit()
122 {
123 	requestToQuit = true;
124 }
125 
126 
127 status_t
TrackBinder(void * castToThis)128 PeriodicMessageSender::TrackBinder(void *castToThis)
129 {
130 	((PeriodicMessageSender *)castToThis)->Run();
131 	return 0;
132 }
133 
134 
135 void
Run()136 PeriodicMessageSender::Run()
137 {
138 	for (;;) {
139 		snooze(period);
140 		if (requestToQuit)
141 			break;
142 		target.SendMessage(&message);
143 	}
144 	delete this;
145 }
146 
147 
148 class SkipButtonKeypressFilter : public BMessageFilter {
149 public:
150 	SkipButtonKeypressFilter(uint32 shortcutKey, uint32 shortcutModifier,
151 		TransportButton *target);
152 
153 protected:
154 	filter_result Filter(BMessage *message, BHandler **handler);
155 
156 private:
157 	uint32 shortcutKey;
158 	uint32 shortcutModifier;
159 	TransportButton *target;
160 };
161 
162 
SkipButtonKeypressFilter(uint32 shortcutKey,uint32 shortcutModifier,TransportButton * target)163 SkipButtonKeypressFilter::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 
173 filter_result
Filter(BMessage * message,BHandler ** handler)174 SkipButtonKeypressFilter::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 
TransportButton(BRect frame,const char * name,const unsigned char * normalBits,const unsigned char * pressedBits,const unsigned char * disabledBits,BMessage * invokeMessage,BMessage * startPressingMessage,BMessage * pressingMessage,BMessage * donePressingMessage,bigtime_t period,uint32 key,uint32 modifiers,uint32 resizeFlags)208 TransportButton::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 
235 void
AttachedToWindow()236 TransportButton::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 
247 void
DetachedFromWindow()248 TransportButton::DetachedFromWindow()
249 {
250 	if (keyPressFilter) {
251 		Window()->RemoveCommonFilter(keyPressFilter);
252 		delete keyPressFilter;
253 	}
254 	_inherited::DetachedFromWindow();
255 }
256 
257 
~TransportButton()258 TransportButton::~TransportButton()
259 {
260 	delete startPressingMessage;
261 	delete pressingMessage;
262 	delete donePressingMessage;
263 	delete bitmaps;
264 }
265 
266 
267 void
WindowActivated(bool state)268 TransportButton::WindowActivated(bool state)
269 {
270 	if (!state)
271 		ShortcutKeyUp();
272 
273 	_inherited::WindowActivated(state);
274 }
275 
276 
277 void
SetEnabled(bool on)278 TransportButton::SetEnabled(bool on)
279 {
280 	_inherited::SetEnabled(on);
281 	if (!on)
282 		ShortcutKeyUp();
283 }
284 
285 
286 const unsigned char *
BitsForMask(uint32 mask) const287 TransportButton::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 
304 BBitmap *
MakeBitmap(uint32 mask)305 TransportButton::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 
317 uint32
ModeMask() const318 TransportButton::ModeMask() const
319 {
320 	return (IsEnabled() ? 0 : kDisabledMask)
321 		| (Value() ? kPressedMask : 0);
322 }
323 
324 
325 void
Draw(BRect)326 TransportButton::Draw(BRect)
327 {
328 	DrawBitmapAsync(bitmaps->GetBitmap(ModeMask()));
329 }
330 
331 
332 void
StartPressing()333 TransportButton::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 
347 void
MouseCancelPressing()348 TransportButton::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 
368 void
DonePressing()369 TransportButton::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 
383 void
MouseStartPressing()384 TransportButton::MouseStartPressing()
385 {
386 	if (mouseDown)
387 		return;
388 
389 	mouseDown = true;
390 	if (!keyDown)
391 		StartPressing();
392 }
393 
394 
395 void
MouseDonePressing()396 TransportButton::MouseDonePressing()
397 {
398 	if (!mouseDown)
399 		return;
400 
401 	mouseDown = false;
402 	if (!keyDown)
403 		DonePressing();
404 }
405 
406 
407 void
ShortcutKeyDown()408 TransportButton::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 
422 void
ShortcutKeyUp()423 TransportButton::ShortcutKeyUp()
424 {
425 	if (!keyDown)
426 		return;
427 
428 	keyDown = false;
429 	if (!mouseDown)
430 		DonePressing();
431 }
432 
433 
434 void
MouseDown(BPoint)435 TransportButton::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 
446 void
MouseMoved(BPoint point,uint32 code,const BMessage *)447 TransportButton::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 
457 void
MouseUp(BPoint point)458 TransportButton::MouseUp(BPoint point)
459 {
460 	if (IsTracking()) {
461 		if (Bounds().Contains(point))
462 			MouseDonePressing();
463 		else
464 			MouseCancelPressing();
465 		SetTracking(false);
466 	}
467 }
468 
469 void
SetStartPressingMessage(BMessage * message)470 TransportButton::SetStartPressingMessage(BMessage *message)
471 {
472 	delete startPressingMessage;
473 	startPressingMessage = message;
474 }
475 
476 void
SetPressingMessage(BMessage * message)477 TransportButton::SetPressingMessage(BMessage *message)
478 {
479 	delete pressingMessage;
480 	pressingMessage = message;
481 }
482 
483 void
SetDonePressingMessage(BMessage * message)484 TransportButton::SetDonePressingMessage(BMessage *message)
485 {
486 	delete donePressingMessage;
487 	donePressingMessage = message;
488 }
489 
490 void
SetPressingPeriod(bigtime_t newTime)491 TransportButton::SetPressingPeriod(bigtime_t newTime)
492 {
493 	pressingPeriod = newTime;
494 }
495 
496 
PlayPauseButton(BRect frame,const char * name,BMessage * invokeMessage,BMessage * blinkMessage,uint32 key,uint32 modifiers,uint32 resizeFlags)497 PlayPauseButton::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 
511 void
SetStopped()512 PlayPauseButton::SetStopped()
513 {
514 	if (fState == kStopped || fState == kAboutToPlay)
515 		return;
516 
517 	fState = kStopped;
518 	delete fRunner;
519 	fRunner = NULL;
520 	Invalidate();
521 }
522 
523 void
SetPlaying()524 PlayPauseButton::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 
538 const bigtime_t kPlayingBlinkPeriod = 600000;
539 
540 void
SetPaused()541 PlayPauseButton::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 
555 uint32
ModeMask() const556 PlayPauseButton::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 
574 const unsigned char *
BitsForMask(uint32 mask) const575 PlayPauseButton::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 
594 void
StartPressing()595 PlayPauseButton::StartPressing()
596 {
597 	if (fState == kPlayingLedOn || fState == kPlayingLedOff)
598 		fState = kAboutToPause;
599 	else
600 	 	fState = kAboutToPlay;
601 
602 	_inherited::StartPressing();
603 }
604 
605 void
MouseCancelPressing()606 PlayPauseButton::MouseCancelPressing()
607 {
608 	if (fState == kAboutToPause)
609 	 	fState = kPlayingLedOn;
610 	else
611 		fState = kStopped;
612 
613 	_inherited::MouseCancelPressing();
614 }
615 
616 void
DonePressing()617 PlayPauseButton::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 
RecordButton(BRect frame,const char * name,BMessage * invokeMessage,BMessage * blinkMessage,uint32 key,uint32 modifiers,uint32 resizeFlags)632 RecordButton::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 
646 void
SetStopped()647 RecordButton::SetStopped()
648 {
649 	if (fState == kStopped || fState == kAboutToRecord)
650 		return;
651 
652 	fState = kStopped;
653 	delete fRunner;
654 	fRunner = NULL;
655 	Invalidate();
656 }
657 
658 const bigtime_t kRecordingBlinkPeriod = 600000;
659 
660 void
SetRecording()661 RecordButton::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 
674 uint32
ModeMask() const675 RecordButton::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 
691 const unsigned char *
BitsForMask(uint32 mask) const692 RecordButton::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 
707 void
StartPressing()708 RecordButton::StartPressing()
709 {
710 	if (fState == kRecordingLedOn || fState == kRecordingLedOff)
711 		fState = kAboutToStop;
712 	else
713 	 	fState = kAboutToRecord;
714 
715 	_inherited::StartPressing();
716 }
717 
718 void
MouseCancelPressing()719 RecordButton::MouseCancelPressing()
720 {
721 	if (fState == kAboutToStop)
722 	 	fState = kRecordingLedOn;
723 	else
724 		fState = kStopped;
725 
726 	_inherited::MouseCancelPressing();
727 }
728 
729 void
DonePressing()730 RecordButton::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