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