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 <AboutWindow.h> 21 #include <Alert.h> 22 #include <Application.h> 23 #include <AppFileInfo.h> 24 #include <Beep.h> 25 #include <Bitmap.h> 26 #include <Catalog.h> 27 #include <ControlLook.h> 28 #include <Clipboard.h> 29 #include <File.h> 30 #include <Font.h> 31 #include <MenuItem.h> 32 #include <Message.h> 33 #include <MessageRunner.h> 34 #include <PlaySound.h> 35 #include <Point.h> 36 #include <PopUpMenu.h> 37 #include <Region.h> 38 #include <Roster.h> 39 40 #include <ExpressionParser.h> 41 42 #include "CalcApplication.h" 43 #include "CalcOptions.h" 44 #include "ExpressionTextView.h" 45 46 #undef B_TRANSLATE_CONTEXT 47 #define B_TRANSLATE_CONTEXT "CalcView" 48 49 //const uint8 K_COLOR_OFFSET = 32; 50 const float kFontScaleY = 0.4f; 51 const float kFontScaleX = 0.4f; 52 const float kExpressionFontScaleY = 0.6f; 53 const float kDisplayScaleY = 0.2f; 54 55 static const bigtime_t kFlashOnOffInterval = 100000; 56 57 enum { 58 MSG_OPTIONS_AUTO_NUM_LOCK = 'opan', 59 MSG_OPTIONS_AUDIO_FEEDBACK = 'opaf', 60 MSG_OPTIONS_SHOW_KEYPAD = 'opsk', 61 62 MSG_UNFLASH_KEY = 'uflk' 63 }; 64 65 // default calculator key pad layout 66 const char *kDefaultKeypadDescription = 67 "7 8 9 ( ) \n" 68 "4 5 6 * / \n" 69 "1 2 3 + - \n" 70 "0 . BS = C \n"; 71 72 73 enum { 74 FLAGS_FLASH_KEY = 1 << 0, 75 FLAGS_MOUSE_DOWN = 1 << 1 76 }; 77 78 struct CalcView::CalcKey { 79 char label[8]; 80 char code[8]; 81 char keymap[4]; 82 uint32 flags; 83 // float width; 84 }; 85 86 87 CalcView* 88 CalcView::Instantiate(BMessage* archive) 89 { 90 if (!validate_instantiation(archive, "CalcView")) 91 return NULL; 92 93 return new CalcView(archive); 94 } 95 96 97 CalcView::CalcView(BRect frame, rgb_color rgbBaseColor, BMessage* settings) 98 : 99 BView(frame, "DeskCalc", B_FOLLOW_ALL_SIDES, 100 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), 101 fColums(5), 102 fRows(4), 103 104 fBaseColor(rgbBaseColor), 105 fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }), 106 107 fWidth(1), 108 fHeight(1), 109 110 fKeypadDescription(strdup(kDefaultKeypadDescription)), 111 fKeypad(NULL), 112 113 #ifdef __HAIKU__ 114 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)), 115 #else 116 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_CMAP8)), 117 #endif 118 119 fPopUpMenu(NULL), 120 fAutoNumlockItem(NULL), 121 fAudioFeedbackItem(NULL), 122 fShowKeypadItem(NULL), 123 fOptions(new CalcOptions()), 124 fShowKeypad(true) 125 { 126 // create expression text view 127 fExpressionTextView = new ExpressionTextView(_ExpressionRect(), this); 128 AddChild(fExpressionTextView); 129 130 _LoadSettings(settings); 131 132 // tell the app server not to erase our b/g 133 SetViewColor(B_TRANSPARENT_32_BIT); 134 135 // parse calculator description 136 _ParseCalcDesc(fKeypadDescription); 137 138 // colorize based on base color. 139 _Colorize(); 140 141 // create pop-up menu system 142 _CreatePopUpMenu(); 143 144 _FetchAppIcon(fCalcIcon); 145 } 146 147 148 CalcView::CalcView(BMessage* archive) 149 : 150 BView(archive), 151 fColums(5), 152 fRows(4), 153 154 fBaseColor((rgb_color){ 128, 128, 128, 255 }), 155 fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }), 156 157 fWidth(1), 158 fHeight(1), 159 160 fKeypadDescription(strdup(kDefaultKeypadDescription)), 161 fKeypad(NULL), 162 163 #ifdef __HAIKU__ 164 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)), 165 #else 166 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_CMAP8)), 167 #endif 168 169 fPopUpMenu(NULL), 170 fAutoNumlockItem(NULL), 171 fAudioFeedbackItem(NULL), 172 fShowKeypadItem(NULL), 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 Invalidate(); 680 } 681 682 683 void 684 CalcView::AboutRequested() 685 { 686 const char* authors[] = { 687 "Timothy Wayper", 688 "Stephan Aßmus", 689 "Ingo Weinhold", 690 NULL 691 }; 692 BAboutWindow about(B_TRANSLATE_SYSTEM_NAME("DeskCalc"), 2006, authors, 693 B_UTF8_COPYRIGHT "1997, 1998 R3 Software Ltd."); 694 about.Show(); 695 } 696 697 698 status_t 699 CalcView::Archive(BMessage* archive, bool deep) const 700 { 701 fExpressionTextView->RemoveSelf(); 702 703 // passed on request to parent 704 status_t ret = BView::Archive(archive, deep); 705 706 const_cast<CalcView*>(this)->AddChild(fExpressionTextView); 707 708 // save app signature for replicant add-on loading 709 if (ret == B_OK) 710 ret = archive->AddString("add_on", kAppSig); 711 712 // save all the options 713 if (ret == B_OK) 714 ret = SaveSettings(archive); 715 716 // add class info last 717 if (ret == B_OK) 718 ret = archive->AddString("class", "CalcView"); 719 720 return ret; 721 } 722 723 724 void 725 CalcView::Cut() 726 { 727 Copy(); // copy data to clipboard 728 fExpressionTextView->Clear(); // remove data 729 } 730 731 732 void 733 CalcView::Copy() 734 { 735 // access system clipboard 736 if (be_clipboard->Lock()) { 737 be_clipboard->Clear(); 738 BMessage* clipper = be_clipboard->Data(); 739 clipper->what = B_MIME_DATA; 740 // TODO: should check return for errors! 741 BString expression = fExpressionTextView->Text(); 742 clipper->AddData("text/plain", B_MIME_TYPE, 743 expression.String(), expression.Length()); 744 //clipper->PrintToStream(); 745 be_clipboard->Commit(); 746 be_clipboard->Unlock(); 747 } 748 } 749 750 751 void 752 CalcView::Paste(BMessage* message) 753 { 754 // handle color drops first 755 // read incoming color 756 const rgb_color* dropColor = NULL; 757 ssize_t dataSize; 758 if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 759 (const void**)&dropColor, &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", B_MIME_TYPE, 782 (const void**)&text, &numBytes) == B_OK) { 783 BString temp; 784 temp.Append(text, numBytes); 785 fExpressionTextView->Insert(temp.String()); 786 } 787 } 788 } 789 790 791 status_t 792 CalcView::_LoadSettings(BMessage* archive) 793 { 794 if (!archive) 795 return B_BAD_VALUE; 796 797 // record calculator description 798 const char* calcDesc; 799 if (archive->FindString("calcDesc", &calcDesc) < B_OK) 800 calcDesc = kDefaultKeypadDescription; 801 802 // save calculator description for reference 803 free(fKeypadDescription); 804 fKeypadDescription = strdup(calcDesc); 805 806 // read grid dimensions 807 if (archive->FindInt16("cols", &fColums) < B_OK) 808 fColums = 5; 809 if (archive->FindInt16("rows", &fRows) < B_OK) 810 fRows = 4; 811 812 // read color scheme 813 const rgb_color* color; 814 ssize_t size; 815 if (archive->FindData("rgbBaseColor", B_RGB_COLOR_TYPE, 816 (const void**)&color, &size) < B_OK 817 || size != sizeof(rgb_color)) { 818 fBaseColor = (rgb_color){ 128, 128, 128, 255 }; 819 puts("Missing rgbBaseColor from CalcView archive!\n"); 820 } else { 821 fBaseColor = *color; 822 } 823 824 if (archive->FindData("rgbDisplay", B_RGB_COLOR_TYPE, 825 (const void**)&color, &size) < B_OK 826 || size != sizeof(rgb_color)) { 827 fExpressionBGColor = (rgb_color){ 0, 0, 0, 255 }; 828 puts("Missing rgbBaseColor from CalcView archive!\n"); 829 } else { 830 fExpressionBGColor = *color; 831 } 832 833 // load options 834 fOptions->LoadSettings(archive); 835 836 // load display text 837 const char* display; 838 if (archive->FindString("displayText", &display) < B_OK) { 839 puts("Missing expression text from CalcView archive.\n"); 840 } else { 841 // init expression text 842 fExpressionTextView->SetText(display); 843 } 844 845 // load expression history 846 fExpressionTextView->LoadSettings(archive); 847 848 // parse calculator description 849 _ParseCalcDesc(fKeypadDescription); 850 851 // colorize based on base color. 852 _Colorize(); 853 854 return B_OK; 855 } 856 857 858 status_t 859 CalcView::SaveSettings(BMessage* archive) const 860 { 861 status_t ret = archive ? B_OK : B_BAD_VALUE; 862 863 // record grid dimensions 864 if (ret == B_OK) 865 ret = archive->AddInt16("cols", fColums); 866 if (ret == B_OK) 867 ret = archive->AddInt16("rows", fRows); 868 869 // record color scheme 870 if (ret == B_OK) 871 ret = archive->AddData("rgbBaseColor", B_RGB_COLOR_TYPE, 872 &fBaseColor, sizeof(rgb_color)); 873 if (ret == B_OK) 874 ret = archive->AddData("rgbDisplay", B_RGB_COLOR_TYPE, 875 &fExpressionBGColor, sizeof(rgb_color)); 876 877 // record current options 878 if (ret == B_OK) 879 ret = fOptions->SaveSettings(archive); 880 881 // record display text 882 if (ret == B_OK) 883 ret = archive->AddString("displayText", fExpressionTextView->Text()); 884 885 // record expression history 886 if (ret == B_OK) 887 ret = fExpressionTextView->SaveSettings(archive); 888 889 // record calculator description 890 if (ret == B_OK) 891 ret = archive->AddString("calcDesc", fKeypadDescription); 892 893 return ret; 894 } 895 896 897 void 898 CalcView::Evaluate() 899 { 900 BString expression = fExpressionTextView->Text(); 901 902 if (expression.Length() == 0) { 903 beep(); 904 return; 905 } 906 907 _AudioFeedback(false); 908 909 // evaluate expression 910 BString value; 911 912 try { 913 ExpressionParser parser; 914 value = parser.Evaluate(expression.String()); 915 } catch (ParseException e) { 916 BString error(e.message.String()); 917 error << " at " << (e.position + 1); 918 fExpressionTextView->SetText(error.String()); 919 return; 920 } 921 922 // render new result to display 923 fExpressionTextView->SetValue(value.String()); 924 } 925 926 927 void 928 CalcView::FlashKey(const char* bytes, int32 numBytes) 929 { 930 BString temp; 931 temp.Append(bytes, numBytes); 932 int32 key = _KeyForLabel(temp.String()); 933 if (key >= 0) 934 _FlashKey(key, FLAGS_FLASH_KEY); 935 } 936 937 938 // #pragma mark - 939 940 941 void 942 CalcView::_ParseCalcDesc(const char* keypadDescription) 943 { 944 // TODO: should calculate dimensions from desc here! 945 fKeypad = new CalcKey[fRows * fColums]; 946 947 // scan through calculator description and assemble keypad 948 CalcKey* key = fKeypad; 949 const char* p = keypadDescription; 950 951 while (*p != 0) { 952 // copy label 953 char* l = key->label; 954 while (!isspace(*p)) 955 *l++ = *p++; 956 *l = '\0'; 957 958 // set code 959 if (strcmp(key->label, "=") == 0) 960 strlcpy(key->code, "\n", sizeof(key->code)); 961 else 962 strlcpy(key->code, key->label, sizeof(key->code)); 963 964 // set keymap 965 if (strlen(key->label) == 1) 966 strlcpy(key->keymap, key->label, sizeof(key->keymap)); 967 else 968 *key->keymap = '\0'; 969 970 key->flags = 0; 971 972 // add this to the expression text view, so that it 973 // will forward the respective KeyDown event to us 974 fExpressionTextView->AddKeypadLabel(key->label); 975 976 // advance 977 while (isspace(*p)) 978 ++p; 979 key++; 980 } 981 } 982 983 984 void 985 CalcView::_PressKey(int key) 986 { 987 assert(key < (fRows * fColums)); 988 assert(key >= 0); 989 990 // check for backspace 991 if (strcmp(fKeypad[key].label, "BS") == 0) { 992 fExpressionTextView->BackSpace(); 993 } else if (strcmp(fKeypad[key].label, "C") == 0) { 994 // C means clear 995 fExpressionTextView->Clear(); 996 } else { 997 // check for evaluation order 998 if (fKeypad[key].code[0] == '\n') { 999 fExpressionTextView->ApplyChanges(); 1000 } else { 1001 // insert into expression text 1002 fExpressionTextView->Insert(fKeypad[key].code); 1003 } 1004 } 1005 1006 _AudioFeedback(true); 1007 } 1008 1009 1010 void 1011 CalcView::_PressKey(const char* label) 1012 { 1013 int32 key = _KeyForLabel(label); 1014 if (key >= 0) 1015 _PressKey(key); 1016 } 1017 1018 1019 int32 1020 CalcView::_KeyForLabel(const char* label) const 1021 { 1022 int keys = fRows * fColums; 1023 for (int i = 0; i < keys; i++) { 1024 if (strcmp(fKeypad[i].label, label) == 0) { 1025 return i; 1026 } 1027 } 1028 return -1; 1029 } 1030 1031 1032 void 1033 CalcView::_FlashKey(int32 key, uint32 flashFlags) 1034 { 1035 if (!fShowKeypad) 1036 return; 1037 1038 if (flashFlags != 0) 1039 fKeypad[key].flags |= flashFlags; 1040 else 1041 fKeypad[key].flags = 0; 1042 Invalidate(); 1043 1044 if (fKeypad[key].flags == FLAGS_FLASH_KEY) { 1045 BMessage message(MSG_UNFLASH_KEY); 1046 message.AddInt32("key", key); 1047 BMessageRunner::StartSending(BMessenger(this), &message, 1048 kFlashOnOffInterval, 1); 1049 } 1050 } 1051 1052 1053 void 1054 CalcView::_AudioFeedback(bool inBackGround) 1055 { 1056 // TODO: Use beep events... This interface is not implemented on Haiku 1057 // anyways... 1058 #if 0 1059 if (fOptions->audio_feedback) { 1060 BEntry zimp("key.AIFF"); 1061 entry_ref zimp_ref; 1062 zimp.GetRef(&zimp_ref); 1063 play_sound(&zimp_ref, true, false, inBackGround); 1064 } 1065 #endif 1066 } 1067 1068 1069 void 1070 CalcView::_Colorize() 1071 { 1072 // calculate light and dark color from base color 1073 fLightColor.red = (uint8)(fBaseColor.red * 1.25); 1074 fLightColor.green = (uint8)(fBaseColor.green * 1.25); 1075 fLightColor.blue = (uint8)(fBaseColor.blue * 1.25); 1076 fLightColor.alpha = 255; 1077 1078 fDarkColor.red = (uint8)(fBaseColor.red * 0.75); 1079 fDarkColor.green = (uint8)(fBaseColor.green * 0.75); 1080 fDarkColor.blue = (uint8)(fBaseColor.blue * 0.75); 1081 fDarkColor.alpha = 255; 1082 1083 // keypad text color 1084 uint8 lightness = (fBaseColor.red + fBaseColor.green + fBaseColor.blue) / 3; 1085 if (lightness > 200) 1086 fButtonTextColor = (rgb_color){ 0, 0, 0, 255 }; 1087 else 1088 fButtonTextColor = (rgb_color){ 255, 255, 255, 255 }; 1089 1090 // expression text color 1091 lightness = (fExpressionBGColor.red 1092 + fExpressionBGColor.green + fExpressionBGColor.blue) / 3; 1093 if (lightness > 200) 1094 fExpressionTextColor = (rgb_color){ 0, 0, 0, 255 }; 1095 else 1096 fExpressionTextColor = (rgb_color){ 255, 255, 255, 255 }; 1097 } 1098 1099 1100 void 1101 CalcView::_CreatePopUpMenu() 1102 { 1103 // construct items 1104 fAutoNumlockItem = new BMenuItem(B_TRANSLATE("Enable Num Lock on startup"), 1105 new BMessage(MSG_OPTIONS_AUTO_NUM_LOCK)); 1106 fAudioFeedbackItem = new BMenuItem(B_TRANSLATE("Audio Feedback"), 1107 new BMessage(MSG_OPTIONS_AUDIO_FEEDBACK)); 1108 fShowKeypadItem = new BMenuItem(B_TRANSLATE("Show keypad"), 1109 new BMessage(MSG_OPTIONS_SHOW_KEYPAD)); 1110 1111 // apply current settings 1112 fAutoNumlockItem->SetMarked(fOptions->auto_num_lock); 1113 fAudioFeedbackItem->SetMarked(fOptions->audio_feedback); 1114 fShowKeypadItem->SetMarked(fOptions->show_keypad); 1115 1116 // construct menu 1117 fPopUpMenu = new BPopUpMenu("pop-up", false, false); 1118 1119 fPopUpMenu->AddItem(fAutoNumlockItem); 1120 // TODO: Enabled when we use beep events which can be configured in the Sounds 1121 // preflet. 1122 // fPopUpMenu->AddItem(fAudioFeedbackItem); 1123 fPopUpMenu->AddItem(fShowKeypadItem); 1124 } 1125 1126 1127 BRect 1128 CalcView::_ExpressionRect() const 1129 { 1130 BRect r(0.0, 0.0, fWidth, fHeight); 1131 if (fShowKeypad) { 1132 r.bottom = floorf(fHeight * kDisplayScaleY) + 1; 1133 } 1134 return r; 1135 } 1136 1137 1138 BRect 1139 CalcView::_KeypadRect() const 1140 { 1141 BRect r(0.0, 0.0, -1.0, -1.0); 1142 if (fShowKeypad) { 1143 r.right = fWidth; 1144 r.bottom = fHeight; 1145 r.top = floorf(fHeight * kDisplayScaleY); 1146 } 1147 return r; 1148 } 1149 1150 1151 void 1152 CalcView::_ShowKeypad(bool show) 1153 { 1154 if (fShowKeypad == show) 1155 return; 1156 1157 fShowKeypad = show; 1158 if (fShowKeypadItem && fShowKeypadItem->IsMarked() ^ fShowKeypad) 1159 fShowKeypadItem->SetMarked(fShowKeypad); 1160 1161 float height 1162 = fShowKeypad ? fHeight / kDisplayScaleY : fHeight * kDisplayScaleY; 1163 1164 BWindow* window = Window(); 1165 if (window) { 1166 if (window->Bounds() == Frame()) { 1167 window->SetSizeLimits(100.0, 400.0, 1168 fShowKeypad ? 100.0 : 20.0, fShowKeypad ? 400.0 : 60.0); 1169 window->ResizeTo(fWidth, height); 1170 } else 1171 ResizeTo(fWidth, height); 1172 } 1173 } 1174 1175 1176 void 1177 CalcView::_FetchAppIcon(BBitmap* into) 1178 { 1179 entry_ref appRef; 1180 status_t status = be_roster->FindApp(kAppSig, &appRef); 1181 if (status == B_OK) { 1182 BFile file(&appRef, B_READ_ONLY); 1183 BAppFileInfo appInfo(&file); 1184 status = appInfo.GetIcon(into, B_MINI_ICON); 1185 } 1186 if (status != B_OK) 1187 memset(into->Bits(), 0, into->BitsLength()); 1188 } 1189