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