vfs: check userland buffers before reading them.
[haiku.git] / src / apps / deskcalc / ExpressionTextView.cpp
blobf3fa9f79ee9a9a3ced7df9bdf3cc8cc0dcce4e03
1 /*
2 * Copyright 2006-2013 Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Stephan Aßmus, superstippi@gmx.de
7 * John Scipione, jscipione@gmail.com
8 */
11 #include "ExpressionTextView.h"
13 #include <new>
14 #include <stdio.h>
16 #include <Beep.h>
17 #include <Window.h>
19 #include "CalcView.h"
22 using std::nothrow;
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),
32 fCalcView(calcView),
33 fKeypadLabels(""),
34 fPreviousExpressions(20),
35 fHistoryPos(0),
36 fCurrentExpression(""),
37 fCurrentValue(""),
38 fChangesApplied(false)
40 SetStylable(false);
41 SetDoesUndo(true);
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);
55 void
56 ExpressionTextView::MakeFocus(bool focused)
58 if (focused == IsFocus()) {
59 // stop endless loop when CalcView calls us again
60 return;
63 // NOTE: order of lines important!
64 InputTextView::MakeFocus(focused);
65 fCalcView->MakeFocus(focused);
69 void
70 ExpressionTextView::KeyDown(const char* bytes, int32 numBytes)
72 // Handle expression history
73 if (bytes[0] == B_UP_ARROW) {
74 PreviousExpression();
75 return;
77 if (bytes[0] == B_DOWN_ARROW) {
78 NextExpression();
79 return;
81 BString current = Text();
83 // Handle in InputTextView, except B_TAB
84 if (bytes[0] == '=')
85 ApplyChanges();
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
96 // history.
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("");
105 else
106 fChangesApplied = false;
110 void
111 ExpressionTextView::MouseDown(BPoint where)
113 uint32 buttons;
114 Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
115 if (buttons & B_PRIMARY_MOUSE_BUTTON) {
116 InputTextView::MouseDown(where);
117 return;
119 where = ConvertToParent(where);
120 fCalcView->MouseDown(where);
124 void
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");
133 void
134 ExpressionTextView::SetTextRect(BRect rect)
136 InputTextView::SetTextRect(rect);
138 int32 count = fPreviousExpressions.CountItems();
139 if (fHistoryPos == count && fCurrentValue.CountChars() > 0) {
140 int32 start;
141 int32 finish;
142 GetSelection(&start, &finish);
143 SetValue(fCurrentValue.String());
144 Select(start, finish);
149 // #pragma mark -
152 void
153 ExpressionTextView::RevertChanges()
155 Clear();
159 void
160 ExpressionTextView::ApplyChanges()
162 AddExpressionToHistory(Text());
163 fCalcView->FlashKey("=", 1);
164 fCalcView->Evaluate();
165 fChangesApplied = true;
169 // #pragma mark -
172 void
173 ExpressionTextView::AddKeypadLabel(const char* label)
175 fKeypadLabels << label;
179 void
180 ExpressionTextView::SetExpression(const char* expression)
182 SetText(expression);
183 int32 lastPos = strlen(expression);
184 Select(lastPos, lastPos);
188 void
189 ExpressionTextView::SetValue(BString value)
191 // save the value
192 fCurrentValue = value;
194 // calculate the width of the string
195 BFont font;
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;
205 if (value[0] == '-')
206 firstDigit++;
208 // calculate the value of the exponent
209 int32 exponent = 0;
210 int32 offset = value.FindFirst('.');
211 if (offset == B_ERROR) {
212 exponent = value.CountChars() - 1 - firstDigit;
213 value.Insert('.', 1, firstDigit + 1);
214 } else {
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') {
220 exponent = 0;
221 } else {
222 // remove the period
223 value.Remove(offset, 1);
225 // check for negative exponent value
226 exponent = 0;
227 while (value[firstDigit] == '0') {
228 value.Remove(firstDigit, 1);
229 exponent--;
232 // add the period
233 value.Insert('.', 1, firstDigit + 1);
235 } else {
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)
242 exponent = 0;
243 else {
244 // move the period
245 value.Remove(offset, 1);
246 value.Insert('.', 1, firstDigit + 1);
248 exponent = offset - (firstDigit + 1);
253 if (exponent != 0) {
254 value.Truncate(40);
255 // truncate to a reasonable precision
256 // while ensuring result will be rounded
257 offset = value.CountChars() - 1;
258 value << "E" << exponent;
259 // add the exponent
260 } else
261 offset = value.CountChars() - 1;
263 // reduce the number of digits until the string fits or can not be
264 // made any shorter
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);
277 offset--;
280 // take care of proper rounding of the result
281 int digit = (int)lastRemovedDigit - '0'; // ascii to int
282 if (digit >= 5) {
283 for (; offset >= firstDigit; offset--) {
284 if (value[offset] == '.')
285 continue;
287 digit = (int)(value[offset]) - '0' + 1; // ascii to int + 1
288 if (digit != 10)
289 break;
291 value.SetByteAt(offset, '0');
293 if (digit == 10) {
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
310 exponent++;
311 value << 'E' << exponent;
312 } else {
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();
321 offset--;
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);
337 // set the new value
338 SetExpression(value);
342 void
343 ExpressionTextView::BackSpace()
345 const char bytes[1] = { B_BACKSPACE };
346 KeyDown(bytes, 1);
348 fCalcView->FlashKey("BS", 2);
352 void
353 ExpressionTextView::Clear()
355 SetText("");
357 fCalcView->FlashKey("C", 1);
361 // #pragma mark -
364 void
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)) {
373 delete item;
374 i--;
375 count--;
379 BString* item = new (nothrow) BString(expression);
380 if (!item)
381 return;
382 if (!fPreviousExpressions.AddItem(item)) {
383 delete item;
384 return;
386 while (fPreviousExpressions.CountItems() > kMaxPreviousExpressions)
387 delete (BString*)fPreviousExpressions.RemoveItem((int32)0);
389 fHistoryPos = fPreviousExpressions.CountItems();
393 void
394 ExpressionTextView::PreviousExpression()
396 int32 count = fPreviousExpressions.CountItems();
397 if (fHistoryPos == count) {
398 // save current expression
399 fCurrentExpression = Text();
402 fHistoryPos--;
403 if (fHistoryPos < 0) {
404 fHistoryPos = 0;
405 return;
408 BString* item = (BString*)fPreviousExpressions.ItemAt(fHistoryPos);
409 if (item != NULL)
410 SetExpression(item->String());
414 void
415 ExpressionTextView::NextExpression()
417 int32 count = fPreviousExpressions.CountItems();
419 fHistoryPos++;
420 if (fHistoryPos == count) {
421 SetExpression(fCurrentExpression.String());
422 return;
425 if (fHistoryPos > count) {
426 fHistoryPos = count;
427 return;
430 BString* item = (BString*)fPreviousExpressions.ItemAt(fHistoryPos);
431 if (item)
432 SetExpression(item->String());
436 // #pragma mark -
439 void
440 ExpressionTextView::LoadSettings(const BMessage* archive)
442 const char* oldExpression;
443 for (int32 i = 0;
444 archive->FindString("previous expression", i, &oldExpression) == B_OK;
445 i++) {
446 AddExpressionToHistory(oldExpression);
451 status_t
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",
458 item->String());
459 if (ret < B_OK)
460 return ret;
462 return B_OK;