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