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