/* * Copyright 2005, Jérôme Duval. All rights reserved. * Distributed under the terms of the MIT License. * * Inspired by SoundCapture from Be newsletter (Media Kit Basics: Consumers * and Producers) */ #include #include #include #include #include #include "TransportButton.h" #include "DrawingTidbits.h" using std::map; class BitmapStash { // Bitmap stash is a simple class to hold all the lazily-allocated // bitmaps that the TransportButton needs when rendering itself. // signature is a combination of the different enabled, pressed, playing, etc. // flavors of a bitmap. If the stash does not have a particular bitmap, // it turns around to ask the button to create one and stores it for next time. public: BitmapStash(TransportButton *); ~BitmapStash(); BBitmap *GetBitmap(uint32 signature); private: TransportButton *owner; map stash; }; BitmapStash::BitmapStash(TransportButton *owner) : owner(owner) { } BBitmap * BitmapStash::GetBitmap(uint32 signature) { if (stash.find(signature) == stash.end()) { BBitmap *newBits = owner->MakeBitmap(signature); ASSERT(newBits); stash[signature] = newBits; } return stash[signature]; } BitmapStash::~BitmapStash() { // delete all the bitmaps for (map::iterator i = stash.begin(); i != stash.end(); i++) delete (*i).second; } class PeriodicMessageSender { // used to send a specified message repeatedly when holding down a button public: static PeriodicMessageSender *Launch(BMessenger target, const BMessage *message, bigtime_t period); void Quit(); private: PeriodicMessageSender(BMessenger target, const BMessage *message, bigtime_t period); ~PeriodicMessageSender() {} // use quit static status_t TrackBinder(void *); void Run(); BMessenger target; BMessage message; bigtime_t period; bool requestToQuit; }; PeriodicMessageSender::PeriodicMessageSender(BMessenger target, const BMessage *message, bigtime_t period) : target(target), message(*message), period(period), requestToQuit(false) { } PeriodicMessageSender * PeriodicMessageSender::Launch(BMessenger target, const BMessage *message, bigtime_t period) { PeriodicMessageSender *result = new PeriodicMessageSender(target, message, period); thread_id thread = spawn_thread(&PeriodicMessageSender::TrackBinder, "ButtonRepeatingThread", B_NORMAL_PRIORITY, result); if (thread <= 0 || resume_thread(thread) != B_OK) { // didn't start, don't leak self delete result; result = 0; } return result; } void PeriodicMessageSender::Quit() { requestToQuit = true; } status_t PeriodicMessageSender::TrackBinder(void *castToThis) { ((PeriodicMessageSender *)castToThis)->Run(); return 0; } void PeriodicMessageSender::Run() { for (;;) { snooze(period); if (requestToQuit) break; target.SendMessage(&message); } delete this; } class SkipButtonKeypressFilter : public BMessageFilter { public: SkipButtonKeypressFilter(uint32 shortcutKey, uint32 shortcutModifier, TransportButton *target); protected: filter_result Filter(BMessage *message, BHandler **handler); private: uint32 shortcutKey; uint32 shortcutModifier; TransportButton *target; }; SkipButtonKeypressFilter::SkipButtonKeypressFilter(uint32 shortcutKey, uint32 shortcutModifier, TransportButton *target) : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), shortcutKey(shortcutKey), shortcutModifier(shortcutModifier), target(target) { } filter_result SkipButtonKeypressFilter::Filter(BMessage *message, BHandler **handler) { if (target->IsEnabled() && (message->what == B_KEY_DOWN || message->what == B_KEY_UP)) { uint32 modifiers; uint32 rawKeyChar = 0; uint8 byte = 0; int32 key = 0; if (message->FindInt32("modifiers", (int32 *)&modifiers) != B_OK || message->FindInt32("raw_char", (int32 *)&rawKeyChar) != B_OK || message->FindInt8("byte", (int8 *)&byte) != B_OK || message->FindInt32("key", &key) != B_OK) return B_DISPATCH_MESSAGE; modifiers &= B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY | B_MENU_KEY; // strip caps lock, etc. if (modifiers == shortcutModifier && rawKeyChar == shortcutKey) { if (message->what == B_KEY_DOWN) target->ShortcutKeyDown(); else target->ShortcutKeyUp(); return B_SKIP_MESSAGE; } } // let others deal with this return B_DISPATCH_MESSAGE; } TransportButton::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) : BControl(frame, name, "", invokeMessage, resizeFlags, B_WILL_DRAW | B_NAVIGABLE), bitmaps(new BitmapStash(this)), normalBits(normalBits), pressedBits(pressedBits), disabledBits(disabledBits), startPressingMessage(startPressingMessage), pressingMessage(pressingMessage), donePressingMessage(donePressingMessage), pressingPeriod(period), mouseDown(false), keyDown(false), messageSender(0), keyPressFilter(0) { if (key) keyPressFilter = new SkipButtonKeypressFilter(key, modifiers, this); } void TransportButton::AttachedToWindow() { _inherited::AttachedToWindow(); if (keyPressFilter) Window()->AddCommonFilter(keyPressFilter); // transparent to reduce flicker SetViewColor(B_TRANSPARENT_COLOR); } void TransportButton::DetachedFromWindow() { if (keyPressFilter) { Window()->RemoveCommonFilter(keyPressFilter); delete keyPressFilter; } _inherited::DetachedFromWindow(); } TransportButton::~TransportButton() { delete startPressingMessage; delete pressingMessage; delete donePressingMessage; delete bitmaps; } void TransportButton::WindowActivated(bool state) { if (!state) ShortcutKeyUp(); _inherited::WindowActivated(state); } void TransportButton::SetEnabled(bool on) { _inherited::SetEnabled(on); if (!on) ShortcutKeyUp(); } const unsigned char * TransportButton::BitsForMask(uint32 mask) const { switch (mask) { case 0: return normalBits; case kDisabledMask: return disabledBits; case kPressedMask: return pressedBits; default: break; } TRESPASS(); return 0; } BBitmap * TransportButton::MakeBitmap(uint32 mask) { BBitmap *result = new BBitmap(Bounds(), B_CMAP8); result->SetBits(BitsForMask(mask), (Bounds().Width() + 1) * (Bounds().Height() + 1), 0, B_CMAP8); ReplaceTransparentColor(result, Parent()->ViewColor()); return result; } uint32 TransportButton::ModeMask() const { return (IsEnabled() ? 0 : kDisabledMask) | (Value() ? kPressedMask : 0); } void TransportButton::Draw(BRect) { DrawBitmapAsync(bitmaps->GetBitmap(ModeMask())); } void TransportButton::StartPressing() { SetValue(1); if (startPressingMessage) Invoke(startPressingMessage); if (pressingMessage) { ASSERT(pressingMessage); messageSender = PeriodicMessageSender::Launch(Messenger(), pressingMessage, pressingPeriod); } } void TransportButton::MouseCancelPressing() { if (!mouseDown || keyDown) return; mouseDown = false; if (pressingMessage) { ASSERT(messageSender); PeriodicMessageSender *sender = messageSender; messageSender = 0; sender->Quit(); } if (donePressingMessage) Invoke(donePressingMessage); SetValue(0); } void TransportButton::DonePressing() { if (pressingMessage) { ASSERT(messageSender); PeriodicMessageSender *sender = messageSender; messageSender = 0; sender->Quit(); } Invoke(); SetValue(0); } void TransportButton::MouseStartPressing() { if (mouseDown) return; mouseDown = true; if (!keyDown) StartPressing(); } void TransportButton::MouseDonePressing() { if (!mouseDown) return; mouseDown = false; if (!keyDown) DonePressing(); } void TransportButton::ShortcutKeyDown() { if (!IsEnabled()) return; if (keyDown) return; keyDown = true; if (!mouseDown) StartPressing(); } void TransportButton::ShortcutKeyUp() { if (!keyDown) return; keyDown = false; if (!mouseDown) DonePressing(); } void TransportButton::MouseDown(BPoint) { if (!IsEnabled()) return; ASSERT(Window()->Flags() & B_ASYNCHRONOUS_CONTROLS); SetTracking(true); SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); MouseStartPressing(); } void TransportButton::MouseMoved(BPoint point, uint32 code, const BMessage *) { if (IsTracking() && Bounds().Contains(point) != Value()) { if (!Value()) MouseStartPressing(); else MouseCancelPressing(); } } void TransportButton::MouseUp(BPoint point) { if (IsTracking()) { if (Bounds().Contains(point)) MouseDonePressing(); else MouseCancelPressing(); SetTracking(false); } } void TransportButton::SetStartPressingMessage(BMessage *message) { delete startPressingMessage; startPressingMessage = message; } void TransportButton::SetPressingMessage(BMessage *message) { delete pressingMessage; pressingMessage = message; } void TransportButton::SetDonePressingMessage(BMessage *message) { delete donePressingMessage; donePressingMessage = message; } void TransportButton::SetPressingPeriod(bigtime_t newTime) { pressingPeriod = newTime; } PlayPauseButton::PlayPauseButton(BRect frame, const char *name, BMessage *invokeMessage, BMessage *blinkMessage, uint32 key, uint32 modifiers, uint32 resizeFlags) : TransportButton(frame, name, kPlayButtonBitmapBits, kPressedPlayButtonBitmapBits, kDisabledPlayButtonBitmapBits, invokeMessage, NULL, NULL, NULL, 0, key, modifiers, resizeFlags), fState(PlayPauseButton::kStopped), fLastModeMask(0), fRunner(NULL), fBlinkMessage(blinkMessage) { } void PlayPauseButton::SetStopped() { if (fState == kStopped || fState == kAboutToPlay) return; fState = kStopped; delete fRunner; fRunner = NULL; Invalidate(); } void PlayPauseButton::SetPlaying() { if (fState == kAboutToPause) return; // in playing state blink the LED on and off if (fState == kPlayingLedOn) fState = kPlayingLedOff; else fState = kPlayingLedOn; Invalidate(); } const bigtime_t kPlayingBlinkPeriod = 600000; void PlayPauseButton::SetPaused() { if (fState == kAboutToPlay) return; // in paused state blink the LED on and off if (fState == kPausedLedOn) fState = kPausedLedOff; else fState = kPausedLedOn; Invalidate(); } uint32 PlayPauseButton::ModeMask() const { if (!IsEnabled()) return kDisabledMask; uint32 result = 0; if (Value()) result = kPressedMask; if (fState == kPlayingLedOn || fState == kAboutToPlay) result |= kPlayingMask; else if (fState == kAboutToPause || fState == kPausedLedOn) result |= kPausedMask; return result; } const unsigned char * PlayPauseButton::BitsForMask(uint32 mask) const { switch (mask) { case kPlayingMask: return kPlayingPlayButtonBitmapBits; case kPlayingMask | kPressedMask: return kPressedPlayingPlayButtonBitmapBits; case kPausedMask: return kPausedPlayButtonBitmapBits; case kPausedMask | kPressedMask: return kPressedPausedPlayButtonBitmapBits; default: return _inherited::BitsForMask(mask); } TRESPASS(); return 0; } void PlayPauseButton::StartPressing() { if (fState == kPlayingLedOn || fState == kPlayingLedOff) fState = kAboutToPause; else fState = kAboutToPlay; _inherited::StartPressing(); } void PlayPauseButton::MouseCancelPressing() { if (fState == kAboutToPause) fState = kPlayingLedOn; else fState = kStopped; _inherited::MouseCancelPressing(); } void PlayPauseButton::DonePressing() { if (fState == kAboutToPause) { fState = kPausedLedOn; } else if (fState == kAboutToPlay) { fState = kPlayingLedOn; if (!fRunner && fBlinkMessage) fRunner = new BMessageRunner(Messenger(), fBlinkMessage, kPlayingBlinkPeriod); } _inherited::DonePressing(); } RecordButton::RecordButton(BRect frame, const char *name, BMessage *invokeMessage, BMessage *blinkMessage, uint32 key, uint32 modifiers, uint32 resizeFlags) : TransportButton(frame, name, kRecordButtonBitmapBits, kPressedRecordButtonBitmapBits, kDisabledRecordButtonBitmapBits, invokeMessage, NULL, NULL, NULL, 0, key, modifiers, resizeFlags), fState(RecordButton::kStopped), fLastModeMask(0), fRunner(NULL), fBlinkMessage(blinkMessage) { } void RecordButton::SetStopped() { if (fState == kStopped || fState == kAboutToRecord) return; fState = kStopped; delete fRunner; fRunner = NULL; Invalidate(); } const bigtime_t kRecordingBlinkPeriod = 600000; void RecordButton::SetRecording() { if (fState == kAboutToStop) return; if (fState == kRecordingLedOff) fState = kRecordingLedOn; else fState = kRecordingLedOff; Invalidate(); } uint32 RecordButton::ModeMask() const { if (!IsEnabled()) return kDisabledMask; uint32 result = 0; if (Value()) result = kPressedMask; if (fState == kAboutToStop || fState == kRecordingLedOn) result |= kRecordingMask; return result; } const unsigned char * RecordButton::BitsForMask(uint32 mask) const { switch (mask) { case kRecordingMask: return kRecordingRecordButtonBitmapBits; case kRecordingMask | kPressedMask: return kPressedRecordingRecordButtonBitmapBits; default: return _inherited::BitsForMask(mask); } TRESPASS(); return 0; } void RecordButton::StartPressing() { if (fState == kRecordingLedOn || fState == kRecordingLedOff) fState = kAboutToStop; else fState = kAboutToRecord; _inherited::StartPressing(); } void RecordButton::MouseCancelPressing() { if (fState == kAboutToStop) fState = kRecordingLedOn; else fState = kStopped; _inherited::MouseCancelPressing(); } void RecordButton::DonePressing() { if (fState == kAboutToStop) { fState = kStopped; delete fRunner; fRunner = NULL; } else if (fState == kAboutToRecord) { fState = kRecordingLedOn; if (!fRunner && fBlinkMessage) fRunner = new BMessageRunner(Messenger(), fBlinkMessage, kRecordingBlinkPeriod); } _inherited::DonePressing(); }