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