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