xref: /haiku/src/preferences/virtualmemory/SettingsWindow.cpp (revision 7a74a5df454197933bc6e80a542102362ee98703)
1 /*
2  * Copyright 2010-2011, Hamish Morrison, hamish@lavabit.com
3  * Copyright 2005-2009, Axel Dörfler, axeld@pinc-software.de
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include "SettingsWindow.h"
9 #include "Settings.h"
10 
11 #include <Application.h>
12 #include <Alert.h>
13 #include <Box.h>
14 #include <Button.h>
15 #include <Catalog.h>
16 #include <CheckBox.h>
17 #include <GroupLayout.h>
18 #include <GroupLayoutBuilder.h>
19 #include <Locale.h>
20 #include <StringView.h>
21 #include <String.h>
22 #include <Slider.h>
23 #include <PopUpMenu.h>
24 #include <MenuItem.h>
25 #include <MenuField.h>
26 #include <Screen.h>
27 #include <FindDirectory.h>
28 #include <Path.h>
29 #include <Volume.h>
30 #include <VolumeRoster.h>
31 
32 #include <stdio.h>
33 
34 #undef B_TRANSLATION_CONTEXT
35 #define B_TRANSLATION_CONTEXT "SettingsWindow"
36 
37 
38 static const uint32 kMsgDefaults = 'dflt';
39 static const uint32 kMsgRevert = 'rvrt';
40 static const uint32 kMsgSliderUpdate = 'slup';
41 static const uint32 kMsgSwapEnabledUpdate = 'swen';
42 static const uint32 kMsgVolumeSelected = 'vlsl';
43 static const int64 kMegaByte = 1024 * 1024;
44 
45 
46 class SizeSlider : public BSlider {
47 	public:
48 		SizeSlider(const char* name, const char* label,
49 			BMessage* message, int32 min, int32 max, uint32 flags);
50 		virtual ~SizeSlider();
51 
52 		virtual const char* UpdateText() const;
53 
54 	private:
55 		mutable BString	fText;
56 };
57 
58 
59 SizeSlider::SizeSlider(const char* name, const char* label,
60 	BMessage* message, int32 min, int32 max, uint32 flags)
61 	: BSlider(name, label, message, min, max, B_HORIZONTAL, B_BLOCK_THUMB, flags)
62 {
63 	rgb_color color = ui_color(B_CONTROL_HIGHLIGHT_COLOR);
64 	UseFillColor(true, &color);
65 }
66 
67 
68 SizeSlider::~SizeSlider()
69 {
70 }
71 
72 
73 const char*
74 byte_string(int64 size)
75 {
76 	double value = 1. * size;
77 	static char string[256];
78 
79 	if (value < 1024)
80 		snprintf(string, sizeof(string), B_TRANSLATE("%Ld B"), size);
81 	else {
82 		static const char *units[] = {
83 			B_TRANSLATE_MARK("KB"),
84 			B_TRANSLATE_MARK("MB"),
85 			B_TRANSLATE_MARK("GB"),
86 			NULL
87 		};
88 		int32 i = -1;
89 
90 		do {
91 			value /= 1024.0;
92 			i++;
93 		} while (value >= 1024 && units[i + 1]);
94 
95 		off_t rounded = off_t(value * 100LL);
96 		snprintf(string, sizeof(string), "%g %s", rounded / 100.0,
97 			B_TRANSLATE_NOCOLLECT(units[i]));
98 	}
99 
100 	return string;
101 }
102 
103 
104 const char*
105 SizeSlider::UpdateText() const
106 {
107 	fText = byte_string(Value() * kMegaByte);
108 	return fText.String();
109 }
110 
111 
112 class VolumeMenuItem : public BMenuItem {
113 	public:
114 		VolumeMenuItem(const char* label, BMessage* message, BVolume* volume);
115 	    BVolume* Volume() { return fVolume; }
116 
117 	private:
118 		BVolume* fVolume;
119 };
120 
121 
122 VolumeMenuItem::VolumeMenuItem(const char* label, BMessage* message,
123 	BVolume* volume)
124 	: BMenuItem(label, message)
125 {
126 	fVolume = volume;
127 }
128 
129 
130 SettingsWindow::SettingsWindow()
131 	:
132 	BWindow(BRect(0, 0, 269, 172), B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
133 		B_TITLED_WINDOW, B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS
134 		| B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS),
135 	fLocked(false)
136 
137 {
138 	BView* view = new BGroupView();
139 
140 	fSwapEnabledCheckBox = new BCheckBox("enable swap",
141 		B_TRANSLATE("Enable virtual memory"),
142 		new BMessage(kMsgSwapEnabledUpdate));
143 
144 	BBox* box = new BBox("box", B_FOLLOW_LEFT_RIGHT);
145 	box->SetLabel(fSwapEnabledCheckBox);
146 
147 	system_info info;
148 	get_system_info(&info);
149 
150 	BString string = B_TRANSLATE("Physical memory: ");
151 	string << byte_string((off_t)info.max_pages * B_PAGE_SIZE);
152 	BStringView* memoryView = new BStringView("physical memory", string.String());
153 
154 	string = B_TRANSLATE("Current swap file size: ");
155 	string << byte_string(fSettings.SwapSize());
156 	BStringView* swapfileView = new BStringView("current swap size", string.String());
157 
158 	BPopUpMenu* menu = new BPopUpMenu("invalid");
159 
160 	// collect volumes
161 	// TODO: listen to volume changes!
162 	// TODO: accept dropped volumes
163 
164 	BVolumeRoster volumeRoster;
165 	BVolume* volume = new BVolume();
166 	char name[B_FILE_NAME_LENGTH];
167 	while (volumeRoster.GetNextVolume(volume) == B_OK) {
168 		if (!volume->IsPersistent() || volume->GetName(name) != B_OK || !name[0])
169 			continue;
170 		VolumeMenuItem* item = new VolumeMenuItem(name,
171 			new BMessage(kMsgVolumeSelected), volume);
172 		menu->AddItem(item);
173 		volume = new BVolume();
174 	}
175 
176 	fVolumeMenuField = new BMenuField("volumes", B_TRANSLATE("Use volume:"), menu);
177 
178 	fSizeSlider = new SizeSlider("size slider",
179 		B_TRANSLATE("Requested swap file size:"), new BMessage(kMsgSliderUpdate),
180 		1, 1, B_WILL_DRAW | B_FRAME_EVENTS);
181 	fSizeSlider->SetViewColor(255, 0, 255);
182 
183 	fWarningStringView = new BStringView("", "");
184 	fWarningStringView->SetAlignment(B_ALIGN_CENTER);
185 
186 	view->SetLayout(new BGroupLayout(B_HORIZONTAL));
187 	view->AddChild(BGroupLayoutBuilder(B_VERTICAL, 10)
188 		.AddGroup(B_HORIZONTAL)
189 			.Add(memoryView)
190 			.AddGlue()
191 		.End()
192 		.AddGroup(B_HORIZONTAL)
193 			.Add(swapfileView)
194 			.AddGlue()
195 		.End()
196 #ifdef SWAP_VOLUME_IMPLEMENTED
197 		.AddGroup(B_HORIZONTAL)
198 			.Add(fVolumeMenuField)
199 			.AddGlue()
200 		.End()
201 #else
202 		.AddGlue()
203 #endif
204 		.Add(fSizeSlider)
205 		.Add(fWarningStringView)
206 		.SetInsets(10, 10, 10, 10)
207 	);
208 	box->AddChild(view);
209 
210 	fDefaultsButton = new BButton("defaults", B_TRANSLATE("Defaults"),
211 		new BMessage(kMsgDefaults));
212 
213 	fRevertButton = new BButton("revert", B_TRANSLATE("Revert"),
214 		new BMessage(kMsgRevert));
215 	fRevertButton->SetEnabled(false);
216 
217 	SetLayout(new BGroupLayout(B_HORIZONTAL));
218 	AddChild(BGroupLayoutBuilder(B_VERTICAL, 10)
219 		.Add(box)
220 		.AddGroup(B_HORIZONTAL, 10)
221 			.Add(fDefaultsButton)
222 			.Add(fRevertButton)
223 			.AddGlue()
224 		.End()
225 		.SetInsets(10, 10, 10, 10)
226 	);
227 
228 	BScreen screen;
229 	BRect screenFrame = screen.Frame();
230 	if (!screenFrame.Contains(fSettings.WindowPosition()))
231 		CenterOnScreen();
232 	else
233 		MoveTo(fSettings.WindowPosition());
234 
235 #ifdef SWAP_VOLUME_IMPLEMENTED
236 	// Validate the volume specified in settings file
237 	status_t result = fSettings.SwapVolume().InitCheck();
238 
239 	if (result != B_OK) {
240 		BAlert* alert = new BAlert("VirtualMemory", B_TRANSLATE(
241 			"The swap volume specified in the settings file is invalid.\n"
242 			"You can keep the current setting or switch to the "
243 			"default swap volume."),
244 			B_TRANSLATE("Keep"), B_TRANSLATE("Switch"), NULL,
245 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
246 		alert->SetShortcut(0, B_ESCAPE);
247 		int32 choice = alert->Go();
248 		if (choice == 1) {
249 			BVolumeRoster volumeRoster;
250 			BVolume bootVolume;
251 			volumeRoster.GetBootVolume(&bootVolume);
252 			fSettings.SetSwapVolume(bootVolume);
253 		}
254 	}
255 #endif
256 
257 	_Update();
258 }
259 
260 
261 SettingsWindow::~SettingsWindow()
262 {
263 }
264 
265 
266 void
267 SettingsWindow::MessageReceived(BMessage* message)
268 {
269 	switch (message->what) {
270 		case kMsgRevert:
271 			fSettings.RevertSwapChanges();
272 			_Update();
273 			break;
274 		case kMsgDefaults:
275 			_SetSwapDefaults();
276 			_Update();
277 			break;
278 		case kMsgSliderUpdate:
279 			fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte);
280 			_Update();
281 			break;
282 		case kMsgVolumeSelected:
283 			fSettings.SetSwapVolume(*((VolumeMenuItem*)fVolumeMenuField->Menu()
284 				->FindMarked())->Volume());
285 			_Update();
286 			break;
287 		case kMsgSwapEnabledUpdate:
288 		{
289 			int32 value;
290 			if (message->FindInt32("be:value", &value) != B_OK)
291 				break;
292 
293 			if (value == 0) {
294 				// print out warning, give the user the time to think about it :)
295 				// ToDo: maybe we want to remove this possibility in the GUI
296 				// as Be did, but I thought a proper warning could be helpful
297 				// (for those that want to change that anyway)
298 				BAlert* alert = new BAlert("VirtualMemory",
299 					B_TRANSLATE(
300 					"Disabling virtual memory will have unwanted effects on "
301 					"system stability once the memory is used up.\n"
302 					"Virtual memory does not affect system performance "
303 					"until this point is reached.\n\n"
304 					"Are you really sure you want to turn it off?"),
305 					B_TRANSLATE("Turn off"), B_TRANSLATE("Keep enabled"), NULL,
306 					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
307 				alert->SetShortcut(1, B_ESCAPE);
308 				int32 choice = alert->Go();
309 				if (choice == 1) {
310 					fSwapEnabledCheckBox->SetValue(1);
311 					break;
312 				}
313 			}
314 
315 			fSettings.SetSwapEnabled(value != 0);
316 			_Update();
317 			break;
318 		}
319 
320 		default:
321 			BWindow::MessageReceived(message);
322 	}
323 }
324 
325 
326 bool
327 SettingsWindow::QuitRequested()
328 {
329 	fSettings.SetWindowPosition(Frame().LeftTop());
330 	be_app->PostMessage(B_QUIT_REQUESTED);
331 	return true;
332 }
333 
334 
335 void
336 SettingsWindow::_Update()
337 {
338 	if ((fSwapEnabledCheckBox->Value() != 0) != fSettings.SwapEnabled())
339 		fSwapEnabledCheckBox->SetValue(fSettings.SwapEnabled());
340 
341 #ifdef SWAP_VOLUME_IMPLEMENTED
342 	if (fVolumeMenuField->IsEnabled() != fSettings.SwapEnabled())
343 		fVolumeMenuField->SetEnabled(fSettings.SwapEnabled());
344 	VolumeMenuItem* selectedVolumeItem =
345 		(VolumeMenuItem*)fVolumeMenuField->Menu()->FindMarked();
346 	if (selectedVolumeItem == NULL) {
347 		VolumeMenuItem* currentVolumeItem;
348 		int32 items = fVolumeMenuField->Menu()->CountItems();
349 		for (int32 index = 0; index < items; ++index) {
350 			currentVolumeItem = ((VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(index));
351 			if (*(currentVolumeItem->fVolume) == fSettings.SwapVolume()) {
352 				currentVolumeItem->SetMarked(true);
353 				break;
354 			}
355 		}
356 	} else if (*selectedVolumeItem->fVolume != fSettings.SwapVolume()) {
357 		VolumeMenuItem* currentVolumeItem;
358 		int32 items = fVolumeMenuField->Menu()->CountItems();
359 		for (int32 index = 0; index < items; ++index) {
360 			currentVolumeItem = ((VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(index));
361 			if (*(currentVolumeItem->fVolume) == fSettings.SwapVolume()) {
362 				currentVolumeItem->SetMarked(true);
363 				break;
364 			}
365 		}
366 	}
367 #endif
368 
369 	fWarningStringView->SetText("");
370 	fLocked = false;
371 
372 	if (fSettings.IsRevertible())
373 			fWarningStringView->SetText(
374 				B_TRANSLATE("Changes will take effect on restart!"));
375 	if (fRevertButton->IsEnabled() != fSettings.IsRevertible())
376 		fRevertButton->SetEnabled(fSettings.IsRevertible());
377 
378 	off_t minSize, maxSize;
379 	if (_GetSwapFileLimits(minSize, maxSize) == B_OK) {
380 		// round to nearest MB -- slider steps in whole MBs
381 		(minSize >>= 20) <<= 20;
382 		(maxSize >>= 20) <<= 20;
383 		BString minLabel, maxLabel;
384 		minLabel << byte_string(minSize);
385 		maxLabel << byte_string(maxSize);
386 		if (minLabel != fSizeSlider->MinLimitLabel()
387 			|| maxLabel != fSizeSlider->MaxLimitLabel()) {
388 			fSizeSlider->SetLimitLabels(minLabel.String(), maxLabel.String());
389 			fSizeSlider->SetLimits(minSize / kMegaByte, maxSize / kMegaByte);
390 		}
391 	} else if (fSettings.SwapEnabled()) {
392 		fWarningStringView->SetText(
393 			B_TRANSLATE("Insufficient space for a swap file."));
394 		fLocked = true;
395 	}
396 	if (fSizeSlider->Value() != fSettings.SwapSize() / kMegaByte)
397 		fSizeSlider->SetValue(fSettings.SwapSize() / kMegaByte);
398 	if (fSizeSlider->IsEnabled() != fSettings.SwapEnabled() || fLocked)
399 	{
400 		fSizeSlider->SetEnabled(fSettings.SwapEnabled() && !fLocked);
401 		fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte);
402 	}
403 }
404 
405 
406 status_t
407 SettingsWindow::_GetSwapFileLimits(off_t& minSize, off_t& maxSize)
408 {
409 	minSize = kMegaByte;
410 
411 	// maximum size is the free space on the current volume
412 	// (minus some safety offset, depending on the disk size)
413 	off_t freeSpace = fSettings.SwapVolume().FreeBytes();
414 	off_t safetyFreeSpace = fSettings.SwapVolume().Capacity() / 100;
415 	if (safetyFreeSpace > 1024 * kMegaByte)
416 		safetyFreeSpace = 1024 * kMegaByte;
417 
418 	// check if there already is a page file on this disk and
419 	// adjust the free space accordingly
420 	BPath path;
421 	if (find_directory(B_COMMON_VAR_DIRECTORY, &path, false,
422 		&fSettings.SwapVolume()) == B_OK) {
423 		path.Append("swap");
424 		BEntry swap(path.Path());
425 
426 		off_t size;
427 		if (swap.GetSize(&size) == B_OK) {
428 			// If swap file exists, forget about safety space;
429 			// disk may have filled after creation of swap file.
430 			safetyFreeSpace = 0;
431 			freeSpace += size;
432 		}
433 	}
434 
435 	maxSize = freeSpace - safetyFreeSpace;
436 	if (maxSize < minSize) {
437 		maxSize = 0;
438 		minSize = 0;
439 		return B_ERROR;
440 	}
441 
442 	return B_OK;
443 }
444 
445 
446 void
447 SettingsWindow::_SetSwapDefaults()
448 {
449 	fSettings.SetSwapEnabled(true);
450 
451 	BVolumeRoster volumeRoster;
452 	BVolume temporaryVolume;
453 	volumeRoster.GetBootVolume(&temporaryVolume);
454 	fSettings.SetSwapVolume(temporaryVolume);
455 
456 	system_info info;
457 	get_system_info(&info);
458 
459 	off_t defaultSize = (off_t)info.max_pages * B_PAGE_SIZE;
460 	off_t minSize, maxSize;
461 	_GetSwapFileLimits(minSize, maxSize);
462 
463 	if (defaultSize > maxSize / 2)
464 		defaultSize = maxSize / 2;
465 
466 	fSettings.SetSwapSize(defaultSize);
467 }
468 
469