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