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