xref: /haiku/src/apps/screenshot/ScreenshotWindow.cpp (revision 60c26cd332a044bb9003091b9196cc404ebe5482)
1 /*
2  * Copyright 2010 Wim van der Meer <WPJvanderMeer@gmail.com>
3  * Copyright Karsten Heimrich, host.haiku@gmx.de.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  *
6  * Authors:
7  *		Karsten Heimrich
8  *		Fredrik Modéen
9  *		Christophe Huriaux
10  *		Wim van der Meer
11  */
12 
13 
14 #include "ScreenshotWindow.h"
15 
16 #include <stdlib.h>
17 
18 #include <Alert.h>
19 #include <Application.h>
20 #include <Bitmap.h>
21 #include <Box.h>
22 #include <Button.h>
23 #include <Catalog.h>
24 #include <CheckBox.h>
25 #include <File.h>
26 #include <FilePanel.h>
27 #include <FindDirectory.h>
28 #include <GridLayoutBuilder.h>
29 #include <GroupLayoutBuilder.h>
30 #include <Locale.h>
31 #include <Menu.h>
32 #include <MenuField.h>
33 #include <MenuItem.h>
34 #include <Path.h>
35 #include <Roster.h>
36 #include <String.h>
37 #include <StringView.h>
38 #include <TextControl.h>
39 #include <TranslationUtils.h>
40 
41 #include "PreviewView.h"
42 #include "Utility.h"
43 
44 
45 enum {
46 	kActiveWindow,
47 	kIncludeBorder,
48 	kIncludeCursor,
49 	kNewScreenshot,
50 	kImageFormat,
51 	kLocationChanged,
52 	kChooseLocation,
53 	kSaveScreenshot
54 };
55 
56 
57 class DirectoryRefFilter : public BRefFilter {
58 public:
59 	virtual ~DirectoryRefFilter()
60 	{
61 	}
62 
63 	virtual bool Filter(const entry_ref* ref, BNode* node,
64 		struct stat_beos* stat, const char* filetype)
65 	{
66 		return node->IsDirectory();
67 	}
68 };
69 
70 
71 #undef B_TRANSLATE_CONTEXT
72 #define B_TRANSLATE_CONTEXT "ScreenshotWindow"
73 
74 
75 ScreenshotWindow::ScreenshotWindow(const Utility& utility, bool silent,
76 	bool clipboard)
77 	:
78 	BWindow(BRect(0, 0, 200.0, 100.0), B_TRANSLATE("Screenshot"),
79 		B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_AVOID_FRONT
80 		| B_QUIT_ON_WINDOW_CLOSE | B_AUTO_UPDATE_SIZE_LIMITS
81 		| B_CLOSE_ON_ESCAPE),
82 	fUtility(utility),
83 	fDelayControl(NULL),
84 	fScreenshot(NULL),
85 	fOutputPathPanel(NULL),
86 	fLastSelectedPath(NULL),
87 	fDelay(0),
88 	fIncludeBorder(false),
89 	fIncludeCursor(false),
90 	fGrabActiveWindow(false),
91 	fOutputFilename(NULL),
92 	fExtension(""),
93 	fImageFileType(B_PNG_FORMAT)
94 {
95 	// _ReadSettings() needs a valid fOutputPathMenu
96 	fOutputPathMenu = new BMenu(B_TRANSLATE("Please select"));
97 	_ReadSettings();
98 
99 	// _NewScreenshot() needs a valid fNameControl
100 	BString name(B_TRANSLATE(fUtility.sDefaultFileNameBase));
101 	name << 1;
102 	name = _FindValidFileName(name.String());
103 	fNameControl = new BTextControl("", B_TRANSLATE("Name:"), name, NULL);
104 
105 	// Check if fUtility contains valid data
106 	if (fUtility.wholeScreen == NULL) {
107 		_NewScreenshot(silent, clipboard);
108 		return;
109 	}
110 
111 	fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fGrabActiveWindow,
112 		fIncludeBorder);
113 
114 	fActiveWindow = new BCheckBox(B_TRANSLATE("Capture active window"),
115 		new BMessage(kActiveWindow));
116 	if (fGrabActiveWindow)
117 		fActiveWindow->SetValue(B_CONTROL_ON);
118 
119 	fWindowBorder = new BCheckBox(B_TRANSLATE("Include window border"),
120 		new BMessage(kIncludeBorder));
121 	if (fIncludeBorder)
122 		fWindowBorder->SetValue(B_CONTROL_ON);
123 	if (!fGrabActiveWindow)
124 		fWindowBorder->SetEnabled(false);
125 
126 	fShowCursor = new BCheckBox(B_TRANSLATE("Include mouse pointer"),
127 		new BMessage(kIncludeCursor));
128 	if (fIncludeCursor)
129 		fShowCursor->SetValue(B_CONTROL_ON);
130 
131 	BString delay;
132 	delay << fDelay / 1000000;
133 	fDelayControl = new BTextControl("", B_TRANSLATE("Delay:"), delay.String(),
134 		NULL);
135 	_DisallowChar(fDelayControl->TextView());
136 	fDelayControl->TextView()->SetAlignment(B_ALIGN_RIGHT);
137 	BStringView* seconds = new BStringView("", B_TRANSLATE("seconds"));
138 	seconds->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
139 
140 	BMenuField* menuField2 = new BMenuField(B_TRANSLATE("Save in:"),
141 		fOutputPathMenu);
142 
143 	fTranslatorMenu = new BMenu(B_TRANSLATE("Please select"));
144 	_SetupTranslatorMenu();
145 	BMenuField* menuField = new BMenuField(B_TRANSLATE("Save as:"),
146 		fTranslatorMenu);
147 
148 	BBox* divider = new BBox(B_FANCY_BORDER, NULL);
149 	divider->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 1));
150 
151 	BButton* saveScreenshot  = new BButton("", B_TRANSLATE("Save"),
152 		new BMessage(kSaveScreenshot));
153 
154 	fPreview = new PreviewView();
155 
156 	BGridLayout* gridLayout = BGridLayoutBuilder(0.0, 5.0)
157 		.Add(fNameControl->CreateLabelLayoutItem(), 0, 0)
158 		.Add(fNameControl->CreateTextViewLayoutItem(), 1, 0)
159 		.Add(menuField->CreateLabelLayoutItem(), 0, 1)
160 		.Add(menuField->CreateMenuBarLayoutItem(), 1, 1)
161 		.Add(menuField2->CreateLabelLayoutItem(), 0, 2)
162 		.Add(menuField2->CreateMenuBarLayoutItem(), 1, 2);
163 	gridLayout->SetMinColumnWidth(1,
164 		menuField->StringWidth("SomethingLongHere"));
165 
166 	SetLayout(new BGroupLayout(B_HORIZONTAL));
167 
168 	AddChild(BGroupLayoutBuilder(B_VERTICAL)
169 		.Add(BGroupLayoutBuilder(B_HORIZONTAL, 10.0)
170 			.Add(fPreview)
171 			.AddGroup(B_VERTICAL)
172 				.Add(fActiveWindow)
173 				.Add(fWindowBorder)
174 				.Add(fShowCursor)
175 				.AddGroup(B_HORIZONTAL, 5.0)
176 					.Add(fDelayControl->CreateLabelLayoutItem())
177 					.Add(fDelayControl->CreateTextViewLayoutItem())
178 					.Add(seconds)
179 					.End()
180 				.AddStrut(10.0)
181 				.Add(gridLayout->View())
182 				.AddGlue()
183 				.End())
184 		.AddStrut(10)
185 		.Add(divider)
186 		.AddStrut(10)
187 		.AddGroup(B_HORIZONTAL, 10.0)
188 			.Add(new BButton("", B_TRANSLATE("Copy to clipboard"),
189 				new BMessage(B_COPY)))
190 			.Add(new BButton("", B_TRANSLATE("New screenshot"),
191 				new BMessage(kNewScreenshot)))
192 			.AddGlue()
193 			.Add(saveScreenshot)
194 			.End()
195 		.SetInsets(10.0, 10.0, 10.0, 10.0)
196 	);
197 
198 	saveScreenshot->MakeDefault(true);
199 
200 	_UpdatePreviewPanel();
201 	_UpdateFilenameSelection();
202 
203 	CenterOnScreen();
204 	Show();
205 }
206 
207 
208 ScreenshotWindow::~ScreenshotWindow()
209 {
210 	if (fOutputPathPanel)
211 		delete fOutputPathPanel->RefFilter();
212 
213 	delete fOutputPathPanel;
214 	delete fScreenshot;
215 }
216 
217 
218 void
219 ScreenshotWindow::MessageReceived(BMessage* message)
220 {
221 	switch (message->what) {
222 		case kActiveWindow:
223 			fGrabActiveWindow = false;
224 			if (fActiveWindow->Value() == B_CONTROL_ON)
225 				fGrabActiveWindow = true;
226 
227 			fWindowBorder->SetEnabled(fGrabActiveWindow);
228 
229 			delete fScreenshot;
230 			fScreenshot = fUtility.MakeScreenshot(fIncludeCursor,
231 				fGrabActiveWindow, fIncludeBorder);
232 			_UpdatePreviewPanel();
233 			break;
234 
235 		case kIncludeBorder:
236 			fIncludeBorder = (fWindowBorder->Value() == B_CONTROL_ON);
237 			delete fScreenshot;
238 			fScreenshot = fUtility.MakeScreenshot(fIncludeCursor,
239 				fGrabActiveWindow, fIncludeBorder);
240 			_UpdatePreviewPanel();
241 			break;
242 
243 		case kIncludeCursor:
244 			fIncludeCursor = (fShowCursor->Value() == B_CONTROL_ON);
245 			delete fScreenshot;
246 			fScreenshot = fUtility.MakeScreenshot(fIncludeCursor,
247 				fGrabActiveWindow, fIncludeBorder);
248 			_UpdatePreviewPanel();
249 			break;
250 
251 		case kNewScreenshot:
252 			fDelay = (atoi(fDelayControl->Text()) * 1000000) + 50000;
253 			_NewScreenshot();
254 			break;
255 
256 		case kImageFormat:
257 			message->FindInt32("be:type", &fImageFileType);
258 			fNameControl->SetText(_FindValidFileName(
259 				fNameControl->Text()).String());
260 			_UpdateFilenameSelection();
261 			break;
262 
263 		case kLocationChanged:
264 		{
265 			void* source = NULL;
266 			if (message->FindPointer("source", &source) == B_OK)
267 				fLastSelectedPath = static_cast<BMenuItem*> (source);
268 
269 			fNameControl->SetText(_FindValidFileName(
270 				fNameControl->Text()).String());
271 
272 			_UpdateFilenameSelection();
273 			break;
274 		}
275 
276 		case kChooseLocation:
277 		{
278 			if (!fOutputPathPanel) {
279 				BMessenger target(this);
280 				fOutputPathPanel = new BFilePanel(B_OPEN_PANEL, &target, NULL,
281 					B_DIRECTORY_NODE, false, NULL, new DirectoryRefFilter());
282 				fOutputPathPanel->Window()->SetTitle(
283 					B_TRANSLATE("Choose folder"));
284 				fOutputPathPanel->SetButtonLabel(B_DEFAULT_BUTTON,
285 					B_TRANSLATE("Select"));
286 				fOutputPathPanel->SetButtonLabel(B_CANCEL_BUTTON,
287 					B_TRANSLATE("Cancel"));
288 			}
289 			fOutputPathPanel->Show();
290 			break;
291 		}
292 
293 		case B_CANCEL:
294 			fLastSelectedPath->SetMarked(true);
295 			break;
296 
297 		case kSaveScreenshot:
298 			if (_SaveScreenshot() == B_OK)
299 				be_app->PostMessage(B_QUIT_REQUESTED);
300 			break;
301 
302 		case B_COPY:
303 			fUtility.CopyToClipboard(fScreenshot);
304 			break;
305 
306 		default:
307 			BWindow::MessageReceived(message);
308 			break;
309 	}
310 }
311 
312 
313 void
314 ScreenshotWindow::Quit()
315 {
316 	if (fUtility.wholeScreen != NULL)
317 		_WriteSettings();
318 	BWindow::Quit();
319 }
320 
321 
322 void
323 ScreenshotWindow::_NewScreenshot(bool silent, bool clipboard)
324 {
325 	BMessage message(B_ARGV_RECEIVED);
326 	int32 argc = 3;
327 	BString delay;
328 	delay << fDelay / 1000000;
329 	message.AddString("argv", "screenshot");
330 	message.AddString("argv", "--delay");
331 	message.AddString("argv", delay);
332 
333 	if (silent || clipboard) {
334 		if (silent) {
335 			argc++;
336 			message.AddString("argv", "--silent");
337 		}
338 		if (clipboard) {
339 			argc++;
340 			message.AddString("argv", "--clipboard");
341 		}
342 		if (fIncludeBorder) {
343 			argc++;
344 			message.AddString("argv", "--border");
345 		}
346 		if (fIncludeCursor) {
347 			argc++;
348 			message.AddString("argv", "--mouse-pointer");
349 		}
350 		if (fGrabActiveWindow) {
351 			argc++;
352 			message.AddString("argv", "--window");
353 		}
354 		if (fLastSelectedPath) {
355 			BPath path(_GetDirectory());
356 			if (path != NULL) {
357 				path.Append(fNameControl->Text());
358 				argc++;
359 				message.AddString("argv", path.Path());
360 			}
361 		}
362 	}
363 	message.AddInt32("argc", argc);
364 
365 	be_roster->Launch("application/x-vnd.haiku-screenshot-cli", &message);
366 	be_app->PostMessage(B_QUIT_REQUESTED);
367 }
368 
369 
370 void
371 ScreenshotWindow::_UpdatePreviewPanel()
372 {
373 	float height = 150.0f;
374 
375 	float width = (fScreenshot->Bounds().Width()
376 		/ fScreenshot->Bounds().Height()) * height;
377 
378 	// to prevent a preview way too wide
379 	if (width > 400.0f) {
380 		width = 400.0f;
381 		height = (fScreenshot->Bounds().Height()
382 			/ fScreenshot->Bounds().Width()) * width;
383 	}
384 
385 	fPreview->SetExplicitMinSize(BSize(width, height));
386 
387 	fPreview->ClearViewBitmap();
388 	fPreview->SetViewBitmap(fScreenshot, fScreenshot->Bounds(),
389 		fPreview->Bounds(), B_FOLLOW_ALL, B_FILTER_BITMAP_BILINEAR);
390 }
391 
392 
393 void
394 ScreenshotWindow::_DisallowChar(BTextView* textView)
395 {
396 	for (uint32 i = 0; i < '0'; ++i)
397 		textView->DisallowChar(i);
398 
399 	for (uint32 i = '9' + 1; i < 255; ++i)
400 		textView->DisallowChar(i);
401 }
402 
403 
404 void
405 ScreenshotWindow::_SetupOutputPathMenu(const BMessage& settings)
406 {
407 	fOutputPathMenu->SetLabelFromMarked(true);
408 
409 	BString lastSelectedPath;
410 	settings.FindString("lastSelectedPath", &lastSelectedPath);
411 
412 	BPath path;
413 	find_directory(B_USER_DIRECTORY, &path);
414 
415 	BString label(B_TRANSLATE("Home folder"));
416 	_AddItemToPathMenu(path.Path(), label, 0,
417 		(path.Path() == lastSelectedPath));
418 
419 	path.Append("Desktop");
420 	label.SetTo(B_TRANSLATE("Desktop"));
421 	_AddItemToPathMenu(path.Path(), label, 0, (
422 		path.Path() == lastSelectedPath));
423 
424 	find_directory(B_BEOS_ETC_DIRECTORY, &path);
425 	path.Append("artwork");
426 
427 	label.SetTo(B_TRANSLATE("Artwork folder"));
428 	_AddItemToPathMenu(path.Path(), label, 2,
429 		(path.Path() == lastSelectedPath));
430 
431 	int32 i = 0;
432 	BString userPath;
433 	while (settings.FindString("path", ++i, &userPath) == B_OK) {
434 		_AddItemToPathMenu(userPath.String(), userPath, 3,
435 			(userPath == lastSelectedPath));
436 	}
437 
438 	if (!fLastSelectedPath) {
439 		if (settings.IsEmpty() || lastSelectedPath.Length() == 0) {
440 			fOutputPathMenu->ItemAt(1)->SetMarked(true);
441 			fLastSelectedPath = fOutputPathMenu->ItemAt(1);
442 		} else
443 			_AddItemToPathMenu(lastSelectedPath.String(), lastSelectedPath, 3,
444 				true);
445 	}
446 
447 	fOutputPathMenu->AddItem(new BSeparatorItem());
448 	fOutputPathMenu->AddItem(new BMenuItem(B_TRANSLATE("Choose folder..."),
449 		new BMessage(kChooseLocation)));
450 }
451 
452 
453 void
454 ScreenshotWindow::_AddItemToPathMenu(const char* path, BString& label,
455 	int32 index, bool markItem)
456 {
457 	BMessage* message = new BMessage(kLocationChanged);
458 	message->AddString("path", path);
459 
460 	fOutputPathMenu->TruncateString(&label, B_TRUNCATE_MIDDLE,
461 		fOutputPathMenu->StringWidth("SomethingLongHere"));
462 
463 	fOutputPathMenu->AddItem(new BMenuItem(label.String(), message), index);
464 
465 	if (markItem) {
466 		fOutputPathMenu->ItemAt(index)->SetMarked(true);
467 		fLastSelectedPath = fOutputPathMenu->ItemAt(index);
468 	}
469 }
470 
471 
472 void
473 ScreenshotWindow::_UpdateFilenameSelection()
474 {
475 	fNameControl->MakeFocus(true);
476 	fNameControl->TextView()->Select(0,	fNameControl->TextView()->TextLength()
477 		- fExtension.Length());
478 
479 	fNameControl->TextView()->ScrollToSelection();
480 }
481 
482 
483 void
484 ScreenshotWindow::_SetupTranslatorMenu()
485 {
486 	BMessage message(kImageFormat);
487 	fTranslatorMenu = new BMenu("Please select");
488 	BTranslationUtils::AddTranslationItems(fTranslatorMenu, B_TRANSLATOR_BITMAP,
489 		&message, NULL, NULL, NULL);
490 
491 	fTranslatorMenu->SetLabelFromMarked(true);
492 
493 	if (fTranslatorMenu->ItemAt(0))
494 		fTranslatorMenu->ItemAt(0)->SetMarked(true);
495 
496 	int32 imageFileType;
497 	for (int32 i = 0; i < fTranslatorMenu->CountItems(); ++i) {
498 		BMenuItem* item = fTranslatorMenu->ItemAt(i);
499 		if (item && item->Message()) {
500 			item->Message()->FindInt32("be:type", &imageFileType);
501 			if (fImageFileType == imageFileType) {
502 				item->SetMarked(true);
503 				MessageReceived(item->Message());
504 				break;
505 			}
506 		}
507 	}
508 }
509 
510 
511 status_t
512 ScreenshotWindow::_SaveScreenshot()
513 {
514 	if (!fScreenshot || !fLastSelectedPath)
515 		return B_ERROR;
516 
517 	BPath path(_GetDirectory());
518 
519 	if (path == NULL)
520 		return B_ERROR;
521 
522 	path.Append(fNameControl->Text());
523 
524 	BEntry entry;
525 	entry.SetTo(path.Path());
526 
527 	if (entry.Exists()) {
528 		BAlert* overwriteAlert = new BAlert(
529 			B_TRANSLATE("overwrite"),
530 			B_TRANSLATE("This file already exists.\n Are you sure would "
531 				"you like to overwrite it?"),
532 			B_TRANSLATE("Cancel"),
533 			B_TRANSLATE("Overwrite"),
534 			NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT);
535 
536 			overwriteAlert->SetShortcut(0, B_ESCAPE);
537 
538 			if (overwriteAlert->Go() == 0)
539 				return B_CANCELED;
540 	}
541 
542 	return fUtility.Save(&fScreenshot, path.Path(), fImageFileType);
543 }
544 
545 
546 BString
547 ScreenshotWindow::_FindValidFileName(const char* name)
548 {
549 	BString baseName(name);
550 
551 	if (fExtension.Compare(""))
552 		baseName.RemoveLast(fExtension);
553 
554 	if (!fLastSelectedPath)
555 		return baseName;
556 
557 	BPath orgPath(_GetDirectory());
558 	if (orgPath == NULL)
559 		return baseName;
560 
561 	fExtension = BString(fUtility.GetFileNameExtension(fImageFileType));
562 
563 	BPath outputPath = orgPath;
564 	BString fileName;
565 	fileName << baseName << fExtension;
566 	outputPath.Append(fileName);
567 
568 	if (!BEntry(outputPath.Path()).Exists())
569 		return fileName;
570 
571 	if (baseName.FindFirst(B_TRANSLATE(fUtility.sDefaultFileNameBase)) == 0)
572 		baseName.SetTo(fUtility.sDefaultFileNameBase);
573 
574 	BEntry entry;
575 	int32 index = 1;
576 
577 	do {
578 		fileName = "";
579 		fileName << baseName << index++ << fExtension;
580 		outputPath.SetTo(orgPath.Path());
581 		outputPath.Append(fileName);
582 		entry.SetTo(outputPath.Path());
583 	} while (entry.Exists());
584 
585 	return fileName;
586 }
587 
588 
589 BPath
590 ScreenshotWindow::_GetDirectory()
591 {
592 	BPath path;
593 
594 	BMessage* message = fLastSelectedPath->Message();
595 	const char* stringPath;
596 	if (message && message->FindString("path", &stringPath) == B_OK)
597 		path.SetTo(stringPath);
598 
599 	return path;
600 }
601 
602 
603 void
604 ScreenshotWindow::_ReadSettings()
605 {
606 	BMessage settings;
607 
608 	BPath settingsPath;
609 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &settingsPath) != B_OK)
610 		return;
611 
612 	settingsPath.Append("Screenshot_settings");
613 
614 	BFile file(settingsPath.Path(), B_READ_ONLY);
615 	if (file.InitCheck() == B_OK)
616 		settings.Unflatten(&file);
617 
618 	if (settings.FindInt32("type", &fImageFileType) != B_OK)
619 		fImageFileType = B_PNG_FORMAT;
620 	settings.FindBool("includeBorder", &fIncludeBorder);
621 	settings.FindBool("includeCursor", &fIncludeCursor);
622 	settings.FindBool("grabActiveWindow", &fGrabActiveWindow);
623 	settings.FindInt64("delay", &fDelay);
624 	settings.FindString("outputFilename", &fOutputFilename);
625 
626 	_SetupOutputPathMenu(settings);
627 }
628 
629 
630 void
631 ScreenshotWindow::_WriteSettings()
632 {
633 	if (fDelayControl)
634 		fDelay = (atoi(fDelayControl->Text()) * 1000000) + 50000;
635 
636 	BMessage settings;
637 
638 	settings.AddInt32("type", fImageFileType);
639 	settings.AddBool("includeBorder", fIncludeBorder);
640 	settings.AddBool("includeCursor", fIncludeCursor);
641 	settings.AddBool("grabActiveWindow", fGrabActiveWindow);
642 	settings.AddInt64("delay", fDelay);
643 	settings.AddString("outputFilename", fOutputFilename);
644 
645 	BString path;
646 	int32 count = fOutputPathMenu->CountItems();
647 	if (count > 5) {
648 		for (int32 i = count - 3; i > count - 8 && i > 2; --i) {
649 			BMenuItem* item = fOutputPathMenu->ItemAt(i);
650 			if (item) {
651 				BMessage* msg = item->Message();
652 				if (msg && msg->FindString("path", &path) == B_OK)
653 					settings.AddString("path", path.String());
654 			}
655 		}
656 	}
657 
658 	if (fLastSelectedPath) {
659 		BMessage* msg = fLastSelectedPath->Message();
660 		if (msg && msg->FindString("path", &path) == B_OK)
661 			settings.AddString("lastSelectedPath", path.String());
662 	}
663 
664 	BPath settingsPath;
665 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &settingsPath) != B_OK)
666 		return;
667 	settingsPath.Append("Screenshot_settings");
668 
669 	BFile file(settingsPath.Path(), B_CREATE_FILE | B_ERASE_FILE
670 		| B_WRITE_ONLY);
671 	if (file.InitCheck() == B_OK) {
672 		ssize_t size;
673 		settings.Flatten(&file, &size);
674 	}
675 }
676