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 BWindow::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 BWindow::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_COMMENT("%width �� %height  @ {0, plural, "
1092		"one{# pixel/pixel} other{# pixels/pixel}}",
1093		"The '��' is the Unicode multiplication sign U+00D7"));
1094
1095	BString dimensionsInfo;
1096	format.Format(dimensionsInfo, pixelSize);
1097
1098	BString rep;
1099	rep << hPixelCount;
1100	dimensionsInfo.ReplaceAll("%width", rep);
1101	rep = "";
1102	rep << vPixelCount;
1103	dimensionsInfo.ReplaceAll("%height", rep);
1104
1105	invalRect.Set(10, 5, 10 + StringWidth(fInfoStr), fFontHeight+7);
1106	SetHighColor(ViewColor());
1107	FillRect(invalRect);
1108	SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1109	strcpy(fInfoStr, dimensionsInfo);
1110	if (fInfoTextVisible)
1111		DrawString(fInfoStr);
1112
1113	rgb_color color = { 0, 0, 0, 255 };
1114	if (fMagView)
1115		color = fMagView->SelectionColor();
1116	char str[64];
1117	snprintf(str, sizeof(str), "R: %i G: %i B: %i (#%02x%02x%02x)",
1118		color.red, color.green, color.blue, color.red, color.green, color.blue);
1119
1120	MovePenTo(15 + fPopUp->Bounds().Width(), fFontHeight*2+5);
1121	invalRect.Set(10, fFontHeight+7, 10 + StringWidth(fRGBStr), fFontHeight*2+7);
1122	SetHighColor(ViewColor());
1123	FillRect(invalRect);
1124	SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1125	strcpy(fRGBStr,str);
1126	if (fInfoTextVisible)
1127		DrawString(fRGBStr);
1128
1129	bool ch1Showing, ch2Showing;
1130	dynamic_cast<TWindow*>(Window())->CrossHairsShowing(&ch1Showing, &ch2Showing);
1131
1132	if (fMagView) {
1133		BPoint pt1(fMagView->CrossHair1Loc());
1134		BPoint pt2(fMagView->CrossHair2Loc());
1135
1136		float h = Bounds().Height();
1137		if (ch2Showing) {
1138			MovePenTo(10, h-12);
1139			sprintf(str, "2) x: %" B_PRIi32 " y: %" B_PRIi32 "   y: %d",
1140				(int32)pt2.x, (int32)pt2.y, abs((int)(pt1.y - pt2.y)));
1141			invalRect.Set(10, h-12-fFontHeight, 10 + StringWidth(fCH2Str), h-10);
1142			SetHighColor(ViewColor());
1143			FillRect(invalRect);
1144			SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1145			strcpy(fCH2Str,str);
1146			if (fInfoTextVisible)
1147				DrawString(fCH2Str);
1148		}
1149
1150		if (ch1Showing && ch2Showing) {
1151			MovePenTo(10, h-10-fFontHeight-2);
1152			sprintf(str, "1) x: %" B_PRIi32 "  y: %" B_PRIi32 "   x: %d",
1153				(int32)pt1.x, (int32)pt1.y, abs((int)(pt1.x - pt2.x)));
1154			invalRect.Set(10, h-10-2*fFontHeight-2, 10 + StringWidth(fCH1Str), h-10-fFontHeight);
1155			SetHighColor(ViewColor());
1156			FillRect(invalRect);
1157			SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1158			strcpy(fCH1Str,str);
1159			if (fInfoTextVisible)
1160				DrawString(fCH1Str);
1161		} else if (ch1Showing) {
1162			MovePenTo(10, h-10);
1163			sprintf(str, "x: %" B_PRIi32 "  y: %" B_PRIi32, (int32)pt1.x, (int32)pt1.y);
1164			invalRect.Set(10, h-10-fFontHeight, 10 + StringWidth(fCH1Str), h-8);
1165			SetHighColor(ViewColor());
1166			FillRect(invalRect);
1167			SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1168			strcpy(fCH1Str,str);
1169			if (fInfoTextVisible)
1170				DrawString(fCH1Str);
1171		}
1172	}
1173
1174	PopState();
1175}
1176
1177
1178void
1179TInfoView::FrameResized(float width, float height)
1180{
1181	BBox::FrameResized(width, height);
1182}
1183
1184
1185void
1186TInfoView::AddMenu()
1187{
1188	fMenu = new TMenu(dynamic_cast<TWindow*>(Window()), "");
1189	BuildInfoMenu(fMenu);
1190
1191	BRect r(9, 11, 22, 27);
1192	fPopUp = new BMenuField( r, "region menu", NULL, fMenu, true,
1193		B_FOLLOW_LEFT | B_FOLLOW_TOP);
1194	AddChild(fPopUp);
1195}
1196
1197
1198void
1199TInfoView::SetMagView(TMagnify* magView)
1200{
1201	fMagView = magView;
1202}
1203
1204
1205//	#pragma mark -
1206
1207
1208TMenu::TMenu(TWindow *mainWindow, const char *title, menu_layout layout)
1209	: BMenu(title, layout),
1210	fMainWindow(mainWindow)
1211{
1212}
1213
1214
1215TMenu::~TMenu()
1216{
1217}
1218
1219
1220void
1221TMenu::AttachedToWindow()
1222{
1223	UpdateInfoMenu(this, fMainWindow);
1224
1225	BMenu::AttachedToWindow();
1226}
1227
1228
1229void
1230TInfoView::GetPreferredSize(float* _width, float* _height)
1231{
1232	if (_width) {
1233		float str1Width = StringWidth(fCH1Str)
1234			+ StringWidth(fCH2Str)
1235			+ StringWidth(fRGBStr)
1236			+ 30;
1237		float str2Width = StringWidth(fInfoStr) + 30;
1238		*_width = str1Width > str2Width ? str1Width : str2Width;
1239	}
1240
1241	if (_height)
1242		*_height = fFontHeight * 2 + 10;
1243}
1244
1245
1246bool
1247TInfoView::IsInfoTextVisible()
1248{
1249	return fInfoTextVisible;
1250}
1251
1252
1253void
1254TInfoView::SetInfoTextVisible(bool visible)
1255{
1256	fInfoTextVisible = visible;
1257	Draw(Bounds());
1258}
1259
1260
1261//	#pragma mark -
1262
1263
1264TMagnify::TMagnify(BRect r, TWindow* parent)
1265	: BView(r, "MagView", B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS),
1266	fNeedToUpdate(true),
1267	fThread(-1),
1268	fActive(true),
1269	fImageBuf(NULL),
1270	fImageView(NULL),
1271	fLastLoc(-1, -1),
1272	fSelection(-1),
1273	fShowSelection(false),
1274	fSelectionLoc(0, 0),
1275	fShowCrossHair1(false),
1276	fCrossHair1(-1, -1),
1277	fShowCrossHair2(false),
1278	fCrossHair2(-1, -1),
1279	fParent(parent),
1280	fStickCoordinates(false)
1281{
1282}
1283
1284
1285TMagnify::~TMagnify()
1286{
1287	kill_thread(fThread);
1288	delete fImageBuf;
1289}
1290
1291
1292void
1293TMagnify::AttachedToWindow()
1294{
1295	int32 width, height;
1296	fParent->PixelCount(&width, &height);
1297	InitBuffers(width, height, fParent->PixelSize(), fParent->ShowGrid());
1298
1299	fThread = spawn_thread(TMagnify::MagnifyTask, "MagnifyTask",
1300		B_NORMAL_PRIORITY, this);
1301
1302	resume_thread(fThread);
1303
1304	SetViewColor(B_TRANSPARENT_32_BIT);
1305	MakeFocus();
1306}
1307
1308
1309void
1310TMagnify::InitBuffers(int32 hPixelCount, int32 vPixelCount,
1311	int32 pixelSize, bool showGrid)
1312{
1313	color_space colorSpace = BScreen(Window()).ColorSpace();
1314
1315	BRect r(0, 0, (pixelSize * hPixelCount)-1, (pixelSize * vPixelCount)-1);
1316	if (Bounds().Width() != r.Width() || Bounds().Height() != r.Height())
1317		ResizeTo(r.Width(), r.Height());
1318
1319	if (fImageView) {
1320		fImageBuf->Lock();
1321		fImageView->RemoveSelf();
1322		fImageBuf->Unlock();
1323
1324		fImageView->Resize((int32)r.Width(), (int32)r.Height());
1325		fImageView->SetSpace(colorSpace);
1326	} else
1327		fImageView = new TOSMagnify(r, this, colorSpace);
1328
1329	delete fImageBuf;
1330	fImageBuf = new BBitmap(r, colorSpace, true);
1331	fImageBuf->Lock();
1332	fImageBuf->AddChild(fImageView);
1333	fImageBuf->Unlock();
1334}
1335
1336
1337void
1338TMagnify::Draw(BRect)
1339{
1340	BRect bounds(Bounds());
1341	DrawBitmap(fImageBuf, bounds, bounds);
1342	static_cast<TWindow*>(Window())->UpdateInfo();
1343}
1344
1345
1346void
1347TMagnify::KeyDown(const char *key, int32 numBytes)
1348{
1349	if (!fShowSelection)
1350		BView::KeyDown(key, numBytes);
1351
1352	switch (key[0]) {
1353		case B_TAB:
1354			if (fShowCrossHair1) {
1355				fSelection++;
1356
1357				if (fShowCrossHair2) {
1358					if (fSelection > 2)
1359						fSelection = 0;
1360				} else if (fShowCrossHair1) {
1361					if (fSelection > 1)
1362						fSelection = 0;
1363				}
1364				fNeedToUpdate = true;
1365				Invalidate();
1366			}
1367			break;
1368
1369		case B_LEFT_ARROW:
1370			MoveSelection(-1,0);
1371			break;
1372		case B_RIGHT_ARROW:
1373			MoveSelection(1,0);
1374			break;
1375		case B_UP_ARROW:
1376			MoveSelection(0,-1);
1377			break;
1378		case B_DOWN_ARROW:
1379			MoveSelection(0,1);
1380			break;
1381
1382		default:
1383			BView::KeyDown(key,numBytes);
1384			break;
1385	}
1386}
1387
1388
1389void
1390TMagnify::FrameResized(float newW, float newH)
1391{
1392	int32 w, h;
1393	PixelCount(&w, &h);
1394
1395	if (fSelectionLoc.x >= w)
1396		fSelectionLoc.x = 0;
1397	if (fSelectionLoc.y >= h)
1398		fSelectionLoc.y = 0;
1399
1400	if (fShowCrossHair1) {
1401		if (fCrossHair1.x >= w) {
1402			fCrossHair1.x = fSelectionLoc.x + 2;
1403			if (fCrossHair1.x >= w)
1404				fCrossHair1.x = 0;
1405		}
1406		if (fCrossHair1.y >= h) {
1407			fCrossHair1.y = fSelectionLoc.y + 2;
1408			if (fCrossHair1.y >= h)
1409				fCrossHair1.y = 0;
1410		}
1411
1412		if (fShowCrossHair2) {
1413			if (fCrossHair2.x >= w) {
1414				fCrossHair2.x = fCrossHair1.x + 2;
1415				if (fCrossHair2.x >= w)
1416					fCrossHair2.x = 0;
1417			}
1418			if (fCrossHair2.y >= h) {
1419				fCrossHair2.y = fCrossHair1.y + 2;
1420				if (fCrossHair2.y >= h)
1421					fCrossHair2.y = 0;
1422			}
1423		}
1424	}
1425}
1426
1427
1428void
1429TMagnify::MouseDown(BPoint where)
1430{
1431	BMessage *currentMsg = Window()->CurrentMessage();
1432	if (currentMsg->what == B_MOUSE_DOWN) {
1433		uint32 buttons = 0;
1434		currentMsg->FindInt32("buttons", (int32 *)&buttons);
1435
1436		uint32 modifiers = 0;
1437		currentMsg->FindInt32("modifiers", (int32 *)&modifiers);
1438
1439		if ((buttons & B_SECONDARY_MOUSE_BUTTON) || (modifiers & B_CONTROL_KEY)) {
1440			// secondary button was clicked or control key was down, show menu and return
1441
1442			BPopUpMenu *menu = new BPopUpMenu(B_TRANSLATE("Info"), false, false);
1443			menu->SetFont(be_plain_font);
1444			BuildInfoMenu(menu);
1445			UpdateInfoMenu(menu, dynamic_cast<TWindow*>(Window()));
1446			BMenuItem *selected = menu->Go(ConvertToScreen(where));
1447			if (selected)
1448				Window()->PostMessage(selected->Message()->what);
1449			delete menu;
1450			return;
1451		}
1452
1453		// add a mousedown looper here
1454
1455		int32 pixelSize = PixelSize();
1456		float x = where.x / pixelSize;
1457		float y = where.y / pixelSize;
1458
1459		MoveSelectionTo(x, y);
1460
1461		// draw the frozen image
1462		// update the info region
1463
1464		fNeedToUpdate = true;
1465		Invalidate();
1466	}
1467}
1468
1469
1470void
1471TMagnify::ScreenChanged(BRect, color_space)
1472{
1473	int32 width, height;
1474	fParent->PixelCount(&width, &height);
1475	InitBuffers(width, height, fParent->PixelSize(), fParent->ShowGrid());
1476}
1477
1478
1479void
1480TMagnify::SetSelection(bool state)
1481{
1482	if (fShowSelection == state)
1483		return;
1484
1485	fShowSelection = state;
1486	fSelection = 0;
1487	Invalidate();
1488}
1489
1490
1491void
1492TMagnify::MoveSelection(int32 x, int32 y)
1493{
1494	if (!fShowSelection)
1495		return;
1496
1497	int32 xCount, yCount;
1498	PixelCount(&xCount, &yCount);
1499
1500	float xloc, yloc;
1501	if (fSelection == 0) {
1502		xloc = fSelectionLoc.x;
1503		yloc = fSelectionLoc.y;
1504		BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
1505		fSelectionLoc.x = xloc;
1506		fSelectionLoc.y = yloc;
1507	} else if (fSelection == 1) {
1508		xloc = fCrossHair1.x;
1509		yloc = fCrossHair1.y;
1510		BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
1511		fCrossHair1.x = xloc;
1512		fCrossHair1.y = yloc;
1513	} else if (fSelection == 2) {
1514		xloc = fCrossHair2.x;
1515		yloc = fCrossHair2.y;
1516		BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
1517		fCrossHair2.x = xloc;
1518		fCrossHair2.y = yloc;
1519	}
1520
1521	fNeedToUpdate = true;
1522	Invalidate();
1523}
1524
1525
1526void
1527TMagnify::MoveSelectionTo(int32 x, int32 y)
1528{
1529	if (!fShowSelection)
1530		return;
1531
1532	int32 xCount, yCount;
1533	PixelCount(&xCount, &yCount);
1534	if (x >= xCount)
1535		x = 0;
1536	if (y >= yCount)
1537		y = 0;
1538
1539	if (fSelection == 0) {
1540		fSelectionLoc.x = x;
1541		fSelectionLoc.y = y;
1542	} else if (fSelection == 1) {
1543		fCrossHair1.x = x;
1544		fCrossHair1.y = y;
1545	} else if (fSelection == 2) {
1546		fCrossHair2.x = x;
1547		fCrossHair2.y = y;
1548	}
1549
1550	fNeedToUpdate = true;
1551	Invalidate(); //Draw(Bounds());
1552}
1553
1554
1555void
1556TMagnify::ShowSelection()
1557{
1558}
1559
1560
1561short
1562TMagnify::Selection()
1563{
1564	return fSelection;
1565}
1566
1567
1568bool
1569TMagnify::SelectionIsShowing()
1570{
1571	return fShowSelection;
1572}
1573
1574
1575void
1576TMagnify::SelectionLoc(float* x, float* y)
1577{
1578	*x = fSelectionLoc.x;
1579	*y = fSelectionLoc.y;
1580}
1581
1582
1583void
1584TMagnify::SetSelectionLoc(float x, float y)
1585{
1586	fSelectionLoc.x = x;
1587	fSelectionLoc.y = y;
1588}
1589
1590
1591rgb_color
1592TMagnify::SelectionColor()
1593{
1594	return fImageView->ColorAtSelection();
1595}
1596
1597
1598void
1599TMagnify::CrossHair1Loc(float* x, float* y)
1600{
1601	*x = fCrossHair1.x;
1602	*y = fCrossHair1.y;
1603}
1604
1605
1606void
1607TMagnify::CrossHair2Loc(float* x, float* y)
1608{
1609	*x = fCrossHair2.x;
1610	*y = fCrossHair2.y;
1611}
1612
1613
1614BPoint
1615TMagnify::CrossHair1Loc()
1616{
1617	return fCrossHair1;
1618}
1619
1620
1621BPoint
1622TMagnify::CrossHair2Loc()
1623{
1624	return fCrossHair2;
1625}
1626
1627
1628void
1629TMagnify::NudgeMouse(float x, float y)
1630{
1631	BPoint loc;
1632	uint32 button;
1633
1634	GetMouse(&loc, &button);
1635	ConvertToScreen(&loc);
1636	loc.x += x;
1637	loc.y += y;
1638
1639	set_mouse_position((int32)loc.x, (int32)loc.y);
1640}
1641
1642
1643void
1644TMagnify::WindowActivated(bool active)
1645{
1646	if (active)
1647		MakeFocus();
1648}
1649
1650
1651status_t
1652TMagnify::MagnifyTask(void *arg)
1653{
1654	TMagnify* view = (TMagnify*)arg;
1655
1656	// static data members can't access members, methods without
1657	// a pointer to an instance of the class
1658	TWindow* window = (TWindow*)view->Window();
1659
1660	while (true) {
1661		if (window->Lock()) {
1662			if (view->NeedToUpdate() || view->Active())
1663				view->Update(view->NeedToUpdate());
1664
1665			window->Unlock();
1666		}
1667		snooze(35000);
1668	}
1669
1670	return B_NO_ERROR;
1671}
1672
1673
1674void
1675TMagnify::Update(bool force)
1676{
1677	BPoint loc;
1678	uint32 button;
1679	static long counter = 0;
1680
1681	if (!fStickCoordinates) {
1682		GetMouse(&loc, &button);
1683		ConvertToScreen(&loc);
1684	} else
1685		loc = fLastLoc;
1686
1687	if (force || fLastLoc != loc || counter++ % 35 == 0) {
1688		if (fImageView->CreateImage(loc, force))
1689			Invalidate();
1690
1691		counter = 0;
1692		if (force)
1693			SetUpdate(false);
1694	}
1695	fLastLoc = loc;
1696}
1697
1698
1699bool
1700TMagnify::NeedToUpdate()
1701{
1702	return fNeedToUpdate;
1703}
1704
1705
1706void
1707TMagnify::SetUpdate(bool s)
1708{
1709	fNeedToUpdate = s;
1710}
1711
1712
1713void
1714TMagnify::CopyImage()
1715{
1716	StartSave();
1717	be_clipboard->Lock();
1718	be_clipboard->Clear();
1719
1720	BMessage *message = be_clipboard->Data();
1721	if (!message) {
1722		puts(B_TRANSLATE_CONTEXT("no clip msg",
1723			"In console, when clipboard is empty after clicking Copy image"));
1724		return;
1725	}
1726
1727	BMessage *embeddedBitmap = new BMessage();
1728	(fImageView->Bitmap())->Archive(embeddedBitmap,false);
1729	status_t err = message->AddMessage(kBitmapMimeType, embeddedBitmap);
1730	if (err == B_OK)
1731		err = message->AddRect("rect", fImageView->Bitmap()->Bounds());
1732	if (err == B_OK)
1733		be_clipboard->Commit();
1734
1735	be_clipboard->Unlock();
1736	EndSave();
1737}
1738
1739
1740void
1741TMagnify::AddCrossHair()
1742{
1743	if (fShowCrossHair1 && fShowCrossHair2)
1744		return;
1745
1746	int32 w, h;
1747	PixelCount(&w, &h);
1748
1749	if (fShowCrossHair1) {
1750		fSelection = 2;
1751		fShowCrossHair2 = true;
1752		fCrossHair2.x = fCrossHair1.x + 2;
1753		if (fCrossHair2.x >= w)
1754			fCrossHair2.x = 0;
1755		fCrossHair2.y = fCrossHair1.y + 2;
1756		if (fCrossHair2.y >= h)
1757			fCrossHair2.y = 0;
1758	} else {
1759		fSelection = 1;
1760		fShowCrossHair1 = true;
1761		fCrossHair1.x = fSelectionLoc.x + 2;
1762		if (fCrossHair1.x >= w)
1763			fCrossHair1.x = 0;
1764		fCrossHair1.y = fSelectionLoc.y + 2;
1765		if (fCrossHair1.y >= h)
1766			fCrossHair1.y = 0;
1767	}
1768	Invalidate();
1769}
1770
1771
1772void
1773TMagnify::RemoveCrossHair()
1774{
1775	if (!fShowCrossHair1 && !fShowCrossHair2)
1776		return;
1777
1778	if (fShowCrossHair2) {
1779		fSelection = 1;
1780		fShowCrossHair2 = false;
1781	} else if (fShowCrossHair1) {
1782		fSelection = 0;
1783		fShowCrossHair1 = false;
1784	}
1785	Invalidate();
1786}
1787
1788
1789void
1790TMagnify::SetCrossHairsShowing(bool ch1, bool ch2)
1791{
1792	fShowCrossHair1 = ch1;
1793	fShowCrossHair2 = ch2;
1794}
1795
1796
1797void
1798TMagnify::CrossHairsShowing(bool* ch1, bool* ch2)
1799{
1800	*ch1 = fShowCrossHair1;
1801	*ch2 = fShowCrossHair2;
1802}
1803
1804
1805void
1806TMagnify::MakeActive(bool s)
1807{
1808	fActive = s;
1809}
1810
1811
1812void
1813TMagnify::MakeSticked(bool s)
1814{
1815	fStickCoordinates = s;
1816}
1817
1818
1819void
1820TMagnify::PixelCount(int32* width, int32* height)
1821{
1822	fParent->PixelCount(width, height);
1823}
1824
1825
1826int32
1827TMagnify::PixelSize()
1828{
1829	return fParent->PixelSize();
1830}
1831
1832
1833bool
1834TMagnify::ShowGrid()
1835{
1836	return fParent->ShowGrid();
1837}
1838
1839
1840void
1841TMagnify::StartSave()
1842{
1843	fImageFrozenOnSave = Active();
1844	if (fImageFrozenOnSave)
1845		MakeActive(false);
1846}
1847
1848
1849void
1850TMagnify::EndSave()
1851{
1852	if (fImageFrozenOnSave)
1853		MakeActive(true);
1854}
1855
1856
1857void
1858TMagnify::SaveImage(entry_ref* ref, char* name)
1859{
1860	// create a new file
1861	BFile file;
1862	BDirectory parentDir(ref);
1863	parentDir.CreateFile(name, &file);
1864
1865	// Write the screenshot bitmap to the file
1866	BBitmapStream stream(fImageView->Bitmap());
1867	BTranslatorRoster* roster = BTranslatorRoster::Default();
1868	roster->Translate(&stream, NULL, NULL, &file, B_PNG_FORMAT,
1869		B_TRANSLATOR_BITMAP);
1870
1871	BBitmap* bitmap;
1872	stream.DetachBitmap(&bitmap);
1873		// The stream takes over ownership of the bitmap
1874
1875	// unfreeze the image, image was frozen before invoke of FilePanel
1876	EndSave();
1877}
1878
1879//	#pragma mark -
1880
1881
1882TOSMagnify::TOSMagnify(BRect r, TMagnify* parent, color_space space)
1883	: BView(r, "ImageView", B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS),
1884		fColorSpace(space), fParent(parent)
1885{
1886	switch (space) {
1887		case B_CMAP8:
1888			fBytesPerPixel = 1;
1889			break;
1890		case B_RGB15:
1891		case B_RGBA15:
1892		case B_RGB15_BIG:
1893		case B_RGBA15_BIG:
1894		case B_RGB16:
1895		case B_RGB16_BIG:
1896			fBytesPerPixel = 2;
1897			break;
1898		case B_RGB24:
1899			fBytesPerPixel = 3;
1900			break;
1901		case B_RGB32:
1902		case B_RGBA32:
1903		case B_RGB32_BIG:
1904		case B_RGBA32_BIG:
1905			fBytesPerPixel = 4;
1906			break;
1907		default:
1908			// uh, oh -- a color space we don't support
1909			fprintf(stderr, "Tried to run in an unsupported color space; exiting\n");
1910			exit(1);
1911			break;
1912	}
1913
1914	fPixel = NULL;
1915	fBitmap = NULL;
1916	fOldBits = NULL;
1917	InitObject();
1918}
1919
1920
1921TOSMagnify::~TOSMagnify()
1922{
1923	delete fPixel;
1924	delete fBitmap;
1925	free(fOldBits);
1926}
1927
1928
1929void
1930TOSMagnify::SetSpace(color_space space)
1931{
1932	fColorSpace = space;
1933	InitObject();
1934};
1935
1936
1937void
1938TOSMagnify::InitObject()
1939{
1940	int32 w, h;
1941	fParent->PixelCount(&w, &h);
1942
1943	delete fBitmap;
1944	BRect bitsRect(0, 0, w-1, h-1);
1945	fBitmap = new BBitmap(bitsRect, fColorSpace);
1946
1947	free(fOldBits);
1948	fOldBits = (char*)malloc(fBitmap->BitsLength());
1949
1950	if (!fPixel) {
1951#if B_HOST_IS_BENDIAN
1952		fPixel = new BBitmap(BRect(0,0,0,0), B_RGBA32_BIG, true);
1953#else
1954		fPixel = new BBitmap(BRect(0,0,0,0), B_RGBA32, true);
1955#endif
1956		fPixelView = new BView(BRect(0,0,0,0), NULL, 0, 0);
1957		fPixel->Lock();
1958		fPixel->AddChild(fPixelView);
1959		fPixel->Unlock();
1960	}
1961}
1962
1963
1964void
1965TOSMagnify::FrameResized(float width, float height)
1966{
1967	BView::FrameResized(width, height);
1968	InitObject();
1969}
1970
1971
1972void
1973TOSMagnify::Resize(int32 width, int32 height)
1974{
1975	ResizeTo(width, height);
1976	InitObject();
1977}
1978
1979
1980bool
1981TOSMagnify::CreateImage(BPoint mouseLoc, bool force)
1982{
1983	bool created = false;
1984	if (Window() && Window()->Lock()) {
1985		int32 width, height;
1986		fParent->PixelCount(&width, &height);
1987		int32 pixelSize = fParent->PixelSize();
1988
1989		BRect srcRect(0, 0, width - 1, height - 1);
1990		srcRect.OffsetBy(mouseLoc.x - (width / 2),
1991			mouseLoc.y - (height / 2));
1992
1993		if (force || CopyScreenRect(srcRect)) {
1994			srcRect.OffsetTo(BPoint(0, 0));
1995			BRect destRect(Bounds());
1996
1997			DrawBitmap(fBitmap, srcRect, destRect);
1998
1999			DrawGrid(width, height, destRect, pixelSize);
2000			DrawSelection();
2001
2002			Sync();
2003			created = true;
2004		}
2005		Window()->Unlock();
2006	} else
2007		puts("window problem");
2008
2009	return created;
2010}
2011
2012
2013bool
2014TOSMagnify::CopyScreenRect(BRect srcRect)
2015{
2016	// constrain src rect to legal screen rect
2017	BScreen screen(Window());
2018	BRect scrnframe = screen.Frame();
2019
2020	if (srcRect.right > scrnframe.right)
2021		srcRect.OffsetTo(scrnframe.right - srcRect.Width(), srcRect.top);
2022	if (srcRect.top < 0)
2023		srcRect.OffsetTo(srcRect.left, 0);
2024
2025	if (srcRect.bottom > scrnframe.bottom)
2026		srcRect.OffsetTo(srcRect.left, scrnframe.bottom - srcRect.Height());
2027	if (srcRect.left < 0)
2028		srcRect.OffsetTo(0, srcRect.top);
2029
2030	// save a copy of the bits for comparison later
2031	memcpy(fOldBits, fBitmap->Bits(), fBitmap->BitsLength());
2032
2033	screen.ReadBitmap(fBitmap, false, &srcRect);
2034
2035	// let caller know whether bits have actually changed
2036	return memcmp(fBitmap->Bits(), fOldBits, fBitmap->BitsLength()) != 0;
2037}
2038
2039
2040void
2041TOSMagnify::DrawGrid(int32 width, int32 height, BRect destRect, int32 pixelSize)
2042{
2043	// draw grid
2044	if (fParent->ShowGrid() && fParent->PixelSize() > 2) {
2045		BeginLineArray(width * height);
2046
2047		// horizontal lines
2048		for (int32 i = pixelSize; i < (height * pixelSize); i += pixelSize)
2049			AddLine(BPoint(0, i), BPoint(destRect.right, i), kGridGray);
2050
2051		// vertical lines
2052		for (int32 i = pixelSize; i < (width * pixelSize); i += pixelSize)
2053			AddLine(BPoint(i, 0), BPoint(i, destRect.bottom), kGridGray);
2054
2055		EndLineArray();
2056	}
2057
2058	SetHighColor(kGridGray);
2059	StrokeRect(destRect);
2060}
2061
2062
2063void
2064TOSMagnify::DrawSelection()
2065{
2066	if (!fParent->SelectionIsShowing())
2067		return;
2068
2069	float x, y;
2070	int32 pixelSize = fParent->PixelSize();
2071	int32 squareSize = pixelSize - 2;
2072
2073	fParent->SelectionLoc(&x, &y);
2074	x *= pixelSize; x++;
2075	y *= pixelSize; y++;
2076	BRect selRect(x, y, x+squareSize, y+squareSize);
2077
2078	short selection = fParent->Selection();
2079
2080	PushState();
2081	SetLowColor(ViewColor());
2082	SetHighColor(kRedColor);
2083	StrokeRect(selRect);
2084	if (selection == 0) {
2085		StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
2086		StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
2087	}
2088
2089	bool ch1Showing, ch2Showing;
2090	fParent->CrossHairsShowing(&ch1Showing, &ch2Showing);
2091	if (ch1Showing) {
2092		SetHighColor(kBlueColor);
2093		fParent->CrossHair1Loc(&x, &y);
2094		x *= pixelSize; x++;
2095		y *= pixelSize; y++;
2096		selRect.Set(x, y,x+squareSize, y+squareSize);
2097		StrokeRect(selRect);
2098		BeginLineArray(4);
2099		AddLine(BPoint(0, y+(squareSize/2)),
2100			BPoint(x, y+(squareSize/2)), kBlueColor);					//	left
2101		AddLine(BPoint(x+squareSize,y+(squareSize/2)),
2102			BPoint(Bounds().Width(), y+(squareSize/2)), kBlueColor);	// right
2103		AddLine(BPoint(x+(squareSize/2), 0),
2104			BPoint(x+(squareSize/2), y), kBlueColor);					// top
2105		AddLine(BPoint(x+(squareSize/2), y+squareSize),
2106			BPoint(x+(squareSize/2), Bounds().Height()), kBlueColor);	// bottom
2107		EndLineArray();
2108		if (selection == 1) {
2109			StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
2110			StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
2111		}
2112	}
2113	if (ch2Showing) {
2114		SetHighColor(kBlueColor);
2115		fParent->CrossHair2Loc(&x, &y);
2116		x *= pixelSize; x++;
2117		y *= pixelSize; y++;
2118		selRect.Set(x, y,x+squareSize, y+squareSize);
2119		StrokeRect(selRect);
2120		BeginLineArray(4);
2121		AddLine(BPoint(0, y+(squareSize/2)),
2122			BPoint(x, y+(squareSize/2)), kBlueColor);					//	left
2123		AddLine(BPoint(x+squareSize,y+(squareSize/2)),
2124			BPoint(Bounds().Width(), y+(squareSize/2)), kBlueColor);	// right
2125		AddLine(BPoint(x+(squareSize/2), 0),
2126			BPoint(x+(squareSize/2), y), kBlueColor);					// top
2127		AddLine(BPoint(x+(squareSize/2), y+squareSize),
2128			BPoint(x+(squareSize/2), Bounds().Height()), kBlueColor);	// bottom
2129		EndLineArray();
2130		if (selection == 2) {
2131			StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
2132			StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
2133		}
2134	}
2135
2136	PopState();
2137}
2138
2139
2140rgb_color
2141TOSMagnify::ColorAtSelection()
2142{
2143	float x, y;
2144	fParent->SelectionLoc(&x, &y);
2145	BRect srcRect(x, y, x, y);
2146	BRect dstRect(0, 0, 0, 0);
2147	fPixel->Lock();
2148	fPixelView->DrawBitmap(fBitmap, srcRect, dstRect);
2149	fPixelView->Sync();
2150	fPixel->Unlock();
2151
2152	uint32 pixel = *((uint32*)fPixel->Bits());
2153	rgb_color c;
2154	c.alpha = pixel >> 24;
2155	c.red = (pixel >> 16) & 0xFF;
2156	c.green = (pixel >> 8) & 0xFF;
2157	c.blue = pixel & 0xFF;
2158
2159	return c;
2160}
2161
2162
2163//	#pragma mark -
2164
2165
2166int
2167main(int argc, char* argv[])
2168{
2169	int32 pixelCount = -1;
2170
2171	if (argc > 2) {
2172		puts(B_TRANSLATE_CONTEXT(
2173			"usage: magnify [size] (magnify size * size pixels)",
2174			"Console"));
2175		exit(1);
2176	} else {
2177		if (argc == 2) {
2178			pixelCount = abs(atoi(argv[1]));
2179
2180			if ((pixelCount > 100) || (pixelCount < 4)) {
2181				puts(B_TRANSLATE_CONTEXT(
2182					"usage: magnify [size] (magnify size * size pixels)",
2183					"Console"));
2184				puts(B_TRANSLATE_CONTEXT(
2185					"  size must be > 4 and a multiple of 4",
2186					"Console"));
2187				exit(1);
2188			}
2189
2190			if (pixelCount % 4) {
2191				puts(B_TRANSLATE_CONTEXT(
2192					"magnify: size must be a multiple of 4",
2193					"Console"));
2194				exit(1);
2195			}
2196		}
2197	}
2198
2199	TApp app(pixelCount);
2200	app.Run();
2201	return 0;
2202}
2203