xref: /haiku/src/apps/deskcalc/CalcView.cpp (revision a5c0d1a80e18f50987966fda2005210092d7671b)
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 	fColumns(5),
197 	fRows(4),
198 
199 	fBaseColor(rgbBaseColor),
200 	fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }),
201 
202 	fHasCustomBaseColor(rgbBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR)),
203 
204 	fWidth(1),
205 	fHeight(1),
206 
207 	fKeypadDescription(kKeypadDescriptionBasic),
208 	fKeypad(NULL),
209 
210 	fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)),
211 
212 	fPopUpMenu(NULL),
213 	fAutoNumlockItem(NULL),
214 	fOptions(new CalcOptions()),
215 	fEvaluateThread(-1),
216 	fEvaluateMessageRunner(NULL),
217 	fEvaluateSemaphore(B_BAD_SEM_ID),
218 	fEnabled(true)
219 {
220 	// tell the app server not to erase our b/g
221 	SetViewColor(B_TRANSPARENT_32_BIT);
222 
223 	_Init(settings);
224 }
225 
226 
227 CalcView::CalcView(BMessage* archive)
228 	:
229 	BView(archive),
230 	fColumns(5),
231 	fRows(4),
232 
233 	fBaseColor(ui_color(B_PANEL_BACKGROUND_COLOR)),
234 	fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }),
235 
236 	fHasCustomBaseColor(false),
237 
238 	fWidth(1),
239 	fHeight(1),
240 
241 	fKeypadDescription(kKeypadDescriptionBasic),
242 	fKeypad(NULL),
243 
244 	fCalcIcon(new BBitmap(BRect(0, 0, 15, 15), 0, B_RGBA32)),
245 
246 	fPopUpMenu(NULL),
247 	fAutoNumlockItem(NULL),
248 	fOptions(new CalcOptions()),
249 	fEvaluateThread(-1),
250 	fEvaluateMessageRunner(NULL),
251 	fEvaluateSemaphore(B_BAD_SEM_ID),
252 	fEnabled(true)
253 {
254 	// Do not restore the follow mode, in shelfs, we never follow.
255 	SetResizingMode(B_FOLLOW_NONE);
256 
257 	_Init(archive);
258 }
259 
260 
261 CalcView::~CalcView()
262 {
263 	delete[] fKeypad;
264 	delete fOptions;
265 	delete fEvaluateMessageRunner;
266 	delete_sem(fEvaluateSemaphore);
267 }
268 
269 
270 void
271 CalcView::AttachedToWindow()
272 {
273 	if (be_control_look == NULL)
274 		SetFont(be_bold_font);
275 
276 	BRect frame(Frame());
277 	FrameResized(frame.Width(), frame.Height());
278 
279 	bool addKeypadModeMenuItems = true;
280 	if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) {
281 		// don't add these items if we are a replicant on the desktop
282 		addKeypadModeMenuItems = false;
283 	}
284 
285 	// create and attach the pop-up menu
286 	_CreatePopUpMenu(addKeypadModeMenuItems);
287 
288 	if (addKeypadModeMenuItems)
289 		SetKeypadMode(fOptions->keypad_mode);
290 }
291 
292 
293 void
294 CalcView::MessageReceived(BMessage* message)
295 {
296 	if (message->what == B_COLORS_UPDATED && !fHasCustomBaseColor) {
297 		const char* panelBgColorName = ui_color_name(B_PANEL_BACKGROUND_COLOR);
298 		if (message->HasColor(panelBgColorName)) {
299 			fBaseColor = message->GetColor(panelBgColorName, fBaseColor);
300 			_Colorize();
301 		}
302 
303 		return;
304 	}
305 
306 	if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) {
307 		// if we are embedded in desktop we need to receive these
308 		// message here since we don't have a parent BWindow
309 		switch (message->what) {
310 			case MSG_OPTIONS_AUTO_NUM_LOCK:
311 				ToggleAutoNumlock();
312 				return;
313 
314 			case MSG_OPTIONS_ANGLE_MODE_RADIAN:
315 				SetDegreeMode(false);
316 				return;
317 
318 			case MSG_OPTIONS_ANGLE_MODE_DEGREE:
319 				SetDegreeMode(true);
320 				return;
321 		}
322 	}
323 
324 	// check if message was dropped
325 	if (message->WasDropped()) {
326 		// pass message on to paste
327 		if (message->IsSourceRemote())
328 			Paste(message);
329 	} else {
330 		// act on posted message type
331 		switch (message->what) {
332 
333 			// handle "cut"
334 			case B_CUT:
335 				Cut();
336 				break;
337 
338 			// handle copy
339 			case B_COPY:
340 				Copy();
341 				break;
342 
343 			// handle paste
344 			case B_PASTE:
345 			{
346 				// access system clipboard
347 				ClipboardLocker locker(be_clipboard);
348 				if (locker.IsLocked()) {
349 					BMessage* clipper = be_clipboard->Data();
350 					if (clipper)
351 						Paste(clipper);
352 				}
353 				break;
354 			}
355 
356 			// (replicant) about box requested
357 			case B_ABOUT_REQUESTED:
358 			{
359 				BAboutWindow* window = new BAboutWindow(kAppName, kSignature);
360 
361 				// create the about window
362 				const char* extraCopyrights[] = {
363 					"1997, 1998 R3 Software Ltd.",
364 					NULL
365 				};
366 
367 				const char* authors[] = {
368 					"Stephan Aßmus",
369 					"John Scipione",
370 					"Timothy Wayper",
371 					"Ingo Weinhold",
372 					NULL
373 				};
374 
375 				window->AddCopyright(2006, "Haiku, Inc.", extraCopyrights);
376 				window->AddAuthors(authors);
377 
378 				window->Show();
379 
380 				break;
381 			}
382 
383 			case MSG_UNFLASH_KEY:
384 			{
385 				int32 key;
386 				if (message->FindInt32("key", &key) == B_OK)
387 					_FlashKey(key, 0);
388 
389 				break;
390 			}
391 
392 			case kMsgAnimateDots:
393 			{
394 				int32 end = fExpressionTextView->TextLength();
395 				int32 start = end - 3;
396 				if (fEnabled || strcmp(fExpressionTextView->Text() + start,
397 						"...") != 0) {
398 					// stop the message runner
399 					delete fEvaluateMessageRunner;
400 					fEvaluateMessageRunner = NULL;
401 					break;
402 				}
403 
404 				uint8 dot = 0;
405 				if (message->FindUInt8("dot", &dot) == B_OK) {
406 					rgb_color fontColor = fExpressionTextView->HighColor();
407 					rgb_color backColor = fExpressionTextView->LowColor();
408 					fExpressionTextView->SetStylable(true);
409 					fExpressionTextView->SetFontAndColor(start, end, NULL, 0,
410 						&backColor);
411 					fExpressionTextView->SetFontAndColor(start + dot - 1,
412 						start + dot, NULL, 0, &fontColor);
413 					fExpressionTextView->SetStylable(false);
414 				}
415 
416 				dot++;
417 				if (dot == 4)
418 					dot = 1;
419 
420 				delete fEvaluateMessageRunner;
421 				BMessage animate(kMsgAnimateDots);
422 				animate.AddUInt8("dot", dot);
423 				fEvaluateMessageRunner = new (std::nothrow) BMessageRunner(
424 					BMessenger(this), &animate, kAnimationInterval, 1);
425 				break;
426 			}
427 
428 			case kMsgCalculating:
429 			{
430 				// calculation has taken more than 3 seconds
431 				if (fEnabled) {
432 					// stop the message runner
433 					delete fEvaluateMessageRunner;
434 					fEvaluateMessageRunner = NULL;
435 					break;
436 				}
437 
438 				BString calculating;
439 				calculating << B_TRANSLATE("Calculating") << "...";
440 				fExpressionTextView->SetText(calculating.String());
441 
442 				delete fEvaluateMessageRunner;
443 				BMessage animate(kMsgAnimateDots);
444 				animate.AddUInt8("dot", 1U);
445 				fEvaluateMessageRunner = new (std::nothrow) BMessageRunner(
446 					BMessenger(this), &animate, kAnimationInterval, 1);
447 				break;
448 			}
449 
450 			case kMsgDoneEvaluating:
451 			{
452 				_SetEnabled(true);
453 				rgb_color fontColor = fExpressionTextView->HighColor();
454 				fExpressionTextView->SetFontAndColor(NULL, 0, &fontColor);
455 
456 				const char* result;
457 				if (message->FindString("error", &result) == B_OK)
458 					fExpressionTextView->SetText(result);
459 				else if (message->FindString("value", &result) == B_OK)
460 					fExpressionTextView->SetValue(result);
461 
462 				// stop the message runner
463 				delete fEvaluateMessageRunner;
464 				fEvaluateMessageRunner = NULL;
465 				break;
466 			}
467 
468 			default:
469 				BView::MessageReceived(message);
470 				break;
471 		}
472 	}
473 }
474 
475 
476 void
477 CalcView::Draw(BRect updateRect)
478 {
479 	bool drawBackground = !_IsEmbedded();
480 
481 	SetHighColor(fBaseColor);
482 	BRect expressionRect(_ExpressionRect());
483 	if (updateRect.Intersects(expressionRect)) {
484 		if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT
485 			&& expressionRect.Height() >= fCalcIcon->Bounds().Height()) {
486 			// render calc icon
487 			expressionRect.left = fExpressionTextView->Frame().right + 2;
488 			if (drawBackground) {
489 				SetHighColor(fBaseColor);
490 				FillRect(updateRect & expressionRect);
491 			}
492 
493 			SetDrawingMode(B_OP_ALPHA);
494 			SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
495 
496 			BPoint iconPos;
497 			iconPos.x = expressionRect.right - (expressionRect.Width()
498 				+ fCalcIcon->Bounds().Width()) / 2.0;
499 			iconPos.y = expressionRect.top + (expressionRect.Height()
500 				- fCalcIcon->Bounds().Height()) / 2.0;
501 			DrawBitmap(fCalcIcon, iconPos);
502 
503 			SetDrawingMode(B_OP_COPY);
504 		}
505 
506 		// render border around expression text view
507 		expressionRect = fExpressionTextView->Frame();
508 		expressionRect.InsetBy(-2, -2);
509 		if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT && drawBackground) {
510 			expressionRect.InsetBy(-2, -2);
511 			StrokeRect(expressionRect);
512 			expressionRect.InsetBy(1, 1);
513 			StrokeRect(expressionRect);
514 			expressionRect.InsetBy(1, 1);
515 		}
516 
517 		uint32 flags = 0;
518 		if (!drawBackground)
519 			flags |= BControlLook::B_BLEND_FRAME;
520 		be_control_look->DrawTextControlBorder(this, expressionRect,
521 			updateRect, fBaseColor, flags);
522 	}
523 
524 	if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
525 		return;
526 
527 	// calculate grid sizes
528 	BRect keypadRect(_KeypadRect());
529 
530 	if (be_control_look != NULL) {
531 		if (drawBackground)
532 			StrokeRect(keypadRect);
533 		keypadRect.InsetBy(1, 1);
534 	}
535 
536 	float sizeDisp = keypadRect.top;
537 	float sizeCol = (keypadRect.Width() + 1) / (float)fColumns;
538 	float sizeRow = (keypadRect.Height() + 1) / (float)fRows;
539 
540 	if (!updateRect.Intersects(keypadRect))
541 		return;
542 
543 	SetFontSize(min_c(sizeRow * kFontScaleY, sizeCol * kFontScaleX));
544 
545 	CalcKey* key = fKeypad;
546 	for (int row = 0; row < fRows; row++) {
547 		for (int col = 0; col < fColumns; col++) {
548 			BRect frame;
549 			frame.left = keypadRect.left + col * sizeCol;
550 			frame.right = keypadRect.left + (col + 1) * sizeCol - 1;
551 			frame.top = sizeDisp + row * sizeRow;
552 			frame.bottom = sizeDisp + (row + 1) * sizeRow - 1;
553 
554 			if (drawBackground) {
555 				SetHighColor(fBaseColor);
556 				StrokeRect(frame);
557 			}
558 			frame.InsetBy(1, 1);
559 
560 			uint32 flags = 0;
561 			if (!drawBackground)
562 				flags |= BControlLook::B_BLEND_FRAME;
563 			if (key->flags != 0)
564 				flags |= BControlLook::B_ACTIVATED;
565 			flags |= BControlLook::B_IGNORE_OUTLINE;
566 
567 			be_control_look->DrawButtonFrame(this, frame, updateRect,
568 				fBaseColor, fBaseColor, flags);
569 
570 			be_control_look->DrawButtonBackground(this, frame, updateRect,
571 				fBaseColor, flags);
572 
573 			be_control_look->DrawLabel(this, key->label, frame, updateRect,
574 				fBaseColor, flags, BAlignment(B_ALIGN_HORIZONTAL_CENTER,
575 					B_ALIGN_VERTICAL_CENTER), &fButtonTextColor);
576 
577 			key++;
578 		}
579 	}
580 }
581 
582 
583 void
584 CalcView::MouseDown(BPoint point)
585 {
586 	// ensure this view is the current focus
587 	if (!fExpressionTextView->IsFocus()) {
588 		// Call our version of MakeFocus(), since that will also apply the
589 		// num_lock setting.
590 		MakeFocus();
591 	}
592 
593 	// read mouse buttons state
594 	int32 buttons = 0;
595 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
596 
597 	if ((B_PRIMARY_MOUSE_BUTTON & buttons) == 0) {
598 		// display popup menu if not primary mouse button
599 		BMenuItem* selected;
600 		if ((selected = fPopUpMenu->Go(ConvertToScreen(point))) != NULL
601 			&& selected->Message() != NULL) {
602 			Window()->PostMessage(selected->Message(), this);
603 		}
604 		return;
605 	}
606 
607 	if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) {
608 		if (fCalcIcon != NULL) {
609 			BRect bounds(Bounds());
610 			bounds.left = bounds.right - fCalcIcon->Bounds().Width();
611 			if (bounds.Contains(point)) {
612 				// user clicked on calculator icon
613 				fExpressionTextView->Clear();
614 			}
615 		}
616 		return;
617 	}
618 
619 	// calculate grid sizes
620 	float sizeDisp = fHeight * kDisplayScaleY;
621 	float sizeCol = fWidth / (float)fColumns;
622 	float sizeRow = (fHeight - sizeDisp) / (float)fRows;
623 
624 	// calculate location within grid
625 	int gridCol = (int)floorf(point.x / sizeCol);
626 	int gridRow = (int)floorf((point.y - sizeDisp) / sizeRow);
627 
628 	// check limits
629 	if ((gridCol >= 0) && (gridCol < fColumns)
630 		&& (gridRow >= 0) && (gridRow < fRows)) {
631 
632 		// process key press
633 		int key = gridRow * fColumns + gridCol;
634 		_FlashKey(key, FLAGS_MOUSE_DOWN);
635 		_PressKey(key);
636 
637 		// make sure we receive the mouse up!
638 		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
639 	}
640 }
641 
642 
643 void
644 CalcView::MouseUp(BPoint point)
645 {
646 	if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
647 		return;
648 
649 	int keys = fRows * fColumns;
650 	for (int i = 0; i < keys; i++) {
651 		if (fKeypad[i].flags & FLAGS_MOUSE_DOWN) {
652 			_FlashKey(i, 0);
653 			break;
654 		}
655 	}
656 }
657 
658 
659 void
660 CalcView::KeyDown(const char* bytes, int32 numBytes)
661 {
662 	// if single byte character...
663 	if (numBytes == 1) {
664 
665 		//printf("Key pressed: %c\n", bytes[0]);
666 
667 		switch (bytes[0]) {
668 
669 			case B_ENTER:
670 				// translate to evaluate key
671 				_PressKey("=");
672 				break;
673 
674 			case B_LEFT_ARROW:
675 			case B_BACKSPACE:
676 				// translate to backspace key
677 				_PressKey("BS");
678 				break;
679 
680 			case B_SPACE:
681 			case B_ESCAPE:
682 			case 'c':
683 				// translate to clear key
684 				_PressKey("C");
685 				break;
686 
687 			// bracket translation
688 			case '[':
689 			case '{':
690 				_PressKey("(");
691 				break;
692 
693 			case ']':
694 			case '}':
695 				_PressKey(")");
696 				break;
697 
698 			default: {
699 				// scan the keymap array for match
700 				int keys = fRows * fColumns;
701 				for (int i = 0; i < keys; i++) {
702 					if (fKeypad[i].keymap[0] == bytes[0]) {
703 						_PressKey(i);
704 						return;
705 					}
706 				}
707 				break;
708 			}
709 		}
710 	}
711 }
712 
713 
714 void
715 CalcView::MakeFocus(bool focused)
716 {
717 	if (focused) {
718 		// set num lock
719 		if (fOptions->auto_num_lock) {
720 			set_keyboard_locks(B_NUM_LOCK
721 				| (modifiers() & (B_CAPS_LOCK | B_SCROLL_LOCK)));
722 		}
723 	}
724 
725 	// pass on request to text view
726 	fExpressionTextView->MakeFocus(focused);
727 }
728 
729 
730 void
731 CalcView::FrameResized(float width, float height)
732 {
733 	fWidth = width;
734 	fHeight = height;
735 
736 	// layout expression text view
737 	BRect expressionRect = _ExpressionRect();
738 	if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT) {
739 		expressionRect.InsetBy(2, 2);
740 		expressionRect.right -= ceilf(fCalcIcon->Bounds().Width() * 1.5);
741 	} else
742 		expressionRect.InsetBy(4, 4);
743 
744 	fExpressionTextView->MoveTo(expressionRect.LeftTop());
745 	fExpressionTextView->ResizeTo(expressionRect.Width(), expressionRect.Height());
746 
747 	// configure expression text view font size and color
748 	float sizeDisp = fOptions->keypad_mode == KEYPAD_MODE_COMPACT
749 		? fHeight : fHeight * kDisplayScaleY;
750 	BFont font(be_bold_font);
751 	font.SetSize(sizeDisp * kExpressionFontScaleY);
752 	rgb_color fontColor = fExpressionTextView->HighColor();
753 	fExpressionTextView->SetFontAndColor(&font, B_FONT_ALL, &fontColor);
754 
755 	expressionRect.OffsetTo(B_ORIGIN);
756 	fExpressionTextView->SetTextRect(expressionRect);
757 	Invalidate();
758 }
759 
760 
761 status_t
762 CalcView::Archive(BMessage* archive, bool deep) const
763 {
764 	fExpressionTextView->RemoveSelf();
765 
766 	// passed on request to parent
767 	status_t ret = BView::Archive(archive, deep);
768 
769 	const_cast<CalcView*>(this)->AddChild(fExpressionTextView);
770 
771 	// save app signature for replicant add-on loading
772 	if (ret == B_OK)
773 		ret = archive->AddString("add_on", kSignature);
774 
775 	// save all the options
776 	if (ret == B_OK)
777 		ret = SaveSettings(archive);
778 
779 	// add class info last
780 	if (ret == B_OK)
781 		ret = archive->AddString("class", "CalcView");
782 
783 	return ret;
784 }
785 
786 
787 void
788 CalcView::Cut()
789 {
790 	Copy();	// copy data to clipboard
791 	fExpressionTextView->Clear(); // remove data
792 }
793 
794 
795 void
796 CalcView::Copy()
797 {
798 	// access system clipboard
799 	ClipboardLocker locker(be_clipboard);
800 	if (!locker.IsLocked())
801 		return;
802 
803 	if (be_clipboard->Clear() != B_OK)
804 		return;
805 
806 	BMessage* clipper = be_clipboard->Data();
807 	if (clipper == NULL)
808 		return;
809 
810 	BString expression = fExpressionTextView->Text();
811 	if (clipper->AddData("text/plain", B_MIME_TYPE,
812 		expression.String(), expression.Length()) == B_OK) {
813 		clipper->what = B_MIME_DATA;
814 		be_clipboard->Commit();
815 	}
816 }
817 
818 
819 void
820 CalcView::Paste(BMessage* message)
821 {
822 	// handle files first
823 	int32 count;
824 	if (message->GetInfo("refs", NULL, &count) == B_OK) {
825 		entry_ref ref;
826 		ssize_t read;
827 		BFile file;
828 		char buffer[256];
829 		memset(buffer, 0, sizeof(buffer));
830 		for (int32 i = 0; i < count; i++) {
831 			if (message->FindRef("refs", i, &ref) == B_OK) {
832 				if (file.SetTo(&ref, B_READ_ONLY) == B_OK) {
833 					read = file.Read(buffer, sizeof(buffer) - 1);
834 					if (read <= 0)
835 						continue;
836 					BString expression(buffer);
837 					int32 j = expression.Length();
838 					while (j > 0 && expression[j - 1] == '\n')
839 						j--;
840 					expression.Truncate(j);
841 					if (expression.Length() > 0)
842 						fExpressionTextView->Insert(expression.String());
843 				}
844 			}
845 		}
846 		return;
847 	}
848 	// handle color drops
849 	// read incoming color
850 	const rgb_color* dropColor = NULL;
851 	ssize_t dataSize;
852 	if (message->FindData("RGBColor", B_RGB_COLOR_TYPE,
853 			(const void**)&dropColor, &dataSize) == B_OK
854 		&& dataSize == sizeof(rgb_color)) {
855 
856 		// calculate view relative drop point
857 		BPoint dropPoint = ConvertFromScreen(message->DropPoint());
858 
859 		// calculate current keypad area
860 		float sizeDisp = fHeight * kDisplayScaleY;
861 		BRect keypadRect(0.0, sizeDisp, fWidth, fHeight);
862 
863 		// check location of color drop
864 		if (keypadRect.Contains(dropPoint) && dropColor != NULL) {
865 			fBaseColor = *dropColor;
866 			fHasCustomBaseColor =
867 				fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR);
868 			_Colorize();
869 			// redraw
870 			Invalidate();
871 		}
872 
873 	} else {
874 		// look for text/plain MIME data
875 		const char* text;
876 		ssize_t numBytes;
877 		if (message->FindData("text/plain", B_MIME_TYPE,
878 				(const void**)&text, &numBytes) == B_OK) {
879 			BString temp;
880 			temp.Append(text, numBytes);
881 			fExpressionTextView->Insert(temp.String());
882 		}
883 	}
884 }
885 
886 
887 status_t
888 CalcView::SaveSettings(BMessage* archive) const
889 {
890 	status_t ret = archive ? B_OK : B_BAD_VALUE;
891 
892 	// record grid dimensions
893 	if (ret == B_OK)
894 		ret = archive->AddInt16("cols", fColumns);
895 
896 	if (ret == B_OK)
897 		ret = archive->AddInt16("rows", fRows);
898 
899 	// record color scheme
900 	if (ret == B_OK) {
901 		ret = archive->AddData("rgbBaseColor", B_RGB_COLOR_TYPE,
902 			&fBaseColor, sizeof(rgb_color));
903 	}
904 
905 	if (ret == B_OK) {
906 		ret = archive->AddData("rgbDisplay", B_RGB_COLOR_TYPE,
907 			&fExpressionBGColor, 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 	if (archive->FindData("rgbDisplay", B_RGB_COLOR_TYPE,
1181 			(const void**)&color, &size) < B_OK
1182 		|| size != sizeof(rgb_color)) {
1183 		fExpressionBGColor = (rgb_color){ 0, 0, 0, 255 };
1184 		puts("Missing rgbBaseColor from CalcView archive!\n");
1185 	} else {
1186 		fExpressionBGColor = *color;
1187 	}
1188 
1189 	fHasCustomBaseColor = fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR);
1190 
1191 	// load options
1192 	fOptions->LoadSettings(archive);
1193 
1194 	// load display text
1195 	const char* display;
1196 	if (archive->FindString("displayText", &display) < B_OK) {
1197 		puts("Missing expression text from CalcView archive.\n");
1198 	} else {
1199 		// init expression text
1200 		fExpressionTextView->SetText(display);
1201 	}
1202 
1203 	// load expression history
1204 	fExpressionTextView->LoadSettings(archive);
1205 
1206 	// parse calculator description
1207 	_ParseCalcDesc(fKeypadDescription);
1208 
1209 	// colorize based on base color.
1210 	_Colorize();
1211 
1212 	return B_OK;
1213 }
1214 
1215 
1216 void
1217 CalcView::_ParseCalcDesc(const char** keypadDescription)
1218 {
1219 	// TODO: should calculate dimensions from desc here!
1220 	fKeypad = new CalcKey[fRows * fColumns];
1221 
1222 	// scan through calculator description and assemble keypad
1223 	CalcKey* key = fKeypad;
1224 	for (int i = 0; const char* p = keypadDescription[i]; i++) {
1225 		// Move to next row as needed
1226 		if (strcmp(p, "\n") == 0)
1227 			continue;
1228 
1229 		// copy label
1230 		strlcpy(key->label, B_TRANSLATE_NOCOLLECT(p), sizeof(key->label));
1231 
1232 		// set code
1233 		if (strcmp(p, "=") == 0)
1234 			strlcpy(key->code, "\n", sizeof(key->code));
1235 		else
1236 			strlcpy(key->code, p, sizeof(key->code));
1237 
1238 		// set keymap
1239 		if (strlen(key->label) == 1)
1240 			strlcpy(key->keymap, key->label, sizeof(key->keymap));
1241 		else
1242 			*key->keymap = '\0';
1243 
1244 		key->flags = 0;
1245 
1246 		// add this to the expression text view, so that it
1247 		// will forward the respective KeyDown event to us
1248 		fExpressionTextView->AddKeypadLabel(key->label);
1249 
1250 		// advance
1251 		key++;
1252 	}
1253 }
1254 
1255 
1256 void
1257 CalcView::_PressKey(int key)
1258 {
1259 	if (!fEnabled)
1260 		return;
1261 
1262 	assert(key < (fRows * fColumns));
1263 	assert(key >= 0);
1264 
1265 	if (strcmp(fKeypad[key].code, "BS") == 0) {
1266 		// BS means backspace
1267 		fExpressionTextView->BackSpace();
1268 	} else if (strcmp(fKeypad[key].code, "C") == 0) {
1269 		// C means clear
1270 		fExpressionTextView->Clear();
1271 	} else if (strcmp(fKeypad[key].code, "acos") == 0
1272 		|| strcmp(fKeypad[key].code, "asin") == 0
1273 		|| strcmp(fKeypad[key].code, "atan") == 0
1274 		|| strcmp(fKeypad[key].code, "cbrt") == 0
1275 		|| strcmp(fKeypad[key].code, "ceil") == 0
1276 		|| strcmp(fKeypad[key].code, "cos") == 0
1277 		|| strcmp(fKeypad[key].code, "cosh") == 0
1278 		|| strcmp(fKeypad[key].code, "exp") == 0
1279 		|| strcmp(fKeypad[key].code, "floor") == 0
1280 		|| strcmp(fKeypad[key].code, "log") == 0
1281 		|| strcmp(fKeypad[key].code, "ln") == 0
1282 		|| strcmp(fKeypad[key].code, "sin") == 0
1283 		|| strcmp(fKeypad[key].code, "sinh") == 0
1284 		|| strcmp(fKeypad[key].code, "sqrt") == 0
1285 		|| strcmp(fKeypad[key].code, "tan") == 0
1286 		|| strcmp(fKeypad[key].code, "tanh") == 0) {
1287 		int32 labelLen = strlen(fKeypad[key].code);
1288 		int32 startSelection = 0;
1289 		int32 endSelection = 0;
1290 		fExpressionTextView->GetSelection(&startSelection, &endSelection);
1291 		if (endSelection > startSelection) {
1292 			// There is selected text, put it inbetween the parens
1293 			fExpressionTextView->Insert(startSelection, fKeypad[key].code,
1294 				labelLen);
1295 			fExpressionTextView->Insert(startSelection + labelLen, "(", 1);
1296 			fExpressionTextView->Insert(endSelection + labelLen + 1, ")", 1);
1297 			// Put the cursor after the ending paren
1298 			// Need to cast to BTextView because Select() is protected
1299 			// in the InputTextView class
1300 			static_cast<BTextView*>(fExpressionTextView)->Select(
1301 				endSelection + labelLen + 2, endSelection + labelLen + 2);
1302 		} else {
1303 			// There is no selected text, insert at the cursor location
1304 			fExpressionTextView->Insert(fKeypad[key].code);
1305 			fExpressionTextView->Insert("()");
1306 			// Put the cursor inside the parens so you can enter an argument
1307 			// Need to cast to BTextView because Select() is protected
1308 			// in the InputTextView class
1309 			static_cast<BTextView*>(fExpressionTextView)->Select(
1310 				endSelection + labelLen + 1, endSelection + labelLen + 1);
1311 		}
1312 	} else if (strcmp(fKeypad[key].code, ".") == 0) {
1313 		BLocale locale;
1314 		BNumberFormat format(&locale);
1315 
1316 		fExpressionTextView->Insert(format.GetSeparator(B_DECIMAL_SEPARATOR));
1317 	} else {
1318 		// check for evaluation order
1319 		if (fKeypad[key].code[0] == '\n') {
1320 			fExpressionTextView->ApplyChanges();
1321 		} else {
1322 			// insert into expression text
1323 			fExpressionTextView->Insert(fKeypad[key].code);
1324 		}
1325 	}
1326 }
1327 
1328 
1329 void
1330 CalcView::_PressKey(const char* label)
1331 {
1332 	int32 key = _KeyForLabel(label);
1333 	if (key >= 0)
1334 		_PressKey(key);
1335 }
1336 
1337 
1338 int32
1339 CalcView::_KeyForLabel(const char* label) const
1340 {
1341 	int keys = fRows * fColumns;
1342 	for (int i = 0; i < keys; i++) {
1343 		if (strcmp(fKeypad[i].label, label) == 0) {
1344 			return i;
1345 		}
1346 	}
1347 	return -1;
1348 }
1349 
1350 
1351 void
1352 CalcView::_FlashKey(int32 key, uint32 flashFlags)
1353 {
1354 	if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
1355 		return;
1356 
1357 	if (flashFlags != 0)
1358 		fKeypad[key].flags |= flashFlags;
1359 	else
1360 		fKeypad[key].flags = 0;
1361 	Invalidate();
1362 
1363 	if (fKeypad[key].flags == FLAGS_FLASH_KEY) {
1364 		BMessage message(MSG_UNFLASH_KEY);
1365 		message.AddInt32("key", key);
1366 		BMessageRunner::StartSending(BMessenger(this), &message,
1367 			kFlashOnOffInterval, 1);
1368 	}
1369 }
1370 
1371 
1372 void
1373 CalcView::_Colorize()
1374 {
1375 	// calculate light and dark color from base color
1376 	fLightColor.red		= (uint8)(fBaseColor.red * 1.25);
1377 	fLightColor.green	= (uint8)(fBaseColor.green * 1.25);
1378 	fLightColor.blue	= (uint8)(fBaseColor.blue * 1.25);
1379 	fLightColor.alpha	= 255;
1380 
1381 	fDarkColor.red		= (uint8)(fBaseColor.red * 0.75);
1382 	fDarkColor.green	= (uint8)(fBaseColor.green * 0.75);
1383 	fDarkColor.blue		= (uint8)(fBaseColor.blue * 0.75);
1384 	fDarkColor.alpha	= 255;
1385 
1386 	// keypad text color
1387 	if (fBaseColor.Brightness() > 100)
1388 		fButtonTextColor = (rgb_color){ 0, 0, 0, 255 };
1389 	else
1390 		fButtonTextColor = (rgb_color){ 255, 255, 255, 255 };
1391 
1392 	// expression text color
1393 	if (fExpressionBGColor.Brightness() > 100)
1394 		fExpressionTextColor = (rgb_color){ 0, 0, 0, 255 };
1395 	else
1396 		fExpressionTextColor = (rgb_color){ 255, 255, 255, 255 };
1397 }
1398 
1399 
1400 void
1401 CalcView::_CreatePopUpMenu(bool addKeypadModeMenuItems)
1402 {
1403 	// construct items
1404 	fAutoNumlockItem = new BMenuItem(B_TRANSLATE("Enable Num Lock on startup"),
1405 		new BMessage(MSG_OPTIONS_AUTO_NUM_LOCK));
1406 	fAngleModeRadianItem = new BMenuItem(B_TRANSLATE("Radians"),
1407 		new BMessage(MSG_OPTIONS_ANGLE_MODE_RADIAN));
1408 	fAngleModeDegreeItem = new BMenuItem(B_TRANSLATE("Degrees"),
1409 		new BMessage(MSG_OPTIONS_ANGLE_MODE_DEGREE));
1410 	if (addKeypadModeMenuItems) {
1411 		fKeypadModeCompactItem = new BMenuItem(B_TRANSLATE("Compact"),
1412 			new BMessage(MSG_OPTIONS_KEYPAD_MODE_COMPACT), '0');
1413 		fKeypadModeBasicItem = new BMenuItem(B_TRANSLATE("Basic"),
1414 			new BMessage(MSG_OPTIONS_KEYPAD_MODE_BASIC), '1');
1415 		fKeypadModeScientificItem = new BMenuItem(B_TRANSLATE("Scientific"),
1416 			new BMessage(MSG_OPTIONS_KEYPAD_MODE_SCIENTIFIC), '2');
1417 	}
1418 
1419 	// apply current settings
1420 	fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
1421 	fAngleModeRadianItem->SetMarked(!fOptions->degree_mode);
1422 	fAngleModeDegreeItem->SetMarked(fOptions->degree_mode);
1423 
1424 	// construct menu
1425 	fPopUpMenu = new BPopUpMenu("pop-up", false, false);
1426 
1427 	fPopUpMenu->AddItem(fAutoNumlockItem);
1428 	fPopUpMenu->AddSeparatorItem();
1429 	fPopUpMenu->AddItem(fAngleModeRadianItem);
1430 	fPopUpMenu->AddItem(fAngleModeDegreeItem);
1431 	if (addKeypadModeMenuItems) {
1432 		fPopUpMenu->AddSeparatorItem();
1433 		fPopUpMenu->AddItem(fKeypadModeCompactItem);
1434 		fPopUpMenu->AddItem(fKeypadModeBasicItem);
1435 		fPopUpMenu->AddItem(fKeypadModeScientificItem);
1436 		_MarkKeypadItems(fOptions->keypad_mode);
1437 	}
1438 }
1439 
1440 
1441 BRect
1442 CalcView::_ExpressionRect() const
1443 {
1444 	BRect r(0.0, 0.0, fWidth, fHeight);
1445 	if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) {
1446 		r.bottom = floorf(fHeight * kDisplayScaleY) + 1;
1447 	}
1448 	return r;
1449 }
1450 
1451 
1452 BRect
1453 CalcView::_KeypadRect() const
1454 {
1455 	BRect r(0.0, 0.0, -1.0, -1.0);
1456 	if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) {
1457 		r.right = fWidth;
1458 		r.bottom = fHeight;
1459 		r.top = floorf(fHeight * kDisplayScaleY);
1460 	}
1461 	return r;
1462 }
1463 
1464 
1465 void
1466 CalcView::_MarkKeypadItems(uint8 keypad_mode)
1467 {
1468 	switch (keypad_mode) {
1469 		case KEYPAD_MODE_COMPACT:
1470 			fKeypadModeCompactItem->SetMarked(true);
1471 			fKeypadModeBasicItem->SetMarked(false);
1472 			fKeypadModeScientificItem->SetMarked(false);
1473 			break;
1474 
1475 		case KEYPAD_MODE_SCIENTIFIC:
1476 			fKeypadModeCompactItem->SetMarked(false);
1477 			fKeypadModeBasicItem->SetMarked(false);
1478 			fKeypadModeScientificItem->SetMarked(true);
1479 			break;
1480 
1481 		default: // KEYPAD_MODE_BASIC is the default
1482 			fKeypadModeCompactItem->SetMarked(false);
1483 			fKeypadModeBasicItem->SetMarked(true);
1484 			fKeypadModeScientificItem->SetMarked(false);
1485 	}
1486 }
1487 
1488 
1489 void
1490 CalcView::_FetchAppIcon(BBitmap* into)
1491 {
1492 	entry_ref appRef;
1493 	status_t status = be_roster->FindApp(kSignature, &appRef);
1494 	if (status == B_OK) {
1495 		BFile file(&appRef, B_READ_ONLY);
1496 		BAppFileInfo appInfo(&file);
1497 		status = appInfo.GetIcon(into, B_MINI_ICON);
1498 	}
1499 	if (status != B_OK)
1500 		memset(into->Bits(), 0, into->BitsLength());
1501 }
1502 
1503 
1504 // Returns whether or not CalcView is embedded somewhere, most likely
1505 // the Desktop
1506 bool
1507 CalcView::_IsEmbedded()
1508 {
1509 	return Parent() != NULL && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0;
1510 }
1511 
1512 
1513 void
1514 CalcView::_SetEnabled(bool enable)
1515 {
1516 	fEnabled = enable;
1517 	fExpressionTextView->MakeSelectable(enable);
1518 	fExpressionTextView->MakeEditable(enable);
1519 }
1520