* 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 <new>
#include <stdio.h>
#include <Beep.h>
#include <ControlLook.h>
#include <Window.h>
#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()) {
return;
}
InputTextView::MakeFocus(focused);
fCalcView->MakeFocus(focused);
}
void
ExpressionTextView::KeyDown(const char* bytes, int32 numBytes)
{
if (bytes[0] == B_UP_ARROW) {
PreviousExpression();
return;
}
if (bytes[0] == B_DOWN_ARROW) {
NextExpression();
return;
}
BString current = Text();
if (bytes[0] == '=')
ApplyChanges();
else if (bytes[0] != B_TAB)
InputTextView::KeyDown(bytes, numBytes);
if (fKeypadLabels.FindFirst(bytes[0]) >= 0)
fCalcView->FlashKey(bytes, numBytes);
else if (bytes[0] == B_BACKSPACE)
fCalcView->FlashKey("BS", 2);
if (current != Text())
fHistoryPos = fPreviousExpressions.CountItems();
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);
}
}
void
ExpressionTextView::RevertChanges()
{
Clear();
}
void
ExpressionTextView::ApplyChanges()
{
AddExpressionToHistory(Text());
fCalcView->FlashKey("=", 1);
fCalcView->Evaluate();
fChangesApplied = true;
}
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)
{
fCurrentValue = value;
BFont font;
uint32 mode = B_FONT_ALL;
GetFontAndColor(&font, &mode);
float stringWidth = font.StringWidth(value);
uint decimalSeparatorWidth = decimalSeparator.CountChars();
float viewWidth = Frame().Width()
- floorf(be_control_look->DefaultLabelSpacing() / 2);
if (value.CountChars() > 3 && stringWidth > viewWidth) {
int32 firstDigit = 0;
if (value[0] == '-')
firstDigit++;
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 (value[firstDigit] != '0' || value[firstDigit + 2] != '0'
|| value[firstDigit + 3] != '0') {
exponent = 0;
} else {
value.Remove(offset, decimalSeparatorWidth);
exponent = 0;
while (value[firstDigit] == '0') {
value.Remove(firstDigit, 1);
exponent--;
}
value.InsertChars(decimalSeparator, firstDigit + 1);
}
} else {
BString temp = value;
temp.Truncate(offset + 2);
stringWidth = font.StringWidth(temp);
if (stringWidth < viewWidth)
exponent = 0;
else {
value.Remove(offset, decimalSeparatorWidth);
value.InsertChars(decimalSeparator, firstDigit + 1);
exponent = offset - (firstDigit + 1);
}
}
}
if (exponent != 0) {
value.Truncate(40);
offset = value.CountChars() - 1;
value << "E" << exponent;
} else
offset = value.CountChars() - 1;
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);
}
if (value.CharAt(offset) == decimalSeparator) {
value.Remove(offset, decimalSeparatorWidth);
offset--;
}
int digit = (int)lastRemovedDigit - '0';
if (digit >= 5) {
for (; offset >= firstDigit; offset--) {
if (value.CharAt(offset) == decimalSeparator)
continue;
digit = (int)(value[offset]) - '0' + 1;
if (digit != 10)
break;
value.SetByteAt(offset, '0');
}
if (digit == 10) {
if (value.CharAt(firstDigit + 1) == decimalSeparator) {
value.SetByteAt(firstDigit + decimalSeparatorWidth, '0');
value.RemoveChars(firstDigit, decimalSeparatorWidth);
value.InsertChars(decimalSeparator, firstDigit);
}
value.Insert('1', 1, firstDigit);
offset = value.FindFirst('E');
if (offset == B_ERROR)
offset = value.CountChars();
value.Truncate(--offset);
offset--;
exponent++;
value << 'E' << exponent;
} else {
value.SetByteAt(offset, char(digit + 48));
offset = value.FindFirst('E');
if (offset == B_ERROR)
offset = value.CountChars();
offset--;
}
}
if (value.FindFirst(decimalSeparator) != B_ERROR) {
while (value[offset] == '0')
value.Remove(offset--, 1);
if (value.CharAt(offset) == decimalSeparator)
value.Remove(offset, decimalSeparatorWidth);
}
}
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);
}
void
ExpressionTextView::AddExpressionToHistory(const char* expression)
{
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) {
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());
}
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;
}