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