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