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