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