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