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