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