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