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