xref: /haiku/src/preferences/virtualmemory/SettingsWindow.cpp (revision 5c6260dc232fcb2d4d5d1103c1623dba9663b753)
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_TRANSLATE_CONTEXT
35 #define B_TRANSLATE_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 		int32 choice = (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))->Go();
246 		if (choice == 1) {
247 			BVolumeRoster volumeRoster;
248 			BVolume bootVolume;
249 			volumeRoster.GetBootVolume(&bootVolume);
250 			fSettings.SetSwapVolume(bootVolume);
251 		}
252 	}
253 #endif
254 
255 	_Update();
256 }
257 
258 
259 SettingsWindow::~SettingsWindow()
260 {
261 }
262 
263 
264 void
265 SettingsWindow::MessageReceived(BMessage* message)
266 {
267 	switch (message->what) {
268 		case kMsgRevert:
269 			fSettings.RevertSwapChanges();
270 			_Update();
271 			break;
272 		case kMsgDefaults:
273 			_SetSwapDefaults();
274 			_Update();
275 			break;
276 		case kMsgSliderUpdate:
277 			fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte);
278 			_Update();
279 			break;
280 		case kMsgVolumeSelected:
281 			fSettings.SetSwapVolume(*((VolumeMenuItem*)fVolumeMenuField->Menu()
282 				->FindMarked())->Volume());
283 			_Update();
284 			break;
285 		case kMsgSwapEnabledUpdate:
286 		{
287 			int32 value;
288 			if (message->FindInt32("be:value", &value) != B_OK)
289 				break;
290 
291 			if (value == 0) {
292 				// print out warning, give the user the time to think about it :)
293 				// ToDo: maybe we want to remove this possibility in the GUI
294 				// as Be did, but I thought a proper warning could be helpful
295 				// (for those that want to change that anyway)
296 				int32 choice = (new BAlert("VirtualMemory",
297 					B_TRANSLATE(
298 					"Disabling virtual memory will have unwanted effects on "
299 					"system stability once the memory is used up.\n"
300 					"Virtual memory does not affect system performance "
301 					"until this point is reached.\n\n"
302 					"Are you really sure you want to turn it off?"),
303 					B_TRANSLATE("Turn off"), B_TRANSLATE("Keep enabled"), NULL,
304 					B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
305 				if (choice == 1) {
306 					fSwapEnabledCheckBox->SetValue(1);
307 					break;
308 				}
309 			}
310 
311 			fSettings.SetSwapEnabled(value != 0);
312 			_Update();
313 			break;
314 		}
315 
316 		default:
317 			BWindow::MessageReceived(message);
318 	}
319 }
320 
321 
322 bool
323 SettingsWindow::QuitRequested()
324 {
325 	fSettings.SetWindowPosition(Frame().LeftTop());
326 	be_app->PostMessage(B_QUIT_REQUESTED);
327 	return true;
328 }
329 
330 
331 void
332 SettingsWindow::_Update()
333 {
334 	if ((fSwapEnabledCheckBox->Value() != 0) != fSettings.SwapEnabled())
335 		fSwapEnabledCheckBox->SetValue(fSettings.SwapEnabled());
336 
337 #ifdef SWAP_VOLUME_IMPLEMENTED
338 	if (fVolumeMenuField->IsEnabled() != fSettings.SwapEnabled())
339 		fVolumeMenuField->SetEnabled(fSettings.SwapEnabled());
340 	VolumeMenuItem* selectedVolumeItem =
341 		(VolumeMenuItem*)fVolumeMenuField->Menu()->FindMarked();
342 	if (selectedVolumeItem == NULL) {
343 		VolumeMenuItem* currentVolumeItem;
344 		int32 items = fVolumeMenuField->Menu()->CountItems();
345 		for (int32 index = 0; index < items; ++index) {
346 			currentVolumeItem = ((VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(index));
347 			if (*(currentVolumeItem->fVolume) == fSettings.SwapVolume()) {
348 				currentVolumeItem->SetMarked(true);
349 				break;
350 			}
351 		}
352 	} else if (*selectedVolumeItem->fVolume != fSettings.SwapVolume()) {
353 		VolumeMenuItem* currentVolumeItem;
354 		int32 items = fVolumeMenuField->Menu()->CountItems();
355 		for (int32 index = 0; index < items; ++index) {
356 			currentVolumeItem = ((VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(index));
357 			if (*(currentVolumeItem->fVolume) == fSettings.SwapVolume()) {
358 				currentVolumeItem->SetMarked(true);
359 				break;
360 			}
361 		}
362 	}
363 #endif
364 
365 	fWarningStringView->SetText("");
366 	fLocked = false;
367 
368 	if (fSettings.IsRevertible())
369 			fWarningStringView->SetText(
370 				B_TRANSLATE("Changes will take effect on restart!"));
371 	if (fRevertButton->IsEnabled() != fSettings.IsRevertible())
372 		fRevertButton->SetEnabled(fSettings.IsRevertible());
373 
374 	off_t minSize, maxSize;
375 	if (_GetSwapFileLimits(minSize, maxSize) == B_OK) {
376 		// round to nearest MB -- slider steps in whole MBs
377 		(minSize >>= 20) <<= 20;
378 		(maxSize >>= 20) <<= 20;
379 		BString minLabel, maxLabel;
380 		minLabel << byte_string(minSize);
381 		maxLabel << byte_string(maxSize);
382 		if (minLabel != fSizeSlider->MinLimitLabel()
383 			|| maxLabel != fSizeSlider->MaxLimitLabel()) {
384 			fSizeSlider->SetLimitLabels(minLabel.String(), maxLabel.String());
385 			fSizeSlider->SetLimits(minSize / kMegaByte, maxSize / kMegaByte);
386 		}
387 	} else if (fSettings.SwapEnabled()) {
388 		fWarningStringView->SetText(
389 			B_TRANSLATE("Insufficient space for a swap file."));
390 		fLocked = true;
391 	}
392 	if (fSizeSlider->Value() != fSettings.SwapSize() / kMegaByte)
393 		fSizeSlider->SetValue(fSettings.SwapSize() / kMegaByte);
394 	if (fSizeSlider->IsEnabled() != fSettings.SwapEnabled() || fLocked)
395 	{
396 		fSizeSlider->SetEnabled(fSettings.SwapEnabled() && !fLocked);
397 		fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte);
398 	}
399 }
400 
401 
402 status_t
403 SettingsWindow::_GetSwapFileLimits(off_t& minSize, off_t& maxSize)
404 {
405 	minSize = kMegaByte;
406 
407 	// maximum size is the free space on the current volume
408 	// (minus some safety offset, depending on the disk size)
409 	off_t freeSpace = fSettings.SwapVolume().FreeBytes();
410 	off_t safetyFreeSpace = fSettings.SwapVolume().Capacity() / 100;
411 	if (safetyFreeSpace > 1024 * kMegaByte)
412 		safetyFreeSpace = 1024 * kMegaByte;
413 
414 	// check if there already is a page file on this disk and
415 	// adjust the free space accordingly
416 	BPath path;
417 	if (find_directory(B_COMMON_VAR_DIRECTORY, &path, false,
418 		&fSettings.SwapVolume()) == B_OK) {
419 		path.Append("swap");
420 		BEntry swap(path.Path());
421 
422 		off_t size;
423 		if (swap.GetSize(&size) == B_OK) {
424 			// If swap file exists, forget about safety space;
425 			// disk may have filled after creation of swap file.
426 			safetyFreeSpace = 0;
427 			freeSpace += size;
428 		}
429 	}
430 
431 	maxSize = freeSpace - safetyFreeSpace;
432 	if (maxSize < minSize) {
433 		maxSize = 0;
434 		minSize = 0;
435 		return B_ERROR;
436 	}
437 
438 	return B_OK;
439 }
440 
441 
442 void
443 SettingsWindow::_SetSwapDefaults()
444 {
445 	fSettings.SetSwapEnabled(true);
446 
447 	BVolumeRoster volumeRoster;
448 	BVolume temporaryVolume;
449 	volumeRoster.GetBootVolume(&temporaryVolume);
450 	fSettings.SetSwapVolume(temporaryVolume);
451 
452 	system_info info;
453 	get_system_info(&info);
454 
455 	off_t defaultSize = (off_t)info.max_pages * B_PAGE_SIZE;
456 	off_t minSize, maxSize;
457 	_GetSwapFileLimits(minSize, maxSize);
458 
459 	if (defaultSize > maxSize / 2)
460 		defaultSize = maxSize / 2;
461 
462 	fSettings.SetSwapSize(defaultSize);
463 }
464 
465