1/*****************************************************************************/
2// ImageView
3// Written by Michael Wilber, Haiku Translation Kit Team
4//
5// ImageView.cpp
6//
7// BView class for showing images.  Images can be dropped on this view
8// from the tracker and images viewed in this view can be dragged
9// to the tracker to be saved.
10//
11//
12// Copyright (c) 2003 Haiku Project
13//
14// Permission is hereby granted, free of charge, to any person obtaining a
15// copy of this software and associated documentation files (the "Software"),
16// to deal in the Software without restriction, including without limitation
17// the rights to use, copy, modify, merge, publish, distribute, sublicense,
18// and/or sell copies of the Software, and to permit persons to whom the
19// Software is furnished to do so, subject to the following conditions:
20//
21// The above copyright notice and this permission notice shall be included
22// in all copies or substantial portions of the Software.
23//
24// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
25// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
27// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30// DEALINGS IN THE SOFTWARE.
31/*****************************************************************************/
32
33
34#include "ImageView.h"
35#include "Constants.h"
36#include "StatusCheck.h"
37#include "InspectorApp.h"
38#include "TranslatorItem.h"
39#include <Application.h>
40#include <Catalog.h>
41#include <Message.h>
42#include <Locale.h>
43#include <List.h>
44#include <String.h>
45#include <TranslationUtils.h>
46#include <TranslatorRoster.h>
47#include <BitmapStream.h>
48#include <Entry.h>
49#include <Path.h>
50#include <Directory.h>
51#include <File.h>
52#include <MenuBar.h>
53#include <Screen.h>
54#include <ScrollBar.h>
55#include <Alert.h>
56#include <stdlib.h>
57#include <stdio.h>
58#include <string.h>
59
60#define BORDER_WIDTH 16
61#define BORDER_HEIGHT 16
62#define PEN_SIZE 1.0f
63
64#undef B_TRANSLATION_CONTEXT
65#define B_TRANSLATION_CONTEXT "ImageView"
66
67
68ImageView::ImageView(BRect rect, const char *name)
69	: BView(rect, name, B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS)
70{
71	fpbitmap = NULL;
72	fdocumentIndex = 1;
73	fdocumentCount = 1;
74
75	SetViewColor(192, 192, 192);
76	SetHighColor(0, 0, 0);
77	SetPenSize(PEN_SIZE);
78}
79
80
81ImageView::~ImageView()
82{
83	delete fpbitmap;
84	fpbitmap = NULL;
85}
86
87
88void
89ImageView::AttachedToWindow()
90{
91	AdjustScrollBars();
92}
93
94
95void
96ImageView::Draw(BRect rect)
97{
98	if (HasImage()) {
99		// Draw black rectangle around image
100		StrokeRect(
101			BRect(BORDER_WIDTH - PEN_SIZE,
102				BORDER_HEIGHT - PEN_SIZE,
103				fpbitmap->Bounds().Width() + BORDER_WIDTH + PEN_SIZE,
104				fpbitmap->Bounds().Height() + BORDER_HEIGHT + PEN_SIZE));
105
106		DrawBitmap(fpbitmap, BPoint(BORDER_WIDTH, BORDER_HEIGHT));
107	}
108}
109
110
111void
112ImageView::ReDraw()
113{
114	Draw(Bounds());
115}
116
117
118void
119ImageView::FrameResized(float width, float height)
120{
121	AdjustScrollBars();
122
123	if (!HasImage())
124		Invalidate();
125}
126
127
128void
129ImageView::MouseDown(BPoint point)
130{
131	if (!HasImage())
132		return;
133
134	// Only accept left button clicks
135	BMessage *pmsg = Window()->CurrentMessage();
136	int32 button = pmsg->FindInt32("buttons");
137	if (button != B_PRIMARY_MOUSE_BUTTON)
138		return;
139
140	// Tell BeOS to setup a Drag/Drop operation
141	//
142	// (When the image is dropped, BeOS sends
143	// the following message to ImageWindow,
144	// which causes it to call ImageView::SetImage())
145	BMessage msg(B_SIMPLE_DATA);
146	msg.AddInt32("be:actions", B_COPY_TARGET);
147	msg.AddString("be:filetypes", "application/octet-stream");
148	msg.AddString("be:types", "application/octet-stream");
149	msg.AddString("be:clip_name", "Bitmap");
150
151	DragMessage(&msg, Bounds());
152}
153
154
155void
156ImageView::MouseMoved(BPoint point, uint32 state, const BMessage *pmsg)
157{
158}
159
160
161void
162ImageView::MouseUp(BPoint point)
163{
164}
165
166
167void
168ImageView::MessageReceived(BMessage *pmsg)
169{
170	switch (pmsg->what) {
171		case B_COPY_TARGET:
172			SaveImageAtDropLocation(pmsg);
173			break;
174
175		default:
176			BView::MessageReceived(pmsg);
177			break;
178	}
179}
180
181
182void
183ImageView::SaveImageAtDropLocation(BMessage *pmsg)
184{
185	// Find the location and name of the drop and
186	// write the image file there
187	BBitmapStream stream(fpbitmap);
188
189	StatusCheck chk;
190		// throw an exception if this is assigned
191		// anything other than B_OK
192
193	try {
194		entry_ref dirref;
195		chk = pmsg->FindRef("directory", &dirref);
196		const char *filename;
197		chk = pmsg->FindString("name", &filename);
198
199		BDirectory dir(&dirref);
200		BFile file(&dir, filename, B_WRITE_ONLY | B_CREATE_FILE);
201		chk = file.InitCheck();
202
203		BTranslatorRoster *proster = BTranslatorRoster::Default();
204		chk = proster->Translate(&stream, NULL, NULL, &file, B_TGA_FORMAT);
205
206	} catch (StatusNotOKException) {
207		BAlert *palert = new BAlert(NULL,
208			B_TRANSLATE("Sorry, unable to write the image file."),
209			B_TRANSLATE("OK"));
210		palert->Go();
211	}
212
213	stream.DetachBitmap(&fpbitmap);
214}
215
216
217void
218ImageView::AdjustScrollBars()
219{
220	BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0);
221	if (HasImage())
222		rctbitmap = fpbitmap->Bounds();
223
224	float prop, range;
225	BScrollBar *psb = ScrollBar(B_HORIZONTAL);
226	if (psb) {
227		range = rctbitmap.Width() + (BORDER_WIDTH * 2) - rctview.Width();
228		if (range < 0) range = 0;
229		prop = rctview.Width() / (rctbitmap.Width() + (BORDER_WIDTH * 2));
230		if (prop > 1.0f) prop = 1.0f;
231		psb->SetRange(0, range);
232		psb->SetProportion(prop);
233		psb->SetSteps(10, 100);
234	}
235
236	psb = ScrollBar(B_VERTICAL);
237	if (psb) {
238		range = rctbitmap.Height() + (BORDER_HEIGHT * 2) - rctview.Height();
239		if (range < 0) range = 0;
240		prop = rctview.Height() / (rctbitmap.Height() + (BORDER_HEIGHT * 2));
241		if (prop > 1.0f) prop = 1.0f;
242		psb->SetRange(0, range);
243		psb->SetProportion(prop);
244		psb->SetSteps(10, 100);
245	}
246}
247
248
249struct ColorSpaceName {
250	color_space id;
251	const char *name;
252};
253#define COLORSPACENAME(id) {id, #id}
254
255
256// convert colorspace numerical value to
257// a string value
258const char *
259get_color_space_name(color_space colors)
260{
261	// print out colorspace if it matches an item in the list
262	const ColorSpaceName kcolorspaces[] = {
263		COLORSPACENAME(B_NO_COLOR_SPACE),
264		COLORSPACENAME(B_RGB32),
265		COLORSPACENAME(B_RGBA32),
266		COLORSPACENAME(B_RGB24),
267		COLORSPACENAME(B_RGB16),
268		COLORSPACENAME(B_RGB15),
269		COLORSPACENAME(B_RGBA15),
270		COLORSPACENAME(B_CMAP8),
271		COLORSPACENAME(B_GRAY8),
272		COLORSPACENAME(B_GRAY1),
273		COLORSPACENAME(B_RGB32_BIG),
274		COLORSPACENAME(B_RGBA32_BIG),
275		COLORSPACENAME(B_RGB24_BIG),
276		COLORSPACENAME(B_RGB16_BIG),
277		COLORSPACENAME(B_RGB15_BIG),
278		COLORSPACENAME(B_RGBA15_BIG),
279		COLORSPACENAME(B_YCbCr422),
280		COLORSPACENAME(B_YCbCr411),
281		COLORSPACENAME(B_YCbCr444),
282		COLORSPACENAME(B_YCbCr420),
283		COLORSPACENAME(B_YUV422),
284		COLORSPACENAME(B_YUV411),
285		COLORSPACENAME(B_YUV444),
286		COLORSPACENAME(B_YUV420),
287		COLORSPACENAME(B_YUV9),
288		COLORSPACENAME(B_YUV12),
289		COLORSPACENAME(B_UVL24),
290		COLORSPACENAME(B_UVL32),
291		COLORSPACENAME(B_UVLA32),
292		COLORSPACENAME(B_LAB24),
293		COLORSPACENAME(B_LAB32),
294		COLORSPACENAME(B_LABA32),
295		COLORSPACENAME(B_HSI24),
296		COLORSPACENAME(B_HSI32),
297		COLORSPACENAME(B_HSIA32),
298		COLORSPACENAME(B_HSV24),
299		COLORSPACENAME(B_HSV32),
300		COLORSPACENAME(B_HSVA32),
301		COLORSPACENAME(B_HLS24),
302		COLORSPACENAME(B_HLS32),
303		COLORSPACENAME(B_HLSA32),
304		COLORSPACENAME(B_CMY24),
305		COLORSPACENAME(B_CMY32),
306		COLORSPACENAME(B_CMYA32),
307		COLORSPACENAME(B_CMYK32)
308	};
309	const int32 kncolorspaces =  sizeof(kcolorspaces) /
310		sizeof(ColorSpaceName);
311	for (int32 i = 0; i < kncolorspaces; i++) {
312		if (colors == kcolorspaces[i].id)
313			return kcolorspaces[i].name;
314	}
315
316	return B_TRANSLATE("Unknown");
317}
318
319
320// return a string of the passed number formated
321// as a hexadecimal number in lowercase with a leading "0x"
322const char *
323hex_format(uint32 num)
324{
325	static char str[11] = { 0 };
326	sprintf(str, "0x%.8lx", num);
327
328	return str;
329}
330
331
332// convert passed number to a string of 4 characters
333// and return that string
334const char *
335char_format(uint32 num)
336{
337	static char str[5] = { 0 };
338	uint32 bnum = B_HOST_TO_BENDIAN_INT32(num);
339	memcpy(str, &bnum, 4);
340
341	return str;
342}
343
344
345void
346dump_translation_formats(BString &bstr, const translation_format *pfmts,
347	int32 nfmts)
348{
349	BString *str1 = NULL;
350	for (int i = 0; i < nfmts; i++) {
351		BString string = B_TRANSLATE("\nType: '%1' (%2)\n"
352			"Group: '%3' (%4)\n"
353			"Quality: %5\n"
354			"Capability: %6\n"
355			"MIME Type: %7\n"
356			"Name: %8\n");
357		string.ReplaceFirst("%1", char_format(pfmts[i].type));
358		string.ReplaceFirst("%2", hex_format(pfmts[i].type));
359		string.ReplaceFirst("%3", char_format(pfmts[i].group));
360		string.ReplaceFirst("%4", hex_format(pfmts[i].group));
361		char str2[127] = { 0 };
362		sprintf(str2, "%f", pfmts[i].quality);
363		string.ReplaceFirst("%5", str2 );
364		str2[0] = '\0';
365		sprintf(str2, "%f", pfmts[i].capability);
366		string.ReplaceFirst("%6",  str2 );
367		string.ReplaceFirst("%7", pfmts[i].MIME);
368		string.ReplaceFirst("%8", pfmts[i].name);
369		if (i == 0)
370			str1 = new BString(string);
371		else
372			str1->Append(string);
373	}
374	bstr = str1->String();
375}
376
377
378// Send information about the currently open image to the
379// BApplication object so it can send it to the InfoWindow
380void
381ImageView::UpdateInfoWindow(const BPath &path, BMessage &ioExtension,
382	const translator_info &tinfo, BTranslatorRoster *proster)
383{
384	BMessage msg(M_INFO_WINDOW_TEXT);
385	BString bstr;
386
387	bstr = B_TRANSLATE("Image: %1\n"
388		"Color Space: %2 (%3)\n"
389		"Dimensions: %4 x %5\n"
390		"Bytes per Row: %6\n"
391		"Total Bytes: %7\n"
392		"\nIdentify Info:\n"
393		"ID String: %8\n"
394		"MIME Type: %9\n"
395		"Type: '%10' (%11)\n"
396		"Translator ID: %12\n"
397		"Group: '%13' (%14)\n"
398		"Quality: %15\n"
399		"Capability: %16\n"
400		"\nExtension Info:\n");
401	bstr.ReplaceFirst("%1", path.Path());
402	color_space cs = fpbitmap->ColorSpace();
403	bstr.ReplaceFirst("%2", get_color_space_name(cs));
404	bstr.ReplaceFirst("%3", hex_format(static_cast<uint32>(cs)));
405	char str2[127] = { 0 };
406	sprintf(str2, "%ld", fpbitmap->Bounds().IntegerWidth() + 1);
407	bstr.ReplaceFirst("%4", str2);
408	str2[0] = '\0';
409	sprintf(str2, "%ld", fpbitmap->Bounds().IntegerHeight() + 1);
410	bstr.ReplaceFirst("%5", str2);
411	str2[0] = '\0';
412	sprintf(str2, "%ld", fpbitmap->BytesPerRow());
413	bstr.ReplaceFirst("%6", str2);
414	str2[0] = '\0';
415	sprintf(str2, "%ld", fpbitmap->BitsLength());
416	bstr.ReplaceFirst("%7", str2);
417	bstr.ReplaceFirst("%8", tinfo.name);
418	bstr.ReplaceFirst("%9", tinfo.MIME);
419	bstr.ReplaceFirst("%10", char_format(tinfo.type));
420	bstr.ReplaceFirst("%11", hex_format(tinfo.type));
421	str2[0] = '\0';
422	sprintf(str2, "%ld", tinfo.translator);
423	bstr.ReplaceFirst("%12", str2);
424	bstr.ReplaceFirst("%13", char_format(tinfo.group));
425	bstr.ReplaceFirst("%14", hex_format(tinfo.group));
426	str2[0] = '\0';
427	sprintf(str2, "%f", tinfo.quality);
428	bstr.ReplaceFirst("%15", str2);
429	str2[0] = '\0';
430	sprintf(str2, "%f", tinfo.capability);
431	bstr.ReplaceFirst("%16", str2);
432
433	int32 document_count = 0, document_index = 0;
434	// Translator Info
435	const char *tranname = NULL, *traninfo = NULL;
436	int32 tranversion = 0;
437
438	if (ioExtension.FindInt32("/documentCount", &document_count) == B_OK) {
439		BString str = B_TRANSLATE("Number of Documents: %1\n"
440			"\nTranslator Used:\n"
441			"Name: %2\n"
442			"Info: %3\n"
443			"Version: %4\n");
444		char str2[127] = { 0 };
445		sprintf(str2, "%ld", document_count);
446		str.ReplaceFirst("%1", str2);
447		str.ReplaceFirst("%2", tranname);
448		str.ReplaceFirst("%3", traninfo);
449		str2[0] = '\0';
450		sprintf(str2, "%d", (int)tranversion);
451		str.ReplaceFirst("%4", str2);
452		bstr.Append(str.String());
453	}
454	else
455		if (ioExtension.FindInt32("/documentIndex", &document_index) == B_OK) {
456			BString str = B_TRANSLATE("Selected Document: %1\n"
457				"\nTranslator Used:\n"
458				"Name: %2\n"
459				"Info: %3\n"
460				"Version: %4\n");
461			char str2[127] = { 0 };
462			sprintf(str2, "%ld", document_index);
463			str.ReplaceFirst("%1", str2);
464			str.ReplaceFirst("%2", tranname);
465			str.ReplaceFirst("%3", traninfo);
466			str2[0] = '\0';
467			sprintf(str2, "%d", (int)tranversion);
468			str.ReplaceFirst("%4", str2);
469			bstr.Append(str.String());
470		}
471		else
472			if (proster->GetTranslatorInfo(tinfo.translator, &tranname,
473				&traninfo, &tranversion) == B_OK) {
474					BString str = B_TRANSLATE("\nTranslator Used:\n"
475						"Name: %1\n"
476						"Info: %2\n"
477						"Version: %3\n");
478					str.ReplaceFirst("%1", tranname);
479					str.ReplaceFirst("%2", traninfo);
480					char str2[127] = { 0 };
481					sprintf(str2, "%d", (int)tranversion);
482					str.ReplaceFirst("%3", str2);
483					bstr.Append(str.String());
484			}
485
486	// Translator Input / Output Formats
487	int32 nins = 0, nouts = 0;
488	const translation_format *pins = NULL, *pouts = NULL;
489	if (proster->GetInputFormats(tinfo.translator, &pins, &nins) == B_OK) {
490		bstr << B_TRANSLATE("\nInput Formats:");
491		dump_translation_formats(bstr, pins, nins);
492		pins = NULL;
493	}
494	if (proster->GetOutputFormats(tinfo.translator, &pouts, &nouts) == B_OK) {
495		bstr << B_TRANSLATE("\nOutput Formats:");
496		dump_translation_formats(bstr, pouts, nouts);
497		pouts = NULL;
498	}
499	msg.AddString("text", bstr);
500	be_app->PostMessage(&msg);
501}
502
503
504BTranslatorRoster *
505ImageView::SelectTranslatorRoster(BTranslatorRoster &roster)
506{
507	bool bNoneSelected = true;
508
509	InspectorApp *papp;
510	papp = static_cast<InspectorApp *>(be_app);
511	if (papp) {
512		BList *plist = papp->GetTranslatorsList();
513		if (plist) {
514			for (int32 i = 0; i < plist->CountItems(); i++) {
515				BTranslatorItem *pitem =
516					static_cast<BTranslatorItem *>(plist->ItemAt(i));
517				if (pitem->IsSelected()) {
518					bNoneSelected = false;
519					roster.AddTranslators(pitem->Path());
520				}
521			}
522		}
523	}
524
525	if (bNoneSelected)
526		return BTranslatorRoster::Default();
527	else
528		return &roster;
529}
530
531
532void
533ImageView::SetImage(BMessage *pmsg)
534{
535	// Replace current image with the image
536	// specified in the given BMessage
537
538	entry_ref ref;
539	if (!pmsg)
540		ref = fcurrentRef;
541	else if (pmsg->FindRef("refs", &ref) != B_OK)
542		// If refs not found, just ignore the message
543		return;
544
545	StatusCheck chk;
546
547	try {
548		BFile file(&ref, B_READ_ONLY);
549		chk = file.InitCheck();
550
551		BTranslatorRoster roster, *proster;
552		proster = SelectTranslatorRoster(roster);
553		if (!proster)
554			// throw exception
555			chk = B_ERROR;
556		// determine what type the image is
557		translator_info tinfo;
558		BMessage ioExtension;
559		if (ref != fcurrentRef)
560			// if new image, reset to first document
561			fdocumentIndex = 1;
562		chk = ioExtension.AddInt32("/documentIndex", fdocumentIndex);
563		chk = proster->Identify(&file, &ioExtension, &tinfo, 0, NULL,
564			B_TRANSLATOR_BITMAP);
565
566		// perform the actual translation
567		BBitmapStream outstream;
568		chk = proster->Translate(&file, &tinfo, &ioExtension, &outstream,
569			B_TRANSLATOR_BITMAP);
570		BBitmap *pbitmap = NULL;
571		chk = outstream.DetachBitmap(&pbitmap);
572		delete fpbitmap;
573		fpbitmap = pbitmap;
574		pbitmap = NULL;
575		fcurrentRef = ref;
576			// need to keep the ref around if user wants to switch pages
577		int32 documentCount = 0;
578		if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK &&
579			documentCount > 0)
580			fdocumentCount = documentCount;
581		else
582			fdocumentCount = 1;
583
584		// Set the name of the Window to reflect the file name
585		BWindow *pwin = Window();
586		BEntry entry(&ref);
587		BPath path;
588		if (entry.InitCheck() == B_OK) {
589			if (path.SetTo(&entry) == B_OK)
590				pwin->SetTitle(path.Leaf());
591			else
592				pwin->SetTitle(IMAGEWINDOW_TITLE);
593		} else
594			pwin->SetTitle(IMAGEWINDOW_TITLE);
595		UpdateInfoWindow(path, ioExtension, tinfo, proster);
596
597		// Resize parent window and set size limits to
598		// reflect the size of the new bitmap
599		float width, height;
600		BMenuBar *pbar = pwin->KeyMenuBar();
601		width = fpbitmap->Bounds().Width() + B_V_SCROLL_BAR_WIDTH + (BORDER_WIDTH * 2);
602		height = fpbitmap->Bounds().Height() +
603			pbar->Bounds().Height() + B_H_SCROLL_BAR_HEIGHT + (BORDER_HEIGHT * 2) + 1;
604		BScreen *pscreen = new BScreen(pwin);
605		BRect rctscreen = pscreen->Frame();
606		if (width > rctscreen.Width())
607			width = rctscreen.Width();
608		if (height > rctscreen.Height())
609			height = rctscreen.Height();
610		pwin->SetSizeLimits(B_V_SCROLL_BAR_WIDTH * 4, width,
611			pbar->Bounds().Height() + (B_H_SCROLL_BAR_HEIGHT * 4) + 1, height);
612		pwin->SetZoomLimits(width, height);
613		AdjustScrollBars();
614
615		//pwin->Zoom();
616			// Perform all of the hard work of resizing the
617			// window while taking into account the size of
618			// the screen, the tab and borders of the window
619			//
620			// HACK: Need to fix case where window un-zooms
621			// when the window is already the correct size
622			// for the current image
623
624		// repaint view
625		Invalidate();
626
627	} catch (StatusNotOKException) {
628		BAlert *palert = new BAlert(NULL,
629			B_TRANSLATE("Sorry, unable to load the image."),
630			B_TRANSLATE("OK"));
631		palert->Go();
632	}
633}
634
635
636void
637ImageView::FirstPage()
638{
639	if (fdocumentIndex != 1) {
640		fdocumentIndex = 1;
641		SetImage(NULL);
642	}
643}
644
645
646void
647ImageView::LastPage()
648{
649	if (fdocumentIndex != fdocumentCount) {
650		fdocumentIndex = fdocumentCount;
651		SetImage(NULL);
652	}
653}
654
655
656void
657ImageView::NextPage()
658{
659	if (fdocumentIndex < fdocumentCount) {
660		fdocumentIndex++;
661		SetImage(NULL);
662	}
663}
664
665
666void
667ImageView::PrevPage()
668{
669	if (fdocumentIndex > 1) {
670		fdocumentIndex--;
671		SetImage(NULL);
672	}
673}
674
675