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