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