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