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