1 /* 2 * Copyright 2006-2013, Haiku, Inc. All rights reserved. 3 * Copyright 1997, 1998 R3 Software Ltd. All Rights Reserved. 4 * Distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * Stephan Aßmus, superstippi@gmx.de 8 * Philippe Saint-Pierre, stpere@gmail.com 9 * John Scipione, jscipione@gmail.com 10 * Timothy Wayper, timmy@wunderbear.com 11 */ 12 13 14 #include "CalcView.h" 15 16 #include <stdlib.h> 17 #include <stdio.h> 18 #include <string.h> 19 #include <ctype.h> 20 #include <assert.h> 21 22 #include <AboutWindow.h> 23 #include <Alert.h> 24 #include <Application.h> 25 #include <AppFileInfo.h> 26 #include <Beep.h> 27 #include <Bitmap.h> 28 #include <Catalog.h> 29 #include <ControlLook.h> 30 #include <Clipboard.h> 31 #include <File.h> 32 #include <Font.h> 33 #include <MenuItem.h> 34 #include <Message.h> 35 #include <MessageRunner.h> 36 #include <PlaySound.h> 37 #include <Point.h> 38 #include <PopUpMenu.h> 39 #include <Region.h> 40 #include <Roster.h> 41 42 #include <ExpressionParser.h> 43 44 #include "CalcApplication.h" 45 #include "CalcOptions.h" 46 #include "ExpressionTextView.h" 47 48 49 #undef B_TRANSLATION_CONTEXT 50 #define B_TRANSLATION_CONTEXT "CalcView" 51 52 53 static const int32 kMsgCalculating = 'calc'; 54 static const int32 kMsgAnimateDots = 'dots'; 55 static const int32 kMsgDoneEvaluating = 'done'; 56 57 //const uint8 K_COLOR_OFFSET = 32; 58 const float kFontScaleY = 0.4f; 59 const float kFontScaleX = 0.4f; 60 const float kExpressionFontScaleY = 0.6f; 61 const float kDisplayScaleY = 0.2f; 62 63 static const bigtime_t kFlashOnOffInterval = 100000; 64 static const bigtime_t kCalculatingInterval = 1000000; 65 static const bigtime_t kAnimationInterval = 333333; 66 67 static const float kMinimumWidthCompact = 130.0f; 68 static const float kMaximumWidthCompact = 400.0f; 69 static const float kMinimumHeightCompact = 20.0f; 70 static const float kMaximumHeightCompact = 60.0f; 71 72 // Basic mode size limits are defined in CalcView.h so 73 // that they can be used by the CalcWindow constructor. 74 75 static const float kMinimumWidthScientific = 240.0f; 76 static const float kMaximumWidthScientific = 400.0f; 77 static const float kMinimumHeightScientific = 200.0f; 78 static const float kMaximumHeightScientific = 400.0f; 79 80 // basic mode keypad layout (default) 81 const char *kKeypadDescriptionBasic = 82 "7 8 9 ( ) \n" 83 "4 5 6 * / \n" 84 "1 2 3 + - \n" 85 "0 . BS = C \n"; 86 87 // scientific mode keypad layout 88 const char *kKeypadDescriptionScientific = 89 "ln sin cos tan π \n" 90 "log asin acos atan sqrt \n" 91 "exp sinh cosh tanh cbrt \n" 92 "! ceil floor E ^ \n" 93 "7 8 9 ( ) \n" 94 "4 5 6 * / \n" 95 "1 2 3 + - \n" 96 "0 . BS = C \n"; 97 98 99 enum { 100 FLAGS_FLASH_KEY = 1 << 0, 101 FLAGS_MOUSE_DOWN = 1 << 1 102 }; 103 104 105 struct CalcView::CalcKey { 106 char label[8]; 107 char code[8]; 108 char keymap[4]; 109 uint32 flags; 110 // float width; 111 }; 112 113 114 CalcView* 115 CalcView::Instantiate(BMessage* archive) 116 { 117 if (!validate_instantiation(archive, "CalcView")) 118 return NULL; 119 120 return new CalcView(archive); 121 } 122 123 124 CalcView::CalcView(BRect frame, rgb_color rgbBaseColor, BMessage* settings) 125 : 126 BView(frame, "DeskCalc", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS), 127 fColumns(5), 128 fRows(4), 129 130 fBaseColor(rgbBaseColor), 131 fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }), 132 133 fWidth(1), 134 fHeight(1), 135 136 fKeypadDescription(strdup(kKeypadDescriptionBasic)), 137 fKeypad(NULL), 138 139 #ifdef __HAIKU__ 140 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)), 141 #else 142 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_CMAP8)), 143 #endif 144 145 fPopUpMenu(NULL), 146 fAutoNumlockItem(NULL), 147 fAudioFeedbackItem(NULL), 148 fOptions(new CalcOptions()), 149 fEvaluateThread(-1), 150 fEvaluateMessageRunner(NULL), 151 fEvaluateSemaphore(B_BAD_SEM_ID), 152 fEnabled(true) 153 { 154 // tell the app server not to erase our b/g 155 SetViewColor(B_TRANSPARENT_32_BIT); 156 157 _Init(settings); 158 } 159 160 161 CalcView::CalcView(BMessage* archive) 162 : 163 BView(archive), 164 fColumns(5), 165 fRows(4), 166 167 fBaseColor((rgb_color){ 128, 128, 128, 255 }), 168 fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }), 169 170 fWidth(1), 171 fHeight(1), 172 173 fKeypadDescription(strdup(kKeypadDescriptionBasic)), 174 fKeypad(NULL), 175 176 #ifdef __HAIKU__ 177 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)), 178 #else 179 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_CMAP8)), 180 #endif 181 182 fPopUpMenu(NULL), 183 fAutoNumlockItem(NULL), 184 fAudioFeedbackItem(NULL), 185 fOptions(new CalcOptions()), 186 fEvaluateThread(-1), 187 fEvaluateMessageRunner(NULL), 188 fEvaluateSemaphore(B_BAD_SEM_ID), 189 fEnabled(true) 190 { 191 // Do not restore the follow mode, in shelfs, we never follow. 192 SetResizingMode(B_FOLLOW_NONE); 193 194 _Init(archive); 195 } 196 197 198 CalcView::~CalcView() 199 { 200 delete fKeypad; 201 delete fOptions; 202 free(fKeypadDescription); 203 delete fEvaluateMessageRunner; 204 delete_sem(fEvaluateSemaphore); 205 } 206 207 208 void 209 CalcView::AttachedToWindow() 210 { 211 if (be_control_look == NULL) 212 SetFont(be_bold_font); 213 214 BRect frame(Frame()); 215 FrameResized(frame.Width(), frame.Height()); 216 217 bool addKeypadModeMenuItems = true; 218 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) { 219 // don't add these items if we are a replicant on the desktop 220 addKeypadModeMenuItems = false; 221 } 222 223 // create and attach the pop-up menu 224 _CreatePopUpMenu(addKeypadModeMenuItems); 225 226 if (addKeypadModeMenuItems) 227 SetKeypadMode(fOptions->keypad_mode); 228 } 229 230 231 void 232 CalcView::MessageReceived(BMessage* message) 233 { 234 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) { 235 // if we are embedded in desktop we need to receive these 236 // message here since we don't have a parent BWindow 237 switch (message->what) { 238 case MSG_OPTIONS_AUTO_NUM_LOCK: 239 ToggleAutoNumlock(); 240 return; 241 242 case MSG_OPTIONS_AUDIO_FEEDBACK: 243 ToggleAudioFeedback(); 244 return; 245 246 case MSG_OPTIONS_ANGLE_MODE_RADIAN: 247 SetDegreeMode(false); 248 return; 249 250 case MSG_OPTIONS_ANGLE_MODE_DEGREE: 251 SetDegreeMode(true); 252 return; 253 } 254 } 255 256 // check if message was dropped 257 if (message->WasDropped()) { 258 // pass message on to paste 259 if (message->IsSourceRemote()) 260 Paste(message); 261 } else { 262 // act on posted message type 263 switch (message->what) { 264 265 // handle "cut" 266 case B_CUT: 267 Cut(); 268 break; 269 270 // handle copy 271 case B_COPY: 272 Copy(); 273 break; 274 275 // handle paste 276 case B_PASTE: 277 // access system clipboard 278 if (be_clipboard->Lock()) { 279 BMessage* clipper = be_clipboard->Data(); 280 //clipper->PrintToStream(); 281 Paste(clipper); 282 be_clipboard->Unlock(); 283 } 284 break; 285 286 // (replicant) about box requested 287 case B_ABOUT_REQUESTED: 288 { 289 BAboutWindow* window = new BAboutWindow(kAppName, kSignature); 290 291 // create the about window 292 const char* extraCopyrights[] = { 293 "1997, 1998 R3 Software Ltd.", 294 NULL 295 }; 296 297 const char* authors[] = { 298 "Stephan Aßmus", 299 "John Scipione", 300 "Timothy Wayper", 301 "Ingo Weinhold", 302 NULL 303 }; 304 305 window->AddCopyright(2006, "Haiku, Inc.", extraCopyrights); 306 window->AddAuthors(authors); 307 308 window->Show(); 309 310 break; 311 } 312 313 case MSG_UNFLASH_KEY: 314 { 315 int32 key; 316 if (message->FindInt32("key", &key) == B_OK) 317 _FlashKey(key, 0); 318 319 break; 320 } 321 322 case kMsgAnimateDots: 323 { 324 int32 end = fExpressionTextView->TextLength(); 325 int32 start = end - 3; 326 if (fEnabled || strcmp(fExpressionTextView->Text() + start, "...") != 0) { 327 // stop the message runner 328 delete fEvaluateMessageRunner; 329 fEvaluateMessageRunner = NULL; 330 break; 331 } 332 333 uint8 dot = 0; 334 if (message->FindUInt8("dot", &dot) == B_OK) { 335 rgb_color fontColor = fExpressionTextView->HighColor(); 336 rgb_color backColor = fExpressionTextView->LowColor(); 337 fExpressionTextView->SetStylable(true); 338 fExpressionTextView->SetFontAndColor(start, end, NULL, 0, &backColor); 339 fExpressionTextView->SetFontAndColor(start + dot - 1, start + dot, NULL, 340 0, &fontColor); 341 fExpressionTextView->SetStylable(false); 342 } 343 344 dot++; 345 if (dot == 4) 346 dot = 1; 347 348 delete fEvaluateMessageRunner; 349 BMessage animate(kMsgAnimateDots); 350 animate.AddUInt8("dot", dot); 351 fEvaluateMessageRunner = new (std::nothrow) BMessageRunner( 352 BMessenger(this), &animate, kAnimationInterval, 1); 353 break; 354 } 355 356 case kMsgCalculating: 357 { 358 // calculation has taken more than 3 seconds 359 if (fEnabled) { 360 // stop the message runner 361 delete fEvaluateMessageRunner; 362 fEvaluateMessageRunner = NULL; 363 break; 364 } 365 366 BString calculating; 367 calculating << B_TRANSLATE("Calculating") << "..."; 368 fExpressionTextView->SetText(calculating.String()); 369 370 delete fEvaluateMessageRunner; 371 BMessage animate(kMsgAnimateDots); 372 animate.AddUInt8("dot", 1U); 373 fEvaluateMessageRunner = new (std::nothrow) BMessageRunner( 374 BMessenger(this), &animate, kAnimationInterval, 1); 375 break; 376 } 377 378 case kMsgDoneEvaluating: 379 { 380 _SetEnabled(true); 381 rgb_color fontColor = fExpressionTextView->HighColor(); 382 fExpressionTextView->SetFontAndColor(NULL, 0, &fontColor); 383 384 const char* result; 385 if (message->FindString("error", &result) == B_OK) 386 fExpressionTextView->SetText(result); 387 else if (message->FindString("value", &result) == B_OK) 388 fExpressionTextView->SetValue(result); 389 390 // stop the message runner 391 delete fEvaluateMessageRunner; 392 fEvaluateMessageRunner = NULL; 393 break; 394 } 395 396 default: 397 BView::MessageReceived(message); 398 break; 399 } 400 } 401 } 402 403 404 void 405 CalcView::Draw(BRect updateRect) 406 { 407 bool drawBackground = !_IsEmbedded(); 408 409 SetHighColor(fBaseColor); 410 BRect expressionRect(_ExpressionRect()); 411 if (updateRect.Intersects(expressionRect)) { 412 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT 413 && expressionRect.Height() >= fCalcIcon->Bounds().Height()) { 414 // render calc icon 415 expressionRect.left = fExpressionTextView->Frame().right + 2; 416 if (drawBackground) { 417 SetHighColor(fBaseColor); 418 FillRect(updateRect & expressionRect); 419 } 420 421 if (fCalcIcon->ColorSpace() == B_RGBA32) { 422 SetDrawingMode(B_OP_ALPHA); 423 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 424 } else { 425 SetDrawingMode(B_OP_OVER); 426 } 427 428 BPoint iconPos; 429 iconPos.x = expressionRect.right - (expressionRect.Width() 430 + fCalcIcon->Bounds().Width()) / 2.0; 431 iconPos.y = expressionRect.top + (expressionRect.Height() 432 - fCalcIcon->Bounds().Height()) / 2.0; 433 DrawBitmap(fCalcIcon, iconPos); 434 435 SetDrawingMode(B_OP_COPY); 436 } 437 438 // render border around expression text view 439 expressionRect = fExpressionTextView->Frame(); 440 expressionRect.InsetBy(-2, -2); 441 if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT && drawBackground) { 442 expressionRect.InsetBy(-2, -2); 443 StrokeRect(expressionRect); 444 expressionRect.InsetBy(1, 1); 445 StrokeRect(expressionRect); 446 expressionRect.InsetBy(1, 1); 447 } 448 449 uint32 flags = 0; 450 if (!drawBackground) 451 flags |= BControlLook::B_BLEND_FRAME; 452 be_control_look->DrawTextControlBorder(this, expressionRect, 453 updateRect, fBaseColor, flags); 454 } 455 456 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) 457 return; 458 459 // calculate grid sizes 460 BRect keypadRect(_KeypadRect()); 461 462 if (be_control_look != NULL) { 463 if (drawBackground) 464 StrokeRect(keypadRect); 465 keypadRect.InsetBy(1, 1); 466 } 467 468 float sizeDisp = keypadRect.top; 469 float sizeCol = (keypadRect.Width() + 1) / (float)fColumns; 470 float sizeRow = (keypadRect.Height() + 1) / (float)fRows; 471 472 if (!updateRect.Intersects(keypadRect)) 473 return; 474 475 SetFontSize(min_c(sizeRow * kFontScaleY, sizeCol * kFontScaleX)); 476 477 CalcKey* key = fKeypad; 478 for (int row = 0; row < fRows; row++) { 479 for (int col = 0; col < fColumns; col++) { 480 BRect frame; 481 frame.left = keypadRect.left + col * sizeCol; 482 frame.right = keypadRect.left + (col + 1) * sizeCol - 1; 483 frame.top = sizeDisp + row * sizeRow; 484 frame.bottom = sizeDisp + (row + 1) * sizeRow - 1; 485 486 if (drawBackground) { 487 SetHighColor(fBaseColor); 488 StrokeRect(frame); 489 } 490 frame.InsetBy(1, 1); 491 492 uint32 flags = 0; 493 if (!drawBackground) 494 flags |= BControlLook::B_BLEND_FRAME; 495 if (key->flags != 0) 496 flags |= BControlLook::B_ACTIVATED; 497 flags |= BControlLook::B_IGNORE_OUTLINE; 498 499 be_control_look->DrawButtonFrame(this, frame, updateRect, 500 fBaseColor, fBaseColor, flags); 501 502 be_control_look->DrawButtonBackground(this, frame, updateRect, 503 fBaseColor, flags); 504 505 be_control_look->DrawLabel(this, key->label, frame, updateRect, 506 fBaseColor, flags, BAlignment(B_ALIGN_HORIZONTAL_CENTER, 507 B_ALIGN_VERTICAL_CENTER)); 508 509 key++; 510 } 511 } 512 } 513 514 515 void 516 CalcView::MouseDown(BPoint point) 517 { 518 // ensure this view is the current focus 519 if (!fExpressionTextView->IsFocus()) { 520 // Call our version of MakeFocus(), since that will also apply the 521 // num_lock setting. 522 MakeFocus(); 523 } 524 525 // read mouse buttons state 526 int32 buttons = 0; 527 Window()->CurrentMessage()->FindInt32("buttons", &buttons); 528 529 if ((B_PRIMARY_MOUSE_BUTTON & buttons) == 0) { 530 // display popup menu if not primary mouse button 531 BMenuItem* selected; 532 if ((selected = fPopUpMenu->Go(ConvertToScreen(point))) != NULL 533 && selected->Message() != NULL) { 534 Window()->PostMessage(selected->Message(), this); 535 } 536 return; 537 } 538 539 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) { 540 if (fCalcIcon != NULL) { 541 BRect bounds(Bounds()); 542 bounds.left = bounds.right - fCalcIcon->Bounds().Width(); 543 if (bounds.Contains(point)) { 544 // user clicked on calculator icon 545 fExpressionTextView->Clear(); 546 } 547 } 548 return; 549 } 550 551 // calculate grid sizes 552 float sizeDisp = fHeight * kDisplayScaleY; 553 float sizeCol = fWidth / (float)fColumns; 554 float sizeRow = (fHeight - sizeDisp) / (float)fRows; 555 556 // calculate location within grid 557 int gridCol = (int)floorf(point.x / sizeCol); 558 int gridRow = (int)floorf((point.y - sizeDisp) / sizeRow); 559 560 // check limits 561 if ((gridCol >= 0) && (gridCol < fColumns) 562 && (gridRow >= 0) && (gridRow < fRows)) { 563 564 // process key press 565 int key = gridRow * fColumns + gridCol; 566 _FlashKey(key, FLAGS_MOUSE_DOWN); 567 _PressKey(key); 568 569 // make sure we receive the mouse up! 570 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 571 } 572 } 573 574 575 void 576 CalcView::MouseUp(BPoint point) 577 { 578 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) 579 return; 580 581 int keys = fRows * fColumns; 582 for (int i = 0; i < keys; i++) { 583 if (fKeypad[i].flags & FLAGS_MOUSE_DOWN) { 584 _FlashKey(i, 0); 585 break; 586 } 587 } 588 } 589 590 591 void 592 CalcView::KeyDown(const char* bytes, int32 numBytes) 593 { 594 // if single byte character... 595 if (numBytes == 1) { 596 597 //printf("Key pressed: %c\n", bytes[0]); 598 599 switch (bytes[0]) { 600 601 case B_ENTER: 602 // translate to evaluate key 603 _PressKey("="); 604 break; 605 606 case B_LEFT_ARROW: 607 case B_BACKSPACE: 608 // translate to backspace key 609 _PressKey("BS"); 610 break; 611 612 case B_SPACE: 613 case B_ESCAPE: 614 case 'c': 615 // translate to clear key 616 _PressKey("C"); 617 break; 618 619 // bracket translation 620 case '[': 621 case '{': 622 _PressKey("("); 623 break; 624 625 case ']': 626 case '}': 627 _PressKey(")"); 628 break; 629 630 default: { 631 // scan the keymap array for match 632 int keys = fRows * fColumns; 633 for (int i = 0; i < keys; i++) { 634 if (fKeypad[i].keymap[0] == bytes[0]) { 635 _PressKey(i); 636 return; 637 } 638 } 639 break; 640 } 641 } 642 } 643 } 644 645 646 void 647 CalcView::MakeFocus(bool focused) 648 { 649 if (focused) { 650 // set num lock 651 if (fOptions->auto_num_lock) { 652 set_keyboard_locks(B_NUM_LOCK 653 | (modifiers() & (B_CAPS_LOCK | B_SCROLL_LOCK))); 654 } 655 } 656 657 // pass on request to text view 658 fExpressionTextView->MakeFocus(focused); 659 } 660 661 662 void 663 CalcView::FrameResized(float width, float height) 664 { 665 fWidth = width; 666 fHeight = height; 667 668 // layout expression text view 669 BRect expressionRect = _ExpressionRect(); 670 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) { 671 expressionRect.InsetBy(2, 2); 672 expressionRect.right -= ceilf(fCalcIcon->Bounds().Width() * 1.5); 673 } else 674 expressionRect.InsetBy(4, 4); 675 676 fExpressionTextView->MoveTo(expressionRect.LeftTop()); 677 fExpressionTextView->ResizeTo(expressionRect.Width(), expressionRect.Height()); 678 679 // configure expression text view font size and color 680 float sizeDisp = fOptions->keypad_mode == KEYPAD_MODE_COMPACT 681 ? fHeight : fHeight * kDisplayScaleY; 682 BFont font(be_bold_font); 683 font.SetSize(sizeDisp * kExpressionFontScaleY); 684 rgb_color fontColor = fExpressionTextView->HighColor(); 685 fExpressionTextView->SetFontAndColor(&font, B_FONT_ALL, &fontColor); 686 687 expressionRect.OffsetTo(B_ORIGIN); 688 float inset = (expressionRect.Height() - fExpressionTextView->LineHeight(0)) / 2; 689 expressionRect.InsetBy(0, inset); 690 fExpressionTextView->SetTextRect(expressionRect); 691 Invalidate(); 692 } 693 694 695 status_t 696 CalcView::Archive(BMessage* archive, bool deep) const 697 { 698 fExpressionTextView->RemoveSelf(); 699 700 // passed on request to parent 701 status_t ret = BView::Archive(archive, deep); 702 703 const_cast<CalcView*>(this)->AddChild(fExpressionTextView); 704 705 // save app signature for replicant add-on loading 706 if (ret == B_OK) 707 ret = archive->AddString("add_on", kSignature); 708 709 // save all the options 710 if (ret == B_OK) 711 ret = SaveSettings(archive); 712 713 // add class info last 714 if (ret == B_OK) 715 ret = archive->AddString("class", "CalcView"); 716 717 return ret; 718 } 719 720 721 void 722 CalcView::Cut() 723 { 724 Copy(); // copy data to clipboard 725 fExpressionTextView->Clear(); // remove data 726 } 727 728 729 void 730 CalcView::Copy() 731 { 732 // access system clipboard 733 if (be_clipboard->Lock()) { 734 be_clipboard->Clear(); 735 BMessage* clipper = be_clipboard->Data(); 736 clipper->what = B_MIME_DATA; 737 // TODO: should check return for errors! 738 BString expression = fExpressionTextView->Text(); 739 clipper->AddData("text/plain", B_MIME_TYPE, 740 expression.String(), expression.Length()); 741 //clipper->PrintToStream(); 742 be_clipboard->Commit(); 743 be_clipboard->Unlock(); 744 } 745 } 746 747 748 void 749 CalcView::Paste(BMessage* message) 750 { 751 // handle color drops first 752 // read incoming color 753 const rgb_color* dropColor = NULL; 754 ssize_t dataSize; 755 if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 756 (const void**)&dropColor, &dataSize) == B_OK 757 && dataSize == sizeof(rgb_color)) { 758 759 // calculate view relative drop point 760 BPoint dropPoint = ConvertFromScreen(message->DropPoint()); 761 762 // calculate current keypad area 763 float sizeDisp = fHeight * kDisplayScaleY; 764 BRect keypadRect(0.0, sizeDisp, fWidth, fHeight); 765 766 // check location of color drop 767 if (keypadRect.Contains(dropPoint) && dropColor) { 768 fBaseColor = *dropColor; 769 _Colorize(); 770 // redraw 771 Invalidate(); 772 } 773 774 } else { 775 // look for text/plain MIME data 776 const char* text; 777 ssize_t numBytes; 778 if (message->FindData("text/plain", B_MIME_TYPE, 779 (const void**)&text, &numBytes) == B_OK) { 780 BString temp; 781 temp.Append(text, numBytes); 782 fExpressionTextView->Insert(temp.String()); 783 } 784 } 785 } 786 787 788 status_t 789 CalcView::SaveSettings(BMessage* archive) const 790 { 791 status_t ret = archive ? B_OK : B_BAD_VALUE; 792 793 // record grid dimensions 794 if (ret == B_OK) 795 ret = archive->AddInt16("cols", fColumns); 796 if (ret == B_OK) 797 ret = archive->AddInt16("rows", fRows); 798 799 // record color scheme 800 if (ret == B_OK) 801 ret = archive->AddData("rgbBaseColor", B_RGB_COLOR_TYPE, 802 &fBaseColor, sizeof(rgb_color)); 803 if (ret == B_OK) 804 ret = archive->AddData("rgbDisplay", B_RGB_COLOR_TYPE, 805 &fExpressionBGColor, sizeof(rgb_color)); 806 807 // record current options 808 if (ret == B_OK) 809 ret = fOptions->SaveSettings(archive); 810 811 // record display text 812 if (ret == B_OK) 813 ret = archive->AddString("displayText", fExpressionTextView->Text()); 814 815 // record expression history 816 if (ret == B_OK) 817 ret = fExpressionTextView->SaveSettings(archive); 818 819 // record calculator description 820 if (ret == B_OK) 821 ret = archive->AddString("calcDesc", fKeypadDescription); 822 823 return ret; 824 } 825 826 827 void 828 CalcView::Evaluate() 829 { 830 if (fExpressionTextView->TextLength() == 0) { 831 beep(); 832 return; 833 } 834 835 fEvaluateThread = spawn_thread(_EvaluateThread, "Evaluate Thread", 836 B_LOW_PRIORITY, this); 837 if (fEvaluateThread < B_OK) { 838 // failed to create evaluate thread, error out 839 fExpressionTextView->SetText(strerror(fEvaluateThread)); 840 return; 841 } 842 843 _AudioFeedback(false); 844 _SetEnabled(false); 845 // Disable input while we evaluate 846 847 status_t threadStatus = resume_thread(fEvaluateThread); 848 if (threadStatus != B_OK) { 849 // evaluate thread failed to start, error out 850 fExpressionTextView->SetText(strerror(threadStatus)); 851 _SetEnabled(true); 852 return; 853 } 854 855 if (fEvaluateMessageRunner == NULL) { 856 BMessage message(kMsgCalculating); 857 fEvaluateMessageRunner = new (std::nothrow) BMessageRunner( 858 BMessenger(this), &message, kCalculatingInterval, 1); 859 status_t runnerStatus = fEvaluateMessageRunner->InitCheck(); 860 if (runnerStatus != B_OK) 861 printf("Evaluate Message Runner: %s\n", strerror(runnerStatus)); 862 } 863 } 864 865 866 void 867 CalcView::FlashKey(const char* bytes, int32 numBytes) 868 { 869 BString temp; 870 temp.Append(bytes, numBytes); 871 int32 key = _KeyForLabel(temp.String()); 872 if (key >= 0) 873 _FlashKey(key, FLAGS_FLASH_KEY); 874 } 875 876 877 void 878 CalcView::ToggleAutoNumlock(void) 879 { 880 fOptions->auto_num_lock = !fOptions->auto_num_lock; 881 fAutoNumlockItem->SetMarked(fOptions->auto_num_lock); 882 } 883 884 885 void 886 CalcView::ToggleAudioFeedback(void) 887 { 888 fOptions->audio_feedback = !fOptions->audio_feedback; 889 fAudioFeedbackItem->SetMarked(fOptions->audio_feedback); 890 } 891 892 893 void 894 CalcView::SetDegreeMode(bool degrees) 895 { 896 fOptions->degree_mode = degrees; 897 fAngleModeRadianItem->SetMarked(!degrees); 898 fAngleModeDegreeItem->SetMarked(degrees); 899 } 900 901 902 void 903 CalcView::SetKeypadMode(uint8 mode) 904 { 905 if (_IsEmbedded()) 906 return; 907 908 BWindow* window = Window(); 909 if (window == NULL) 910 return; 911 912 if (fOptions->keypad_mode == mode) 913 return; 914 915 fOptions->keypad_mode = mode; 916 _MarkKeypadItems(fOptions->keypad_mode); 917 918 float width = fWidth; 919 float height = fHeight; 920 921 switch (fOptions->keypad_mode) { 922 case KEYPAD_MODE_COMPACT: 923 { 924 if (window->Bounds() == Frame()) { 925 window->SetSizeLimits(kMinimumWidthCompact, 926 kMaximumWidthCompact, kMinimumHeightCompact, 927 kMaximumHeightCompact); 928 window->ResizeTo(width, height * kDisplayScaleY); 929 } else 930 ResizeTo(width, height * kDisplayScaleY); 931 932 break; 933 } 934 935 case KEYPAD_MODE_SCIENTIFIC: 936 { 937 free(fKeypadDescription); 938 fKeypadDescription = strdup(kKeypadDescriptionScientific); 939 fRows = 8; 940 _ParseCalcDesc(fKeypadDescription); 941 942 window->SetSizeLimits(kMinimumWidthScientific, 943 kMaximumWidthScientific, kMinimumHeightScientific, 944 kMaximumHeightScientific); 945 946 if (width < kMinimumWidthScientific) 947 width = kMinimumWidthScientific; 948 else if (width > kMaximumWidthScientific) 949 width = kMaximumWidthScientific; 950 951 if (height < kMinimumHeightScientific) 952 height = kMinimumHeightScientific; 953 else if (height > kMaximumHeightScientific) 954 height = kMaximumHeightScientific; 955 956 if (width != fWidth || height != fHeight) 957 ResizeTo(width, height); 958 else 959 Invalidate(); 960 961 break; 962 } 963 964 case KEYPAD_MODE_BASIC: 965 default: 966 { 967 free(fKeypadDescription); 968 fKeypadDescription = strdup(kKeypadDescriptionBasic); 969 fRows = 4; 970 _ParseCalcDesc(fKeypadDescription); 971 972 window->SetSizeLimits(kMinimumWidthBasic, kMaximumWidthBasic, 973 kMinimumHeightBasic, kMaximumHeightBasic); 974 975 if (width < kMinimumWidthBasic) 976 width = kMinimumWidthBasic; 977 else if (width > kMaximumWidthBasic) 978 width = kMaximumWidthBasic; 979 980 if (height < kMinimumHeightBasic) 981 height = kMinimumHeightBasic; 982 else if (height > kMaximumHeightBasic) 983 height = kMaximumHeightBasic; 984 985 if (width != fWidth || height != fHeight) 986 ResizeTo(width, height); 987 else 988 Invalidate(); 989 } 990 } 991 } 992 993 994 // #pragma mark - 995 996 997 /*static*/ status_t 998 CalcView::_EvaluateThread(void* data) 999 { 1000 CalcView* calcView = reinterpret_cast<CalcView*>(data); 1001 if (calcView == NULL) 1002 return B_BAD_TYPE; 1003 1004 BMessenger messenger(calcView); 1005 if (!messenger.IsValid()) 1006 return B_BAD_VALUE; 1007 1008 BString result; 1009 status_t status = acquire_sem(calcView->fEvaluateSemaphore); 1010 if (status == B_OK) { 1011 ExpressionParser parser; 1012 parser.SetDegreeMode(calcView->fOptions->degree_mode); 1013 BString expression(calcView->fExpressionTextView->Text()); 1014 try { 1015 result = parser.Evaluate(expression.String()); 1016 } catch (ParseException e) { 1017 result << e.message.String() << " at " << (e.position + 1); 1018 status = B_ERROR; 1019 } 1020 release_sem(calcView->fEvaluateSemaphore); 1021 } else 1022 result = strerror(status); 1023 1024 BMessage message(kMsgDoneEvaluating); 1025 message.AddString(status == B_OK ? "value" : "error", result.String()); 1026 messenger.SendMessage(&message); 1027 1028 return status; 1029 } 1030 1031 1032 void 1033 CalcView::_Init(BMessage* settings) 1034 { 1035 // create expression text view 1036 fExpressionTextView = new ExpressionTextView(_ExpressionRect(), this); 1037 AddChild(fExpressionTextView); 1038 1039 // read data from archive 1040 _LoadSettings(settings); 1041 1042 // fetch the calc icon for compact view 1043 _FetchAppIcon(fCalcIcon); 1044 1045 fEvaluateSemaphore = create_sem(1, "Evaluate Semaphore"); 1046 } 1047 1048 1049 status_t 1050 CalcView::_LoadSettings(BMessage* archive) 1051 { 1052 if (!archive) 1053 return B_BAD_VALUE; 1054 1055 // record calculator description 1056 const char* calcDesc; 1057 if (archive->FindString("calcDesc", &calcDesc) < B_OK) 1058 calcDesc = kKeypadDescriptionBasic; 1059 1060 // save calculator description for reference 1061 free(fKeypadDescription); 1062 fKeypadDescription = strdup(calcDesc); 1063 1064 // read grid dimensions 1065 if (archive->FindInt16("cols", &fColumns) < B_OK) 1066 fColumns = 5; 1067 if (archive->FindInt16("rows", &fRows) < B_OK) 1068 fRows = 4; 1069 1070 // read color scheme 1071 const rgb_color* color; 1072 ssize_t size; 1073 if (archive->FindData("rgbBaseColor", B_RGB_COLOR_TYPE, 1074 (const void**)&color, &size) < B_OK 1075 || size != sizeof(rgb_color)) { 1076 fBaseColor = ui_color(B_PANEL_BACKGROUND_COLOR); 1077 puts("Missing rgbBaseColor from CalcView archive!\n"); 1078 } else { 1079 fBaseColor = *color; 1080 } 1081 1082 if (archive->FindData("rgbDisplay", B_RGB_COLOR_TYPE, 1083 (const void**)&color, &size) < B_OK 1084 || size != sizeof(rgb_color)) { 1085 fExpressionBGColor = (rgb_color){ 0, 0, 0, 255 }; 1086 puts("Missing rgbBaseColor from CalcView archive!\n"); 1087 } else { 1088 fExpressionBGColor = *color; 1089 } 1090 1091 // load options 1092 fOptions->LoadSettings(archive); 1093 1094 // load display text 1095 const char* display; 1096 if (archive->FindString("displayText", &display) < B_OK) { 1097 puts("Missing expression text from CalcView archive.\n"); 1098 } else { 1099 // init expression text 1100 fExpressionTextView->SetText(display); 1101 } 1102 1103 // load expression history 1104 fExpressionTextView->LoadSettings(archive); 1105 1106 // parse calculator description 1107 _ParseCalcDesc(fKeypadDescription); 1108 1109 // colorize based on base color. 1110 _Colorize(); 1111 1112 return B_OK; 1113 } 1114 1115 1116 void 1117 CalcView::_ParseCalcDesc(const char* keypadDescription) 1118 { 1119 // TODO: should calculate dimensions from desc here! 1120 fKeypad = new CalcKey[fRows * fColumns]; 1121 1122 // scan through calculator description and assemble keypad 1123 CalcKey* key = fKeypad; 1124 const char* p = keypadDescription; 1125 1126 while (*p != 0) { 1127 // copy label 1128 char* l = key->label; 1129 while (!isspace(*p)) 1130 *l++ = *p++; 1131 *l = '\0'; 1132 1133 // set code 1134 if (strcmp(key->label, "=") == 0) 1135 strlcpy(key->code, "\n", sizeof(key->code)); 1136 else 1137 strlcpy(key->code, key->label, sizeof(key->code)); 1138 1139 // set keymap 1140 if (strlen(key->label) == 1) 1141 strlcpy(key->keymap, key->label, sizeof(key->keymap)); 1142 else 1143 *key->keymap = '\0'; 1144 1145 key->flags = 0; 1146 1147 // add this to the expression text view, so that it 1148 // will forward the respective KeyDown event to us 1149 fExpressionTextView->AddKeypadLabel(key->label); 1150 1151 // advance 1152 while (isspace(*p)) 1153 ++p; 1154 key++; 1155 } 1156 } 1157 1158 1159 void 1160 CalcView::_PressKey(int key) 1161 { 1162 if (!fEnabled) 1163 return; 1164 1165 assert(key < (fRows * fColumns)); 1166 assert(key >= 0); 1167 1168 if (strcmp(fKeypad[key].label, "BS") == 0) { 1169 // BS means backspace 1170 fExpressionTextView->BackSpace(); 1171 } else if (strcmp(fKeypad[key].label, "C") == 0) { 1172 // C means clear 1173 fExpressionTextView->Clear(); 1174 } else if (strcmp(fKeypad[key].label, "acos") == 0 1175 || strcmp(fKeypad[key].label, "asin") == 0 1176 || strcmp(fKeypad[key].label, "atan") == 0 1177 || strcmp(fKeypad[key].label, "cbrt") == 0 1178 || strcmp(fKeypad[key].label, "ceil") == 0 1179 || strcmp(fKeypad[key].label, "cos") == 0 1180 || strcmp(fKeypad[key].label, "cosh") == 0 1181 || strcmp(fKeypad[key].label, "exp") == 0 1182 || strcmp(fKeypad[key].label, "floor") == 0 1183 || strcmp(fKeypad[key].label, "log") == 0 1184 || strcmp(fKeypad[key].label, "ln") == 0 1185 || strcmp(fKeypad[key].label, "sin") == 0 1186 || strcmp(fKeypad[key].label, "sinh") == 0 1187 || strcmp(fKeypad[key].label, "sqrt") == 0 1188 || strcmp(fKeypad[key].label, "tan") == 0 1189 || strcmp(fKeypad[key].label, "tanh") == 0) { 1190 int32 labelLen = strlen(fKeypad[key].label); 1191 int32 startSelection = 0; 1192 int32 endSelection = 0; 1193 fExpressionTextView->GetSelection(&startSelection, &endSelection); 1194 if (endSelection > startSelection) { 1195 // There is selected text, put it inbetween the parens 1196 fExpressionTextView->Insert(startSelection, fKeypad[key].label, 1197 labelLen); 1198 fExpressionTextView->Insert(startSelection + labelLen, "(", 1); 1199 fExpressionTextView->Insert(endSelection + labelLen + 1, ")", 1); 1200 // Put the cursor after the ending paren 1201 // Need to cast to BTextView because Select() is protected 1202 // in the InputTextView class 1203 static_cast<BTextView*>(fExpressionTextView)->Select( 1204 endSelection + labelLen + 2, endSelection + labelLen + 2); 1205 } else { 1206 // There is no selected text, insert at the cursor location 1207 fExpressionTextView->Insert(fKeypad[key].label); 1208 fExpressionTextView->Insert("()"); 1209 // Put the cursor inside the parens so you can enter an argument 1210 // Need to cast to BTextView because Select() is protected 1211 // in the InputTextView class 1212 static_cast<BTextView*>(fExpressionTextView)->Select( 1213 endSelection + labelLen + 1, endSelection + labelLen + 1); 1214 } 1215 } else { 1216 // check for evaluation order 1217 if (fKeypad[key].code[0] == '\n') { 1218 fExpressionTextView->ApplyChanges(); 1219 } else { 1220 // insert into expression text 1221 fExpressionTextView->Insert(fKeypad[key].code); 1222 } 1223 } 1224 1225 _AudioFeedback(true); 1226 } 1227 1228 1229 void 1230 CalcView::_PressKey(const char* label) 1231 { 1232 int32 key = _KeyForLabel(label); 1233 if (key >= 0) 1234 _PressKey(key); 1235 } 1236 1237 1238 int32 1239 CalcView::_KeyForLabel(const char* label) const 1240 { 1241 int keys = fRows * fColumns; 1242 for (int i = 0; i < keys; i++) { 1243 if (strcmp(fKeypad[i].label, label) == 0) { 1244 return i; 1245 } 1246 } 1247 return -1; 1248 } 1249 1250 1251 void 1252 CalcView::_FlashKey(int32 key, uint32 flashFlags) 1253 { 1254 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) 1255 return; 1256 1257 if (flashFlags != 0) 1258 fKeypad[key].flags |= flashFlags; 1259 else 1260 fKeypad[key].flags = 0; 1261 Invalidate(); 1262 1263 if (fKeypad[key].flags == FLAGS_FLASH_KEY) { 1264 BMessage message(MSG_UNFLASH_KEY); 1265 message.AddInt32("key", key); 1266 BMessageRunner::StartSending(BMessenger(this), &message, 1267 kFlashOnOffInterval, 1); 1268 } 1269 } 1270 1271 1272 void 1273 CalcView::_AudioFeedback(bool inBackGround) 1274 { 1275 // TODO: Use beep events... This interface is not implemented on Haiku 1276 // anyways... 1277 #if 0 1278 if (fOptions->audio_feedback) { 1279 BEntry zimp("key.AIFF"); 1280 entry_ref zimp_ref; 1281 zimp.GetRef(&zimp_ref); 1282 play_sound(&zimp_ref, true, false, inBackGround); 1283 } 1284 #endif 1285 } 1286 1287 1288 void 1289 CalcView::_Colorize() 1290 { 1291 // calculate light and dark color from base color 1292 fLightColor.red = (uint8)(fBaseColor.red * 1.25); 1293 fLightColor.green = (uint8)(fBaseColor.green * 1.25); 1294 fLightColor.blue = (uint8)(fBaseColor.blue * 1.25); 1295 fLightColor.alpha = 255; 1296 1297 fDarkColor.red = (uint8)(fBaseColor.red * 0.75); 1298 fDarkColor.green = (uint8)(fBaseColor.green * 0.75); 1299 fDarkColor.blue = (uint8)(fBaseColor.blue * 0.75); 1300 fDarkColor.alpha = 255; 1301 1302 // keypad text color 1303 uint8 lightness = (fBaseColor.red + fBaseColor.green + fBaseColor.blue) / 3; 1304 if (lightness > 200) 1305 fButtonTextColor = (rgb_color){ 0, 0, 0, 255 }; 1306 else 1307 fButtonTextColor = (rgb_color){ 255, 255, 255, 255 }; 1308 1309 // expression text color 1310 lightness = (fExpressionBGColor.red 1311 + fExpressionBGColor.green + fExpressionBGColor.blue) / 3; 1312 if (lightness > 200) 1313 fExpressionTextColor = (rgb_color){ 0, 0, 0, 255 }; 1314 else 1315 fExpressionTextColor = (rgb_color){ 255, 255, 255, 255 }; 1316 } 1317 1318 1319 void 1320 CalcView::_CreatePopUpMenu(bool addKeypadModeMenuItems) 1321 { 1322 // construct items 1323 fAutoNumlockItem = new BMenuItem(B_TRANSLATE("Enable Num Lock on startup"), 1324 new BMessage(MSG_OPTIONS_AUTO_NUM_LOCK)); 1325 fAudioFeedbackItem = new BMenuItem(B_TRANSLATE("Audio Feedback"), 1326 new BMessage(MSG_OPTIONS_AUDIO_FEEDBACK)); 1327 fAngleModeRadianItem = new BMenuItem(B_TRANSLATE("Radians"), 1328 new BMessage(MSG_OPTIONS_ANGLE_MODE_RADIAN)); 1329 fAngleModeDegreeItem = new BMenuItem(B_TRANSLATE("Degrees"), 1330 new BMessage(MSG_OPTIONS_ANGLE_MODE_DEGREE)); 1331 if (addKeypadModeMenuItems) { 1332 fKeypadModeCompactItem = new BMenuItem(B_TRANSLATE("Compact"), 1333 new BMessage(MSG_OPTIONS_KEYPAD_MODE_COMPACT), '0'); 1334 fKeypadModeBasicItem = new BMenuItem(B_TRANSLATE("Basic"), 1335 new BMessage(MSG_OPTIONS_KEYPAD_MODE_BASIC), '1'); 1336 fKeypadModeScientificItem = new BMenuItem(B_TRANSLATE("Scientific"), 1337 new BMessage(MSG_OPTIONS_KEYPAD_MODE_SCIENTIFIC), '2'); 1338 } 1339 1340 // apply current settings 1341 fAutoNumlockItem->SetMarked(fOptions->auto_num_lock); 1342 fAudioFeedbackItem->SetMarked(fOptions->audio_feedback); 1343 fAngleModeRadianItem->SetMarked(!fOptions->degree_mode); 1344 fAngleModeDegreeItem->SetMarked(fOptions->degree_mode); 1345 1346 // construct menu 1347 fPopUpMenu = new BPopUpMenu("pop-up", false, false); 1348 1349 fPopUpMenu->AddItem(fAutoNumlockItem); 1350 // TODO: Enable this when we use beep events which can be configured 1351 // in the Sounds preflet. 1352 //fPopUpMenu->AddItem(fAudioFeedbackItem); 1353 fPopUpMenu->AddSeparatorItem(); 1354 fPopUpMenu->AddItem(fAngleModeRadianItem); 1355 fPopUpMenu->AddItem(fAngleModeDegreeItem); 1356 if (addKeypadModeMenuItems) { 1357 fPopUpMenu->AddSeparatorItem(); 1358 fPopUpMenu->AddItem(fKeypadModeCompactItem); 1359 fPopUpMenu->AddItem(fKeypadModeBasicItem); 1360 fPopUpMenu->AddItem(fKeypadModeScientificItem); 1361 _MarkKeypadItems(fOptions->keypad_mode); 1362 } 1363 } 1364 1365 1366 BRect 1367 CalcView::_ExpressionRect() const 1368 { 1369 BRect r(0.0, 0.0, fWidth, fHeight); 1370 if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) { 1371 r.bottom = floorf(fHeight * kDisplayScaleY) + 1; 1372 } 1373 return r; 1374 } 1375 1376 1377 BRect 1378 CalcView::_KeypadRect() const 1379 { 1380 BRect r(0.0, 0.0, -1.0, -1.0); 1381 if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) { 1382 r.right = fWidth; 1383 r.bottom = fHeight; 1384 r.top = floorf(fHeight * kDisplayScaleY); 1385 } 1386 return r; 1387 } 1388 1389 1390 void 1391 CalcView::_MarkKeypadItems(uint8 keypad_mode) 1392 { 1393 switch (keypad_mode) { 1394 case KEYPAD_MODE_COMPACT: 1395 fKeypadModeCompactItem->SetMarked(true); 1396 fKeypadModeBasicItem->SetMarked(false); 1397 fKeypadModeScientificItem->SetMarked(false); 1398 break; 1399 1400 case KEYPAD_MODE_SCIENTIFIC: 1401 fKeypadModeCompactItem->SetMarked(false); 1402 fKeypadModeBasicItem->SetMarked(false); 1403 fKeypadModeScientificItem->SetMarked(true); 1404 break; 1405 1406 default: // KEYPAD_MODE_BASIC is the default 1407 fKeypadModeCompactItem->SetMarked(false); 1408 fKeypadModeBasicItem->SetMarked(true); 1409 fKeypadModeScientificItem->SetMarked(false); 1410 } 1411 } 1412 1413 1414 void 1415 CalcView::_FetchAppIcon(BBitmap* into) 1416 { 1417 entry_ref appRef; 1418 status_t status = be_roster->FindApp(kSignature, &appRef); 1419 if (status == B_OK) { 1420 BFile file(&appRef, B_READ_ONLY); 1421 BAppFileInfo appInfo(&file); 1422 status = appInfo.GetIcon(into, B_MINI_ICON); 1423 } 1424 if (status != B_OK) 1425 memset(into->Bits(), 0, into->BitsLength()); 1426 } 1427 1428 1429 // Returns whether or not CalcView is embedded somewhere, most likely 1430 // the Desktop 1431 bool 1432 CalcView::_IsEmbedded() 1433 { 1434 return Parent() != NULL && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0; 1435 } 1436 1437 1438 void 1439 CalcView::_SetEnabled(bool enable) 1440 { 1441 fEnabled = enable; 1442 fExpressionTextView->MakeSelectable(enable); 1443 fExpressionTextView->MakeEditable(enable); 1444 } 1445