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