/* * Copyright 2006-2013 Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus, superstippi@gmx.de * John Scipione, jscipione@gmail.com */ #include "ExpressionTextView.h" #include #include #include #include #include #include "CalcView.h" using std::nothrow; static const int32 kMaxPreviousExpressions = 20; ExpressionTextView::ExpressionTextView(BRect frame, CalcView* calcView) : InputTextView(frame, "expression text view", (frame.OffsetToCopy(B_ORIGIN)).InsetByCopy(2, 2), B_FOLLOW_NONE, B_WILL_DRAW), fCalcView(calcView), fKeypadLabels(""), fPreviousExpressions(20), fHistoryPos(0), fCurrentExpression(""), fCurrentValue(""), fChangesApplied(false) { SetStylable(false); SetDoesUndo(true); SetColorSpace(B_RGB32); SetFontAndColor(be_bold_font, B_FONT_ALL); SetHighUIColor(B_DOCUMENT_TEXT_COLOR); SetAlignment(B_ALIGN_RIGHT); } ExpressionTextView::~ExpressionTextView() { int32 count = fPreviousExpressions.CountItems(); for (int32 i = 0; i < count; i++) delete (BString*)fPreviousExpressions.ItemAtFast(i); } void ExpressionTextView::MakeFocus(bool focused) { if (focused == IsFocus()) { // stop endless loop when CalcView calls us again return; } // NOTE: order of lines important! InputTextView::MakeFocus(focused); fCalcView->MakeFocus(focused); } void ExpressionTextView::KeyDown(const char* bytes, int32 numBytes) { // Handle expression history if (bytes[0] == B_UP_ARROW) { PreviousExpression(); return; } if (bytes[0] == B_DOWN_ARROW) { NextExpression(); return; } BString current = Text(); // Handle in InputTextView, except B_TAB if (bytes[0] == '=') ApplyChanges(); else if (bytes[0] != B_TAB) InputTextView::KeyDown(bytes, numBytes); // Pass on to CalcView if this was a label on a key if (fKeypadLabels.FindFirst(bytes[0]) >= 0) fCalcView->FlashKey(bytes, numBytes); else if (bytes[0] == B_BACKSPACE) fCalcView->FlashKey("BS", 2); // As soon as something is typed, we are at the end of the expression // history. if (current != Text()) fHistoryPos = fPreviousExpressions.CountItems(); // If changes where not applied the value has become a new expression // note that even if only the left or right arrow keys are pressed the // fCurrentValue string will be cleared. if (!fChangesApplied) fCurrentValue.SetTo(""); else fChangesApplied = false; } void ExpressionTextView::MouseDown(BPoint where) { uint32 buttons; Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); if (buttons & B_PRIMARY_MOUSE_BUTTON) { InputTextView::MouseDown(where); return; } where = ConvertToParent(where); fCalcView->MouseDown(where); } void ExpressionTextView::GetDragParameters(BMessage* dragMessage, BBitmap** bitmap, BPoint* point, BHandler** handler) { InputTextView::GetDragParameters(dragMessage, bitmap, point, handler); dragMessage->AddString("be:clip_name", "DeskCalc clipping"); } void ExpressionTextView::SetTextRect(BRect rect) { float hInset = floorf(be_control_look->DefaultLabelSpacing() / 2); float vInset = floorf((rect.Height() - LineHeight(0)) / 2); InputTextView::SetInsets(hInset, vInset, hInset, vInset); InputTextView::SetTextRect(rect); int32 count = fPreviousExpressions.CountItems(); if (fHistoryPos == count && fCurrentValue.CountChars() > 0) { int32 start; int32 finish; GetSelection(&start, &finish); SetValue(fCurrentValue.String()); Select(start, finish); } } // #pragma mark - void ExpressionTextView::RevertChanges() { Clear(); } void ExpressionTextView::ApplyChanges() { AddExpressionToHistory(Text()); fCalcView->FlashKey("=", 1); fCalcView->Evaluate(); fChangesApplied = true; } // #pragma mark - void ExpressionTextView::AddKeypadLabel(const char* label) { fKeypadLabels << label; } void ExpressionTextView::SetExpression(const char* expression) { SetText(expression); int32 lastPos = strlen(expression); Select(lastPos, lastPos); } void ExpressionTextView::SetValue(BString value, BString decimalSeparator) { // save the value fCurrentValue = value; // calculate the width of the string BFont font; uint32 mode = B_FONT_ALL; GetFontAndColor(&font, &mode); float stringWidth = font.StringWidth(value); uint decimalSeparatorWidth = decimalSeparator.CountChars(); // make the string shorter if it does not fit in the view float viewWidth = Frame().Width() - floorf(be_control_look->DefaultLabelSpacing() / 2); if (value.CountChars() > 3 && stringWidth > viewWidth) { // get the position of the first digit int32 firstDigit = 0; if (value[0] == '-') firstDigit++; // calculate the value of the exponent int32 exponent = 0; int32 offset = value.FindFirst(decimalSeparator); if (offset == B_ERROR) { exponent = value.CountChars() - decimalSeparatorWidth - firstDigit; value.InsertChars(decimalSeparator, firstDigit + 1); } else { if (offset == firstDigit + 1) { // if the value is 0.01 or larger then scientific notation // won't shorten the string if (value[firstDigit] != '0' || value[firstDigit + 2] != '0' || value[firstDigit + 3] != '0') { exponent = 0; } else { // remove the period value.Remove(offset, decimalSeparatorWidth); // check for negative exponent value exponent = 0; while (value[firstDigit] == '0') { value.Remove(firstDigit, 1); exponent--; } // add the period value.InsertChars(decimalSeparator, firstDigit + 1); } } else { // if the period + 1 digit fits in the view scientific notation // won't shorten the string BString temp = value; temp.Truncate(offset + 2); stringWidth = font.StringWidth(temp); if (stringWidth < viewWidth) exponent = 0; else { // move the period value.Remove(offset, decimalSeparatorWidth); value.InsertChars(decimalSeparator, firstDigit + 1); exponent = offset - (firstDigit + 1); } } } if (exponent != 0) { value.Truncate(40); // truncate to a reasonable precision // while ensuring result will be rounded offset = value.CountChars() - 1; value << "E" << exponent; // add the exponent } else offset = value.CountChars() - 1; // reduce the number of digits until the string fits or can not be // made any shorter stringWidth = font.StringWidth(value); char lastRemovedDigit = '0'; while (offset > firstDigit && stringWidth > viewWidth) { if (value.CharAt(offset) != decimalSeparator) lastRemovedDigit = value[offset]; value.Remove(offset--, 1); stringWidth = font.StringWidth(value); } // no need to keep the period if no digits follow if (value.CharAt(offset) == decimalSeparator) { value.Remove(offset, decimalSeparatorWidth); offset--; } // take care of proper rounding of the result int digit = (int)lastRemovedDigit - '0'; // ascii to int if (digit >= 5) { for (; offset >= firstDigit; offset--) { if (value.CharAt(offset) == decimalSeparator) continue; digit = (int)(value[offset]) - '0' + 1; // ascii to int + 1 if (digit != 10) break; value.SetByteAt(offset, '0'); } if (digit == 10) { // carry over, shift the result if (value.CharAt(firstDigit + 1) == decimalSeparator) { value.SetByteAt(firstDigit + decimalSeparatorWidth, '0'); value.RemoveChars(firstDigit, decimalSeparatorWidth); value.InsertChars(decimalSeparator, firstDigit); } value.Insert('1', 1, firstDigit); // remove the exponent value and the last digit offset = value.FindFirst('E'); if (offset == B_ERROR) offset = value.CountChars(); value.Truncate(--offset); offset--; // offset now points to the last digit // increase the exponent and add it back to the string exponent++; value << 'E' << exponent; } else { // increase the current digit value with one value.SetByteAt(offset, char(digit + 48)); // set offset to last digit offset = value.FindFirst('E'); if (offset == B_ERROR) offset = value.CountChars(); offset--; } } // clean up decimal part if we have one if (value.FindFirst(decimalSeparator) != B_ERROR) { // remove trailing zeros while (value[offset] == '0') value.Remove(offset--, 1); // no need to keep the period if no digits follow if (value.CharAt(offset) == decimalSeparator) value.Remove(offset, decimalSeparatorWidth); } } // set the new value SetExpression(value); } void ExpressionTextView::BackSpace() { const char bytes[1] = { B_BACKSPACE }; KeyDown(bytes, 1); fCalcView->FlashKey("BS", 2); } void ExpressionTextView::Clear() { SetText(""); fCalcView->FlashKey("C", 1); } // #pragma mark - void ExpressionTextView::AddExpressionToHistory(const char* expression) { // clean out old expressions that are the same as // the one to be added int32 count = fPreviousExpressions.CountItems(); for (int32 i = 0; i < count; i++) { BString* item = (BString*)fPreviousExpressions.ItemAt(i); if (*item == expression && fPreviousExpressions.RemoveItem(i)) { delete item; i--; count--; } } BString* item = new (nothrow) BString(expression); if (!item) return; if (!fPreviousExpressions.AddItem(item)) { delete item; return; } while (fPreviousExpressions.CountItems() > kMaxPreviousExpressions) delete (BString*)fPreviousExpressions.RemoveItem((int32)0); fHistoryPos = fPreviousExpressions.CountItems(); } void ExpressionTextView::PreviousExpression() { int32 count = fPreviousExpressions.CountItems(); if (fHistoryPos == count) { // save current expression fCurrentExpression = Text(); } fHistoryPos--; if (fHistoryPos < 0) { fHistoryPos = 0; return; } BString* item = (BString*)fPreviousExpressions.ItemAt(fHistoryPos); if (item != NULL) SetExpression(item->String()); } void ExpressionTextView::NextExpression() { int32 count = fPreviousExpressions.CountItems(); fHistoryPos++; if (fHistoryPos == count) { SetExpression(fCurrentExpression.String()); return; } if (fHistoryPos > count) { fHistoryPos = count; return; } BString* item = (BString*)fPreviousExpressions.ItemAt(fHistoryPos); if (item) SetExpression(item->String()); } // #pragma mark - void ExpressionTextView::LoadSettings(const BMessage* archive) { const char* oldExpression; for (int32 i = 0; archive->FindString("previous expression", i, &oldExpression) == B_OK; i++) { AddExpressionToHistory(oldExpression); } } status_t ExpressionTextView::SaveSettings(BMessage* archive) const { int32 count = fPreviousExpressions.CountItems(); for (int32 i = 0; i < count; i++) { BString* item = (BString*)fPreviousExpressions.ItemAtFast(i); status_t ret = archive->AddString("previous expression", item->String()); if (ret < B_OK) return ret; } return B_OK; }