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