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 <Window.h> 18 19 #include "CalcView.h" 20 21 22 using std::nothrow; 23 24 static const int32 kMaxPreviousExpressions = 20; 25 26 27 ExpressionTextView::ExpressionTextView(BRect frame, CalcView* calcView) 28 : 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) 39 { 40 SetStylable(false); 41 SetDoesUndo(true); 42 SetColorSpace(B_RGB32); 43 SetFontAndColor(be_bold_font, B_FONT_ALL); 44 } 45 46 47 ExpressionTextView::~ExpressionTextView() 48 { 49 int32 count = fPreviousExpressions.CountItems(); 50 for (int32 i = 0; i < count; i++) 51 delete (BString*)fPreviousExpressions.ItemAtFast(i); 52 } 53 54 55 void 56 ExpressionTextView::MakeFocus(bool focused) 57 { 58 if (focused == IsFocus()) { 59 // stop endless loop when CalcView calls us again 60 return; 61 } 62 63 // NOTE: order of lines important! 64 InputTextView::MakeFocus(focused); 65 fCalcView->MakeFocus(focused); 66 } 67 68 69 void 70 ExpressionTextView::KeyDown(const char* bytes, int32 numBytes) 71 { 72 // Handle expression history 73 if (bytes[0] == B_UP_ARROW) { 74 PreviousExpression(); 75 return; 76 } 77 if (bytes[0] == B_DOWN_ARROW) { 78 NextExpression(); 79 return; 80 } 81 BString current = Text(); 82 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); 88 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); 94 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(); 99 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; 107 } 108 109 110 void 111 ExpressionTextView::MouseDown(BPoint where) 112 { 113 uint32 buttons; 114 Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); 115 if (buttons & B_PRIMARY_MOUSE_BUTTON) { 116 InputTextView::MouseDown(where); 117 return; 118 } 119 where = ConvertToParent(where); 120 fCalcView->MouseDown(where); 121 } 122 123 124 void 125 ExpressionTextView::GetDragParameters(BMessage* dragMessage, 126 BBitmap** bitmap, BPoint* point, BHandler** handler) 127 { 128 InputTextView::GetDragParameters(dragMessage, bitmap, point, handler); 129 dragMessage->AddString("be:clip_name", "DeskCalc clipping"); 130 } 131 132 133 void 134 ExpressionTextView::SetTextRect(BRect rect) 135 { 136 InputTextView::SetTextRect(rect); 137 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); 145 } 146 } 147 148 149 // #pragma mark - 150 151 152 void 153 ExpressionTextView::RevertChanges() 154 { 155 Clear(); 156 } 157 158 159 void 160 ExpressionTextView::ApplyChanges() 161 { 162 AddExpressionToHistory(Text()); 163 fCalcView->FlashKey("=", 1); 164 fCalcView->Evaluate(); 165 fChangesApplied = true; 166 } 167 168 169 // #pragma mark - 170 171 172 void 173 ExpressionTextView::AddKeypadLabel(const char* label) 174 { 175 fKeypadLabels << label; 176 } 177 178 179 void 180 ExpressionTextView::SetExpression(const char* expression) 181 { 182 SetText(expression); 183 int32 lastPos = strlen(expression); 184 Select(lastPos, lastPos); 185 } 186 187 188 void 189 ExpressionTextView::SetValue(BString value) 190 { 191 // save the value 192 fCurrentValue = value; 193 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); 199 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++; 207 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); 224 225 // check for negative exponent value 226 exponent = 0; 227 while (value[firstDigit] == '0') { 228 value.Remove(firstDigit, 1); 229 exponent--; 230 } 231 232 // add the period 233 value.Insert('.', 1, firstDigit + 1); 234 } 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); 247 248 exponent = offset - (firstDigit + 1); 249 } 250 } 251 } 252 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; 262 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); 272 } 273 274 // no need to keep the period if no digits follow 275 if (value[offset] == '.') { 276 value.Remove(offset, 1); 277 offset--; 278 } 279 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; 286 287 digit = (int)(value[offset]) - '0' + 1; // ascii to int + 1 288 if (digit != 10) 289 break; 290 291 value.SetByteAt(offset, '0'); 292 } 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, '.'); 298 } 299 value.Insert('1', 1, firstDigit); 300 301 // remove the exponent value and the last digit 302 offset = value.FindFirst('E'); 303 if (offset == B_ERROR) 304 offset = value.CountChars(); 305 306 value.Truncate(--offset); 307 offset--; // offset now points to the last digit 308 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)); 315 316 // set offset to last digit 317 offset = value.FindFirst('E'); 318 if (offset == B_ERROR) 319 offset = value.CountChars(); 320 321 offset--; 322 } 323 } 324 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); 330 331 // no need to keep the period if no digits follow 332 if (value[offset] == '.') 333 value.Remove(offset, 1); 334 } 335 } 336 337 // set the new value 338 SetExpression(value); 339 } 340 341 342 void 343 ExpressionTextView::BackSpace() 344 { 345 const char bytes[1] = { B_BACKSPACE }; 346 KeyDown(bytes, 1); 347 348 fCalcView->FlashKey("BS", 2); 349 } 350 351 352 void 353 ExpressionTextView::Clear() 354 { 355 SetText(""); 356 357 fCalcView->FlashKey("C", 1); 358 } 359 360 361 // #pragma mark - 362 363 364 void 365 ExpressionTextView::AddExpressionToHistory(const char* expression) 366 { 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--; 376 } 377 } 378 379 BString* item = new (nothrow) BString(expression); 380 if (!item) 381 return; 382 if (!fPreviousExpressions.AddItem(item)) { 383 delete item; 384 return; 385 } 386 while (fPreviousExpressions.CountItems() > kMaxPreviousExpressions) 387 delete (BString*)fPreviousExpressions.RemoveItem((int32)0); 388 389 fHistoryPos = fPreviousExpressions.CountItems(); 390 } 391 392 393 void 394 ExpressionTextView::PreviousExpression() 395 { 396 int32 count = fPreviousExpressions.CountItems(); 397 if (fHistoryPos == count) { 398 // save current expression 399 fCurrentExpression = Text(); 400 } 401 402 fHistoryPos--; 403 if (fHistoryPos < 0) { 404 fHistoryPos = 0; 405 return; 406 } 407 408 BString* item = (BString*)fPreviousExpressions.ItemAt(fHistoryPos); 409 if (item != NULL) 410 SetExpression(item->String()); 411 } 412 413 414 void 415 ExpressionTextView::NextExpression() 416 { 417 int32 count = fPreviousExpressions.CountItems(); 418 419 fHistoryPos++; 420 if (fHistoryPos == count) { 421 SetExpression(fCurrentExpression.String()); 422 return; 423 } 424 425 if (fHistoryPos > count) { 426 fHistoryPos = count; 427 return; 428 } 429 430 BString* item = (BString*)fPreviousExpressions.ItemAt(fHistoryPos); 431 if (item) 432 SetExpression(item->String()); 433 } 434 435 436 // #pragma mark - 437 438 439 void 440 ExpressionTextView::LoadSettings(const BMessage* archive) 441 { 442 const char* oldExpression; 443 for (int32 i = 0; 444 archive->FindString("previous expression", i, &oldExpression) == B_OK; 445 i++) { 446 AddExpressionToHistory(oldExpression); 447 } 448 } 449 450 451 status_t 452 ExpressionTextView::SaveSettings(BMessage* archive) const 453 { 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; 461 } 462 return B_OK; 463 } 464