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