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