xref: /haiku/src/apps/showimage/ShowImageWindow.cpp (revision 76e9533e9e9945f62053bf7c737df2f3e1735bbd)
1 /*
2  * Copyright 2003-2010, Haiku, Inc. All Rights Reserved.
3  * Copyright 2004-2005 yellowTAB GmbH. All Rights Reserverd.
4  * Copyright 2006 Bernd Korz. All Rights Reserved
5  * Distributed under the terms of the MIT License.
6  *
7  * Authors:
8  *		Fernando Francisco de Oliveira
9  *		Michael Wilber
10  *		Michael Pfeiffer
11  *		yellowTAB GmbH
12  *		Bernd Korz
13  *		Axel Dörfler, axeld@pinc-software.de
14  */
15 
16 
17 #include "ShowImageWindow.h"
18 
19 #include <new>
20 #include <stdio.h>
21 #include <stdlib.h>
22 
23 #include <Alert.h>
24 #include <Application.h>
25 #include <Bitmap.h>
26 #include <BitmapStream.h>
27 #include <Catalog.h>
28 #include <Clipboard.h>
29 #include <Entry.h>
30 #include <File.h>
31 #include <FilePanel.h>
32 #include <Locale.h>
33 #include <Menu.h>
34 #include <MenuBar.h>
35 #include <MenuItem.h>
36 #include <Path.h>
37 #include <PrintJob.h>
38 #include <RecentItems.h>
39 #include <Roster.h>
40 #include <Screen.h>
41 #include <ScrollView.h>
42 #include <String.h>
43 #include <SupportDefs.h>
44 #include <TranslationDefs.h>
45 #include <TranslationUtils.h>
46 #include <TranslatorRoster.h>
47 
48 #include "ImageCache.h"
49 #include "ProgressWindow.h"
50 #include "ShowImageApp.h"
51 #include "ShowImageConstants.h"
52 #include "ShowImageStatusView.h"
53 #include "ShowImageView.h"
54 
55 
56 // BMessage field names used in Save messages
57 const char* kTypeField = "be:type";
58 const char* kTranslatorField = "be:translator";
59 
60 
61 // message constants
62 enum {
63 	MSG_CAPTURE_MOUSE			= 'mCPM',
64 	MSG_CHANGE_FOCUS			= 'mCFS',
65 	MSG_WINDOW_QUIT				= 'mWQT',
66 	MSG_OUTPUT_TYPE				= 'BTMN',
67 	MSG_SAVE_PANEL				= 'mFSP',
68 	MSG_CLEAR_SELECT			= 'mCSL',
69 	MSG_SELECT_ALL				= 'mSAL',
70 	MSG_SELECTION_MODE			= 'mSLM',
71 	MSG_PAGE_FIRST				= 'mPGF',
72 	MSG_PAGE_LAST				= 'mPGL',
73 	MSG_PAGE_NEXT				= 'mPGN',
74 	MSG_PAGE_PREV				= 'mPGP',
75 	MSG_GOTO_PAGE				= 'mGTP',
76 	MSG_ZOOM_IN					= 'mZIN',
77 	MSG_ZOOM_OUT				= 'mZOU',
78 	MSG_SCALE_BILINEAR			= 'mSBL',
79 	MSG_DESKTOP_BACKGROUND		= 'mDBG',
80 	MSG_ROTATE_90				= 'mR90',
81 	MSG_ROTATE_270				= 'mR27',
82 	MSG_FLIP_LEFT_TO_RIGHT		= 'mFLR',
83 	MSG_FLIP_TOP_TO_BOTTOM		= 'mFTB',
84 	MSG_SLIDE_SHOW_DELAY		= 'mSSD',
85 	MSG_FULL_SCREEN				= 'mFSC',
86 	MSG_SHOW_CAPTION			= 'mSCP',
87 	MSG_PAGE_SETUP				= 'mPSU',
88 	MSG_PREPARE_PRINT			= 'mPPT',
89 	kMsgFitToWindow				= 'mFtW',
90 	kMsgOriginalSize			= 'mOSZ',
91 	kMsgStretchToWindow			= 'mStW'
92 };
93 
94 
95 // This is temporary solution for building BString with printf like format.
96 // will be removed in the future.
97 static void
98 bs_printf(BString* string, const char* format, ...)
99 {
100 	va_list ap;
101 	char* buf;
102 
103 	va_start(ap, format);
104 	vasprintf(&buf, format, ap);
105 	string->SetTo(buf);
106 	free(buf);
107 	va_end(ap);
108 }
109 
110 
111 //	#pragma mark -- ShowImageWindow
112 
113 
114 ShowImageWindow::ShowImageWindow(const entry_ref& ref,
115 	const BMessenger& trackerMessenger)
116 	:
117 	BWindow(BRect(5, 24, 250, 100), "", B_DOCUMENT_WINDOW, 0),
118 	fNavigator(ref, trackerMessenger),
119 	fSavePanel(NULL),
120 	fBar(NULL),
121 	fBrowseMenu(NULL),
122 	fGoToPageMenu(NULL),
123 	fSlideShowDelay(NULL),
124 	fImageView(NULL),
125 	fStatusView(NULL),
126 	fProgressWindow(new ProgressWindow()),
127 	fModified(false),
128 	fFullScreen(false),
129 	fShowCaption(true),
130 	fPrintSettings(NULL)
131 {
132 	_ApplySettings();
133 
134 	// create menu bar
135 	fBar = new BMenuBar(BRect(0, 0, Bounds().right, 1), "menu_bar");
136 	_AddMenus(fBar);
137 	AddChild(fBar);
138 
139 	BRect viewFrame = Bounds();
140 	viewFrame.top = fBar->Bounds().Height() + 1;
141 	viewFrame.right -= B_V_SCROLL_BAR_WIDTH;
142 	viewFrame.bottom -= B_H_SCROLL_BAR_HEIGHT;
143 
144 	// create the image view
145 	fImageView = new ShowImageView(viewFrame, "image_view", B_FOLLOW_ALL,
146 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_PULSE_NEEDED
147 			| B_FRAME_EVENTS);
148 	// wrap a scroll view around the view
149 	BScrollView* scrollView = new BScrollView("image_scroller", fImageView,
150 		B_FOLLOW_ALL, 0, false, false, B_PLAIN_BORDER);
151 	AddChild(scrollView);
152 
153 	const int32 kstatusWidth = 190;
154 	BRect rect;
155 	rect = Bounds();
156 	rect.top = viewFrame.bottom + 1;
157 	rect.left = viewFrame.left + kstatusWidth;
158 	rect.right = viewFrame.right + 1;
159 	rect.bottom += 1;
160 	BScrollBar* horizontalScrollBar = new BScrollBar(rect, "hscroll",
161 		fImageView, 0, 150, B_HORIZONTAL);
162 	AddChild(horizontalScrollBar);
163 
164 	rect.left = 0;
165 	rect.right = kstatusWidth - 1;
166 	rect.bottom -= 1;
167 	fStatusView = new ShowImageStatusView(rect, "status_view", B_FOLLOW_BOTTOM,
168 		B_WILL_DRAW);
169 	AddChild(fStatusView);
170 
171 	rect = Bounds();
172 	rect.top = viewFrame.top - 1;
173 	rect.left = viewFrame.right + 1;
174 	rect.bottom = viewFrame.bottom + 1;
175 	rect.right += 1;
176 	BScrollBar* verticalScrollBar = new BScrollBar(rect, "vscroll", fImageView,
177 		0, 150, B_VERTICAL);
178 	AddChild(verticalScrollBar);
179 
180 	SetSizeLimits(250, 100000, 100, 100000);
181 
182 	// finish creating the window
183 	if (_LoadImage() != B_OK) {
184 		_LoadError(ref);
185 		Quit();
186 		return;
187 	}
188 
189 	// add View menu here so it can access ShowImageView methods
190 	BMenu* menu = new BMenu(B_TRANSLATE_WITH_CONTEXT("View", "Menus"));
191 	_BuildViewMenu(menu, false);
192 	fBar->AddItem(menu);
193 
194 	SetPulseRate(100000);
195 		// every 1/10 second; ShowImageView needs it for marching ants
196 
197 	_MarkMenuItem(menu, MSG_SELECTION_MODE,
198 		fImageView->IsSelectionModeEnabled());
199 
200 	// Tell application object to query the clipboard
201 	// and tell this window if it contains interesting data or not
202 	be_app_messenger.SendMessage(B_CLIPBOARD_CHANGED);
203 
204 	// The window will be shown on screen automatically
205 	Run();
206 }
207 
208 
209 ShowImageWindow::~ShowImageWindow()
210 {
211 	fProgressWindow->Lock();
212 	fProgressWindow->Quit();
213 }
214 
215 
216 void
217 ShowImageWindow::BuildContextMenu(BMenu* menu)
218 {
219 	_BuildViewMenu(menu, true);
220 }
221 
222 
223 #undef B_TRANSLATE_CONTEXT
224 #define B_TRANSLATE_CONTEXT "Menus"
225 
226 
227 void
228 ShowImageWindow::_BuildViewMenu(BMenu* menu, bool popupMenu)
229 {
230 	_AddItemMenu(menu, B_TRANSLATE("Slide show"), MSG_SLIDE_SHOW, 0, 0, this);
231 	_MarkMenuItem(menu, MSG_SLIDE_SHOW, fImageView->SlideShowStarted());
232 	BMenu* delayMenu = new BMenu(B_TRANSLATE("Slide delay"));
233 	if (fSlideShowDelay == NULL)
234 		fSlideShowDelay = delayMenu;
235 
236 	delayMenu->SetRadioMode(true);
237 	// Note: ShowImage loads images in window thread so it becomes unresponsive
238 	//		 if slide show delay is too short! (Especially if loading the image
239 	//		 takes as long as or longer than the slide show delay). Should load
240 	//		 in background thread!
241 	_AddDelayItem(delayMenu, B_TRANSLATE("3 seconds"), 3);
242 	_AddDelayItem(delayMenu, B_TRANSLATE("4 seconds"), 4);
243 	_AddDelayItem(delayMenu, B_TRANSLATE("5 seconds"), 5);
244 	_AddDelayItem(delayMenu, B_TRANSLATE("6 seconds"), 6);
245 	_AddDelayItem(delayMenu, B_TRANSLATE("7 seconds"), 7);
246 	_AddDelayItem(delayMenu, B_TRANSLATE("8 seconds"), 8);
247 	_AddDelayItem(delayMenu, B_TRANSLATE("9 seconds"), 9);
248 	_AddDelayItem(delayMenu, B_TRANSLATE("10 seconds"), 10);
249 	_AddDelayItem(delayMenu, B_TRANSLATE("20 seconds"), 20);
250 	menu->AddItem(delayMenu);
251 
252 	menu->AddSeparatorItem();
253 
254 	_AddItemMenu(menu, B_TRANSLATE("Original size"),
255 		kMsgOriginalSize, '1', 0, this);
256 	_AddItemMenu(menu, B_TRANSLATE("Fit to window"),
257 		kMsgFitToWindow, '0', 0, this);
258 	_AddItemMenu(menu, B_TRANSLATE("Zoom in"), MSG_ZOOM_IN, '+', 0, this);
259 	_AddItemMenu(menu, B_TRANSLATE("Zoom out"), MSG_ZOOM_OUT, '-', 0, this);
260 
261 	menu->AddSeparatorItem();
262 
263 	if (!popupMenu || fFullScreen) {
264 		_AddItemMenu(menu, B_TRANSLATE("High-quality zooming"),
265 			MSG_SCALE_BILINEAR, 0, 0, this);
266 		_AddItemMenu(menu, B_TRANSLATE("Stretch to window"),
267 			kMsgStretchToWindow, 0, 0, this);
268 
269 		menu->AddSeparatorItem();
270 	}
271 
272 	_AddItemMenu(menu, B_TRANSLATE("Full screen"),
273 		MSG_FULL_SCREEN, B_ENTER, 0, this);
274 	_MarkMenuItem(menu, MSG_FULL_SCREEN, fFullScreen);
275 
276 	_AddItemMenu(menu, B_TRANSLATE("Show caption in full screen mode"),
277 		MSG_SHOW_CAPTION, 0, 0, this);
278 	_MarkMenuItem(menu, MSG_SHOW_CAPTION, fShowCaption);
279 
280 	_MarkMenuItem(menu, MSG_SCALE_BILINEAR, fImageView->ScaleBilinear());
281 	_MarkMenuItem(menu, kMsgStretchToWindow, fImageView->StretchesToBounds());
282 
283 	if (popupMenu) {
284 		menu->AddSeparatorItem();
285 		_AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS),
286 			MSG_DESKTOP_BACKGROUND, 0, 0, this);
287 	}
288 }
289 
290 
291 void
292 ShowImageWindow::_AddMenus(BMenuBar* bar)
293 {
294 	BMenu* menu = new BMenu(B_TRANSLATE("File"));
295 
296 	// Add recent files to "Open File" entry as sub-menu.
297 	BMenuItem* item = new BMenuItem(BRecentFilesList::NewFileListMenu(
298 		B_TRANSLATE("Open"B_UTF8_ELLIPSIS), NULL, NULL, be_app, 10, true,
299 		NULL, kApplicationSignature), new BMessage(MSG_FILE_OPEN));
300 	item->SetShortcut('O', 0);
301 	item->SetTarget(be_app);
302 	menu->AddItem(item);
303 	menu->AddSeparatorItem();
304 
305 	BMenu* menuSaveAs = new BMenu(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
306 		B_ITEMS_IN_COLUMN);
307 	BTranslationUtils::AddTranslationItems(menuSaveAs, B_TRANSLATOR_BITMAP);
308 		// Fill Save As submenu with all types that can be converted
309 		// to from the Be bitmap image format
310 	menu->AddItem(menuSaveAs);
311 	_AddItemMenu(menu, B_TRANSLATE("Close"), B_QUIT_REQUESTED, 'W', 0, this);
312 	menu->AddSeparatorItem();
313 	_AddItemMenu(menu, B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS),
314 		MSG_PAGE_SETUP, 0, 0, this);
315 	_AddItemMenu(menu, B_TRANSLATE("Print" B_UTF8_ELLIPSIS),
316 		MSG_PREPARE_PRINT, 'P', 0, this);
317 	menu->AddSeparatorItem();
318 	_AddItemMenu(menu, B_TRANSLATE("About ShowImage" B_UTF8_ELLIPSIS),
319 		B_ABOUT_REQUESTED, 0, 0, be_app);
320 	menu->AddSeparatorItem();
321 	_AddItemMenu(menu, B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q', 0, be_app);
322 	bar->AddItem(menu);
323 
324 	menu = new BMenu(B_TRANSLATE("Edit"));
325 	_AddItemMenu(menu, B_TRANSLATE("Undo"), B_UNDO, 'Z', 0, this, false);
326 	menu->AddSeparatorItem();
327 	_AddItemMenu(menu, B_TRANSLATE("Copy"), B_COPY, 'C', 0, this, false);
328 	menu->AddSeparatorItem();
329 	_AddItemMenu(menu, B_TRANSLATE("Selection Mode"), MSG_SELECTION_MODE, 0, 0,
330 		this);
331 	_AddItemMenu(menu, B_TRANSLATE("Clear selection"),
332 		MSG_CLEAR_SELECT, 0, 0, this, false);
333 	_AddItemMenu(menu, B_TRANSLATE("Select all"),
334 		MSG_SELECT_ALL, 'A', 0, this);
335 	bar->AddItem(menu);
336 
337 	menu = fBrowseMenu = new BMenu(B_TRANSLATE("Browse"));
338 	_AddItemMenu(menu, B_TRANSLATE("First page"),
339 		MSG_PAGE_FIRST, B_LEFT_ARROW, B_SHIFT_KEY, this);
340 	_AddItemMenu(menu, B_TRANSLATE("Last page"),
341 		MSG_PAGE_LAST, B_RIGHT_ARROW, B_SHIFT_KEY, this);
342 	_AddItemMenu(menu, B_TRANSLATE("Previous page"),
343 		MSG_PAGE_PREV, B_LEFT_ARROW, 0, this);
344 	_AddItemMenu(menu, B_TRANSLATE("Next page"),
345 		MSG_PAGE_NEXT, B_RIGHT_ARROW, 0, this);
346 	fGoToPageMenu = new BMenu(B_TRANSLATE("Go to page"));
347 	fGoToPageMenu->SetRadioMode(true);
348 	menu->AddItem(fGoToPageMenu);
349 	menu->AddSeparatorItem();
350 	_AddItemMenu(menu, B_TRANSLATE("Previous file"),
351 		MSG_FILE_PREV, B_UP_ARROW, 0, this);
352 	_AddItemMenu(menu, B_TRANSLATE("Next file"),
353 		MSG_FILE_NEXT, B_DOWN_ARROW, 0, this);
354 	bar->AddItem(menu);
355 
356 	menu = new BMenu(B_TRANSLATE("Image"));
357 	_AddItemMenu(menu, B_TRANSLATE("Rotate clockwise"),
358 		MSG_ROTATE_90, 'R', 0, this);
359 	_AddItemMenu(menu, B_TRANSLATE("Rotate counterclockwise"),
360 		MSG_ROTATE_270, 'R', B_SHIFT_KEY, this);
361 	menu->AddSeparatorItem();
362 	_AddItemMenu(menu, B_TRANSLATE("Flip left to right"),
363 		MSG_FLIP_LEFT_TO_RIGHT, 0, 0, this);
364 	_AddItemMenu(menu, B_TRANSLATE("Flip top to bottom"),
365 		MSG_FLIP_TOP_TO_BOTTOM, 0, 0, this);
366 	menu->AddSeparatorItem();
367 	_AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS),
368 		MSG_DESKTOP_BACKGROUND, 0, 0, this);
369 
370 	bar->AddItem(menu);
371 }
372 
373 
374 BMenuItem*
375 ShowImageWindow::_AddItemMenu(BMenu* menu, const char* label, uint32 what,
376 	char shortcut, uint32 modifier, const BHandler* target, bool enabled)
377 {
378 	BMenuItem* item = new BMenuItem(label, new BMessage(what), shortcut,
379 		modifier);
380 	menu->AddItem(item);
381 
382 	item->SetTarget(target);
383 	item->SetEnabled(enabled);
384 
385 	return item;
386 }
387 
388 
389 BMenuItem*
390 ShowImageWindow::_AddDelayItem(BMenu* menu, const char* label, float value)
391 {
392 	BMessage* message = new BMessage(MSG_SLIDE_SHOW_DELAY);
393 	message->AddFloat("value", value);
394 
395 	BMenuItem* item = new BMenuItem(label, message, 0);
396 	item->SetTarget(this);
397 
398 	bool marked = fImageView->GetSlideShowDelay() == value;
399 	if (marked)
400 		item->SetMarked(true);
401 
402 	menu->AddItem(item);
403 	return item;
404 }
405 
406 
407 void
408 ShowImageWindow::_ResizeWindowToImage()
409 {
410 	BBitmap* bitmap = fImageView->Bitmap();
411 	BScreen screen;
412 	if (bitmap == NULL || !screen.IsValid())
413 		return;
414 
415 	// TODO: use View::GetPreferredSize() instead?
416 	BRect r(bitmap->Bounds());
417 	float width = r.Width() + B_V_SCROLL_BAR_WIDTH;
418 	float height = r.Height() + 1 + fBar->Frame().Height()
419 		+ B_H_SCROLL_BAR_HEIGHT;
420 
421 	BRect frame = screen.Frame();
422 	const float windowBorder = 5;
423 	// dimensions so that window does not reach outside of screen
424 	float maxWidth = frame.Width() + 1 - windowBorder - Frame().left;
425 	float maxHeight = frame.Height() + 1 - windowBorder - Frame().top;
426 
427 	// We have to check size limits manually, otherwise
428 	// menu bar will be too short for small images.
429 
430 	float minW, maxW, minH, maxH;
431 	GetSizeLimits(&minW, &maxW, &minH, &maxH);
432 	if (maxWidth > maxW)
433 		maxWidth = maxW;
434 	if (maxHeight > maxH)
435 		maxHeight = maxH;
436 	if (width < minW)
437 		width = minW;
438 	if (height < minH)
439 		height = minH;
440 
441 	if (width > maxWidth)
442 		width = maxWidth;
443 	if (height > maxHeight)
444 		height = maxHeight;
445 
446 	ResizeTo(width, height);
447 }
448 
449 
450 bool
451 ShowImageWindow::_ToggleMenuItem(uint32 what)
452 {
453 	bool marked = false;
454 	BMenuItem* item = fBar->FindItem(what);
455 	if (item != NULL) {
456 		marked = !item->IsMarked();
457 		item->SetMarked(marked);
458 	}
459 	return marked;
460 }
461 
462 
463 void
464 ShowImageWindow::_EnableMenuItem(BMenu* menu, uint32 what, bool enable)
465 {
466 	BMenuItem* item = menu->FindItem(what);
467 	if (item && item->IsEnabled() != enable)
468 		item->SetEnabled(enable);
469 }
470 
471 
472 void
473 ShowImageWindow::_MarkMenuItem(BMenu* menu, uint32 what, bool marked)
474 {
475 	BMenuItem* item = menu->FindItem(what);
476 	if (item && item->IsMarked() != marked)
477 		item->SetMarked(marked);
478 }
479 
480 
481 void
482 ShowImageWindow::_MarkSlideShowDelay(float value)
483 {
484 	const int32 n = fSlideShowDelay->CountItems();
485 	float v;
486 	for (int32 i = 0; i < n; i ++) {
487 		BMenuItem* item = fSlideShowDelay->ItemAt(i);
488 		if (item) {
489 			if (item->Message()->FindFloat("value", &v) == B_OK && v == value) {
490 				if (!item->IsMarked())
491 					item->SetMarked(true);
492 				return;
493 			}
494 		}
495 	}
496 }
497 
498 
499 void
500 ShowImageWindow::Zoom(BPoint origin, float width, float height)
501 {
502 	_ToggleFullScreen();
503 }
504 
505 
506 void
507 ShowImageWindow::MessageReceived(BMessage* message)
508 {
509 	if (message->WasDropped()) {
510 		uint32 type;
511 		int32 count;
512 		status_t status = message->GetInfo("refs", &type, &count);
513 		if (status == B_OK && type == B_REF_TYPE) {
514 			message->what = B_REFS_RECEIVED;
515 			be_app->PostMessage(message);
516 		}
517 	}
518 
519 	switch (message->what) {
520 		case kMsgImageCacheImageLoaded:
521 		{
522 			fProgressWindow->Stop();
523 
524 			BitmapOwner* bitmapOwner = NULL;
525 			message->FindPointer("bitmapOwner", (void**)&bitmapOwner);
526 
527 			bool first = fImageView->Bitmap() == NULL;
528 			entry_ref ref;
529 			message->FindRef("ref", &ref);
530 			if (!first && ref != fNavigator.CurrentRef()) {
531 				// ignore older images
532 				if (bitmapOwner != NULL)
533 					bitmapOwner->ReleaseReference();
534 				break;
535 			}
536 
537 			status_t status = fImageView->SetImage(message);
538 			if (status != B_OK) {
539 				if (bitmapOwner != NULL)
540 					bitmapOwner->ReleaseReference();
541 
542 				_LoadError(ref);
543 
544 				// quit if file could not be opened
545 				if (first)
546 					Quit();
547 				break;
548 			}
549 
550 			fImageType = message->FindString("type");
551 			fNavigator.SetTo(ref, message->FindInt32("page"),
552 				message->FindInt32("pageCount"));
553 
554 			if (first || (!fImageView->StretchesToBounds() && !fFullScreen)) {
555 				_ResizeWindowToImage();
556 				fImageView->FitToBounds();
557 			}
558 			if (first) {
559 				fImageView->MakeFocus(true);
560 					// to receive key messages
561 				Show();
562 			}
563 			break;
564 		}
565 
566 		case kMsgImageCacheProgressUpdate:
567 		{
568 			entry_ref ref;
569 			if (message->FindRef("ref", &ref) == B_OK
570 				&& ref == fNavigator.CurrentRef()) {
571 				message->what = kMsgProgressUpdate;
572 				fProgressWindow->PostMessage(message);
573 			}
574 			break;
575 		}
576 
577 		case MSG_MODIFIED:
578 			// If image has been modified due to a Cut or Paste
579 			fModified = true;
580 			break;
581 
582 		case MSG_OUTPUT_TYPE:
583 			// User clicked Save As then choose an output format
584 			if (!fSavePanel)
585 				// If user doesn't already have a save panel open
586 				_SaveAs(message);
587 			break;
588 
589 		case MSG_SAVE_PANEL:
590 			// User specified where to save the output image
591 			_SaveToFile(message);
592 			break;
593 
594 		case B_CANCEL:
595 			delete fSavePanel;
596 			fSavePanel = NULL;
597 			break;
598 
599 		case MSG_UPDATE_STATUS:
600 		{
601 			int32 pages = fNavigator.PageCount();
602 			int32 currentPage = fNavigator.CurrentPage();
603 
604 			bool enable = pages > 1 ? true : false;
605 			_EnableMenuItem(fBar, MSG_PAGE_FIRST, enable);
606 			_EnableMenuItem(fBar, MSG_PAGE_LAST, enable);
607 			_EnableMenuItem(fBar, MSG_PAGE_NEXT, enable);
608 			_EnableMenuItem(fBar, MSG_PAGE_PREV, enable);
609 			fGoToPageMenu->SetEnabled(enable);
610 
611 			_EnableMenuItem(fBar, MSG_FILE_NEXT, fNavigator.HasNextFile());
612 			_EnableMenuItem(fBar, MSG_FILE_PREV, fNavigator.HasPreviousFile());
613 
614 			if (fGoToPageMenu->CountItems() != pages) {
615 				// Only rebuild the submenu if the number of
616 				// pages is different
617 
618 				while (fGoToPageMenu->CountItems() > 0) {
619 					// Remove all page numbers
620 					delete fGoToPageMenu->RemoveItem(0L);
621 				}
622 
623 				for (int32 i = 1; i <= pages; i++) {
624 					// Fill Go To page submenu with an entry for each page
625 					BMessage* goTo = new BMessage(MSG_GOTO_PAGE);
626 					goTo->AddInt32("page", i);
627 
628 					char shortcut = 0;
629 					if (i < 10)
630 						shortcut = '0' + i;
631 
632 					BString strCaption;
633 					strCaption << i;
634 
635 					BMenuItem* item = new BMenuItem(strCaption.String(), goTo,
636 						B_SHIFT_KEY, shortcut);
637 					if (currentPage == i)
638 						item->SetMarked(true);
639 					fGoToPageMenu->AddItem(item);
640 				}
641 			} else {
642 				// Make sure the correct page is marked
643 				BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1);
644 				if (currentItem != NULL && !currentItem->IsMarked())
645 					currentItem->SetMarked(true);
646 			}
647 
648 			_UpdateStatusText(message);
649 
650 			BPath path(fImageView->Image());
651 			SetTitle(path.Path());
652 			break;
653 		}
654 
655 		case MSG_UPDATE_STATUS_TEXT:
656 		{
657 			_UpdateStatusText(message);
658 			break;
659 		}
660 
661 		case MSG_SELECTION:
662 		{
663 			// The view sends this message when a selection is
664 			// made or the selection is cleared so that the window
665 			// can update the state of the appropriate menu items
666 			bool enable;
667 			if (message->FindBool("has_selection", &enable) == B_OK) {
668 				_EnableMenuItem(fBar, B_COPY, enable);
669 				_EnableMenuItem(fBar, MSG_CLEAR_SELECT, enable);
670 			}
671 			break;
672 		}
673 
674 		case MSG_UNDO_STATE:
675 		{
676 			bool enable;
677 			if (message->FindBool("can_undo", &enable) == B_OK)
678 				_EnableMenuItem(fBar, B_UNDO, enable);
679 			break;
680 		}
681 
682 		case B_UNDO:
683 			fImageView->Undo();
684 			break;
685 
686 		case B_COPY:
687 			fImageView->CopySelectionToClipboard();
688 			break;
689 
690 		case MSG_SELECTION_MODE:
691 			fImageView->SetSelectionMode(_ToggleMenuItem(MSG_SELECTION_MODE));
692 			break;
693 
694 		case MSG_CLEAR_SELECT:
695 			fImageView->ClearSelection();
696 			break;
697 
698 		case MSG_SELECT_ALL:
699 			fImageView->SelectAll();
700 			break;
701 
702 		case MSG_PAGE_FIRST:
703 			if (_ClosePrompt() && fNavigator.FirstPage())
704 				_LoadImage();
705 			break;
706 
707 		case MSG_PAGE_LAST:
708 			if (_ClosePrompt() && fNavigator.LastPage())
709 				_LoadImage();
710 			break;
711 
712 		case MSG_PAGE_NEXT:
713 			if (_ClosePrompt() && fNavigator.NextPage())
714 				_LoadImage();
715 			break;
716 
717 		case MSG_PAGE_PREV:
718 			if (_ClosePrompt() && fNavigator.PreviousPage())
719 				_LoadImage();
720 			break;
721 
722 		case MSG_GOTO_PAGE:
723 		{
724 			if (!_ClosePrompt())
725 				break;
726 
727 			int32 newPage;
728 			if (message->FindInt32("page", &newPage) != B_OK)
729 				break;
730 
731 			int32 currentPage = fNavigator.CurrentPage();
732 			int32 pages = fNavigator.PageCount();
733 
734 			// TODO: use radio mode instead!
735 			if (newPage > 0 && newPage <= pages) {
736 				BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1);
737 				BMenuItem* newItem = fGoToPageMenu->ItemAt(newPage - 1);
738 				if (currentItem != NULL && newItem != NULL) {
739 					currentItem->SetMarked(false);
740 					newItem->SetMarked(true);
741 					if (fNavigator.GoToPage(newPage))
742 						_LoadImage();
743 				}
744 			}
745 			break;
746 		}
747 
748 		case kMsgFitToWindow:
749 			fImageView->FitToBounds();
750 			break;
751 
752 		case kMsgStretchToWindow:
753 			fImageView->SetStretchToBounds(
754 				_ToggleMenuItem(kMsgStretchToWindow));
755 			break;
756 
757 		case MSG_FILE_PREV:
758 			if (_ClosePrompt() && fNavigator.PreviousFile())
759 				_LoadImage(false);
760 			break;
761 
762 		case MSG_FILE_NEXT:
763 			if (_ClosePrompt() && fNavigator.NextFile())
764 				_LoadImage();
765 			break;
766 
767 		case kMsgDeleteCurrentFile:
768 		{
769 			if (fNavigator.MoveFileToTrash())
770 				_LoadImage();
771 			else
772 				PostMessage(B_QUIT_REQUESTED);
773 			break;
774 		}
775 
776 		case MSG_ROTATE_90:
777 			fImageView->Rotate(90);
778 			break;
779 
780 		case MSG_ROTATE_270:
781 			fImageView->Rotate(270);
782 			break;
783 
784 		case MSG_FLIP_LEFT_TO_RIGHT:
785 			fImageView->Flip(true);
786 			break;
787 
788 		case MSG_FLIP_TOP_TO_BOTTOM:
789 			fImageView->Flip(false);
790 			break;
791 
792 		case MSG_SLIDE_SHOW:
793 		{
794 			BMenuItem* item = fBar->FindItem(message->what);
795 			if (!item)
796 				break;
797 			if (item->IsMarked()) {
798 				item->SetMarked(false);
799 				fImageView->StopSlideShow();
800 			} else if (_ClosePrompt()) {
801 				item->SetMarked(true);
802 				fImageView->StartSlideShow();
803 			}
804 			break;
805 		}
806 
807 		case MSG_SLIDE_SHOW_DELAY:
808 		{
809 			float value;
810 			if (message->FindFloat("value", &value) == B_OK) {
811 				fImageView->SetSlideShowDelay(value);
812 				// in case message is sent from popup menu
813 				_MarkSlideShowDelay(value);
814 			}
815 			break;
816 		}
817 
818 		case MSG_FULL_SCREEN:
819 			_ToggleFullScreen();
820 			break;
821 
822 		case MSG_EXIT_FULL_SCREEN:
823 			if (fFullScreen)
824 				_ToggleFullScreen();
825 			break;
826 
827 		case MSG_SHOW_CAPTION: {
828 			fShowCaption = _ToggleMenuItem(message->what);
829 			ShowImageSettings* settings = my_app->Settings();
830 
831 			if (settings->Lock()) {
832 				settings->SetBool("ShowCaption", fShowCaption);
833 				settings->Unlock();
834 			}
835 			if (fFullScreen)
836 				fImageView->SetShowCaption(fShowCaption);
837 		}	break;
838 
839 		case MSG_PAGE_SETUP:
840 			_PageSetup();
841 			break;
842 
843 		case MSG_PREPARE_PRINT:
844 			_PrepareForPrint();
845 			break;
846 
847 		case MSG_PRINT:
848 			_Print(message);
849 			break;
850 
851 		case MSG_ZOOM_IN:
852 			fImageView->ZoomIn();
853 			break;
854 
855 		case MSG_ZOOM_OUT:
856 			fImageView->ZoomOut();
857 			break;
858 
859 		case kMsgOriginalSize:
860 			fImageView->SetZoom(1.0);
861 			break;
862 
863 		case MSG_SCALE_BILINEAR:
864 			fImageView->SetScaleBilinear(_ToggleMenuItem(message->what));
865 			break;
866 
867 		case MSG_DESKTOP_BACKGROUND:
868 		{
869 			BMessage backgroundsMessage(B_REFS_RECEIVED);
870 			backgroundsMessage.AddRef("refs", fImageView->Image());
871 			// This is used in the Backgrounds code for scaled placement
872 			backgroundsMessage.AddInt32("placement", 'scpl');
873 			be_roster->Launch("application/x-vnd.haiku-backgrounds", &backgroundsMessage);
874 			break;
875 		}
876 
877 		default:
878 			BWindow::MessageReceived(message);
879 			break;
880 	}
881 }
882 
883 
884 void
885 ShowImageWindow::_UpdateStatusText(const BMessage* message)
886 {
887 	BString status;
888 	if (fImageView->Bitmap() != NULL) {
889 		BRect bounds = fImageView->Bitmap()->Bounds();
890 		status << bounds.IntegerWidth() + 1
891 			<< "x" << bounds.IntegerHeight() + 1 << ", " << fImageType;
892 	}
893 
894 	BString text;
895 	if (message != NULL && message->FindString("status", &text) == B_OK
896 		&& text.Length() > 0) {
897 		status << ", " << text;
898 	}
899 
900 	fStatusView->Update(fNavigator.CurrentRef(), status);
901 }
902 
903 
904 void
905 ShowImageWindow::_LoadError(const entry_ref& ref)
906 {
907 	// TODO: give a better error message!
908 	BAlert* alert = new BAlert(B_TRANSLATE("ShowImage"),
909 		B_TRANSLATE_WITH_CONTEXT("Could not load image! Either the "
910 			"file or an image translator for it does not exist.",
911 			"LoadAlerts"),
912 		B_TRANSLATE_WITH_CONTEXT("OK", "Alerts"), NULL, NULL,
913 		B_WIDTH_AS_USUAL, B_INFO_ALERT);
914 	alert->Go();
915 }
916 
917 
918 void
919 ShowImageWindow::_SaveAs(BMessage* message)
920 {
921 	// Read the translator and output type the user chose
922 	translator_id outTranslator;
923 	uint32 outType;
924 	if (message->FindInt32(kTranslatorField,
925 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
926 		|| message->FindInt32(kTypeField,
927 			reinterpret_cast<int32 *>(&outType)) != B_OK)
928 		return;
929 
930 	// Add the chosen translator and output type to the
931 	// message that the save panel will send back
932 	BMessage panelMsg(MSG_SAVE_PANEL);
933 	panelMsg.AddInt32(kTranslatorField, outTranslator);
934 	panelMsg.AddInt32(kTypeField, outType);
935 
936 	// Create save panel and show it
937 	BMessenger target(this);
938 	fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL,
939 		&target, NULL, 0, false, &panelMsg);
940 	if (!fSavePanel)
941 		return;
942 
943 	fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE);
944 	fSavePanel->Show();
945 }
946 
947 
948 void
949 ShowImageWindow::_SaveToFile(BMessage* message)
950 {
951 	// Read in where the file should be saved
952 	entry_ref dirRef;
953 	if (message->FindRef("directory", &dirRef) != B_OK)
954 		return;
955 
956 	const char* filename;
957 	if (message->FindString("name", &filename) != B_OK)
958 		return;
959 
960 	// Read in the translator and type to be used
961 	// to save the output image
962 	translator_id outTranslator;
963 	uint32 outType;
964 	if (message->FindInt32(kTranslatorField,
965 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
966 		|| message->FindInt32(kTypeField,
967 			reinterpret_cast<int32 *>(&outType)) != B_OK)
968 		return;
969 
970 	// Find the translator_format information needed to
971 	// write a MIME attribute for the image file
972 	BTranslatorRoster* roster = BTranslatorRoster::Default();
973 	const translation_format* outFormat = NULL;
974 	int32 outCount = 0;
975 	if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK
976 		|| outCount < 1)
977 		return;
978 
979 	int32 i;
980 	for (i = 0; i < outCount; i++) {
981 		if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type == outType)
982 			break;
983 	}
984 	if (i == outCount)
985 		return;
986 
987 	// Write out the image file
988 	BDirectory dir(&dirRef);
989 	fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]);
990 }
991 
992 
993 #undef B_TRANSLATE_CONTEXT
994 #define B_TRANSLATE_CONTEXT "ClosePrompt"
995 
996 
997 bool
998 ShowImageWindow::_ClosePrompt()
999 {
1000 	if (!fModified)
1001 		return true;
1002 
1003 	int32 count = fNavigator.PageCount();
1004 	int32 page = fNavigator.CurrentPage();
1005 	BString prompt;
1006 
1007 	if (count > 1) {
1008 		bs_printf(&prompt,
1009 			B_TRANSLATE("The document '%s' (page %d) has been changed. Do you "
1010 				"want to close the document?"),
1011 			fImageView->Image()->name, page);
1012 	} else {
1013 		bs_printf(&prompt,
1014 			B_TRANSLATE("The document '%s' has been changed. Do you want to "
1015 				"close the document?"),
1016 			fImageView->Image()->name);
1017 	}
1018 
1019 	BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(),
1020 		B_TRANSLATE("Cancel"), B_TRANSLATE("Close"));
1021 	if (alert->Go() == 0) {
1022 		// Cancel
1023 		return false;
1024 	}
1025 
1026 	// Close
1027 	fModified = false;
1028 	return true;
1029 }
1030 
1031 
1032 status_t
1033 ShowImageWindow::_LoadImage(bool forward)
1034 {
1035 	BMessenger us(this);
1036 	status_t status = ImageCache::Default().RetrieveImage(
1037 		fNavigator.CurrentRef(), fNavigator.CurrentPage(), &us);
1038 	if (status != B_OK)
1039 		return status;
1040 
1041 	fProgressWindow->Start(this);
1042 
1043 	// Preload previous/next images - two in the navigation direction, one
1044 	// in the opposite direction.
1045 
1046 	entry_ref nextRef = fNavigator.CurrentRef();
1047 	if (_PreloadImage(forward, nextRef))
1048 		_PreloadImage(forward, nextRef);
1049 
1050 	entry_ref previousRef = fNavigator.CurrentRef();
1051 	_PreloadImage(!forward, previousRef);
1052 
1053 	return B_OK;
1054 }
1055 
1056 
1057 bool
1058 ShowImageWindow::_PreloadImage(bool forward, entry_ref& ref)
1059 {
1060 	entry_ref currentRef = ref;
1061 	if ((forward && !fNavigator.GetNextFile(currentRef, ref))
1062 		|| (!forward && !fNavigator.GetPreviousFile(currentRef, ref)))
1063 		return false;
1064 
1065 	return ImageCache::Default().RetrieveImage(ref) == B_OK;
1066 }
1067 
1068 
1069 void
1070 ShowImageWindow::_ToggleFullScreen()
1071 {
1072 	BRect frame;
1073 	fFullScreen = !fFullScreen;
1074 	if (fFullScreen) {
1075 		BScreen screen;
1076 		fWindowFrame = Frame();
1077 		frame = screen.Frame();
1078 		frame.top -= fBar->Bounds().Height()+1;
1079 		frame.right += B_V_SCROLL_BAR_WIDTH;
1080 		frame.bottom += B_H_SCROLL_BAR_HEIGHT;
1081 		frame.InsetBy(-1, -1); // PEN_SIZE in ShowImageView
1082 
1083 		SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE);
1084 
1085 		Activate();
1086 			// make the window frontmost
1087 	} else {
1088 		frame = fWindowFrame;
1089 
1090 		SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
1091 	}
1092 
1093 	MoveTo(frame.left, frame.top);
1094 	ResizeTo(frame.Width(), frame.Height());
1095 
1096 	fImageView->SetHideIdlingCursor(fFullScreen);
1097 	fImageView->SetShowCaption(fFullScreen && fShowCaption);
1098 	fImageView->FitToBounds();
1099 }
1100 
1101 
1102 void
1103 ShowImageWindow::_ApplySettings()
1104 {
1105 	ShowImageSettings* settings = my_app->Settings();
1106 
1107 	if (settings->Lock()) {
1108 		fShowCaption = settings->GetBool("ShowCaption", fShowCaption);
1109 		fPrintOptions.SetBounds(BRect(0, 0, 1023, 767));
1110 
1111 		int32 op = settings->GetInt32("PO:Option", fPrintOptions.Option());
1112 		fPrintOptions.SetOption((enum PrintOptions::Option)op);
1113 
1114 		float f = settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor());
1115 		fPrintOptions.SetZoomFactor(f);
1116 
1117 		f = settings->GetFloat("PO:DPI", fPrintOptions.DPI());
1118 		fPrintOptions.SetDPI(f);
1119 
1120 		f = settings->GetFloat("PO:Width", fPrintOptions.Width());
1121 		fPrintOptions.SetWidth(f);
1122 
1123 		f = settings->GetFloat("PO:Height", fPrintOptions.Height());
1124 		fPrintOptions.SetHeight(f);
1125 
1126 		settings->Unlock();
1127 	}
1128 }
1129 
1130 
1131 void
1132 ShowImageWindow::_SavePrintOptions()
1133 {
1134 	ShowImageSettings* settings = my_app->Settings();
1135 
1136 	if (settings->Lock()) {
1137 		settings->SetInt32("PO:Option", fPrintOptions.Option());
1138 		settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor());
1139 		settings->SetFloat("PO:DPI", fPrintOptions.DPI());
1140 		settings->SetFloat("PO:Width", fPrintOptions.Width());
1141 		settings->SetFloat("PO:Height", fPrintOptions.Height());
1142 		settings->Unlock();
1143 	}
1144 }
1145 
1146 
1147 bool
1148 ShowImageWindow::_PageSetup()
1149 {
1150 	BPrintJob printJob(fImageView->Image()->name);
1151 	if (fPrintSettings != NULL)
1152 		printJob.SetSettings(new BMessage(*fPrintSettings));
1153 
1154 	status_t status = printJob.ConfigPage();
1155 	if (status == B_OK) {
1156 		delete fPrintSettings;
1157 		fPrintSettings = printJob.Settings();
1158 	}
1159 
1160 	return status == B_OK;
1161 }
1162 
1163 
1164 void
1165 ShowImageWindow::_PrepareForPrint()
1166 {
1167 	if (fPrintSettings == NULL) {
1168 		BPrintJob printJob(fImageView->Image()->name);
1169 		if (printJob.ConfigJob() == B_OK)
1170 			fPrintSettings = printJob.Settings();
1171 	}
1172 
1173 	fPrintOptions.SetBounds(fImageView->Bitmap()->Bounds());
1174 	fPrintOptions.SetWidth(fImageView->Bitmap()->Bounds().Width() + 1);
1175 
1176 	new PrintOptionsWindow(BPoint(Frame().left + 30, Frame().top + 50),
1177 		&fPrintOptions, this);
1178 }
1179 
1180 
1181 void
1182 ShowImageWindow::_Print(BMessage* msg)
1183 {
1184 	status_t st;
1185 	if (msg->FindInt32("status", &st) != B_OK || st != B_OK)
1186 		return;
1187 
1188 	_SavePrintOptions();
1189 
1190 	BPrintJob printJob(fImageView->Image()->name);
1191 	if (fPrintSettings)
1192 		printJob.SetSettings(new BMessage(*fPrintSettings));
1193 
1194 	if (printJob.ConfigJob() == B_OK) {
1195 		delete fPrintSettings;
1196 		fPrintSettings = printJob.Settings();
1197 
1198 		// first/lastPage is unused for now
1199 		int32 firstPage = printJob.FirstPage();
1200 		int32 lastPage = printJob.LastPage();
1201 		BRect printableRect = printJob.PrintableRect();
1202 
1203 		if (firstPage < 1)
1204 			firstPage = 1;
1205 		if (lastPage < firstPage)
1206 			lastPage = firstPage;
1207 
1208 		BBitmap* bitmap = fImageView->Bitmap();
1209 		float imageWidth = bitmap->Bounds().Width() + 1.0;
1210 		float imageHeight = bitmap->Bounds().Height() + 1.0;
1211 
1212 		float width;
1213 		switch (fPrintOptions.Option()) {
1214 			case PrintOptions::kFitToPage: {
1215 				float w1 = printableRect.Width()+1;
1216 				float w2 = imageWidth * (printableRect.Height() + 1)
1217 					/ imageHeight;
1218 				if (w2 < w1)
1219 					width = w2;
1220 				else
1221 					width = w1;
1222 			}	break;
1223 			case PrintOptions::kZoomFactor:
1224 				width = imageWidth * fPrintOptions.ZoomFactor();
1225 				break;
1226 			case PrintOptions::kDPI:
1227 				width = imageWidth * 72.0 / fPrintOptions.DPI();
1228 				break;
1229 			case PrintOptions::kWidth:
1230 			case PrintOptions::kHeight:
1231 				width = fPrintOptions.Width();
1232 				break;
1233 
1234 			default:
1235 				// keep compiler silent; should not reach here
1236 				width = imageWidth;
1237 		}
1238 
1239 		// TODO: eventually print large images on several pages
1240 		printJob.BeginJob();
1241 		fImageView->SetScale(width / imageWidth);
1242 		// coordinates are relative to printable rectangle
1243 		BRect bounds(bitmap->Bounds());
1244 		printJob.DrawView(fImageView, bounds, BPoint(0, 0));
1245 		fImageView->SetScale(1.0);
1246 		printJob.SpoolPage();
1247 		printJob.CommitJob();
1248 	}
1249 }
1250 
1251 
1252 bool
1253 ShowImageWindow::QuitRequested()
1254 {
1255 	if (fSavePanel) {
1256 		// Don't allow this window to be closed if a save panel is open
1257 		return false;
1258 	}
1259 
1260 	return _ClosePrompt();
1261 }
1262