haiku/src/apps/soundrecorder/TransportButton.cpp

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();
}