xref: /haiku/src/apps/deskcalc/CalcView.cpp (revision faf79e7f783976326856422ff006b4c6ae9c3031)
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.
5  *
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
11  */
12 
13 
14 #include "CalcView.h"
15 
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <ctype.h>
20 #include <assert.h>
21 
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>
41 
42 #include <ExpressionParser.h>
43 
44 #include "CalcApplication.h"
45 #include "CalcOptions.h"
46 #include "ExpressionTextView.h"
47 
48 
49 #undef B_TRANSLATION_CONTEXT
50 #define B_TRANSLATION_CONTEXT "CalcView"
51 
52 
53 static const int32 kMsgCalculating = 'calc';
54 static const int32 kMsgAnimateDots = 'dots';
55 static const int32 kMsgDoneEvaluating = 'done';
56 
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;
62 
63 static const bigtime_t kFlashOnOffInterval	= 100000;
64 static const bigtime_t kCalculatingInterval	= 1000000;
65 static const bigtime_t kAnimationInterval	= 333333;
66 
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;
71 
72 // Basic mode size limits are defined in CalcView.h so
73 // that they can be used by the CalcWindow constructor.
74 
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;
79 
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";
86 
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";
97 
98 
99 enum {
100 	FLAGS_FLASH_KEY							= 1 << 0,
101 	FLAGS_MOUSE_DOWN						= 1 << 1
102 };
103 
104 
105 struct CalcView::CalcKey {
106 	char		label[8];
107 	char		code[8];
108 	char		keymap[4];
109 	uint32		flags;
110 //	float		width;
111 };
112 
113 
114 CalcView*
115 CalcView::Instantiate(BMessage* archive)
116 {
117 	if (!validate_instantiation(archive, "CalcView"))
118 		return NULL;
119 
120 	return new CalcView(archive);
121 }
122 
123 
124 CalcView::CalcView(BRect frame, rgb_color rgbBaseColor, BMessage* settings)
125 	:
126 	BView(frame, "DeskCalc", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS),
127 	fColumns(5),
128 	fRows(4),
129 
130 	fBaseColor(rgbBaseColor),
131 	fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }),
132 
133 	fHasCustomBaseColor(rgbBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR)),
134 
135 	fWidth(1),
136 	fHeight(1),
137 
138 	fKeypadDescription(strdup(kKeypadDescriptionBasic)),
139 	fKeypad(NULL),
140 
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
146 
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)
155 {
156 	// tell the app server not to erase our b/g
157 	SetViewColor(B_TRANSPARENT_32_BIT);
158 
159 	_Init(settings);
160 }
161 
162 
163 CalcView::CalcView(BMessage* archive)
164 	:
165 	BView(archive),
166 	fColumns(5),
167 	fRows(4),
168 
169 	fBaseColor(ui_color(B_PANEL_BACKGROUND_COLOR)),
170 	fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }),
171 
172 	fHasCustomBaseColor(false),
173 
174 	fWidth(1),
175 	fHeight(1),
176 
177 	fKeypadDescription(strdup(kKeypadDescriptionBasic)),
178 	fKeypad(NULL),
179 
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
185 
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)
194 {
195 	// Do not restore the follow mode, in shelfs, we never follow.
196 	SetResizingMode(B_FOLLOW_NONE);
197 
198 	_Init(archive);
199 }
200 
201 
202 CalcView::~CalcView()
203 {
204 	delete fKeypad;
205 	delete fOptions;
206 	free(fKeypadDescription);
207 	delete fEvaluateMessageRunner;
208 	delete_sem(fEvaluateSemaphore);
209 }
210 
211 
212 void
213 CalcView::AttachedToWindow()
214 {
215 	if (be_control_look == NULL)
216 		SetFont(be_bold_font);
217 
218 	BRect frame(Frame());
219 	FrameResized(frame.Width(), frame.Height());
220 
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;
225 	}
226 
227 	// create and attach the pop-up menu
228 	_CreatePopUpMenu(addKeypadModeMenuItems);
229 
230 	if (addKeypadModeMenuItems)
231 		SetKeypadMode(fOptions->keypad_mode);
232 }
233 
234 
235 void
236 CalcView::MessageReceived(BMessage* message)
237 {
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();
243 		}
244 
245 		return;
246 	}
247 
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;
255 
256 			case MSG_OPTIONS_AUDIO_FEEDBACK:
257 				ToggleAudioFeedback();
258 				return;
259 
260 			case MSG_OPTIONS_ANGLE_MODE_RADIAN:
261 				SetDegreeMode(false);
262 				return;
263 
264 			case MSG_OPTIONS_ANGLE_MODE_DEGREE:
265 				SetDegreeMode(true);
266 				return;
267 		}
268 	}
269 
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) {
278 
279 			// handle "cut"
280 			case B_CUT:
281 				Cut();
282 				break;
283 
284 			// handle copy
285 			case B_COPY:
286 				Copy();
287 				break;
288 
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();
297 				}
298 				break;
299 
300 			// (replicant) about box requested
301 			case B_ABOUT_REQUESTED:
302 			{
303 				BAboutWindow* window = new BAboutWindow(kAppName, kSignature);
304 
305 				// create the about window
306 				const char* extraCopyrights[] = {
307 					"1997, 1998 R3 Software Ltd.",
308 					NULL
309 				};
310 
311 				const char* authors[] = {
312 					"Stephan Aßmus",
313 					"John Scipione",
314 					"Timothy Wayper",
315 					"Ingo Weinhold",
316 					NULL
317 				};
318 
319 				window->AddCopyright(2006, "Haiku, Inc.", extraCopyrights);
320 				window->AddAuthors(authors);
321 
322 				window->Show();
323 
324 				break;
325 			}
326 
327 			case MSG_UNFLASH_KEY:
328 			{
329 				int32 key;
330 				if (message->FindInt32("key", &key) == B_OK)
331 					_FlashKey(key, 0);
332 
333 				break;
334 			}
335 
336 			case kMsgAnimateDots:
337 			{
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;
346 				}
347 
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);
358 				}
359 
360 				dot++;
361 				if (dot == 4)
362 					dot = 1;
363 
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;
370 			}
371 
372 			case kMsgCalculating:
373 			{
374 				// calculation has taken more than 3 seconds
375 				if (fEnabled) {
376 					// stop the message runner
377 					delete fEvaluateMessageRunner;
378 					fEvaluateMessageRunner = NULL;
379 					break;
380 				}
381 
382 				BString calculating;
383 				calculating << B_TRANSLATE("Calculating") << "...";
384 				fExpressionTextView->SetText(calculating.String());
385 
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;
392 			}
393 
394 			case kMsgDoneEvaluating:
395 			{
396 				_SetEnabled(true);
397 				rgb_color fontColor = fExpressionTextView->HighColor();
398 				fExpressionTextView->SetFontAndColor(NULL, 0, &fontColor);
399 
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);
405 
406 				// stop the message runner
407 				delete fEvaluateMessageRunner;
408 				fEvaluateMessageRunner = NULL;
409 				break;
410 			}
411 
412 			default:
413 				BView::MessageReceived(message);
414 				break;
415 		}
416 	}
417 }
418 
419 
420 void
421 CalcView::Draw(BRect updateRect)
422 {
423 	bool drawBackground = !_IsEmbedded();
424 
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);
435 			}
436 
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);
442 			}
443 
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);
450 
451 			SetDrawingMode(B_OP_COPY);
452 		}
453 
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);
463 		}
464 
465 		uint32 flags = 0;
466 		if (!drawBackground)
467 			flags |= BControlLook::B_BLEND_FRAME;
468 		be_control_look->DrawTextControlBorder(this, expressionRect,
469 			updateRect, fBaseColor, flags);
470 	}
471 
472 	if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
473 		return;
474 
475 	// calculate grid sizes
476 	BRect keypadRect(_KeypadRect());
477 
478 	if (be_control_look != NULL) {
479 		if (drawBackground)
480 			StrokeRect(keypadRect);
481 		keypadRect.InsetBy(1, 1);
482 	}
483 
484 	float sizeDisp = keypadRect.top;
485 	float sizeCol = (keypadRect.Width() + 1) / (float)fColumns;
486 	float sizeRow = (keypadRect.Height() + 1) / (float)fRows;
487 
488 	if (!updateRect.Intersects(keypadRect))
489 		return;
490 
491 	SetFontSize(min_c(sizeRow * kFontScaleY, sizeCol * kFontScaleX));
492 
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;
501 
502 			if (drawBackground) {
503 				SetHighColor(fBaseColor);
504 				StrokeRect(frame);
505 			}
506 			frame.InsetBy(1, 1);
507 
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;
514 
515 			be_control_look->DrawButtonFrame(this, frame, updateRect,
516 				fBaseColor, fBaseColor, flags);
517 
518 			be_control_look->DrawButtonBackground(this, frame, updateRect,
519 				fBaseColor, flags);
520 
521 			be_control_look->DrawLabel(this, key->label, frame, updateRect,
522 				fBaseColor, flags, BAlignment(B_ALIGN_HORIZONTAL_CENTER,
523 					B_ALIGN_VERTICAL_CENTER), &fButtonTextColor);
524 
525 			key++;
526 		}
527 	}
528 }
529 
530 
531 void
532 CalcView::MouseDown(BPoint point)
533 {
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();
539 	}
540 
541 	// read mouse buttons state
542 	int32 buttons = 0;
543 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
544 
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);
551 		}
552 		return;
553 	}
554 
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();
562 			}
563 		}
564 		return;
565 	}
566 
567 	// calculate grid sizes
568 	float sizeDisp = fHeight * kDisplayScaleY;
569 	float sizeCol = fWidth / (float)fColumns;
570 	float sizeRow = (fHeight - sizeDisp) / (float)fRows;
571 
572 	// calculate location within grid
573 	int gridCol = (int)floorf(point.x / sizeCol);
574 	int gridRow = (int)floorf((point.y - sizeDisp) / sizeRow);
575 
576 	// check limits
577 	if ((gridCol >= 0) && (gridCol < fColumns)
578 		&& (gridRow >= 0) && (gridRow < fRows)) {
579 
580 		// process key press
581 		int key = gridRow * fColumns + gridCol;
582 		_FlashKey(key, FLAGS_MOUSE_DOWN);
583 		_PressKey(key);
584 
585 		// make sure we receive the mouse up!
586 		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
587 	}
588 }
589 
590 
591 void
592 CalcView::MouseUp(BPoint point)
593 {
594 	if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
595 		return;
596 
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;
602 		}
603 	}
604 }
605 
606 
607 void
608 CalcView::KeyDown(const char* bytes, int32 numBytes)
609 {
610 	// if single byte character...
611 	if (numBytes == 1) {
612 
613 		//printf("Key pressed: %c\n", bytes[0]);
614 
615 		switch (bytes[0]) {
616 
617 			case B_ENTER:
618 				// translate to evaluate key
619 				_PressKey("=");
620 				break;
621 
622 			case B_LEFT_ARROW:
623 			case B_BACKSPACE:
624 				// translate to backspace key
625 				_PressKey("BS");
626 				break;
627 
628 			case B_SPACE:
629 			case B_ESCAPE:
630 			case 'c':
631 				// translate to clear key
632 				_PressKey("C");
633 				break;
634 
635 			// bracket translation
636 			case '[':
637 			case '{':
638 				_PressKey("(");
639 				break;
640 
641 			case ']':
642 			case '}':
643 				_PressKey(")");
644 				break;
645 
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;
653 					}
654 				}
655 				break;
656 			}
657 		}
658 	}
659 }
660 
661 
662 void
663 CalcView::MakeFocus(bool focused)
664 {
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)));
670 		}
671 	}
672 
673 	// pass on request to text view
674 	fExpressionTextView->MakeFocus(focused);
675 }
676 
677 
678 void
679 CalcView::FrameResized(float width, float height)
680 {
681 	fWidth = width;
682 	fHeight = height;
683 
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);
691 
692 	fExpressionTextView->MoveTo(expressionRect.LeftTop());
693 	fExpressionTextView->ResizeTo(expressionRect.Width(), expressionRect.Height());
694 
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);
702 
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();
708 }
709 
710 
711 status_t
712 CalcView::Archive(BMessage* archive, bool deep) const
713 {
714 	fExpressionTextView->RemoveSelf();
715 
716 	// passed on request to parent
717 	status_t ret = BView::Archive(archive, deep);
718 
719 	const_cast<CalcView*>(this)->AddChild(fExpressionTextView);
720 
721 	// save app signature for replicant add-on loading
722 	if (ret == B_OK)
723 		ret = archive->AddString("add_on", kSignature);
724 
725 	// save all the options
726 	if (ret == B_OK)
727 		ret = SaveSettings(archive);
728 
729 	// add class info last
730 	if (ret == B_OK)
731 		ret = archive->AddString("class", "CalcView");
732 
733 	return ret;
734 }
735 
736 
737 void
738 CalcView::Cut()
739 {
740 	Copy();	// copy data to clipboard
741 	fExpressionTextView->Clear(); // remove data
742 }
743 
744 
745 void
746 CalcView::Copy()
747 {
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();
760 	}
761 }
762 
763 
764 void
765 CalcView::Paste(BMessage* message)
766 {
767 	// handle color drops first
768 	// read incoming color
769 	const rgb_color* dropColor = NULL;
770 	ssize_t dataSize;
771 	if (message->FindData("RGBColor", B_RGB_COLOR_TYPE,
772 			(const void**)&dropColor, &dataSize) == B_OK
773 		&& dataSize == sizeof(rgb_color)) {
774 
775 		// calculate view relative drop point
776 		BPoint dropPoint = ConvertFromScreen(message->DropPoint());
777 
778 		// calculate current keypad area
779 		float sizeDisp = fHeight * kDisplayScaleY;
780 		BRect keypadRect(0.0, sizeDisp, fWidth, fHeight);
781 
782 		// check location of color drop
783 		if (keypadRect.Contains(dropPoint) && dropColor != NULL) {
784 			fBaseColor = *dropColor;
785 			fHasCustomBaseColor =
786 				fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR);
787 			_Colorize();
788 			// redraw
789 			Invalidate();
790 		}
791 
792 	} else {
793 		// look for text/plain MIME data
794 		const char* text;
795 		ssize_t numBytes;
796 		if (message->FindData("text/plain", B_MIME_TYPE,
797 				(const void**)&text, &numBytes) == B_OK) {
798 			BString temp;
799 			temp.Append(text, numBytes);
800 			fExpressionTextView->Insert(temp.String());
801 		}
802 	}
803 }
804 
805 
806 status_t
807 CalcView::SaveSettings(BMessage* archive) const
808 {
809 	status_t ret = archive ? B_OK : B_BAD_VALUE;
810 
811 	// record grid dimensions
812 	if (ret == B_OK)
813 		ret = archive->AddInt16("cols", fColumns);
814 
815 	if (ret == B_OK)
816 		ret = archive->AddInt16("rows", fRows);
817 
818 	// record color scheme
819 	if (ret == B_OK) {
820 		ret = archive->AddData("rgbBaseColor", B_RGB_COLOR_TYPE,
821 			&fBaseColor, sizeof(rgb_color));
822 	}
823 
824 	if (ret == B_OK) {
825 		ret = archive->AddData("rgbDisplay", B_RGB_COLOR_TYPE,
826 			&fExpressionBGColor, sizeof(rgb_color));
827 	}
828 
829 	// record current options
830 	if (ret == B_OK)
831 		ret = fOptions->SaveSettings(archive);
832 
833 	// record display text
834 	if (ret == B_OK)
835 		ret = archive->AddString("displayText", fExpressionTextView->Text());
836 
837 	// record expression history
838 	if (ret == B_OK)
839 		ret = fExpressionTextView->SaveSettings(archive);
840 
841 	// record calculator description
842 	if (ret == B_OK)
843 		ret = archive->AddString("calcDesc", fKeypadDescription);
844 
845 	return ret;
846 }
847 
848 
849 void
850 CalcView::Evaluate()
851 {
852 	if (fExpressionTextView->TextLength() == 0) {
853 		beep();
854 		return;
855 	}
856 
857 	fEvaluateThread = spawn_thread(_EvaluateThread, "Evaluate Thread",
858 		B_LOW_PRIORITY, this);
859 	if (fEvaluateThread < B_OK) {
860 		// failed to create evaluate thread, error out
861 		fExpressionTextView->SetText(strerror(fEvaluateThread));
862 		return;
863 	}
864 
865 	_AudioFeedback(false);
866 	_SetEnabled(false);
867 		// Disable input while we evaluate
868 
869 	status_t threadStatus = resume_thread(fEvaluateThread);
870 	if (threadStatus != B_OK) {
871 		// evaluate thread failed to start, error out
872 		fExpressionTextView->SetText(strerror(threadStatus));
873 		_SetEnabled(true);
874 		return;
875 	}
876 
877 	if (fEvaluateMessageRunner == NULL) {
878 		BMessage message(kMsgCalculating);
879 		fEvaluateMessageRunner = new (std::nothrow) BMessageRunner(
880 			BMessenger(this), &message, kCalculatingInterval, 1);
881 		status_t runnerStatus = fEvaluateMessageRunner->InitCheck();
882 		if (runnerStatus != B_OK)
883 			printf("Evaluate Message Runner: %s\n", strerror(runnerStatus));
884 	}
885 }
886 
887 
888 void
889 CalcView::FlashKey(const char* bytes, int32 numBytes)
890 {
891 	BString temp;
892 	temp.Append(bytes, numBytes);
893 	int32 key = _KeyForLabel(temp.String());
894 	if (key >= 0)
895 		_FlashKey(key, FLAGS_FLASH_KEY);
896 }
897 
898 
899 void
900 CalcView::ToggleAutoNumlock(void)
901 {
902 	fOptions->auto_num_lock = !fOptions->auto_num_lock;
903 	fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
904 }
905 
906 
907 void
908 CalcView::ToggleAudioFeedback(void)
909 {
910 	fOptions->audio_feedback = !fOptions->audio_feedback;
911 	fAudioFeedbackItem->SetMarked(fOptions->audio_feedback);
912 }
913 
914 
915 void
916 CalcView::SetDegreeMode(bool degrees)
917 {
918 	fOptions->degree_mode = degrees;
919 	fAngleModeRadianItem->SetMarked(!degrees);
920 	fAngleModeDegreeItem->SetMarked(degrees);
921 }
922 
923 
924 void
925 CalcView::SetKeypadMode(uint8 mode)
926 {
927 	if (_IsEmbedded())
928 		return;
929 
930 	BWindow* window = Window();
931 	if (window == NULL)
932 		return;
933 
934 	if (fOptions->keypad_mode == mode)
935 		return;
936 
937 	fOptions->keypad_mode = mode;
938 	_MarkKeypadItems(fOptions->keypad_mode);
939 
940 	float width = fWidth;
941 	float height = fHeight;
942 
943 	switch (fOptions->keypad_mode) {
944 		case KEYPAD_MODE_COMPACT:
945 		{
946 			if (window->Bounds() == Frame()) {
947 				window->SetSizeLimits(kMinimumWidthCompact,
948 					kMaximumWidthCompact, kMinimumHeightCompact,
949 					kMaximumHeightCompact);
950 				window->ResizeTo(width, height * kDisplayScaleY);
951 			} else
952 				ResizeTo(width, height * kDisplayScaleY);
953 
954 			break;
955 		}
956 
957 		case KEYPAD_MODE_SCIENTIFIC:
958 		{
959 			free(fKeypadDescription);
960 			fKeypadDescription = strdup(kKeypadDescriptionScientific);
961 			fRows = 8;
962 			_ParseCalcDesc(fKeypadDescription);
963 
964 			window->SetSizeLimits(kMinimumWidthScientific,
965 				kMaximumWidthScientific, kMinimumHeightScientific,
966 				kMaximumHeightScientific);
967 
968 			if (width < kMinimumWidthScientific)
969 				width = kMinimumWidthScientific;
970 			else if (width > kMaximumWidthScientific)
971 				width = kMaximumWidthScientific;
972 
973 			if (height < kMinimumHeightScientific)
974 				height = kMinimumHeightScientific;
975 			else if (height > kMaximumHeightScientific)
976 				height = kMaximumHeightScientific;
977 
978 			if (width != fWidth || height != fHeight)
979 				ResizeTo(width, height);
980 			else
981 				Invalidate();
982 
983 			break;
984 		}
985 
986 		case KEYPAD_MODE_BASIC:
987 		default:
988 		{
989 			free(fKeypadDescription);
990 			fKeypadDescription = strdup(kKeypadDescriptionBasic);
991 			fRows = 4;
992 			_ParseCalcDesc(fKeypadDescription);
993 
994 			window->SetSizeLimits(kMinimumWidthBasic, kMaximumWidthBasic,
995 				kMinimumHeightBasic, kMaximumHeightBasic);
996 
997 			if (width < kMinimumWidthBasic)
998 				width = kMinimumWidthBasic;
999 			else if (width > kMaximumWidthBasic)
1000 				width = kMaximumWidthBasic;
1001 
1002 			if (height < kMinimumHeightBasic)
1003 				height = kMinimumHeightBasic;
1004 			else if (height > kMaximumHeightBasic)
1005 				height = kMaximumHeightBasic;
1006 
1007 			if (width != fWidth || height != fHeight)
1008 				ResizeTo(width, height);
1009 			else
1010 				Invalidate();
1011 		}
1012 	}
1013 }
1014 
1015 
1016 // #pragma mark -
1017 
1018 
1019 /*static*/ status_t
1020 CalcView::_EvaluateThread(void* data)
1021 {
1022 	CalcView* calcView = reinterpret_cast<CalcView*>(data);
1023 	if (calcView == NULL)
1024 		return B_BAD_TYPE;
1025 
1026 	BMessenger messenger(calcView);
1027 	if (!messenger.IsValid())
1028 		return B_BAD_VALUE;
1029 
1030 	BString result;
1031 	status_t status = acquire_sem(calcView->fEvaluateSemaphore);
1032 	if (status == B_OK) {
1033 		ExpressionParser parser;
1034 		parser.SetDegreeMode(calcView->fOptions->degree_mode);
1035 		BString expression(calcView->fExpressionTextView->Text());
1036 		try {
1037 			result = parser.Evaluate(expression.String());
1038 		} catch (ParseException e) {
1039 			result << e.message.String() << " at " << (e.position + 1);
1040 			status = B_ERROR;
1041 		}
1042 		release_sem(calcView->fEvaluateSemaphore);
1043 	} else
1044 		result = strerror(status);
1045 
1046 	BMessage message(kMsgDoneEvaluating);
1047 	message.AddString(status == B_OK ? "value" : "error", result.String());
1048 	messenger.SendMessage(&message);
1049 
1050 	return status;
1051 }
1052 
1053 
1054 void
1055 CalcView::_Init(BMessage* settings)
1056 {
1057 	// create expression text view
1058 	fExpressionTextView = new ExpressionTextView(_ExpressionRect(), this);
1059 	AddChild(fExpressionTextView);
1060 
1061 	// read data from archive
1062 	_LoadSettings(settings);
1063 
1064 	// fetch the calc icon for compact view
1065 	_FetchAppIcon(fCalcIcon);
1066 
1067 	fEvaluateSemaphore = create_sem(1, "Evaluate Semaphore");
1068 }
1069 
1070 
1071 status_t
1072 CalcView::_LoadSettings(BMessage* archive)
1073 {
1074 	if (!archive)
1075 		return B_BAD_VALUE;
1076 
1077 	// record calculator description
1078 	const char* calcDesc;
1079 	if (archive->FindString("calcDesc", &calcDesc) < B_OK)
1080 		calcDesc = kKeypadDescriptionBasic;
1081 
1082 	// save calculator description for reference
1083 	free(fKeypadDescription);
1084 	fKeypadDescription = strdup(calcDesc);
1085 
1086 	// read grid dimensions
1087 	if (archive->FindInt16("cols", &fColumns) < B_OK)
1088 		fColumns = 5;
1089 	if (archive->FindInt16("rows", &fRows) < B_OK)
1090 		fRows = 4;
1091 
1092 	// read color scheme
1093 	const rgb_color* color;
1094 	ssize_t size;
1095 	if (archive->FindData("rgbBaseColor", B_RGB_COLOR_TYPE,
1096 			(const void**)&color, &size) < B_OK
1097 		|| size != sizeof(rgb_color)) {
1098 		fBaseColor = ui_color(B_PANEL_BACKGROUND_COLOR);
1099 		puts("Missing rgbBaseColor from CalcView archive!\n");
1100 	} else
1101 		fBaseColor = *color;
1102 
1103 	if (archive->FindData("rgbDisplay", B_RGB_COLOR_TYPE,
1104 			(const void**)&color, &size) < B_OK
1105 		|| size != sizeof(rgb_color)) {
1106 		fExpressionBGColor = (rgb_color){ 0, 0, 0, 255 };
1107 		puts("Missing rgbBaseColor from CalcView archive!\n");
1108 	} else {
1109 		fExpressionBGColor = *color;
1110 	}
1111 
1112 	fHasCustomBaseColor = fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR);
1113 
1114 	// load options
1115 	fOptions->LoadSettings(archive);
1116 
1117 	// load display text
1118 	const char* display;
1119 	if (archive->FindString("displayText", &display) < B_OK) {
1120 		puts("Missing expression text from CalcView archive.\n");
1121 	} else {
1122 		// init expression text
1123 		fExpressionTextView->SetText(display);
1124 	}
1125 
1126 	// load expression history
1127 	fExpressionTextView->LoadSettings(archive);
1128 
1129 	// parse calculator description
1130 	_ParseCalcDesc(fKeypadDescription);
1131 
1132 	// colorize based on base color.
1133 	_Colorize();
1134 
1135 	return B_OK;
1136 }
1137 
1138 
1139 void
1140 CalcView::_ParseCalcDesc(const char* keypadDescription)
1141 {
1142 	// TODO: should calculate dimensions from desc here!
1143 	fKeypad = new CalcKey[fRows * fColumns];
1144 
1145 	// scan through calculator description and assemble keypad
1146 	CalcKey* key = fKeypad;
1147 	const char* p = keypadDescription;
1148 
1149 	while (*p != 0) {
1150 		// copy label
1151 		char* l = key->label;
1152 		while (!isspace(*p))
1153 			*l++ = *p++;
1154 		*l = '\0';
1155 
1156 		// set code
1157 		if (strcmp(key->label, "=") == 0)
1158 			strlcpy(key->code, "\n", sizeof(key->code));
1159 		else
1160 			strlcpy(key->code, key->label, sizeof(key->code));
1161 
1162 		// set keymap
1163 		if (strlen(key->label) == 1)
1164 			strlcpy(key->keymap, key->label, sizeof(key->keymap));
1165 		else
1166 			*key->keymap = '\0';
1167 
1168 		key->flags = 0;
1169 
1170 		// add this to the expression text view, so that it
1171 		// will forward the respective KeyDown event to us
1172 		fExpressionTextView->AddKeypadLabel(key->label);
1173 
1174 		// advance
1175 		while (isspace(*p))
1176 			++p;
1177 		key++;
1178 	}
1179 }
1180 
1181 
1182 void
1183 CalcView::_PressKey(int key)
1184 {
1185 	if (!fEnabled)
1186 		return;
1187 
1188 	assert(key < (fRows * fColumns));
1189 	assert(key >= 0);
1190 
1191 	if (strcmp(fKeypad[key].label, "BS") == 0) {
1192 		// BS means backspace
1193 		fExpressionTextView->BackSpace();
1194 	} else if (strcmp(fKeypad[key].label, "C") == 0) {
1195 		// C means clear
1196 		fExpressionTextView->Clear();
1197 	} else if (strcmp(fKeypad[key].label, "acos") == 0
1198 		|| strcmp(fKeypad[key].label, "asin") == 0
1199 		|| strcmp(fKeypad[key].label, "atan") == 0
1200 		|| strcmp(fKeypad[key].label, "cbrt") == 0
1201 		|| strcmp(fKeypad[key].label, "ceil") == 0
1202 		|| strcmp(fKeypad[key].label, "cos") == 0
1203 		|| strcmp(fKeypad[key].label, "cosh") == 0
1204 		|| strcmp(fKeypad[key].label, "exp") == 0
1205 		|| strcmp(fKeypad[key].label, "floor") == 0
1206 		|| strcmp(fKeypad[key].label, "log") == 0
1207 		|| strcmp(fKeypad[key].label, "ln") == 0
1208 		|| strcmp(fKeypad[key].label, "sin") == 0
1209 		|| strcmp(fKeypad[key].label, "sinh") == 0
1210 		|| strcmp(fKeypad[key].label, "sqrt") == 0
1211 		|| strcmp(fKeypad[key].label, "tan") == 0
1212 		|| strcmp(fKeypad[key].label, "tanh") == 0) {
1213 		int32 labelLen = strlen(fKeypad[key].label);
1214 		int32 startSelection = 0;
1215 		int32 endSelection = 0;
1216 		fExpressionTextView->GetSelection(&startSelection, &endSelection);
1217 		if (endSelection > startSelection) {
1218 			// There is selected text, put it inbetween the parens
1219 			fExpressionTextView->Insert(startSelection, fKeypad[key].label,
1220 				labelLen);
1221 			fExpressionTextView->Insert(startSelection + labelLen, "(", 1);
1222 			fExpressionTextView->Insert(endSelection + labelLen + 1, ")", 1);
1223 			// Put the cursor after the ending paren
1224 			// Need to cast to BTextView because Select() is protected
1225 			// in the InputTextView class
1226 			static_cast<BTextView*>(fExpressionTextView)->Select(
1227 				endSelection + labelLen + 2, endSelection + labelLen + 2);
1228 		} else {
1229 			// There is no selected text, insert at the cursor location
1230 			fExpressionTextView->Insert(fKeypad[key].label);
1231 			fExpressionTextView->Insert("()");
1232 			// Put the cursor inside the parens so you can enter an argument
1233 			// Need to cast to BTextView because Select() is protected
1234 			// in the InputTextView class
1235 			static_cast<BTextView*>(fExpressionTextView)->Select(
1236 				endSelection + labelLen + 1, endSelection + labelLen + 1);
1237 		}
1238 	} else {
1239 		// check for evaluation order
1240 		if (fKeypad[key].code[0] == '\n') {
1241 			fExpressionTextView->ApplyChanges();
1242 		} else {
1243 			// insert into expression text
1244 			fExpressionTextView->Insert(fKeypad[key].code);
1245 		}
1246 	}
1247 
1248 	_AudioFeedback(true);
1249 }
1250 
1251 
1252 void
1253 CalcView::_PressKey(const char* label)
1254 {
1255 	int32 key = _KeyForLabel(label);
1256 	if (key >= 0)
1257 		_PressKey(key);
1258 }
1259 
1260 
1261 int32
1262 CalcView::_KeyForLabel(const char* label) const
1263 {
1264 	int keys = fRows * fColumns;
1265 	for (int i = 0; i < keys; i++) {
1266 		if (strcmp(fKeypad[i].label, label) == 0) {
1267 			return i;
1268 		}
1269 	}
1270 	return -1;
1271 }
1272 
1273 
1274 void
1275 CalcView::_FlashKey(int32 key, uint32 flashFlags)
1276 {
1277 	if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
1278 		return;
1279 
1280 	if (flashFlags != 0)
1281 		fKeypad[key].flags |= flashFlags;
1282 	else
1283 		fKeypad[key].flags = 0;
1284 	Invalidate();
1285 
1286 	if (fKeypad[key].flags == FLAGS_FLASH_KEY) {
1287 		BMessage message(MSG_UNFLASH_KEY);
1288 		message.AddInt32("key", key);
1289 		BMessageRunner::StartSending(BMessenger(this), &message,
1290 			kFlashOnOffInterval, 1);
1291 	}
1292 }
1293 
1294 
1295 void
1296 CalcView::_AudioFeedback(bool inBackGround)
1297 {
1298 	// TODO: Use beep events... This interface is not implemented on Haiku
1299 	// anyways...
1300 #if 0
1301 	if (fOptions->audio_feedback) {
1302 		BEntry zimp("key.AIFF");
1303 		entry_ref zimp_ref;
1304 		zimp.GetRef(&zimp_ref);
1305 		play_sound(&zimp_ref, true, false, inBackGround);
1306 	}
1307 #endif
1308 }
1309 
1310 
1311 void
1312 CalcView::_Colorize()
1313 {
1314 	// calculate light and dark color from base color
1315 	fLightColor.red		= (uint8)(fBaseColor.red * 1.25);
1316 	fLightColor.green	= (uint8)(fBaseColor.green * 1.25);
1317 	fLightColor.blue	= (uint8)(fBaseColor.blue * 1.25);
1318 	fLightColor.alpha	= 255;
1319 
1320 	fDarkColor.red		= (uint8)(fBaseColor.red * 0.75);
1321 	fDarkColor.green	= (uint8)(fBaseColor.green * 0.75);
1322 	fDarkColor.blue		= (uint8)(fBaseColor.blue * 0.75);
1323 	fDarkColor.alpha	= 255;
1324 
1325 	// keypad text color
1326 	if (fBaseColor.Brightness() > 100)
1327 		fButtonTextColor = (rgb_color){ 0, 0, 0, 255 };
1328 	else
1329 		fButtonTextColor = (rgb_color){ 255, 255, 255, 255 };
1330 
1331 	// expression text color
1332 	if (fExpressionBGColor.Brightness() > 100)
1333 		fExpressionTextColor = (rgb_color){ 0, 0, 0, 255 };
1334 	else
1335 		fExpressionTextColor = (rgb_color){ 255, 255, 255, 255 };
1336 }
1337 
1338 
1339 void
1340 CalcView::_CreatePopUpMenu(bool addKeypadModeMenuItems)
1341 {
1342 	// construct items
1343 	fAutoNumlockItem = new BMenuItem(B_TRANSLATE("Enable Num Lock on startup"),
1344 		new BMessage(MSG_OPTIONS_AUTO_NUM_LOCK));
1345 	fAudioFeedbackItem = new BMenuItem(B_TRANSLATE("Audio Feedback"),
1346 		new BMessage(MSG_OPTIONS_AUDIO_FEEDBACK));
1347 	fAngleModeRadianItem = new BMenuItem(B_TRANSLATE("Radians"),
1348 		new BMessage(MSG_OPTIONS_ANGLE_MODE_RADIAN));
1349 	fAngleModeDegreeItem = new BMenuItem(B_TRANSLATE("Degrees"),
1350 		new BMessage(MSG_OPTIONS_ANGLE_MODE_DEGREE));
1351 	if (addKeypadModeMenuItems) {
1352 		fKeypadModeCompactItem = new BMenuItem(B_TRANSLATE("Compact"),
1353 			new BMessage(MSG_OPTIONS_KEYPAD_MODE_COMPACT), '0');
1354 		fKeypadModeBasicItem = new BMenuItem(B_TRANSLATE("Basic"),
1355 			new BMessage(MSG_OPTIONS_KEYPAD_MODE_BASIC), '1');
1356 		fKeypadModeScientificItem = new BMenuItem(B_TRANSLATE("Scientific"),
1357 			new BMessage(MSG_OPTIONS_KEYPAD_MODE_SCIENTIFIC), '2');
1358 	}
1359 
1360 	// apply current settings
1361 	fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
1362 	fAudioFeedbackItem->SetMarked(fOptions->audio_feedback);
1363 	fAngleModeRadianItem->SetMarked(!fOptions->degree_mode);
1364 	fAngleModeDegreeItem->SetMarked(fOptions->degree_mode);
1365 
1366 	// construct menu
1367 	fPopUpMenu = new BPopUpMenu("pop-up", false, false);
1368 
1369 	fPopUpMenu->AddItem(fAutoNumlockItem);
1370 	// TODO: Enable this when we use beep events which can be configured
1371 	// in the Sounds preflet.
1372 	//fPopUpMenu->AddItem(fAudioFeedbackItem);
1373 	fPopUpMenu->AddSeparatorItem();
1374 	fPopUpMenu->AddItem(fAngleModeRadianItem);
1375 	fPopUpMenu->AddItem(fAngleModeDegreeItem);
1376 	if (addKeypadModeMenuItems) {
1377 		fPopUpMenu->AddSeparatorItem();
1378 		fPopUpMenu->AddItem(fKeypadModeCompactItem);
1379 		fPopUpMenu->AddItem(fKeypadModeBasicItem);
1380 		fPopUpMenu->AddItem(fKeypadModeScientificItem);
1381 		_MarkKeypadItems(fOptions->keypad_mode);
1382 	}
1383 }
1384 
1385 
1386 BRect
1387 CalcView::_ExpressionRect() const
1388 {
1389 	BRect r(0.0, 0.0, fWidth, fHeight);
1390 	if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) {
1391 		r.bottom = floorf(fHeight * kDisplayScaleY) + 1;
1392 	}
1393 	return r;
1394 }
1395 
1396 
1397 BRect
1398 CalcView::_KeypadRect() const
1399 {
1400 	BRect r(0.0, 0.0, -1.0, -1.0);
1401 	if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) {
1402 		r.right = fWidth;
1403 		r.bottom = fHeight;
1404 		r.top = floorf(fHeight * kDisplayScaleY);
1405 	}
1406 	return r;
1407 }
1408 
1409 
1410 void
1411 CalcView::_MarkKeypadItems(uint8 keypad_mode)
1412 {
1413 	switch (keypad_mode) {
1414 		case KEYPAD_MODE_COMPACT:
1415 			fKeypadModeCompactItem->SetMarked(true);
1416 			fKeypadModeBasicItem->SetMarked(false);
1417 			fKeypadModeScientificItem->SetMarked(false);
1418 			break;
1419 
1420 		case KEYPAD_MODE_SCIENTIFIC:
1421 			fKeypadModeCompactItem->SetMarked(false);
1422 			fKeypadModeBasicItem->SetMarked(false);
1423 			fKeypadModeScientificItem->SetMarked(true);
1424 			break;
1425 
1426 		default: // KEYPAD_MODE_BASIC is the default
1427 			fKeypadModeCompactItem->SetMarked(false);
1428 			fKeypadModeBasicItem->SetMarked(true);
1429 			fKeypadModeScientificItem->SetMarked(false);
1430 	}
1431 }
1432 
1433 
1434 void
1435 CalcView::_FetchAppIcon(BBitmap* into)
1436 {
1437 	entry_ref appRef;
1438 	status_t status = be_roster->FindApp(kSignature, &appRef);
1439 	if (status == B_OK) {
1440 		BFile file(&appRef, B_READ_ONLY);
1441 		BAppFileInfo appInfo(&file);
1442 		status = appInfo.GetIcon(into, B_MINI_ICON);
1443 	}
1444 	if (status != B_OK)
1445 		memset(into->Bits(), 0, into->BitsLength());
1446 }
1447 
1448 
1449 // Returns whether or not CalcView is embedded somewhere, most likely
1450 // the Desktop
1451 bool
1452 CalcView::_IsEmbedded()
1453 {
1454 	return Parent() != NULL && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0;
1455 }
1456 
1457 
1458 void
1459 CalcView::_SetEnabled(bool enable)
1460 {
1461 	fEnabled = enable;
1462 	fExpressionTextView->MakeSelectable(enable);
1463 	fExpressionTextView->MakeEditable(enable);
1464 }
1465