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