xref: /haiku/src/apps/deskcalc/CalcView.cpp (revision 1a3518cf757c2da8006753f83962da5935bbc82b)
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 	float inset = (expressionRect.Height() - fExpressionTextView->LineHeight(0)) / 2;
767 	expressionRect.InsetBy(0, inset);
768 	fExpressionTextView->SetTextRect(expressionRect);
769 	Invalidate();
770 }
771 
772 
773 status_t
774 CalcView::Archive(BMessage* archive, bool deep) const
775 {
776 	fExpressionTextView->RemoveSelf();
777 
778 	// passed on request to parent
779 	status_t ret = BView::Archive(archive, deep);
780 
781 	const_cast<CalcView*>(this)->AddChild(fExpressionTextView);
782 
783 	// save app signature for replicant add-on loading
784 	if (ret == B_OK)
785 		ret = archive->AddString("add_on", kSignature);
786 
787 	// save all the options
788 	if (ret == B_OK)
789 		ret = SaveSettings(archive);
790 
791 	// add class info last
792 	if (ret == B_OK)
793 		ret = archive->AddString("class", "CalcView");
794 
795 	return ret;
796 }
797 
798 
799 void
800 CalcView::Cut()
801 {
802 	Copy();	// copy data to clipboard
803 	fExpressionTextView->Clear(); // remove data
804 }
805 
806 
807 void
808 CalcView::Copy()
809 {
810 	// access system clipboard
811 	ClipboardLocker locker(be_clipboard);
812 	if (!locker.IsLocked())
813 		return;
814 
815 	if (be_clipboard->Clear() != B_OK)
816 		return;
817 
818 	BMessage* clipper = be_clipboard->Data();
819 	if (clipper == NULL)
820 		return;
821 
822 	BString expression = fExpressionTextView->Text();
823 	if (clipper->AddData("text/plain", B_MIME_TYPE,
824 		expression.String(), expression.Length()) == B_OK) {
825 		clipper->what = B_MIME_DATA;
826 		be_clipboard->Commit();
827 	}
828 }
829 
830 
831 void
832 CalcView::Paste(BMessage* message)
833 {
834 	// handle files first
835 	int32 count;
836 	if (message->GetInfo("refs", NULL, &count) == B_OK) {
837 		entry_ref ref;
838 		ssize_t read;
839 		BFile file;
840 		char buffer[256];
841 		memset(buffer, 0, sizeof(buffer));
842 		for (int32 i = 0; i < count; i++) {
843 			if (message->FindRef("refs", i, &ref) == B_OK) {
844 				if (file.SetTo(&ref, B_READ_ONLY) == B_OK) {
845 					read = file.Read(buffer, sizeof(buffer) - 1);
846 					if (read <= 0)
847 						continue;
848 					BString expression(buffer);
849 					int32 j = expression.Length();
850 					while (j > 0 && expression[j - 1] == '\n')
851 						j--;
852 					expression.Truncate(j);
853 					if (expression.Length() > 0)
854 						fExpressionTextView->Insert(expression.String());
855 				}
856 			}
857 		}
858 		return;
859 	}
860 	// handle color drops
861 	// read incoming color
862 	const rgb_color* dropColor = NULL;
863 	ssize_t dataSize;
864 	if (message->FindData("RGBColor", B_RGB_COLOR_TYPE,
865 			(const void**)&dropColor, &dataSize) == B_OK
866 		&& dataSize == sizeof(rgb_color)) {
867 
868 		// calculate view relative drop point
869 		BPoint dropPoint = ConvertFromScreen(message->DropPoint());
870 
871 		// calculate current keypad area
872 		float sizeDisp = fHeight * kDisplayScaleY;
873 		BRect keypadRect(0.0, sizeDisp, fWidth, fHeight);
874 
875 		// check location of color drop
876 		if (keypadRect.Contains(dropPoint) && dropColor != NULL) {
877 			fBaseColor = *dropColor;
878 			fHasCustomBaseColor =
879 				fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR);
880 			_Colorize();
881 			// redraw
882 			Invalidate();
883 		}
884 
885 	} else {
886 		// look for text/plain MIME data
887 		const char* text;
888 		ssize_t numBytes;
889 		if (message->FindData("text/plain", B_MIME_TYPE,
890 				(const void**)&text, &numBytes) == B_OK) {
891 			BString temp;
892 			temp.Append(text, numBytes);
893 			fExpressionTextView->Insert(temp.String());
894 		}
895 	}
896 }
897 
898 
899 status_t
900 CalcView::SaveSettings(BMessage* archive) const
901 {
902 	status_t ret = archive ? B_OK : B_BAD_VALUE;
903 
904 	// record grid dimensions
905 	if (ret == B_OK)
906 		ret = archive->AddInt16("cols", fColumns);
907 
908 	if (ret == B_OK)
909 		ret = archive->AddInt16("rows", fRows);
910 
911 	// record color scheme
912 	if (ret == B_OK) {
913 		ret = archive->AddData("rgbBaseColor", B_RGB_COLOR_TYPE,
914 			&fBaseColor, sizeof(rgb_color));
915 	}
916 
917 	if (ret == B_OK) {
918 		ret = archive->AddData("rgbDisplay", B_RGB_COLOR_TYPE,
919 			&fExpressionBGColor, sizeof(rgb_color));
920 	}
921 
922 	// record current options
923 	if (ret == B_OK)
924 		ret = fOptions->SaveSettings(archive);
925 
926 	// record display text
927 	if (ret == B_OK)
928 		ret = archive->AddString("displayText", fExpressionTextView->Text());
929 
930 	// record expression history
931 	if (ret == B_OK)
932 		ret = fExpressionTextView->SaveSettings(archive);
933 
934 	// record calculator description
935 	if (ret == B_OK)
936 		ret = archive->AddString("calcDesc",
937 			fKeypadDescription == kKeypadDescriptionBasic
938 			? "basic" : "scientific");
939 
940 	return ret;
941 }
942 
943 
944 void
945 CalcView::Evaluate()
946 {
947 	if (fExpressionTextView->TextLength() == 0) {
948 		beep();
949 		return;
950 	}
951 
952 	fEvaluateThread = spawn_thread(_EvaluateThread, "Evaluate Thread",
953 		B_LOW_PRIORITY, this);
954 	if (fEvaluateThread < B_OK) {
955 		// failed to create evaluate thread, error out
956 		fExpressionTextView->SetText(strerror(fEvaluateThread));
957 		return;
958 	}
959 
960 	_SetEnabled(false);
961 		// Disable input while we evaluate
962 
963 	status_t threadStatus = resume_thread(fEvaluateThread);
964 	if (threadStatus != B_OK) {
965 		// evaluate thread failed to start, error out
966 		fExpressionTextView->SetText(strerror(threadStatus));
967 		_SetEnabled(true);
968 		return;
969 	}
970 
971 	if (fEvaluateMessageRunner == NULL) {
972 		BMessage message(kMsgCalculating);
973 		fEvaluateMessageRunner = new (std::nothrow) BMessageRunner(
974 			BMessenger(this), &message, kCalculatingInterval, 1);
975 		status_t runnerStatus = fEvaluateMessageRunner->InitCheck();
976 		if (runnerStatus != B_OK)
977 			printf("Evaluate Message Runner: %s\n", strerror(runnerStatus));
978 	}
979 }
980 
981 
982 void
983 CalcView::FlashKey(const char* bytes, int32 numBytes)
984 {
985 	BString temp;
986 	temp.Append(bytes, numBytes);
987 	int32 key = _KeyForLabel(temp.String());
988 	if (key >= 0)
989 		_FlashKey(key, FLAGS_FLASH_KEY);
990 }
991 
992 
993 void
994 CalcView::ToggleAutoNumlock(void)
995 {
996 	fOptions->auto_num_lock = !fOptions->auto_num_lock;
997 	fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
998 }
999 
1000 
1001 void
1002 CalcView::SetDegreeMode(bool degrees)
1003 {
1004 	fOptions->degree_mode = degrees;
1005 	fAngleModeRadianItem->SetMarked(!degrees);
1006 	fAngleModeDegreeItem->SetMarked(degrees);
1007 }
1008 
1009 
1010 void
1011 CalcView::SetKeypadMode(uint8 mode)
1012 {
1013 	if (_IsEmbedded())
1014 		return;
1015 
1016 	BWindow* window = Window();
1017 	if (window == NULL)
1018 		return;
1019 
1020 	if (fOptions->keypad_mode == mode)
1021 		return;
1022 
1023 	fOptions->keypad_mode = mode;
1024 	_MarkKeypadItems(fOptions->keypad_mode);
1025 
1026 	float width = fWidth;
1027 	float height = fHeight;
1028 
1029 	switch (fOptions->keypad_mode) {
1030 		case KEYPAD_MODE_COMPACT:
1031 		{
1032 			if (window->Bounds() == Frame()) {
1033 				window->SetSizeLimits(kMinimumWidthCompact,
1034 					kMaximumWidthCompact, kMinimumHeightCompact,
1035 					kMaximumHeightCompact);
1036 				window->ResizeTo(width, height * kDisplayScaleY);
1037 			} else
1038 				ResizeTo(width, height * kDisplayScaleY);
1039 
1040 			break;
1041 		}
1042 
1043 		case KEYPAD_MODE_SCIENTIFIC:
1044 		{
1045 			fKeypadDescription = kKeypadDescriptionScientific;
1046 			fRows = 8;
1047 			_ParseCalcDesc(fKeypadDescription);
1048 
1049 			window->SetSizeLimits(kMinimumWidthScientific,
1050 				kMaximumWidthScientific, kMinimumHeightScientific,
1051 				kMaximumHeightScientific);
1052 
1053 			if (width < kMinimumWidthScientific)
1054 				width = kMinimumWidthScientific;
1055 			else if (width > kMaximumWidthScientific)
1056 				width = kMaximumWidthScientific;
1057 
1058 			if (height < kMinimumHeightScientific)
1059 				height = kMinimumHeightScientific;
1060 			else if (height > kMaximumHeightScientific)
1061 				height = kMaximumHeightScientific;
1062 
1063 			if (width != fWidth || height != fHeight)
1064 				ResizeTo(width, height);
1065 			else
1066 				Invalidate();
1067 
1068 			break;
1069 		}
1070 
1071 		case KEYPAD_MODE_BASIC:
1072 		default:
1073 		{
1074 			fKeypadDescription = kKeypadDescriptionBasic;
1075 			fRows = 4;
1076 			_ParseCalcDesc(fKeypadDescription);
1077 
1078 			window->SetSizeLimits(kMinimumWidthBasic, kMaximumWidthBasic,
1079 				kMinimumHeightBasic, kMaximumHeightBasic);
1080 
1081 			if (width < kMinimumWidthBasic)
1082 				width = kMinimumWidthBasic;
1083 			else if (width > kMaximumWidthBasic)
1084 				width = kMaximumWidthBasic;
1085 
1086 			if (height < kMinimumHeightBasic)
1087 				height = kMinimumHeightBasic;
1088 			else if (height > kMaximumHeightBasic)
1089 				height = kMaximumHeightBasic;
1090 
1091 			if (width != fWidth || height != fHeight)
1092 				ResizeTo(width, height);
1093 			else
1094 				Invalidate();
1095 		}
1096 	}
1097 }
1098 
1099 
1100 // #pragma mark -
1101 
1102 
1103 /*static*/ status_t
1104 CalcView::_EvaluateThread(void* data)
1105 {
1106 	CalcView* calcView = reinterpret_cast<CalcView*>(data);
1107 	if (calcView == NULL)
1108 		return B_BAD_TYPE;
1109 
1110 	BMessenger messenger(calcView);
1111 	if (!messenger.IsValid())
1112 		return B_BAD_VALUE;
1113 
1114 	BString result;
1115 	status_t status = acquire_sem(calcView->fEvaluateSemaphore);
1116 	if (status == B_OK) {
1117 		ExpressionParser parser;
1118 		parser.SetDegreeMode(calcView->fOptions->degree_mode);
1119 		BString expression(calcView->fExpressionTextView->Text());
1120 		try {
1121 			result = parser.Evaluate(expression.String());
1122 		} catch (ParseException& e) {
1123 			result << e.message.String() << " at " << (e.position + 1);
1124 			status = B_ERROR;
1125 		}
1126 		release_sem(calcView->fEvaluateSemaphore);
1127 	} else
1128 		result = strerror(status);
1129 
1130 	BMessage message(kMsgDoneEvaluating);
1131 	message.AddString(status == B_OK ? "value" : "error", result.String());
1132 	messenger.SendMessage(&message);
1133 
1134 	return status;
1135 }
1136 
1137 
1138 void
1139 CalcView::_Init(BMessage* settings)
1140 {
1141 	// create expression text view
1142 	fExpressionTextView = new ExpressionTextView(_ExpressionRect(), this);
1143 	AddChild(fExpressionTextView);
1144 
1145 	// read data from archive
1146 	_LoadSettings(settings);
1147 
1148 	// fetch the calc icon for compact view
1149 	_FetchAppIcon(fCalcIcon);
1150 
1151 	fEvaluateSemaphore = create_sem(1, "Evaluate Semaphore");
1152 }
1153 
1154 
1155 status_t
1156 CalcView::_LoadSettings(BMessage* archive)
1157 {
1158 	if (!archive)
1159 		return B_BAD_VALUE;
1160 
1161 	// record calculator description
1162 	BString calcDesc;
1163 	archive->FindString("calcDesc", &calcDesc);
1164 	if (calcDesc == "scientific" || calcDesc.StartsWith("ln"))
1165 		fKeypadDescription = kKeypadDescriptionScientific;
1166 	else
1167 		fKeypadDescription = kKeypadDescriptionBasic;
1168 
1169 	// read grid dimensions
1170 	if (archive->FindInt16("cols", &fColumns) < B_OK)
1171 		fColumns = 5;
1172 	if (archive->FindInt16("rows", &fRows) < B_OK)
1173 		fRows = 4;
1174 
1175 	// read color scheme
1176 	const rgb_color* color;
1177 	ssize_t size;
1178 	if (archive->FindData("rgbBaseColor", B_RGB_COLOR_TYPE,
1179 			(const void**)&color, &size) < B_OK
1180 		|| size != sizeof(rgb_color)) {
1181 		fBaseColor = ui_color(B_PANEL_BACKGROUND_COLOR);
1182 		puts("Missing rgbBaseColor from CalcView archive!\n");
1183 	} else
1184 		fBaseColor = *color;
1185 
1186 	if (archive->FindData("rgbDisplay", B_RGB_COLOR_TYPE,
1187 			(const void**)&color, &size) < B_OK
1188 		|| size != sizeof(rgb_color)) {
1189 		fExpressionBGColor = (rgb_color){ 0, 0, 0, 255 };
1190 		puts("Missing rgbBaseColor from CalcView archive!\n");
1191 	} else {
1192 		fExpressionBGColor = *color;
1193 	}
1194 
1195 	fHasCustomBaseColor = fBaseColor != ui_color(B_PANEL_BACKGROUND_COLOR);
1196 
1197 	// load options
1198 	fOptions->LoadSettings(archive);
1199 
1200 	// load display text
1201 	const char* display;
1202 	if (archive->FindString("displayText", &display) < B_OK) {
1203 		puts("Missing expression text from CalcView archive.\n");
1204 	} else {
1205 		// init expression text
1206 		fExpressionTextView->SetText(display);
1207 	}
1208 
1209 	// load expression history
1210 	fExpressionTextView->LoadSettings(archive);
1211 
1212 	// parse calculator description
1213 	_ParseCalcDesc(fKeypadDescription);
1214 
1215 	// colorize based on base color.
1216 	_Colorize();
1217 
1218 	return B_OK;
1219 }
1220 
1221 
1222 void
1223 CalcView::_ParseCalcDesc(const char** keypadDescription)
1224 {
1225 	// TODO: should calculate dimensions from desc here!
1226 	fKeypad = new CalcKey[fRows * fColumns];
1227 
1228 	// scan through calculator description and assemble keypad
1229 	CalcKey* key = fKeypad;
1230 	for (int i = 0; const char* p = keypadDescription[i]; i++) {
1231 		// Move to next row as needed
1232 		if (strcmp(p, "\n") == 0)
1233 			continue;
1234 
1235 		// copy label
1236 		strlcpy(key->label, B_TRANSLATE_NOCOLLECT(p), sizeof(key->label));
1237 
1238 		// set code
1239 		if (strcmp(key->label, "=") == 0)
1240 			strlcpy(key->code, "\n", sizeof(key->code));
1241 		else
1242 			strlcpy(key->code, key->label, sizeof(key->code));
1243 
1244 		// set keymap
1245 		if (strlen(key->label) == 1)
1246 			strlcpy(key->keymap, key->label, sizeof(key->keymap));
1247 		else
1248 			*key->keymap = '\0';
1249 
1250 		key->flags = 0;
1251 
1252 		// add this to the expression text view, so that it
1253 		// will forward the respective KeyDown event to us
1254 		fExpressionTextView->AddKeypadLabel(key->label);
1255 
1256 		// advance
1257 		key++;
1258 	}
1259 }
1260 
1261 
1262 void
1263 CalcView::_PressKey(int key)
1264 {
1265 	if (!fEnabled)
1266 		return;
1267 
1268 	assert(key < (fRows * fColumns));
1269 	assert(key >= 0);
1270 
1271 	if (strcmp(fKeypad[key].label, B_TRANSLATE_COMMENT("BS",
1272 		"Key label, 'BS' means backspace")) == 0) {
1273 		// BS means backspace
1274 		fExpressionTextView->BackSpace();
1275 	} else if (strcmp(fKeypad[key].label, B_TRANSLATE_COMMENT("C",
1276 		"Key label, 'C' means clear")) == 0) {
1277 		// C means clear
1278 		fExpressionTextView->Clear();
1279 	} else if (strcmp(fKeypad[key].label, B_TRANSLATE("acos")) == 0
1280 		|| strcmp(fKeypad[key].label, B_TRANSLATE("asin")) == 0
1281 		|| strcmp(fKeypad[key].label, B_TRANSLATE("atan")) == 0
1282 		|| strcmp(fKeypad[key].label, B_TRANSLATE("cbrt")) == 0
1283 		|| strcmp(fKeypad[key].label, B_TRANSLATE("ceil")) == 0
1284 		|| strcmp(fKeypad[key].label, B_TRANSLATE("cos")) == 0
1285 		|| strcmp(fKeypad[key].label, B_TRANSLATE("cosh")) == 0
1286 		|| strcmp(fKeypad[key].label, B_TRANSLATE("exp")) == 0
1287 		|| strcmp(fKeypad[key].label, B_TRANSLATE("floor")) == 0
1288 		|| strcmp(fKeypad[key].label, B_TRANSLATE("log")) == 0
1289 		|| strcmp(fKeypad[key].label, B_TRANSLATE("ln")) == 0
1290 		|| strcmp(fKeypad[key].label, B_TRANSLATE("sin")) == 0
1291 		|| strcmp(fKeypad[key].label, B_TRANSLATE("sinh")) == 0
1292 		|| strcmp(fKeypad[key].label, B_TRANSLATE("sqrt")) == 0
1293 		|| strcmp(fKeypad[key].label, B_TRANSLATE("tan")) == 0
1294 		|| strcmp(fKeypad[key].label, B_TRANSLATE("tanh")) == 0) {
1295 		int32 labelLen = strlen(fKeypad[key].label);
1296 		int32 startSelection = 0;
1297 		int32 endSelection = 0;
1298 		fExpressionTextView->GetSelection(&startSelection, &endSelection);
1299 		if (endSelection > startSelection) {
1300 			// There is selected text, put it inbetween the parens
1301 			fExpressionTextView->Insert(startSelection, fKeypad[key].label,
1302 				labelLen);
1303 			fExpressionTextView->Insert(startSelection + labelLen, "(", 1);
1304 			fExpressionTextView->Insert(endSelection + labelLen + 1, ")", 1);
1305 			// Put the cursor after the ending paren
1306 			// Need to cast to BTextView because Select() is protected
1307 			// in the InputTextView class
1308 			static_cast<BTextView*>(fExpressionTextView)->Select(
1309 				endSelection + labelLen + 2, endSelection + labelLen + 2);
1310 		} else {
1311 			// There is no selected text, insert at the cursor location
1312 			fExpressionTextView->Insert(fKeypad[key].label);
1313 			fExpressionTextView->Insert("()");
1314 			// Put the cursor inside the parens so you can enter an argument
1315 			// Need to cast to BTextView because Select() is protected
1316 			// in the InputTextView class
1317 			static_cast<BTextView*>(fExpressionTextView)->Select(
1318 				endSelection + labelLen + 1, endSelection + labelLen + 1);
1319 		}
1320 	} else {
1321 		// check for evaluation order
1322 		if (fKeypad[key].code[0] == '\n') {
1323 			fExpressionTextView->ApplyChanges();
1324 		} else {
1325 			// insert into expression text
1326 			fExpressionTextView->Insert(fKeypad[key].code);
1327 		}
1328 	}
1329 }
1330 
1331 
1332 void
1333 CalcView::_PressKey(const char* label)
1334 {
1335 	int32 key = _KeyForLabel(label);
1336 	if (key >= 0)
1337 		_PressKey(key);
1338 }
1339 
1340 
1341 int32
1342 CalcView::_KeyForLabel(const char* label) const
1343 {
1344 	int keys = fRows * fColumns;
1345 	for (int i = 0; i < keys; i++) {
1346 		if (strcmp(fKeypad[i].label, label) == 0) {
1347 			return i;
1348 		}
1349 	}
1350 	return -1;
1351 }
1352 
1353 
1354 void
1355 CalcView::_FlashKey(int32 key, uint32 flashFlags)
1356 {
1357 	if (fOptions->keypad_mode == KEYPAD_MODE_COMPACT)
1358 		return;
1359 
1360 	if (flashFlags != 0)
1361 		fKeypad[key].flags |= flashFlags;
1362 	else
1363 		fKeypad[key].flags = 0;
1364 	Invalidate();
1365 
1366 	if (fKeypad[key].flags == FLAGS_FLASH_KEY) {
1367 		BMessage message(MSG_UNFLASH_KEY);
1368 		message.AddInt32("key", key);
1369 		BMessageRunner::StartSending(BMessenger(this), &message,
1370 			kFlashOnOffInterval, 1);
1371 	}
1372 }
1373 
1374 
1375 void
1376 CalcView::_Colorize()
1377 {
1378 	// calculate light and dark color from base color
1379 	fLightColor.red		= (uint8)(fBaseColor.red * 1.25);
1380 	fLightColor.green	= (uint8)(fBaseColor.green * 1.25);
1381 	fLightColor.blue	= (uint8)(fBaseColor.blue * 1.25);
1382 	fLightColor.alpha	= 255;
1383 
1384 	fDarkColor.red		= (uint8)(fBaseColor.red * 0.75);
1385 	fDarkColor.green	= (uint8)(fBaseColor.green * 0.75);
1386 	fDarkColor.blue		= (uint8)(fBaseColor.blue * 0.75);
1387 	fDarkColor.alpha	= 255;
1388 
1389 	// keypad text color
1390 	if (fBaseColor.Brightness() > 100)
1391 		fButtonTextColor = (rgb_color){ 0, 0, 0, 255 };
1392 	else
1393 		fButtonTextColor = (rgb_color){ 255, 255, 255, 255 };
1394 
1395 	// expression text color
1396 	if (fExpressionBGColor.Brightness() > 100)
1397 		fExpressionTextColor = (rgb_color){ 0, 0, 0, 255 };
1398 	else
1399 		fExpressionTextColor = (rgb_color){ 255, 255, 255, 255 };
1400 }
1401 
1402 
1403 void
1404 CalcView::_CreatePopUpMenu(bool addKeypadModeMenuItems)
1405 {
1406 	// construct items
1407 	fAutoNumlockItem = new BMenuItem(B_TRANSLATE("Enable Num Lock on startup"),
1408 		new BMessage(MSG_OPTIONS_AUTO_NUM_LOCK));
1409 	fAngleModeRadianItem = new BMenuItem(B_TRANSLATE("Radians"),
1410 		new BMessage(MSG_OPTIONS_ANGLE_MODE_RADIAN));
1411 	fAngleModeDegreeItem = new BMenuItem(B_TRANSLATE("Degrees"),
1412 		new BMessage(MSG_OPTIONS_ANGLE_MODE_DEGREE));
1413 	if (addKeypadModeMenuItems) {
1414 		fKeypadModeCompactItem = new BMenuItem(B_TRANSLATE("Compact"),
1415 			new BMessage(MSG_OPTIONS_KEYPAD_MODE_COMPACT), '0');
1416 		fKeypadModeBasicItem = new BMenuItem(B_TRANSLATE("Basic"),
1417 			new BMessage(MSG_OPTIONS_KEYPAD_MODE_BASIC), '1');
1418 		fKeypadModeScientificItem = new BMenuItem(B_TRANSLATE("Scientific"),
1419 			new BMessage(MSG_OPTIONS_KEYPAD_MODE_SCIENTIFIC), '2');
1420 	}
1421 
1422 	// apply current settings
1423 	fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
1424 	fAngleModeRadianItem->SetMarked(!fOptions->degree_mode);
1425 	fAngleModeDegreeItem->SetMarked(fOptions->degree_mode);
1426 
1427 	// construct menu
1428 	fPopUpMenu = new BPopUpMenu("pop-up", false, false);
1429 
1430 	fPopUpMenu->AddItem(fAutoNumlockItem);
1431 	fPopUpMenu->AddSeparatorItem();
1432 	fPopUpMenu->AddItem(fAngleModeRadianItem);
1433 	fPopUpMenu->AddItem(fAngleModeDegreeItem);
1434 	if (addKeypadModeMenuItems) {
1435 		fPopUpMenu->AddSeparatorItem();
1436 		fPopUpMenu->AddItem(fKeypadModeCompactItem);
1437 		fPopUpMenu->AddItem(fKeypadModeBasicItem);
1438 		fPopUpMenu->AddItem(fKeypadModeScientificItem);
1439 		_MarkKeypadItems(fOptions->keypad_mode);
1440 	}
1441 }
1442 
1443 
1444 BRect
1445 CalcView::_ExpressionRect() const
1446 {
1447 	BRect r(0.0, 0.0, fWidth, fHeight);
1448 	if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) {
1449 		r.bottom = floorf(fHeight * kDisplayScaleY) + 1;
1450 	}
1451 	return r;
1452 }
1453 
1454 
1455 BRect
1456 CalcView::_KeypadRect() const
1457 {
1458 	BRect r(0.0, 0.0, -1.0, -1.0);
1459 	if (fOptions->keypad_mode != KEYPAD_MODE_COMPACT) {
1460 		r.right = fWidth;
1461 		r.bottom = fHeight;
1462 		r.top = floorf(fHeight * kDisplayScaleY);
1463 	}
1464 	return r;
1465 }
1466 
1467 
1468 void
1469 CalcView::_MarkKeypadItems(uint8 keypad_mode)
1470 {
1471 	switch (keypad_mode) {
1472 		case KEYPAD_MODE_COMPACT:
1473 			fKeypadModeCompactItem->SetMarked(true);
1474 			fKeypadModeBasicItem->SetMarked(false);
1475 			fKeypadModeScientificItem->SetMarked(false);
1476 			break;
1477 
1478 		case KEYPAD_MODE_SCIENTIFIC:
1479 			fKeypadModeCompactItem->SetMarked(false);
1480 			fKeypadModeBasicItem->SetMarked(false);
1481 			fKeypadModeScientificItem->SetMarked(true);
1482 			break;
1483 
1484 		default: // KEYPAD_MODE_BASIC is the default
1485 			fKeypadModeCompactItem->SetMarked(false);
1486 			fKeypadModeBasicItem->SetMarked(true);
1487 			fKeypadModeScientificItem->SetMarked(false);
1488 	}
1489 }
1490 
1491 
1492 void
1493 CalcView::_FetchAppIcon(BBitmap* into)
1494 {
1495 	entry_ref appRef;
1496 	status_t status = be_roster->FindApp(kSignature, &appRef);
1497 	if (status == B_OK) {
1498 		BFile file(&appRef, B_READ_ONLY);
1499 		BAppFileInfo appInfo(&file);
1500 		status = appInfo.GetIcon(into, B_MINI_ICON);
1501 	}
1502 	if (status != B_OK)
1503 		memset(into->Bits(), 0, into->BitsLength());
1504 }
1505 
1506 
1507 // Returns whether or not CalcView is embedded somewhere, most likely
1508 // the Desktop
1509 bool
1510 CalcView::_IsEmbedded()
1511 {
1512 	return Parent() != NULL && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0;
1513 }
1514 
1515 
1516 void
1517 CalcView::_SetEnabled(bool enable)
1518 {
1519 	fEnabled = enable;
1520 	fExpressionTextView->MakeSelectable(enable);
1521 	fExpressionTextView->MakeEditable(enable);
1522 }
1523