xref: /haiku/src/apps/showimage/ShowImageWindow.cpp (revision 9760dcae2038d47442f4658c2575844c6cf92c40)
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, "3 seconds", 3);
262 	_AddDelayItem(delayMenu, "4 seconds", 4);
263 	_AddDelayItem(delayMenu, "5 seconds", 5);
264 	_AddDelayItem(delayMenu, "6 seconds", 6);
265 	_AddDelayItem(delayMenu, "7 seconds", 7);
266 	_AddDelayItem(delayMenu, "8 seconds", 8);
267 	_AddDelayItem(delayMenu, "9 seconds", 9);
268 	_AddDelayItem(delayMenu, "10 seconds", 10);
269 	_AddDelayItem(delayMenu, "20 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" B_UTF8_ELLIPSIS, 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" B_UTF8_ELLIPSIS, 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 					&& !fFullScreen)
636 					WindowRedimension(fImageView->GetBitmap());
637 			}
638 
639 			fStatusView->SetText(status);
640 
641 			UpdateTitle();
642 		}	break;
643 
644 		case MSG_UPDATE_STATUS_TEXT: {
645 			BString status;
646 			status << fWidth << "x" << fHeight;
647 			BString str;
648 			if (message->FindString("status", &str) == B_OK && str.Length() > 0) {
649 				status << ", " << str;
650 				fStatusView->SetText(status);
651 			}
652 		}	break;
653 
654 		case MSG_SELECTION: {
655 			// The view sends this message when a selection is
656 			// made or the selection is cleared so that the window
657 			// can update the state of the appropriate menu items
658 			bool benable;
659 			if (message->FindBool("has_selection", &benable) == B_OK) {
660 				_EnableMenuItem(fBar, B_CUT, benable);
661 				_EnableMenuItem(fBar, B_COPY, benable);
662 				_EnableMenuItem(fBar, MSG_CLEAR_SELECT, benable);
663 			}
664 		}	break;
665 
666 		case MSG_UNDO_STATE: {
667 			bool benable;
668 			if (message->FindBool("can_undo", &benable) == B_OK)
669 				_EnableMenuItem(fBar, B_UNDO, benable);
670 		}	break;
671 
672 		case MSG_CLIPBOARD_CHANGED: {
673 			// The app sends this message after it examines the clipboard in
674 			// response to a B_CLIPBOARD_CHANGED message
675 			bool bdata;
676 			if (message->FindBool("data_available", &bdata) == B_OK)
677 				_EnableMenuItem(fBar, B_PASTE, bdata);
678 		}	break;
679 
680 		case B_UNDO:
681 			fImageView->Undo();
682 			break;
683 
684 		case B_CUT:
685 			fImageView->Cut();
686 			break;
687 
688 		case B_COPY:
689 			fImageView->CopySelectionToClipboard();
690 			break;
691 
692 		case B_PASTE:
693 			fImageView->Paste();
694 			break;
695 
696 		case MSG_CLEAR_SELECT:
697 			fImageView->ClearSelection();
698 			break;
699 
700 		case MSG_SELECT_ALL:
701 			fImageView->SelectAll();
702 			break;
703 
704 		case MSG_PAGE_FIRST:
705 			if (_ClosePrompt())
706 				fImageView->FirstPage();
707 			break;
708 
709 		case MSG_PAGE_LAST:
710 			if (_ClosePrompt())
711 				fImageView->LastPage();
712 			break;
713 
714 		case MSG_PAGE_NEXT:
715 			if (_ClosePrompt())
716 				fImageView->NextPage();
717 			break;
718 
719 		case MSG_PAGE_PREV:
720 			if (_ClosePrompt())
721 				fImageView->PrevPage();
722 			break;
723 
724 		case MSG_GOTO_PAGE: {
725 			if (!_ClosePrompt())
726 				break;
727 
728 			int32 newPage;
729 			if (message->FindInt32("page", &newPage) != B_OK)
730 				break;
731 
732 			int32 curPage = fImageView->CurrentPage();
733 			int32 pages = fImageView->PageCount();
734 
735 			if (newPage > 0 && newPage <= pages) {
736 				BMenuItem* pcurItem = fGoToPageMenu->ItemAt(curPage - 1);
737 				BMenuItem* pnewItem = fGoToPageMenu->ItemAt(newPage - 1);
738 				if (pcurItem && pnewItem) {
739 					pcurItem->SetMarked(false);
740 					pnewItem->SetMarked(true);
741 					fImageView->GoToPage(newPage);
742 				}
743 			}
744 		}	break;
745 
746 		case MSG_DITHER_IMAGE:
747 			fImageView->SetDither(_ToggleMenuItem(message->what));
748 			break;
749 
750 		case MSG_SHRINK_TO_WINDOW:
751 			_ResizeToWindow(true, message->what);
752 			break;
753 
754 		case MSG_ZOOM_TO_WINDOW:
755 			_ResizeToWindow(false, message->what);
756 			break;
757 
758 		case MSG_FILE_PREV:
759 			if (_ClosePrompt())
760 				fImageView->PrevFile();
761 			break;
762 
763 		case MSG_FILE_NEXT:
764 			if (_ClosePrompt())
765 				fImageView->NextFile();
766 			break;
767 
768 		case MSG_ROTATE_90:
769 			fImageView->Rotate(90);
770 			break;
771 
772 		case MSG_ROTATE_270:
773 			fImageView->Rotate(270);
774 			break;
775 
776 		case MSG_FLIP_LEFT_TO_RIGHT:
777 			fImageView->Flip(true);
778 			break;
779 
780 		case MSG_FLIP_TOP_TO_BOTTOM:
781 			fImageView->Flip(false);
782 			break;
783 
784 		case MSG_INVERT:
785 			fImageView->Invert();
786 			break;
787 
788 		case MSG_SLIDE_SHOW: {
789 			BMenuItem *item = fBar->FindItem(message->what);
790 			if (!item)
791 				break;
792 			if (item->IsMarked()) {
793 				item->SetMarked(false);
794 				fResizeItem->SetEnabled(true);
795 				fImageView->StopSlideShow();
796 			} else if (_ClosePrompt()) {
797 				item->SetMarked(true);
798 				fResizeItem->SetEnabled(false);
799 				fImageView->StartSlideShow();
800 			}
801 		}	break;
802 
803 		case MSG_SLIDE_SHOW_DELAY: {
804 			float value;
805 			if (message->FindFloat("value", &value) == B_OK) {
806 				fImageView->SetSlideShowDelay(value);
807 				// in case message is sent from popup menu
808 				_MarkSlideShowDelay(value);
809 			}
810 		}	break;
811 
812 		case MSG_FULL_SCREEN:
813 			_ToggleFullScreen();
814 			break;
815 
816 		case MSG_EXIT_FULL_SCREEN:
817 			if (fFullScreen)
818 				_ToggleFullScreen();
819 			break;
820 
821 		case MSG_SHOW_CAPTION: {
822 			fShowCaption = _ToggleMenuItem(message->what);
823 			ShowImageSettings* settings = my_app->Settings();
824 
825 			if (settings->Lock()) {
826 				settings->SetBool("ShowCaption", fShowCaption);
827 				settings->Unlock();
828 			}
829 			if (fFullScreen)
830 				fImageView->SetShowCaption(fShowCaption);
831 		}	break;
832 
833 		case MSG_PAGE_SETUP:
834 			_PageSetup();
835 			break;
836 
837 		case MSG_PREPARE_PRINT:
838 			_PrepareForPrint();
839 			break;
840 
841 		case MSG_PRINT:
842 			_Print(message);
843 			break;
844 
845 		case MSG_ZOOM_IN:
846 			fImageView->ZoomIn();
847 			break;
848 
849 		case MSG_ZOOM_OUT:
850 			fImageView->ZoomOut();
851 			break;
852 
853 		case MSG_ORIGINAL_SIZE:
854 			fImageView->SetZoom(1.0);
855 			break;
856 
857 		case MSG_SCALE_BILINEAR:
858 			fImageView->SetScaleBilinear(_ToggleMenuItem(message->what));
859 			break;
860 
861 		case MSG_OPEN_RESIZER_WINDOW: {
862 			if (fImageView->GetBitmap() != NULL) {
863 				BRect rect = fImageView->GetBitmap()->Bounds();
864 				_OpenResizerWindow(rect.IntegerWidth()+1, rect.IntegerHeight()+1);
865 			}
866 		}	break;
867 
868 		case MSG_RESIZE: {
869 			int w = message->FindInt32("w");
870 			int h = message->FindInt32("h");
871 			fImageView->ResizeImage(w, h);
872 		} break;
873 
874 		case MSG_RESIZER_WINDOW_QUIT:
875 			delete fResizerWindowMessenger;
876 			fResizerWindowMessenger = NULL;
877 			break;
878 
879 		case MSG_DESKTOP_BACKGROUND: {
880 			BMessage message(B_REFS_RECEIVED);
881 			message.AddRef("refs", fImageView->Image());
882 			// This is used in the Backgrounds code for scaled placement
883 			message.AddInt32("placement", 'scpl');
884 			be_roster->Launch("application/x-vnd.haiku-backgrounds", &message);
885 		}	break;
886 
887 		default:
888 			BWindow::MessageReceived(message);
889 			break;
890 	}
891 }
892 
893 
894 void
895 ShowImageWindow::_SaveAs(BMessage *message)
896 {
897 	// Read the translator and output type the user chose
898 	translator_id outTranslator;
899 	uint32 outType;
900 	if (message->FindInt32(kTranslatorField,
901 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
902 		|| message->FindInt32(kTypeField,
903 			reinterpret_cast<int32 *>(&outType)) != B_OK)
904 		return;
905 
906 	// Add the chosen translator and output type to the
907 	// message that the save panel will send back
908 	BMessage panelMsg(MSG_SAVE_PANEL);
909 	panelMsg.AddInt32(kTranslatorField, outTranslator);
910 	panelMsg.AddInt32(kTypeField, outType);
911 
912 	// Create save panel and show it
913 	BMessenger target(this);
914 	fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL,
915 		&target, NULL, 0, false, &panelMsg);
916 	if (!fSavePanel)
917 		return;
918 
919 	fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE);
920 	fSavePanel->Show();
921 }
922 
923 
924 void
925 ShowImageWindow::_SaveToFile(BMessage *message)
926 {
927 	// Read in where the file should be saved
928 	entry_ref dirRef;
929 	if (message->FindRef("directory", &dirRef) != B_OK)
930 		return;
931 
932 	const char *filename;
933 	if (message->FindString("name", &filename) != B_OK)
934 		return;
935 
936 	// Read in the translator and type to be used
937 	// to save the output image
938 	translator_id outTranslator;
939 	uint32 outType;
940 	if (message->FindInt32(kTranslatorField,
941 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
942 		|| message->FindInt32(kTypeField,
943 			reinterpret_cast<int32 *>(&outType)) != B_OK)
944 		return;
945 
946 	// Find the translator_format information needed to
947 	// write a MIME attribute for the image file
948 	BTranslatorRoster *roster = BTranslatorRoster::Default();
949 	const translation_format *outFormat = NULL;
950 	int32 outCount = 0;
951 	if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK
952 		|| outCount < 1)
953 		return;
954 
955 	int32 i;
956 	for (i = 0; i < outCount; i++) {
957 		if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type == outType)
958 			break;
959 	}
960 	if (i == outCount)
961 		return;
962 
963 	// Write out the image file
964 	BDirectory dir(&dirRef);
965 	fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]);
966 }
967 
968 
969 bool
970 ShowImageWindow::_ClosePrompt()
971 {
972 	if (!fModified)
973 		return true;
974 
975 	int32 page, count;
976 	count = fImageView->PageCount();
977 	page = fImageView->CurrentPage();
978 	BString prompt, name;
979 	fImageView->GetName(&name);
980 	prompt << "The document '" << name << "'";
981 	if (count > 1)
982 		prompt << " (page " << page << ")";
983 
984 	prompt << " has been changed. "
985 		   << "Do you want to close the document?";
986 	BAlert *pAlert = new BAlert("Close document", prompt.String(),
987 		"Cancel", "Close");
988 	if (pAlert->Go() == 0) {
989 		// Cancel
990 		return false;
991 	} else {
992 		// Close
993 		fModified = false;
994 		return true;
995 	}
996 }
997 
998 
999 void
1000 ShowImageWindow::_ToggleFullScreen()
1001 {
1002 	BRect frame;
1003 	fFullScreen = !fFullScreen;
1004 	if (fFullScreen) {
1005 		BScreen screen;
1006 		fWindowFrame = Frame();
1007 		frame = screen.Frame();
1008 		frame.top -= fBar->Bounds().Height()+1;
1009 		frame.right += B_V_SCROLL_BAR_WIDTH;
1010 		frame.bottom += B_H_SCROLL_BAR_HEIGHT;
1011 		frame.InsetBy(-1, -1); // PEN_SIZE in ShowImageView
1012 
1013 		SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE);
1014 
1015 		Activate();
1016 			// make the window frontmost
1017 	} else {
1018 		frame = fWindowFrame;
1019 
1020 		SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
1021 	}
1022 
1023 	fImageView->SetFullScreen(fFullScreen);
1024 	fImageView->SetShowCaption(fFullScreen && fShowCaption);
1025 	MoveTo(frame.left, frame.top);
1026 	ResizeTo(frame.Width(), frame.Height());
1027 }
1028 
1029 
1030 void
1031 ShowImageWindow::_LoadSettings()
1032 {
1033 	ShowImageSettings* settings = my_app->Settings();
1034 
1035 	if (settings->Lock()) {
1036 		fShowCaption = settings->GetBool("ShowCaption", fShowCaption);
1037 		fPrintOptions.SetBounds(BRect(0, 0, 1023, 767));
1038 
1039 		int32 op = settings->GetInt32("PO:Option", fPrintOptions.Option());
1040 		fPrintOptions.SetOption((enum PrintOptions::Option)op);
1041 
1042 		float f = settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor());
1043 		fPrintOptions.SetZoomFactor(f);
1044 
1045 		f = settings->GetFloat("PO:DPI", fPrintOptions.DPI());
1046 		fPrintOptions.SetDPI(f);
1047 
1048 		f = settings->GetFloat("PO:Width", fPrintOptions.Width());
1049 		fPrintOptions.SetWidth(f);
1050 
1051 		f = settings->GetFloat("PO:Height", fPrintOptions.Height());
1052 		fPrintOptions.SetHeight(f);
1053 
1054 		settings->Unlock();
1055 	}
1056 }
1057 
1058 
1059 void
1060 ShowImageWindow::_SavePrintOptions()
1061 {
1062 	ShowImageSettings* settings = my_app->Settings();
1063 
1064 	if (settings->Lock()) {
1065 		settings->SetInt32("PO:Option", fPrintOptions.Option());
1066 		settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor());
1067 		settings->SetFloat("PO:DPI", fPrintOptions.DPI());
1068 		settings->SetFloat("PO:Width", fPrintOptions.Width());
1069 		settings->SetFloat("PO:Height", fPrintOptions.Height());
1070 		settings->Unlock();
1071 	}
1072 }
1073 
1074 
1075 bool
1076 ShowImageWindow::_PageSetup()
1077 {
1078 	BString name;
1079 	fImageView->GetName(&name);
1080 	BPrintJob printJob(name.String());
1081 	if (fPrintSettings != NULL)
1082 		printJob.SetSettings(new BMessage(*fPrintSettings));
1083 
1084 	status_t status = printJob.ConfigPage();
1085 	if (status == B_OK) {
1086 		delete fPrintSettings;
1087 		fPrintSettings = printJob.Settings();
1088 	}
1089 
1090 	return status == B_OK;
1091 }
1092 
1093 
1094 void
1095 ShowImageWindow::_PrepareForPrint()
1096 {
1097 	if (fPrintSettings == NULL) {
1098 		BString name;
1099 		fImageView->GetName(&name);
1100 
1101 		BPrintJob printJob("");
1102 		if (printJob.ConfigJob() == B_OK)
1103 			fPrintSettings = printJob.Settings();
1104 	}
1105 
1106 	fPrintOptions.SetBounds(fImageView->GetBitmap()->Bounds());
1107 	fPrintOptions.SetWidth(fImageView->GetBitmap()->Bounds().Width()+1);
1108 
1109 	new PrintOptionsWindow(BPoint(Frame().left+30, Frame().top+50),
1110 		&fPrintOptions, this);
1111 }
1112 
1113 
1114 void
1115 ShowImageWindow::_Print(BMessage *msg)
1116 {
1117 	status_t st;
1118 	if (msg->FindInt32("status", &st) != B_OK || st != B_OK)
1119 		return;
1120 
1121 	_SavePrintOptions();
1122 
1123 	BString name;
1124 	fImageView->GetName(&name);
1125 
1126 	BPrintJob printJob(name.String());
1127 	if (fPrintSettings)
1128 		printJob.SetSettings(new BMessage(*fPrintSettings));
1129 
1130 	if (printJob.ConfigJob() == B_OK) {
1131 		delete fPrintSettings;
1132 		fPrintSettings = printJob.Settings();
1133 
1134 		// first/lastPage is unused for now
1135 		int32 firstPage = printJob.FirstPage();
1136 		int32 lastPage = printJob.LastPage();
1137 		BRect printableRect = printJob.PrintableRect();
1138 
1139 		if (firstPage < 1)
1140 			firstPage = 1;
1141 		if (lastPage < firstPage)
1142 			lastPage = firstPage;
1143 
1144 		BBitmap* bitmap = fImageView->GetBitmap();
1145 		float imageWidth = bitmap->Bounds().Width() + 1.0;
1146 		float imageHeight = bitmap->Bounds().Height() + 1.0;
1147 
1148 		float width;
1149 		switch (fPrintOptions.Option()) {
1150 			case PrintOptions::kFitToPage: {
1151 				float w1 = printableRect.Width()+1;
1152 				float w2 = imageWidth * (printableRect.Height() + 1) / imageHeight;
1153 				if (w2 < w1)
1154 					width = w2;
1155 				else
1156 					width = w1;
1157 			}	break;
1158 			case PrintOptions::kZoomFactor:
1159 				width = imageWidth * fPrintOptions.ZoomFactor();
1160 				break;
1161 			case PrintOptions::kDPI:
1162 				width = imageWidth * 72.0 / fPrintOptions.DPI();
1163 				break;
1164 			case PrintOptions::kWidth:
1165 			case PrintOptions::kHeight:
1166 				width = fPrintOptions.Width();
1167 				break;
1168 
1169 			default:
1170 				// keep compiler silent; should not reach here
1171 				width = imageWidth;
1172 		}
1173 
1174 		// TODO: eventually print large images on several pages
1175 		printJob.BeginJob();
1176 		fImageView->SetScale(width / imageWidth);
1177 		// coordinates are relative to printable rectangle
1178 		BRect bounds(bitmap->Bounds());
1179 		printJob.DrawView(fImageView, bounds, BPoint(0, 0));
1180 		fImageView->SetScale(1.0);
1181 		printJob.SpoolPage();
1182 		printJob.CommitJob();
1183 	}
1184 }
1185 
1186 
1187 void
1188 ShowImageWindow::_OpenResizerWindow(int32 width, int32 height)
1189 {
1190 	if (fResizerWindowMessenger == NULL) {
1191 		// open window if it is not already opened
1192 		BWindow* window = new ResizerWindow(this, width, height);
1193 		fResizerWindowMessenger = new BMessenger(window);
1194 		window->Show();
1195 	} else {
1196 		fResizerWindowMessenger->SendMessage(ResizerWindow::kActivateMsg);
1197 	}
1198 }
1199 
1200 
1201 void
1202 ShowImageWindow::_UpdateResizerWindow(int32 width, int32 height)
1203 {
1204 	if (fResizerWindowMessenger == NULL)
1205 		return;
1206 
1207 	BMessage updateMsg(ResizerWindow::kUpdateMsg);
1208 	updateMsg.AddInt32("width", width);
1209 	updateMsg.AddInt32("height", height);
1210 	fResizerWindowMessenger->SendMessage(&updateMsg);
1211 }
1212 
1213 
1214 void
1215 ShowImageWindow::_CloseResizerWindow()
1216 {
1217 	if (fResizerWindowMessenger == NULL)
1218 		return;
1219 
1220 	fResizerWindowMessenger->SendMessage(B_QUIT_REQUESTED);
1221 	delete fResizerWindowMessenger;
1222 	fResizerWindowMessenger = NULL;
1223 }
1224 
1225 
1226 bool
1227 ShowImageWindow::QuitRequested()
1228 {
1229 	if (fSavePanel) {
1230 		// Don't allow this window to be closed if a save panel is open
1231 		return false;
1232 	}
1233 
1234 	bool quit = _ClosePrompt();
1235 
1236 	if (quit) {
1237 		_CloseResizerWindow();
1238 
1239 		// tell the app to forget about this window
1240 		be_app->PostMessage(MSG_WINDOW_QUIT);
1241 	}
1242 
1243 	return quit;
1244 }
1245 
1246 
1247 void
1248 ShowImageWindow::ScreenChanged(BRect frame, color_space mode)
1249 {
1250 	fImageView->SetDither(mode == B_CMAP8);
1251 }
1252