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