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