745 lines
14 KiB
C++
745 lines
14 KiB
C++
/*
|
|
* 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 <Bitmap.h>
|
|
#include <Debug.h>
|
|
#include <MessageFilter.h>
|
|
#include <Window.h>
|
|
|
|
#include <map>
|
|
|
|
#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<uint32, BBitmap *> 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<uint32, BBitmap *>::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();
|
|
}
|