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