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