* Copyright 2006-2013, Haiku, Inc. All rights reserved.
* Copyright 1997, 1998 R3 Software Ltd. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus, superstippi@gmx.de
* Philippe Saint-Pierre, stpere@gmail.com
* John Scipione, jscipione@gmail.com
* Timothy Wayper, timmy@wunderbear.com
*/
#include "CalcView.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <AboutWindow.h>
#include <Alert.h>
#include <Application.h>
#include <AppFileInfo.h>
#include <AutoLocker.h>
#include <Beep.h>
#include <Bitmap.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Clipboard.h>
#include <File.h>
#include <Font.h>
#include <Locale.h>
#include <MenuItem.h>
#include <Message.h>
#include <MessageRunner.h>
#include <NumberFormat.h>
#include <Point.h>
#include <PopUpMenu.h>
#include <Region.h>
#include <Roster.h>
#include <ExpressionParser.h>
#include "CalcApplication.h"
#include "CalcOptions.h"
#include "ExpressionTextView.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "CalcView"
static const int32 kMsgCalculating = 'calc';
static const int32 kMsgAnimateDots = 'dots';
static const int32 kMsgDoneEvaluating = 'done';
const float kFontScaleY = 0.4f;
const float kFontScaleX = 0.4f;
const float kExpressionFontScaleY = 0.6f;
const float kDisplayScaleY = 0.2f;
static const bigtime_t kFlashOnOffInterval = 100000;
static const bigtime_t kCalculatingInterval = 1000000;
static const bigtime_t kAnimationInterval = 333333;
static const float kMinimumWidthCompact = 130.0f;
static const float kMaximumWidthCompact = 400.0f;
static const float kMinimumHeightCompact = 20.0f;
static const float kMaximumHeightCompact = 60.0f;
static const float kMinimumWidthScientific = 240.0f;
static const float kMaximumWidthScientific = 400.0f;
static const float kMinimumHeightScientific = 200.0f;
static const float kMaximumHeightScientific = 400.0f;
const char *kKeypadDescriptionBasic[] = {
B_TRANSLATE_MARK("7"),
B_TRANSLATE_MARK("8"),
B_TRANSLATE_MARK("9"),
B_TRANSLATE_MARK("("),
B_TRANSLATE_MARK(")"),
"\n",
B_TRANSLATE_MARK("4"),
B_TRANSLATE_MARK("5"),
B_TRANSLATE_MARK("6"),
B_TRANSLATE_MARK("*"),
B_TRANSLATE_MARK("/"),
"\n",
B_TRANSLATE_MARK("1"),
B_TRANSLATE_MARK("2"),
B_TRANSLATE_MARK("3"),
B_TRANSLATE_MARK("+"),
B_TRANSLATE_MARK("-"),
"\n",
B_TRANSLATE_MARK("0"),
B_TRANSLATE_MARK("."),
B_TRANSLATE_MARK("BS"),
B_TRANSLATE_MARK("="),
B_TRANSLATE_MARK("C"),
"\n",
NULL
};
const char *kKeypadDescriptionScientific[] = {
B_TRANSLATE_MARK("ln"),
B_TRANSLATE_MARK("sin"),
B_TRANSLATE_MARK("cos"),
B_TRANSLATE_MARK("tan"),
B_TRANSLATE_MARK("π"),
"\n",
B_TRANSLATE_MARK("log"),
B_TRANSLATE_MARK("asin"),
B_TRANSLATE_MARK("acos"),
B_TRANSLATE_MARK("atan"),
B_TRANSLATE_MARK("sqrt"),
"\n",
B_TRANSLATE_MARK("exp"),
B_TRANSLATE_MARK("sinh"),
B_TRANSLATE_MARK("cosh"),
B_TRANSLATE_MARK("tanh"),
B_TRANSLATE_MARK("cbrt"),
"\n",
B_TRANSLATE_MARK("!"),
B_TRANSLATE_MARK("ceil"),
B_TRANSLATE_MARK("floor"),
B_TRANSLATE_MARK("E"),
B_TRANSLATE_MARK("^"),
"\n",
B_TRANSLATE_MARK("7"),
B_TRANSLATE_MARK("8"),
B_TRANSLATE_MARK("9"),
B_TRANSLATE_MARK("("),
B_TRANSLATE_MARK(")"),
"\n",
B_TRANSLATE_MARK("4"),
B_TRANSLATE_MARK("5"),
B_TRANSLATE_MARK("6"),
B_TRANSLATE_MARK("*"),
B_TRANSLATE_MARK("/"),
"\n",
B_TRANSLATE_MARK("1"),
B_TRANSLATE_MARK("2"),
B_TRANSLATE_MARK("3"),
B_TRANSLATE_MARK("+"),
B_TRANSLATE_MARK("-"),
"\n",
B_TRANSLATE_MARK("0"),
B_TRANSLATE_MARK("."),
B_TRANSLATE_MARK("BS"),
B_TRANSLATE_MARK("="),
B_TRANSLATE_MARK("C"),
"\n",
NULL
};
enum {
FLAGS_FLASH_KEY = 1 << 0,
FLAGS_MOUSE_DOWN = 1 << 1
};
struct CalcView::CalcKey {
char label[8];
char code[8];
char keymap[4];
uint32 flags;
};
typedef AutoLocker<BClipboard> ClipboardLocker;
CalcView*
CalcView::Instantiate(BMessage* archive)
{
if (!validate_instantiation(archive, "CalcView"))
return NULL;
return new CalcView(archive);
}
CalcView::CalcView(BRect frame, rgb_color rgbBaseColor, BMessage* settings)
:
BView(frame, "DeskCalc", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS),
fColumns(5),
fRows(4),
fBaseColor(rgbBaseColor),
fHasCustomBaseColor(rgbBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR)),
fWidth(1),
fHeight(1),
fKeypadDescription(kKeypadDescriptionBasic),
fKeypad(NULL),
fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)),
fPopUpMenu(NULL),
fAutoNumlockItem(NULL),
fOptions(new CalcOptions()),
fEvaluateThread(-1),
fEvaluateMessageRunner(NULL),
fEvaluateSemaphore(B_BAD_SEM_ID),
fEnabled(true)
{
SetViewColor(B_TRANSPARENT_32_BIT);
_Init(settings);
}
CalcView::CalcView(BMessage* archive)
:
BView(archive),
fColumns(5),
fRows(4),
fBaseColor(ui_color(B_PANEL_BACKGROUND_COLOR)),
fHasCustomBaseColor(false),
fWidth(1),
fHeight(1),
fKeypadDescription(kKeypadDescriptionBasic),
fKeypad(NULL),
fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)),
fPopUpMenu(NULL),
fAutoNumlockItem(NULL),
fOptions(new CalcOptions()),
fEvaluateThread(-1),
fEvaluateMessageRunner(NULL),
fEvaluateSemaphore(B_BAD_SEM_ID),
fEnabled(true)
{
SetResizingMode(B_FOLLOW_NONE);
_Init(archive);
}
CalcView::~CalcView()
{
delete[] fKeypad;
delete fOptions;
delete fEvaluateMessageRunner;
delete_sem(fEvaluateSemaphore);
}
void
CalcView::AttachedToWindow()
{
if (be_control_look == NULL)
SetFont(be_bold_font);
BRect frame(Frame());
FrameResized(frame.Width(), frame.Height());
bool addKeypadModeMenuItems = true;
if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) {
addKeypadModeMenuItems = false;
}
_CreatePopUpMenu(addKeypadModeMenuItems);
if (addKeypadModeMenuItems)
SetKeypadMode(fOptions->keypad_mode);
}
void
CalcView::MessageReceived(BMessage* message)
{
if (message->what == B_COLORS_UPDATED) {
const char* panelBgColorName = ui_color_name(B_PANEL_BACKGROUND_COLOR);
if (message->HasColor(panelBgColorName) && !fHasCustomBaseColor) {
fBaseColor = message->GetColor(panelBgColorName, fBaseColor);
_Colorize();
}
if (message->HasColor(ui_color_name(B_PANEL_TEXT_COLOR)))
_Colorize();
return;
}
if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) {
switch (message->what) {
case MSG_OPTIONS_AUTO_NUM_LOCK:
ToggleAutoNumlock();
return;
case MSG_OPTIONS_ANGLE_MODE_RADIAN:
SetDegreeMode(false);
return;
case MSG_OPTIONS_ANGLE_MODE_DEGREE:
SetDegreeMode(true);
return;
}
}
if (message->WasDropped()) {
if (message->IsSourceRemote())
Paste(message);
} else {
switch (message->what) {
case B_CUT:
Cut();
break;
case B_COPY:
Copy();
break;
case B_PASTE:
{
ClipboardLocker locker(be_clipboard);
if (locker.IsLocked()) {
BMessage* clipper = be_clipboard->Data();
if (clipper)
Paste(clipper);
}
break;
}
case B_ABOUT_REQUESTED:
{
BAboutWindow* window = new BAboutWindow(kAppName, kSignature);
const char* extraCopyrights[] = {
"1997, 1998 R3 Software Ltd.",
NULL
};
const char* authors[] = {
"Stephan Aßmus",
"John Scipione",
"Timothy Wayper",
"Ingo Weinhold",
NULL
};
window->AddCopyright(2006, "Haiku, Inc.", extraCopyrights);
window->AddAuthors(authors);
window->Show();
break;
}
case MSG_UNFLASH_KEY:
{
int32 key;
if (message->FindInt32("key", &key) == B_OK)
_FlashKey(key, 0);
break;
}
case kMsgAnimateDots:
{
int32 end = fExpressionTextView->TextLength();
int32 start = end - 3;
if (fEnabled || strcmp(fExpressionTextView->Text() + start,
"...") != 0) {
delete fEvaluateMessageRunner;
fEvaluateMessageRunner = NULL;
break;
}
uint8 dot = 0;
if (message->FindUInt8("dot", &dot) == B_OK) {
rgb_color fontColor = fExpressionTextView->HighColor();
rgb_color backColor = fExpressionTextView->LowColor();
fExpressionTextView->SetStylable(true);
fExpressionTextView->SetFontAndColor(start, end, NULL, 0,
&backColor);
fExpressionTextView->SetFontAndColor(start + dot - 1,
start + dot, NULL, 0, &fontColor);
fExpressionTextView->SetStylable(false);
}
dot++;
if (dot == 4)
dot = 1;
delete fEvaluateMessageRunner;
BMessage animate(kMsgAnimateDots);
animate.AddUInt8("dot", dot);
fEvaluateMessageRunner = new (std::nothrow) BMessageRunner(
BMessenger(this), &animate, kAnimationInterval, 1);
break;
}
case kMsgCalculating:
{
if (fEnabled) {
delete fEvaluateMessageRunner;
fEvaluateMessageRunner = NULL;
break;
}
BString calculating;
calculating << B_TRANSLATE("Calculating") << "...";
fExpressionTextView->SetText(calculating.String());
delete fEvaluateMessageRunner;
BMessage animate(kMsgAnimateDots);
animate.AddUInt8("dot", 1U);
fEvaluateMessageRunner = new (std::nothrow) BMessageRunner(
BMessenger(this), &animate, kAnimationInterval, 1);
break;
}
case kMsgDoneEvaluating:
{
_SetEnabled(true);
rgb_color fontColor = fExpressionTextView->HighColor();
fExpressionTextView->SetFontAndColor(NULL, 0, &fontColor);
const char* result;
if (message->FindString("error", &result) == B_OK)
fExpressionTextView->SetText(result);
else if (message->FindString("value", &result) == B_OK) {
BLocale locale;
BNumberFormat format(&locale);
fExpressionTextView->SetValue(result, format.GetSeparator(B_DECIMAL_SEPARATOR));
}
delete fEvaluateMessageRunner;
fEvaluateMessageRunner = NULL;
break;
}
default:
BView::MessageReceived(message);
break;
}
}
}
void
CalcView::Draw(BRect updateRect)
{
bool drawBackground = !_IsEmbedded();
SetHighColor(fBaseColor);
BRect expressionRect(_ExpressionRect());
if (updateRect.Intersects(expressionRect)) {
if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT
&& expressionRect.Height() >= fCalcIcon->Bounds().Height()) {
expressionRect.left = fExpressionTextView->Frame().right + 2;
if (drawBackground) {
SetHighColor(fBaseColor);
FillRect(updateRect & expressionRect);
}
SetDrawingMode(B_OP_ALPHA);
SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
BPoint iconPos;
iconPos.x = expressionRect.right - (expressionRect.Width()
+ fCalcIcon->Bounds().Width()) / 2.0;
iconPos.y = expressionRect.top + (expressionRect.Height()
- fCalcIcon->Bounds().Height()) / 2.0;
DrawBitmap(fCalcIcon, iconPos);
SetDrawingMode(B_OP_COPY);
}
expressionRect = fExpressionTextView->Frame();
expressionRect.InsetBy(-2, -2);
if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT && drawBackground) {
expressionRect.InsetBy(-2, -2);
StrokeRect(expressionRect);
expressionRect.InsetBy(1, 1);
StrokeRect(expressionRect);
expressionRect.InsetBy(1, 1);
}
uint32 flags = 0;
if (!drawBackground)
flags |= BControlLook::B_BLEND_FRAME;
be_control_look->DrawTextControlBorder(this, expressionRect,
updateRect, fBaseColor, flags);
}
if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
return;
BRect keypadRect(_KeypadRect());
if (be_control_look != NULL) {
if (drawBackground)
StrokeRect(keypadRect);
keypadRect.InsetBy(1, 1);
}
float sizeDisp = keypadRect.top;
float sizeCol = (keypadRect.Width() + 1) / (float)fColumns;
float sizeRow = (keypadRect.Height() + 1) / (float)fRows;
if (!updateRect.Intersects(keypadRect))
return;
SetFontSize(min_c(sizeRow * kFontScaleY, sizeCol * kFontScaleX));
CalcKey* key = fKeypad;
for (int row = 0; row < fRows; row++) {
for (int col = 0; col < fColumns; col++) {
BRect frame;
frame.left = keypadRect.left + col * sizeCol;
frame.right = keypadRect.left + (col + 1) * sizeCol - 1;
frame.top = sizeDisp + row * sizeRow;
frame.bottom = sizeDisp + (row + 1) * sizeRow - 1;
if (drawBackground) {
SetHighColor(fBaseColor);
StrokeRect(frame);
}
frame.InsetBy(1, 1);
uint32 flags = 0;
if (!drawBackground)
flags |= BControlLook::B_BLEND_FRAME;
if (key->flags != 0)
flags |= BControlLook::B_ACTIVATED;
flags |= BControlLook::B_IGNORE_OUTLINE;
be_control_look->DrawButtonFrame(this, frame, updateRect,
fBaseColor, fBaseColor, flags);
be_control_look->DrawButtonBackground(this, frame, updateRect,
fBaseColor, flags);
be_control_look->DrawLabel(this, key->label, frame, updateRect,
fBaseColor, flags, BAlignment(B_ALIGN_HORIZONTAL_CENTER,
B_ALIGN_VERTICAL_CENTER), &fButtonTextColor);
key++;
}
}
}
void
CalcView::MouseDown(BPoint point)
{
if (!fExpressionTextView->IsFocus()) {
MakeFocus();
}
int32 buttons = 0;
Window()->CurrentMessage()->FindInt32("buttons", &buttons);
if ((B_PRIMARY_MOUSE_BUTTON & buttons) == 0) {
BMenuItem* selected;
if ((selected = fPopUpMenu->Go(ConvertToScreen(point))) != NULL
&& selected->Message() != NULL) {
Window()->PostMessage(selected->Message(), this);
}
return;
}
if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) {
if (fCalcIcon != NULL) {
BRect bounds(Bounds());
bounds.left = bounds.right - fCalcIcon->Bounds().Width();
if (bounds.Contains(point)) {
fExpressionTextView->Clear();
}
}
return;
}
float sizeDisp = fHeight * kDisplayScaleY;
float sizeCol = fWidth / (float)fColumns;
float sizeRow = (fHeight - sizeDisp) / (float)fRows;
int gridCol = (int)floorf(point.x / sizeCol);
int gridRow = (int)floorf((point.y - sizeDisp) / sizeRow);
if ((gridCol >= 0) && (gridCol < fColumns)
&& (gridRow >= 0) && (gridRow < fRows)) {
int key = gridRow * fColumns + gridCol;
_FlashKey(key, FLAGS_MOUSE_DOWN);
_PressKey(key);
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
}
}
void
CalcView::MouseUp(BPoint point)
{
if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
return;
int keys = fRows * fColumns;
for (int i = 0; i < keys; i++) {
if (fKeypad[i].flags & FLAGS_MOUSE_DOWN) {
_FlashKey(i, 0);
break;
}
}
}
void
CalcView::KeyDown(const char* bytes, int32 numBytes)
{
if (numBytes == 1) {
switch (bytes[0]) {
case B_ENTER:
_PressKey("=");
break;
case B_LEFT_ARROW:
case B_BACKSPACE:
_PressKey("BS");
break;
case B_SPACE:
case B_ESCAPE:
case 'c':
_PressKey("C");
break;
case '[':
case '{':
_PressKey("(");
break;
case ']':
case '}':
_PressKey(")");
break;
default: {
int keys = fRows * fColumns;
for (int i = 0; i < keys; i++) {
if (fKeypad[i].keymap[0] == bytes[0]) {
_PressKey(i);
return;
}
}
break;
}
}
}
}
void
CalcView::MakeFocus(bool focused)
{
if (focused) {
if (fOptions->auto_num_lock) {
set_keyboard_locks(B_NUM_LOCK
| (modifiers() & (B_CAPS_LOCK | B_SCROLL_LOCK)));
}
}
fExpressionTextView->MakeFocus(focused);
}
void
CalcView::FrameResized(float width, float height)
{
fWidth = width;
fHeight = height;
BRect expressionRect = _ExpressionRect();
if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) {
expressionRect.InsetBy(2, 2);
expressionRect.right -= ceilf(fCalcIcon->Bounds().Width() * 1.5);
} else
expressionRect.InsetBy(4, 4);
fExpressionTextView->MoveTo(expressionRect.LeftTop());
fExpressionTextView->ResizeTo(expressionRect.Width(), expressionRect.Height());
float sizeDisp = fOptions->keypad_mode == KEYPAD_MODE_COMPACT
? fHeight : fHeight * kDisplayScaleY;
BFont font(be_bold_font);
font.SetSize(sizeDisp * kExpressionFontScaleY);
rgb_color fontColor = fExpressionTextView->HighColor();
fExpressionTextView->SetFontAndColor(&font, B_FONT_ALL, &fontColor);
expressionRect.OffsetTo(B_ORIGIN);
fExpressionTextView->SetTextRect(expressionRect);
Invalidate();
}
status_t
CalcView::Archive(BMessage* archive, bool deep) const
{
fExpressionTextView->RemoveSelf();
status_t ret = BView::Archive(archive, deep);
const_cast<CalcView*>(this)->AddChild(fExpressionTextView);
if (ret == B_OK)
ret = archive->AddString("add_on", kSignature);
if (ret == B_OK)
ret = SaveSettings(archive);
if (ret == B_OK)
ret = archive->AddString("class", "CalcView");
return ret;
}
void
CalcView::Cut()
{
Copy();
fExpressionTextView->Clear();
}
void
CalcView::Copy()
{
ClipboardLocker locker(be_clipboard);
if (!locker.IsLocked())
return;
if (be_clipboard->Clear() != B_OK)
return;
BMessage* clipper = be_clipboard->Data();
if (clipper == NULL)
return;
BString expression = fExpressionTextView->Text();
if (clipper->AddData("text/plain", B_MIME_TYPE,
expression.String(), expression.Length()) == B_OK) {
clipper->what = B_MIME_DATA;
be_clipboard->Commit();
}
}
void
CalcView::Paste(BMessage* message)
{
int32 count;
if (message->GetInfo("refs", NULL, &count) == B_OK) {
entry_ref ref;
ssize_t read;
BFile file;
char buffer[256];
memset(buffer, 0, sizeof(buffer));
for (int32 i = 0; i < count; i++) {
if (message->FindRef("refs", i, &ref) == B_OK) {
if (file.SetTo(&ref, B_READ_ONLY) == B_OK) {
read = file.Read(buffer, sizeof(buffer) - 1);
if (read <= 0)
continue;
BString expression(buffer);
int32 j = expression.Length();
while (j > 0 && expression[j - 1] == '\n')
j--;
expression.Truncate(j);
if (expression.Length() > 0)
fExpressionTextView->Insert(expression.String());
}
}
}
return;
}
const rgb_color* dropColor = NULL;
ssize_t dataSize;
if (message->FindData("RGBColor", B_RGB_COLOR_TYPE,
(const void**)&dropColor, &dataSize) == B_OK
&& dataSize == sizeof(rgb_color)) {
BPoint dropPoint = ConvertFromScreen(message->DropPoint());
float sizeDisp = fHeight * kDisplayScaleY;
BRect keypadRect(0.0, sizeDisp, fWidth, fHeight);
if (keypadRect.Contains(dropPoint) && dropColor != NULL) {
fBaseColor = *dropColor;
fHasCustomBaseColor =
fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR);
_Colorize();
Invalidate();
}
} else {
const char* text;
ssize_t numBytes;
if (message->FindData("text/plain", B_MIME_TYPE,
(const void**)&text, &numBytes) == B_OK) {
BString temp;
temp.Append(text, numBytes);
fExpressionTextView->Insert(temp.String());
}
}
}
status_t
CalcView::SaveSettings(BMessage* archive) const
{
status_t ret = archive ? B_OK : B_BAD_VALUE;
if (ret == B_OK)
ret = archive->AddInt16("cols", fColumns);
if (ret == B_OK)
ret = archive->AddInt16("rows", fRows);
if (ret == B_OK && fHasCustomBaseColor) {
ret = archive->AddData("rgbBaseColor", B_RGB_COLOR_TYPE,
&fBaseColor, sizeof(rgb_color));
}
if (ret == B_OK)
ret = fOptions->SaveSettings(archive);
if (ret == B_OK)
ret = archive->AddString("displayText", fExpressionTextView->Text());
if (ret == B_OK)
ret = fExpressionTextView->SaveSettings(archive);
if (ret == B_OK)
ret = archive->AddString("calcDesc",
fKeypadDescription == kKeypadDescriptionBasic
? "basic" : "scientific");
return ret;
}
void
CalcView::Evaluate()
{
if (fExpressionTextView->TextLength() == 0) {
beep();
return;
}
fEvaluateThread = spawn_thread(_EvaluateThread, "Evaluate Thread",
B_LOW_PRIORITY, this);
if (fEvaluateThread < B_OK) {
fExpressionTextView->SetText(strerror(fEvaluateThread));
return;
}
_SetEnabled(false);
status_t threadStatus = resume_thread(fEvaluateThread);
if (threadStatus != B_OK) {
fExpressionTextView->SetText(strerror(threadStatus));
_SetEnabled(true);
return;
}
if (fEvaluateMessageRunner == NULL) {
BMessage message(kMsgCalculating);
fEvaluateMessageRunner = new (std::nothrow) BMessageRunner(
BMessenger(this), &message, kCalculatingInterval, 1);
status_t runnerStatus = fEvaluateMessageRunner->InitCheck();
if (runnerStatus != B_OK)
printf("Evaluate Message Runner: %s\n", strerror(runnerStatus));
}
}
void
CalcView::FlashKey(const char* bytes, int32 numBytes)
{
BString temp;
temp.Append(bytes, numBytes);
int32 key = _KeyForLabel(temp.String());
if (key >= 0)
_FlashKey(key, FLAGS_FLASH_KEY);
}
void
CalcView::ToggleAutoNumlock(void)
{
fOptions->auto_num_lock = !fOptions->auto_num_lock;
fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
}
void
CalcView::SetDegreeMode(bool degrees)
{
fOptions->degree_mode = degrees;
fAngleModeRadianItem->SetMarked(!degrees);
fAngleModeDegreeItem->SetMarked(degrees);
}
void
CalcView::SetKeypadMode(uint8 mode)
{
if (_IsEmbedded())
return;
BWindow* window = Window();
if (window == NULL)
return;
if (fOptions->keypad_mode == mode)
return;
fOptions->keypad_mode = mode;
_MarkKeypadItems(fOptions->keypad_mode);
float width = fWidth;
float height = fHeight;
switch (fOptions->keypad_mode) {
case KEYPAD_MODE_COMPACT:
{
if (window->Bounds() == Frame()) {
window->SetSizeLimits(kMinimumWidthCompact,
kMaximumWidthCompact, kMinimumHeightCompact,
kMaximumHeightCompact);
window->ResizeTo(width, height * kDisplayScaleY);
} else
ResizeTo(width, height * kDisplayScaleY);
break;
}
case KEYPAD_MODE_SCIENTIFIC:
{
fKeypadDescription = kKeypadDescriptionScientific;
fRows = 8;
_ParseCalcDesc(fKeypadDescription);
window->SetSizeLimits(kMinimumWidthScientific,
kMaximumWidthScientific, kMinimumHeightScientific,
kMaximumHeightScientific);
if (width < kMinimumWidthScientific)
width = kMinimumWidthScientific;
else if (width > kMaximumWidthScientific)
width = kMaximumWidthScientific;
if (height < kMinimumHeightScientific)
height = kMinimumHeightScientific;
else if (height > kMaximumHeightScientific)
height = kMaximumHeightScientific;
if (width != fWidth || height != fHeight)
ResizeTo(width, height);
else
Invalidate();
break;
}
case KEYPAD_MODE_BASIC:
default:
{
fKeypadDescription = kKeypadDescriptionBasic;
fRows = 4;
_ParseCalcDesc(fKeypadDescription);
window->SetSizeLimits(kMinimumWidthBasic, kMaximumWidthBasic,
kMinimumHeightBasic, kMaximumHeightBasic);
if (width < kMinimumWidthBasic)
width = kMinimumWidthBasic;
else if (width > kMaximumWidthBasic)
width = kMaximumWidthBasic;
if (height < kMinimumHeightBasic)
height = kMinimumHeightBasic;
else if (height > kMaximumHeightBasic)
height = kMaximumHeightBasic;
if (width != fWidth || height != fHeight)
ResizeTo(width, height);
else
Invalidate();
}
}
}
status_t
CalcView::_EvaluateThread(void* data)
{
CalcView* calcView = reinterpret_cast<CalcView*>(data);
if (calcView == NULL)
return B_BAD_TYPE;
BMessenger messenger(calcView);
if (!messenger.IsValid())
return B_BAD_VALUE;
BString result;
status_t status = acquire_sem(calcView->fEvaluateSemaphore);
if (status == B_OK) {
BLocale locale;
BNumberFormat format(&locale);
ExpressionParser parser;
parser.SetDegreeMode(calcView->fOptions->degree_mode);
parser.SetSeparators(format.GetSeparator(B_DECIMAL_SEPARATOR),
format.GetSeparator(B_GROUPING_SEPARATOR));
BString expression(calcView->fExpressionTextView->Text());
try {
result = parser.Evaluate(expression.String());
} catch (ParseException& e) {
result << e.message.String() << " at " << (e.position + 1);
status = B_ERROR;
}
release_sem(calcView->fEvaluateSemaphore);
} else
result = strerror(status);
BMessage message(kMsgDoneEvaluating);
message.AddString(status == B_OK ? "value" : "error", result.String());
messenger.SendMessage(&message);
return status;
}
void
CalcView::_Init(BMessage* settings)
{
fExpressionTextView = new ExpressionTextView(_ExpressionRect(), this);
AddChild(fExpressionTextView);
_LoadSettings(settings);
_FetchAppIcon(fCalcIcon);
fEvaluateSemaphore = create_sem(1, "Evaluate Semaphore");
}
status_t
CalcView::_LoadSettings(BMessage* archive)
{
if (!archive)
return B_BAD_VALUE;
BString calcDesc;
archive->FindString("calcDesc", &calcDesc);
if (calcDesc == "scientific" || calcDesc.StartsWith("ln"))
fKeypadDescription = kKeypadDescriptionScientific;
else
fKeypadDescription = kKeypadDescriptionBasic;
if (archive->FindInt16("cols", &fColumns) < B_OK)
fColumns = 5;
if (archive->FindInt16("rows", &fRows) < B_OK)
fRows = 4;
const rgb_color* color;
ssize_t size;
if (archive->FindData("rgbBaseColor", B_RGB_COLOR_TYPE,
(const void**)&color, &size) < B_OK
|| size != sizeof(rgb_color)) {
fBaseColor = ui_color(B_PANEL_BACKGROUND_COLOR);
puts("Missing rgbBaseColor from CalcView archive!\n");
} else
fBaseColor = *color;
fHasCustomBaseColor = fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR);
fOptions->LoadSettings(archive);
const char* display;
if (archive->FindString("displayText", &display) < B_OK) {
puts("Missing expression text from CalcView archive.\n");
} else {
fExpressionTextView->SetText(display);
}
fExpressionTextView->LoadSettings(archive);
_ParseCalcDesc(fKeypadDescription);
_Colorize();
return B_OK;
}
void
CalcView::_ParseCalcDesc(const char** keypadDescription)
{
fKeypad = new CalcKey[fRows * fColumns];
CalcKey* key = fKeypad;
for (int i = 0; const char* p = keypadDescription[i]; i++) {
if (strcmp(p, "\n") == 0)
continue;
strlcpy(key->label, B_TRANSLATE_NOCOLLECT(p), sizeof(key->label));
if (strcmp(p, "=") == 0)
strlcpy(key->code, "\n", sizeof(key->code));
else
strlcpy(key->code, p, sizeof(key->code));
if (strlen(key->label) == 1)
strlcpy(key->keymap, key->label, sizeof(key->keymap));
else
*key->keymap = '\0';
key->flags = 0;
fExpressionTextView->AddKeypadLabel(key->label);
key++;
}
}
void
CalcView::_PressKey(int key)
{
if (!fEnabled)
return;
assert(key < (fRows * fColumns));
assert(key >= 0);
if (strcmp(fKeypad[key].code, "BS") == 0) {
fExpressionTextView->BackSpace();
} else if (strcmp(fKeypad[key].code, "C") == 0) {
fExpressionTextView->Clear();
} else if (strcmp(fKeypad[key].code, "acos") == 0
|| strcmp(fKeypad[key].code, "asin") == 0
|| strcmp(fKeypad[key].code, "atan") == 0
|| strcmp(fKeypad[key].code, "cbrt") == 0
|| strcmp(fKeypad[key].code, "ceil") == 0
|| strcmp(fKeypad[key].code, "cos") == 0
|| strcmp(fKeypad[key].code, "cosh") == 0
|| strcmp(fKeypad[key].code, "exp") == 0
|| strcmp(fKeypad[key].code, "floor") == 0
|| strcmp(fKeypad[key].code, "log") == 0
|| strcmp(fKeypad[key].code, "ln") == 0
|| strcmp(fKeypad[key].code, "sin") == 0
|| strcmp(fKeypad[key].code, "sinh") == 0
|| strcmp(fKeypad[key].code, "sqrt") == 0
|| strcmp(fKeypad[key].code, "tan") == 0
|| strcmp(fKeypad[key].code, "tanh") == 0) {
int32 labelLen = strlen(fKeypad[key].code);
int32 startSelection = 0;
int32 endSelection = 0;
fExpressionTextView->GetSelection(&startSelection, &endSelection);
if (endSelection > startSelection) {
fExpressionTextView->Insert(startSelection, fKeypad[key].code,
labelLen);
fExpressionTextView->Insert(startSelection + labelLen, "(", 1);
fExpressionTextView->Insert(endSelection + labelLen + 1, ")", 1);
static_cast<BTextView*>(fExpressionTextView)->Select(
endSelection + labelLen + 2, endSelection + labelLen + 2);
} else {
fExpressionTextView->Insert(fKeypad[key].code);
fExpressionTextView->Insert("()");
static_cast<BTextView*>(fExpressionTextView)->Select(
endSelection + labelLen + 1, endSelection + labelLen + 1);
}
} else if (strcmp(fKeypad[key].code, ".") == 0) {
BLocale locale;
BNumberFormat format(&locale);
fExpressionTextView->Insert(format.GetSeparator(B_DECIMAL_SEPARATOR));
} else {
if (fKeypad[key].code[0] == '\n') {
fExpressionTextView->ApplyChanges();
} else {
fExpressionTextView->Insert(fKeypad[key].code);
}
}
}
void
CalcView::_PressKey(const char* label)
{
int32 key = _KeyForLabel(label);
if (key >= 0)
_PressKey(key);
}
int32
CalcView::_KeyForLabel(const char* label) const
{
int keys = fRows * fColumns;
for (int i = 0; i < keys; i++) {
if (strcmp(fKeypad[i].label, label) == 0) {
return i;
}
}
return -1;
}
void
CalcView::_FlashKey(int32 key, uint32 flashFlags)
{
if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
return;
if (flashFlags != 0)
fKeypad[key].flags |= flashFlags;
else
fKeypad[key].flags = 0;
Invalidate();
if (fKeypad[key].flags == FLAGS_FLASH_KEY) {
BMessage message(MSG_UNFLASH_KEY);
message.AddInt32("key", key);
BMessageRunner::StartSending(BMessenger(this), &message,
kFlashOnOffInterval, 1);
}
}
void
CalcView::_Colorize()
{
rgb_color panelColor = ui_color(B_PANEL_TEXT_COLOR);
if (rgb_color::Contrast(fBaseColor, panelColor) > 100)
fButtonTextColor = panelColor;
else {
if (fBaseColor.IsLight())
fButtonTextColor = (rgb_color){ 0, 0, 0, 255 };
else
fButtonTextColor = (rgb_color){ 255, 255, 255, 255 };
}
}
void
CalcView::_CreatePopUpMenu(bool addKeypadModeMenuItems)
{
fAutoNumlockItem = new BMenuItem(B_TRANSLATE("Enable Num Lock on startup"),
new BMessage(MSG_OPTIONS_AUTO_NUM_LOCK));
fAngleModeRadianItem = new BMenuItem(B_TRANSLATE("Radians"),
new BMessage(MSG_OPTIONS_ANGLE_MODE_RADIAN));
fAngleModeDegreeItem = new BMenuItem(B_TRANSLATE("Degrees"),
new BMessage(MSG_OPTIONS_ANGLE_MODE_DEGREE));
if (addKeypadModeMenuItems) {
fKeypadModeCompactItem = new BMenuItem(B_TRANSLATE("Compact"),
new BMessage(MSG_OPTIONS_KEYPAD_MODE_COMPACT), '0');
fKeypadModeBasicItem = new BMenuItem(B_TRANSLATE("Basic"),
new BMessage(MSG_OPTIONS_KEYPAD_MODE_BASIC), '1');
fKeypadModeScientificItem = new BMenuItem(B_TRANSLATE("Scientific"),
new BMessage(MSG_OPTIONS_KEYPAD_MODE_SCIENTIFIC), '2');
}
fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
fAngleModeRadianItem->SetMarked(!fOptions->degree_mode);
fAngleModeDegreeItem->SetMarked(fOptions->degree_mode);
fPopUpMenu = new BPopUpMenu("pop-up", false, false);
fPopUpMenu->AddItem(fAutoNumlockItem);
fPopUpMenu->AddSeparatorItem();
fPopUpMenu->AddItem(fAngleModeRadianItem);
fPopUpMenu->AddItem(fAngleModeDegreeItem);
if (addKeypadModeMenuItems) {
fPopUpMenu->AddSeparatorItem();
fPopUpMenu->AddItem(fKeypadModeCompactItem);
fPopUpMenu->AddItem(fKeypadModeBasicItem);
fPopUpMenu->AddItem(fKeypadModeScientificItem);
_MarkKeypadItems(fOptions->keypad_mode);
}
}
BRect
CalcView::_ExpressionRect() const
{
BRect r(0.0, 0.0, fWidth, fHeight);
if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) {
r.bottom = floorf(fHeight * kDisplayScaleY) + 1;
}
return r;
}
BRect
CalcView::_KeypadRect() const
{
BRect r(0.0, 0.0, -1.0, -1.0);
if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) {
r.right = fWidth;
r.bottom = fHeight;
r.top = floorf(fHeight * kDisplayScaleY);
}
return r;
}
void
CalcView::_MarkKeypadItems(uint8 keypad_mode)
{
switch (keypad_mode) {
case KEYPAD_MODE_COMPACT:
fKeypadModeCompactItem->SetMarked(true);
fKeypadModeBasicItem->SetMarked(false);
fKeypadModeScientificItem->SetMarked(false);
break;
case KEYPAD_MODE_SCIENTIFIC:
fKeypadModeCompactItem->SetMarked(false);
fKeypadModeBasicItem->SetMarked(false);
fKeypadModeScientificItem->SetMarked(true);
break;
default:
fKeypadModeCompactItem->SetMarked(false);
fKeypadModeBasicItem->SetMarked(true);
fKeypadModeScientificItem->SetMarked(false);
}
}
void
CalcView::_FetchAppIcon(BBitmap* into)
{
entry_ref appRef;
status_t status = be_roster->FindApp(kSignature, &appRef);
if (status == B_OK) {
BFile file(&appRef, B_READ_ONLY);
BAppFileInfo appInfo(&file);
status = appInfo.GetIcon(into, B_MINI_ICON);
}
if (status != B_OK)
memset(into->Bits(), 0, into->BitsLength());
}
bool
CalcView::_IsEmbedded()
{
return Parent() != NULL && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0;
}
void
CalcView::_SetEnabled(bool enable)
{
fEnabled = enable;
fExpressionTextView->MakeSelectable(enable);
fExpressionTextView->MakeEditable(enable);
}