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