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