xref: /haiku/src/apps/showimage/ShowImageWindow.cpp (revision e8e6c3e8add38406e0cc4e7110032a253a80f054)
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 <algobase.h>
30 #include <stdio.h>
31 #include <Application.h>
32 #include <Bitmap.h>
33 #include <BitmapStream.h>
34 #include <Entry.h>
35 #include <File.h>
36 #include <Menu.h>
37 #include <MenuBar.h>
38 #include <MenuItem.h>
39 #include <Path.h>
40 #include <ScrollView.h>
41 #include <TranslationUtils.h>
42 #include <TranslatorRoster.h>
43 #include <Alert.h>
44 #include <SupportDefs.h>
45 #include <Screen.h>
46 
47 #include "ShowImageConstants.h"
48 #include "ShowImageWindow.h"
49 #include "ShowImageView.h"
50 #include "ShowImageStatusView.h"
51 
52 ShowImageWindow::ShowImageWindow(const entry_ref *pref)
53 	: BWindow(BRect(50, 50, 350, 250), "", B_DOCUMENT_WINDOW, 0)
54 {
55 	fpSavePanel = NULL;
56 	fpRef = NULL;
57 	fFullScreen = false;
58 
59 	// create menu bar
60 	fpBar = new BMenuBar(BRect(0, 0, Bounds().right, 20), "menu_bar");
61 	LoadMenus(fpBar);
62 	AddChild(fpBar);
63 
64 	BRect viewFrame = Bounds();
65 	viewFrame.top		= fpBar->Bounds().bottom + 1;
66 	viewFrame.right		-= B_V_SCROLL_BAR_WIDTH;
67 	viewFrame.bottom	-= B_H_SCROLL_BAR_HEIGHT;
68 
69 	// create the image view
70 	fpImageView = new ShowImageView(viewFrame, "image_view", B_FOLLOW_ALL,
71 		B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE | B_PULSE_NEEDED);
72 	// wrap a scroll view around the view
73 	BScrollView *pscrollView = new BScrollView("image_scroller", fpImageView,
74 		B_FOLLOW_ALL, 0, false, false, B_PLAIN_BORDER);
75 	AddChild(pscrollView);
76 
77 	const int32 kstatusWidth = 190;
78 	BRect rect;
79 	rect = Bounds();
80 	rect.top	= viewFrame.bottom + 1;
81 	rect.left 	= viewFrame.left + kstatusWidth;
82 	rect.right	= viewFrame.right;
83 	BScrollBar *phscroll;
84 	phscroll = new BScrollBar(rect, "hscroll", fpImageView, 0, 150,
85 		B_HORIZONTAL);
86 	AddChild(phscroll);
87 
88 	rect.left = 0;
89 	rect.right = kstatusWidth - 1;
90 	fpStatusView = new ShowImageStatusView(rect, "status_view", B_FOLLOW_BOTTOM,
91 		B_WILL_DRAW);
92 	fpStatusView->SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR));
93 	AddChild(fpStatusView);
94 
95 	rect = Bounds();
96 	rect.top    = viewFrame.top;
97 	rect.left 	= viewFrame.right + 1;
98 	rect.bottom	= viewFrame.bottom;
99 	BScrollBar *pvscroll;
100 	pvscroll = new BScrollBar(rect, "vscroll", fpImageView, 0, 150, B_VERTICAL);
101 	AddChild(pvscroll);
102 
103 	SetSizeLimits(250, 100000, 100, 100000);
104 
105 	// finish creating window
106 	SetRef(pref);
107 	UpdateTitle();
108 
109 	fpImageView->SetImage(pref);
110 
111 	SetPulseRate(100000); // every 1/10 second; ShowImageView needs it for marching ants
112 
113 	Show();
114 }
115 
116 ShowImageWindow::~ShowImageWindow()
117 {
118 	delete fpRef;
119 }
120 
121 status_t
122 ShowImageWindow::InitCheck()
123 {
124 	if (!fpRef || !fpImageView)
125 		return B_ERROR;
126 	else
127 		return B_OK;
128 }
129 
130 void
131 ShowImageWindow::SetRef(const entry_ref *pref)
132 {
133 	if (!fpRef)
134 		fpRef = new entry_ref(*pref);
135 	else
136 		*fpRef = *pref;
137 }
138 
139 void
140 ShowImageWindow::UpdateTitle()
141 {
142 	BEntry entry(fpRef);
143 	if (entry.InitCheck() == B_OK) {
144 		BPath path;
145 		entry.GetPath(&path);
146 		if (path.InitCheck() == B_OK)
147 			SetTitle(path.Path());
148 	}
149 }
150 
151 void
152 ShowImageWindow::LoadMenus(BMenuBar *pbar)
153 {
154 	BMenu *pmenu = new BMenu("File");
155 	AddItemMenu(pmenu, "Open", MSG_FILE_OPEN, 'O', 0, 'A', true);
156 	pmenu->AddSeparatorItem();
157 	AddItemMenu(pmenu, "Dia Show", MSG_DIA_SHOW, 0, 0, 'W', true);
158 	BMenu* pDelay = new BMenu("Delay");
159 	pDelay->SetRadioMode(true);
160 	// Note: ShowImage loades images in window thread so it becomes unresponsive if
161 	// dia show delay is too short! (Especially if loading the image takes as long as
162 	// or longer than the dia show delay). Should load in background thread!
163 	// AddDelayItem(pDelay, "Half a Second", 0.5, false);
164 	// AddDelayItem(pDelay, "One Second", 1, false);
165 	// AddDelayItem(pDelay, "Two Second", 2, false);
166 	AddDelayItem(pDelay, "Three Seconds", 3, true);
167 	AddDelayItem(pDelay, "Four Second", 4, false);
168 	AddDelayItem(pDelay, "Five Seconds", 5, false);
169 	AddDelayItem(pDelay, "Six Seconds", 6, false);
170 	AddDelayItem(pDelay, "Seven Seconds", 7, false);
171 	AddDelayItem(pDelay, "Eight Seconds", 8, false);
172 	AddDelayItem(pDelay, "Nine Seconds", 9, false);
173 	AddDelayItem(pDelay, "Ten Seconds", 10, false);
174 	AddDelayItem(pDelay, "Tweenty Seconds", 20, false);
175 	pmenu->AddItem(pDelay);
176 	pmenu->AddSeparatorItem();
177 	AddItemMenu(pmenu, "Next", MSG_FILE_NEXT, B_DOWN_ARROW, 0, 'W', true);
178 	AddItemMenu(pmenu, "Previous", MSG_FILE_PREV, B_UP_ARROW, 0, 'W', true);
179 	pmenu->AddSeparatorItem();
180 	BMenu *pmenuSaveAs = new BMenu("Save As...", B_ITEMS_IN_COLUMN);
181 	BTranslationUtils::AddTranslationItems(pmenuSaveAs, B_TRANSLATOR_BITMAP);
182 		// Fill Save As submenu with all types that can be converted
183 		// to from the Be bitmap image format
184 	pmenu->AddItem(pmenuSaveAs);
185 	AddItemMenu(pmenu, "Close", MSG_CLOSE, 'W', 0, 'W', true);
186 	pmenu->AddSeparatorItem();
187 	AddItemMenu(pmenu, "About ShowImage...", B_ABOUT_REQUESTED, 0, 0, 'A', true);
188 	pmenu->AddSeparatorItem();
189 	AddItemMenu(pmenu, "Quit", B_QUIT_REQUESTED, 'Q', 0, 'A', true);
190 	pbar->AddItem(pmenu);
191 
192 	pmenu = new BMenu("Edit");
193 	AddItemMenu(pmenu, "Undo", B_UNDO, 'Z', 0, 'W', false);
194 	pmenu->AddSeparatorItem();
195 	AddItemMenu(pmenu, "Cut", B_CUT, 'X', 0, 'W', false);
196 	AddItemMenu(pmenu, "Copy", B_COPY, 'C', 0, 'W', false);
197 	AddItemMenu(pmenu, "Paste", B_PASTE, 'V', 0, 'W', false);
198 	AddItemMenu(pmenu, "Clear", MSG_CLEAR_SELECT, 0, 0, 'W', false);
199 	pmenu->AddSeparatorItem();
200 	AddItemMenu(pmenu, "Select All", MSG_SELECT_ALL, 'A', 0, 'W', false);
201 	pbar->AddItem(pmenu);
202 
203 	pmenu = fpPageMenu = new BMenu("Page");
204 	AddItemMenu(pmenu, "First", MSG_PAGE_FIRST, 'F', 0, 'W', true);
205 	AddItemMenu(pmenu, "Last", MSG_PAGE_LAST, 'L', 0, 'W', true);
206 	AddItemMenu(pmenu, "Next", MSG_PAGE_NEXT, 'N', 0, 'W', true);
207 	AddItemMenu(pmenu, "Previous", MSG_PAGE_PREV, 'P', 0, 'W', true);
208 	pbar->AddItem(pmenu);
209 
210 	pmenu = new BMenu("Image");
211 	AddItemMenu(pmenu, "Dither Image", MSG_DITHER_IMAGE, 0, 0, 'W', true);
212 	pmenu->AddSeparatorItem();
213 	AddItemMenu(pmenu, "Rotate Clockwise", MSG_ROTATE_CLOCKWISE, B_RIGHT_ARROW, 0, 'W', true);
214 	AddItemMenu(pmenu, "Rotate Anticlockwise", MSG_ROTATE_ACLKWISE, B_LEFT_ARROW, 0, 'W', true);
215 	pmenu->AddSeparatorItem();
216 	AddItemMenu(pmenu, "Mirror Vertical", MSG_MIRROR_VERTICAL, 0, 0, 'W', true);
217 	AddItemMenu(pmenu, "Mirror Horizontal", MSG_MIRROR_HORIZONTAL, 0, 0, 'W', true);
218 	pmenu->AddSeparatorItem();
219 	AddItemMenu(pmenu, "Invert", MSG_INVERT, 0, 0, 'W', true);
220 	pbar->AddItem(pmenu);
221 
222 	pmenu = new BMenu("View");
223 	AddItemMenu(pmenu, "Fit To Window Size", MSG_FIT_TO_WINDOW_SIZE, 0, 0, 'W', true);
224 	AddItemMenu(pmenu, "Full Screen", MSG_FULL_SCREEN, B_ENTER, 0, 'W', true);
225 	pbar->AddItem(pmenu);
226 }
227 
228 BMenuItem *
229 ShowImageWindow::AddItemMenu(BMenu *pmenu, char *caption, long unsigned int msg,
230 	char shortcut, uint32 modifier, char target, bool benabled)
231 {
232 	BMenuItem* pitem;
233 	pitem = new BMenuItem(caption, new BMessage(msg), shortcut);
234 
235 	if (target == 'A')
236 	   pitem->SetTarget(be_app);
237 
238 	pitem->SetEnabled(benabled);
239 	pmenu->AddItem(pitem);
240 
241 	return pitem;
242 }
243 
244 BMenuItem*
245 ShowImageWindow::AddDelayItem(BMenu *pmenu, char *caption, float value, bool marked)
246 {
247 	BMenuItem* pitem;
248 	BMessage* pmsg;
249 	pmsg = new BMessage(MSG_DIA_SHOW_DELAY);
250 	pmsg->AddFloat("value", value);
251 
252 	pitem = new BMenuItem(caption, pmsg, 0);
253 
254 	if (marked) pitem->SetMarked(true);
255 	pmenu->AddItem(pitem);
256 
257 	return pitem;
258 }
259 
260 void
261 ShowImageWindow::WindowRedimension(BBitmap *pbitmap)
262 {
263 	// set the window's min & max size limits
264 	// based on document's data bounds
265 	float maxWidth = pbitmap->Bounds().Width() + B_V_SCROLL_BAR_WIDTH;
266 	float maxHeight = pbitmap->Bounds().Height() + fpBar->Frame().Height() +
267 		B_H_SCROLL_BAR_HEIGHT + 1;
268 	float minWidth = min(maxWidth, 100.0f);
269 	float minHeight = min(maxHeight, 100.0f);
270 
271 	// adjust the window's current size based on new min/max values
272 	float curWidth = Bounds().Width();
273 	float curHeight = Bounds().Height();
274 	if (curWidth < minWidth)
275 		curWidth = minWidth;
276 	else if (curWidth > maxWidth)
277 		curWidth = maxWidth;
278 
279 	if (curHeight < minHeight)
280 		curHeight = minHeight;
281 	else if (curHeight > maxHeight)
282 		curHeight = maxHeight;
283 
284 	if (minWidth < 250)
285 		minWidth = 250;
286 
287 	SetSizeLimits(minWidth, maxWidth, minHeight, maxHeight);
288 	ResizeTo(curWidth, curHeight);
289 }
290 
291 void
292 ShowImageWindow::FrameResized(float width, float height)
293 {
294 }
295 
296 bool
297 ShowImageWindow::ToggleMenuItem(uint32 what)
298 {
299 	BMenuItem *item;
300 	bool marked = false;
301 	item = fpBar->FindItem(what);
302 	if (item != NULL) {
303 		marked = !item->IsMarked();
304 		item->SetMarked(marked);
305 	}
306 	return marked;
307 }
308 
309 void
310 ShowImageWindow::MessageReceived(BMessage *pmsg)
311 {
312 	switch (pmsg->what) {
313 		case MSG_OUTPUT_TYPE:
314 			// User clicked Save As then choose an output format
315 			if (!fpSavePanel)
316 				// If user doesn't already have a save panel open
317 				SaveAs(pmsg);
318 			break;
319 
320 		case MSG_SAVE_PANEL:
321 			// User specified where to save the output image
322 			SaveToFile(pmsg);
323 			break;
324 
325 		case MSG_CLOSE:
326 			if (CanQuit())
327 				Quit();
328 			break;
329 
330 		case B_CANCEL:
331 			delete fpSavePanel;
332 			fpSavePanel = NULL;
333 			break;
334 
335 		case MSG_UPDATE_STATUS:
336 		{
337 			bool benable = (fpImageView->PageCount() > 1) ? true : false;
338 			if (fpPageMenu->IsEnabled() != benable)
339 				// Only call this function if the state is changing
340 				// to avoid flickering
341 				fpPageMenu->SetEnabled(benable);
342 
343 			BString str;
344 			if (pmsg->FindString("status", &str) == B_OK)
345 				fpStatusView->SetText(str);
346 
347 			entry_ref ref;
348 			if (pmsg->FindRef("ref", &ref) == B_OK) {
349 				SetRef(&ref);
350 				UpdateTitle();
351 			}
352 			break;
353 		}
354 
355 		case B_UNDO:
356 			break;
357 		case B_CUT:
358 			break;
359 		case B_COPY:
360 			break;
361 		case B_PASTE:
362 			break;
363 		case MSG_CLEAR_SELECT:
364 			break;
365 		case MSG_SELECT_ALL:
366 			break;
367 
368 		case MSG_PAGE_FIRST:
369 			fpImageView->FirstPage();
370 			break;
371 
372 		case MSG_PAGE_LAST:
373 			fpImageView->LastPage();
374 			break;
375 
376 		case MSG_PAGE_NEXT:
377 			fpImageView->NextPage();
378 			break;
379 
380 		case MSG_PAGE_PREV:
381 			fpImageView->PrevPage();
382 			break;
383 
384 		case MSG_DITHER_IMAGE:
385 			ToggleMenuItem(pmsg->what);
386 			break;
387 
388 		case MSG_FIT_TO_WINDOW_SIZE:
389 			{
390 				bool resize;
391 				resize = ToggleMenuItem(pmsg->what);
392 				fpImageView->ResizeToViewBounds(resize);
393 			}
394 			break;
395 
396 		case MSG_FILE_PREV:
397 			fpImageView->PrevFile();
398 			break;
399 
400 		case MSG_FILE_NEXT:
401 			fpImageView->NextFile();
402 			break;
403 
404 		case MSG_ROTATE_CLOCKWISE:
405 			fpImageView->Rotate(90);
406 			break;
407 		case MSG_ROTATE_ACLKWISE:
408 			fpImageView->Rotate(270);
409 			break;
410 		case MSG_MIRROR_VERTICAL:
411 			fpImageView->Mirror(true);
412 			break;
413 		case MSG_MIRROR_HORIZONTAL:
414 			fpImageView->Mirror(false);
415 			break;
416 		case MSG_INVERT:
417 			fpImageView->Invert();
418 			break;
419 		case MSG_DIA_SHOW:
420 			if (ToggleMenuItem(pmsg->what)) {
421 				fpImageView->StartDiaShow();
422 			} else {
423 				fpImageView->StopDiaShow();
424 			}
425 		case MSG_DIA_SHOW_DELAY:
426 			{
427 				float value;
428 				if (pmsg->FindFloat("value", &value) == B_OK) {
429 					fpImageView->SetDiaShowDelay(value);
430 				}
431 			}
432 			break;
433 
434 		case MSG_FULL_SCREEN:
435 			ToggleFullScreen();
436 			break;
437 
438 		default:
439 			BWindow::MessageReceived(pmsg);
440 			break;
441 	}
442 }
443 
444 void
445 ShowImageWindow::SaveAs(BMessage *pmsg)
446 {
447 	// Read the translator and output type the user chose
448 	translator_id outTranslator;
449 	uint32 outType;
450 	if (pmsg->FindInt32(TRANSLATOR_FLD,
451 		reinterpret_cast<int32 *>(&outTranslator)) != B_OK)
452 		return;
453 	if (pmsg->FindInt32(TYPE_FLD,
454 		reinterpret_cast<int32 *>(&outType)) != B_OK)
455 		return;
456 
457 	// Add the chosen translator and output type to the
458 	// message that the save panel will send back
459 	BMessage *ppanelMsg = new BMessage(MSG_SAVE_PANEL);
460 	ppanelMsg->AddInt32(TRANSLATOR_FLD, outTranslator);
461 	ppanelMsg->AddInt32(TYPE_FLD, outType);
462 
463 	// Create save panel and show it
464 	fpSavePanel = new BFilePanel(B_SAVE_PANEL, new BMessenger(this), NULL, 0,
465 		false, ppanelMsg);
466 	if (!fpSavePanel)
467 		return;
468 	fpSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE);
469 	fpSavePanel->Show();
470 }
471 
472 void
473 ShowImageWindow::SaveToFile(BMessage *pmsg)
474 {
475 	// Read in where the file should be saved
476 	entry_ref dirref;
477 	if (pmsg->FindRef("directory", &dirref) != B_OK)
478 		return;
479 	const char *filename;
480 	if (pmsg->FindString("name", &filename) != B_OK)
481 		return;
482 
483 	// Read in the translator and type to be used
484 	// to save the output image
485 	translator_id outTranslator;
486 	uint32 outType;
487 	if (pmsg->FindInt32(TRANSLATOR_FLD,
488 		reinterpret_cast<int32 *>(&outTranslator)) != B_OK)
489 		return;
490 	if (pmsg->FindInt32(TYPE_FLD,
491 		reinterpret_cast<int32 *>(&outType)) != B_OK)
492 		return;
493 
494 	// Create the output file
495 	BDirectory dir(&dirref);
496 	BFile file(&dir, filename, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
497 	if (file.InitCheck() != B_OK)
498 		return;
499 
500 	// Translate the image and write it out to the output file
501 	BBitmapStream stream(fpImageView->GetBitmap());
502 	BTranslatorRoster *proster = BTranslatorRoster::Default();
503 	if (proster->Translate(outTranslator, &stream, NULL,
504 		&file, outType) != B_OK) {
505 		BAlert *palert = new BAlert(NULL, "Error writing image file.", "Ok");
506 		palert->Go();
507 	}
508 
509 	BBitmap *pout = NULL;
510 	stream.DetachBitmap(&pout);
511 		// bitmap used by stream still belongs to the view,
512 		// detach so it doesn't get deleted
513 }
514 
515 bool
516 ShowImageWindow::CanQuit()
517 {
518 	if (fpSavePanel)
519 		// Don't allow this window to be closed if a save panel is open
520 		return false;
521 	else
522 		return true;
523 }
524 
525 void
526 ShowImageWindow::ToggleFullScreen()
527 {
528 	BRect frame;
529 	fFullScreen = !fFullScreen;
530 	if (fFullScreen) {
531 		BScreen screen;
532 		fWindowFrame = Frame();
533 		frame = screen.Frame();
534 		frame.top -= fpBar->Bounds().Height()+1;
535 		frame.right += B_V_SCROLL_BAR_WIDTH;
536 		frame.bottom += B_H_SCROLL_BAR_HEIGHT;
537 		frame.InsetBy(-1, -1); // PEN_SIZE in ShowImageView
538 
539 		SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE);
540 	} else {
541 		frame = fWindowFrame;
542 
543 		SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
544 	}
545 	MoveTo(frame.left, frame.top);
546 	ResizeTo(frame.Width(), frame.Height());
547 }
548 
549 bool
550 ShowImageWindow::QuitRequested()
551 {
552 	return CanQuit();
553 }
554 
555 void
556 ShowImageWindow::Quit()
557 {
558 	// tell the app to forget about this window
559 	be_app->PostMessage(MSG_WINDOW_QUIT);
560 	BWindow::Quit();
561 }
562 
563