* Copyright 2001-2015 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, axeld@pinc-software.de
* Erik Jaesler, erik@cgsoftware.com
* John Scipione, jscipione@gmail.com
*/
#include <Alert.h>
#include <new>
#include <stdio.h>
#include <Bitmap.h>
#include <Button.h>
#include <ControlLook.h>
#include <Debug.h>
#include <FindDirectory.h>
#include <IconUtils.h>
#include <LayoutBuilder.h>
#include <MenuField.h>
#include <MessageFilter.h>
#include <Path.h>
#include <Resources.h>
#include <Screen.h>
#include <String.h>
#include <Window.h>
#include <binary_compatibility/Interface.h>
#ifdef DEBUG_ALERT
# define FTRACE(x) fprintf(x)
#else
# define FTRACE(x) ;
#endif
class TAlertView : public BView {
public:
TAlertView();
TAlertView(BMessage* archive);
~TAlertView();
static TAlertView* Instantiate(BMessage* archive);
virtual status_t Archive(BMessage* archive,
bool deep = true) const;
virtual void GetPreferredSize(float* _width, float* _height);
virtual BSize MaxSize();
virtual void Draw(BRect updateRect);
void SetBitmap(BBitmap* icon);
BBitmap* Bitmap()
{ return fIconBitmap; }
private:
BBitmap* fIconBitmap;
};
class _BAlertFilter_ : public BMessageFilter {
public:
_BAlertFilter_(BAlert* Alert);
~_BAlertFilter_();
virtual filter_result Filter(BMessage* msg, BHandler** target);
private:
BAlert* fAlert;
};
static const unsigned int kAlertButtonMsg = 'ALTB';
static const int kSemTimeOut = 50000;
static const int kButtonOffsetSpacing = 62;
static const int kButtonUsualWidth = 55;
static const int kIconStripeWidthFactor = 5;
static const int kWindowMinWidth = 310;
static const int kWindowOffsetMinWidth = 335;
BAlert::BAlert()
:
BWindow(BRect(0, 0, 100, 100), "", B_MODAL_WINDOW,
B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS)
{
_Init(NULL, NULL, NULL, NULL, B_WIDTH_FROM_WIDEST, B_EVEN_SPACING,
B_INFO_ALERT);
}
BAlert::BAlert(const char *title, const char *text, const char *button1,
const char *button2, const char *button3, button_width width,
alert_type type)
:
BWindow(BRect(0, 0, 100, 100), title, B_MODAL_WINDOW,
B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS)
{
_Init(text, button1, button2, button3, width, B_EVEN_SPACING, type);
}
BAlert::BAlert(const char *title, const char *text, const char *button1,
const char *button2, const char *button3, button_width width,
button_spacing spacing, alert_type type)
:
BWindow(BRect(0, 0, 100, 100), title, B_MODAL_WINDOW,
B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS)
{
_Init(text, button1, button2, button3, width, spacing, type);
}
BAlert::BAlert(BMessage* data)
:
BWindow(data)
{
fInvoker = NULL;
fAlertSem = -1;
fAlertValue = -1;
fTextView = (BTextView*)FindView("_tv_");
TAlertView* view = (TAlertView*)FindView("_master_");
if (view)
view->SetBitmap(_CreateTypeIcon());
char key;
for (int32 i = 0; i < 3; ++i) {
if (data->FindInt8("_but_key", i, (int8*)&key) == B_OK)
fKeys[i] = key;
}
AddCommonFilter(new(std::nothrow) _BAlertFilter_(this));
}
BAlert::~BAlert()
{
if (fAlertSem >= B_OK)
delete_sem(fAlertSem);
}
BArchivable*
BAlert::Instantiate(BMessage* data)
{
if (!validate_instantiation(data, "BAlert"))
return NULL;
return new(std::nothrow) BAlert(data);
}
status_t
BAlert::Archive(BMessage* data, bool deep) const
{
status_t ret = BWindow::Archive(data, deep);
if (ret == B_OK)
ret = data->AddString("_text", fTextView->Text());
if (ret == B_OK)
ret = data->AddInt32("_atype", fType);
if (ret == B_OK)
ret = data->AddInt32("_but_width", fButtonWidth);
if (fKeys[0] || fKeys[1] || fKeys[2]) {
if (ret == B_OK)
ret = data->AddInt8("_but_key", fKeys[0]);
if (ret == B_OK)
ret = data->AddInt8("_but_key", fKeys[1]);
if (ret == B_OK)
ret = data->AddInt8("_but_key", fKeys[2]);
}
return ret;
}
alert_type
BAlert::Type() const
{
return (alert_type)fType;
}
void
BAlert::SetType(alert_type type)
{
fType = type;
}
void
BAlert::SetText(const char* text)
{
TextView()->SetText(text);
}
void
BAlert::SetIcon(BBitmap* bitmap)
{
fIconView->SetBitmap(bitmap);
}
void
BAlert::SetButtonSpacing(button_spacing spacing)
{
fButtonSpacing = spacing;
}
void
BAlert::SetButtonWidth(button_width width)
{
fButtonWidth = width;
}
void
BAlert::SetShortcut(int32 index, char key)
{
if (index >= 0 && (size_t)index < fKeys.size())
fKeys[index] = key;
}
char
BAlert::Shortcut(int32 index) const
{
if (index >= 0 && (size_t)index < fKeys.size())
return fKeys[index];
return 0;
}
int32
BAlert::Go()
{
fAlertSem = create_sem(0, "AlertSem");
if (fAlertSem < 0) {
Quit();
return -1;
}
BWindow* window = dynamic_cast<BWindow*>(
BLooper::LooperForThread(find_thread(NULL)));
_Prepare();
Show();
if (window != NULL) {
status_t status;
for (;;) {
do {
status = acquire_sem_etc(fAlertSem, 1, B_RELATIVE_TIMEOUT,
kSemTimeOut);
} while (status == B_INTERRUPTED);
if (status == B_BAD_SEM_ID) {
break;
}
window->UpdateIfNeeded();
}
} else {
while (acquire_sem(fAlertSem) == B_INTERRUPTED) {
}
}
int32 value = fAlertValue;
if (Lock())
Quit();
return value;
}
status_t
BAlert::Go(BInvoker* invoker)
{
fInvoker = invoker;
_Prepare();
Show();
return B_OK;
}
void
BAlert::MessageReceived(BMessage* msg)
{
if (msg->what != kAlertButtonMsg)
return BWindow::MessageReceived(msg);
int32 which;
if (msg->FindInt32("which", &which) == B_OK) {
if (fAlertSem < 0) {
if (fInvoker != NULL) {
BMessage* out = fInvoker->Message();
if (out && (out->ReplaceInt32("which", which) == B_OK
|| out->AddInt32("which", which) == B_OK))
fInvoker->Invoke();
}
PostMessage(B_QUIT_REQUESTED);
} else {
fAlertValue = which;
delete_sem(fAlertSem);
fAlertSem = -1;
}
}
}
void
BAlert::FrameResized(float newWidth, float newHeight)
{
BWindow::FrameResized(newWidth, newHeight);
}
void
BAlert::AddButton(const char* label, char key)
{
if (label == NULL || label[0] == '\0')
return;
BButton* button = _CreateButton(fButtons.size(), label);
fButtons.push_back(button);
fKeys.push_back(key);
SetDefaultButton(button);
fButtonLayout->AddView(button);
}
int32
BAlert::CountButtons() const
{
return (int32)fButtons.size();
}
BButton*
BAlert::ButtonAt(int32 index) const
{
if (index >= 0 && (size_t)index < fButtons.size())
return fButtons[index];
return NULL;
}
BTextView*
BAlert::TextView() const
{
return fTextView;
}
BHandler*
BAlert::ResolveSpecifier(BMessage* msg, int32 index,
BMessage* specifier, int32 form, const char* property)
{
return BWindow::ResolveSpecifier(msg, index, specifier, form, property);
}
status_t
BAlert::GetSupportedSuites(BMessage* data)
{
return BWindow::GetSupportedSuites(data);
}
void
BAlert::DispatchMessage(BMessage* msg, BHandler* handler)
{
BWindow::DispatchMessage(msg, handler);
}
void
BAlert::Quit()
{
BWindow::Quit();
}
bool
BAlert::QuitRequested()
{
return BWindow::QuitRequested();
}
BPoint
BAlert::AlertPosition(float width, float height)
{
BPoint result(100, 100);
BWindow* window =
dynamic_cast<BWindow*>(BLooper::LooperForThread(find_thread(NULL)));
BScreen screen(window);
BRect screenFrame(0, 0, 640, 480);
if (screen.IsValid())
screenFrame = screen.Frame();
result.x = screenFrame.left + (screenFrame.Width() / 2.0) - (width / 2.0);
result.y = screenFrame.top + (screenFrame.Height() / 4.0) - ceil(height / 3.0);
return result;
}
status_t
BAlert::Perform(perform_code code, void* _data)
{
switch (code) {
case PERFORM_CODE_SET_LAYOUT:
perform_data_set_layout* data = (perform_data_set_layout*)_data;
BAlert::SetLayout(data->layout);
return B_OK;
}
return BWindow::Perform(code, _data);
}
void BAlert::_ReservedAlert1() {}
void BAlert::_ReservedAlert2() {}
void BAlert::_ReservedAlert3() {}
void
BAlert::_Init(const char* text, const char* button1, const char* button2,
const char* button3, button_width buttonWidth, button_spacing spacing,
alert_type type)
{
fInvoker = NULL;
fAlertSem = -1;
fAlertValue = -1;
fIconView = new TAlertView();
fTextView = new BTextView("_tv_");
fTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
fTextView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor);
fTextView->MakeEditable(false);
fTextView->MakeSelectable(false);
fTextView->SetWordWrap(true);
fTextView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
fButtonLayout = new BGroupLayout(B_HORIZONTAL, B_USE_HALF_ITEM_SPACING);
SetType(type);
SetButtonWidth(buttonWidth);
SetButtonSpacing(spacing);
SetText(text);
BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0)
.Add(fIconView)
.AddGroup(B_VERTICAL, B_USE_HALF_ITEM_SPACING)
.SetInsets(B_USE_HALF_ITEM_INSETS)
.Add(fTextView)
.AddGroup(B_HORIZONTAL, 0)
.AddGlue()
.Add(fButtonLayout);
AddButton(button1);
AddButton(button2);
AddButton(button3);
AddCommonFilter(new(std::nothrow) _BAlertFilter_(this));
}
BBitmap*
BAlert::_CreateTypeIcon()
{
if (Type() == B_EMPTY_ALERT)
return NULL;
BBitmap* icon = NULL;
const char* iconName;
switch (fType) {
case B_INFO_ALERT:
iconName = "dialog-information";
break;
case B_IDEA_ALERT:
iconName = "dialog-idea";
break;
case B_WARNING_ALERT:
iconName = "dialog-warning";
break;
case B_STOP_ALERT:
iconName = "dialog-error";
break;
default:
return NULL;
}
icon = new(std::nothrow) BBitmap(BRect(BPoint(0, 0), be_control_look->ComposeIconSize(32)),
0, B_RGBA32);
if (icon == NULL || icon->InitCheck() < B_OK) {
FTRACE((stderr, "BAlert::_CreateTypeIcon() - No memory for bitmap\n"));
delete icon;
return NULL;
}
BIconUtils::GetSystemIcon(iconName, icon);
return icon;
}
BButton*
BAlert::_CreateButton(int32 which, const char* label)
{
BMessage* message = new BMessage(kAlertButtonMsg);
if (message == NULL)
return NULL;
message->AddInt32("which", which);
char name[32];
snprintf(name, sizeof(name), "_b%" B_PRId32 "_", which);
return new(std::nothrow) BButton(name, label, message);
}
*/
void
BAlert::_Prepare()
{
if (CountButtons() == 0)
debugger("BAlerts must have at least one button.");
float fontFactor = be_plain_font->Size() / 11.0f;
if (fIconView->Bitmap() == NULL)
fIconView->SetBitmap(_CreateTypeIcon());
if (fButtonWidth == B_WIDTH_AS_USUAL) {
float usualWidth = kButtonUsualWidth * fontFactor;
for (int32 index = 0; index < CountButtons(); index++) {
BButton* button = ButtonAt(index);
if (button->MinSize().width < usualWidth)
button->SetExplicitSize(BSize(usualWidth, B_SIZE_UNSET));
}
} else if (fButtonWidth == B_WIDTH_FROM_WIDEST) {
float maxWidth = 0;
for (int32 index = 0; index < CountButtons(); index++) {
BButton* button = ButtonAt(index);
float width;
button->GetPreferredSize(&width, NULL);
if (width > maxWidth)
maxWidth = width;
}
for (int32 index = 0; index < CountButtons(); index++) {
BButton* button = ButtonAt(index);
button->SetExplicitSize(BSize(maxWidth, B_SIZE_UNSET));
}
}
if (fButtonSpacing == B_OFFSET_SPACING && CountButtons() > 1) {
fButtonLayout->AddItem(1, BSpaceLayoutItem::CreateHorizontalStrut(
kButtonOffsetSpacing * fontFactor));
}
float minWindowWidth = (fButtonSpacing == B_OFFSET_SPACING
? kWindowOffsetMinWidth : kWindowMinWidth) * fontFactor;
GetLayout()->SetExplicitMinSize(BSize(minWindowWidth, B_SIZE_UNSET));
ResizeToPreferred();
if (Frame().left != 0 && Frame().right != 0)
return;
BWindow* parent = dynamic_cast<BWindow*>(BLooper::LooperForThread(
find_thread(NULL)));
const BRect frame = parent != NULL ? parent->Frame()
: BScreen(this).Frame();
MoveTo(static_cast<BWindow*>(this)->AlertPosition(frame));
}
TAlertView::TAlertView()
:
BView("TAlertView", B_WILL_DRAW),
fIconBitmap(NULL)
{
SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
}
TAlertView::TAlertView(BMessage* archive)
:
BView(archive),
fIconBitmap(NULL)
{
}
TAlertView::~TAlertView()
{
delete fIconBitmap;
}
TAlertView*
TAlertView::Instantiate(BMessage* archive)
{
if (!validate_instantiation(archive, "TAlertView"))
return NULL;
return new(std::nothrow) TAlertView(archive);
}
status_t
TAlertView::Archive(BMessage* archive, bool deep) const
{
return BView::Archive(archive, deep);
}
void
TAlertView::SetBitmap(BBitmap* icon)
{
if (icon == NULL && fIconBitmap == NULL)
return;
ASSERT(icon != fIconBitmap);
BBitmap* oldBitmap = fIconBitmap;
fIconBitmap = icon;
Invalidate();
if (oldBitmap == NULL || icon == NULL || oldBitmap->Bounds() != icon->Bounds())
InvalidateLayout();
delete oldBitmap;
}
void
TAlertView::GetPreferredSize(float* _width, float* _height)
{
if (_width != NULL) {
*_width = be_control_look->DefaultLabelSpacing() * 3;
if (fIconBitmap != NULL)
*_width += fIconBitmap->Bounds().Width();
else
*_width += be_control_look->ComposeIconSize(B_LARGE_ICON).Width();
}
if (_height != NULL) {
*_height = be_control_look->DefaultLabelSpacing();
if (fIconBitmap != NULL)
*_height += fIconBitmap->Bounds().Height();
else
*_height += be_control_look->ComposeIconSize(B_LARGE_ICON).Height();
}
}
BSize
TAlertView::MaxSize()
{
return BSize(MinSize().width, B_SIZE_UNLIMITED);
}
void
TAlertView::Draw(BRect updateRect)
{
BRect stripeRect = Bounds();
stripeRect.right = kIconStripeWidthFactor * be_control_look->DefaultLabelSpacing();
SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
FillRect(stripeRect);
if (fIconBitmap == NULL)
return;
SetDrawingMode(B_OP_ALPHA);
SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
DrawBitmapAsync(fIconBitmap, BPoint(be_control_look->DefaultLabelSpacing() * 3,
be_control_look->DefaultLabelSpacing()));
}
_BAlertFilter_::_BAlertFilter_(BAlert* alert)
: BMessageFilter(B_KEY_DOWN),
fAlert(alert)
{
}
_BAlertFilter_::~_BAlertFilter_()
{
}
filter_result
_BAlertFilter_::Filter(BMessage* msg, BHandler** target)
{
if (msg->what == B_KEY_DOWN) {
char byte;
if (msg->FindInt8("byte", (int8*)&byte) == B_OK) {
for (int i = 0; i < fAlert->CountButtons(); ++i) {
if (byte == fAlert->Shortcut(i) && fAlert->ButtonAt(i)) {
char space = ' ';
fAlert->ButtonAt(i)->KeyDown(&space, 1);
return B_SKIP_MESSAGE;
}
}
}
}
return B_DISPATCH_MESSAGE;
}