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