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