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