xref: /haiku/src/apps/deskcalc/CalcView.cpp (revision 9ecf9d1c1d4888d341a6eac72112c72d1ae3a4cb)
1 /*
2  * Copyright 2006 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  */
10 
11 #include "CalcView.h"
12 
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <ctype.h>
17 #include <assert.h>
18 
19 #include <Alert.h>
20 #include <Beep.h>
21 #include <Bitmap.h>
22 #include <Clipboard.h>
23 #include <Font.h>
24 #include <MenuItem.h>
25 #include <Message.h>
26 #include <PlaySound.h>
27 #include <Point.h>
28 #include <PopUpMenu.h>
29 
30 #include "CalcApplication.h"
31 #include "CalcIcon.h"
32 #include "CalcOptionsWindow.h"
33 #include "ExpressionParser.h"
34 #include "ExpressionTextView.h"
35 
36 
37 const uint8 K_COLOR_OFFSET		= 32;
38 const float K_FONT_YPROP		= 0.6f;
39 const float K_DISPLAY_YPROP		= 0.2;
40 
41 enum {
42 	K_OPTIONS_REQUESTED			= 'opts',
43 	K_OPTIONS_GONE				= 'opgn',
44 };
45 
46 // default calculator key pad layout
47 const char *kDefaultKeypadDescription =
48 	"7   8   9   (   )  \n"
49 	"4   5   6   *   /  \n"
50 	"1   2   3   +   -  \n"
51 	"0   .   BS  =   C  \n";
52 
53 
54 struct CalcView::CalcKey {
55 	char		label[8];
56 	char		code[8];
57 	char		keymap[4];
58 	uint32		flags;
59 	float		width;
60 };
61 
62 
63 CalcView *CalcView::Instantiate(BMessage *archive)
64 {
65 	if (!validate_instantiation(archive, "CalcView"))
66 		return NULL;
67 
68 	return new CalcView(archive);
69 }
70 
71 
72 CalcView::CalcView(BRect frame, rgb_color rgbBaseColor)
73 	: BView(frame, "DeskCalc", B_FOLLOW_ALL_SIDES,
74 			B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
75 	  fColums(5),
76 	  fRows(4),
77 
78 	  fBaseColor(rgbBaseColor),
79 	  fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }),
80 
81 	  fWidth(1),
82 	  fHeight(1),
83 
84 	  fKeypadDescription(strdup(kDefaultKeypadDescription)),
85 	  fKeypad(NULL),
86 
87 	  fCalcIcon(new BBitmap(BRect(0, 0, kIconWidth - 1, kIconHeight - 1),
88 	  						0, kIconFormat)),
89 
90 	  fAboutItem(NULL),
91 	  fOptionsItem(NULL),
92 	  fPopUpMenu(NULL),
93 
94 	  fOptions(new CalcOptions()),
95 	  fOptionsWindow(NULL),
96 	  fOptionsWindowFrame(30.0, 50.0, 230.0, 200.0),
97 	  fShowKeypad(true)
98 {
99 	// create expression text view
100 	fExpressionTextView = new ExpressionTextView(_ExpressionRect(), this);
101 	AddChild(fExpressionTextView);
102 
103 	// tell the app server not to erase our b/g
104 	SetViewColor(B_TRANSPARENT_32_BIT);
105 
106 	// parse calculator description
107 	_ParseCalcDesc(fKeypadDescription);
108 
109 	// colorize based on base color.
110 	_Colorize();
111 
112 	// create pop-up menu system
113 	_CreatePopUpMenu();
114 
115 	// copy calculator icon into bitmap
116 	memcpy(fCalcIcon->Bits(), kIconBits, fCalcIcon->BitsLength());
117 }
118 
119 
120 CalcView::CalcView(BMessage* archive)
121 	: BView(archive),
122 	  fColums(5),
123 	  fRows(4),
124 
125 	  fBaseColor((rgb_color){ 128, 128, 128, 255 }),
126 	  fExpressionBGColor((rgb_color){ 0, 0, 0, 255 }),
127 
128 	  fWidth(1),
129 	  fHeight(1),
130 
131 	  fKeypadDescription(strdup(kDefaultKeypadDescription)),
132 	  fKeypad(NULL),
133 
134 	  fCalcIcon(new BBitmap(BRect(0, 0, kIconWidth - 1, kIconHeight - 1),
135 	  						0, kIconFormat)),
136 
137 	  fAboutItem(NULL),
138 	  fOptionsItem(NULL),
139 	  fPopUpMenu(NULL),
140 
141 	  fOptions(new CalcOptions()),
142 	  fOptionsWindow(NULL),
143 	  fOptionsWindowFrame(30.0, 50.0, 230.0, 200.0),
144 	  fShowKeypad(true)
145 {
146 	// create expression text view
147 	fExpressionTextView = new ExpressionTextView(_ExpressionRect(), this);
148 	AddChild(fExpressionTextView);
149 
150 	// read data from archive
151 	LoadSettings(archive);
152 
153 	// create pop-up menu system
154 	_CreatePopUpMenu();
155 
156 	// copy calculator icon into bitmap
157 	memcpy(fCalcIcon->Bits(), kIconBits, fCalcIcon->BitsLength());
158 }
159 
160 
161 CalcView::~CalcView()
162 {
163 	delete fKeypad;
164 	delete fOptions;
165 	free(fKeypadDescription);
166 }
167 
168 
169 void
170 CalcView::AttachedToWindow()
171 {
172 	SetFont(be_bold_font);
173 
174 	BRect frame(Frame());
175 	FrameResized(frame.Width(), frame.Height());
176 }
177 
178 
179 void
180 CalcView::MessageReceived(BMessage* message)
181 {
182 	//message->PrintToStream();
183 
184 	// check if message was dropped
185 	if (message->WasDropped()) {
186 		// pass message on to paste
187 		if (message->IsSourceRemote())
188 			Paste(message);
189 
190 	} else {
191 
192 		// act on posted message type
193 		switch (message->what) {
194 
195 			// handle "cut"
196 			case B_CUT:
197 				Cut();
198 				break;
199 
200 			// handle copy
201 			case B_COPY:
202 				Copy();
203 				break;
204 
205 			// handle paste
206 			case B_PASTE:
207 				// access system clipboard
208 				if (be_clipboard->Lock()){
209 					BMessage *clipper = be_clipboard->Data();
210 					//clipper->PrintToStream();
211 					Paste(clipper);
212 					be_clipboard->Unlock();
213 					} // end if
214 				break;
215 
216 			// (replicant) about box requested
217 			case B_ABOUT_REQUESTED:
218 				AboutRequested();
219 				break;
220 
221 			// calculator options window requested
222 			case K_OPTIONS_REQUESTED: {
223 				if (fOptionsWindow != NULL) {
224 // TODO: remove race condition and activate
225 //					fOptionsWindow->Activate();
226 					break;
227 				}
228 
229 				fOptionsWindow = new CalcOptionsWindow(fOptionsWindowFrame,
230 													   fOptions,
231 													   new BMessage(K_OPTIONS_GONE),
232 													   this);
233 				fOptionsWindow->Show();
234 				break;
235 			}
236 
237 			// calculator options window has quit
238 			case K_OPTIONS_GONE: {
239 				fOptionsWindow = NULL;
240 
241 				BRect frame;
242 				if (message->FindRect("window frame", &frame) == B_OK)
243 					fOptionsWindowFrame = frame;
244 
245 				_ShowKeypad(fOptions->show_keypad);
246 				break;
247 			}
248 
249 			default:
250 				BView::MessageReceived(message);
251 				break;
252 		}
253 	}
254 }
255 
256 
257 void
258 CalcView::Draw(BRect updateRect)
259 {
260 	BRect expressionRect(_ExpressionRect());
261 	if (updateRect.Intersects(expressionRect)) {
262 		if (!fShowKeypad && expressionRect.Height() >= fCalcIcon->Bounds().Height()) {
263 			// render calc icon
264 			expressionRect.left = fExpressionTextView->Frame().right + 2;
265 			SetHighColor(fBaseColor);
266 			FillRect(updateRect & expressionRect);
267 
268 			SetDrawingMode(B_OP_ALPHA);
269 			SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
270 
271 			BPoint iconPos;
272 			iconPos.x = expressionRect.right - (expressionRect.Width()
273 				+ fCalcIcon->Bounds().Width()) / 2.0;
274 			iconPos.y = expressionRect.top + (expressionRect.Height()
275 				- fCalcIcon->Bounds().Height()) / 2.0;
276 			DrawBitmap(fCalcIcon, iconPos);
277 
278 			SetDrawingMode(B_OP_COPY);
279 		}
280 
281 		// render border around expression text view
282 		expressionRect = fExpressionTextView->Frame();
283 		expressionRect.InsetBy(-2, -2);
284 		BeginLineArray(8);
285 
286 		rgb_color lightShadow = tint_color(fBaseColor, B_DARKEN_1_TINT);
287 		rgb_color darkShadow = tint_color(fBaseColor, B_DARKEN_3_TINT);
288 
289 		AddLine(BPoint(expressionRect.left, expressionRect.bottom),
290 				BPoint(expressionRect.left, expressionRect.top),
291 				lightShadow);
292 		AddLine(BPoint(expressionRect.left + 1, expressionRect.top),
293 				BPoint(expressionRect.right, expressionRect.top),
294 				lightShadow);
295 		AddLine(BPoint(expressionRect.right, expressionRect.top + 1),
296 				BPoint(expressionRect.right, expressionRect.bottom),
297 				fLightColor);
298 		AddLine(BPoint(expressionRect.left + 1, expressionRect.bottom),
299 				BPoint(expressionRect.right - 1, expressionRect.bottom),
300 				fLightColor);
301 
302 		expressionRect.InsetBy(1, 1);
303 		AddLine(BPoint(expressionRect.left, expressionRect.bottom),
304 				BPoint(expressionRect.left, expressionRect.top),
305 				darkShadow);
306 		AddLine(BPoint(expressionRect.left + 1, expressionRect.top),
307 				BPoint(expressionRect.right, expressionRect.top),
308 				darkShadow);
309 		AddLine(BPoint(expressionRect.right, expressionRect.top + 1),
310 				BPoint(expressionRect.right, expressionRect.bottom),
311 				fBaseColor);
312 		AddLine(BPoint(expressionRect.left + 1, expressionRect.bottom),
313 				BPoint(expressionRect.right - 1, expressionRect.bottom),
314 				fBaseColor);
315 
316 		EndLineArray();
317 	}
318 
319 	if (!fShowKeypad)
320 		return;
321 
322 	// calculate grid sizes
323 	BRect keypadRect(_KeypadRect());
324 	float sizeDisp = keypadRect.top;
325 	float sizeCol = fWidth / (float)fColums;
326 	float sizeRow = (fHeight - sizeDisp) / (float)fRows;
327 
328 	if (updateRect.Intersects(keypadRect)) {
329 		// TODO: support pressed keys
330 
331 		// paint keypad b/g
332 		SetHighColor(fBaseColor);
333 		FillRect(updateRect & keypadRect);
334 
335 		// render key main grid
336 		BeginLineArray((fColums + fRows) << 1 + 1);
337 
338 		// render cols
339 		AddLine(BPoint(0.0, sizeDisp),
340 				BPoint(0.0, fHeight),
341 				fLightColor);
342 		for (int col = 1; col < fColums; col++) {
343 			AddLine(BPoint(col * sizeCol - 1.0, sizeDisp),
344 					BPoint(col * sizeCol - 1.0, fHeight),
345 					fDarkColor);
346 			AddLine(BPoint(col * sizeCol, sizeDisp),
347 					BPoint(col * sizeCol, fHeight),
348 					fLightColor);
349 		}
350 		AddLine(BPoint(fColums * sizeCol, sizeDisp),
351 				BPoint(fColums * sizeCol, fHeight),
352 				fDarkColor);
353 
354 		// render rows
355 		for (int row = 0; row < fRows; row++) {
356 			AddLine(BPoint(0.0, sizeDisp + row * sizeRow - 1.0),
357 					BPoint(fWidth, sizeDisp + row * sizeRow - 1.0),
358 					fDarkColor);
359 			AddLine(BPoint(0.0, sizeDisp + row * sizeRow),
360 					BPoint(fWidth, sizeDisp + row * sizeRow),
361 					fLightColor);
362 		}
363 		AddLine(BPoint(0.0, sizeDisp + fRows * sizeRow),
364 				BPoint(fWidth, sizeDisp + fRows * sizeRow),
365 				fDarkColor);
366 
367 		// main grid complete
368 		EndLineArray();
369 
370 		// render key symbols
371 		float halfSizeCol = sizeCol * 0.5f;
372 		SetHighColor(fButtonTextColor);
373 		SetLowColor(fBaseColor);
374 		SetDrawingMode(B_OP_COPY);
375 		SetFontSize(((fHeight - sizeDisp) / (float)fRows) * K_FONT_YPROP);
376 		float baselineOffset = ((fHeight - sizeDisp) / (float)fRows)
377 								* (1.0 - K_FONT_YPROP) * 0.5;
378 		CalcKey *key = fKeypad;
379 		for (int row = 0; row < fRows; row++) {
380 			for (int col = 0; col < fColums; col++) {
381 				float halfSymbolWidth = StringWidth(key->label) * 0.5f;
382 				DrawString(key->label,
383 						   BPoint(col * sizeCol + halfSizeCol - halfSymbolWidth,
384 								  sizeDisp + (row + 1) * sizeRow - baselineOffset));
385 				key++;
386 			}
387 		}
388 	}
389 }
390 
391 
392 void
393 CalcView::MouseDown(BPoint point)
394 {
395 	// ensure this view is the current focus
396 	MakeFocus();
397 
398 	// read mouse buttons state
399 	int32 buttons = 0;
400 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
401 
402 	// display popup menu if not primary mouse button
403 	if ((B_PRIMARY_MOUSE_BUTTON & buttons) == 0) {
404 		BMenuItem* selected;
405 		if ((selected = fPopUpMenu->Go(ConvertToScreen(point))) != NULL)
406 			MessageReceived(selected->Message());
407 		return;
408 	}
409 
410 	if (!fShowKeypad)
411 		return;
412 
413 	// calculate grid sizes
414 	float sizeDisp = fHeight * K_DISPLAY_YPROP;
415 	float sizeCol = fWidth / (float)fColums;
416 	float sizeRow = (fHeight - sizeDisp) / (float)fRows;
417 
418 	// calculate location within grid
419 	int gridCol = (int)(point.x / sizeCol);
420 	int gridRow = (int)((point.y - sizeDisp) / sizeRow);
421 
422 	// check limits
423 	if ((gridCol >= 0) && (gridCol < fColums) &&
424 		(gridRow >= 0) && (gridRow < fRows)) {
425 
426 		// process key press
427 		_PressKey(gridRow * fColums + gridCol);
428 	}
429 }
430 
431 
432 void
433 CalcView::KeyDown(const char *bytes, int32 numBytes)
434 {
435  	// if single byte character...
436 	if (numBytes == 1) {
437 
438 		//printf("Key pressed: %c\n", bytes[0]);
439 
440 		switch (bytes[0]) {
441 
442 			case B_ENTER:
443 				// translate to evaluate key
444 				_PressKey("=");
445 				break;
446 
447 			case B_LEFT_ARROW:
448 			case B_BACKSPACE:
449 				// translate to backspace key
450 				_PressKey("BS");
451 				break;
452 
453 			case B_SPACE:
454 			case B_ESCAPE:
455 			case 'c':
456 				// translate to clear key
457 				_PressKey("C");
458 				break;
459 
460 			// bracket translation
461 			case '[':
462 			case '{':
463 				_PressKey("(");
464 				break;
465 
466 			case ']':
467 			case '}':
468 				_PressKey(")");
469 				break;
470 
471 			default: {
472 				// scan the keymap array for match
473 				int keys = fRows * fColums;
474 				for (int i = 0; i < keys; i++) {
475 					if (fKeypad[i].keymap[0] == bytes[0]) {
476 						_PressKey(i);
477 						return;
478 					}
479 				}
480 				break;
481 			}
482 		}
483 	}
484 }
485 
486 
487 void
488 CalcView::MakeFocus(bool focused)
489 {
490 	if (focused) {
491 		// set num lock
492 		if (fOptions->auto_num_lock) {
493 			set_keyboard_locks(B_NUM_LOCK |
494 							   (modifiers() & (B_CAPS_LOCK | B_SCROLL_LOCK)));
495 		}
496 	}
497 
498 	// pass on request to text view
499 	fExpressionTextView->MakeFocus(focused);
500 }
501 
502 
503 void
504 CalcView::FrameResized(float width, float height)
505 {
506 	fWidth = width;
507 	fHeight = height;
508 
509 	// layout expression text view
510 	BRect frame = _ExpressionRect();
511 	frame.InsetBy(2, 2);
512 
513 	if (!fShowKeypad)
514 		frame.right -= ceilf(fCalcIcon->Bounds().Width() * 1.5);
515 
516 	fExpressionTextView->MoveTo(frame.LeftTop());
517 	fExpressionTextView->ResizeTo(frame.Width(), frame.Height());
518 
519 	frame.OffsetTo(B_ORIGIN);
520 	frame.InsetBy(2, 2);
521 	fExpressionTextView->SetTextRect(frame);
522 
523 	// configure expression text view font size and color
524 	float sizeDisp = fShowKeypad ? fHeight * K_DISPLAY_YPROP : fHeight;
525 	BFont font(be_bold_font);
526 	font.SetSize(sizeDisp * K_FONT_YPROP);
527 //	fExpressionTextView->SetViewColor(fExpressionBGColor);
528 //	fExpressionTextView->SetLowColor(fExpressionBGColor);
529 //	fExpressionTextView->SetFontAndColor(&font, B_FONT_ALL, &fExpressionTextColor);
530 fExpressionTextView->SetFontAndColor(&font, B_FONT_ALL);
531 }
532 
533 
534 void
535 CalcView::AboutRequested()
536 {
537 	BAlert* alert = new BAlert("about",
538 		"DeskCalc v2.1.0\n\n"
539 		"written by Timothy Wayper,\nStephan Aßmus and Ingo Weinhold\n\n"
540 		B_UTF8_COPYRIGHT"1997, 1998 R3 Software Ltd.\n"
541 		B_UTF8_COPYRIGHT"2006 Haiku, Inc.\n\n"
542 		"All Rights Reserved.", "Cool");
543 	alert->Go(NULL);
544 }
545 
546 
547 status_t
548 CalcView::Archive(BMessage* archive, bool deep) const
549 {
550 	fExpressionTextView->RemoveSelf();
551 
552 	// passed on request to parent
553 	status_t ret = BView::Archive(archive, deep);
554 
555 	const_cast<CalcView*>(this)->AddChild(fExpressionTextView);
556 
557 	// save app signature for replicant add-on loading
558     if (ret == B_OK)
559     	ret = archive->AddString("add_on", kAppSig);
560 
561 	// save all the options
562 	if (ret == B_OK)
563 		ret = SaveSettings(archive);
564 
565 	// add class info last
566 	if (ret == B_OK)
567 		ret = archive->AddString("class", "CalcView");
568 
569 	return ret;
570 }
571 
572 
573 void
574 CalcView::Cut()
575 {
576 	Copy();	// copy data to clipboard
577 	fExpressionTextView->Clear(); // remove data
578 }
579 
580 
581 void
582 CalcView::Copy()
583 {
584 	// access system clipboard
585 	if (be_clipboard->Lock()) {
586 		be_clipboard->Clear();
587 		BMessage *clipper = be_clipboard->Data();
588 		clipper->what = B_MIME_DATA;
589 		// TODO: should check return for errors!
590 		BString expression = fExpressionTextView->Text();
591 		clipper->AddData("text/plain",
592 						 B_MIME_TYPE,
593 						 expression.String(),
594 						 expression.Length());
595 		//clipper->PrintToStream();
596 		be_clipboard->Commit();
597 		be_clipboard->Unlock();
598 	}
599 }
600 
601 
602 void
603 CalcView::Paste(BMessage *message)
604 {
605 	// handle color drops first
606 	// read incoming color
607 	const rgb_color* dropColor = NULL;
608 	ssize_t dataSize;
609 	if (message->FindData("RGBColor",
610 						  B_RGB_COLOR_TYPE,
611 						  (const void**)&dropColor,
612 						  &dataSize) == B_OK
613 		&& dataSize == sizeof(rgb_color)) {
614 
615 		// calculate view relative drop point
616 		BPoint dropPoint = ConvertFromScreen(message->DropPoint());
617 
618 		// calculate current keypad area
619 		float sizeDisp = fHeight * K_DISPLAY_YPROP;
620 		BRect keypadRect(0.0, sizeDisp, fWidth, fHeight);
621 
622 		// check location of color drop
623 		if (keypadRect.Contains(dropPoint) && dropColor) {
624 			fBaseColor = *dropColor;
625 			_Colorize();
626 			// redraw keypad
627 			Invalidate(keypadRect);
628 		}
629 
630 	} else {
631 		// look for text/plain MIME data
632 		const char* text;
633 		ssize_t numBytes;
634 		if (message->FindData("text/plain",
635 							  B_MIME_TYPE,
636 							  (const void**)&text,
637 							  &numBytes) == B_OK) {
638 			BString temp;
639 			temp.Append(text, numBytes);
640 			fExpressionTextView->Insert(temp.String());
641 		}
642 	}
643 }
644 
645 
646 status_t
647 CalcView::LoadSettings(BMessage* archive)
648 {
649 	if (!archive)
650 		return B_BAD_VALUE;
651 
652 	// record calculator description
653 	const char* calcDesc;
654 	if (archive->FindString("calcDesc", &calcDesc) < B_OK)
655 		calcDesc = kDefaultKeypadDescription;
656 
657 	// save calculator description for reference
658 	free(fKeypadDescription);
659 	fKeypadDescription = strdup(calcDesc);
660 
661 	// read grid dimensions
662 	if (archive->FindInt16("cols", &fColums) < B_OK)
663 		fColums = 5;
664 	if (archive->FindInt16("rows", &fRows) < B_OK)
665 		fRows = 4;
666 
667 	// read color scheme
668 	const rgb_color* color;
669 	ssize_t size;
670 	if (archive->FindData("rgbBaseColor", B_RGB_COLOR_TYPE,
671 						  (const void**)&color, &size) < B_OK
672 						  || size != sizeof(rgb_color)) {
673 		fBaseColor = (rgb_color){ 128, 128, 128, 255 };
674 		puts("Missing rgbBaseColor from CalcView archive!\n");
675 	} else {
676 		fBaseColor = *color;
677 	}
678 
679 	if (archive->FindData("rgbDisplay", B_RGB_COLOR_TYPE,
680 						  (const void**)&color, &size) < B_OK
681 						  || size != sizeof(rgb_color)) {
682 		fExpressionBGColor = (rgb_color){ 0, 0, 0, 255 };
683 		puts("Missing rgbBaseColor from CalcView archive!\n");
684 	} else {
685 		fExpressionBGColor = *color;
686 	}
687 
688 	// load options
689 	fOptions->LoadSettings(archive);
690 	fShowKeypad = fOptions->show_keypad;
691 
692 	// load option window frame
693 	BRect frame;
694 	if (archive->FindRect("option window frame", &frame) == B_OK)
695 		fOptionsWindowFrame = frame;
696 
697 	// load display text
698 	const char* display;
699 	if (archive->FindString("displayText", &display) < B_OK) {
700 		puts("Missing expression text from CalcView archive.\n");
701 	} else {
702 		// init expression text
703 		fExpressionTextView->SetText(display);
704 	}
705 
706 	// load expression history
707 	fExpressionTextView->LoadSettings(archive);
708 
709 	// parse calculator description
710 	_ParseCalcDesc(fKeypadDescription);
711 
712 	// colorize based on base color.
713 	_Colorize();
714 
715 	return B_OK;
716 }
717 
718 
719 status_t
720 CalcView::SaveSettings(BMessage* archive) const
721 {
722 	status_t ret = archive ? B_OK : B_BAD_VALUE;
723 
724 	// record grid dimensions
725     if (ret == B_OK)
726 		ret = archive->AddInt16("cols", fColums);
727     if (ret == B_OK)
728 		ret = archive->AddInt16("rows", fRows);
729 
730 	// record color scheme
731     if (ret == B_OK)
732 		ret = archive->AddData("rgbBaseColor", B_RGB_COLOR_TYPE,
733 							   &fBaseColor, sizeof(rgb_color));
734 	if (ret == B_OK)
735 		ret = archive->AddData("rgbDisplay", B_RGB_COLOR_TYPE,
736 							   &fExpressionBGColor, sizeof(rgb_color));
737 
738 	// record current options
739 	if (ret == B_OK)
740 		ret = fOptions->SaveSettings(archive);
741 
742 	// record option window frame
743 	if (ret == B_OK)
744 		ret = archive->AddRect("option window frame", fOptionsWindowFrame);
745 
746 	// record display text
747 	if (ret == B_OK)
748 		ret = archive->AddString("displayText", fExpressionTextView->Text());
749 
750 	// record expression history
751 	if (ret == B_OK)
752 		ret = fExpressionTextView->SaveSettings(archive);
753 
754 	// record calculator description
755 	if (ret == B_OK)
756 		ret = archive->AddString("calcDesc", fKeypadDescription);
757 
758 	return ret;
759 }
760 
761 
762 void
763 CalcView::Evaluate()
764 {
765 	const double EXP_SWITCH_HI = 1e12; // # digits to switch from std->exp form
766 	const double EXP_SWITCH_LO = 1e-12;
767 
768 	BString expression = fExpressionTextView->Text();
769 
770 	if (expression.Length() == 0) {
771 		beep();
772 		return;
773 	}
774 
775 	// audio feedback
776 	if (fOptions->audio_feedback) {
777 		BEntry zimp("zimp.AIFF");
778 		entry_ref zimp_ref;
779 		zimp.GetRef(&zimp_ref);
780 		play_sound(&zimp_ref, true, false, false);
781 	}
782 
783 //printf("evaluate: %s\n", expression.String());
784 
785 	// evaluate expression
786 	double value = 0.0;
787 
788 	try {
789 		ExpressionParser parser;
790 		value = parser.Evaluate(expression.String());
791 	} catch (ParseException e) {
792 		BString error(e.message.String());
793 		error << " at " << (e.position + 1);
794 		fExpressionTextView->SetText(error.String());
795 		return;
796 	}
797 
798 //printf("  -> value: %f\n", value);
799 
800 	// beautify the expression
801 	// TODO: see if this is necessary at all
802 	char buf[64];
803 	if (value == 0) {
804 		strcpy(buf, "0");
805 	} else if (((value <  EXP_SWITCH_HI) && (value >  EXP_SWITCH_LO)) ||
806 			   ((value > -EXP_SWITCH_HI) && (value < -EXP_SWITCH_LO))) {
807 		// print in std form
808 		sprintf(buf, "%.13f", value);
809 
810 		// hack to remove surplus zeros!
811 		if (strchr(buf, '.')) {
812 			int32 i = strlen(buf) - 1;
813 			for (; i > 0; i--) {
814 				if (buf[i] == '0')
815 					buf[i] = '\0';
816 				else
817 					break;
818 			}
819 			if (buf[i] == '.')
820 				buf[i] = '\0';
821 		}
822 	} else {
823 		// print in exponential form
824 		sprintf(buf, "%e", value);
825 	}
826 
827 	// render new result to display
828 	fExpressionTextView->SetExpression(buf);
829 }
830 
831 
832 void
833 CalcView::FlashKey(const char* bytes, int32 numBytes)
834 {
835 	BString temp;
836 	temp.Append(bytes, numBytes);
837 	int32 key = _KeyForLabel(temp.String());
838 	if (key >= 0)
839 		_FlashKey(key);
840 }
841 
842 
843 // #pragma mark -
844 
845 
846 void
847 CalcView::_ParseCalcDesc(const char* keypadDescription)
848 {
849 	// TODO: should calculate dimensions from desc here!
850 	fKeypad = new CalcKey[fRows * fColums];
851 
852 	// scan through calculator description and assemble keypad
853 	CalcKey *key = fKeypad;
854 	const char *p = keypadDescription;
855 
856 	while (*p != 0) {
857 		// copy label
858 		char *l = key->label;
859 		while (!isspace(*p))
860 			*l++ = *p++;
861 		*l = '\0';
862 
863 		// set code
864 		if (strcmp(key->label, "=") == 0)
865 			strcpy(key->code, "\n");
866 		else
867 			strcpy(key->code, key->label);
868 
869 		// set keymap
870 		if (strlen(key->label) == 1) {
871 			strcpy(key->keymap, key->label);
872 		} else {
873 			*key->keymap = '\0';
874 		} // end if
875 
876 		// add this to the expression text view, so that it
877 		// will forward the respective KeyDown event to us
878 		fExpressionTextView->AddKeypadLabel(key->label);
879 
880 		// advance
881 		while (isspace(*p))
882 			++p;
883 		key++;
884 	}
885 }
886 
887 
888 void
889 CalcView::_PressKey(int key)
890 {
891 	assert(key < (fRows * fColums));
892 	assert(key >= 0);
893 
894 	// check for backspace
895 	if (strcmp(fKeypad[key].label, "BS") == 0) {
896 		fExpressionTextView->BackSpace();
897 	} else if (strcmp(fKeypad[key].label, "C") == 0) {
898 		// C means clear
899 		fExpressionTextView->Clear();
900 	} else {
901 		// check for evaluation order
902 		if (fKeypad[key].code[0] == '\n') {
903 			Evaluate();
904 		} else {
905 			// insert into expression text
906 			fExpressionTextView->Insert(fKeypad[key].code);
907 
908 			// audio feedback
909 			if (fOptions->audio_feedback) {
910 				BEntry zimp("key.AIFF");
911 				entry_ref zimp_ref;
912 				zimp.GetRef(&zimp_ref);
913 				play_sound(&zimp_ref, true, false, true);
914 			}
915 		}
916 	}
917 
918 	// redraw display
919 //	_InvalidateExpression();
920 }
921 
922 
923 void
924 CalcView::_PressKey(const char *label)
925 {
926 	int32 key = _KeyForLabel(label);
927 	if (key >= 0)
928 		_PressKey(key);
929 }
930 
931 
932 int32
933 CalcView::_KeyForLabel(const char *label) const
934 {
935 	int keys = fRows * fColums;
936 	for (int i = 0; i < keys; i++) {
937 		if (strcmp(fKeypad[i].label, label) == 0) {
938 			return i;
939 		}
940 	}
941 	return -1;
942 }
943 
944 
945 void
946 CalcView::_FlashKey(int32 key)
947 {
948 	// TODO ...
949 }
950 
951 
952 void
953 CalcView::_Colorize()
954 {
955 	// calculate light and dark color from base color
956 	fLightColor.red		= (uint8)(fBaseColor.red * 1.25);
957 	fLightColor.green	= (uint8)(fBaseColor.green * 1.25);
958 	fLightColor.blue	= (uint8)(fBaseColor.blue * 1.25);
959 	fLightColor.alpha	= 255;
960 
961 	fDarkColor.red		= (uint8)(fBaseColor.red * 0.75);
962 	fDarkColor.green	= (uint8)(fBaseColor.green * 0.75);
963 	fDarkColor.blue		= (uint8)(fBaseColor.blue * 0.75);
964 	fDarkColor.alpha	= 255;
965 
966 	// keypad text color
967 	uint8 lightness = (fBaseColor.red + fBaseColor.green + fBaseColor.blue) / 3;
968 	if (lightness > 200)
969 		fButtonTextColor = (rgb_color){ 0, 0, 0, 255 };
970 	else
971 		fButtonTextColor = (rgb_color){ 255, 255, 255, 255 };
972 
973 	// expression text color
974 	lightness = (fExpressionBGColor.red +
975 				 fExpressionBGColor.green + fExpressionBGColor.blue) / 3;
976 	if (lightness > 200)
977 		fExpressionTextColor = (rgb_color){ 0, 0, 0, 255 };
978 	else
979 		fExpressionTextColor = (rgb_color){ 255, 255, 255, 255 };
980 }
981 
982 
983 void
984 CalcView::_CreatePopUpMenu()
985 {
986 	// construct items
987 	fAboutItem = new BMenuItem("About Calculator...",
988 		new BMessage(B_ABOUT_REQUESTED));
989 	fOptionsItem = new BMenuItem("Options...",
990 		new BMessage(K_OPTIONS_REQUESTED));
991 
992 	// construct menu
993 	fPopUpMenu = new BPopUpMenu("pop-up", false, false);
994 	fPopUpMenu->AddItem(fAboutItem);
995 	fPopUpMenu->AddItem(fOptionsItem);
996 }
997 
998 
999 BRect
1000 CalcView::_ExpressionRect() const
1001 {
1002 	BRect r(0.0, 0.0, fWidth, fHeight);
1003 	if (fShowKeypad) {
1004 		r.bottom = floorf(fHeight * K_DISPLAY_YPROP);
1005 	}
1006 	return r;
1007 }
1008 
1009 
1010 BRect
1011 CalcView::_KeypadRect() const
1012 {
1013 	BRect r(0.0, 0.0, -1.0, -1.0);
1014 	if (fShowKeypad) {
1015 		r.right = fWidth;
1016 		r.bottom = fHeight;
1017 		r.top = floorf(fHeight * K_DISPLAY_YPROP) + 1;
1018 	}
1019 	return r;
1020 }
1021 
1022 
1023 void
1024 CalcView::_ShowKeypad(bool show)
1025 {
1026 	if (fShowKeypad == show)
1027 		return;
1028 
1029 	fShowKeypad = show;
1030 
1031 	float height = fShowKeypad ? fHeight / K_DISPLAY_YPROP
1032 							   : fHeight * K_DISPLAY_YPROP;
1033 
1034 	BWindow* window = Window();
1035 	if (window->Bounds() == Frame())
1036 		window->ResizeTo(fWidth, height);
1037 	else
1038 		ResizeTo(fWidth, height);
1039 }
1040 
1041 
1042 
1043