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