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