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