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