xref: /haiku/src/apps/screenshot/ScreenshotWindow.cpp (revision 6bcf4a432e32a639558020dba35f98e4edc24003)
1 /*
2  * Copyright Karsten Heimrich, host.haiku@gmx.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Karsten Heimrich
7  *		Fredrik Modéen
8  *		Christophe Huriaux
9  *		Wim van der Meer, WPJvanderMeer@gmail.com
10  */
11 
12 
13 #include "ScreenshotWindow.h"
14 
15 
16 #include <stdio.h>
17 #include <stdlib.h>
18 
19 
20 #include <Alert.h>
21 #include <Application.h>
22 #include <Bitmap.h>
23 #include <Box.h>
24 #include <BitmapStream.h>
25 #include <Button.h>
26 #include <CardLayout.h>
27 #include <Catalog.h>
28 #include <CheckBox.h>
29 #include <Directory.h>
30 #include <Entry.h>
31 #include <File.h>
32 #include <FindDirectory.h>
33 #include <FilePanel.h>
34 #include <GridLayoutBuilder.h>
35 #include <GroupLayoutBuilder.h>
36 #include <LayoutItem.h>
37 #include <Locale.h>
38 #include <Menu.h>
39 #include <MenuField.h>
40 #include <MenuItem.h>
41 #include <Message.h>
42 #include <NodeInfo.h>
43 #include <Path.h>
44 #include <RadioButton.h>
45 #include <Region.h>
46 #include <Roster.h>
47 #include <Screen.h>
48 #include <String.h>
49 #include <StringView.h>
50 #include <SpaceLayoutItem.h>
51 #include <TextControl.h>
52 #include <TranslatorFormats.h>
53 #include <TranslationUtils.h>
54 #include <TranslatorRoster.h>
55 #include <View.h>
56 #include <WindowInfo.h>
57 
58 
59 #include "PreviewView.h"
60 
61 
62 enum {
63 	kScreenshotType,
64 	kIncludeBorder,
65 	kShowMouse,
66 	kBackToSave,
67 	kTakeScreenshot,
68 	kImageOutputFormat,
69 	kLocationChanged,
70 	kChooseLocation,
71 	kFinishScreenshot,
72 	kShowOptions
73 };
74 
75 
76 // #pragma mark - DirectoryRefFilter
77 
78 
79 class DirectoryRefFilter : public BRefFilter {
80 public:
81 	virtual ~DirectoryRefFilter()
82 	{
83 	}
84 
85 	virtual bool Filter(const entry_ref* ref, BNode* node,
86 		struct stat_beos* stat, const char* filetype)
87 	{
88 		return node->IsDirectory();
89 	}
90 };
91 
92 
93 // #pragma mark - ScreenshotWindow
94 
95 
96 #undef TR_CONTEXT
97 #define TR_CONTEXT "ScreenshotWindow"
98 
99 
100 ScreenshotWindow::ScreenshotWindow()
101 	:
102 	BWindow(BRect(0, 0, 200.0, 100.0), B_TRANSLATE("Retake screenshot"),
103 		B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_NOT_RESIZABLE
104 		| B_QUIT_ON_WINDOW_CLOSE | B_AVOID_FRONT
105 		| B_AUTO_UPDATE_SIZE_LIMITS | B_CLOSE_ON_ESCAPE),
106 	fDelayControl(NULL),
107 	fScreenshot(NULL),
108 	fOutputPathPanel(NULL),
109 	fLastSelectedPath(NULL),
110 	fDelay(0),
111 	fTabHeight(0),
112 	fIncludeBorder(false),
113 	fIncludeMouse(false),
114 	fGrabActiveWindow(false),
115 	fShowConfigWindow(false),
116 	fSaveScreenshotSilent(false),
117 	fOutputFilename(NULL),
118 	fExtension(""),
119 	fImageFileType(B_PNG_FORMAT)
120 {
121 	BMessage settings = _ReadSettings();
122 
123 	if (settings.FindInt32("type", &fImageFileType) != B_OK)
124 		fImageFileType = B_PNG_FORMAT;
125 	settings.FindBool("includeBorder", &fIncludeBorder);
126 	settings.FindBool("includeMouse", &fIncludeMouse);
127 	settings.FindBool("grabActiveWindow", &fGrabActiveWindow);
128 	settings.FindInt64("delay", &fDelay);
129 	settings.FindString("outputFilename", &fOutputFilename);
130 
131 	_InitWindow(settings);
132 	CenterOnScreen();
133 	Show();
134 }
135 
136 
137 ScreenshotWindow::ScreenshotWindow(bigtime_t delay, bool includeBorder,
138 	bool includeMouse, bool grabActiveWindow, bool showConfigWindow,
139 	bool saveScreenshotSilent, int32 imageFileType, const char* outputFilename)
140 	:
141 	BWindow(BRect(0, 0, 200.0, 100.0), B_TRANSLATE("Retake screenshot"),
142 		B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_NOT_RESIZABLE
143 		| B_QUIT_ON_WINDOW_CLOSE | B_AVOID_FRONT
144 		| B_AUTO_UPDATE_SIZE_LIMITS | B_CLOSE_ON_ESCAPE),
145 	fDelayControl(NULL),
146 	fScreenshot(NULL),
147 	fOutputPathPanel(NULL),
148 	fLastSelectedPath(NULL),
149 	fDelay(delay),
150 	fTabHeight(0),
151 	fIncludeBorder(includeBorder),
152 	fIncludeMouse(includeMouse),
153 	fGrabActiveWindow(grabActiveWindow),
154 	fShowConfigWindow(showConfigWindow),
155 	fSaveScreenshotSilent(saveScreenshotSilent),
156 	fOutputFilename(outputFilename),
157 	fExtension(""),
158 	fImageFileType(imageFileType)
159 {
160 	if (fSaveScreenshotSilent) {
161 		_TakeScreenshot(fDelay);
162 		_SaveScreenshot();
163 		be_app_messenger.SendMessage(B_QUIT_REQUESTED);
164 	} else {
165 		BMessage settings = _ReadSettings();
166 		_InitWindow(settings);
167 		CenterOnScreen();
168 		Show();
169 	}
170 }
171 
172 
173 ScreenshotWindow::~ScreenshotWindow()
174 {
175 	if (fOutputPathPanel)
176 		delete fOutputPathPanel->RefFilter();
177 
178 	delete fScreenshot;
179 	delete fOutputPathPanel;
180 }
181 
182 
183 void
184 ScreenshotWindow::MessageReceived(BMessage* message)
185 {
186 //	message->PrintToStream();
187 	switch (message->what) {
188 		case kScreenshotType:
189 			fGrabActiveWindow = false;
190 			if (fActiveWindow->Value() == B_CONTROL_ON)
191 				fGrabActiveWindow = true;
192 			fWindowBorder->SetEnabled(fGrabActiveWindow);
193 			break;
194 
195 		case kIncludeBorder:
196 			fIncludeBorder = (fWindowBorder->Value() == B_CONTROL_ON);
197 			break;
198 
199 		case kShowMouse:
200 			fIncludeMouse = (fShowMouse->Value() == B_CONTROL_ON);
201 			break;
202 
203 		case kBackToSave:
204 		{
205 			BCardLayout* layout = dynamic_cast<BCardLayout*> (GetLayout());
206 			if (layout)
207 				layout->SetVisibleItem(1L);
208 
209 			fSaveScreenshot->MakeDefault(true);
210 
211 			SetTitle(B_TRANSLATE("Save screenshot"));
212 			break;
213 		}
214 
215 		case kTakeScreenshot:
216 			fDelay = (atoi(fDelayControl->Text()) * 1000000) + 50000;
217 			Hide();
218 			_TakeScreenshot(fDelay);
219 			_UpdatePreviewPanel();
220 			Show();
221 			_UpdateFilenameSelection();
222 			break;
223 
224 		case kImageOutputFormat:
225 			message->FindInt32("be:type", &fImageFileType);
226 			fNameControl->SetText(_FindValidFileName(
227 				fNameControl->Text()).String());
228 			_UpdateFilenameSelection();
229 			break;
230 
231 		case kLocationChanged:
232 		{
233 			void* source = NULL;
234 			if (message->FindPointer("source", &source) == B_OK)
235 				fLastSelectedPath = static_cast<BMenuItem*> (source);
236 
237 			fNameControl->SetText(_FindValidFileName(
238 				fNameControl->Text()).String());
239 			_UpdateFilenameSelection();
240 			break;
241 		}
242 
243 		case kChooseLocation:
244 		{
245 			if (!fOutputPathPanel) {
246 				BMessenger target(this);
247 				fOutputPathPanel = new BFilePanel(B_OPEN_PANEL, &target, NULL,
248 					B_DIRECTORY_NODE, false, NULL, new DirectoryRefFilter());
249 				fOutputPathPanel->Window()->SetTitle(
250 					B_TRANSLATE("Choose folder"));
251 				fOutputPathPanel->SetButtonLabel(B_DEFAULT_BUTTON,
252 					B_TRANSLATE("Select"));
253 				fOutputPathPanel->SetButtonLabel(B_CANCEL_BUTTON,
254 					B_TRANSLATE("Cancel"));
255 			}
256 			fOutputPathPanel->Show();
257 			break;
258 		}
259 
260 		case B_CANCEL:
261 			fLastSelectedPath->SetMarked(true);
262 			break;
263 
264 		case B_REFS_RECEIVED:
265 		{
266 			entry_ref ref;
267 			if (message->FindRef("refs", &ref) == B_OK) {
268 				BString path(BPath(&ref).Path());
269 				int32 index = _PathIndexInMenu(path);
270 				if (index < 0)
271 					_AddItemToPathMenu(path.String(), path,
272 						fOutputPathMenu->CountItems() - 2, true);
273 				else
274 					fOutputPathMenu->ItemAt(index)->SetMarked(true);
275 			}
276 			break;
277 		}
278 
279 		case kFinishScreenshot:
280 			_WriteSettings();
281 			if (_SaveScreenshot() != B_OK)
282 				break;
283 
284 		// fall through
285 		case B_QUIT_REQUESTED:
286 			_WriteSettings();
287 			be_app_messenger.SendMessage(B_QUIT_REQUESTED);
288 			break;
289 
290 		case kShowOptions:
291 		{
292 			BCardLayout* layout = dynamic_cast<BCardLayout*> (GetLayout());
293 
294 			if (layout)
295 				layout->SetVisibleItem(0L);
296 			SetDefaultButton(NULL);
297 
298 			SetTitle(B_TRANSLATE("Take Screenshot"));
299 			fBackToSave->SetEnabled(true);
300 			break;
301 		}
302 
303 		default:
304 			BWindow::MessageReceived(message);
305 			break;
306 	}
307 }
308 
309 
310 BPath
311 ScreenshotWindow::_GetDirectory()
312 {
313 	BPath path;
314 	if (!fSaveScreenshotSilent) {
315 		BMessage* message = fLastSelectedPath->Message();
316 		const char* stringPath;
317 		if (!message || message->FindString("path", &stringPath) != B_OK) {
318 			fprintf(stderr, "failed to find path in message\n");
319 		} else
320 			path.SetTo(stringPath);
321 	} else {
322 		if (find_directory(B_USER_DIRECTORY, &path) != B_OK)
323 			fprintf(stderr, "failed to find user home folder\n");
324 	}
325 	return path;
326 }
327 
328 
329 void
330 ScreenshotWindow::_InitWindow(const BMessage& settings)
331 {
332 	BCardLayout* layout = new BCardLayout();
333 	SetLayout(layout);
334 
335 	_SetupFirstLayoutItem(layout);
336 	_SetupSecondLayoutItem(layout, settings);
337 
338 	if (!fShowConfigWindow) {
339 		_TakeScreenshot(0);
340 		_UpdatePreviewPanel();
341 		layout->SetVisibleItem(1L);
342 		fSaveScreenshot->MakeDefault(true);
343 	} else {
344 		layout->SetVisibleItem(0L);
345 		SetDefaultButton(NULL);
346 	}
347 }
348 
349 
350 void
351 ScreenshotWindow::_SetupFirstLayoutItem(BCardLayout* layout)
352 {
353 	BStringView* stringView = new BStringView("", B_TRANSLATE("Options"));
354 	stringView->SetFont(be_bold_font);
355 	stringView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
356 
357 	fActiveWindow = new BRadioButton(B_TRANSLATE("Capture active window"),
358 		new BMessage(kScreenshotType));
359 	fWholeDesktop = new BRadioButton(B_TRANSLATE("Capture entire screen"),
360 		new BMessage(kScreenshotType));
361 	fWholeDesktop->SetValue(B_CONTROL_ON);
362 
363 	BString delay;
364 	delay << fDelay / 1000000;
365 	fDelayControl = new BTextControl("",
366 		B_TRANSLATE("Take screenshot after a delay of"), delay.String(), NULL);
367 	_DisallowChar(fDelayControl->TextView());
368 	fDelayControl->TextView()->SetAlignment(B_ALIGN_RIGHT);
369 
370 	BStringView* stringView2 = new BStringView("", B_TRANSLATE("seconds"));
371 	stringView2->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
372 
373 	fWindowBorder = new BCheckBox(B_TRANSLATE("Include window border"),
374 		new BMessage(kIncludeBorder));
375 	fWindowBorder->SetEnabled(false);
376 
377 	fShowMouse = new BCheckBox(B_TRANSLATE("Include mouse pointer"),
378 		new BMessage(kShowMouse));
379 	fShowMouse->SetValue(fIncludeMouse);
380 
381 	BBox* divider = new BBox(B_FANCY_BORDER, NULL);
382 	divider->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 1));
383 
384 	fBackToSave = new BButton("", B_TRANSLATE("Back to saving"),
385 		new BMessage(kBackToSave));
386 	fBackToSave->SetEnabled(false);
387 
388 	fTakeScreenshot = new BButton("", B_TRANSLATE("Take screenshot"),
389 		new BMessage(kTakeScreenshot));
390 
391 	layout->AddView(0, BGroupLayoutBuilder(B_VERTICAL)
392 		.Add(stringView)
393 		.Add(BGridLayoutBuilder()
394 			.Add(BSpaceLayoutItem::CreateHorizontalStrut(15.0), 0, 0)
395 			.Add(fWholeDesktop, 1, 0)
396 			.Add(BSpaceLayoutItem::CreateHorizontalStrut(15.0), 0, 1)
397 			.Add(fActiveWindow, 1, 1)
398 			.SetInsets(0.0, 5.0, 0.0, 0.0))
399 		.AddGroup(B_HORIZONTAL)
400 			.AddStrut(30.0)
401 			.Add(fWindowBorder)
402 			.End()
403 		.AddStrut(10.0)
404 		.AddGroup(B_HORIZONTAL)
405 			.AddStrut(15.0)
406 			.Add(fShowMouse)
407 			.End()
408 		.AddStrut(5.0)
409 		.AddGroup(B_HORIZONTAL, 5.0)
410 			.AddStrut(10.0)
411 			.Add(fDelayControl->CreateLabelLayoutItem())
412 			.Add(fDelayControl->CreateTextViewLayoutItem())
413 			.Add(stringView2)
414 			.End()
415 		.AddStrut(10.0)
416 		.AddGlue()
417 		.Add(divider)
418 		.AddStrut(10)
419 		.AddGroup(B_HORIZONTAL, 10.0)
420 			.Add(fBackToSave)
421 			.AddGlue()
422 			.Add(new BButton("", B_TRANSLATE("Cancel"),
423 				new BMessage(B_QUIT_REQUESTED)))
424 			.Add(fTakeScreenshot)
425 			.End()
426 		.SetInsets(10.0, 10.0, 10.0, 10.0)
427 	);
428 
429 	if (fGrabActiveWindow) {
430 		fWindowBorder->SetEnabled(true);
431 		fActiveWindow->SetValue(B_CONTROL_ON);
432 		fWindowBorder->SetValue(fIncludeBorder);
433 	}
434 }
435 
436 
437 void
438 ScreenshotWindow::_SetupSecondLayoutItem(BCardLayout* layout,
439 	const BMessage& settings)
440 {
441 	fPreview = new PreviewView();
442 
443 	fNameControl = new BTextControl("", B_TRANSLATE("Name:"),
444 		B_TRANSLATE_COMMENT("screenshot1", "!! Filename of first "
445 			"screenshot !!"), NULL);
446 
447 	_SetupOutputPathMenu(new BMenu(B_TRANSLATE("Please select")), settings);
448 
449 	BMenuField* menuField2 = new BMenuField(B_TRANSLATE("Save in:"),
450 		fOutputPathMenu);
451 
452 	fNameControl->SetText(_FindValidFileName(
453 		B_TRANSLATE_COMMENT("screenshot1", "!! Filename of first "
454 			"screenshot !!")).String());
455 
456 	_SetupTranslatorMenu(new BMenu(B_TRANSLATE("Please select")));
457 	BMenuField* menuField = new BMenuField(B_TRANSLATE("Save as:"),
458 		fTranslatorMenu);
459 
460 	BBox* divider = new BBox(B_FANCY_BORDER, NULL);
461 	divider->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 1));
462 
463 	fSaveScreenshot  = new BButton("",
464 		B_TRANSLATE("Save"), new BMessage(kFinishScreenshot));
465 
466 	BGridLayout* gridLayout = BGridLayoutBuilder(0.0, 5.0)
467 		.Add(fNameControl->CreateLabelLayoutItem(), 0, 0)
468 		.Add(fNameControl->CreateTextViewLayoutItem(), 1, 0)
469 		.Add(menuField->CreateLabelLayoutItem(), 0, 1)
470 		.Add(menuField->CreateMenuBarLayoutItem(), 1, 1)
471 		.Add(menuField2->CreateLabelLayoutItem(), 0, 2)
472 		.Add(menuField2->CreateMenuBarLayoutItem(), 1, 2);
473 	gridLayout->SetMinColumnWidth(1,
474 		menuField->StringWidth("SomethingLongHere"));
475 
476 	layout->AddView(1, BGroupLayoutBuilder(B_VERTICAL)
477 		.Add(BGroupLayoutBuilder(B_HORIZONTAL, 10.0)
478 			.Add(fPreview)
479 			.AddGroup(B_VERTICAL)
480 				.Add(gridLayout->View())
481 				.AddGlue()
482 				.End())
483 		.AddStrut(10)
484 		.Add(divider)
485 		.AddStrut(10)
486 		.AddGroup(B_HORIZONTAL, 10.0)
487 			.Add(new BButton("", B_TRANSLATE("Options"),
488 				new BMessage(kShowOptions)))
489 			.AddGlue()
490 			.Add(new BButton("", B_TRANSLATE("Cancel"),
491 				new BMessage(B_QUIT_REQUESTED)))
492 			.Add(fSaveScreenshot)
493 			.End()
494 		.SetInsets(10.0, 10.0, 10.0, 10.0)
495 	);
496 }
497 
498 
499 void
500 ScreenshotWindow::_DisallowChar(BTextView* textView)
501 {
502 	for (uint32 i = 0; i < '0'; ++i)
503 		textView->DisallowChar(i);
504 
505 	for (uint32 i = '9' + 1; i < 255; ++i)
506 		textView->DisallowChar(i);
507 }
508 
509 
510 void
511 ScreenshotWindow::_SetupTranslatorMenu(BMenu* translatorMenu)
512 {
513 	fTranslatorMenu = translatorMenu;
514 
515 	BMessage message(kImageOutputFormat);
516 	fTranslatorMenu = new BMenu("Please select");
517 	BTranslationUtils::AddTranslationItems(fTranslatorMenu, B_TRANSLATOR_BITMAP,
518 		&message, NULL, NULL, NULL);
519 
520 	fTranslatorMenu->SetLabelFromMarked(true);
521 
522 	if (fTranslatorMenu->ItemAt(0))
523 		fTranslatorMenu->ItemAt(0)->SetMarked(true);
524 
525 	int32 imageFileType;
526 	for (int32 i = 0; i < fTranslatorMenu->CountItems(); ++i) {
527 		BMenuItem* item = fTranslatorMenu->ItemAt(i);
528 		if (item && item->Message()) {
529 			item->Message()->FindInt32("be:type", &imageFileType);
530 			if (fImageFileType == imageFileType) {
531 				item->SetMarked(true);
532 				MessageReceived(item->Message());
533 				break;
534 			}
535 		}
536 	}
537 }
538 
539 
540 void
541 ScreenshotWindow::_SetupOutputPathMenu(BMenu* outputPathMenu,
542 	const BMessage& settings)
543 {
544 	fOutputPathMenu = outputPathMenu;
545 	fOutputPathMenu->SetLabelFromMarked(true);
546 
547 	BString lastSelectedPath;
548 	settings.FindString("lastSelectedPath", &lastSelectedPath);
549 
550 	BPath path;
551 	find_directory(B_USER_DIRECTORY, &path);
552 
553 	BString label(B_TRANSLATE("Home folder"));
554 	_AddItemToPathMenu(path.Path(), label, 0,
555 		(path.Path() == lastSelectedPath));
556 
557 	path.Append("Desktop");
558 	label.SetTo(B_TRANSLATE("Desktop"));
559 	_AddItemToPathMenu(path.Path(), label, 0, (
560 		path.Path() == lastSelectedPath));
561 
562 	find_directory(B_BEOS_ETC_DIRECTORY, &path);
563 	path.Append("artwork");
564 
565 	label.SetTo(B_TRANSLATE("Artwork folder"));
566 	_AddItemToPathMenu(path.Path(), label, 2,
567 		(path.Path() == lastSelectedPath));
568 
569 	int32 i = 0;
570 	BString userPath;
571 	while (settings.FindString("path", i++, &userPath) == B_OK) {
572 		_AddItemToPathMenu(userPath.String(), userPath, 3,
573 			(userPath == lastSelectedPath));
574 	}
575 
576 	if (!fLastSelectedPath) {
577 		if (settings.IsEmpty() || lastSelectedPath.Length() == 0) {
578 			fOutputPathMenu->ItemAt(1)->SetMarked(true);
579 			fLastSelectedPath = fOutputPathMenu->ItemAt(1);
580 		} else
581 			_AddItemToPathMenu(lastSelectedPath.String(), lastSelectedPath, 3,
582 				true);
583 	}
584 
585 	fOutputPathMenu->AddItem(new BSeparatorItem());
586 	fOutputPathMenu->AddItem(new BMenuItem(B_TRANSLATE("Choose folder..."),
587 		new BMessage(kChooseLocation)));
588 }
589 
590 
591 void
592 ScreenshotWindow::_AddItemToPathMenu(const char* path, BString& label,
593 	int32 index, bool markItem)
594 {
595 	BMessage* message = new BMessage(kLocationChanged);
596 	message->AddString("path", path);
597 
598 	fOutputPathMenu->TruncateString(&label, B_TRUNCATE_MIDDLE,
599 		fOutputPathMenu->StringWidth("SomethingLongHere"));
600 
601 	fOutputPathMenu->AddItem(new BMenuItem(label.String(), message), index);
602 
603 	if (markItem) {
604 		fOutputPathMenu->ItemAt(index)->SetMarked(true);
605 		fLastSelectedPath = fOutputPathMenu->ItemAt(index);
606 	}
607 }
608 
609 
610 void
611 ScreenshotWindow::_UpdatePreviewPanel()
612 {
613 	float height = 150.0f;
614 
615 	float width = (fScreenshot->Bounds().Width()
616 		/ fScreenshot->Bounds().Height()) * height;
617 
618 	// to prevent a preview way too wide
619 	if (width > 400.0f) {
620 		width = 400.0f;
621 		height = (fScreenshot->Bounds().Height()
622 			/ fScreenshot->Bounds().Width()) * width;
623 	}
624 
625 	fPreview->SetExplicitMinSize(BSize(width, height));
626 	fPreview->SetExplicitMaxSize(BSize(width, height));
627 
628 	fPreview->ClearViewBitmap();
629 	fPreview->SetViewBitmap(fScreenshot, fScreenshot->Bounds(),
630 		fPreview->Bounds(), B_FOLLOW_ALL, 0);
631 
632 	BCardLayout* layout = dynamic_cast<BCardLayout*> (GetLayout());
633 	if (layout)
634 		layout->SetVisibleItem(1L);
635 
636 	_UpdateFilenameSelection();
637 
638 	SetTitle(B_TRANSLATE("Save screenshot"));
639 }
640 
641 
642 void
643 ScreenshotWindow::_UpdateFilenameSelection()
644 {
645 	fNameControl->MakeFocus(true);
646 	fNameControl->TextView()->Select(0,
647 		fNameControl->TextView()->TextLength()
648 			- fExtension.Length());
649 	fNameControl->TextView()->ScrollToSelection();
650 }
651 
652 
653 BString
654 ScreenshotWindow::_FindValidFileName(const char* name)
655 {
656 	BString baseName(name);
657 
658 	if (fExtension.Compare(""))
659 		baseName.RemoveLast(fExtension);
660 
661 	if (!fSaveScreenshotSilent && !fLastSelectedPath)
662 		return baseName;
663 
664 	BPath orgPath(_GetDirectory());
665 	if (orgPath == NULL)
666 		return baseName;
667 
668 	BTranslatorRoster* roster = BTranslatorRoster::Default();
669 
670 	translator_id id = 0;
671 	if (_FindTranslator(fImageFileType, &id) == B_OK) {
672 
673 		const translation_format* formats = NULL;
674 
675 		int32 numFormats;
676 		if (roster->GetOutputFormats(id, &formats, &numFormats) == B_OK) {
677 			for (int32 i = 0; i < numFormats; ++i) {
678 				if (formats[i].type == uint32(fImageFileType)) {
679 					BMimeType mimeType(formats[i].MIME);
680 					BMessage msgExtensions;
681 					if (mimeType.GetFileExtensions(&msgExtensions) == B_OK) {
682 						const char* extension;
683 						if (msgExtensions.FindString("extensions", 0,
684 							&extension) == B_OK) {
685 							fExtension.SetTo(extension);
686 							fExtension.Prepend(".");
687 						} else
688 							fExtension.SetTo("");
689 					}
690 					break;
691 				}
692 			}
693 		}
694 	}
695 
696 	BPath outputPath = orgPath;
697 	BString fileName;
698 	fileName << baseName << fExtension;
699 	outputPath.Append(fileName);
700 
701 	if (!BEntry(outputPath.Path()).Exists())
702 		return fileName;
703 
704 	if (baseName.FindFirst(B_TRANSLATE_COMMENT("screenshot",
705 		"!! Basename of screenshot files. !!")) == 0)
706 		baseName.SetTo(B_TRANSLATE_COMMENT("screenshot",
707 			"!! Basename of screenshot files. !!"));
708 
709 	BEntry entry;
710 	int32 index = 1;
711 	char filename[B_FILE_NAME_LENGTH];
712 	do {
713 		sprintf(filename, "%s%ld%s", baseName.String(), index++,
714 			fExtension.String());
715 		outputPath.SetTo(orgPath.Path());
716 		outputPath.Append(filename);
717 		entry.SetTo(outputPath.Path());
718 	} while (entry.Exists());
719 
720 	return BString(filename);
721 }
722 
723 
724 int32
725 ScreenshotWindow::_PathIndexInMenu(const BString& path) const
726 {
727 	BString userPath;
728 	for (int32 i = 0; i < fOutputPathMenu->CountItems(); ++i) {
729 		BMenuItem* item = fOutputPathMenu->ItemAt(i);
730 		if (item && item->Message()
731 			&& item->Message()->FindString("path", &userPath) == B_OK) {
732 			if (userPath == path)
733 				return i;
734 		}
735 	}
736 	return -1;
737 }
738 
739 
740 BMessage
741 ScreenshotWindow::_ReadSettings() const
742 {
743 	BMessage settings;
744 
745 	BPath settingsPath;
746 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &settingsPath) != B_OK)
747 		return settings;
748 	settingsPath.Append("Screenshot_settings");
749 
750 	BFile file(settingsPath.Path(), B_READ_ONLY);
751 	if (file.InitCheck() == B_OK)
752 		settings.Unflatten(&file);
753 
754 	return settings;
755 }
756 
757 
758 void
759 ScreenshotWindow::_WriteSettings() const
760 {
761 	BMessage settings;
762 
763 	settings.AddInt32("type", fImageFileType);
764 	settings.AddBool("includeBorder", fIncludeBorder);
765 	settings.AddBool("includeMouse", fIncludeMouse);
766 	settings.AddBool("grabActiveWindow", fGrabActiveWindow);
767 	settings.AddInt64("delay", fDelay);
768 	settings.AddString("outputFilename", fOutputFilename);
769 
770 	BString path;
771 	int32 count = fOutputPathMenu->CountItems();
772 	if (count > 5) {
773 		for (int32 i = count - 3; i > count - 8 && i > 2; --i) {
774 			BMenuItem* item = fOutputPathMenu->ItemAt(i);
775 			if (item) {
776 				BMessage* msg = item->Message();
777 				if (msg && msg->FindString("path", &path) == B_OK)
778 					settings.AddString("path", path.String());
779 			}
780 		}
781 	}
782 
783 	if (fLastSelectedPath) {
784 		BMessage* msg = fLastSelectedPath->Message();
785 		if (msg && msg->FindString("path", &path) == B_OK)
786 			settings.AddString("lastSelectedPath", path.String());
787 	}
788 
789 	BPath settingsPath;
790 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &settingsPath) != B_OK)
791 		return;
792 	settingsPath.Append("Screenshot_settings");
793 
794 	BFile file(settingsPath.Path(), B_CREATE_FILE | B_ERASE_FILE
795 		| B_WRITE_ONLY);
796 	if (file.InitCheck() == B_OK) {
797 		ssize_t size;
798 		settings.Flatten(&file, &size);
799 	}
800 }
801 
802 
803 void
804 ScreenshotWindow::_TakeScreenshot(bigtime_t delay)
805 {
806 	if (delay > 0)
807 		snooze(delay);
808 
809 	BRect frame;
810 	delete fScreenshot;
811 	if (_GetActiveWindowFrame(&frame) == B_OK) {
812 		fScreenshot = new BBitmap(frame.OffsetToCopy(B_ORIGIN), B_RGBA32, true);
813 		BScreen(this).ReadBitmap(fScreenshot, fIncludeMouse, &frame);
814 		if (fIncludeBorder)
815 			_MakeTabSpaceTransparent(&frame);
816 	} else
817 		BScreen(this).GetBitmap(&fScreenshot, fIncludeMouse);
818 }
819 
820 
821 status_t
822 ScreenshotWindow::_GetActiveWindowFrame(BRect* frame)
823 {
824 	if (!fGrabActiveWindow || !frame)
825 		return B_ERROR;
826 
827 	int32* tokens;
828 	int32 tokenCount;
829 	status_t status = BPrivate::get_window_order(current_workspace(), &tokens,
830 		&tokenCount);
831 	if (status != B_OK || !tokens || tokenCount < 1)
832 		return B_ERROR;
833 
834 	status = B_ERROR;
835 	for (int32 i = 0; i < tokenCount; ++i) {
836 		client_window_info* windowInfo = get_window_info(tokens[i]);
837 		if (!windowInfo->is_mini && !windowInfo->show_hide_level > 0) {
838 			frame->left = windowInfo->window_left;
839 			frame->top = windowInfo->window_top;
840 			frame->right = windowInfo->window_right;
841 			frame->bottom = windowInfo->window_bottom;
842 
843 			status = B_OK;
844 			if (fIncludeBorder) {
845 				float border = (windowInfo->border_size);
846 				frame->InsetBy(-(border), -(border));
847 				frame->top -= windowInfo->tab_height;
848 				fTabHeight = windowInfo->tab_height;
849 			}
850 			free(windowInfo);
851 
852 			BRect screenFrame(BScreen(this).Frame());
853 			if (frame->left < screenFrame.left)
854 				frame->left = screenFrame.left;
855 			if (frame->top < screenFrame.top)
856 				frame->top = screenFrame.top;
857 			if (frame->right > screenFrame.right)
858 				frame->right = screenFrame.right;
859 			if (frame->bottom > screenFrame.bottom)
860 				frame->bottom = screenFrame.bottom;
861 
862 			break;
863 		}
864 		free(windowInfo);
865 	}
866 	free(tokens);
867 	return status;
868 }
869 
870 
871 status_t
872 ScreenshotWindow::_SaveScreenshot()
873 {
874 	if (!fScreenshot || (!fSaveScreenshotSilent && !fLastSelectedPath))
875 		return B_ERROR;
876 
877 	BPath path(_GetDirectory());
878 
879 	if (path == NULL)
880 		return B_ERROR;
881 
882 	if (fSaveScreenshotSilent) {
883 		if (!fOutputFilename.Compare("")) {
884 			path.Append(_FindValidFileName(B_TRANSLATE_COMMENT("screenshot1",
885 					"!! Filename of first screenshot !!")).String());
886 		} else
887 			path.SetTo(fOutputFilename);
888 	} else
889 		path.Append(fNameControl->Text());
890 
891 	BEntry entry;
892 	entry.SetTo(path.Path());
893 
894 	if (!fSaveScreenshotSilent) {
895 		if (entry.Exists()) {
896 			BAlert* overwriteAlert = new BAlert(
897 				B_TRANSLATE("overwrite"),
898 				B_TRANSLATE("This file already exists.\n Are you sure would "
899 					"you like to overwrite it?"),
900 				B_TRANSLATE("Cancel"),
901 				B_TRANSLATE("Overwrite"),
902 				NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT);
903 
904 				overwriteAlert->SetShortcut(0, B_ESCAPE);
905 
906 				if (overwriteAlert->Go() == 0)
907 					return B_CANCELED;
908 		}
909 	}
910 
911 	BFile file(&entry, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
912 	if (file.InitCheck() != B_OK)
913 		return B_ERROR;
914 
915 	BBitmapStream bitmapStream(fScreenshot);
916 	BTranslatorRoster* roster = BTranslatorRoster::Default();
917 	roster->Translate(&bitmapStream, NULL, NULL, &file, fImageFileType,
918 		B_TRANSLATOR_BITMAP);
919 	fScreenshot = NULL;
920 
921 	BNodeInfo nodeInfo(&file);
922 	if (nodeInfo.InitCheck() != B_OK)
923 		return B_ERROR;
924 
925 	translator_id id = 0;
926 	if (_FindTranslator(fImageFileType, &id) != B_OK)
927 		return B_ERROR;
928 
929 	int32 numFormats;
930 	const translation_format* formats = NULL;
931 
932 	if (roster->GetOutputFormats(id, &formats, &numFormats) != B_OK)
933 		return B_ERROR;
934 
935 	for (int32 i = 0; i < numFormats; ++i) {
936 		if (formats[i].type == uint32(fImageFileType)) {
937 			nodeInfo.SetType(formats[i].MIME);
938 			break;
939 		}
940 	}
941 
942 	return B_OK;
943 }
944 
945 
946 void
947 ScreenshotWindow::_MakeTabSpaceTransparent(BRect* frame)
948 {
949 	if (!frame)
950 		return;
951 
952 	if (fScreenshot->ColorSpace() != B_RGBA32)
953 		return;
954 
955 	BRect fullFrame = *frame;
956 
957 	BMessage message;
958 	BMessage reply;
959 
960 	app_info appInfo;
961 	if (be_roster->GetActiveAppInfo(&appInfo) != B_OK)
962 		return;
963 
964 	BMessenger messenger(appInfo.signature, appInfo.team);
965 	if (!messenger.IsValid())
966 		return;
967 
968 	bool foundActiveWindow = false;
969 	int32 index = 0;
970 
971 	while (true) {
972 		message.MakeEmpty();
973 		message.what = B_GET_PROPERTY;
974 		message.AddSpecifier("Active");
975 		message.AddSpecifier("Window", index);
976 
977 		reply.MakeEmpty();
978 		messenger.SendMessage(&message, &reply);
979 
980 		if (reply.what == B_MESSAGE_NOT_UNDERSTOOD)
981 			break;
982 
983 		bool result;
984 		if (reply.FindBool("result", &result) == B_OK) {
985 			foundActiveWindow = result;
986 
987 			if (foundActiveWindow)
988 				break;
989 		}
990 		index++;
991 	}
992 
993 	if (!foundActiveWindow)
994 		return;
995 
996 	message.MakeEmpty();
997 	message.what = B_GET_PROPERTY;
998 	message.AddSpecifier("TabFrame");
999 	message.AddSpecifier("Window", index);
1000 	reply.MakeEmpty();
1001 	messenger.SendMessage(&message, &reply);
1002 
1003 	BRect tabFrame;
1004 	if (reply.FindRect("result", &tabFrame) != B_OK)
1005 		return;
1006 
1007 	if (!fullFrame.Contains(tabFrame))
1008 		return;
1009 
1010 	BRegion tabSpace(fullFrame);
1011 	fullFrame.OffsetBy(0, fTabHeight);
1012 	tabSpace.Exclude(fullFrame);
1013 	tabSpace.Exclude(tabFrame);
1014 	fullFrame.OffsetBy(0, -fTabHeight);
1015 	tabSpace.OffsetBy(-fullFrame.left, -fullFrame.top);
1016 	BScreen screen;
1017 	BRect screenFrame = screen.Frame();
1018 	tabSpace.OffsetBy(-screenFrame.left, -screenFrame.top);
1019 
1020 	BView view(fScreenshot->Bounds(), "bitmap", B_FOLLOW_ALL_SIDES, 0);
1021 	fScreenshot->AddChild(&view);
1022 	if (view.Looper() && view.Looper()->Lock()) {
1023 		view.SetDrawingMode(B_OP_COPY);
1024 		view.SetHighColor(B_TRANSPARENT_32_BIT);
1025 
1026 		for (int i = 0; i < tabSpace.CountRects(); i++)
1027 			view.FillRect(tabSpace.RectAt(i));
1028 
1029 		view.Sync();
1030 		view.Looper()->Unlock();
1031 	}
1032 	fScreenshot->RemoveChild(&view);
1033 }
1034 
1035 
1036 status_t
1037 ScreenshotWindow::_FindTranslator(uint32 imageType, translator_id* id)
1038 {
1039 	translator_id* translators = NULL;
1040 	int32 numTranslators = 0;
1041 	BTranslatorRoster* roster = BTranslatorRoster::Default();
1042 	status_t status = roster->GetAllTranslators(&translators, &numTranslators);
1043 	if (status != B_OK)
1044 		return status;
1045 
1046 	status = B_ERROR;
1047 	for (int32 x = 0; x < numTranslators && status != B_OK; x++) {
1048 		int32 numFormats;
1049 		const translation_format* formats = NULL;
1050 
1051 		if (roster->GetOutputFormats(x, &formats, &numFormats) == B_OK) {
1052 			for (int32 i = 0; i < numFormats; ++i) {
1053 				if (formats[i].type == imageType) {
1054 					*id = x;
1055 					status = B_OK;
1056 					break;
1057 				}
1058 			}
1059 		}
1060 	}
1061 
1062 	delete [] translators;
1063 
1064 	return status;
1065 }
1066