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