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