xref: /haiku/src/apps/deskcalc/CalcView.cpp (revision 90ca02568835b140b0e59de496a7f1f1d3513f67)
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.2f;
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 	if (!IsFocus())
408 		MakeFocus();
409 
410 	// read mouse buttons state
411 	int32 buttons = 0;
412 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
413 
414 	// display popup menu if not primary mouse button
415 	if ((B_PRIMARY_MOUSE_BUTTON & buttons) == 0) {
416 		BMenuItem* selected;
417 		if ((selected = fPopUpMenu->Go(ConvertToScreen(point))) != NULL)
418 			MessageReceived(selected->Message());
419 		return;
420 	}
421 
422 	if (!fShowKeypad)
423 		return;
424 
425 	// calculate grid sizes
426 	float sizeDisp = fHeight * K_DISPLAY_YPROP;
427 	float sizeCol = fWidth / (float)fColums;
428 	float sizeRow = (fHeight - sizeDisp) / (float)fRows;
429 
430 	// calculate location within grid
431 	int gridCol = (int)floorf(point.x / sizeCol);
432 	int gridRow = (int)floorf((point.y - sizeDisp) / sizeRow);
433 
434 	// check limits
435 	if ((gridCol >= 0) && (gridCol < fColums) &&
436 		(gridRow >= 0) && (gridRow < fRows)) {
437 
438 		// process key press
439 		_PressKey(gridRow * fColums + gridCol);
440 	}
441 }
442 
443 
444 void
445 CalcView::KeyDown(const char *bytes, int32 numBytes)
446 {
447  	// if single byte character...
448 	if (numBytes == 1) {
449 
450 		//printf("Key pressed: %c\n", bytes[0]);
451 
452 		switch (bytes[0]) {
453 
454 			case B_ENTER:
455 				// translate to evaluate key
456 				_PressKey("=");
457 				break;
458 
459 			case B_LEFT_ARROW:
460 			case B_BACKSPACE:
461 				// translate to backspace key
462 				_PressKey("BS");
463 				break;
464 
465 			case B_SPACE:
466 			case B_ESCAPE:
467 			case 'c':
468 				// translate to clear key
469 				_PressKey("C");
470 				break;
471 
472 			// bracket translation
473 			case '[':
474 			case '{':
475 				_PressKey("(");
476 				break;
477 
478 			case ']':
479 			case '}':
480 				_PressKey(")");
481 				break;
482 
483 			default: {
484 				// scan the keymap array for match
485 				int keys = fRows * fColums;
486 				for (int i = 0; i < keys; i++) {
487 					if (fKeypad[i].keymap[0] == bytes[0]) {
488 						_PressKey(i);
489 						return;
490 					}
491 				}
492 				break;
493 			}
494 		}
495 	}
496 }
497 
498 
499 void
500 CalcView::MakeFocus(bool focused)
501 {
502 	if (focused) {
503 		// set num lock
504 		if (fOptions->auto_num_lock) {
505 			set_keyboard_locks(B_NUM_LOCK |
506 							   (modifiers() & (B_CAPS_LOCK | B_SCROLL_LOCK)));
507 		}
508 	}
509 
510 	// pass on request to text view
511 	fExpressionTextView->MakeFocus(focused);
512 }
513 
514 
515 void
516 CalcView::FrameResized(float width, float height)
517 {
518 	fWidth = width;
519 	fHeight = height;
520 
521 	// layout expression text view
522 	BRect frame = _ExpressionRect();
523 	frame.InsetBy(2, 2);
524 
525 	if (!fShowKeypad)
526 		frame.right -= ceilf(fCalcIcon->Bounds().Width() * 1.5);
527 
528 	fExpressionTextView->MoveTo(frame.LeftTop());
529 	fExpressionTextView->ResizeTo(frame.Width(), frame.Height());
530 
531 	frame.OffsetTo(B_ORIGIN);
532 	frame.InsetBy(2, 2);
533 	fExpressionTextView->SetTextRect(frame);
534 
535 	// configure expression text view font size and color
536 	float sizeDisp = fShowKeypad ? fHeight * K_DISPLAY_YPROP : fHeight;
537 	BFont font(be_bold_font);
538 	font.SetSize(sizeDisp * K_FONT_YPROP);
539 //	fExpressionTextView->SetViewColor(fExpressionBGColor);
540 //	fExpressionTextView->SetLowColor(fExpressionBGColor);
541 //	fExpressionTextView->SetFontAndColor(&font, B_FONT_ALL, &fExpressionTextColor);
542 fExpressionTextView->SetFontAndColor(&font, B_FONT_ALL);
543 }
544 
545 
546 void
547 CalcView::AboutRequested()
548 {
549 	BAlert* alert = new BAlert("about",
550 		"DeskCalc v2.1.0\n\n"
551 		"written by Timothy Wayper,\nStephan Aßmus and Ingo Weinhold\n\n"
552 		B_UTF8_COPYRIGHT"1997, 1998 R3 Software Ltd.\n"
553 		B_UTF8_COPYRIGHT"2006 Haiku, Inc.\n\n"
554 		"All Rights Reserved.", "Cool");
555 	alert->Go(NULL);
556 }
557 
558 
559 status_t
560 CalcView::Archive(BMessage* archive, bool deep) const
561 {
562 	fExpressionTextView->RemoveSelf();
563 
564 	// passed on request to parent
565 	status_t ret = BView::Archive(archive, deep);
566 
567 	const_cast<CalcView*>(this)->AddChild(fExpressionTextView);
568 
569 	// save app signature for replicant add-on loading
570     if (ret == B_OK)
571     	ret = archive->AddString("add_on", kAppSig);
572 
573 	// save all the options
574 	if (ret == B_OK)
575 		ret = SaveSettings(archive);
576 
577 	// add class info last
578 	if (ret == B_OK)
579 		ret = archive->AddString("class", "CalcView");
580 
581 	return ret;
582 }
583 
584 
585 void
586 CalcView::Cut()
587 {
588 	Copy();	// copy data to clipboard
589 	fExpressionTextView->Clear(); // remove data
590 }
591 
592 
593 void
594 CalcView::Copy()
595 {
596 	// access system clipboard
597 	if (be_clipboard->Lock()) {
598 		be_clipboard->Clear();
599 		BMessage *clipper = be_clipboard->Data();
600 		clipper->what = B_MIME_DATA;
601 		// TODO: should check return for errors!
602 		BString expression = fExpressionTextView->Text();
603 		clipper->AddData("text/plain",
604 						 B_MIME_TYPE,
605 						 expression.String(),
606 						 expression.Length());
607 		//clipper->PrintToStream();
608 		be_clipboard->Commit();
609 		be_clipboard->Unlock();
610 	}
611 }
612 
613 
614 void
615 CalcView::Paste(BMessage *message)
616 {
617 	// handle color drops first
618 	// read incoming color
619 	const rgb_color* dropColor = NULL;
620 	ssize_t dataSize;
621 	if (message->FindData("RGBColor",
622 						  B_RGB_COLOR_TYPE,
623 						  (const void**)&dropColor,
624 						  &dataSize) == B_OK
625 		&& dataSize == sizeof(rgb_color)) {
626 
627 		// calculate view relative drop point
628 		BPoint dropPoint = ConvertFromScreen(message->DropPoint());
629 
630 		// calculate current keypad area
631 		float sizeDisp = fHeight * K_DISPLAY_YPROP;
632 		BRect keypadRect(0.0, sizeDisp, fWidth, fHeight);
633 
634 		// check location of color drop
635 		if (keypadRect.Contains(dropPoint) && dropColor) {
636 			fBaseColor = *dropColor;
637 			_Colorize();
638 			// redraw keypad
639 			Invalidate(keypadRect);
640 		}
641 
642 	} else {
643 		// look for text/plain MIME data
644 		const char* text;
645 		ssize_t numBytes;
646 		if (message->FindData("text/plain",
647 							  B_MIME_TYPE,
648 							  (const void**)&text,
649 							  &numBytes) == B_OK) {
650 			BString temp;
651 			temp.Append(text, numBytes);
652 			fExpressionTextView->Insert(temp.String());
653 		}
654 	}
655 }
656 
657 
658 status_t
659 CalcView::LoadSettings(BMessage* archive)
660 {
661 	if (!archive)
662 		return B_BAD_VALUE;
663 
664 	// record calculator description
665 	const char* calcDesc;
666 	if (archive->FindString("calcDesc", &calcDesc) < B_OK)
667 		calcDesc = kDefaultKeypadDescription;
668 
669 	// save calculator description for reference
670 	free(fKeypadDescription);
671 	fKeypadDescription = strdup(calcDesc);
672 
673 	// read grid dimensions
674 	if (archive->FindInt16("cols", &fColums) < B_OK)
675 		fColums = 5;
676 	if (archive->FindInt16("rows", &fRows) < B_OK)
677 		fRows = 4;
678 
679 	// read color scheme
680 	const rgb_color* color;
681 	ssize_t size;
682 	if (archive->FindData("rgbBaseColor", B_RGB_COLOR_TYPE,
683 						  (const void**)&color, &size) < B_OK
684 						  || size != sizeof(rgb_color)) {
685 		fBaseColor = (rgb_color){ 128, 128, 128, 255 };
686 		puts("Missing rgbBaseColor from CalcView archive!\n");
687 	} else {
688 		fBaseColor = *color;
689 	}
690 
691 	if (archive->FindData("rgbDisplay", B_RGB_COLOR_TYPE,
692 						  (const void**)&color, &size) < B_OK
693 						  || size != sizeof(rgb_color)) {
694 		fExpressionBGColor = (rgb_color){ 0, 0, 0, 255 };
695 		puts("Missing rgbBaseColor from CalcView archive!\n");
696 	} else {
697 		fExpressionBGColor = *color;
698 	}
699 
700 	// load options
701 	fOptions->LoadSettings(archive);
702 	fShowKeypad = fOptions->show_keypad;
703 
704 	// load option window frame
705 	BRect frame;
706 	if (archive->FindRect("option window frame", &frame) == B_OK)
707 		fOptionsWindowFrame = frame;
708 
709 	// load display text
710 	const char* display;
711 	if (archive->FindString("displayText", &display) < B_OK) {
712 		puts("Missing expression text from CalcView archive.\n");
713 	} else {
714 		// init expression text
715 		fExpressionTextView->SetText(display);
716 	}
717 
718 	// load expression history
719 	fExpressionTextView->LoadSettings(archive);
720 
721 	// parse calculator description
722 	_ParseCalcDesc(fKeypadDescription);
723 
724 	// colorize based on base color.
725 	_Colorize();
726 
727 	return B_OK;
728 }
729 
730 
731 status_t
732 CalcView::SaveSettings(BMessage* archive) const
733 {
734 	status_t ret = archive ? B_OK : B_BAD_VALUE;
735 
736 	// record grid dimensions
737     if (ret == B_OK)
738 		ret = archive->AddInt16("cols", fColums);
739     if (ret == B_OK)
740 		ret = archive->AddInt16("rows", fRows);
741 
742 	// record color scheme
743     if (ret == B_OK)
744 		ret = archive->AddData("rgbBaseColor", B_RGB_COLOR_TYPE,
745 							   &fBaseColor, sizeof(rgb_color));
746 	if (ret == B_OK)
747 		ret = archive->AddData("rgbDisplay", B_RGB_COLOR_TYPE,
748 							   &fExpressionBGColor, sizeof(rgb_color));
749 
750 	// record current options
751 	if (ret == B_OK)
752 		ret = fOptions->SaveSettings(archive);
753 
754 	// record option window frame
755 	if (ret == B_OK)
756 		ret = archive->AddRect("option window frame", fOptionsWindowFrame);
757 
758 	// record display text
759 	if (ret == B_OK)
760 		ret = archive->AddString("displayText", fExpressionTextView->Text());
761 
762 	// record expression history
763 	if (ret == B_OK)
764 		ret = fExpressionTextView->SaveSettings(archive);
765 
766 	// record calculator description
767 	if (ret == B_OK)
768 		ret = archive->AddString("calcDesc", fKeypadDescription);
769 
770 	return ret;
771 }
772 
773 
774 void
775 CalcView::Evaluate()
776 {
777 	const double EXP_SWITCH_HI = 1e12; // # digits to switch from std->exp form
778 	const double EXP_SWITCH_LO = 1e-12;
779 
780 	BString expression = fExpressionTextView->Text();
781 
782 	if (expression.Length() == 0) {
783 		beep();
784 		return;
785 	}
786 
787 	// audio feedback
788 	if (fOptions->audio_feedback) {
789 		BEntry zimp("zimp.AIFF");
790 		entry_ref zimp_ref;
791 		zimp.GetRef(&zimp_ref);
792 		play_sound(&zimp_ref, true, false, false);
793 	}
794 
795 //printf("evaluate: %s\n", expression.String());
796 
797 	// evaluate expression
798 	double value = 0.0;
799 
800 	try {
801 		ExpressionParser parser;
802 		value = parser.Evaluate(expression.String());
803 	} catch (ParseException e) {
804 		BString error(e.message.String());
805 		error << " at " << (e.position + 1);
806 		fExpressionTextView->SetText(error.String());
807 		return;
808 	}
809 
810 //printf("  -> value: %f\n", value);
811 
812 	// beautify the expression
813 	// TODO: see if this is necessary at all
814 	char buf[64];
815 	if (value == 0) {
816 		strcpy(buf, "0");
817 	} else if (((value <  EXP_SWITCH_HI) && (value >  EXP_SWITCH_LO)) ||
818 			   ((value > -EXP_SWITCH_HI) && (value < -EXP_SWITCH_LO))) {
819 		// print in std form
820 		sprintf(buf, "%.13f", value);
821 
822 		// hack to remove surplus zeros!
823 		if (strchr(buf, '.')) {
824 			int32 i = strlen(buf) - 1;
825 			for (; i > 0; i--) {
826 				if (buf[i] == '0')
827 					buf[i] = '\0';
828 				else
829 					break;
830 			}
831 			if (buf[i] == '.')
832 				buf[i] = '\0';
833 		}
834 	} else {
835 		// print in exponential form
836 		sprintf(buf, "%e", value);
837 	}
838 
839 	// render new result to display
840 	fExpressionTextView->SetExpression(buf);
841 }
842 
843 
844 void
845 CalcView::FlashKey(const char* bytes, int32 numBytes)
846 {
847 	BString temp;
848 	temp.Append(bytes, numBytes);
849 	int32 key = _KeyForLabel(temp.String());
850 	if (key >= 0)
851 		_FlashKey(key);
852 }
853 
854 
855 // #pragma mark -
856 
857 
858 void
859 CalcView::_ParseCalcDesc(const char* keypadDescription)
860 {
861 	// TODO: should calculate dimensions from desc here!
862 	fKeypad = new CalcKey[fRows * fColums];
863 
864 	// scan through calculator description and assemble keypad
865 	CalcKey *key = fKeypad;
866 	const char *p = keypadDescription;
867 
868 	while (*p != 0) {
869 		// copy label
870 		char *l = key->label;
871 		while (!isspace(*p))
872 			*l++ = *p++;
873 		*l = '\0';
874 
875 		// set code
876 		if (strcmp(key->label, "=") == 0)
877 			strcpy(key->code, "\n");
878 		else
879 			strcpy(key->code, key->label);
880 
881 		// set keymap
882 		if (strlen(key->label) == 1) {
883 			strcpy(key->keymap, key->label);
884 		} else {
885 			*key->keymap = '\0';
886 		} // end if
887 
888 		// add this to the expression text view, so that it
889 		// will forward the respective KeyDown event to us
890 		fExpressionTextView->AddKeypadLabel(key->label);
891 
892 		// advance
893 		while (isspace(*p))
894 			++p;
895 		key++;
896 	}
897 }
898 
899 
900 void
901 CalcView::_PressKey(int key)
902 {
903 	assert(key < (fRows * fColums));
904 	assert(key >= 0);
905 
906 	// check for backspace
907 	if (strcmp(fKeypad[key].label, "BS") == 0) {
908 		fExpressionTextView->BackSpace();
909 	} else if (strcmp(fKeypad[key].label, "C") == 0) {
910 		// C means clear
911 		fExpressionTextView->Clear();
912 	} else {
913 		// check for evaluation order
914 		if (fKeypad[key].code[0] == '\n') {
915 			Evaluate();
916 		} else {
917 			// insert into expression text
918 			fExpressionTextView->Insert(fKeypad[key].code);
919 
920 			// audio feedback
921 			if (fOptions->audio_feedback) {
922 				BEntry zimp("key.AIFF");
923 				entry_ref zimp_ref;
924 				zimp.GetRef(&zimp_ref);
925 				play_sound(&zimp_ref, true, false, true);
926 			}
927 		}
928 	}
929 
930 	// redraw display
931 //	_InvalidateExpression();
932 }
933 
934 
935 void
936 CalcView::_PressKey(const char *label)
937 {
938 	int32 key = _KeyForLabel(label);
939 	if (key >= 0)
940 		_PressKey(key);
941 }
942 
943 
944 int32
945 CalcView::_KeyForLabel(const char *label) const
946 {
947 	int keys = fRows * fColums;
948 	for (int i = 0; i < keys; i++) {
949 		if (strcmp(fKeypad[i].label, label) == 0) {
950 			return i;
951 		}
952 	}
953 	return -1;
954 }
955 
956 
957 void
958 CalcView::_FlashKey(int32 key)
959 {
960 	// TODO ...
961 }
962 
963 
964 void
965 CalcView::_Colorize()
966 {
967 	// calculate light and dark color from base color
968 	fLightColor.red		= (uint8)(fBaseColor.red * 1.25);
969 	fLightColor.green	= (uint8)(fBaseColor.green * 1.25);
970 	fLightColor.blue	= (uint8)(fBaseColor.blue * 1.25);
971 	fLightColor.alpha	= 255;
972 
973 	fDarkColor.red		= (uint8)(fBaseColor.red * 0.75);
974 	fDarkColor.green	= (uint8)(fBaseColor.green * 0.75);
975 	fDarkColor.blue		= (uint8)(fBaseColor.blue * 0.75);
976 	fDarkColor.alpha	= 255;
977 
978 	// keypad text color
979 	uint8 lightness = (fBaseColor.red + fBaseColor.green + fBaseColor.blue) / 3;
980 	if (lightness > 200)
981 		fButtonTextColor = (rgb_color){ 0, 0, 0, 255 };
982 	else
983 		fButtonTextColor = (rgb_color){ 255, 255, 255, 255 };
984 
985 	// expression text color
986 	lightness = (fExpressionBGColor.red +
987 				 fExpressionBGColor.green + fExpressionBGColor.blue) / 3;
988 	if (lightness > 200)
989 		fExpressionTextColor = (rgb_color){ 0, 0, 0, 255 };
990 	else
991 		fExpressionTextColor = (rgb_color){ 255, 255, 255, 255 };
992 }
993 
994 
995 void
996 CalcView::_CreatePopUpMenu()
997 {
998 	// construct items
999 	fAboutItem = new BMenuItem("About Calculator" B_UTF8_ELLIPSIS,
1000 		new BMessage(B_ABOUT_REQUESTED));
1001 	fOptionsItem = new BMenuItem("Options" B_UTF8_ELLIPSIS,
1002 		new BMessage(K_OPTIONS_REQUESTED));
1003 
1004 	// construct menu
1005 	fPopUpMenu = new BPopUpMenu("pop-up", false, false);
1006 	fPopUpMenu->AddItem(fAboutItem);
1007 	fPopUpMenu->AddItem(fOptionsItem);
1008 }
1009 
1010 
1011 BRect
1012 CalcView::_ExpressionRect() const
1013 {
1014 	BRect r(0.0, 0.0, fWidth, fHeight);
1015 	if (fShowKeypad) {
1016 		r.bottom = floorf(fHeight * K_DISPLAY_YPROP);
1017 	}
1018 	return r;
1019 }
1020 
1021 
1022 BRect
1023 CalcView::_KeypadRect() const
1024 {
1025 	BRect r(0.0, 0.0, -1.0, -1.0);
1026 	if (fShowKeypad) {
1027 		r.right = fWidth;
1028 		r.bottom = fHeight;
1029 		r.top = floorf(fHeight * K_DISPLAY_YPROP) + 1;
1030 	}
1031 	return r;
1032 }
1033 
1034 
1035 void
1036 CalcView::_ShowKeypad(bool show)
1037 {
1038 	if (fShowKeypad == show)
1039 		return;
1040 
1041 	fShowKeypad = show;
1042 
1043 	float height = fShowKeypad ? fHeight / K_DISPLAY_YPROP
1044 							   : fHeight * K_DISPLAY_YPROP;
1045 
1046 	BWindow* window = Window();
1047 	if (window->Bounds() == Frame())
1048 		window->ResizeTo(fWidth, height);
1049 	else
1050 		ResizeTo(fWidth, height);
1051 }
1052 
1053 
1054 void
1055 CalcView::_FetchAppIcon(BBitmap* into)
1056 {
1057 	entry_ref appRef;
1058 	be_roster->FindApp(kAppSig, &appRef);
1059 	BFile file(&appRef, B_READ_ONLY);
1060 	BAppFileInfo appInfo(&file);
1061 	if (appInfo.GetIcon(into, B_MINI_ICON) != B_OK)
1062 		memset(into->Bits(), 0, into->BitsLength());
1063 }
1064