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