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