/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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 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; // 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[] = { 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 }; // scientific mode keypad layout 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; // float width; }; typedef AutoLocker 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) { // 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(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) { // Do not restore the follow mode, in shelfs, we never follow. 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) { // 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 (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) { // 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_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 ClipboardLocker locker(be_clipboard); if (locker.IsLocked()) { BMessage* clipper = be_clipboard->Data(); if (clipper) Paste(clipper); } 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; } case kMsgAnimateDots: { int32 end = fExpressionTextView->TextLength(); int32 start = end - 3; if (fEnabled || strcmp(fExpressionTextView->Text() + start, "...") != 0) { // stop the message runner 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: { // calculation has taken more than 3 seconds if (fEnabled) { // stop the message runner 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)); } // stop the message runner 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()) { // render calc icon 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); } // 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), &fButtonTextColor); 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); 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(); // passed on request to parent status_t ret = BView::Archive(archive, deep); const_cast(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 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) { // handle files first 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; } // handle color drops // 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 != NULL) { fBaseColor = *dropColor; fHasCustomBaseColor = fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR); _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)); } // 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 == 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) { // failed to create evaluate thread, error out fExpressionTextView->SetText(strerror(fEvaluateThread)); return; } _SetEnabled(false); // Disable input while we evaluate status_t threadStatus = resume_thread(fEvaluateThread); if (threadStatus != B_OK) { // evaluate thread failed to start, error out 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(); } } } // #pragma mark - /*static*/ status_t CalcView::_EvaluateThread(void* data) { CalcView* calcView = reinterpret_cast(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) { // 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); fEvaluateSemaphore = create_sem(1, "Evaluate Semaphore"); } status_t CalcView::_LoadSettings(BMessage* archive) { if (!archive) return B_BAD_VALUE; // record calculator description BString calcDesc; archive->FindString("calcDesc", &calcDesc); if (calcDesc == "scientific" || calcDesc.StartsWith("ln")) fKeypadDescription = kKeypadDescriptionScientific; else fKeypadDescription = kKeypadDescriptionBasic; // 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; fHasCustomBaseColor = fBaseColor != ui_color(B_PANEL_BACKGROUND_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; for (int i = 0; const char* p = keypadDescription[i]; i++) { // Move to next row as needed if (strcmp(p, "\n") == 0) continue; // copy label strlcpy(key->label, B_TRANSLATE_NOCOLLECT(p), sizeof(key->label)); // set code if (strcmp(p, "=") == 0) strlcpy(key->code, "\n", sizeof(key->code)); else strlcpy(key->code, p, 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 key++; } } void CalcView::_PressKey(int key) { if (!fEnabled) return; assert(key < (fRows * fColumns)); assert(key >= 0); if (strcmp(fKeypad[key].code, "BS") == 0) { // BS means backspace fExpressionTextView->BackSpace(); } else if (strcmp(fKeypad[key].code, "C") == 0) { // C means clear 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) { // There is selected text, put it inbetween the parens fExpressionTextView->Insert(startSelection, fKeypad[key].code, 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(fExpressionTextView)->Select( endSelection + labelLen + 2, endSelection + labelLen + 2); } else { // There is no selected text, insert at the cursor location fExpressionTextView->Insert(fKeypad[key].code); 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(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 { // check for evaluation order if (fKeypad[key].code[0] == '\n') { fExpressionTextView->ApplyChanges(); } else { // insert into expression text 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) { // construct items 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'); } // apply current settings fAutoNumlockItem->SetMarked(fOptions->auto_num_lock); fAngleModeRadianItem->SetMarked(!fOptions->degree_mode); fAngleModeDegreeItem->SetMarked(fOptions->degree_mode); // construct menu 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: // 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; } void CalcView::_SetEnabled(bool enable) { fEnabled = enable; fExpressionTextView->MakeSelectable(enable); fExpressionTextView->MakeEditable(enable); }