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