BTRFS: Implement BTree::Path and change _Find.
[haiku.git] / src / apps / deskcalc / CalcView.cpp
bloba17758929b22fca8f8e03dd0a3938c859f892e1d
1 /*
2 * Copyright 2006-2013, Haiku, Inc. All rights reserved.
3 * Copyright 1997, 1998 R3 Software Ltd. All Rights Reserved.
4 * Distributed under the terms of the MIT License.
6 * Authors:
7 * Stephan Aßmus, superstippi@gmx.de
8 * Philippe Saint-Pierre, stpere@gmail.com
9 * John Scipione, jscipione@gmail.com
10 * Timothy Wayper, timmy@wunderbear.com
14 #include "CalcView.h"
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <ctype.h>
20 #include <assert.h>
22 #include <AboutWindow.h>
23 #include <Alert.h>
24 #include <Application.h>
25 #include <AppFileInfo.h>
26 #include <Beep.h>
27 #include <Bitmap.h>
28 #include <Catalog.h>
29 #include <ControlLook.h>
30 #include <Clipboard.h>
31 #include <File.h>
32 #include <Font.h>
33 #include <MenuItem.h>
34 #include <Message.h>
35 #include <MessageRunner.h>
36 #include <PlaySound.h>
37 #include <Point.h>
38 #include <PopUpMenu.h>
39 #include <Region.h>
40 #include <Roster.h>
42 #include <ExpressionParser.h>
44 #include "CalcApplication.h"
45 #include "CalcOptions.h"
46 #include "ExpressionTextView.h"
49 #undef B_TRANSLATION_CONTEXT
50 #define B_TRANSLATION_CONTEXT "CalcView"
53 static const int32 kMsgCalculating = 'calc';
54 static const int32 kMsgAnimateDots = 'dots';
55 static const int32 kMsgDoneEvaluating = 'done';
57 //const uint8 K_COLOR_OFFSET = 32;
58 const float kFontScaleY = 0.4f;
59 const float kFontScaleX = 0.4f;
60 const float kExpressionFontScaleY = 0.6f;
61 const float kDisplayScaleY = 0.2f;
63 static const bigtime_t kFlashOnOffInterval = 100000;
64 static const bigtime_t kCalculatingInterval = 1000000;
65 static const bigtime_t kAnimationInterval = 333333;
67 static const float kMinimumWidthCompact = 130.0f;
68 static const float kMaximumWidthCompact = 400.0f;
69 static const float kMinimumHeightCompact = 20.0f;
70 static const float kMaximumHeightCompact = 60.0f;
72 // Basic mode size limits are defined in CalcView.h so
73 // that they can be used by the CalcWindow constructor.
75 static const float kMinimumWidthScientific = 240.0f;
76 static const float kMaximumWidthScientific = 400.0f;
77 static const float kMinimumHeightScientific = 200.0f;
78 static const float kMaximumHeightScientific = 400.0f;
80 // basic mode keypad layout (default)
81 const char *kKeypadDescriptionBasic =
82 "7 8 9 ( ) \n"
83 "4 5 6 * / \n"
84 "1 2 3 + - \n"
85 "0 . BS = C \n";
87 // scientific mode keypad layout
88 const char *kKeypadDescriptionScientific =
89 "ln sin cos tan π \n"
90 "log asin acos atan sqrt \n"
91 "exp sinh cosh tanh cbrt \n"
92 "! ceil floor E ^ \n"
93 "7 8 9 ( ) \n"
94 "4 5 6 * / \n"
95 "1 2 3 + - \n"
96 "0 . BS = C \n";
99 enum {
100 FLAGS_FLASH_KEY = 1 << 0,
101 FLAGS_MOUSE_DOWN = 1 << 1
105 struct CalcView::CalcKey {
106 char label[8];
107 char code[8];
108 char keymap[4];
109 uint32 flags;
110 // float width;
114 CalcView*
115 CalcView::Instantiate(BMessage* archive)
117 if (!validate_instantiation(archive, "CalcView"))
118 return NULL;
120 return new CalcView(archive);
124 CalcView::CalcView(BRect frame, rgb_color rgbBaseColor, BMessage* settings)
126 BView(frame, "DeskCalc", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS),
127 fColumns(5),
128 fRows(4),
130 fBaseColor(rgbBaseColor),
131 fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }),
133 fHasCustomBaseColor(rgbBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR)),
135 fWidth(1),
136 fHeight(1),
138 fKeypadDescription(strdup(kKeypadDescriptionBasic)),
139 fKeypad(NULL),
141 #ifdef __HAIKU__
142 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)),
143 #else
144 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_CMAP8)),
145 #endif
147 fPopUpMenu(NULL),
148 fAutoNumlockItem(NULL),
149 fAudioFeedbackItem(NULL),
150 fOptions(new CalcOptions()),
151 fEvaluateThread(-1),
152 fEvaluateMessageRunner(NULL),
153 fEvaluateSemaphore(B_BAD_SEM_ID),
154 fEnabled(true)
156 // tell the app server not to erase our b/g
157 SetViewColor(B_TRANSPARENT_32_BIT);
159 _Init(settings);
163 CalcView::CalcView(BMessage* archive)
165 BView(archive),
166 fColumns(5),
167 fRows(4),
169 fBaseColor(ui_color(B_PANEL_BACKGROUND_COLOR)),
170 fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }),
172 fHasCustomBaseColor(false),
174 fWidth(1),
175 fHeight(1),
177 fKeypadDescription(strdup(kKeypadDescriptionBasic)),
178 fKeypad(NULL),
180 #ifdef __HAIKU__
181 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)),
182 #else
183 fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_CMAP8)),
184 #endif
186 fPopUpMenu(NULL),
187 fAutoNumlockItem(NULL),
188 fAudioFeedbackItem(NULL),
189 fOptions(new CalcOptions()),
190 fEvaluateThread(-1),
191 fEvaluateMessageRunner(NULL),
192 fEvaluateSemaphore(B_BAD_SEM_ID),
193 fEnabled(true)
195 // Do not restore the follow mode, in shelfs, we never follow.
196 SetResizingMode(B_FOLLOW_NONE);
198 _Init(archive);
202 CalcView::~CalcView()
204 delete fKeypad;
205 delete fOptions;
206 free(fKeypadDescription);
207 delete fEvaluateMessageRunner;
208 delete_sem(fEvaluateSemaphore);
212 void
213 CalcView::AttachedToWindow()
215 if (be_control_look == NULL)
216 SetFont(be_bold_font);
218 BRect frame(Frame());
219 FrameResized(frame.Width(), frame.Height());
221 bool addKeypadModeMenuItems = true;
222 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) {
223 // don't add these items if we are a replicant on the desktop
224 addKeypadModeMenuItems = false;
227 // create and attach the pop-up menu
228 _CreatePopUpMenu(addKeypadModeMenuItems);
230 if (addKeypadModeMenuItems)
231 SetKeypadMode(fOptions->keypad_mode);
235 void
236 CalcView::MessageReceived(BMessage* message)
238 if (message->what == B_COLORS_UPDATED && !fHasCustomBaseColor) {
239 const char* panelBgColorName = ui_color_name(B_PANEL_BACKGROUND_COLOR);
240 if (message->HasColor(panelBgColorName)) {
241 fBaseColor = message->GetColor(panelBgColorName, fBaseColor);
242 _Colorize();
245 return;
248 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) {
249 // if we are embedded in desktop we need to receive these
250 // message here since we don't have a parent BWindow
251 switch (message->what) {
252 case MSG_OPTIONS_AUTO_NUM_LOCK:
253 ToggleAutoNumlock();
254 return;
256 case MSG_OPTIONS_AUDIO_FEEDBACK:
257 ToggleAudioFeedback();
258 return;
260 case MSG_OPTIONS_ANGLE_MODE_RADIAN:
261 SetDegreeMode(false);
262 return;
264 case MSG_OPTIONS_ANGLE_MODE_DEGREE:
265 SetDegreeMode(true);
266 return;
270 // check if message was dropped
271 if (message->WasDropped()) {
272 // pass message on to paste
273 if (message->IsSourceRemote())
274 Paste(message);
275 } else {
276 // act on posted message type
277 switch (message->what) {
279 // handle "cut"
280 case B_CUT:
281 Cut();
282 break;
284 // handle copy
285 case B_COPY:
286 Copy();
287 break;
289 // handle paste
290 case B_PASTE:
291 // access system clipboard
292 if (be_clipboard->Lock()) {
293 BMessage* clipper = be_clipboard->Data();
294 //clipper->PrintToStream();
295 Paste(clipper);
296 be_clipboard->Unlock();
298 break;
300 // (replicant) about box requested
301 case B_ABOUT_REQUESTED:
303 BAboutWindow* window = new BAboutWindow(kAppName, kSignature);
305 // create the about window
306 const char* extraCopyrights[] = {
307 "1997, 1998 R3 Software Ltd.",
308 NULL
311 const char* authors[] = {
312 "Stephan Aßmus",
313 "John Scipione",
314 "Timothy Wayper",
315 "Ingo Weinhold",
316 NULL
319 window->AddCopyright(2006, "Haiku, Inc.", extraCopyrights);
320 window->AddAuthors(authors);
322 window->Show();
324 break;
327 case MSG_UNFLASH_KEY:
329 int32 key;
330 if (message->FindInt32("key", &key) == B_OK)
331 _FlashKey(key, 0);
333 break;
336 case kMsgAnimateDots:
338 int32 end = fExpressionTextView->TextLength();
339 int32 start = end - 3;
340 if (fEnabled || strcmp(fExpressionTextView->Text() + start,
341 "...") != 0) {
342 // stop the message runner
343 delete fEvaluateMessageRunner;
344 fEvaluateMessageRunner = NULL;
345 break;
348 uint8 dot = 0;
349 if (message->FindUInt8("dot", &dot) == B_OK) {
350 rgb_color fontColor = fExpressionTextView->HighColor();
351 rgb_color backColor = fExpressionTextView->LowColor();
352 fExpressionTextView->SetStylable(true);
353 fExpressionTextView->SetFontAndColor(start, end, NULL, 0,
354 &backColor);
355 fExpressionTextView->SetFontAndColor(start + dot - 1,
356 start + dot, NULL, 0, &fontColor);
357 fExpressionTextView->SetStylable(false);
360 dot++;
361 if (dot == 4)
362 dot = 1;
364 delete fEvaluateMessageRunner;
365 BMessage animate(kMsgAnimateDots);
366 animate.AddUInt8("dot", dot);
367 fEvaluateMessageRunner = new (std::nothrow) BMessageRunner(
368 BMessenger(this), &animate, kAnimationInterval, 1);
369 break;
372 case kMsgCalculating:
374 // calculation has taken more than 3 seconds
375 if (fEnabled) {
376 // stop the message runner
377 delete fEvaluateMessageRunner;
378 fEvaluateMessageRunner = NULL;
379 break;
382 BString calculating;
383 calculating << B_TRANSLATE("Calculating") << "...";
384 fExpressionTextView->SetText(calculating.String());
386 delete fEvaluateMessageRunner;
387 BMessage animate(kMsgAnimateDots);
388 animate.AddUInt8("dot", 1U);
389 fEvaluateMessageRunner = new (std::nothrow) BMessageRunner(
390 BMessenger(this), &animate, kAnimationInterval, 1);
391 break;
394 case kMsgDoneEvaluating:
396 _SetEnabled(true);
397 rgb_color fontColor = fExpressionTextView->HighColor();
398 fExpressionTextView->SetFontAndColor(NULL, 0, &fontColor);
400 const char* result;
401 if (message->FindString("error", &result) == B_OK)
402 fExpressionTextView->SetText(result);
403 else if (message->FindString("value", &result) == B_OK)
404 fExpressionTextView->SetValue(result);
406 // stop the message runner
407 delete fEvaluateMessageRunner;
408 fEvaluateMessageRunner = NULL;
409 break;
412 default:
413 BView::MessageReceived(message);
414 break;
420 void
421 CalcView::Draw(BRect updateRect)
423 bool drawBackground = !_IsEmbedded();
425 SetHighColor(fBaseColor);
426 BRect expressionRect(_ExpressionRect());
427 if (updateRect.Intersects(expressionRect)) {
428 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT
429 && expressionRect.Height() >= fCalcIcon->Bounds().Height()) {
430 // render calc icon
431 expressionRect.left = fExpressionTextView->Frame().right + 2;
432 if (drawBackground) {
433 SetHighColor(fBaseColor);
434 FillRect(updateRect & expressionRect);
437 if (fCalcIcon->ColorSpace() == B_RGBA32) {
438 SetDrawingMode(B_OP_ALPHA);
439 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
440 } else {
441 SetDrawingMode(B_OP_OVER);
444 BPoint iconPos;
445 iconPos.x = expressionRect.right - (expressionRect.Width()
446 + fCalcIcon->Bounds().Width()) / 2.0;
447 iconPos.y = expressionRect.top + (expressionRect.Height()
448 - fCalcIcon->Bounds().Height()) / 2.0;
449 DrawBitmap(fCalcIcon, iconPos);
451 SetDrawingMode(B_OP_COPY);
454 // render border around expression text view
455 expressionRect = fExpressionTextView->Frame();
456 expressionRect.InsetBy(-2, -2);
457 if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT && drawBackground) {
458 expressionRect.InsetBy(-2, -2);
459 StrokeRect(expressionRect);
460 expressionRect.InsetBy(1, 1);
461 StrokeRect(expressionRect);
462 expressionRect.InsetBy(1, 1);
465 uint32 flags = 0;
466 if (!drawBackground)
467 flags |= BControlLook::B_BLEND_FRAME;
468 be_control_look->DrawTextControlBorder(this, expressionRect,
469 updateRect, fBaseColor, flags);
472 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
473 return;
475 // calculate grid sizes
476 BRect keypadRect(_KeypadRect());
478 if (be_control_look != NULL) {
479 if (drawBackground)
480 StrokeRect(keypadRect);
481 keypadRect.InsetBy(1, 1);
484 float sizeDisp = keypadRect.top;
485 float sizeCol = (keypadRect.Width() + 1) / (float)fColumns;
486 float sizeRow = (keypadRect.Height() + 1) / (float)fRows;
488 if (!updateRect.Intersects(keypadRect))
489 return;
491 SetFontSize(min_c(sizeRow * kFontScaleY, sizeCol * kFontScaleX));
493 CalcKey* key = fKeypad;
494 for (int row = 0; row < fRows; row++) {
495 for (int col = 0; col < fColumns; col++) {
496 BRect frame;
497 frame.left = keypadRect.left + col * sizeCol;
498 frame.right = keypadRect.left + (col + 1) * sizeCol - 1;
499 frame.top = sizeDisp + row * sizeRow;
500 frame.bottom = sizeDisp + (row + 1) * sizeRow - 1;
502 if (drawBackground) {
503 SetHighColor(fBaseColor);
504 StrokeRect(frame);
506 frame.InsetBy(1, 1);
508 uint32 flags = 0;
509 if (!drawBackground)
510 flags |= BControlLook::B_BLEND_FRAME;
511 if (key->flags != 0)
512 flags |= BControlLook::B_ACTIVATED;
513 flags |= BControlLook::B_IGNORE_OUTLINE;
515 be_control_look->DrawButtonFrame(this, frame, updateRect,
516 fBaseColor, fBaseColor, flags);
518 be_control_look->DrawButtonBackground(this, frame, updateRect,
519 fBaseColor, flags);
521 be_control_look->DrawLabel(this, key->label, frame, updateRect,
522 fBaseColor, flags, BAlignment(B_ALIGN_HORIZONTAL_CENTER,
523 B_ALIGN_VERTICAL_CENTER), &fButtonTextColor);
525 key++;
531 void
532 CalcView::MouseDown(BPoint point)
534 // ensure this view is the current focus
535 if (!fExpressionTextView->IsFocus()) {
536 // Call our version of MakeFocus(), since that will also apply the
537 // num_lock setting.
538 MakeFocus();
541 // read mouse buttons state
542 int32 buttons = 0;
543 Window()->CurrentMessage()->FindInt32("buttons", &buttons);
545 if ((B_PRIMARY_MOUSE_BUTTON & buttons) == 0) {
546 // display popup menu if not primary mouse button
547 BMenuItem* selected;
548 if ((selected = fPopUpMenu->Go(ConvertToScreen(point))) != NULL
549 && selected->Message() != NULL) {
550 Window()->PostMessage(selected->Message(), this);
552 return;
555 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) {
556 if (fCalcIcon != NULL) {
557 BRect bounds(Bounds());
558 bounds.left = bounds.right - fCalcIcon->Bounds().Width();
559 if (bounds.Contains(point)) {
560 // user clicked on calculator icon
561 fExpressionTextView->Clear();
564 return;
567 // calculate grid sizes
568 float sizeDisp = fHeight * kDisplayScaleY;
569 float sizeCol = fWidth / (float)fColumns;
570 float sizeRow = (fHeight - sizeDisp) / (float)fRows;
572 // calculate location within grid
573 int gridCol = (int)floorf(point.x / sizeCol);
574 int gridRow = (int)floorf((point.y - sizeDisp) / sizeRow);
576 // check limits
577 if ((gridCol >= 0) && (gridCol < fColumns)
578 && (gridRow >= 0) && (gridRow < fRows)) {
580 // process key press
581 int key = gridRow * fColumns + gridCol;
582 _FlashKey(key, FLAGS_MOUSE_DOWN);
583 _PressKey(key);
585 // make sure we receive the mouse up!
586 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
591 void
592 CalcView::MouseUp(BPoint point)
594 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
595 return;
597 int keys = fRows * fColumns;
598 for (int i = 0; i < keys; i++) {
599 if (fKeypad[i].flags & FLAGS_MOUSE_DOWN) {
600 _FlashKey(i, 0);
601 break;
607 void
608 CalcView::KeyDown(const char* bytes, int32 numBytes)
610 // if single byte character...
611 if (numBytes == 1) {
613 //printf("Key pressed: %c\n", bytes[0]);
615 switch (bytes[0]) {
617 case B_ENTER:
618 // translate to evaluate key
619 _PressKey("=");
620 break;
622 case B_LEFT_ARROW:
623 case B_BACKSPACE:
624 // translate to backspace key
625 _PressKey("BS");
626 break;
628 case B_SPACE:
629 case B_ESCAPE:
630 case 'c':
631 // translate to clear key
632 _PressKey("C");
633 break;
635 // bracket translation
636 case '[':
637 case '{':
638 _PressKey("(");
639 break;
641 case ']':
642 case '}':
643 _PressKey(")");
644 break;
646 default: {
647 // scan the keymap array for match
648 int keys = fRows * fColumns;
649 for (int i = 0; i < keys; i++) {
650 if (fKeypad[i].keymap[0] == bytes[0]) {
651 _PressKey(i);
652 return;
655 break;
662 void
663 CalcView::MakeFocus(bool focused)
665 if (focused) {
666 // set num lock
667 if (fOptions->auto_num_lock) {
668 set_keyboard_locks(B_NUM_LOCK
669 | (modifiers() & (B_CAPS_LOCK | B_SCROLL_LOCK)));
673 // pass on request to text view
674 fExpressionTextView->MakeFocus(focused);
678 void
679 CalcView::FrameResized(float width, float height)
681 fWidth = width;
682 fHeight = height;
684 // layout expression text view
685 BRect expressionRect = _ExpressionRect();
686 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) {
687 expressionRect.InsetBy(2, 2);
688 expressionRect.right -= ceilf(fCalcIcon->Bounds().Width() * 1.5);
689 } else
690 expressionRect.InsetBy(4, 4);
692 fExpressionTextView->MoveTo(expressionRect.LeftTop());
693 fExpressionTextView->ResizeTo(expressionRect.Width(), expressionRect.Height());
695 // configure expression text view font size and color
696 float sizeDisp = fOptions->keypad_mode == KEYPAD_MODE_COMPACT
697 ? fHeight : fHeight * kDisplayScaleY;
698 BFont font(be_bold_font);
699 font.SetSize(sizeDisp * kExpressionFontScaleY);
700 rgb_color fontColor = fExpressionTextView->HighColor();
701 fExpressionTextView->SetFontAndColor(&font, B_FONT_ALL, &fontColor);
703 expressionRect.OffsetTo(B_ORIGIN);
704 float inset = (expressionRect.Height() - fExpressionTextView->LineHeight(0)) / 2;
705 expressionRect.InsetBy(0, inset);
706 fExpressionTextView->SetTextRect(expressionRect);
707 Invalidate();
711 status_t
712 CalcView::Archive(BMessage* archive, bool deep) const
714 fExpressionTextView->RemoveSelf();
716 // passed on request to parent
717 status_t ret = BView::Archive(archive, deep);
719 const_cast<CalcView*>(this)->AddChild(fExpressionTextView);
721 // save app signature for replicant add-on loading
722 if (ret == B_OK)
723 ret = archive->AddString("add_on", kSignature);
725 // save all the options
726 if (ret == B_OK)
727 ret = SaveSettings(archive);
729 // add class info last
730 if (ret == B_OK)
731 ret = archive->AddString("class", "CalcView");
733 return ret;
737 void
738 CalcView::Cut()
740 Copy(); // copy data to clipboard
741 fExpressionTextView->Clear(); // remove data
745 void
746 CalcView::Copy()
748 // access system clipboard
749 if (be_clipboard->Lock()) {
750 be_clipboard->Clear();
751 BMessage* clipper = be_clipboard->Data();
752 clipper->what = B_MIME_DATA;
753 // TODO: should check return for errors!
754 BString expression = fExpressionTextView->Text();
755 clipper->AddData("text/plain", B_MIME_TYPE,
756 expression.String(), expression.Length());
757 //clipper->PrintToStream();
758 be_clipboard->Commit();
759 be_clipboard->Unlock();
764 void
765 CalcView::Paste(BMessage* message)
767 // handle files first
768 int32 count;
769 if (message->GetInfo("refs", NULL, &count) == B_OK) {
770 entry_ref ref;
771 ssize_t read;
772 BFile file;
773 char buffer[256];
774 memset(buffer, 0, sizeof(buffer));
775 for (int32 i = 0; i < count; i++) {
776 if (message->FindRef("refs", i, &ref) == B_OK) {
777 if (file.SetTo(&ref, B_READ_ONLY) == B_OK) {
778 read = file.Read(buffer, sizeof(buffer) - 1);
779 if (read <= 0)
780 continue;
781 BString expression(buffer);
782 int32 j = expression.Length();
783 while (j > 0 && expression[j - 1] == '\n')
784 j--;
785 expression.Truncate(j);
786 if (expression.Length() > 0)
787 fExpressionTextView->Insert(expression.String());
791 return;
793 // handle color drops
794 // read incoming color
795 const rgb_color* dropColor = NULL;
796 ssize_t dataSize;
797 if (message->FindData("RGBColor", B_RGB_COLOR_TYPE,
798 (const void**)&dropColor, &dataSize) == B_OK
799 && dataSize == sizeof(rgb_color)) {
801 // calculate view relative drop point
802 BPoint dropPoint = ConvertFromScreen(message->DropPoint());
804 // calculate current keypad area
805 float sizeDisp = fHeight * kDisplayScaleY;
806 BRect keypadRect(0.0, sizeDisp, fWidth, fHeight);
808 // check location of color drop
809 if (keypadRect.Contains(dropPoint) && dropColor != NULL) {
810 fBaseColor = *dropColor;
811 fHasCustomBaseColor =
812 fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR);
813 _Colorize();
814 // redraw
815 Invalidate();
818 } else {
819 // look for text/plain MIME data
820 const char* text;
821 ssize_t numBytes;
822 if (message->FindData("text/plain", B_MIME_TYPE,
823 (const void**)&text, &numBytes) == B_OK) {
824 BString temp;
825 temp.Append(text, numBytes);
826 fExpressionTextView->Insert(temp.String());
832 status_t
833 CalcView::SaveSettings(BMessage* archive) const
835 status_t ret = archive ? B_OK : B_BAD_VALUE;
837 // record grid dimensions
838 if (ret == B_OK)
839 ret = archive->AddInt16("cols", fColumns);
841 if (ret == B_OK)
842 ret = archive->AddInt16("rows", fRows);
844 // record color scheme
845 if (ret == B_OK) {
846 ret = archive->AddData("rgbBaseColor", B_RGB_COLOR_TYPE,
847 &fBaseColor, sizeof(rgb_color));
850 if (ret == B_OK) {
851 ret = archive->AddData("rgbDisplay", B_RGB_COLOR_TYPE,
852 &fExpressionBGColor, sizeof(rgb_color));
855 // record current options
856 if (ret == B_OK)
857 ret = fOptions->SaveSettings(archive);
859 // record display text
860 if (ret == B_OK)
861 ret = archive->AddString("displayText", fExpressionTextView->Text());
863 // record expression history
864 if (ret == B_OK)
865 ret = fExpressionTextView->SaveSettings(archive);
867 // record calculator description
868 if (ret == B_OK)
869 ret = archive->AddString("calcDesc", fKeypadDescription);
871 return ret;
875 void
876 CalcView::Evaluate()
878 if (fExpressionTextView->TextLength() == 0) {
879 beep();
880 return;
883 fEvaluateThread = spawn_thread(_EvaluateThread, "Evaluate Thread",
884 B_LOW_PRIORITY, this);
885 if (fEvaluateThread < B_OK) {
886 // failed to create evaluate thread, error out
887 fExpressionTextView->SetText(strerror(fEvaluateThread));
888 return;
891 _AudioFeedback(false);
892 _SetEnabled(false);
893 // Disable input while we evaluate
895 status_t threadStatus = resume_thread(fEvaluateThread);
896 if (threadStatus != B_OK) {
897 // evaluate thread failed to start, error out
898 fExpressionTextView->SetText(strerror(threadStatus));
899 _SetEnabled(true);
900 return;
903 if (fEvaluateMessageRunner == NULL) {
904 BMessage message(kMsgCalculating);
905 fEvaluateMessageRunner = new (std::nothrow) BMessageRunner(
906 BMessenger(this), &message, kCalculatingInterval, 1);
907 status_t runnerStatus = fEvaluateMessageRunner->InitCheck();
908 if (runnerStatus != B_OK)
909 printf("Evaluate Message Runner: %s\n", strerror(runnerStatus));
914 void
915 CalcView::FlashKey(const char* bytes, int32 numBytes)
917 BString temp;
918 temp.Append(bytes, numBytes);
919 int32 key = _KeyForLabel(temp.String());
920 if (key >= 0)
921 _FlashKey(key, FLAGS_FLASH_KEY);
925 void
926 CalcView::ToggleAutoNumlock(void)
928 fOptions->auto_num_lock = !fOptions->auto_num_lock;
929 fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
933 void
934 CalcView::ToggleAudioFeedback(void)
936 fOptions->audio_feedback = !fOptions->audio_feedback;
937 fAudioFeedbackItem->SetMarked(fOptions->audio_feedback);
941 void
942 CalcView::SetDegreeMode(bool degrees)
944 fOptions->degree_mode = degrees;
945 fAngleModeRadianItem->SetMarked(!degrees);
946 fAngleModeDegreeItem->SetMarked(degrees);
950 void
951 CalcView::SetKeypadMode(uint8 mode)
953 if (_IsEmbedded())
954 return;
956 BWindow* window = Window();
957 if (window == NULL)
958 return;
960 if (fOptions->keypad_mode == mode)
961 return;
963 fOptions->keypad_mode = mode;
964 _MarkKeypadItems(fOptions->keypad_mode);
966 float width = fWidth;
967 float height = fHeight;
969 switch (fOptions->keypad_mode) {
970 case KEYPAD_MODE_COMPACT:
972 if (window->Bounds() == Frame()) {
973 window->SetSizeLimits(kMinimumWidthCompact,
974 kMaximumWidthCompact, kMinimumHeightCompact,
975 kMaximumHeightCompact);
976 window->ResizeTo(width, height * kDisplayScaleY);
977 } else
978 ResizeTo(width, height * kDisplayScaleY);
980 break;
983 case KEYPAD_MODE_SCIENTIFIC:
985 free(fKeypadDescription);
986 fKeypadDescription = strdup(kKeypadDescriptionScientific);
987 fRows = 8;
988 _ParseCalcDesc(fKeypadDescription);
990 window->SetSizeLimits(kMinimumWidthScientific,
991 kMaximumWidthScientific, kMinimumHeightScientific,
992 kMaximumHeightScientific);
994 if (width < kMinimumWidthScientific)
995 width = kMinimumWidthScientific;
996 else if (width > kMaximumWidthScientific)
997 width = kMaximumWidthScientific;
999 if (height < kMinimumHeightScientific)
1000 height = kMinimumHeightScientific;
1001 else if (height > kMaximumHeightScientific)
1002 height = kMaximumHeightScientific;
1004 if (width != fWidth || height != fHeight)
1005 ResizeTo(width, height);
1006 else
1007 Invalidate();
1009 break;
1012 case KEYPAD_MODE_BASIC:
1013 default:
1015 free(fKeypadDescription);
1016 fKeypadDescription = strdup(kKeypadDescriptionBasic);
1017 fRows = 4;
1018 _ParseCalcDesc(fKeypadDescription);
1020 window->SetSizeLimits(kMinimumWidthBasic, kMaximumWidthBasic,
1021 kMinimumHeightBasic, kMaximumHeightBasic);
1023 if (width < kMinimumWidthBasic)
1024 width = kMinimumWidthBasic;
1025 else if (width > kMaximumWidthBasic)
1026 width = kMaximumWidthBasic;
1028 if (height < kMinimumHeightBasic)
1029 height = kMinimumHeightBasic;
1030 else if (height > kMaximumHeightBasic)
1031 height = kMaximumHeightBasic;
1033 if (width != fWidth || height != fHeight)
1034 ResizeTo(width, height);
1035 else
1036 Invalidate();
1042 // #pragma mark -
1045 /*static*/ status_t
1046 CalcView::_EvaluateThread(void* data)
1048 CalcView* calcView = reinterpret_cast<CalcView*>(data);
1049 if (calcView == NULL)
1050 return B_BAD_TYPE;
1052 BMessenger messenger(calcView);
1053 if (!messenger.IsValid())
1054 return B_BAD_VALUE;
1056 BString result;
1057 status_t status = acquire_sem(calcView->fEvaluateSemaphore);
1058 if (status == B_OK) {
1059 ExpressionParser parser;
1060 parser.SetDegreeMode(calcView->fOptions->degree_mode);
1061 BString expression(calcView->fExpressionTextView->Text());
1062 try {
1063 result = parser.Evaluate(expression.String());
1064 } catch (ParseException e) {
1065 result << e.message.String() << " at " << (e.position + 1);
1066 status = B_ERROR;
1068 release_sem(calcView->fEvaluateSemaphore);
1069 } else
1070 result = strerror(status);
1072 BMessage message(kMsgDoneEvaluating);
1073 message.AddString(status == B_OK ? "value" : "error", result.String());
1074 messenger.SendMessage(&message);
1076 return status;
1080 void
1081 CalcView::_Init(BMessage* settings)
1083 // create expression text view
1084 fExpressionTextView = new ExpressionTextView(_ExpressionRect(), this);
1085 AddChild(fExpressionTextView);
1087 // read data from archive
1088 _LoadSettings(settings);
1090 // fetch the calc icon for compact view
1091 _FetchAppIcon(fCalcIcon);
1093 fEvaluateSemaphore = create_sem(1, "Evaluate Semaphore");
1097 status_t
1098 CalcView::_LoadSettings(BMessage* archive)
1100 if (!archive)
1101 return B_BAD_VALUE;
1103 // record calculator description
1104 const char* calcDesc;
1105 if (archive->FindString("calcDesc", &calcDesc) < B_OK)
1106 calcDesc = kKeypadDescriptionBasic;
1108 // save calculator description for reference
1109 free(fKeypadDescription);
1110 fKeypadDescription = strdup(calcDesc);
1112 // read grid dimensions
1113 if (archive->FindInt16("cols", &fColumns) < B_OK)
1114 fColumns = 5;
1115 if (archive->FindInt16("rows", &fRows) < B_OK)
1116 fRows = 4;
1118 // read color scheme
1119 const rgb_color* color;
1120 ssize_t size;
1121 if (archive->FindData("rgbBaseColor", B_RGB_COLOR_TYPE,
1122 (const void**)&color, &size) < B_OK
1123 || size != sizeof(rgb_color)) {
1124 fBaseColor = ui_color(B_PANEL_BACKGROUND_COLOR);
1125 puts("Missing rgbBaseColor from CalcView archive!\n");
1126 } else
1127 fBaseColor = *color;
1129 if (archive->FindData("rgbDisplay", B_RGB_COLOR_TYPE,
1130 (const void**)&color, &size) < B_OK
1131 || size != sizeof(rgb_color)) {
1132 fExpressionBGColor = (rgb_color){ 0, 0, 0, 255 };
1133 puts("Missing rgbBaseColor from CalcView archive!\n");
1134 } else {
1135 fExpressionBGColor = *color;
1138 fHasCustomBaseColor = fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR);
1140 // load options
1141 fOptions->LoadSettings(archive);
1143 // load display text
1144 const char* display;
1145 if (archive->FindString("displayText", &display) < B_OK) {
1146 puts("Missing expression text from CalcView archive.\n");
1147 } else {
1148 // init expression text
1149 fExpressionTextView->SetText(display);
1152 // load expression history
1153 fExpressionTextView->LoadSettings(archive);
1155 // parse calculator description
1156 _ParseCalcDesc(fKeypadDescription);
1158 // colorize based on base color.
1159 _Colorize();
1161 return B_OK;
1165 void
1166 CalcView::_ParseCalcDesc(const char* keypadDescription)
1168 // TODO: should calculate dimensions from desc here!
1169 fKeypad = new CalcKey[fRows * fColumns];
1171 // scan through calculator description and assemble keypad
1172 CalcKey* key = fKeypad;
1173 const char* p = keypadDescription;
1175 while (*p != 0) {
1176 // copy label
1177 char* l = key->label;
1178 while (!isspace(*p))
1179 *l++ = *p++;
1180 *l = '\0';
1182 // set code
1183 if (strcmp(key->label, "=") == 0)
1184 strlcpy(key->code, "\n", sizeof(key->code));
1185 else
1186 strlcpy(key->code, key->label, sizeof(key->code));
1188 // set keymap
1189 if (strlen(key->label) == 1)
1190 strlcpy(key->keymap, key->label, sizeof(key->keymap));
1191 else
1192 *key->keymap = '\0';
1194 key->flags = 0;
1196 // add this to the expression text view, so that it
1197 // will forward the respective KeyDown event to us
1198 fExpressionTextView->AddKeypadLabel(key->label);
1200 // advance
1201 while (isspace(*p))
1202 ++p;
1203 key++;
1208 void
1209 CalcView::_PressKey(int key)
1211 if (!fEnabled)
1212 return;
1214 assert(key < (fRows * fColumns));
1215 assert(key >= 0);
1217 if (strcmp(fKeypad[key].label, "BS") == 0) {
1218 // BS means backspace
1219 fExpressionTextView->BackSpace();
1220 } else if (strcmp(fKeypad[key].label, "C") == 0) {
1221 // C means clear
1222 fExpressionTextView->Clear();
1223 } else if (strcmp(fKeypad[key].label, "acos") == 0
1224 || strcmp(fKeypad[key].label, "asin") == 0
1225 || strcmp(fKeypad[key].label, "atan") == 0
1226 || strcmp(fKeypad[key].label, "cbrt") == 0
1227 || strcmp(fKeypad[key].label, "ceil") == 0
1228 || strcmp(fKeypad[key].label, "cos") == 0
1229 || strcmp(fKeypad[key].label, "cosh") == 0
1230 || strcmp(fKeypad[key].label, "exp") == 0
1231 || strcmp(fKeypad[key].label, "floor") == 0
1232 || strcmp(fKeypad[key].label, "log") == 0
1233 || strcmp(fKeypad[key].label, "ln") == 0
1234 || strcmp(fKeypad[key].label, "sin") == 0
1235 || strcmp(fKeypad[key].label, "sinh") == 0
1236 || strcmp(fKeypad[key].label, "sqrt") == 0
1237 || strcmp(fKeypad[key].label, "tan") == 0
1238 || strcmp(fKeypad[key].label, "tanh") == 0) {
1239 int32 labelLen = strlen(fKeypad[key].label);
1240 int32 startSelection = 0;
1241 int32 endSelection = 0;
1242 fExpressionTextView->GetSelection(&startSelection, &endSelection);
1243 if (endSelection > startSelection) {
1244 // There is selected text, put it inbetween the parens
1245 fExpressionTextView->Insert(startSelection, fKeypad[key].label,
1246 labelLen);
1247 fExpressionTextView->Insert(startSelection + labelLen, "(", 1);
1248 fExpressionTextView->Insert(endSelection + labelLen + 1, ")", 1);
1249 // Put the cursor after the ending paren
1250 // Need to cast to BTextView because Select() is protected
1251 // in the InputTextView class
1252 static_cast<BTextView*>(fExpressionTextView)->Select(
1253 endSelection + labelLen + 2, endSelection + labelLen + 2);
1254 } else {
1255 // There is no selected text, insert at the cursor location
1256 fExpressionTextView->Insert(fKeypad[key].label);
1257 fExpressionTextView->Insert("()");
1258 // Put the cursor inside the parens so you can enter an argument
1259 // Need to cast to BTextView because Select() is protected
1260 // in the InputTextView class
1261 static_cast<BTextView*>(fExpressionTextView)->Select(
1262 endSelection + labelLen + 1, endSelection + labelLen + 1);
1264 } else {
1265 // check for evaluation order
1266 if (fKeypad[key].code[0] == '\n') {
1267 fExpressionTextView->ApplyChanges();
1268 } else {
1269 // insert into expression text
1270 fExpressionTextView->Insert(fKeypad[key].code);
1274 _AudioFeedback(true);
1278 void
1279 CalcView::_PressKey(const char* label)
1281 int32 key = _KeyForLabel(label);
1282 if (key >= 0)
1283 _PressKey(key);
1287 int32
1288 CalcView::_KeyForLabel(const char* label) const
1290 int keys = fRows * fColumns;
1291 for (int i = 0; i < keys; i++) {
1292 if (strcmp(fKeypad[i].label, label) == 0) {
1293 return i;
1296 return -1;
1300 void
1301 CalcView::_FlashKey(int32 key, uint32 flashFlags)
1303 if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
1304 return;
1306 if (flashFlags != 0)
1307 fKeypad[key].flags |= flashFlags;
1308 else
1309 fKeypad[key].flags = 0;
1310 Invalidate();
1312 if (fKeypad[key].flags == FLAGS_FLASH_KEY) {
1313 BMessage message(MSG_UNFLASH_KEY);
1314 message.AddInt32("key", key);
1315 BMessageRunner::StartSending(BMessenger(this), &message,
1316 kFlashOnOffInterval, 1);
1321 void
1322 CalcView::_AudioFeedback(bool inBackGround)
1324 // TODO: Use beep events... This interface is not implemented on Haiku
1325 // anyways...
1326 #if 0
1327 if (fOptions->audio_feedback) {
1328 BEntry zimp("key.AIFF");
1329 entry_ref zimp_ref;
1330 zimp.GetRef(&zimp_ref);
1331 play_sound(&zimp_ref, true, false, inBackGround);
1333 #endif
1337 void
1338 CalcView::_Colorize()
1340 // calculate light and dark color from base color
1341 fLightColor.red = (uint8)(fBaseColor.red * 1.25);
1342 fLightColor.green = (uint8)(fBaseColor.green * 1.25);
1343 fLightColor.blue = (uint8)(fBaseColor.blue * 1.25);
1344 fLightColor.alpha = 255;
1346 fDarkColor.red = (uint8)(fBaseColor.red * 0.75);
1347 fDarkColor.green = (uint8)(fBaseColor.green * 0.75);
1348 fDarkColor.blue = (uint8)(fBaseColor.blue * 0.75);
1349 fDarkColor.alpha = 255;
1351 // keypad text color
1352 if (fBaseColor.Brightness() > 100)
1353 fButtonTextColor = (rgb_color){ 0, 0, 0, 255 };
1354 else
1355 fButtonTextColor = (rgb_color){ 255, 255, 255, 255 };
1357 // expression text color
1358 if (fExpressionBGColor.Brightness() > 100)
1359 fExpressionTextColor = (rgb_color){ 0, 0, 0, 255 };
1360 else
1361 fExpressionTextColor = (rgb_color){ 255, 255, 255, 255 };
1365 void
1366 CalcView::_CreatePopUpMenu(bool addKeypadModeMenuItems)
1368 // construct items
1369 fAutoNumlockItem = new BMenuItem(B_TRANSLATE("Enable Num Lock on startup"),
1370 new BMessage(MSG_OPTIONS_AUTO_NUM_LOCK));
1371 fAudioFeedbackItem = new BMenuItem(B_TRANSLATE("Audio Feedback"),
1372 new BMessage(MSG_OPTIONS_AUDIO_FEEDBACK));
1373 fAngleModeRadianItem = new BMenuItem(B_TRANSLATE("Radians"),
1374 new BMessage(MSG_OPTIONS_ANGLE_MODE_RADIAN));
1375 fAngleModeDegreeItem = new BMenuItem(B_TRANSLATE("Degrees"),
1376 new BMessage(MSG_OPTIONS_ANGLE_MODE_DEGREE));
1377 if (addKeypadModeMenuItems) {
1378 fKeypadModeCompactItem = new BMenuItem(B_TRANSLATE("Compact"),
1379 new BMessage(MSG_OPTIONS_KEYPAD_MODE_COMPACT), '0');
1380 fKeypadModeBasicItem = new BMenuItem(B_TRANSLATE("Basic"),
1381 new BMessage(MSG_OPTIONS_KEYPAD_MODE_BASIC), '1');
1382 fKeypadModeScientificItem = new BMenuItem(B_TRANSLATE("Scientific"),
1383 new BMessage(MSG_OPTIONS_KEYPAD_MODE_SCIENTIFIC), '2');
1386 // apply current settings
1387 fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
1388 fAudioFeedbackItem->SetMarked(fOptions->audio_feedback);
1389 fAngleModeRadianItem->SetMarked(!fOptions->degree_mode);
1390 fAngleModeDegreeItem->SetMarked(fOptions->degree_mode);
1392 // construct menu
1393 fPopUpMenu = new BPopUpMenu("pop-up", false, false);
1395 fPopUpMenu->AddItem(fAutoNumlockItem);
1396 // TODO: Enable this when we use beep events which can be configured
1397 // in the Sounds preflet.
1398 //fPopUpMenu->AddItem(fAudioFeedbackItem);
1399 fPopUpMenu->AddSeparatorItem();
1400 fPopUpMenu->AddItem(fAngleModeRadianItem);
1401 fPopUpMenu->AddItem(fAngleModeDegreeItem);
1402 if (addKeypadModeMenuItems) {
1403 fPopUpMenu->AddSeparatorItem();
1404 fPopUpMenu->AddItem(fKeypadModeCompactItem);
1405 fPopUpMenu->AddItem(fKeypadModeBasicItem);
1406 fPopUpMenu->AddItem(fKeypadModeScientificItem);
1407 _MarkKeypadItems(fOptions->keypad_mode);
1412 BRect
1413 CalcView::_ExpressionRect() const
1415 BRect r(0.0, 0.0, fWidth, fHeight);
1416 if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) {
1417 r.bottom = floorf(fHeight * kDisplayScaleY) + 1;
1419 return r;
1423 BRect
1424 CalcView::_KeypadRect() const
1426 BRect r(0.0, 0.0, -1.0, -1.0);
1427 if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) {
1428 r.right = fWidth;
1429 r.bottom = fHeight;
1430 r.top = floorf(fHeight * kDisplayScaleY);
1432 return r;
1436 void
1437 CalcView::_MarkKeypadItems(uint8 keypad_mode)
1439 switch (keypad_mode) {
1440 case KEYPAD_MODE_COMPACT:
1441 fKeypadModeCompactItem->SetMarked(true);
1442 fKeypadModeBasicItem->SetMarked(false);
1443 fKeypadModeScientificItem->SetMarked(false);
1444 break;
1446 case KEYPAD_MODE_SCIENTIFIC:
1447 fKeypadModeCompactItem->SetMarked(false);
1448 fKeypadModeBasicItem->SetMarked(false);
1449 fKeypadModeScientificItem->SetMarked(true);
1450 break;
1452 default: // KEYPAD_MODE_BASIC is the default
1453 fKeypadModeCompactItem->SetMarked(false);
1454 fKeypadModeBasicItem->SetMarked(true);
1455 fKeypadModeScientificItem->SetMarked(false);
1460 void
1461 CalcView::_FetchAppIcon(BBitmap* into)
1463 entry_ref appRef;
1464 status_t status = be_roster->FindApp(kSignature, &appRef);
1465 if (status == B_OK) {
1466 BFile file(&appRef, B_READ_ONLY);
1467 BAppFileInfo appInfo(&file);
1468 status = appInfo.GetIcon(into, B_MINI_ICON);
1470 if (status != B_OK)
1471 memset(into->Bits(), 0, into->BitsLength());
1475 // Returns whether or not CalcView is embedded somewhere, most likely
1476 // the Desktop
1477 bool
1478 CalcView::_IsEmbedded()
1480 return Parent() != NULL && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0;
1484 void
1485 CalcView::_SetEnabled(bool enable)
1487 fEnabled = enable;
1488 fExpressionTextView->MakeSelectable(enable);
1489 fExpressionTextView->MakeEditable(enable);