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