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