1289 lines
30 KiB
C++
1289 lines
30 KiB
C++
/*
|
|
* 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, [email protected]
|
|
* Philippe Saint-Pierre, [email protected]
|
|
* John Scipione, [email protected]
|
|
* Timothy Wayper, [email protected]
|
|
*/
|
|
|
|
|
|
#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 <Beep.h>
|
|
#include <Bitmap.h>
|
|
#include <Catalog.h>
|
|
#include <ControlLook.h>
|
|
#include <Clipboard.h>
|
|
#include <File.h>
|
|
#include <Font.h>
|
|
#include <MenuItem.h>
|
|
#include <Message.h>
|
|
#include <MessageRunner.h>
|
|
#include <PlaySound.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"
|
|
|
|
|
|
//const uint8 K_COLOR_OFFSET = 32;
|
|
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 float kMinimumWidthCompact = 130.0f;
|
|
static const float kMaximumWidthCompact = 400.0f;
|
|
static const float kMinimumHeightCompact = 20.0f;
|
|
static const float kMaximumHeightCompact = 60.0f;
|
|
|
|
// Basic mode size limits are defined in CalcView.h so
|
|
// that they can be used by the CalcWindow constructor.
|
|
|
|
static const float kMinimumWidthScientific = 240.0f;
|
|
static const float kMaximumWidthScientific = 400.0f;
|
|
static const float kMinimumHeightScientific = 200.0f;
|
|
static const float kMaximumHeightScientific = 400.0f;
|
|
|
|
// basic mode keypad layout (default)
|
|
const char *kKeypadDescriptionBasic =
|
|
"7 8 9 ( ) \n"
|
|
"4 5 6 * / \n"
|
|
"1 2 3 + - \n"
|
|
"0 . BS = C \n";
|
|
|
|
// scientific mode keypad layout
|
|
const char *kKeypadDescriptionScientific =
|
|
"ln sin cos tan π \n"
|
|
"log asin acos atan sqrt \n"
|
|
"exp sinh cosh tanh cbrt \n"
|
|
"! ceil floor E ^ \n"
|
|
"7 8 9 ( ) \n"
|
|
"4 5 6 * / \n"
|
|
"1 2 3 + - \n"
|
|
"0 . BS = C \n";
|
|
|
|
|
|
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;
|
|
// float width;
|
|
};
|
|
|
|
|
|
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_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
|
|
fColumns(5),
|
|
fRows(4),
|
|
|
|
fBaseColor(rgbBaseColor),
|
|
fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }),
|
|
|
|
fWidth(1),
|
|
fHeight(1),
|
|
|
|
fKeypadDescription(strdup(kKeypadDescriptionBasic)),
|
|
fKeypad(NULL),
|
|
|
|
#ifdef __HAIKU__
|
|
fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)),
|
|
#else
|
|
fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_CMAP8)),
|
|
#endif
|
|
|
|
fPopUpMenu(NULL),
|
|
fAutoNumlockItem(NULL),
|
|
fAudioFeedbackItem(NULL),
|
|
fOptions(new CalcOptions())
|
|
{
|
|
// tell the app server not to erase our b/g
|
|
SetViewColor(B_TRANSPARENT_32_BIT);
|
|
|
|
_Init(settings);
|
|
}
|
|
|
|
|
|
CalcView::CalcView(BMessage* archive)
|
|
:
|
|
BView(archive),
|
|
fColumns(5),
|
|
fRows(4),
|
|
|
|
fBaseColor((rgb_color){ 128, 128, 128, 255 }),
|
|
fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }),
|
|
|
|
fWidth(1),
|
|
fHeight(1),
|
|
|
|
fKeypadDescription(strdup(kKeypadDescriptionBasic)),
|
|
fKeypad(NULL),
|
|
|
|
#ifdef __HAIKU__
|
|
fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)),
|
|
#else
|
|
fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_CMAP8)),
|
|
#endif
|
|
|
|
fPopUpMenu(NULL),
|
|
fAutoNumlockItem(NULL),
|
|
fAudioFeedbackItem(NULL),
|
|
fOptions(new CalcOptions())
|
|
{
|
|
// Do not restore the follow mode, in shelfs, we never follow.
|
|
SetResizingMode(B_FOLLOW_NONE);
|
|
|
|
_Init(archive);
|
|
}
|
|
|
|
|
|
CalcView::~CalcView()
|
|
{
|
|
delete fKeypad;
|
|
delete fOptions;
|
|
free(fKeypadDescription);
|
|
}
|
|
|
|
|
|
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) {
|
|
// don't add these items if we are a replicant on the desktop
|
|
addKeypadModeMenuItems = false;
|
|
}
|
|
|
|
// create and attach the pop-up menu
|
|
_CreatePopUpMenu(addKeypadModeMenuItems);
|
|
|
|
if (addKeypadModeMenuItems)
|
|
SetKeypadMode(fOptions->keypad_mode);
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::MessageReceived(BMessage* message)
|
|
{
|
|
if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) {
|
|
// if we are embedded in desktop we need to receive these
|
|
// message here since we don't have a parent BWindow
|
|
switch (message->what) {
|
|
case MSG_OPTIONS_AUTO_NUM_LOCK:
|
|
ToggleAutoNumlock();
|
|
return;
|
|
|
|
case MSG_OPTIONS_AUDIO_FEEDBACK:
|
|
ToggleAudioFeedback();
|
|
return;
|
|
|
|
case MSG_OPTIONS_ANGLE_MODE_RADIAN:
|
|
SetDegreeMode(false);
|
|
return;
|
|
|
|
case MSG_OPTIONS_ANGLE_MODE_DEGREE:
|
|
SetDegreeMode(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check if message was dropped
|
|
if (message->WasDropped()) {
|
|
// pass message on to paste
|
|
if (message->IsSourceRemote())
|
|
Paste(message);
|
|
} else {
|
|
// act on posted message type
|
|
switch (message->what) {
|
|
|
|
// handle "cut"
|
|
case B_CUT:
|
|
Cut();
|
|
break;
|
|
|
|
// handle copy
|
|
case B_COPY:
|
|
Copy();
|
|
break;
|
|
|
|
// handle paste
|
|
case B_PASTE:
|
|
// access system clipboard
|
|
if (be_clipboard->Lock()) {
|
|
BMessage* clipper = be_clipboard->Data();
|
|
//clipper->PrintToStream();
|
|
Paste(clipper);
|
|
be_clipboard->Unlock();
|
|
}
|
|
break;
|
|
|
|
// (replicant) about box requested
|
|
case B_ABOUT_REQUESTED:
|
|
{
|
|
BAboutWindow* window = new BAboutWindow(kAppName, kSignature);
|
|
|
|
// create the about window
|
|
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;
|
|
}
|
|
|
|
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()) {
|
|
// render calc icon
|
|
expressionRect.left = fExpressionTextView->Frame().right + 2;
|
|
if (drawBackground) {
|
|
SetHighColor(fBaseColor);
|
|
FillRect(updateRect & expressionRect);
|
|
}
|
|
|
|
if (fCalcIcon->ColorSpace() == B_RGBA32) {
|
|
SetDrawingMode(B_OP_ALPHA);
|
|
SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
|
|
} else {
|
|
SetDrawingMode(B_OP_OVER);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// render border around expression text view
|
|
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;
|
|
|
|
// calculate grid sizes
|
|
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));
|
|
|
|
key++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::MouseDown(BPoint point)
|
|
{
|
|
// ensure this view is the current focus
|
|
if (!fExpressionTextView->IsFocus()) {
|
|
// Call our version of MakeFocus(), since that will also apply the
|
|
// num_lock setting.
|
|
MakeFocus();
|
|
}
|
|
|
|
// read mouse buttons state
|
|
int32 buttons = 0;
|
|
Window()->CurrentMessage()->FindInt32("buttons", &buttons);
|
|
|
|
if ((B_PRIMARY_MOUSE_BUTTON & buttons) == 0) {
|
|
// display popup menu if not primary mouse button
|
|
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)) {
|
|
// user clicked on calculator icon
|
|
fExpressionTextView->Clear();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// calculate grid sizes
|
|
float sizeDisp = fHeight * kDisplayScaleY;
|
|
float sizeCol = fWidth / (float)fColumns;
|
|
float sizeRow = (fHeight - sizeDisp) / (float)fRows;
|
|
|
|
// calculate location within grid
|
|
int gridCol = (int)floorf(point.x / sizeCol);
|
|
int gridRow = (int)floorf((point.y - sizeDisp) / sizeRow);
|
|
|
|
// check limits
|
|
if ((gridCol >= 0) && (gridCol < fColumns)
|
|
&& (gridRow >= 0) && (gridRow < fRows)) {
|
|
|
|
// process key press
|
|
int key = gridRow * fColumns + gridCol;
|
|
_FlashKey(key, FLAGS_MOUSE_DOWN);
|
|
_PressKey(key);
|
|
|
|
// make sure we receive the mouse up!
|
|
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 single byte character...
|
|
if (numBytes == 1) {
|
|
|
|
//printf("Key pressed: %c\n", bytes[0]);
|
|
|
|
switch (bytes[0]) {
|
|
|
|
case B_ENTER:
|
|
// translate to evaluate key
|
|
_PressKey("=");
|
|
break;
|
|
|
|
case B_LEFT_ARROW:
|
|
case B_BACKSPACE:
|
|
// translate to backspace key
|
|
_PressKey("BS");
|
|
break;
|
|
|
|
case B_SPACE:
|
|
case B_ESCAPE:
|
|
case 'c':
|
|
// translate to clear key
|
|
_PressKey("C");
|
|
break;
|
|
|
|
// bracket translation
|
|
case '[':
|
|
case '{':
|
|
_PressKey("(");
|
|
break;
|
|
|
|
case ']':
|
|
case '}':
|
|
_PressKey(")");
|
|
break;
|
|
|
|
default: {
|
|
// scan the keymap array for match
|
|
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) {
|
|
// set num lock
|
|
if (fOptions->auto_num_lock) {
|
|
set_keyboard_locks(B_NUM_LOCK
|
|
| (modifiers() & (B_CAPS_LOCK | B_SCROLL_LOCK)));
|
|
}
|
|
}
|
|
|
|
// pass on request to text view
|
|
fExpressionTextView->MakeFocus(focused);
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::FrameResized(float width, float height)
|
|
{
|
|
fWidth = width;
|
|
fHeight = height;
|
|
|
|
// layout expression text view
|
|
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());
|
|
|
|
// configure expression text view font size and color
|
|
float sizeDisp = fOptions->keypad_mode == KEYPAD_MODE_COMPACT
|
|
? fHeight : fHeight * kDisplayScaleY;
|
|
BFont font(be_bold_font);
|
|
font.SetSize(sizeDisp * kExpressionFontScaleY);
|
|
fExpressionTextView->SetFontAndColor(&font, B_FONT_ALL);
|
|
|
|
expressionRect.OffsetTo(B_ORIGIN);
|
|
float inset = (expressionRect.Height() - fExpressionTextView->LineHeight(0)) / 2;
|
|
expressionRect.InsetBy(0, inset);
|
|
fExpressionTextView->SetTextRect(expressionRect);
|
|
Invalidate();
|
|
}
|
|
|
|
|
|
status_t
|
|
CalcView::Archive(BMessage* archive, bool deep) const
|
|
{
|
|
fExpressionTextView->RemoveSelf();
|
|
|
|
// passed on request to parent
|
|
status_t ret = BView::Archive(archive, deep);
|
|
|
|
const_cast<CalcView*>(this)->AddChild(fExpressionTextView);
|
|
|
|
// save app signature for replicant add-on loading
|
|
if (ret == B_OK)
|
|
ret = archive->AddString("add_on", kSignature);
|
|
|
|
// save all the options
|
|
if (ret == B_OK)
|
|
ret = SaveSettings(archive);
|
|
|
|
// add class info last
|
|
if (ret == B_OK)
|
|
ret = archive->AddString("class", "CalcView");
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::Cut()
|
|
{
|
|
Copy(); // copy data to clipboard
|
|
fExpressionTextView->Clear(); // remove data
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::Copy()
|
|
{
|
|
// access system clipboard
|
|
if (be_clipboard->Lock()) {
|
|
be_clipboard->Clear();
|
|
BMessage* clipper = be_clipboard->Data();
|
|
clipper->what = B_MIME_DATA;
|
|
// TODO: should check return for errors!
|
|
BString expression = fExpressionTextView->Text();
|
|
clipper->AddData("text/plain", B_MIME_TYPE,
|
|
expression.String(), expression.Length());
|
|
//clipper->PrintToStream();
|
|
be_clipboard->Commit();
|
|
be_clipboard->Unlock();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::Paste(BMessage* message)
|
|
{
|
|
// handle color drops first
|
|
// read incoming color
|
|
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)) {
|
|
|
|
// calculate view relative drop point
|
|
BPoint dropPoint = ConvertFromScreen(message->DropPoint());
|
|
|
|
// calculate current keypad area
|
|
float sizeDisp = fHeight * kDisplayScaleY;
|
|
BRect keypadRect(0.0, sizeDisp, fWidth, fHeight);
|
|
|
|
// check location of color drop
|
|
if (keypadRect.Contains(dropPoint) && dropColor) {
|
|
fBaseColor = *dropColor;
|
|
_Colorize();
|
|
// redraw
|
|
Invalidate();
|
|
}
|
|
|
|
} else {
|
|
// look for text/plain MIME data
|
|
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;
|
|
|
|
// record grid dimensions
|
|
if (ret == B_OK)
|
|
ret = archive->AddInt16("cols", fColumns);
|
|
if (ret == B_OK)
|
|
ret = archive->AddInt16("rows", fRows);
|
|
|
|
// record color scheme
|
|
if (ret == B_OK)
|
|
ret = archive->AddData("rgbBaseColor", B_RGB_COLOR_TYPE,
|
|
&fBaseColor, sizeof(rgb_color));
|
|
if (ret == B_OK)
|
|
ret = archive->AddData("rgbDisplay", B_RGB_COLOR_TYPE,
|
|
&fExpressionBGColor, sizeof(rgb_color));
|
|
|
|
// record current options
|
|
if (ret == B_OK)
|
|
ret = fOptions->SaveSettings(archive);
|
|
|
|
// record display text
|
|
if (ret == B_OK)
|
|
ret = archive->AddString("displayText", fExpressionTextView->Text());
|
|
|
|
// record expression history
|
|
if (ret == B_OK)
|
|
ret = fExpressionTextView->SaveSettings(archive);
|
|
|
|
// record calculator description
|
|
if (ret == B_OK)
|
|
ret = archive->AddString("calcDesc", fKeypadDescription);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::Evaluate()
|
|
{
|
|
BString expression = fExpressionTextView->Text();
|
|
|
|
if (expression.Length() == 0) {
|
|
beep();
|
|
return;
|
|
}
|
|
|
|
_AudioFeedback(false);
|
|
|
|
// evaluate expression
|
|
BString value;
|
|
|
|
try {
|
|
ExpressionParser parser;
|
|
parser.SetDegreeMode(fOptions->degree_mode);
|
|
value = parser.Evaluate(expression.String());
|
|
} catch (ParseException e) {
|
|
BString error(e.message.String());
|
|
error << " at " << (e.position + 1);
|
|
fExpressionTextView->SetText(error.String());
|
|
return;
|
|
}
|
|
|
|
// render new result to display
|
|
fExpressionTextView->SetValue(value.String());
|
|
}
|
|
|
|
|
|
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::ToggleAudioFeedback(void)
|
|
{
|
|
fOptions->audio_feedback = !fOptions->audio_feedback;
|
|
fAudioFeedbackItem->SetMarked(fOptions->audio_feedback);
|
|
}
|
|
|
|
|
|
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:
|
|
{
|
|
free(fKeypadDescription);
|
|
fKeypadDescription = strdup(kKeypadDescriptionScientific);
|
|
fRows = 8;
|
|
_ParseCalcDesc(fKeypadDescription);
|
|
|
|
window->SetSizeLimits(kMinimumWidthScientific,
|
|
kMaximumWidthScientific, kMinimumHeightScientific,
|
|
kMaximumHeightScientific);
|
|
|
|
if (width < kMinimumWidthScientific)
|
|
width = kMinimumWidthScientific;
|
|
if (width > kMaximumWidthScientific)
|
|
width = kMaximumWidthScientific;
|
|
|
|
if (height < kMinimumHeightScientific)
|
|
height = kMinimumHeightScientific;
|
|
if (height > kMaximumHeightScientific)
|
|
height = kMaximumHeightScientific;
|
|
ResizeTo(width, height);
|
|
break;
|
|
}
|
|
|
|
case KEYPAD_MODE_BASIC:
|
|
default:
|
|
{
|
|
free(fKeypadDescription);
|
|
fKeypadDescription = strdup(kKeypadDescriptionBasic);
|
|
fRows = 4;
|
|
_ParseCalcDesc(fKeypadDescription);
|
|
|
|
window->SetSizeLimits(kMinimumWidthBasic, kMaximumWidthBasic,
|
|
kMinimumHeightBasic, kMaximumHeightBasic);
|
|
|
|
if (width < kMinimumWidthBasic)
|
|
width = kMinimumWidthBasic;
|
|
if (width > kMaximumWidthBasic)
|
|
width = kMaximumWidthBasic;
|
|
|
|
if (height < kMinimumHeightBasic)
|
|
height = kMinimumHeightBasic;
|
|
if (height > kMaximumHeightBasic)
|
|
height = kMaximumHeightBasic;
|
|
ResizeTo(width, height);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
void
|
|
CalcView::_Init(BMessage* settings)
|
|
{
|
|
// create expression text view
|
|
fExpressionTextView = new ExpressionTextView(_ExpressionRect(), this);
|
|
AddChild(fExpressionTextView);
|
|
|
|
// read data from archive
|
|
_LoadSettings(settings);
|
|
|
|
// fetch the calc icon for compact view
|
|
_FetchAppIcon(fCalcIcon);
|
|
}
|
|
|
|
|
|
status_t
|
|
CalcView::_LoadSettings(BMessage* archive)
|
|
{
|
|
if (!archive)
|
|
return B_BAD_VALUE;
|
|
|
|
// record calculator description
|
|
const char* calcDesc;
|
|
if (archive->FindString("calcDesc", &calcDesc) < B_OK)
|
|
calcDesc = kKeypadDescriptionBasic;
|
|
|
|
// save calculator description for reference
|
|
free(fKeypadDescription);
|
|
fKeypadDescription = strdup(calcDesc);
|
|
|
|
// read grid dimensions
|
|
if (archive->FindInt16("cols", &fColumns) < B_OK)
|
|
fColumns = 5;
|
|
if (archive->FindInt16("rows", &fRows) < B_OK)
|
|
fRows = 4;
|
|
|
|
// read color scheme
|
|
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;
|
|
}
|
|
|
|
if (archive->FindData("rgbDisplay", B_RGB_COLOR_TYPE,
|
|
(const void**)&color, &size) < B_OK
|
|
|| size != sizeof(rgb_color)) {
|
|
fExpressionBGColor = (rgb_color){ 0, 0, 0, 255 };
|
|
puts("Missing rgbBaseColor from CalcView archive!\n");
|
|
} else {
|
|
fExpressionBGColor = *color;
|
|
}
|
|
|
|
// load options
|
|
fOptions->LoadSettings(archive);
|
|
|
|
// load display text
|
|
const char* display;
|
|
if (archive->FindString("displayText", &display) < B_OK) {
|
|
puts("Missing expression text from CalcView archive.\n");
|
|
} else {
|
|
// init expression text
|
|
fExpressionTextView->SetText(display);
|
|
}
|
|
|
|
// load expression history
|
|
fExpressionTextView->LoadSettings(archive);
|
|
|
|
// parse calculator description
|
|
_ParseCalcDesc(fKeypadDescription);
|
|
|
|
// colorize based on base color.
|
|
_Colorize();
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::_ParseCalcDesc(const char* keypadDescription)
|
|
{
|
|
// TODO: should calculate dimensions from desc here!
|
|
fKeypad = new CalcKey[fRows * fColumns];
|
|
|
|
// scan through calculator description and assemble keypad
|
|
CalcKey* key = fKeypad;
|
|
const char* p = keypadDescription;
|
|
|
|
while (*p != 0) {
|
|
// copy label
|
|
char* l = key->label;
|
|
while (!isspace(*p))
|
|
*l++ = *p++;
|
|
*l = '\0';
|
|
|
|
// set code
|
|
if (strcmp(key->label, "=") == 0)
|
|
strlcpy(key->code, "\n", sizeof(key->code));
|
|
else
|
|
strlcpy(key->code, key->label, sizeof(key->code));
|
|
|
|
// set keymap
|
|
if (strlen(key->label) == 1)
|
|
strlcpy(key->keymap, key->label, sizeof(key->keymap));
|
|
else
|
|
*key->keymap = '\0';
|
|
|
|
key->flags = 0;
|
|
|
|
// add this to the expression text view, so that it
|
|
// will forward the respective KeyDown event to us
|
|
fExpressionTextView->AddKeypadLabel(key->label);
|
|
|
|
// advance
|
|
while (isspace(*p))
|
|
++p;
|
|
key++;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::_PressKey(int key)
|
|
{
|
|
assert(key < (fRows * fColumns));
|
|
assert(key >= 0);
|
|
|
|
if (strcmp(fKeypad[key].label, "BS") == 0) {
|
|
// BS means backspace
|
|
fExpressionTextView->BackSpace();
|
|
} else if (strcmp(fKeypad[key].label, "C") == 0) {
|
|
// C means clear
|
|
fExpressionTextView->Clear();
|
|
} else if (strcmp(fKeypad[key].label, "acos") == 0
|
|
|| strcmp(fKeypad[key].label, "asin") == 0
|
|
|| strcmp(fKeypad[key].label, "atan") == 0
|
|
|| strcmp(fKeypad[key].label, "cbrt") == 0
|
|
|| strcmp(fKeypad[key].label, "ceil") == 0
|
|
|| strcmp(fKeypad[key].label, "cos") == 0
|
|
|| strcmp(fKeypad[key].label, "cosh") == 0
|
|
|| strcmp(fKeypad[key].label, "exp") == 0
|
|
|| strcmp(fKeypad[key].label, "floor") == 0
|
|
|| strcmp(fKeypad[key].label, "log") == 0
|
|
|| strcmp(fKeypad[key].label, "ln") == 0
|
|
|| strcmp(fKeypad[key].label, "sin") == 0
|
|
|| strcmp(fKeypad[key].label, "sinh") == 0
|
|
|| strcmp(fKeypad[key].label, "sqrt") == 0
|
|
|| strcmp(fKeypad[key].label, "tan") == 0
|
|
|| strcmp(fKeypad[key].label, "tanh") == 0) {
|
|
int32 labelLen = strlen(fKeypad[key].label);
|
|
int32 startSelection = 0;
|
|
int32 endSelection = 0;
|
|
fExpressionTextView->GetSelection(&startSelection, &endSelection);
|
|
if (endSelection > startSelection) {
|
|
// There is selected text, put it inbetween the parens
|
|
fExpressionTextView->Insert(startSelection, fKeypad[key].label,
|
|
labelLen);
|
|
fExpressionTextView->Insert(startSelection + labelLen, "(", 1);
|
|
fExpressionTextView->Insert(endSelection + labelLen + 1, ")", 1);
|
|
// Put the cursor after the ending paren
|
|
// Need to cast to BTextView because Select() is protected
|
|
// in the InputTextView class
|
|
static_cast<BTextView*>(fExpressionTextView)->Select(
|
|
endSelection + labelLen + 2, endSelection + labelLen + 2);
|
|
} else {
|
|
// There is no selected text, insert at the cursor location
|
|
fExpressionTextView->Insert(fKeypad[key].label);
|
|
fExpressionTextView->Insert("()");
|
|
// Put the cursor inside the parens so you can enter an argument
|
|
// Need to cast to BTextView because Select() is protected
|
|
// in the InputTextView class
|
|
static_cast<BTextView*>(fExpressionTextView)->Select(
|
|
endSelection + labelLen + 1, endSelection + labelLen + 1);
|
|
}
|
|
} else {
|
|
// check for evaluation order
|
|
if (fKeypad[key].code[0] == '\n') {
|
|
fExpressionTextView->ApplyChanges();
|
|
} else {
|
|
// insert into expression text
|
|
fExpressionTextView->Insert(fKeypad[key].code);
|
|
}
|
|
}
|
|
|
|
_AudioFeedback(true);
|
|
}
|
|
|
|
|
|
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::_AudioFeedback(bool inBackGround)
|
|
{
|
|
// TODO: Use beep events... This interface is not implemented on Haiku
|
|
// anyways...
|
|
#if 0
|
|
if (fOptions->audio_feedback) {
|
|
BEntry zimp("key.AIFF");
|
|
entry_ref zimp_ref;
|
|
zimp.GetRef(&zimp_ref);
|
|
play_sound(&zimp_ref, true, false, inBackGround);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::_Colorize()
|
|
{
|
|
// calculate light and dark color from base color
|
|
fLightColor.red = (uint8)(fBaseColor.red * 1.25);
|
|
fLightColor.green = (uint8)(fBaseColor.green * 1.25);
|
|
fLightColor.blue = (uint8)(fBaseColor.blue * 1.25);
|
|
fLightColor.alpha = 255;
|
|
|
|
fDarkColor.red = (uint8)(fBaseColor.red * 0.75);
|
|
fDarkColor.green = (uint8)(fBaseColor.green * 0.75);
|
|
fDarkColor.blue = (uint8)(fBaseColor.blue * 0.75);
|
|
fDarkColor.alpha = 255;
|
|
|
|
// keypad text color
|
|
uint8 lightness = (fBaseColor.red + fBaseColor.green + fBaseColor.blue) / 3;
|
|
if (lightness > 200)
|
|
fButtonTextColor = (rgb_color){ 0, 0, 0, 255 };
|
|
else
|
|
fButtonTextColor = (rgb_color){ 255, 255, 255, 255 };
|
|
|
|
// expression text color
|
|
lightness = (fExpressionBGColor.red
|
|
+ fExpressionBGColor.green + fExpressionBGColor.blue) / 3;
|
|
if (lightness > 200)
|
|
fExpressionTextColor = (rgb_color){ 0, 0, 0, 255 };
|
|
else
|
|
fExpressionTextColor = (rgb_color){ 255, 255, 255, 255 };
|
|
}
|
|
|
|
|
|
void
|
|
CalcView::_CreatePopUpMenu(bool addKeypadModeMenuItems)
|
|
{
|
|
// construct items
|
|
fAutoNumlockItem = new BMenuItem(B_TRANSLATE("Enable Num Lock on startup"),
|
|
new BMessage(MSG_OPTIONS_AUTO_NUM_LOCK));
|
|
fAudioFeedbackItem = new BMenuItem(B_TRANSLATE("Audio Feedback"),
|
|
new BMessage(MSG_OPTIONS_AUDIO_FEEDBACK));
|
|
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');
|
|
}
|
|
|
|
// apply current settings
|
|
fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
|
|
fAudioFeedbackItem->SetMarked(fOptions->audio_feedback);
|
|
fAngleModeRadianItem->SetMarked(!fOptions->degree_mode);
|
|
fAngleModeDegreeItem->SetMarked(fOptions->degree_mode);
|
|
|
|
// construct menu
|
|
fPopUpMenu = new BPopUpMenu("pop-up", false, false);
|
|
|
|
fPopUpMenu->AddItem(fAutoNumlockItem);
|
|
// TODO: Enable this when we use beep events which can be configured
|
|
// in the Sounds preflet.
|
|
//fPopUpMenu->AddItem(fAudioFeedbackItem);
|
|
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: // KEYPAD_MODE_BASIC is the 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());
|
|
}
|
|
|
|
|
|
// Returns whether or not CalcView is embedded somewhere, most likely
|
|
// the Desktop
|
|
bool
|
|
CalcView::_IsEmbedded()
|
|
{
|
|
return Parent() != NULL && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0;
|
|
}
|