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