1 /*
2 * Copyright 2006-2013 Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Stephan Aßmus, superstippi@gmx.de
7 * John Scipione, jscipione@gmail.com
8 */
9
10
11 #include "ExpressionTextView.h"
12
13 #include <new>
14 #include <stdio.h>
15
16 #include <Beep.h>
17 #include <ControlLook.h>
18 #include <Window.h>
19
20 #include "CalcView.h"
21
22
23 using std::nothrow;
24
25 static const int32 kMaxPreviousExpressions = 20;
26
27
ExpressionTextView(BRect frame,CalcView * calcView)28 ExpressionTextView::ExpressionTextView(BRect frame, CalcView* calcView)
29 :
30 InputTextView(frame, "expression text view",
31 (frame.OffsetToCopy(B_ORIGIN)).InsetByCopy(2, 2),
32 B_FOLLOW_NONE, B_WILL_DRAW),
33 fCalcView(calcView),
34 fKeypadLabels(""),
35 fPreviousExpressions(20),
36 fHistoryPos(0),
37 fCurrentExpression(""),
38 fCurrentValue(""),
39 fChangesApplied(false)
40 {
41 SetStylable(false);
42 SetDoesUndo(true);
43 SetColorSpace(B_RGB32);
44 SetFontAndColor(be_bold_font, B_FONT_ALL);
45 SetHighUIColor(B_DOCUMENT_TEXT_COLOR);
46 SetAlignment(B_ALIGN_RIGHT);
47 }
48
49
~ExpressionTextView()50 ExpressionTextView::~ExpressionTextView()
51 {
52 int32 count = fPreviousExpressions.CountItems();
53 for (int32 i = 0; i < count; i++)
54 delete (BString*)fPreviousExpressions.ItemAtFast(i);
55 }
56
57
58 void
MakeFocus(bool focused)59 ExpressionTextView::MakeFocus(bool focused)
60 {
61 if (focused == IsFocus()) {
62 // stop endless loop when CalcView calls us again
63 return;
64 }
65
66 // NOTE: order of lines important!
67 InputTextView::MakeFocus(focused);
68 fCalcView->MakeFocus(focused);
69 }
70
71
72 void
KeyDown(const char * bytes,int32 numBytes)73 ExpressionTextView::KeyDown(const char* bytes, int32 numBytes)
74 {
75 // Handle expression history
76 if (bytes[0] == B_UP_ARROW) {
77 PreviousExpression();
78 return;
79 }
80 if (bytes[0] == B_DOWN_ARROW) {
81 NextExpression();
82 return;
83 }
84 BString current = Text();
85
86 // Handle in InputTextView, except B_TAB
87 if (bytes[0] == '=')
88 ApplyChanges();
89 else if (bytes[0] != B_TAB)
90 InputTextView::KeyDown(bytes, numBytes);
91
92 // Pass on to CalcView if this was a label on a key
93 if (fKeypadLabels.FindFirst(bytes[0]) >= 0)
94 fCalcView->FlashKey(bytes, numBytes);
95 else if (bytes[0] == B_BACKSPACE)
96 fCalcView->FlashKey("BS", 2);
97
98 // As soon as something is typed, we are at the end of the expression
99 // history.
100 if (current != Text())
101 fHistoryPos = fPreviousExpressions.CountItems();
102
103 // If changes where not applied the value has become a new expression
104 // note that even if only the left or right arrow keys are pressed the
105 // fCurrentValue string will be cleared.
106 if (!fChangesApplied)
107 fCurrentValue.SetTo("");
108 else
109 fChangesApplied = false;
110 }
111
112
113 void
MouseDown(BPoint where)114 ExpressionTextView::MouseDown(BPoint where)
115 {
116 uint32 buttons;
117 Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
118 if (buttons & B_PRIMARY_MOUSE_BUTTON) {
119 InputTextView::MouseDown(where);
120 return;
121 }
122 where = ConvertToParent(where);
123 fCalcView->MouseDown(where);
124 }
125
126
127 void
GetDragParameters(BMessage * dragMessage,BBitmap ** bitmap,BPoint * point,BHandler ** handler)128 ExpressionTextView::GetDragParameters(BMessage* dragMessage,
129 BBitmap** bitmap, BPoint* point, BHandler** handler)
130 {
131 InputTextView::GetDragParameters(dragMessage, bitmap, point, handler);
132 dragMessage->AddString("be:clip_name", "DeskCalc clipping");
133 }
134
135
136 void
SetTextRect(BRect rect)137 ExpressionTextView::SetTextRect(BRect rect)
138 {
139 float hInset = floorf(be_control_look->DefaultLabelSpacing() / 2);
140 float vInset = floorf((rect.Height() - LineHeight(0)) / 2);
141 InputTextView::SetInsets(hInset, vInset, hInset, vInset);
142 InputTextView::SetTextRect(rect);
143
144 int32 count = fPreviousExpressions.CountItems();
145 if (fHistoryPos == count && fCurrentValue.CountChars() > 0) {
146 int32 start;
147 int32 finish;
148 GetSelection(&start, &finish);
149 SetValue(fCurrentValue.String());
150 Select(start, finish);
151 }
152 }
153
154
155 // #pragma mark -
156
157
158 void
RevertChanges()159 ExpressionTextView::RevertChanges()
160 {
161 Clear();
162 }
163
164
165 void
ApplyChanges()166 ExpressionTextView::ApplyChanges()
167 {
168 AddExpressionToHistory(Text());
169 fCalcView->FlashKey("=", 1);
170 fCalcView->Evaluate();
171 fChangesApplied = true;
172 }
173
174
175 // #pragma mark -
176
177
178 void
AddKeypadLabel(const char * label)179 ExpressionTextView::AddKeypadLabel(const char* label)
180 {
181 fKeypadLabels << label;
182 }
183
184
185 void
SetExpression(const char * expression)186 ExpressionTextView::SetExpression(const char* expression)
187 {
188 SetText(expression);
189 int32 lastPos = strlen(expression);
190 Select(lastPos, lastPos);
191 }
192
193
194 void
SetValue(BString value,BString decimalSeparator)195 ExpressionTextView::SetValue(BString value, BString decimalSeparator)
196 {
197 // save the value
198 fCurrentValue = value;
199
200 // calculate the width of the string
201 BFont font;
202 uint32 mode = B_FONT_ALL;
203 GetFontAndColor(&font, &mode);
204 float stringWidth = font.StringWidth(value);
205
206 uint decimalSeparatorWidth = decimalSeparator.CountChars();
207
208 // make the string shorter if it does not fit in the view
209 float viewWidth = Frame().Width()
210 - floorf(be_control_look->DefaultLabelSpacing() / 2);
211 if (value.CountChars() > 3 && stringWidth > viewWidth) {
212 // get the position of the first digit
213 int32 firstDigit = 0;
214 if (value[0] == '-')
215 firstDigit++;
216
217 // calculate the value of the exponent
218 int32 exponent = 0;
219 int32 offset = value.FindFirst(decimalSeparator);
220 if (offset == B_ERROR) {
221 exponent = value.CountChars() - decimalSeparatorWidth - firstDigit;
222 value.InsertChars(decimalSeparator, firstDigit + 1);
223 } else {
224 if (offset == firstDigit + 1) {
225 // if the value is 0.01 or larger then scientific notation
226 // won't shorten the string
227 if (value[firstDigit] != '0' || value[firstDigit + 2] != '0'
228 || value[firstDigit + 3] != '0') {
229 exponent = 0;
230 } else {
231 // remove the period
232 value.Remove(offset, decimalSeparatorWidth);
233
234 // check for negative exponent value
235 exponent = 0;
236 while (value[firstDigit] == '0') {
237 value.Remove(firstDigit, 1);
238 exponent--;
239 }
240
241 // add the period
242 value.InsertChars(decimalSeparator, firstDigit + 1);
243 }
244 } else {
245 // if the period + 1 digit fits in the view scientific notation
246 // won't shorten the string
247 BString temp = value;
248 temp.Truncate(offset + 2);
249 stringWidth = font.StringWidth(temp);
250 if (stringWidth < viewWidth)
251 exponent = 0;
252 else {
253 // move the period
254 value.Remove(offset, decimalSeparatorWidth);
255 value.InsertChars(decimalSeparator, firstDigit + 1);
256
257 exponent = offset - (firstDigit + 1);
258 }
259 }
260 }
261
262 if (exponent != 0) {
263 value.Truncate(40);
264 // truncate to a reasonable precision
265 // while ensuring result will be rounded
266 offset = value.CountChars() - 1;
267 value << "E" << exponent;
268 // add the exponent
269 } else
270 offset = value.CountChars() - 1;
271
272 // reduce the number of digits until the string fits or can not be
273 // made any shorter
274 stringWidth = font.StringWidth(value);
275 char lastRemovedDigit = '0';
276 while (offset > firstDigit && stringWidth > viewWidth) {
277 if (value.CharAt(offset) != decimalSeparator)
278 lastRemovedDigit = value[offset];
279 value.Remove(offset--, 1);
280 stringWidth = font.StringWidth(value);
281 }
282
283 // no need to keep the period if no digits follow
284 if (value.CharAt(offset) == decimalSeparator) {
285 value.Remove(offset, decimalSeparatorWidth);
286 offset--;
287 }
288
289 // take care of proper rounding of the result
290 int digit = (int)lastRemovedDigit - '0'; // ascii to int
291 if (digit >= 5) {
292 for (; offset >= firstDigit; offset--) {
293 if (value.CharAt(offset) == decimalSeparator)
294 continue;
295
296 digit = (int)(value[offset]) - '0' + 1; // ascii to int + 1
297 if (digit != 10)
298 break;
299
300 value.SetByteAt(offset, '0');
301 }
302 if (digit == 10) {
303 // carry over, shift the result
304 if (value.CharAt(firstDigit + 1) == decimalSeparator) {
305 value.SetByteAt(firstDigit + decimalSeparatorWidth, '0');
306 value.RemoveChars(firstDigit, decimalSeparatorWidth);
307 value.InsertChars(decimalSeparator, firstDigit);
308 }
309 value.Insert('1', 1, firstDigit);
310
311 // remove the exponent value and the last digit
312 offset = value.FindFirst('E');
313 if (offset == B_ERROR)
314 offset = value.CountChars();
315
316 value.Truncate(--offset);
317 offset--; // offset now points to the last digit
318
319 // increase the exponent and add it back to the string
320 exponent++;
321 value << 'E' << exponent;
322 } else {
323 // increase the current digit value with one
324 value.SetByteAt(offset, char(digit + 48));
325
326 // set offset to last digit
327 offset = value.FindFirst('E');
328 if (offset == B_ERROR)
329 offset = value.CountChars();
330
331 offset--;
332 }
333 }
334
335 // clean up decimal part if we have one
336 if (value.FindFirst(decimalSeparator) != B_ERROR) {
337 // remove trailing zeros
338 while (value[offset] == '0')
339 value.Remove(offset--, 1);
340
341 // no need to keep the period if no digits follow
342 if (value.CharAt(offset) == decimalSeparator)
343 value.Remove(offset, decimalSeparatorWidth);
344 }
345 }
346
347 // set the new value
348 SetExpression(value);
349 }
350
351
352 void
BackSpace()353 ExpressionTextView::BackSpace()
354 {
355 const char bytes[1] = { B_BACKSPACE };
356 KeyDown(bytes, 1);
357
358 fCalcView->FlashKey("BS", 2);
359 }
360
361
362 void
Clear()363 ExpressionTextView::Clear()
364 {
365 SetText("");
366
367 fCalcView->FlashKey("C", 1);
368 }
369
370
371 // #pragma mark -
372
373
374 void
AddExpressionToHistory(const char * expression)375 ExpressionTextView::AddExpressionToHistory(const char* expression)
376 {
377 // clean out old expressions that are the same as
378 // the one to be added
379 int32 count = fPreviousExpressions.CountItems();
380 for (int32 i = 0; i < count; i++) {
381 BString* item = (BString*)fPreviousExpressions.ItemAt(i);
382 if (*item == expression && fPreviousExpressions.RemoveItem(i)) {
383 delete item;
384 i--;
385 count--;
386 }
387 }
388
389 BString* item = new (nothrow) BString(expression);
390 if (!item)
391 return;
392 if (!fPreviousExpressions.AddItem(item)) {
393 delete item;
394 return;
395 }
396 while (fPreviousExpressions.CountItems() > kMaxPreviousExpressions)
397 delete (BString*)fPreviousExpressions.RemoveItem((int32)0);
398
399 fHistoryPos = fPreviousExpressions.CountItems();
400 }
401
402
403 void
PreviousExpression()404 ExpressionTextView::PreviousExpression()
405 {
406 int32 count = fPreviousExpressions.CountItems();
407 if (fHistoryPos == count) {
408 // save current expression
409 fCurrentExpression = Text();
410 }
411
412 fHistoryPos--;
413 if (fHistoryPos < 0) {
414 fHistoryPos = 0;
415 return;
416 }
417
418 BString* item = (BString*)fPreviousExpressions.ItemAt(fHistoryPos);
419 if (item != NULL)
420 SetExpression(item->String());
421 }
422
423
424 void
NextExpression()425 ExpressionTextView::NextExpression()
426 {
427 int32 count = fPreviousExpressions.CountItems();
428
429 fHistoryPos++;
430 if (fHistoryPos == count) {
431 SetExpression(fCurrentExpression.String());
432 return;
433 }
434
435 if (fHistoryPos > count) {
436 fHistoryPos = count;
437 return;
438 }
439
440 BString* item = (BString*)fPreviousExpressions.ItemAt(fHistoryPos);
441 if (item)
442 SetExpression(item->String());
443 }
444
445
446 // #pragma mark -
447
448
449 void
LoadSettings(const BMessage * archive)450 ExpressionTextView::LoadSettings(const BMessage* archive)
451 {
452 const char* oldExpression;
453 for (int32 i = 0;
454 archive->FindString("previous expression", i, &oldExpression) == B_OK;
455 i++) {
456 AddExpressionToHistory(oldExpression);
457 }
458 }
459
460
461 status_t
SaveSettings(BMessage * archive) const462 ExpressionTextView::SaveSettings(BMessage* archive) const
463 {
464 int32 count = fPreviousExpressions.CountItems();
465 for (int32 i = 0; i < count; i++) {
466 BString* item = (BString*)fPreviousExpressions.ItemAtFast(i);
467 status_t ret = archive->AddString("previous expression",
468 item->String());
469 if (ret < B_OK)
470 return ret;
471 }
472 return B_OK;
473 }
474