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