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