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