xref: /haiku/src/apps/magnify/Magnify.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
1 /*
2  * Copyright 2002-2009, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Updated by Sikosis (beos@gravity24hr.com)
6  *
7  * Copyright 1999, Be Incorporated.   All Rights Reserved.
8  * This file may be used under the terms of the Be Sample Code License.
9  */
10 
11 #include "Magnify.h"
12 
13 #include <Alert.h>
14 #include <Bitmap.h>
15 #include <BitmapStream.h>
16 #include <Catalog.h>
17 #include <Clipboard.h>
18 #include <Debug.h>
19 #include <Directory.h>
20 #include <File.h>
21 #include <FindDirectory.h>
22 #include <Locale.h>
23 #include <MenuItem.h>
24 #include <MenuField.h>
25 #include <NodeInfo.h>
26 #include <Path.h>
27 #include <PopUpMenu.h>
28 #include <PropertyInfo.h>
29 #include <Screen.h>
30 #include <ScrollView.h>
31 #include <StringFormat.h>
32 #include <TextView.h>
33 #include <TranslationUtils.h>
34 #include <TranslatorRoster.h>
35 #include <WindowScreen.h>
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <fcntl.h>
41 #include <unistd.h>
42 #include <sys/stat.h>
43 
44 
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "Magnify-Main"
47 
48 
49 const int32 msg_update_info = 'info';
50 const int32 msg_show_info = 'show';
51 const int32 msg_toggle_grid = 'grid';
52 const int32 msg_shrink = 'shnk';
53 const int32 msg_grow = 'grow';
54 const int32 msg_make_square = 'sqar';
55 const int32 msg_shrink_pixel = 'pshk';
56 const int32 msg_grow_pixel = 'pgrw';
57 
58 const int32 msg_mouse_left = 'mslf';
59 const int32 msg_mouse_right = 'msrt';
60 const int32 msg_mouse_up = 'msup';
61 const int32 msg_mouse_down = 'msdn';
62 
63 const int32 msg_new_color = 'colr';
64 const int32 msg_toggle_ruler = 'rulr';
65 const int32 msg_copy_image = 'copy';
66 const int32 msg_track_color = 'trak';
67 const int32 msg_freeze = 'frez';
68 const int32 msg_stick = 'stic';
69 const int32 msg_dump = 'dump';
70 const int32 msg_add_cross_hair = 'acrs';
71 const int32 msg_remove_cross_hair = 'rcrs';
72 const int32 msg_save = 'save';
73 
74 const rgb_color kViewGray = { 216, 216, 216, 255};
75 const rgb_color kGridGray = {130, 130, 130, 255 };
76 const rgb_color kWhite = { 255, 255, 255, 255};
77 const rgb_color kBlack = { 0, 0, 0, 255};
78 const rgb_color kDarkGray = { 96, 96, 96, 255};
79 const rgb_color kRedColor = { 255, 10, 50, 255 };
80 const rgb_color kGreenColor = { 10, 255, 50, 255 };
81 const rgb_color kBlueColor = { 10, 50, 255, 255 };
82 
83 const char* const kBitmapMimeType = "image/x-vnd.Be-bitmap";
84 
85 const float kCurrentVersion = 1.2;
86 const char *kPrefsFileName = "Magnify_prefs";
87 
88 // prefs are:
89 //		name = Magnify
90 //		version
91 //		show grid
92 //		show info	(rgb, location)
93 //		pixel count
94 //		pixel size
95 const char* const kAppName = "Magnify";
96 const bool kDefaultShowGrid = true;
97 const bool kDefaultShowInfo = true;
98 const int32 kDefaultPixelCount = 32;
99 const int32 kDefaultPixelSize = 8;
100 
101 // each info region will be:
102 // top-bottom: 5 fontheight 5 fontheight 5
103 // left-right: 10 minwindowwidth 10
104 const int32 kBorderSize = 10;
105 
106 
107 static property_info sProperties[] = {
108 	{ "Info", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
109 		{ B_DIRECT_SPECIFIER, 0 },
110 		"Show/hide info.", 0,
111 		{ B_BOOL_TYPE }
112 	},
113 	{ "Grid", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
114 		{ B_DIRECT_SPECIFIER, 0 },
115 		"Show/hide grid.", 0,
116 		{ B_BOOL_TYPE }
117 	},
118 	{ "MakeSquare", { B_EXECUTE_PROPERTY, 0 },
119 		{ B_DIRECT_SPECIFIER, 0 },
120 		"Make the view square.", 0,
121 	},
122 	{ "Zoom", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
123 		{ B_DIRECT_SPECIFIER, 0 },
124 		"Gets/sets the zoom factor (1-16).", 0,
125 		{ B_INT32_TYPE }
126 	},
127 	{ "Stick", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
128 		{ B_DIRECT_SPECIFIER, 0 },
129 		"Stick/unstick coordinates.", 0,
130 		{ B_BOOL_TYPE }
131 	},
132 	{ "CopyImage", { B_EXECUTE_PROPERTY, 0 },
133 		{ B_DIRECT_SPECIFIER, 0 },
134 		"Copy image to clipboard.", 0,
135 	},
136 
137 	{ 0 }
138 };
139 
140 
141 static float
142 FontHeight(BView* target, bool full)
143 {
144 	font_height finfo;
145 	target->GetFontHeight(&finfo);
146 	float h = ceil(finfo.ascent) + ceil(finfo.descent);
147 
148 	if (full)
149 		h += ceil(finfo.leading);
150 
151 	return h;
152 }
153 
154 
155 static void
156 BoundsSelection(int32 incX, int32 incY, float* x, float* y,
157 	int32 xCount, int32 yCount)
158 {
159 	*x += incX;
160 	*y += incY;
161 
162 	if (*x < 0)
163 		*x = xCount-1;
164 	if (*x >= xCount)
165 		*x = 0;
166 
167 	if (*y < 0)
168 		*y = yCount-1;
169 	if (*y >= yCount)
170 		*y = 0;
171 }
172 
173 
174 static void
175 BuildInfoMenu(BMenu *menu)
176 {
177 	BMenuItem* menuItem;
178 	menuItem = new BMenuItem(B_TRANSLATE("Save image"),
179 		new BMessage(msg_save), 'S');
180 	menu->AddItem(menuItem);
181 //	menuItem = new BMenuItem(B_TRANSLATE("Save selection"),
182 //		new BMessage(msg_save), 'S');
183 //	menu->AddItem(menuItem);
184 	menuItem = new BMenuItem(B_TRANSLATE("Copy image"),
185 		new BMessage(msg_copy_image), 'C');
186 	menu->AddItem(menuItem);
187 	menu->AddSeparatorItem();
188 
189 	menuItem = new BMenuItem(B_TRANSLATE("Show info"),
190 		new BMessage(msg_show_info), 'T');
191 	menu->AddItem(menuItem);
192 	menuItem = new BMenuItem(B_TRANSLATE("Add a crosshair"),
193 		new BMessage(msg_add_cross_hair), 'H');
194 	menu->AddItem(menuItem);
195 	menuItem = new BMenuItem(B_TRANSLATE("Remove a crosshair"),
196 		new BMessage(msg_remove_cross_hair), 'H', B_SHIFT_KEY);
197 	menu->AddItem(menuItem);
198 	menuItem = new BMenuItem(B_TRANSLATE("Show grid"),
199 		new BMessage(msg_toggle_grid), 'G');
200 	menu->AddItem(menuItem);
201 	menu->AddSeparatorItem();
202 
203 	menuItem = new BMenuItem(B_TRANSLATE("Freeze image"),
204 		new BMessage(msg_freeze), 'F');
205 	menu->AddItem(menuItem);
206 	menuItem = new BMenuItem(B_TRANSLATE("Stick coordinates"),
207 		new BMessage(msg_stick), 'I');
208 	menu->AddItem(menuItem);
209 	menu->AddSeparatorItem();
210 
211 	menuItem = new BMenuItem(B_TRANSLATE("Make square"),
212 		new BMessage(msg_make_square), '/');
213 	menu->AddItem(menuItem);
214 	menuItem = new BMenuItem(B_TRANSLATE("Decrease window size"),
215 		new BMessage(msg_shrink), '-');
216 	menu->AddItem(menuItem);
217 	menuItem = new BMenuItem(B_TRANSLATE("Increase window size"),
218 		new BMessage(msg_grow), '+');
219 	menu->AddItem(menuItem);
220 	menuItem = new BMenuItem(B_TRANSLATE("Decrease pixel size"),
221 		new BMessage(msg_shrink_pixel), ',');
222 	menu->AddItem(menuItem);
223 	menuItem = new BMenuItem(B_TRANSLATE("Increase pixel size"),
224 		new BMessage(msg_grow_pixel), '.');
225 	menu->AddItem(menuItem);
226 }
227 
228 static void
229 UpdateInfoMenu(BMenu *menu, TWindow *window)
230 {
231 	bool state = true;
232 	bool showGrid = true;
233 	bool infoBarIsVisible = true;
234 	bool stickCordinates = true;
235 	if (window) {
236 		state = window->IsActive();
237 		showGrid = window->ShowGrid();
238 		infoBarIsVisible = window->InfoBarIsVisible();
239 		stickCordinates = window->IsSticked();
240 	}
241 	BMenuItem* menuItem = menu->FindItem(B_TRANSLATE("Show info"));
242 	if (menuItem) {
243 		menuItem->SetEnabled(state);
244 		menuItem->SetMarked(infoBarIsVisible);
245 	}
246 	menuItem = menu->FindItem(B_TRANSLATE("Add a crosshair"));
247 	if (menuItem)
248 		menuItem->SetEnabled(state);
249 	menuItem = menu->FindItem(B_TRANSLATE("Remove a crosshair"));
250 	if (menuItem)
251 		menuItem->SetEnabled(state);
252 	menuItem = menu->FindItem(B_TRANSLATE("Show grid"));
253 	if (menuItem) {
254 		menuItem->SetEnabled(state);
255 		menuItem->SetMarked(showGrid);
256 	}
257 	menuItem = menu->FindItem(B_TRANSLATE("Freeze image"));
258 	if (menuItem) {
259 		menuItem->SetMarked(!state);
260 	}
261 	menuItem = menu->FindItem(B_TRANSLATE("Stick coordinates"));
262 	if (menuItem) {
263 		menuItem->SetMarked(stickCordinates);
264 	}
265 	menuItem = menu->FindItem(B_TRANSLATE("Make square"));
266 	if (menuItem)
267 		menuItem->SetEnabled(state);
268 	menuItem = menu->FindItem(B_TRANSLATE("Decrease window size"));
269 	if (menuItem)
270 		menuItem->SetEnabled(state);
271 	menuItem = menu->FindItem(B_TRANSLATE("Increase window size"));
272 	if (menuItem)
273 		menuItem->SetEnabled(state);
274 	menuItem = menu->FindItem(B_TRANSLATE("Decrease pixel size"));
275 	if (menuItem)
276 		menuItem->SetEnabled(state);
277 	menuItem = menu->FindItem(B_TRANSLATE("Increase pixel size"));
278 	if (menuItem)
279 		menuItem->SetEnabled(state);
280 }
281 
282 //	#pragma mark -
283 
284 
285 // pass in pixelCount to maintain backward compatibility of setting
286 // the pixelcount from the command line
287 TApp::TApp(int32 pixelCount)
288 	: BApplication("application/x-vnd.Haiku-Magnify")
289 {
290 	TWindow* magWindow = new TWindow(pixelCount);
291 	magWindow->Show();
292 }
293 
294 
295 //	#pragma mark -
296 
297 
298 TWindow::TWindow(int32 pixelCount)
299 	:
300 	BWindow(BRect(0, 0, 0, 0), B_TRANSLATE_SYSTEM_NAME("Magnify"),
301 		B_TITLED_WINDOW, B_OUTLINE_RESIZE)
302 {
303 	GetPrefs(pixelCount);
304 
305 	// add info view
306 	BRect infoRect(Bounds());
307 	infoRect.InsetBy(-1, -1);
308 	fInfo = new TInfoView(infoRect);
309 	AddChild(fInfo);
310 
311 	fFontHeight = FontHeight(fInfo, true);
312 	fInfoHeight = (fFontHeight * 2) + (3 * 5);
313 
314 	BRect fbRect(0, 0, (fHPixelCount*fPixelSize), (fHPixelCount*fPixelSize));
315 	if (InfoIsShowing())
316 		fbRect.OffsetBy(10, fInfoHeight);
317 	fFatBits = new TMagnify(fbRect, this);
318 	fInfo->AddChild(fFatBits);
319 
320 	fFatBits->SetSelection(fShowInfo);
321 	fInfo->SetMagView(fFatBits);
322 
323 	ResizeWindow(fHPixelCount, fVPixelCount);
324 	UpdateInfoBarOnResize();
325 
326 	AddShortcut('S', B_COMMAND_KEY, new BMessage(msg_save));
327 	AddShortcut('C', B_COMMAND_KEY, new BMessage(msg_copy_image));
328 	AddShortcut('T', B_COMMAND_KEY, new BMessage(msg_show_info));
329 	AddShortcut('H', B_COMMAND_KEY, new BMessage(msg_add_cross_hair));
330 	AddShortcut('H', B_SHIFT_KEY, 	new BMessage(msg_remove_cross_hair));
331 	AddShortcut('G', B_COMMAND_KEY, new BMessage(msg_toggle_grid));
332 	AddShortcut('F', B_COMMAND_KEY, new BMessage(msg_freeze));
333 	AddShortcut('I', B_COMMAND_KEY, new BMessage(msg_stick));
334 	AddShortcut('-', B_COMMAND_KEY, new BMessage(msg_shrink));
335 	AddShortcut('=', B_COMMAND_KEY, new BMessage(msg_grow));
336 	AddShortcut('/', B_COMMAND_KEY, new BMessage(msg_make_square));
337 	AddShortcut(',', B_COMMAND_KEY, new BMessage(msg_shrink_pixel));
338 	AddShortcut('.', B_COMMAND_KEY, new BMessage(msg_grow_pixel));
339 	AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, new BMessage(msg_mouse_left));
340 	AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, new BMessage(msg_mouse_right));
341 	AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(msg_mouse_up));
342 	AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(msg_mouse_down));
343 }
344 
345 
346 TWindow::~TWindow()
347 {
348 }
349 
350 
351 status_t
352 TWindow::GetSupportedSuites(BMessage* msg)
353 {
354 	msg->AddString("suites", "suite/x-vnd.Haiku-Magnify");
355 
356 	BPropertyInfo propertyInfo(sProperties);
357 	msg->AddFlat("messages", &propertyInfo);
358 
359 	return BWindow::GetSupportedSuites(msg);
360 }
361 
362 
363 BHandler*
364 TWindow::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
365 	int32 what, const char* property)
366 {
367 	BPropertyInfo propertyInfo(sProperties);
368 	if (propertyInfo.FindMatch(msg, index, specifier, what, property) >= 0)
369 		return this;
370 
371 	return BWindow::ResolveSpecifier(msg, index, specifier, what, property);
372 }
373 
374 
375 void
376 TWindow::MessageReceived(BMessage* m)
377 {
378 	bool active = fFatBits->Active();
379 
380 	switch (m->what) {
381 		case B_EXECUTE_PROPERTY:
382 		case B_GET_PROPERTY:
383 		case B_SET_PROPERTY:
384 		{
385 			int32 index;
386 			BMessage specifier;
387 			int32 what;
388 			const char* property;
389 			if (m->GetCurrentSpecifier(&index, &specifier, &what, &property)
390 				!= B_OK)
391 				return BWindow::MessageReceived(m);
392 
393 			status_t result = B_OK;
394 			BMessage reply(B_REPLY);
395 
396 			BPropertyInfo propertyInfo(sProperties);
397 			switch (propertyInfo.FindMatch(m, index, &specifier, what,
398 						property)) {
399 				case 0:
400 					if (m->what == B_GET_PROPERTY)
401 						result = reply.AddBool("result", fInfoBarState);
402 					else if (m->what == B_SET_PROPERTY) {
403 						bool showInfo;
404 						result = m->FindBool("data", &showInfo);
405 						if (result == B_OK) {
406 							fInfoBarState = showInfo;
407 							ShowInfo(fInfoBarState);
408 						}
409 					}
410 					break;
411 
412 				case 1:
413 					if (m->what == B_GET_PROPERTY)
414 						result = reply.AddBool("result", fShowGrid);
415 					else if (m->what == B_SET_PROPERTY) {
416 						bool showGrid;
417 						result = m->FindBool("data", &showGrid);
418 						if (result == B_OK)
419 							SetGrid(showGrid);
420 					}
421 					break;
422 
423 				case 2:
424 					if (fHPixelCount != fVPixelCount) {
425 						int32 big = fHPixelCount > fVPixelCount ? fHPixelCount
426 										: fVPixelCount;
427 						ResizeWindow(big, big);
428 					}
429 					break;
430 
431 				case 3:
432 					if (m->what == B_GET_PROPERTY)
433 						result = reply.AddInt32("result", fPixelSize);
434 					else if (m->what == B_SET_PROPERTY) {
435 						int32 zoom;
436 						result = m->FindInt32("data", &zoom);
437 						if (result == B_OK)
438 							SetPixelSize(zoom);
439 					}
440 					break;
441 
442 				case 4:
443 					if (m->what == B_GET_PROPERTY)
444 						result = reply.AddBool("result", fFatBits->Sticked());
445 					else if (m->what == B_SET_PROPERTY) {
446 						bool stick;
447 						result = m->FindBool("data", &stick);
448 						if (result == B_OK)
449 							fFatBits->MakeSticked(stick);
450 					}
451 					break;
452 
453 				case 5:
454 					fFatBits->CopyImage();
455 					break;
456 
457 				default:
458 					return BWindow::MessageReceived(m);
459 			}
460 
461 			if (result != B_OK) {
462 				reply.what = B_MESSAGE_NOT_UNDERSTOOD;
463 				reply.AddString("message", strerror(result));
464 				reply.AddInt32("error", result);
465 			}
466 
467 			m->SendReply(&reply);
468 			break;
469 		}
470 
471 		case msg_show_info:
472 			if (active) {
473 				fInfoBarState = !fInfoBarState;
474 				ShowInfo(!fShowInfo);
475 			}
476 			break;
477 
478 		case msg_toggle_grid:
479 			if (active)
480 				SetGrid(!fShowGrid);
481 			break;
482 
483 		case msg_grow:
484 			if (active)
485 				ResizeWindow(true);
486 			break;
487 		case msg_shrink:
488 			if (active)
489 				ResizeWindow(false);
490 			break;
491 		case msg_make_square:
492 			if (active) {
493 				if (fHPixelCount == fVPixelCount)
494 					break;
495 				int32 big = (fHPixelCount > fVPixelCount) ? fHPixelCount : fVPixelCount;
496 				ResizeWindow(big, big);
497 			}
498 			break;
499 
500 		case msg_shrink_pixel:
501 			if (active)
502 				SetPixelSize(false);
503 			break;
504 		case msg_grow_pixel:
505 			if (active)
506 				SetPixelSize(true);
507 			break;
508 
509 		case msg_mouse_left:
510 			if (active)
511 				fFatBits->NudgeMouse(-1, 0);
512 			break;
513 		case msg_mouse_right:
514 			if (active)
515 				fFatBits->NudgeMouse(1, 0);
516 			break;
517 		case msg_mouse_up:
518 			if (active)
519 				fFatBits->NudgeMouse(0, -1);
520 			break;
521 		case msg_mouse_down:
522 			if (active)
523 				fFatBits->NudgeMouse(0, 1);
524 			break;
525 
526 		case msg_add_cross_hair:
527 			if (active && fShowInfo)
528 				AddCrossHair();
529 			break;
530 		case msg_remove_cross_hair:
531 			if (active && fShowInfo)
532 				RemoveCrossHair();
533 			break;
534 
535 		case msg_freeze:
536 			if (active)
537 				SetFlags(B_OUTLINE_RESIZE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE);
538 			else
539 				SetFlags(B_OUTLINE_RESIZE | B_NOT_ZOOMABLE);
540 
541 			fFatBits->MakeActive(!fFatBits->Active());
542 			break;
543 
544 		case msg_stick:
545 			fFatBits->MakeSticked(!fFatBits->Sticked());
546 			break;
547 
548 		case msg_save: {
549 			// freeze the image here, unfreeze after dump or cancel
550 			fFatBits->StartSave();
551 
552 			BMessenger messenger(this);
553 			BMessage message(msg_dump);
554 			fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, 0, 0, false,
555 				&message);
556 			fSavePanel->SetSaveText("Bitmaps.png");
557 			fSavePanel->Show();
558 		}	break;
559 		case msg_dump:
560 			{
561 				delete fSavePanel;
562 
563 				entry_ref dirRef;
564 				char* name;
565 				m->FindRef("directory", &dirRef);
566 				m->FindString((const char*)"name",(const char**) &name);
567 
568 				fFatBits->SaveImage(&dirRef, name);
569 			}
570 			break;
571 		case B_CANCEL:
572 			//	image is frozen before the FilePanel is shown
573 			fFatBits->EndSave();
574 			break;
575 
576 		case msg_copy_image:
577 			fFatBits->CopyImage();
578 			break;
579 		default:
580 			BWindow::MessageReceived(m);
581 			break;
582 	}
583 }
584 
585 
586 bool
587 TWindow::QuitRequested()
588 {
589 	SetPrefs();
590 	be_app->PostMessage(B_QUIT_REQUESTED);
591 	return true;
592 }
593 
594 
595 void
596 TWindow::GetPrefs(int32 overridePixelCount)
597 {
598 	BPath path;
599 	char name[8];
600 	float version;
601 	bool haveLoc=false;
602 	BPoint loc;
603 	bool showGrid = kDefaultShowGrid;
604 	bool showInfo = kDefaultShowInfo;
605 	bool ch1Showing=false;
606 	bool ch2Showing=false;
607 	int32 hPixelCount = kDefaultPixelCount;
608 	int32 vPixelCount = kDefaultPixelCount;
609 	int32 pixelSize = kDefaultPixelSize;
610 
611 	if (find_directory (B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
612 		int ref = -1;
613 		path.Append(kPrefsFileName);
614 		if ((ref = open(path.Path(), 0)) >= 0) {
615 			if (read(ref, name, 7) != 7)
616 				goto ALMOST_DONE;
617 
618 			name[7] = 0;
619 			if (strcmp(name, kAppName) != 0)
620 				goto ALMOST_DONE;
621 
622 			read(ref, &version, sizeof(float));
623 
624 			if (read(ref, &loc, sizeof(BPoint)) != sizeof(BPoint))
625 				goto ALMOST_DONE;
626 			else
627 				haveLoc = true;
628 
629 			if (read(ref, &showGrid, sizeof(bool)) != sizeof(bool)) {
630 				showGrid = kDefaultShowGrid;
631 				goto ALMOST_DONE;
632 			}
633 
634 			if (read(ref, &showInfo, sizeof(bool)) != sizeof(bool)) {
635 				showInfo = kDefaultShowInfo;
636 				goto ALMOST_DONE;
637 			}
638 
639 			if (read(ref, &ch1Showing, sizeof(bool)) != sizeof(bool)) {
640 				ch1Showing = false;
641 				goto ALMOST_DONE;
642 			}
643 
644 			if (read(ref, &ch2Showing, sizeof(bool)) != sizeof(bool)) {
645 				ch2Showing = false;
646 				goto ALMOST_DONE;
647 			}
648 
649 			if (read(ref, &hPixelCount, sizeof(int32)) != sizeof(int32)) {
650 				hPixelCount = kDefaultPixelCount;
651 				goto ALMOST_DONE;
652 			}
653 			if (read(ref, &vPixelCount, sizeof(int32)) != sizeof(int32)) {
654 				vPixelCount = kDefaultPixelCount;
655 				goto ALMOST_DONE;
656 			}
657 
658 			if (read(ref, &pixelSize, sizeof(int32)) != sizeof(int32)) {
659 				pixelSize = kDefaultPixelSize;
660 				goto ALMOST_DONE;
661 			}
662 
663 ALMOST_DONE:	//	clean up and try to position the window
664 			close(ref);
665 
666 			if (haveLoc && BScreen(B_MAIN_SCREEN_ID).Frame().Contains(loc)) {
667 				MoveTo(loc);
668 				goto DONE;
669 			}
670 		}
671 	}
672 
673 	// 	if prefs dont yet exist or the window is not onscreen, center the window
674 	CenterOnScreen();
675 
676 	//	set all the settings to defaults if we get here
677 DONE:
678 	fShowGrid = showGrid;
679 	fShowInfo = showInfo;
680 	fInfoBarState = showInfo;
681 	fHPixelCount = (overridePixelCount == -1) ? hPixelCount : overridePixelCount;
682 	fVPixelCount = (overridePixelCount == -1) ? vPixelCount : overridePixelCount;
683 	fPixelSize = pixelSize;
684 }
685 
686 
687 void
688 TWindow::SetPrefs()
689 {
690 	BPath path;
691 
692 	if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true) == B_OK) {
693 		long ref;
694 
695 		path.Append (kPrefsFileName);
696 		if ((ref = creat(path.Path(), S_IRUSR | S_IWUSR)) >= 0) {
697 			float version = kCurrentVersion;
698 
699 			lseek (ref, 0, SEEK_SET);
700 			write(ref, kAppName, 7);
701 			write(ref, &version, sizeof(float));
702 
703 			BPoint loc = Frame().LeftTop();
704 			write(ref, &loc, sizeof(BPoint));
705 
706 			write(ref, &fShowGrid, sizeof(bool));
707 			write(ref, &fShowInfo, sizeof(bool));
708 			bool ch1, ch2;
709 			CrossHairsShowing(&ch1, &ch2);
710 			write(ref, &ch1, sizeof(bool));
711 			write(ref, &ch2, sizeof(bool));
712 
713 			write(ref, &fHPixelCount, sizeof(int32));
714 			write(ref, &fVPixelCount, sizeof(int32));
715 			write(ref, &fPixelSize, sizeof(int32));
716 
717 			close(ref);
718 		}
719 	}
720 }
721 
722 
723 void
724 TWindow::FrameResized(float w, float h)
725 {
726 	CalcViewablePixels();
727 	fFatBits->InitBuffers(fHPixelCount, fVPixelCount, fPixelSize, ShowGrid());
728 	UpdateInfoBarOnResize();
729 }
730 
731 
732 void
733 TWindow::ScreenChanged(BRect screenSize, color_space depth)
734 {
735 	BWindow::ScreenChanged(screenSize, depth);
736 	// reset all bitmaps
737 	fFatBits->ScreenChanged(screenSize,depth);
738 }
739 
740 
741 void
742 TWindow::Minimize(bool m)
743 {
744 	BWindow::Minimize(m);
745 }
746 
747 
748 void
749 TWindow::Zoom(BPoint /*position*/, float /*width*/, float /*height*/)
750 {
751 	if (fFatBits->Active())
752 		ShowInfo(!fShowInfo);
753 }
754 
755 
756 void
757 TWindow::CalcViewablePixels()
758 {
759 	float w = Bounds().Width();
760 	float h = Bounds().Height();
761 
762 	if (InfoIsShowing()) {
763 		w -= 20;							// remove the gutter
764 		h = h-fInfoHeight-10;				// remove info and gutter
765 	}
766 
767 	bool ch1, ch2;
768 	fFatBits->CrossHairsShowing(&ch1, &ch2);
769 	if (ch1)
770 		h -= fFontHeight;
771 	if (ch2)
772 		h -= fFontHeight + 5;
773 
774 	fHPixelCount = (int32)w / fPixelSize;			// calc h pixels
775 	if (fHPixelCount < 16)
776 		fHPixelCount = 16;
777 
778 	fVPixelCount = (int32)h / fPixelSize;			// calc v pixels
779 	if (fVPixelCount < 4)
780 		fVPixelCount = 4;
781 }
782 
783 
784 void
785 TWindow::GetPreferredSize(float* width, float* height)
786 {
787 	*width = fHPixelCount * fPixelSize;			// calc window width
788 	*height = fVPixelCount * fPixelSize;		// calc window height
789 	if (InfoIsShowing()) {
790 		*width += 20;
791 		*height += fInfoHeight + 10;
792 	}
793 
794 	bool ch1, ch2;
795 	fFatBits->CrossHairsShowing(&ch1, &ch2);
796 	if (ch1)
797 		*height += fFontHeight;
798 	if (ch2)
799 		*height += fFontHeight + 5;
800 }
801 
802 
803 void
804 TWindow::ResizeWindow(int32 hPixelCount, int32 vPixelCount)
805 {
806 	fHPixelCount = hPixelCount;
807 	fVPixelCount = vPixelCount;
808 
809 	float width, height;
810 	GetPreferredSize(&width, &height);
811 
812 	ResizeTo(width, height);
813 }
814 
815 
816 void
817 TWindow::ResizeWindow(bool direction)
818 {
819 	int32 x = fHPixelCount;
820 	int32 y = fVPixelCount;
821 
822 	if (direction) {
823 		x += 4;
824 		y += 4;
825 	} else {
826 		x -= 4;
827 		y -= 4;
828 	}
829 
830 	if (x < 4)
831 		x = 4;
832 
833 	if (y < 4)
834 		y = 4;
835 
836 	ResizeWindow(x, y);
837 }
838 
839 
840 void
841 TWindow::SetGrid(bool s)
842 {
843 	if (s == fShowGrid)
844 		return;
845 
846 	fShowGrid = s;
847 	fFatBits->SetUpdate(true);
848 }
849 
850 
851 bool
852 TWindow::ShowGrid()
853 {
854 	return fShowGrid;
855 }
856 
857 
858 void
859 TWindow::ShowInfo(bool i)
860 {
861 	if (i == fShowInfo)
862 		return;
863 
864 	fShowInfo = i;
865 
866 	if (fShowInfo)
867 		fFatBits->MoveTo(10, fInfoHeight);
868 	else {
869 		fFatBits->MoveTo(1,1);
870 		fFatBits->SetCrossHairsShowing(false, false);
871 	}
872 
873 	fFatBits->SetSelection(fShowInfo);
874 	ResizeWindow(fHPixelCount, fVPixelCount);
875 	fInfo->SetInfoTextVisible(i);
876 }
877 
878 
879 bool
880 TWindow::InfoIsShowing()
881 {
882 	return fShowInfo;
883 }
884 
885 
886 bool
887 TWindow::InfoBarIsVisible()
888 {
889 	return fInfoBarState;
890 }
891 
892 
893 void
894 TWindow::UpdateInfo()
895 {
896 	fInfo->Invalidate();
897 }
898 
899 
900 void
901 TWindow::UpdateInfoBarOnResize()
902 {
903 	float infoWidth, infoHeight;
904 	fInfo->GetPreferredSize(&infoWidth, &infoHeight);
905 
906 	if (infoWidth > Bounds().Width()
907 		|| infoHeight > Bounds().Height()) {
908 		ShowInfo(false);
909 	} else {
910 		ShowInfo(fInfoBarState);
911 	}
912 }
913 
914 
915 void
916 TWindow::AddCrossHair()
917 {
918 	fFatBits->AddCrossHair();
919 
920 	// crosshair info needs to be added
921 	// window resizes accordingly
922 	float width;
923 	float height;
924 	GetPreferredSize(&width, &height);
925 	ResizeTo(width, height);
926 }
927 
928 
929 void
930 TWindow::RemoveCrossHair()
931 {
932 	fFatBits->RemoveCrossHair();
933 
934 	//	crosshair info needs to be removed
935 	//	window resizes accordingly
936 	float width;
937 	float height;
938 	GetPreferredSize(&width, &height);
939 	ResizeTo(width, height);
940 }
941 
942 
943 void
944 TWindow::CrossHairsShowing(bool* ch1, bool* ch2)
945 {
946 	fFatBits->CrossHairsShowing(ch1, ch2);
947 }
948 
949 
950 void
951 TWindow::PixelCount(int32* h, int32 *v)
952 {
953 	*h = fHPixelCount;
954 	*v = fVPixelCount;
955 }
956 
957 
958 void
959 TWindow::SetPixelSize(int32 s)
960 {
961 	if (s > 100)
962 		s = 100;
963 	else if (s < 1)
964 		s = 1;
965 
966 	if (s == fPixelSize)
967 		return;
968 
969 	fPixelSize = s;
970 	// resize window
971 	// tell info that size has changed
972 	// tell mag that size has changed
973 
974 	float w = Bounds().Width();
975 	float h = Bounds().Height();
976 	CalcViewablePixels();
977 	ResizeWindow(fHPixelCount, fVPixelCount);
978 
979 	//	the window might not actually change in size
980 	//	in that case force the buffers to the new dimension
981 	if (w == Bounds().Width() && h == Bounds().Height())
982 		fFatBits->InitBuffers(fHPixelCount, fVPixelCount, fPixelSize, ShowGrid());
983 }
984 
985 
986 void
987 TWindow::SetPixelSize(bool plus)
988 {
989 	int32 pixelSize;
990 
991 	if (plus) {
992 		if (fPixelSize >= 16)
993 			return;
994 
995 		pixelSize = fPixelSize + 1;
996 	} else {
997 		pixelSize = fPixelSize / 2;
998 
999 		if (pixelSize < 16) {
1000 			if (fPixelSize > 16)
1001 				pixelSize = (fPixelSize + 16) / 2;
1002 			else
1003 				pixelSize = fPixelSize - 1;
1004 		}
1005 	}
1006 
1007 	SetPixelSize(pixelSize);
1008 }
1009 
1010 
1011 int32
1012 TWindow::PixelSize()
1013 {
1014 	return fPixelSize;
1015 }
1016 
1017 
1018 #undef B_TRANSLATION_CONTEXT
1019 #define B_TRANSLATION_CONTEXT "Magnify-Main"
1020 
1021 
1022 bool
1023 TWindow::IsActive()
1024 {
1025 	return fFatBits->Active();
1026 }
1027 
1028 
1029 bool
1030 TWindow::IsSticked()
1031 {
1032 	return fFatBits->Sticked();
1033 }
1034 
1035 
1036 //	#pragma mark -
1037 
1038 
1039 TInfoView::TInfoView(BRect frame)
1040 	: BBox(frame, "rgb", B_FOLLOW_ALL,
1041 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS,
1042 		B_NO_BORDER)
1043 {
1044 	SetFont(be_plain_font);
1045 	fFontHeight = FontHeight(this, true);
1046 	fMagView = NULL;
1047 
1048 	fSelectionColor = kBlack;
1049 	fCH1Loc.x = fCH1Loc.y = fCH2Loc.x = fCH2Loc.y = 0;
1050 
1051 	fInfoStr[0] = 0;
1052 	fRGBStr[0] = 0;
1053 	fCH1Str[0] = 0;
1054 	fCH2Str[0] = 0;
1055 
1056 	fInfoTextVisible = true;
1057 }
1058 
1059 
1060 TInfoView::~TInfoView()
1061 {
1062 }
1063 
1064 
1065 void
1066 TInfoView::AttachedToWindow()
1067 {
1068 	BBox::AttachedToWindow();
1069 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1070 	dynamic_cast<TWindow*>(Window())->PixelCount(&fHPixelCount, &fVPixelCount);
1071 	fPixelSize = dynamic_cast<TWindow*>(Window())->PixelSize();
1072 
1073 	AddMenu();
1074 }
1075 
1076 
1077 void
1078 TInfoView::Draw(BRect updateRect)
1079 {
1080 	PushState();
1081 	SetLowColor(ViewColor());
1082 
1083 	BRect invalRect;
1084 
1085 	int32 hPixelCount, vPixelCount;
1086 	dynamic_cast<TWindow*>(Window())->PixelCount(&hPixelCount, &vPixelCount);
1087 	int32 pixelSize = dynamic_cast<TWindow*>(Window())->PixelSize();
1088 
1089 	MovePenTo(15 + fPopUp->Bounds().Width(), fFontHeight + 5);
1090 
1091 	static BStringFormat format(B_TRANSLATE_COMMENT("%width × %height  @ {0, plural, "
1092 		"one{# pixel/pixel} other{# pixels/pixel}}",
1093 		"The '×' is the Unicode multiplication sign U+00D7"));
1094 
1095 	BString dimensionsInfo;
1096 	format.Format(dimensionsInfo, pixelSize);
1097 
1098 	BString rep;
1099 	rep << hPixelCount;
1100 	dimensionsInfo.ReplaceAll("%width", rep);
1101 	rep = "";
1102 	rep << vPixelCount;
1103 	dimensionsInfo.ReplaceAll("%height", rep);
1104 
1105 	invalRect.Set(10, 5, 10 + StringWidth(fInfoStr), fFontHeight+7);
1106 	SetHighColor(ViewColor());
1107 	FillRect(invalRect);
1108 	SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1109 	strcpy(fInfoStr, dimensionsInfo);
1110 	if (fInfoTextVisible)
1111 		DrawString(fInfoStr);
1112 
1113 	rgb_color color = { 0, 0, 0, 255 };
1114 	if (fMagView)
1115 		color = fMagView->SelectionColor();
1116 	char str[64];
1117 	snprintf(str, sizeof(str), "R: %i G: %i B: %i (#%02x%02x%02x)",
1118 		color.red, color.green, color.blue, color.red, color.green, color.blue);
1119 
1120 	MovePenTo(15 + fPopUp->Bounds().Width(), fFontHeight*2+5);
1121 	invalRect.Set(10, fFontHeight+7, 10 + StringWidth(fRGBStr), fFontHeight*2+7);
1122 	SetHighColor(ViewColor());
1123 	FillRect(invalRect);
1124 	SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1125 	strcpy(fRGBStr,str);
1126 	if (fInfoTextVisible)
1127 		DrawString(fRGBStr);
1128 
1129 	bool ch1Showing, ch2Showing;
1130 	dynamic_cast<TWindow*>(Window())->CrossHairsShowing(&ch1Showing, &ch2Showing);
1131 
1132 	if (fMagView) {
1133 		BPoint pt1(fMagView->CrossHair1Loc());
1134 		BPoint pt2(fMagView->CrossHair2Loc());
1135 
1136 		float h = Bounds().Height();
1137 		if (ch2Showing) {
1138 			MovePenTo(10, h-12);
1139 			sprintf(str, "2) x: %" B_PRIi32 " y: %" B_PRIi32 "   y: %d",
1140 				(int32)pt2.x, (int32)pt2.y, abs((int)(pt1.y - pt2.y)));
1141 			invalRect.Set(10, h-12-fFontHeight, 10 + StringWidth(fCH2Str), h-10);
1142 			SetHighColor(ViewColor());
1143 			FillRect(invalRect);
1144 			SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1145 			strcpy(fCH2Str,str);
1146 			if (fInfoTextVisible)
1147 				DrawString(fCH2Str);
1148 		}
1149 
1150 		if (ch1Showing && ch2Showing) {
1151 			MovePenTo(10, h-10-fFontHeight-2);
1152 			sprintf(str, "1) x: %" B_PRIi32 "  y: %" B_PRIi32 "   x: %d",
1153 				(int32)pt1.x, (int32)pt1.y, abs((int)(pt1.x - pt2.x)));
1154 			invalRect.Set(10, h-10-2*fFontHeight-2, 10 + StringWidth(fCH1Str), h-10-fFontHeight);
1155 			SetHighColor(ViewColor());
1156 			FillRect(invalRect);
1157 			SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1158 			strcpy(fCH1Str,str);
1159 			if (fInfoTextVisible)
1160 				DrawString(fCH1Str);
1161 		} else if (ch1Showing) {
1162 			MovePenTo(10, h-10);
1163 			sprintf(str, "x: %" B_PRIi32 "  y: %" B_PRIi32, (int32)pt1.x, (int32)pt1.y);
1164 			invalRect.Set(10, h-10-fFontHeight, 10 + StringWidth(fCH1Str), h-8);
1165 			SetHighColor(ViewColor());
1166 			FillRect(invalRect);
1167 			SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1168 			strcpy(fCH1Str,str);
1169 			if (fInfoTextVisible)
1170 				DrawString(fCH1Str);
1171 		}
1172 	}
1173 
1174 	PopState();
1175 }
1176 
1177 
1178 void
1179 TInfoView::FrameResized(float width, float height)
1180 {
1181 	BBox::FrameResized(width, height);
1182 }
1183 
1184 
1185 void
1186 TInfoView::AddMenu()
1187 {
1188 	fMenu = new TMenu(dynamic_cast<TWindow*>(Window()), "");
1189 	BuildInfoMenu(fMenu);
1190 
1191 	BRect r(9, 11, 22, 27);
1192 	fPopUp = new BMenuField( r, "region menu", NULL, fMenu, true,
1193 		B_FOLLOW_LEFT | B_FOLLOW_TOP);
1194 	AddChild(fPopUp);
1195 }
1196 
1197 
1198 void
1199 TInfoView::SetMagView(TMagnify* magView)
1200 {
1201 	fMagView = magView;
1202 }
1203 
1204 
1205 //	#pragma mark -
1206 
1207 
1208 TMenu::TMenu(TWindow *mainWindow, const char *title, menu_layout layout)
1209 	: BMenu(title, layout),
1210 	fMainWindow(mainWindow)
1211 {
1212 }
1213 
1214 
1215 TMenu::~TMenu()
1216 {
1217 }
1218 
1219 
1220 void
1221 TMenu::AttachedToWindow()
1222 {
1223 	UpdateInfoMenu(this, fMainWindow);
1224 
1225 	BMenu::AttachedToWindow();
1226 }
1227 
1228 
1229 void
1230 TInfoView::GetPreferredSize(float* _width, float* _height)
1231 {
1232 	if (_width) {
1233 		float str1Width = StringWidth(fCH1Str)
1234 			+ StringWidth(fCH2Str)
1235 			+ StringWidth(fRGBStr)
1236 			+ 30;
1237 		float str2Width = StringWidth(fInfoStr) + 30;
1238 		*_width = str1Width > str2Width ? str1Width : str2Width;
1239 	}
1240 
1241 	if (_height)
1242 		*_height = fFontHeight * 2 + 10;
1243 }
1244 
1245 
1246 bool
1247 TInfoView::IsInfoTextVisible()
1248 {
1249 	return fInfoTextVisible;
1250 }
1251 
1252 
1253 void
1254 TInfoView::SetInfoTextVisible(bool visible)
1255 {
1256 	fInfoTextVisible = visible;
1257 	Draw(Bounds());
1258 }
1259 
1260 
1261 //	#pragma mark -
1262 
1263 
1264 TMagnify::TMagnify(BRect r, TWindow* parent)
1265 	: BView(r, "MagView", B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS),
1266 	fNeedToUpdate(true),
1267 	fThread(-1),
1268 	fActive(true),
1269 	fImageBuf(NULL),
1270 	fImageView(NULL),
1271 	fLastLoc(-1, -1),
1272 	fSelection(-1),
1273 	fShowSelection(false),
1274 	fSelectionLoc(0, 0),
1275 	fShowCrossHair1(false),
1276 	fCrossHair1(-1, -1),
1277 	fShowCrossHair2(false),
1278 	fCrossHair2(-1, -1),
1279 	fParent(parent),
1280 	fStickCoordinates(false)
1281 {
1282 }
1283 
1284 
1285 TMagnify::~TMagnify()
1286 {
1287 	kill_thread(fThread);
1288 	delete fImageBuf;
1289 }
1290 
1291 
1292 void
1293 TMagnify::AttachedToWindow()
1294 {
1295 	int32 width, height;
1296 	fParent->PixelCount(&width, &height);
1297 	InitBuffers(width, height, fParent->PixelSize(), fParent->ShowGrid());
1298 
1299 	fThread = spawn_thread(TMagnify::MagnifyTask, "MagnifyTask",
1300 		B_NORMAL_PRIORITY, this);
1301 
1302 	resume_thread(fThread);
1303 
1304 	SetViewColor(B_TRANSPARENT_32_BIT);
1305 	MakeFocus();
1306 }
1307 
1308 
1309 void
1310 TMagnify::InitBuffers(int32 hPixelCount, int32 vPixelCount,
1311 	int32 pixelSize, bool showGrid)
1312 {
1313 	color_space colorSpace = BScreen(Window()).ColorSpace();
1314 
1315 	BRect r(0, 0, (pixelSize * hPixelCount)-1, (pixelSize * vPixelCount)-1);
1316 	if (Bounds().Width() != r.Width() || Bounds().Height() != r.Height())
1317 		ResizeTo(r.Width(), r.Height());
1318 
1319 	if (fImageView) {
1320 		fImageBuf->Lock();
1321 		fImageView->RemoveSelf();
1322 		fImageBuf->Unlock();
1323 
1324 		fImageView->Resize((int32)r.Width(), (int32)r.Height());
1325 		fImageView->SetSpace(colorSpace);
1326 	} else
1327 		fImageView = new TOSMagnify(r, this, colorSpace);
1328 
1329 	delete fImageBuf;
1330 	fImageBuf = new BBitmap(r, colorSpace, true);
1331 	fImageBuf->Lock();
1332 	fImageBuf->AddChild(fImageView);
1333 	fImageBuf->Unlock();
1334 }
1335 
1336 
1337 void
1338 TMagnify::Draw(BRect)
1339 {
1340 	BRect bounds(Bounds());
1341 	DrawBitmap(fImageBuf, bounds, bounds);
1342 	static_cast<TWindow*>(Window())->UpdateInfo();
1343 }
1344 
1345 
1346 void
1347 TMagnify::KeyDown(const char *key, int32 numBytes)
1348 {
1349 	if (!fShowSelection)
1350 		BView::KeyDown(key, numBytes);
1351 
1352 	switch (key[0]) {
1353 		case B_TAB:
1354 			if (fShowCrossHair1) {
1355 				fSelection++;
1356 
1357 				if (fShowCrossHair2) {
1358 					if (fSelection > 2)
1359 						fSelection = 0;
1360 				} else if (fShowCrossHair1) {
1361 					if (fSelection > 1)
1362 						fSelection = 0;
1363 				}
1364 				fNeedToUpdate = true;
1365 				Invalidate();
1366 			}
1367 			break;
1368 
1369 		case B_LEFT_ARROW:
1370 			MoveSelection(-1,0);
1371 			break;
1372 		case B_RIGHT_ARROW:
1373 			MoveSelection(1,0);
1374 			break;
1375 		case B_UP_ARROW:
1376 			MoveSelection(0,-1);
1377 			break;
1378 		case B_DOWN_ARROW:
1379 			MoveSelection(0,1);
1380 			break;
1381 
1382 		default:
1383 			BView::KeyDown(key,numBytes);
1384 			break;
1385 	}
1386 }
1387 
1388 
1389 void
1390 TMagnify::FrameResized(float newW, float newH)
1391 {
1392 	int32 w, h;
1393 	PixelCount(&w, &h);
1394 
1395 	if (fSelectionLoc.x >= w)
1396 		fSelectionLoc.x = 0;
1397 	if (fSelectionLoc.y >= h)
1398 		fSelectionLoc.y = 0;
1399 
1400 	if (fShowCrossHair1) {
1401 		if (fCrossHair1.x >= w) {
1402 			fCrossHair1.x = fSelectionLoc.x + 2;
1403 			if (fCrossHair1.x >= w)
1404 				fCrossHair1.x = 0;
1405 		}
1406 		if (fCrossHair1.y >= h) {
1407 			fCrossHair1.y = fSelectionLoc.y + 2;
1408 			if (fCrossHair1.y >= h)
1409 				fCrossHair1.y = 0;
1410 		}
1411 
1412 		if (fShowCrossHair2) {
1413 			if (fCrossHair2.x >= w) {
1414 				fCrossHair2.x = fCrossHair1.x + 2;
1415 				if (fCrossHair2.x >= w)
1416 					fCrossHair2.x = 0;
1417 			}
1418 			if (fCrossHair2.y >= h) {
1419 				fCrossHair2.y = fCrossHair1.y + 2;
1420 				if (fCrossHair2.y >= h)
1421 					fCrossHair2.y = 0;
1422 			}
1423 		}
1424 	}
1425 }
1426 
1427 
1428 void
1429 TMagnify::MouseDown(BPoint where)
1430 {
1431 	BMessage *currentMsg = Window()->CurrentMessage();
1432 	if (currentMsg->what == B_MOUSE_DOWN) {
1433 		uint32 buttons = 0;
1434 		currentMsg->FindInt32("buttons", (int32 *)&buttons);
1435 
1436 		uint32 modifiers = 0;
1437 		currentMsg->FindInt32("modifiers", (int32 *)&modifiers);
1438 
1439 		if ((buttons & B_SECONDARY_MOUSE_BUTTON) || (modifiers & B_CONTROL_KEY)) {
1440 			// secondary button was clicked or control key was down, show menu and return
1441 
1442 			BPopUpMenu *menu = new BPopUpMenu(B_TRANSLATE("Info"), false, false);
1443 			menu->SetFont(be_plain_font);
1444 			BuildInfoMenu(menu);
1445 			UpdateInfoMenu(menu, dynamic_cast<TWindow*>(Window()));
1446 			BMenuItem *selected = menu->Go(ConvertToScreen(where));
1447 			if (selected)
1448 				Window()->PostMessage(selected->Message()->what);
1449 			delete menu;
1450 			return;
1451 		}
1452 
1453 		// add a mousedown looper here
1454 
1455 		int32 pixelSize = PixelSize();
1456 		float x = where.x / pixelSize;
1457 		float y = where.y / pixelSize;
1458 
1459 		MoveSelectionTo(x, y);
1460 
1461 		// draw the frozen image
1462 		// update the info region
1463 
1464 		fNeedToUpdate = true;
1465 		Invalidate();
1466 	}
1467 }
1468 
1469 
1470 void
1471 TMagnify::ScreenChanged(BRect, color_space)
1472 {
1473 	int32 width, height;
1474 	fParent->PixelCount(&width, &height);
1475 	InitBuffers(width, height, fParent->PixelSize(), fParent->ShowGrid());
1476 }
1477 
1478 
1479 void
1480 TMagnify::SetSelection(bool state)
1481 {
1482 	if (fShowSelection == state)
1483 		return;
1484 
1485 	fShowSelection = state;
1486 	fSelection = 0;
1487 	Invalidate();
1488 }
1489 
1490 
1491 void
1492 TMagnify::MoveSelection(int32 x, int32 y)
1493 {
1494 	if (!fShowSelection)
1495 		return;
1496 
1497 	int32 xCount, yCount;
1498 	PixelCount(&xCount, &yCount);
1499 
1500 	float xloc, yloc;
1501 	if (fSelection == 0) {
1502 		xloc = fSelectionLoc.x;
1503 		yloc = fSelectionLoc.y;
1504 		BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
1505 		fSelectionLoc.x = xloc;
1506 		fSelectionLoc.y = yloc;
1507 	} else if (fSelection == 1) {
1508 		xloc = fCrossHair1.x;
1509 		yloc = fCrossHair1.y;
1510 		BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
1511 		fCrossHair1.x = xloc;
1512 		fCrossHair1.y = yloc;
1513 	} else if (fSelection == 2) {
1514 		xloc = fCrossHair2.x;
1515 		yloc = fCrossHair2.y;
1516 		BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
1517 		fCrossHair2.x = xloc;
1518 		fCrossHair2.y = yloc;
1519 	}
1520 
1521 	fNeedToUpdate = true;
1522 	Invalidate();
1523 }
1524 
1525 
1526 void
1527 TMagnify::MoveSelectionTo(int32 x, int32 y)
1528 {
1529 	if (!fShowSelection)
1530 		return;
1531 
1532 	int32 xCount, yCount;
1533 	PixelCount(&xCount, &yCount);
1534 	if (x >= xCount)
1535 		x = 0;
1536 	if (y >= yCount)
1537 		y = 0;
1538 
1539 	if (fSelection == 0) {
1540 		fSelectionLoc.x = x;
1541 		fSelectionLoc.y = y;
1542 	} else if (fSelection == 1) {
1543 		fCrossHair1.x = x;
1544 		fCrossHair1.y = y;
1545 	} else if (fSelection == 2) {
1546 		fCrossHair2.x = x;
1547 		fCrossHair2.y = y;
1548 	}
1549 
1550 	fNeedToUpdate = true;
1551 	Invalidate(); //Draw(Bounds());
1552 }
1553 
1554 
1555 void
1556 TMagnify::ShowSelection()
1557 {
1558 }
1559 
1560 
1561 short
1562 TMagnify::Selection()
1563 {
1564 	return fSelection;
1565 }
1566 
1567 
1568 bool
1569 TMagnify::SelectionIsShowing()
1570 {
1571 	return fShowSelection;
1572 }
1573 
1574 
1575 void
1576 TMagnify::SelectionLoc(float* x, float* y)
1577 {
1578 	*x = fSelectionLoc.x;
1579 	*y = fSelectionLoc.y;
1580 }
1581 
1582 
1583 void
1584 TMagnify::SetSelectionLoc(float x, float y)
1585 {
1586 	fSelectionLoc.x = x;
1587 	fSelectionLoc.y = y;
1588 }
1589 
1590 
1591 rgb_color
1592 TMagnify::SelectionColor()
1593 {
1594 	return fImageView->ColorAtSelection();
1595 }
1596 
1597 
1598 void
1599 TMagnify::CrossHair1Loc(float* x, float* y)
1600 {
1601 	*x = fCrossHair1.x;
1602 	*y = fCrossHair1.y;
1603 }
1604 
1605 
1606 void
1607 TMagnify::CrossHair2Loc(float* x, float* y)
1608 {
1609 	*x = fCrossHair2.x;
1610 	*y = fCrossHair2.y;
1611 }
1612 
1613 
1614 BPoint
1615 TMagnify::CrossHair1Loc()
1616 {
1617 	return fCrossHair1;
1618 }
1619 
1620 
1621 BPoint
1622 TMagnify::CrossHair2Loc()
1623 {
1624 	return fCrossHair2;
1625 }
1626 
1627 
1628 void
1629 TMagnify::NudgeMouse(float x, float y)
1630 {
1631 	BPoint loc;
1632 	uint32 button;
1633 
1634 	GetMouse(&loc, &button);
1635 	ConvertToScreen(&loc);
1636 	loc.x += x;
1637 	loc.y += y;
1638 
1639 	set_mouse_position((int32)loc.x, (int32)loc.y);
1640 }
1641 
1642 
1643 void
1644 TMagnify::WindowActivated(bool active)
1645 {
1646 	if (active)
1647 		MakeFocus();
1648 }
1649 
1650 
1651 status_t
1652 TMagnify::MagnifyTask(void *arg)
1653 {
1654 	TMagnify* view = (TMagnify*)arg;
1655 
1656 	// static data members can't access members, methods without
1657 	// a pointer to an instance of the class
1658 	TWindow* window = (TWindow*)view->Window();
1659 
1660 	while (true) {
1661 		if (window->Lock()) {
1662 			if (view->NeedToUpdate() || view->Active())
1663 				view->Update(view->NeedToUpdate());
1664 
1665 			window->Unlock();
1666 		}
1667 		snooze(35000);
1668 	}
1669 
1670 	return B_NO_ERROR;
1671 }
1672 
1673 
1674 void
1675 TMagnify::Update(bool force)
1676 {
1677 	BPoint loc;
1678 	uint32 button;
1679 	static long counter = 0;
1680 
1681 	if (!fStickCoordinates) {
1682 		GetMouse(&loc, &button);
1683 		ConvertToScreen(&loc);
1684 	} else
1685 		loc = fLastLoc;
1686 
1687 	if (force || fLastLoc != loc || counter++ % 35 == 0) {
1688 		if (fImageView->CreateImage(loc, force))
1689 			Invalidate();
1690 
1691 		counter = 0;
1692 		if (force)
1693 			SetUpdate(false);
1694 	}
1695 	fLastLoc = loc;
1696 }
1697 
1698 
1699 bool
1700 TMagnify::NeedToUpdate()
1701 {
1702 	return fNeedToUpdate;
1703 }
1704 
1705 
1706 void
1707 TMagnify::SetUpdate(bool s)
1708 {
1709 	fNeedToUpdate = s;
1710 }
1711 
1712 
1713 void
1714 TMagnify::CopyImage()
1715 {
1716 	StartSave();
1717 	be_clipboard->Lock();
1718 	be_clipboard->Clear();
1719 
1720 	BMessage *message = be_clipboard->Data();
1721 	if (!message) {
1722 		puts(B_TRANSLATE_CONTEXT("no clip msg",
1723 			"In console, when clipboard is empty after clicking Copy image"));
1724 		return;
1725 	}
1726 
1727 	BMessage *embeddedBitmap = new BMessage();
1728 	(fImageView->Bitmap())->Archive(embeddedBitmap,false);
1729 	status_t err = message->AddMessage(kBitmapMimeType, embeddedBitmap);
1730 	if (err == B_OK)
1731 		err = message->AddRect("rect", fImageView->Bitmap()->Bounds());
1732 	if (err == B_OK)
1733 		be_clipboard->Commit();
1734 
1735 	be_clipboard->Unlock();
1736 	EndSave();
1737 }
1738 
1739 
1740 void
1741 TMagnify::AddCrossHair()
1742 {
1743 	if (fShowCrossHair1 && fShowCrossHair2)
1744 		return;
1745 
1746 	int32 w, h;
1747 	PixelCount(&w, &h);
1748 
1749 	if (fShowCrossHair1) {
1750 		fSelection = 2;
1751 		fShowCrossHair2 = true;
1752 		fCrossHair2.x = fCrossHair1.x + 2;
1753 		if (fCrossHair2.x >= w)
1754 			fCrossHair2.x = 0;
1755 		fCrossHair2.y = fCrossHair1.y + 2;
1756 		if (fCrossHair2.y >= h)
1757 			fCrossHair2.y = 0;
1758 	} else {
1759 		fSelection = 1;
1760 		fShowCrossHair1 = true;
1761 		fCrossHair1.x = fSelectionLoc.x + 2;
1762 		if (fCrossHair1.x >= w)
1763 			fCrossHair1.x = 0;
1764 		fCrossHair1.y = fSelectionLoc.y + 2;
1765 		if (fCrossHair1.y >= h)
1766 			fCrossHair1.y = 0;
1767 	}
1768 	Invalidate();
1769 }
1770 
1771 
1772 void
1773 TMagnify::RemoveCrossHair()
1774 {
1775 	if (!fShowCrossHair1 && !fShowCrossHair2)
1776 		return;
1777 
1778 	if (fShowCrossHair2) {
1779 		fSelection = 1;
1780 		fShowCrossHair2 = false;
1781 	} else if (fShowCrossHair1) {
1782 		fSelection = 0;
1783 		fShowCrossHair1 = false;
1784 	}
1785 	Invalidate();
1786 }
1787 
1788 
1789 void
1790 TMagnify::SetCrossHairsShowing(bool ch1, bool ch2)
1791 {
1792 	fShowCrossHair1 = ch1;
1793 	fShowCrossHair2 = ch2;
1794 }
1795 
1796 
1797 void
1798 TMagnify::CrossHairsShowing(bool* ch1, bool* ch2)
1799 {
1800 	*ch1 = fShowCrossHair1;
1801 	*ch2 = fShowCrossHair2;
1802 }
1803 
1804 
1805 void
1806 TMagnify::MakeActive(bool s)
1807 {
1808 	fActive = s;
1809 }
1810 
1811 
1812 void
1813 TMagnify::MakeSticked(bool s)
1814 {
1815 	fStickCoordinates = s;
1816 }
1817 
1818 
1819 void
1820 TMagnify::PixelCount(int32* width, int32* height)
1821 {
1822 	fParent->PixelCount(width, height);
1823 }
1824 
1825 
1826 int32
1827 TMagnify::PixelSize()
1828 {
1829 	return fParent->PixelSize();
1830 }
1831 
1832 
1833 bool
1834 TMagnify::ShowGrid()
1835 {
1836 	return fParent->ShowGrid();
1837 }
1838 
1839 
1840 void
1841 TMagnify::StartSave()
1842 {
1843 	fImageFrozenOnSave = Active();
1844 	if (fImageFrozenOnSave)
1845 		MakeActive(false);
1846 }
1847 
1848 
1849 void
1850 TMagnify::EndSave()
1851 {
1852 	if (fImageFrozenOnSave)
1853 		MakeActive(true);
1854 }
1855 
1856 
1857 void
1858 TMagnify::SaveImage(entry_ref* ref, char* name)
1859 {
1860 	// create a new file
1861 	BFile file;
1862 	BDirectory parentDir(ref);
1863 	parentDir.CreateFile(name, &file);
1864 
1865 	// Write the screenshot bitmap to the file
1866 	BBitmapStream stream(fImageView->Bitmap());
1867 	BTranslatorRoster* roster = BTranslatorRoster::Default();
1868 	roster->Translate(&stream, NULL, NULL, &file, B_PNG_FORMAT,
1869 		B_TRANSLATOR_BITMAP);
1870 
1871 	BBitmap* bitmap;
1872 	stream.DetachBitmap(&bitmap);
1873 		// The stream takes over ownership of the bitmap
1874 
1875 	// unfreeze the image, image was frozen before invoke of FilePanel
1876 	EndSave();
1877 }
1878 
1879 //	#pragma mark -
1880 
1881 
1882 TOSMagnify::TOSMagnify(BRect r, TMagnify* parent, color_space space)
1883 	: BView(r, "ImageView", B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS),
1884 		fColorSpace(space), fParent(parent)
1885 {
1886 	switch (space) {
1887 		case B_CMAP8:
1888 			fBytesPerPixel = 1;
1889 			break;
1890 		case B_RGB15:
1891 		case B_RGBA15:
1892 		case B_RGB15_BIG:
1893 		case B_RGBA15_BIG:
1894 		case B_RGB16:
1895 		case B_RGB16_BIG:
1896 			fBytesPerPixel = 2;
1897 			break;
1898 		case B_RGB24:
1899 			fBytesPerPixel = 3;
1900 			break;
1901 		case B_RGB32:
1902 		case B_RGBA32:
1903 		case B_RGB32_BIG:
1904 		case B_RGBA32_BIG:
1905 			fBytesPerPixel = 4;
1906 			break;
1907 		default:
1908 			// uh, oh -- a color space we don't support
1909 			fprintf(stderr, "Tried to run in an unsupported color space; exiting\n");
1910 			exit(1);
1911 			break;
1912 	}
1913 
1914 	fPixel = NULL;
1915 	fBitmap = NULL;
1916 	fOldBits = NULL;
1917 	InitObject();
1918 }
1919 
1920 
1921 TOSMagnify::~TOSMagnify()
1922 {
1923 	delete fPixel;
1924 	delete fBitmap;
1925 	free(fOldBits);
1926 }
1927 
1928 
1929 void
1930 TOSMagnify::SetSpace(color_space space)
1931 {
1932 	fColorSpace = space;
1933 	InitObject();
1934 };
1935 
1936 
1937 void
1938 TOSMagnify::InitObject()
1939 {
1940 	int32 w, h;
1941 	fParent->PixelCount(&w, &h);
1942 
1943 	delete fBitmap;
1944 	BRect bitsRect(0, 0, w-1, h-1);
1945 	fBitmap = new BBitmap(bitsRect, fColorSpace);
1946 
1947 	free(fOldBits);
1948 	fOldBits = (char*)malloc(fBitmap->BitsLength());
1949 
1950 	if (!fPixel) {
1951 #if B_HOST_IS_BENDIAN
1952 		fPixel = new BBitmap(BRect(0,0,0,0), B_RGBA32_BIG, true);
1953 #else
1954 		fPixel = new BBitmap(BRect(0,0,0,0), B_RGBA32, true);
1955 #endif
1956 		fPixelView = new BView(BRect(0,0,0,0), NULL, 0, 0);
1957 		fPixel->Lock();
1958 		fPixel->AddChild(fPixelView);
1959 		fPixel->Unlock();
1960 	}
1961 }
1962 
1963 
1964 void
1965 TOSMagnify::FrameResized(float width, float height)
1966 {
1967 	BView::FrameResized(width, height);
1968 	InitObject();
1969 }
1970 
1971 
1972 void
1973 TOSMagnify::Resize(int32 width, int32 height)
1974 {
1975 	ResizeTo(width, height);
1976 	InitObject();
1977 }
1978 
1979 
1980 bool
1981 TOSMagnify::CreateImage(BPoint mouseLoc, bool force)
1982 {
1983 	bool created = false;
1984 	if (Window() && Window()->Lock()) {
1985 		int32 width, height;
1986 		fParent->PixelCount(&width, &height);
1987 		int32 pixelSize = fParent->PixelSize();
1988 
1989 		BRect srcRect(0, 0, width - 1, height - 1);
1990 		srcRect.OffsetBy(mouseLoc.x - (width / 2),
1991 			mouseLoc.y - (height / 2));
1992 
1993 		if (force || CopyScreenRect(srcRect)) {
1994 			srcRect.OffsetTo(BPoint(0, 0));
1995 			BRect destRect(Bounds());
1996 
1997 			DrawBitmap(fBitmap, srcRect, destRect);
1998 
1999 			DrawGrid(width, height, destRect, pixelSize);
2000 			DrawSelection();
2001 
2002 			Sync();
2003 			created = true;
2004 		}
2005 		Window()->Unlock();
2006 	} else
2007 		puts("window problem");
2008 
2009 	return created;
2010 }
2011 
2012 
2013 bool
2014 TOSMagnify::CopyScreenRect(BRect srcRect)
2015 {
2016 	// constrain src rect to legal screen rect
2017 	BScreen screen(Window());
2018 	BRect scrnframe = screen.Frame();
2019 
2020 	if (srcRect.right > scrnframe.right)
2021 		srcRect.OffsetTo(scrnframe.right - srcRect.Width(), srcRect.top);
2022 	if (srcRect.top < 0)
2023 		srcRect.OffsetTo(srcRect.left, 0);
2024 
2025 	if (srcRect.bottom > scrnframe.bottom)
2026 		srcRect.OffsetTo(srcRect.left, scrnframe.bottom - srcRect.Height());
2027 	if (srcRect.left < 0)
2028 		srcRect.OffsetTo(0, srcRect.top);
2029 
2030 	// save a copy of the bits for comparison later
2031 	memcpy(fOldBits, fBitmap->Bits(), fBitmap->BitsLength());
2032 
2033 	screen.ReadBitmap(fBitmap, false, &srcRect);
2034 
2035 	// let caller know whether bits have actually changed
2036 	return memcmp(fBitmap->Bits(), fOldBits, fBitmap->BitsLength()) != 0;
2037 }
2038 
2039 
2040 void
2041 TOSMagnify::DrawGrid(int32 width, int32 height, BRect destRect, int32 pixelSize)
2042 {
2043 	// draw grid
2044 	if (fParent->ShowGrid() && fParent->PixelSize() > 2) {
2045 		BeginLineArray(width * height);
2046 
2047 		// horizontal lines
2048 		for (int32 i = pixelSize; i < (height * pixelSize); i += pixelSize)
2049 			AddLine(BPoint(0, i), BPoint(destRect.right, i), kGridGray);
2050 
2051 		// vertical lines
2052 		for (int32 i = pixelSize; i < (width * pixelSize); i += pixelSize)
2053 			AddLine(BPoint(i, 0), BPoint(i, destRect.bottom), kGridGray);
2054 
2055 		EndLineArray();
2056 	}
2057 
2058 	SetHighColor(kGridGray);
2059 	StrokeRect(destRect);
2060 }
2061 
2062 
2063 void
2064 TOSMagnify::DrawSelection()
2065 {
2066 	if (!fParent->SelectionIsShowing())
2067 		return;
2068 
2069 	float x, y;
2070 	int32 pixelSize = fParent->PixelSize();
2071 	int32 squareSize = pixelSize - 2;
2072 
2073 	fParent->SelectionLoc(&x, &y);
2074 	x *= pixelSize; x++;
2075 	y *= pixelSize; y++;
2076 	BRect selRect(x, y, x+squareSize, y+squareSize);
2077 
2078 	short selection = fParent->Selection();
2079 
2080 	PushState();
2081 	SetLowColor(ViewColor());
2082 	SetHighColor(kRedColor);
2083 	StrokeRect(selRect);
2084 	if (selection == 0) {
2085 		StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
2086 		StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
2087 	}
2088 
2089 	bool ch1Showing, ch2Showing;
2090 	fParent->CrossHairsShowing(&ch1Showing, &ch2Showing);
2091 	if (ch1Showing) {
2092 		SetHighColor(kBlueColor);
2093 		fParent->CrossHair1Loc(&x, &y);
2094 		x *= pixelSize; x++;
2095 		y *= pixelSize; y++;
2096 		selRect.Set(x, y,x+squareSize, y+squareSize);
2097 		StrokeRect(selRect);
2098 		BeginLineArray(4);
2099 		AddLine(BPoint(0, y+(squareSize/2)),
2100 			BPoint(x, y+(squareSize/2)), kBlueColor);					//	left
2101 		AddLine(BPoint(x+squareSize,y+(squareSize/2)),
2102 			BPoint(Bounds().Width(), y+(squareSize/2)), kBlueColor);	// right
2103 		AddLine(BPoint(x+(squareSize/2), 0),
2104 			BPoint(x+(squareSize/2), y), kBlueColor);					// top
2105 		AddLine(BPoint(x+(squareSize/2), y+squareSize),
2106 			BPoint(x+(squareSize/2), Bounds().Height()), kBlueColor);	// bottom
2107 		EndLineArray();
2108 		if (selection == 1) {
2109 			StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
2110 			StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
2111 		}
2112 	}
2113 	if (ch2Showing) {
2114 		SetHighColor(kBlueColor);
2115 		fParent->CrossHair2Loc(&x, &y);
2116 		x *= pixelSize; x++;
2117 		y *= pixelSize; y++;
2118 		selRect.Set(x, y,x+squareSize, y+squareSize);
2119 		StrokeRect(selRect);
2120 		BeginLineArray(4);
2121 		AddLine(BPoint(0, y+(squareSize/2)),
2122 			BPoint(x, y+(squareSize/2)), kBlueColor);					//	left
2123 		AddLine(BPoint(x+squareSize,y+(squareSize/2)),
2124 			BPoint(Bounds().Width(), y+(squareSize/2)), kBlueColor);	// right
2125 		AddLine(BPoint(x+(squareSize/2), 0),
2126 			BPoint(x+(squareSize/2), y), kBlueColor);					// top
2127 		AddLine(BPoint(x+(squareSize/2), y+squareSize),
2128 			BPoint(x+(squareSize/2), Bounds().Height()), kBlueColor);	// bottom
2129 		EndLineArray();
2130 		if (selection == 2) {
2131 			StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
2132 			StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
2133 		}
2134 	}
2135 
2136 	PopState();
2137 }
2138 
2139 
2140 rgb_color
2141 TOSMagnify::ColorAtSelection()
2142 {
2143 	float x, y;
2144 	fParent->SelectionLoc(&x, &y);
2145 	BRect srcRect(x, y, x, y);
2146 	BRect dstRect(0, 0, 0, 0);
2147 	fPixel->Lock();
2148 	fPixelView->DrawBitmap(fBitmap, srcRect, dstRect);
2149 	fPixelView->Sync();
2150 	fPixel->Unlock();
2151 
2152 	uint32 pixel = *((uint32*)fPixel->Bits());
2153 	rgb_color c;
2154 	c.alpha = pixel >> 24;
2155 	c.red = (pixel >> 16) & 0xFF;
2156 	c.green = (pixel >> 8) & 0xFF;
2157 	c.blue = pixel & 0xFF;
2158 
2159 	return c;
2160 }
2161 
2162 
2163 //	#pragma mark -
2164 
2165 
2166 int
2167 main(int argc, char* argv[])
2168 {
2169 	int32 pixelCount = -1;
2170 
2171 	if (argc > 2) {
2172 		puts(B_TRANSLATE_CONTEXT(
2173 			"usage: magnify [size] (magnify size * size pixels)",
2174 			"Console"));
2175 		exit(1);
2176 	} else {
2177 		if (argc == 2) {
2178 			pixelCount = abs(atoi(argv[1]));
2179 
2180 			if ((pixelCount > 100) || (pixelCount < 4)) {
2181 				puts(B_TRANSLATE_CONTEXT(
2182 					"usage: magnify [size] (magnify size * size pixels)",
2183 					"Console"));
2184 				puts(B_TRANSLATE_CONTEXT(
2185 					"  size must be > 4 and a multiple of 4",
2186 					"Console"));
2187 				exit(1);
2188 			}
2189 
2190 			if (pixelCount % 4) {
2191 				puts(B_TRANSLATE_CONTEXT(
2192 					"magnify: size must be a multiple of 4",
2193 					"Console"));
2194 				exit(1);
2195 			}
2196 		}
2197 	}
2198 
2199 	TApp app(pixelCount);
2200 	app.Run();
2201 	return 0;
2202 }
2203