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