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