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