xref: /haiku/src/apps/magnify/Magnify.cpp (revision b8a45b3a2df2379b4301bf3bd5949b9a105be4ba)
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 = "";
1054 	fCH2Str = "";
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 (ch1Showing && ch2Showing) {
1138 			MovePenTo(10, h-10-fFontHeight-2);
1139 			fCH1Str.SetToFormat("➀  x: %" B_PRIi32 ", y: %" B_PRIi32,
1140 				(int32)pt1.x, (int32)pt1.y);
1141 			fCH2Str.SetToFormat("➁  x: %" B_PRIi32 ", y: %" B_PRIi32,
1142 				(int32)pt2.x, (int32)pt2.y);
1143 
1144 			BString dimensions;
1145 			dimensions.SetToFormat("width: %d, height: %d",
1146 				abs((int)(pt1.x - pt2.x)), abs((int)(pt1.y - pt2.y)));
1147 			dimensions.ReplaceFirst("width", B_TRANSLATE("width"));
1148 			dimensions.ReplaceFirst("height", B_TRANSLATE("height"));
1149 
1150 			float width = StringWidth(fCH2Str) + StringWidth(dimensions) + 30;
1151 			invalRect.Set(10, h-10-2*fFontHeight-2, width,	h-10-fFontHeight);
1152 			SetHighColor(ViewColor());
1153 			FillRect(invalRect);
1154 			SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1155 
1156 			if (fInfoTextVisible) {
1157 				DrawString(fCH1Str);
1158 				MovePenTo(10, h-12);
1159 				DrawString(fCH2Str);
1160 				MovePenTo(StringWidth(fCH2Str) + 30, h-10-fFontHeight/2-4);
1161 				DrawString(dimensions);
1162 			}
1163 		} else if (ch1Showing) {
1164 			MovePenTo(10, h-10);
1165 			fCH1Str.SetToFormat("x: %" B_PRIi32 ", y: %" B_PRIi32, (int32)pt1.x, (int32)pt1.y);
1166 			invalRect.Set(10, h-10-fFontHeight, 10 + StringWidth(fCH1Str), h-8);
1167 			SetHighColor(ViewColor());
1168 			FillRect(invalRect);
1169 			SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1170 			if (fInfoTextVisible)
1171 				DrawString(fCH1Str);
1172 		}
1173 	}
1174 
1175 	PopState();
1176 }
1177 
1178 
1179 void
1180 TInfoView::FrameResized(float width, float height)
1181 {
1182 	BBox::FrameResized(width, height);
1183 }
1184 
1185 
1186 void
1187 TInfoView::AddMenu()
1188 {
1189 	fMenu = new TMenu(dynamic_cast<TWindow*>(Window()), "");
1190 	BuildInfoMenu(fMenu);
1191 
1192 	BRect r(9, 11, 22, 27);
1193 	fPopUp = new BMenuField( r, "region menu", NULL, fMenu, true,
1194 		B_FOLLOW_LEFT | B_FOLLOW_TOP);
1195 	AddChild(fPopUp);
1196 }
1197 
1198 
1199 void
1200 TInfoView::SetMagView(TMagnify* magView)
1201 {
1202 	fMagView = magView;
1203 }
1204 
1205 
1206 //	#pragma mark -
1207 
1208 
1209 TMenu::TMenu(TWindow *mainWindow, const char *title, menu_layout layout)
1210 	: BMenu(title, layout),
1211 	fMainWindow(mainWindow)
1212 {
1213 }
1214 
1215 
1216 TMenu::~TMenu()
1217 {
1218 }
1219 
1220 
1221 void
1222 TMenu::AttachedToWindow()
1223 {
1224 	UpdateInfoMenu(this, fMainWindow);
1225 
1226 	BMenu::AttachedToWindow();
1227 }
1228 
1229 
1230 void
1231 TInfoView::GetPreferredSize(float* _width, float* _height)
1232 {
1233 	if (_width) {
1234 		float str1Width = StringWidth(fCH1Str)
1235 			+ StringWidth(fCH2Str)
1236 			+ StringWidth(fRGBStr)
1237 			+ 30;
1238 		float str2Width = StringWidth(fInfoStr) + 30;
1239 		*_width = str1Width > str2Width ? str1Width : str2Width;
1240 	}
1241 
1242 	if (_height)
1243 		*_height = fFontHeight * 2 + 10;
1244 }
1245 
1246 
1247 bool
1248 TInfoView::IsInfoTextVisible()
1249 {
1250 	return fInfoTextVisible;
1251 }
1252 
1253 
1254 void
1255 TInfoView::SetInfoTextVisible(bool visible)
1256 {
1257 	fInfoTextVisible = visible;
1258 	Draw(Bounds());
1259 }
1260 
1261 
1262 //	#pragma mark -
1263 
1264 
1265 TMagnify::TMagnify(BRect r, TWindow* parent)
1266 	: BView(r, "MagView", B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS),
1267 	fNeedToUpdate(true),
1268 	fThread(-1),
1269 	fActive(true),
1270 	fImageBuf(NULL),
1271 	fImageView(NULL),
1272 	fLastLoc(-1, -1),
1273 	fSelection(-1),
1274 	fShowSelection(false),
1275 	fSelectionLoc(0, 0),
1276 	fShowCrossHair1(false),
1277 	fCrossHair1(-1, -1),
1278 	fShowCrossHair2(false),
1279 	fCrossHair2(-1, -1),
1280 	fParent(parent),
1281 	fStickCoordinates(false)
1282 {
1283 }
1284 
1285 
1286 TMagnify::~TMagnify()
1287 {
1288 	kill_thread(fThread);
1289 	delete fImageBuf;
1290 }
1291 
1292 
1293 void
1294 TMagnify::AttachedToWindow()
1295 {
1296 	int32 width, height;
1297 	fParent->PixelCount(&width, &height);
1298 	InitBuffers(width, height, fParent->PixelSize(), fParent->ShowGrid());
1299 
1300 	fThread = spawn_thread(TMagnify::MagnifyTask, "MagnifyTask",
1301 		B_NORMAL_PRIORITY, this);
1302 
1303 	resume_thread(fThread);
1304 
1305 	SetViewColor(B_TRANSPARENT_32_BIT);
1306 	MakeFocus();
1307 }
1308 
1309 
1310 void
1311 TMagnify::InitBuffers(int32 hPixelCount, int32 vPixelCount,
1312 	int32 pixelSize, bool showGrid)
1313 {
1314 	color_space colorSpace = BScreen(Window()).ColorSpace();
1315 
1316 	BRect r(0, 0, (pixelSize * hPixelCount)-1, (pixelSize * vPixelCount)-1);
1317 	if (Bounds().Width() != r.Width() || Bounds().Height() != r.Height())
1318 		ResizeTo(r.Width(), r.Height());
1319 
1320 	if (fImageView) {
1321 		fImageBuf->Lock();
1322 		fImageView->RemoveSelf();
1323 		fImageBuf->Unlock();
1324 
1325 		fImageView->Resize((int32)r.Width(), (int32)r.Height());
1326 		fImageView->SetSpace(colorSpace);
1327 	} else
1328 		fImageView = new TOSMagnify(r, this, colorSpace);
1329 
1330 	delete fImageBuf;
1331 	fImageBuf = new BBitmap(r, colorSpace, true);
1332 	fImageBuf->Lock();
1333 	fImageBuf->AddChild(fImageView);
1334 	fImageBuf->Unlock();
1335 }
1336 
1337 
1338 void
1339 TMagnify::Draw(BRect)
1340 {
1341 	BRect bounds(Bounds());
1342 	DrawBitmap(fImageBuf, bounds, bounds);
1343 	static_cast<TWindow*>(Window())->UpdateInfo();
1344 }
1345 
1346 
1347 void
1348 TMagnify::KeyDown(const char *key, int32 numBytes)
1349 {
1350 	if (!fShowSelection)
1351 		BView::KeyDown(key, numBytes);
1352 
1353 	switch (key[0]) {
1354 		case B_TAB:
1355 			if (fShowCrossHair1) {
1356 				fSelection++;
1357 
1358 				if (fShowCrossHair2) {
1359 					if (fSelection > 2)
1360 						fSelection = 0;
1361 				} else if (fShowCrossHair1) {
1362 					if (fSelection > 1)
1363 						fSelection = 0;
1364 				}
1365 				fNeedToUpdate = true;
1366 				Invalidate();
1367 			}
1368 			break;
1369 
1370 		case B_LEFT_ARROW:
1371 			MoveSelection(-1,0);
1372 			break;
1373 		case B_RIGHT_ARROW:
1374 			MoveSelection(1,0);
1375 			break;
1376 		case B_UP_ARROW:
1377 			MoveSelection(0,-1);
1378 			break;
1379 		case B_DOWN_ARROW:
1380 			MoveSelection(0,1);
1381 			break;
1382 
1383 		default:
1384 			BView::KeyDown(key,numBytes);
1385 			break;
1386 	}
1387 }
1388 
1389 
1390 void
1391 TMagnify::FrameResized(float newW, float newH)
1392 {
1393 	int32 w, h;
1394 	PixelCount(&w, &h);
1395 
1396 	if (fSelectionLoc.x >= w)
1397 		fSelectionLoc.x = 0;
1398 	if (fSelectionLoc.y >= h)
1399 		fSelectionLoc.y = 0;
1400 
1401 	if (fShowCrossHair1) {
1402 		if (fCrossHair1.x >= w) {
1403 			fCrossHair1.x = fSelectionLoc.x + 2;
1404 			if (fCrossHair1.x >= w)
1405 				fCrossHair1.x = 0;
1406 		}
1407 		if (fCrossHair1.y >= h) {
1408 			fCrossHair1.y = fSelectionLoc.y + 2;
1409 			if (fCrossHair1.y >= h)
1410 				fCrossHair1.y = 0;
1411 		}
1412 
1413 		if (fShowCrossHair2) {
1414 			if (fCrossHair2.x >= w) {
1415 				fCrossHair2.x = fCrossHair1.x + 2;
1416 				if (fCrossHair2.x >= w)
1417 					fCrossHair2.x = 0;
1418 			}
1419 			if (fCrossHair2.y >= h) {
1420 				fCrossHair2.y = fCrossHair1.y + 2;
1421 				if (fCrossHair2.y >= h)
1422 					fCrossHair2.y = 0;
1423 			}
1424 		}
1425 	}
1426 }
1427 
1428 
1429 void
1430 TMagnify::MouseDown(BPoint where)
1431 {
1432 	BMessage *currentMsg = Window()->CurrentMessage();
1433 	if (currentMsg->what == B_MOUSE_DOWN) {
1434 		uint32 buttons = 0;
1435 		currentMsg->FindInt32("buttons", (int32 *)&buttons);
1436 
1437 		uint32 modifiers = 0;
1438 		currentMsg->FindInt32("modifiers", (int32 *)&modifiers);
1439 
1440 		if ((buttons & B_SECONDARY_MOUSE_BUTTON) || (modifiers & B_CONTROL_KEY)) {
1441 			// secondary button was clicked or control key was down, show menu and return
1442 
1443 			BPopUpMenu *menu = new BPopUpMenu(B_TRANSLATE("Info"), false, false);
1444 			menu->SetFont(be_plain_font);
1445 			BuildInfoMenu(menu);
1446 			UpdateInfoMenu(menu, dynamic_cast<TWindow*>(Window()));
1447 			BMenuItem *selected = menu->Go(ConvertToScreen(where));
1448 			if (selected)
1449 				Window()->PostMessage(selected->Message()->what);
1450 			delete menu;
1451 			return;
1452 		}
1453 
1454 		// add a mousedown looper here
1455 
1456 		int32 pixelSize = PixelSize();
1457 		float x = where.x / pixelSize;
1458 		float y = where.y / pixelSize;
1459 
1460 		MoveSelectionTo(x, y);
1461 
1462 		// draw the frozen image
1463 		// update the info region
1464 
1465 		fNeedToUpdate = true;
1466 		Invalidate();
1467 	}
1468 }
1469 
1470 
1471 void
1472 TMagnify::ScreenChanged(BRect, color_space)
1473 {
1474 	int32 width, height;
1475 	fParent->PixelCount(&width, &height);
1476 	InitBuffers(width, height, fParent->PixelSize(), fParent->ShowGrid());
1477 }
1478 
1479 
1480 void
1481 TMagnify::SetSelection(bool state)
1482 {
1483 	if (fShowSelection == state)
1484 		return;
1485 
1486 	fShowSelection = state;
1487 	fSelection = 0;
1488 	Invalidate();
1489 }
1490 
1491 
1492 void
1493 TMagnify::MoveSelection(int32 x, int32 y)
1494 {
1495 	if (!fShowSelection)
1496 		return;
1497 
1498 	int32 xCount, yCount;
1499 	PixelCount(&xCount, &yCount);
1500 
1501 	float xloc, yloc;
1502 	if (fSelection == 0) {
1503 		xloc = fSelectionLoc.x;
1504 		yloc = fSelectionLoc.y;
1505 		BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
1506 		fSelectionLoc.x = xloc;
1507 		fSelectionLoc.y = yloc;
1508 	} else if (fSelection == 1) {
1509 		xloc = fCrossHair1.x;
1510 		yloc = fCrossHair1.y;
1511 		BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
1512 		fCrossHair1.x = xloc;
1513 		fCrossHair1.y = yloc;
1514 	} else if (fSelection == 2) {
1515 		xloc = fCrossHair2.x;
1516 		yloc = fCrossHair2.y;
1517 		BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
1518 		fCrossHair2.x = xloc;
1519 		fCrossHair2.y = yloc;
1520 	}
1521 
1522 	fNeedToUpdate = true;
1523 	Invalidate();
1524 }
1525 
1526 
1527 void
1528 TMagnify::MoveSelectionTo(int32 x, int32 y)
1529 {
1530 	if (!fShowSelection)
1531 		return;
1532 
1533 	int32 xCount, yCount;
1534 	PixelCount(&xCount, &yCount);
1535 	if (x >= xCount)
1536 		x = 0;
1537 	if (y >= yCount)
1538 		y = 0;
1539 
1540 	if (fSelection == 0) {
1541 		fSelectionLoc.x = x;
1542 		fSelectionLoc.y = y;
1543 	} else if (fSelection == 1) {
1544 		fCrossHair1.x = x;
1545 		fCrossHair1.y = y;
1546 	} else if (fSelection == 2) {
1547 		fCrossHair2.x = x;
1548 		fCrossHair2.y = y;
1549 	}
1550 
1551 	fNeedToUpdate = true;
1552 	Invalidate(); //Draw(Bounds());
1553 }
1554 
1555 
1556 void
1557 TMagnify::ShowSelection()
1558 {
1559 }
1560 
1561 
1562 short
1563 TMagnify::Selection()
1564 {
1565 	return fSelection;
1566 }
1567 
1568 
1569 bool
1570 TMagnify::SelectionIsShowing()
1571 {
1572 	return fShowSelection;
1573 }
1574 
1575 
1576 void
1577 TMagnify::SelectionLoc(float* x, float* y)
1578 {
1579 	*x = fSelectionLoc.x;
1580 	*y = fSelectionLoc.y;
1581 }
1582 
1583 
1584 void
1585 TMagnify::SetSelectionLoc(float x, float y)
1586 {
1587 	fSelectionLoc.x = x;
1588 	fSelectionLoc.y = y;
1589 }
1590 
1591 
1592 rgb_color
1593 TMagnify::SelectionColor()
1594 {
1595 	return fImageView->ColorAtSelection();
1596 }
1597 
1598 
1599 void
1600 TMagnify::CrossHair1Loc(float* x, float* y)
1601 {
1602 	*x = fCrossHair1.x;
1603 	*y = fCrossHair1.y;
1604 }
1605 
1606 
1607 void
1608 TMagnify::CrossHair2Loc(float* x, float* y)
1609 {
1610 	*x = fCrossHair2.x;
1611 	*y = fCrossHair2.y;
1612 }
1613 
1614 
1615 BPoint
1616 TMagnify::CrossHair1Loc()
1617 {
1618 	return fCrossHair1;
1619 }
1620 
1621 
1622 BPoint
1623 TMagnify::CrossHair2Loc()
1624 {
1625 	return fCrossHair2;
1626 }
1627 
1628 
1629 void
1630 TMagnify::NudgeMouse(float x, float y)
1631 {
1632 	BPoint loc;
1633 	uint32 button;
1634 
1635 	GetMouse(&loc, &button);
1636 	ConvertToScreen(&loc);
1637 	loc.x += x;
1638 	loc.y += y;
1639 
1640 	set_mouse_position((int32)loc.x, (int32)loc.y);
1641 }
1642 
1643 
1644 void
1645 TMagnify::WindowActivated(bool active)
1646 {
1647 	if (active)
1648 		MakeFocus();
1649 }
1650 
1651 
1652 status_t
1653 TMagnify::MagnifyTask(void *arg)
1654 {
1655 	TMagnify* view = (TMagnify*)arg;
1656 
1657 	// static data members can't access members, methods without
1658 	// a pointer to an instance of the class
1659 	TWindow* window = (TWindow*)view->Window();
1660 
1661 	while (true) {
1662 		if (window->Lock()) {
1663 			if (view->NeedToUpdate() || view->Active())
1664 				view->Update(view->NeedToUpdate());
1665 
1666 			window->Unlock();
1667 		}
1668 		snooze(35000);
1669 	}
1670 
1671 	return B_NO_ERROR;
1672 }
1673 
1674 
1675 void
1676 TMagnify::Update(bool force)
1677 {
1678 	BPoint loc;
1679 	uint32 button;
1680 	static long counter = 0;
1681 
1682 	if (!fStickCoordinates) {
1683 		GetMouse(&loc, &button);
1684 		ConvertToScreen(&loc);
1685 	} else
1686 		loc = fLastLoc;
1687 
1688 	if (force || fLastLoc != loc || counter++ % 35 == 0) {
1689 		if (fImageView->CreateImage(loc, force))
1690 			Invalidate();
1691 
1692 		counter = 0;
1693 		if (force)
1694 			SetUpdate(false);
1695 	}
1696 	fLastLoc = loc;
1697 }
1698 
1699 
1700 bool
1701 TMagnify::NeedToUpdate()
1702 {
1703 	return fNeedToUpdate;
1704 }
1705 
1706 
1707 void
1708 TMagnify::SetUpdate(bool s)
1709 {
1710 	fNeedToUpdate = s;
1711 }
1712 
1713 
1714 void
1715 TMagnify::CopyImage()
1716 {
1717 	StartSave();
1718 	be_clipboard->Lock();
1719 	be_clipboard->Clear();
1720 
1721 	BMessage *message = be_clipboard->Data();
1722 	if (!message) {
1723 		puts(B_TRANSLATE_CONTEXT("no clip msg",
1724 			"In console, when clipboard is empty after clicking Copy image"));
1725 		return;
1726 	}
1727 
1728 	BMessage *embeddedBitmap = new BMessage();
1729 	(fImageView->Bitmap())->Archive(embeddedBitmap,false);
1730 	status_t err = message->AddMessage(kBitmapMimeType, embeddedBitmap);
1731 	if (err == B_OK)
1732 		err = message->AddRect("rect", fImageView->Bitmap()->Bounds());
1733 	if (err == B_OK)
1734 		be_clipboard->Commit();
1735 
1736 	be_clipboard->Unlock();
1737 	EndSave();
1738 }
1739 
1740 
1741 void
1742 TMagnify::AddCrossHair()
1743 {
1744 	if (fShowCrossHair1 && fShowCrossHair2)
1745 		return;
1746 
1747 	int32 w, h;
1748 	PixelCount(&w, &h);
1749 
1750 	if (fShowCrossHair1) {
1751 		fSelection = 2;
1752 		fShowCrossHair2 = true;
1753 		fCrossHair2.x = fCrossHair1.x + 2;
1754 		if (fCrossHair2.x >= w)
1755 			fCrossHair2.x = 0;
1756 		fCrossHair2.y = fCrossHair1.y + 2;
1757 		if (fCrossHair2.y >= h)
1758 			fCrossHair2.y = 0;
1759 	} else {
1760 		fSelection = 1;
1761 		fShowCrossHair1 = true;
1762 		fCrossHair1.x = fSelectionLoc.x + 2;
1763 		if (fCrossHair1.x >= w)
1764 			fCrossHair1.x = 0;
1765 		fCrossHair1.y = fSelectionLoc.y + 2;
1766 		if (fCrossHair1.y >= h)
1767 			fCrossHair1.y = 0;
1768 	}
1769 	Invalidate();
1770 }
1771 
1772 
1773 void
1774 TMagnify::RemoveCrossHair()
1775 {
1776 	if (!fShowCrossHair1 && !fShowCrossHair2)
1777 		return;
1778 
1779 	if (fShowCrossHair2) {
1780 		fSelection = 1;
1781 		fShowCrossHair2 = false;
1782 	} else if (fShowCrossHair1) {
1783 		fSelection = 0;
1784 		fShowCrossHair1 = false;
1785 	}
1786 	Invalidate();
1787 }
1788 
1789 
1790 void
1791 TMagnify::SetCrossHairsShowing(bool ch1, bool ch2)
1792 {
1793 	fShowCrossHair1 = ch1;
1794 	fShowCrossHair2 = ch2;
1795 }
1796 
1797 
1798 void
1799 TMagnify::CrossHairsShowing(bool* ch1, bool* ch2)
1800 {
1801 	*ch1 = fShowCrossHair1;
1802 	*ch2 = fShowCrossHair2;
1803 }
1804 
1805 
1806 void
1807 TMagnify::MakeActive(bool s)
1808 {
1809 	fActive = s;
1810 }
1811 
1812 
1813 void
1814 TMagnify::MakeSticked(bool s)
1815 {
1816 	fStickCoordinates = s;
1817 }
1818 
1819 
1820 void
1821 TMagnify::PixelCount(int32* width, int32* height)
1822 {
1823 	fParent->PixelCount(width, height);
1824 }
1825 
1826 
1827 int32
1828 TMagnify::PixelSize()
1829 {
1830 	return fParent->PixelSize();
1831 }
1832 
1833 
1834 bool
1835 TMagnify::ShowGrid()
1836 {
1837 	return fParent->ShowGrid();
1838 }
1839 
1840 
1841 void
1842 TMagnify::StartSave()
1843 {
1844 	fImageFrozenOnSave = Active();
1845 	if (fImageFrozenOnSave)
1846 		MakeActive(false);
1847 }
1848 
1849 
1850 void
1851 TMagnify::EndSave()
1852 {
1853 	if (fImageFrozenOnSave)
1854 		MakeActive(true);
1855 }
1856 
1857 
1858 void
1859 TMagnify::SaveImage(entry_ref* ref, char* name)
1860 {
1861 	// create a new file
1862 	BFile file;
1863 	BDirectory parentDir(ref);
1864 	parentDir.CreateFile(name, &file);
1865 
1866 	// Write the screenshot bitmap to the file
1867 	BBitmapStream stream(fImageView->Bitmap());
1868 	BTranslatorRoster* roster = BTranslatorRoster::Default();
1869 	roster->Translate(&stream, NULL, NULL, &file, B_PNG_FORMAT,
1870 		B_TRANSLATOR_BITMAP);
1871 
1872 	BBitmap* bitmap;
1873 	stream.DetachBitmap(&bitmap);
1874 		// The stream takes over ownership of the bitmap
1875 
1876 	// unfreeze the image, image was frozen before invoke of FilePanel
1877 	EndSave();
1878 }
1879 
1880 //	#pragma mark -
1881 
1882 
1883 TOSMagnify::TOSMagnify(BRect r, TMagnify* parent, color_space space)
1884 	: BView(r, "ImageView", B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS),
1885 		fColorSpace(space), fParent(parent)
1886 {
1887 	switch (space) {
1888 		case B_CMAP8:
1889 			fBytesPerPixel = 1;
1890 			break;
1891 		case B_RGB15:
1892 		case B_RGBA15:
1893 		case B_RGB15_BIG:
1894 		case B_RGBA15_BIG:
1895 		case B_RGB16:
1896 		case B_RGB16_BIG:
1897 			fBytesPerPixel = 2;
1898 			break;
1899 		case B_RGB24:
1900 			fBytesPerPixel = 3;
1901 			break;
1902 		case B_RGB32:
1903 		case B_RGBA32:
1904 		case B_RGB32_BIG:
1905 		case B_RGBA32_BIG:
1906 			fBytesPerPixel = 4;
1907 			break;
1908 		default:
1909 			// uh, oh -- a color space we don't support
1910 			fprintf(stderr, "Tried to run in an unsupported color space; exiting\n");
1911 			exit(1);
1912 			break;
1913 	}
1914 
1915 	fPixel = NULL;
1916 	fBitmap = NULL;
1917 	fOldBits = NULL;
1918 	InitObject();
1919 }
1920 
1921 
1922 TOSMagnify::~TOSMagnify()
1923 {
1924 	delete fPixel;
1925 	delete fBitmap;
1926 	free(fOldBits);
1927 }
1928 
1929 
1930 void
1931 TOSMagnify::SetSpace(color_space space)
1932 {
1933 	fColorSpace = space;
1934 	InitObject();
1935 };
1936 
1937 
1938 void
1939 TOSMagnify::InitObject()
1940 {
1941 	int32 w, h;
1942 	fParent->PixelCount(&w, &h);
1943 
1944 	delete fBitmap;
1945 	BRect bitsRect(0, 0, w-1, h-1);
1946 	fBitmap = new BBitmap(bitsRect, fColorSpace);
1947 
1948 	free(fOldBits);
1949 	fOldBits = (char*)malloc(fBitmap->BitsLength());
1950 
1951 	if (!fPixel) {
1952 #if B_HOST_IS_BENDIAN
1953 		fPixel = new BBitmap(BRect(0,0,0,0), B_RGBA32_BIG, true);
1954 #else
1955 		fPixel = new BBitmap(BRect(0,0,0,0), B_RGBA32, true);
1956 #endif
1957 		fPixelView = new BView(BRect(0,0,0,0), NULL, 0, 0);
1958 		fPixel->Lock();
1959 		fPixel->AddChild(fPixelView);
1960 		fPixel->Unlock();
1961 	}
1962 }
1963 
1964 
1965 void
1966 TOSMagnify::FrameResized(float width, float height)
1967 {
1968 	BView::FrameResized(width, height);
1969 	InitObject();
1970 }
1971 
1972 
1973 void
1974 TOSMagnify::Resize(int32 width, int32 height)
1975 {
1976 	ResizeTo(width, height);
1977 	InitObject();
1978 }
1979 
1980 
1981 bool
1982 TOSMagnify::CreateImage(BPoint mouseLoc, bool force)
1983 {
1984 	bool created = false;
1985 	if (Window() && Window()->Lock()) {
1986 		int32 width, height;
1987 		fParent->PixelCount(&width, &height);
1988 		int32 pixelSize = fParent->PixelSize();
1989 
1990 		BRect srcRect(0, 0, width - 1, height - 1);
1991 		srcRect.OffsetBy(mouseLoc.x - (width / 2),
1992 			mouseLoc.y - (height / 2));
1993 
1994 		if (force || CopyScreenRect(srcRect)) {
1995 			srcRect.OffsetTo(BPoint(0, 0));
1996 			BRect destRect(Bounds());
1997 
1998 			DrawBitmap(fBitmap, srcRect, destRect);
1999 
2000 			DrawGrid(width, height, destRect, pixelSize);
2001 			DrawSelection();
2002 
2003 			Sync();
2004 			created = true;
2005 		}
2006 		Window()->Unlock();
2007 	} else
2008 		puts("window problem");
2009 
2010 	return created;
2011 }
2012 
2013 
2014 bool
2015 TOSMagnify::CopyScreenRect(BRect srcRect)
2016 {
2017 	// constrain src rect to legal screen rect
2018 	BScreen screen(Window());
2019 	BRect scrnframe = screen.Frame();
2020 
2021 	if (srcRect.right > scrnframe.right)
2022 		srcRect.OffsetTo(scrnframe.right - srcRect.Width(), srcRect.top);
2023 	if (srcRect.top < 0)
2024 		srcRect.OffsetTo(srcRect.left, 0);
2025 
2026 	if (srcRect.bottom > scrnframe.bottom)
2027 		srcRect.OffsetTo(srcRect.left, scrnframe.bottom - srcRect.Height());
2028 	if (srcRect.left < 0)
2029 		srcRect.OffsetTo(0, srcRect.top);
2030 
2031 	// save a copy of the bits for comparison later
2032 	memcpy(fOldBits, fBitmap->Bits(), fBitmap->BitsLength());
2033 
2034 	screen.ReadBitmap(fBitmap, false, &srcRect);
2035 
2036 	// let caller know whether bits have actually changed
2037 	return memcmp(fBitmap->Bits(), fOldBits, fBitmap->BitsLength()) != 0;
2038 }
2039 
2040 
2041 void
2042 TOSMagnify::DrawGrid(int32 width, int32 height, BRect destRect, int32 pixelSize)
2043 {
2044 	// draw grid
2045 	if (fParent->ShowGrid() && fParent->PixelSize() > 2) {
2046 		BeginLineArray(width * height);
2047 
2048 		// horizontal lines
2049 		for (int32 i = pixelSize; i < (height * pixelSize); i += pixelSize)
2050 			AddLine(BPoint(0, i), BPoint(destRect.right, i), kGridGray);
2051 
2052 		// vertical lines
2053 		for (int32 i = pixelSize; i < (width * pixelSize); i += pixelSize)
2054 			AddLine(BPoint(i, 0), BPoint(i, destRect.bottom), kGridGray);
2055 
2056 		EndLineArray();
2057 	}
2058 
2059 	SetHighColor(kGridGray);
2060 	StrokeRect(destRect);
2061 }
2062 
2063 
2064 void
2065 TOSMagnify::DrawSelection()
2066 {
2067 	if (!fParent->SelectionIsShowing())
2068 		return;
2069 
2070 	float x, y;
2071 	int32 pixelSize = fParent->PixelSize();
2072 	int32 squareSize = pixelSize - 2;
2073 
2074 	fParent->SelectionLoc(&x, &y);
2075 	x *= pixelSize; x++;
2076 	y *= pixelSize; y++;
2077 	BRect selRect(x, y, x+squareSize, y+squareSize);
2078 
2079 	short selection = fParent->Selection();
2080 
2081 	PushState();
2082 	SetLowColor(ViewColor());
2083 	SetHighColor(kRedColor);
2084 	StrokeRect(selRect);
2085 	if (selection == 0) {
2086 		StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
2087 		StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
2088 	}
2089 
2090 	bool ch1Showing, ch2Showing;
2091 	fParent->CrossHairsShowing(&ch1Showing, &ch2Showing);
2092 	if (ch1Showing) {
2093 		SetHighColor(kBlueColor);
2094 		fParent->CrossHair1Loc(&x, &y);
2095 		x *= pixelSize; x++;
2096 		y *= pixelSize; y++;
2097 		selRect.Set(x, y,x+squareSize, y+squareSize);
2098 		StrokeRect(selRect);
2099 		BeginLineArray(4);
2100 		AddLine(BPoint(0, y+(squareSize/2)),
2101 			BPoint(x, y+(squareSize/2)), kBlueColor);					//	left
2102 		AddLine(BPoint(x+squareSize,y+(squareSize/2)),
2103 			BPoint(Bounds().Width(), y+(squareSize/2)), kBlueColor);	// right
2104 		AddLine(BPoint(x+(squareSize/2), 0),
2105 			BPoint(x+(squareSize/2), y), kBlueColor);					// top
2106 		AddLine(BPoint(x+(squareSize/2), y+squareSize),
2107 			BPoint(x+(squareSize/2), Bounds().Height()), kBlueColor);	// bottom
2108 		EndLineArray();
2109 		if (selection == 1) {
2110 			StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
2111 			StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
2112 		}
2113 	}
2114 	if (ch2Showing) {
2115 		SetHighColor(kBlueColor);
2116 		fParent->CrossHair2Loc(&x, &y);
2117 		x *= pixelSize; x++;
2118 		y *= pixelSize; y++;
2119 		selRect.Set(x, y,x+squareSize, y+squareSize);
2120 		StrokeRect(selRect);
2121 		BeginLineArray(4);
2122 		AddLine(BPoint(0, y+(squareSize/2)),
2123 			BPoint(x, y+(squareSize/2)), kBlueColor);					//	left
2124 		AddLine(BPoint(x+squareSize,y+(squareSize/2)),
2125 			BPoint(Bounds().Width(), y+(squareSize/2)), kBlueColor);	// right
2126 		AddLine(BPoint(x+(squareSize/2), 0),
2127 			BPoint(x+(squareSize/2), y), kBlueColor);					// top
2128 		AddLine(BPoint(x+(squareSize/2), y+squareSize),
2129 			BPoint(x+(squareSize/2), Bounds().Height()), kBlueColor);	// bottom
2130 		EndLineArray();
2131 		if (selection == 2) {
2132 			StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
2133 			StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
2134 		}
2135 	}
2136 
2137 	PopState();
2138 }
2139 
2140 
2141 rgb_color
2142 TOSMagnify::ColorAtSelection()
2143 {
2144 	float x, y;
2145 	fParent->SelectionLoc(&x, &y);
2146 	BRect srcRect(x, y, x, y);
2147 	BRect dstRect(0, 0, 0, 0);
2148 	fPixel->Lock();
2149 	fPixelView->DrawBitmap(fBitmap, srcRect, dstRect);
2150 	fPixelView->Sync();
2151 	fPixel->Unlock();
2152 
2153 	uint32 pixel = *((uint32*)fPixel->Bits());
2154 	rgb_color c;
2155 	c.alpha = pixel >> 24;
2156 	c.red = (pixel >> 16) & 0xFF;
2157 	c.green = (pixel >> 8) & 0xFF;
2158 	c.blue = pixel & 0xFF;
2159 
2160 	return c;
2161 }
2162 
2163 
2164 //	#pragma mark -
2165 
2166 
2167 int
2168 main(int argc, char* argv[])
2169 {
2170 	int32 pixelCount = -1;
2171 
2172 	if (argc > 2) {
2173 		puts(B_TRANSLATE_CONTEXT(
2174 			"usage: magnify [size] (magnify size * size pixels)",
2175 			"Console"));
2176 		exit(1);
2177 	} else {
2178 		if (argc == 2) {
2179 			pixelCount = abs(atoi(argv[1]));
2180 
2181 			if ((pixelCount > 100) || (pixelCount < 4)) {
2182 				puts(B_TRANSLATE_CONTEXT(
2183 					"usage: magnify [size] (magnify size * size pixels)",
2184 					"Console"));
2185 				puts(B_TRANSLATE_CONTEXT(
2186 					"  size must be > 4 and a multiple of 4",
2187 					"Console"));
2188 				exit(1);
2189 			}
2190 
2191 			if (pixelCount % 4) {
2192 				puts(B_TRANSLATE_CONTEXT(
2193 					"magnify: size must be a multiple of 4",
2194 					"Console"));
2195 				exit(1);
2196 			}
2197 		}
2198 	}
2199 
2200 	TApp app(pixelCount);
2201 	app.Run();
2202 	return 0;
2203 }
2204