2 * Copyright 2006-2013 Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
6 * Stephan Aßmus, superstippi@gmx.de
7 * John Scipione, jscipione@gmail.com
11 #include "ExpressionTextView.h"
24 static const int32 kMaxPreviousExpressions
= 20;
27 ExpressionTextView::ExpressionTextView(BRect frame
, CalcView
* calcView
)
29 InputTextView(frame
, "expression text view",
30 (frame
.OffsetToCopy(B_ORIGIN
)).InsetByCopy(2, 2),
31 B_FOLLOW_NONE
, B_WILL_DRAW
),
34 fPreviousExpressions(20),
36 fCurrentExpression(""),
38 fChangesApplied(false)
42 SetColorSpace(B_RGB32
);
43 SetFontAndColor(be_bold_font
, B_FONT_ALL
);
47 ExpressionTextView::~ExpressionTextView()
49 int32 count
= fPreviousExpressions
.CountItems();
50 for (int32 i
= 0; i
< count
; i
++)
51 delete (BString
*)fPreviousExpressions
.ItemAtFast(i
);
56 ExpressionTextView::MakeFocus(bool focused
)
58 if (focused
== IsFocus()) {
59 // stop endless loop when CalcView calls us again
63 // NOTE: order of lines important!
64 InputTextView::MakeFocus(focused
);
65 fCalcView
->MakeFocus(focused
);
70 ExpressionTextView::KeyDown(const char* bytes
, int32 numBytes
)
72 // Handle expression history
73 if (bytes
[0] == B_UP_ARROW
) {
77 if (bytes
[0] == B_DOWN_ARROW
) {
81 BString current
= Text();
83 // Handle in InputTextView, except B_TAB
86 else if (bytes
[0] != B_TAB
)
87 InputTextView::KeyDown(bytes
, numBytes
);
89 // Pass on to CalcView if this was a label on a key
90 if (fKeypadLabels
.FindFirst(bytes
[0]) >= 0)
91 fCalcView
->FlashKey(bytes
, numBytes
);
92 else if (bytes
[0] == B_BACKSPACE
)
93 fCalcView
->FlashKey("BS", 2);
95 // As soon as something is typed, we are at the end of the expression
97 if (current
!= Text())
98 fHistoryPos
= fPreviousExpressions
.CountItems();
100 // If changes where not applied the value has become a new expression
101 // note that even if only the left or right arrow keys are pressed the
102 // fCurrentValue string will be cleared.
103 if (!fChangesApplied
)
104 fCurrentValue
.SetTo("");
106 fChangesApplied
= false;
111 ExpressionTextView::MouseDown(BPoint where
)
114 Window()->CurrentMessage()->FindInt32("buttons", (int32
*)&buttons
);
115 if (buttons
& B_PRIMARY_MOUSE_BUTTON
) {
116 InputTextView::MouseDown(where
);
119 where
= ConvertToParent(where
);
120 fCalcView
->MouseDown(where
);
125 ExpressionTextView::GetDragParameters(BMessage
* dragMessage
,
126 BBitmap
** bitmap
, BPoint
* point
, BHandler
** handler
)
128 InputTextView::GetDragParameters(dragMessage
, bitmap
, point
, handler
);
129 dragMessage
->AddString("be:clip_name", "DeskCalc clipping");
134 ExpressionTextView::SetTextRect(BRect rect
)
136 InputTextView::SetTextRect(rect
);
138 int32 count
= fPreviousExpressions
.CountItems();
139 if (fHistoryPos
== count
&& fCurrentValue
.CountChars() > 0) {
142 GetSelection(&start
, &finish
);
143 SetValue(fCurrentValue
.String());
144 Select(start
, finish
);
153 ExpressionTextView::RevertChanges()
160 ExpressionTextView::ApplyChanges()
162 AddExpressionToHistory(Text());
163 fCalcView
->FlashKey("=", 1);
164 fCalcView
->Evaluate();
165 fChangesApplied
= true;
173 ExpressionTextView::AddKeypadLabel(const char* label
)
175 fKeypadLabels
<< label
;
180 ExpressionTextView::SetExpression(const char* expression
)
183 int32 lastPos
= strlen(expression
);
184 Select(lastPos
, lastPos
);
189 ExpressionTextView::SetValue(BString value
)
192 fCurrentValue
= value
;
194 // calculate the width of the string
196 uint32 mode
= B_FONT_ALL
;
197 GetFontAndColor(&font
, &mode
);
198 float stringWidth
= font
.StringWidth(value
);
200 // make the string shorter if it does not fit in the view
201 float viewWidth
= Frame().Width();
202 if (value
.CountChars() > 3 && stringWidth
> viewWidth
) {
203 // get the position of the first digit
204 int32 firstDigit
= 0;
208 // calculate the value of the exponent
210 int32 offset
= value
.FindFirst('.');
211 if (offset
== B_ERROR
) {
212 exponent
= value
.CountChars() - 1 - firstDigit
;
213 value
.Insert('.', 1, firstDigit
+ 1);
215 if (offset
== firstDigit
+ 1) {
216 // if the value is 0.01 or larger then scientific notation
217 // won't shorten the string
218 if (value
[firstDigit
] != '0' || value
[firstDigit
+ 2] != '0'
219 || value
[firstDigit
+ 3] != '0') {
223 value
.Remove(offset
, 1);
225 // check for negative exponent value
227 while (value
[firstDigit
] == '0') {
228 value
.Remove(firstDigit
, 1);
233 value
.Insert('.', 1, firstDigit
+ 1);
236 // if the period + 1 digit fits in the view scientific notation
237 // won't shorten the string
238 BString temp
= value
;
239 temp
.Truncate(offset
+ 2);
240 stringWidth
= font
.StringWidth(temp
);
241 if (stringWidth
< viewWidth
)
245 value
.Remove(offset
, 1);
246 value
.Insert('.', 1, firstDigit
+ 1);
248 exponent
= offset
- (firstDigit
+ 1);
255 // truncate to a reasonable precision
256 // while ensuring result will be rounded
257 offset
= value
.CountChars() - 1;
258 value
<< "E" << exponent
;
261 offset
= value
.CountChars() - 1;
263 // reduce the number of digits until the string fits or can not be
265 stringWidth
= font
.StringWidth(value
);
266 char lastRemovedDigit
= '0';
267 while (offset
> firstDigit
&& stringWidth
> viewWidth
) {
268 if (value
[offset
] != '.')
269 lastRemovedDigit
= value
[offset
];
270 value
.Remove(offset
--, 1);
271 stringWidth
= font
.StringWidth(value
);
274 // no need to keep the period if no digits follow
275 if (value
[offset
] == '.') {
276 value
.Remove(offset
, 1);
280 // take care of proper rounding of the result
281 int digit
= (int)lastRemovedDigit
- '0'; // ascii to int
283 for (; offset
>= firstDigit
; offset
--) {
284 if (value
[offset
] == '.')
287 digit
= (int)(value
[offset
]) - '0' + 1; // ascii to int + 1
291 value
.SetByteAt(offset
, '0');
294 // carry over, shift the result
295 if (value
[firstDigit
+ 1] == '.') {
296 value
.SetByteAt(firstDigit
+ 1, '0');
297 value
.SetByteAt(firstDigit
, '.');
299 value
.Insert('1', 1, firstDigit
);
301 // remove the exponent value and the last digit
302 offset
= value
.FindFirst('E');
303 if (offset
== B_ERROR
)
304 offset
= value
.CountChars();
306 value
.Truncate(--offset
);
307 offset
--; // offset now points to the last digit
309 // increase the exponent and add it back to the string
311 value
<< 'E' << exponent
;
313 // increase the current digit value with one
314 value
.SetByteAt(offset
, char(digit
+ 48));
316 // set offset to last digit
317 offset
= value
.FindFirst('E');
318 if (offset
== B_ERROR
)
319 offset
= value
.CountChars();
325 // clean up decimal part if we have one
326 if (value
.FindFirst('.') != B_ERROR
) {
327 // remove trailing zeros
328 while (value
[offset
] == '0')
329 value
.Remove(offset
--, 1);
331 // no need to keep the period if no digits follow
332 if (value
[offset
] == '.')
333 value
.Remove(offset
, 1);
338 SetExpression(value
);
343 ExpressionTextView::BackSpace()
345 const char bytes
[1] = { B_BACKSPACE
};
348 fCalcView
->FlashKey("BS", 2);
353 ExpressionTextView::Clear()
357 fCalcView
->FlashKey("C", 1);
365 ExpressionTextView::AddExpressionToHistory(const char* expression
)
367 // clean out old expressions that are the same as
368 // the one to be added
369 int32 count
= fPreviousExpressions
.CountItems();
370 for (int32 i
= 0; i
< count
; i
++) {
371 BString
* item
= (BString
*)fPreviousExpressions
.ItemAt(i
);
372 if (*item
== expression
&& fPreviousExpressions
.RemoveItem(i
)) {
379 BString
* item
= new (nothrow
) BString(expression
);
382 if (!fPreviousExpressions
.AddItem(item
)) {
386 while (fPreviousExpressions
.CountItems() > kMaxPreviousExpressions
)
387 delete (BString
*)fPreviousExpressions
.RemoveItem((int32
)0);
389 fHistoryPos
= fPreviousExpressions
.CountItems();
394 ExpressionTextView::PreviousExpression()
396 int32 count
= fPreviousExpressions
.CountItems();
397 if (fHistoryPos
== count
) {
398 // save current expression
399 fCurrentExpression
= Text();
403 if (fHistoryPos
< 0) {
408 BString
* item
= (BString
*)fPreviousExpressions
.ItemAt(fHistoryPos
);
410 SetExpression(item
->String());
415 ExpressionTextView::NextExpression()
417 int32 count
= fPreviousExpressions
.CountItems();
420 if (fHistoryPos
== count
) {
421 SetExpression(fCurrentExpression
.String());
425 if (fHistoryPos
> count
) {
430 BString
* item
= (BString
*)fPreviousExpressions
.ItemAt(fHistoryPos
);
432 SetExpression(item
->String());
440 ExpressionTextView::LoadSettings(const BMessage
* archive
)
442 const char* oldExpression
;
444 archive
->FindString("previous expression", i
, &oldExpression
) == B_OK
;
446 AddExpressionToHistory(oldExpression
);
452 ExpressionTextView::SaveSettings(BMessage
* archive
) const
454 int32 count
= fPreviousExpressions
.CountItems();
455 for (int32 i
= 0; i
< count
; i
++) {
456 BString
* item
= (BString
*)fPreviousExpressions
.ItemAtFast(i
);
457 status_t ret
= archive
->AddString("previous expression",