xref: /haiku/src/preferences/virtualmemory/SettingsWindow.cpp (revision 079c69cbfd7cd3c97baae91332251c8388a8bb02)
1 /*
2  * Copyright 2005-2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "SettingsWindow.h"
8 #include "Settings.h"
9 
10 #include <Application.h>
11 #include <Alert.h>
12 #include <Box.h>
13 #include <Button.h>
14 #include <CheckBox.h>
15 #include <StringView.h>
16 #include <String.h>
17 #include <Slider.h>
18 #include <PopUpMenu.h>
19 #include <MenuItem.h>
20 #include <MenuField.h>
21 #include <Screen.h>
22 #include <FindDirectory.h>
23 #include <Path.h>
24 #include <Volume.h>
25 #include <VolumeRoster.h>
26 
27 #include <stdio.h>
28 
29 
30 static const uint32 kMsgDefaults = 'dflt';
31 static const uint32 kMsgRevert = 'rvrt';
32 static const uint32 kMsgSliderUpdate = 'slup';
33 static const uint32 kMsgSwapEnabledUpdate = 'swen';
34 
35 
36 class SizeSlider : public BSlider {
37 	public:
38 		SizeSlider(BRect rect, const char* name, const char* label,
39 			BMessage* message, int32 min, int32 max, uint32 resizingMode);
40 		virtual ~SizeSlider();
41 
42 		virtual char* UpdateText() const;
43 
44 	private:
45 		mutable BString	fText;
46 };
47 
48 
49 static const int64 kMegaByte = 1048576;
50 
51 
52 const char *
53 byte_string(int64 size)
54 {
55 	double value = 1. * size;
56 	static char string[64];
57 
58 	if (value < 1024)
59 		snprintf(string, sizeof(string), "%Ld B", size);
60 	else {
61 		char *units[] = {"K", "M", "G", NULL};
62 		int32 i = -1;
63 
64 		do {
65 			value /= 1024.0;
66 			i++;
67 		} while (value >= 1024 && units[i + 1]);
68 
69 		off_t rounded = off_t(value * 100LL);
70 		sprintf(string, "%g %sB", rounded / 100.0, units[i]);
71 	}
72 
73 	return string;
74 }
75 
76 
77 //	#pragma mark -
78 
79 
80 SizeSlider::SizeSlider(BRect rect, const char* name, const char* label,
81 	BMessage* message, int32 min, int32 max, uint32 resizingMode)
82 	: BSlider(rect, name, label, message, min, max, B_BLOCK_THUMB, resizingMode)
83 {
84 	rgb_color color = ui_color(B_CONTROL_HIGHLIGHT_COLOR);
85 	UseFillColor(true, &color);
86 }
87 
88 
89 SizeSlider::~SizeSlider()
90 {
91 }
92 
93 
94 char *
95 SizeSlider::UpdateText() const
96 {
97 	fText = byte_string(Value() * kMegaByte);
98 
99 	return const_cast<char*>(fText.String());
100 }
101 
102 
103 //	#pragma mark -
104 
105 
106 SettingsWindow::SettingsWindow()
107 	: BWindow(BRect(0, 0, 269, 172), "VirtualMemory", B_TITLED_WINDOW,
108 			B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE)
109 {
110 	BRect rect = Bounds();
111 	BView* view = new BView(rect, "background", B_FOLLOW_ALL, B_WILL_DRAW);
112 	view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
113 
114 	font_height fontHeight;
115 	be_plain_font->GetHeight(&fontHeight);
116 
117 	float lineHeight = ceil(fontHeight.ascent + fontHeight.descent + fontHeight.ascent);
118 
119 	fSwapEnabledCheckBox = new BCheckBox(rect, "enable swap", "Enable Virtual Memory",
120 		new BMessage(kMsgSwapEnabledUpdate));
121 	fSwapEnabledCheckBox->SetValue(fSettings.SwapEnabled());
122 	fSwapEnabledCheckBox->ResizeToPreferred();
123 
124 	rect.InsetBy(10, 10);
125 	BBox* box = new BBox(rect, "box", B_FOLLOW_LEFT_RIGHT);
126 	box->SetLabel(fSwapEnabledCheckBox);
127 	view->AddChild(box);
128 
129 	system_info info;
130 	get_system_info(&info);
131 
132 	rect.right -= 20;
133 	rect.top = lineHeight;
134 	BString string = "Physical Memory: ";
135 	string << byte_string((off_t)info.max_pages * B_PAGE_SIZE);
136 	BStringView* stringView = new BStringView(rect, "physical memory", string.String(),
137 		B_FOLLOW_NONE);
138 	stringView->ResizeToPreferred();
139 	box->AddChild(stringView);
140 
141 	rect.OffsetBy(0, lineHeight);
142 	string = "Current Swap File Size: ";
143 	string << byte_string(fSettings.SwapSize());
144 	stringView = new BStringView(rect, "current swap size", string.String(),
145 		B_FOLLOW_NONE);
146 	stringView->ResizeToPreferred();
147 	box->AddChild(stringView);
148 
149 	BPopUpMenu* menu = new BPopUpMenu("volumes");
150 
151 	// collect volumes
152 	// ToDo: listen to volume changes!
153 	// ToDo: accept dropped volumes
154 
155 	BVolumeRoster volumeRoster;
156 	BVolume volume;
157 	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
158 		char name[B_FILE_NAME_LENGTH];
159 		if (!volume.IsPersistent() || volume.GetName(name) != B_OK || !name[0])
160 			continue;
161 
162 		BMenuItem* item = new BMenuItem(name, NULL);
163 		menu->AddItem(item);
164 
165 		if (volume.Device() == fSettings.SwapVolume().Device())
166 			item->SetMarked(true);
167 	}
168 
169 	rect.OffsetBy(0, lineHeight);
170 	BMenuField* field = new BMenuField(rect, "devices", "Use Volume:", menu);
171 	field->SetDivider(field->StringWidth(field->Label()) + 8);
172 	field->ResizeToPreferred();
173 	field->SetEnabled(false);
174 	box->AddChild(field);
175 
176 	off_t minSize, maxSize;
177 	_GetSwapFileLimits(minSize, maxSize);
178 
179 	rect.OffsetBy(0, lineHeight + 8);
180 	fSizeSlider = new SizeSlider(rect, "size slider", "Requested Swap File Size:",
181 		new BMessage(kMsgSliderUpdate), minSize / kMegaByte, maxSize / kMegaByte,
182 		B_FOLLOW_LEFT_RIGHT);
183 	fSizeSlider->SetLimitLabels("999 MB", "999 MB");
184 	fSizeSlider->ResizeToPreferred();
185 	fSizeSlider->SetViewColor(255, 0, 255);
186 	box->AddChild(fSizeSlider);
187 
188 	rect.OffsetBy(0, fSizeSlider->Frame().Height() + 5);
189 	rect.bottom = rect.top + stringView->Frame().Height();
190 	fWarningStringView = new BStringView(rect, "", "", B_FOLLOW_ALL);
191 	fWarningStringView->SetAlignment(B_ALIGN_CENTER);
192 	box->AddChild(fWarningStringView);
193 
194 	box->ResizeTo(box->Frame().Width(), fWarningStringView->Frame().bottom + 10);
195 
196 	// Add "Defaults" and "Revert" buttons
197 
198 	rect.top = box->Frame().bottom + 10;
199 	fDefaultsButton = new BButton(rect, "defaults", "Defaults", new BMessage(kMsgDefaults));
200 	fDefaultsButton->ResizeToPreferred();
201 	fDefaultsButton->SetEnabled(fSettings.IsDefaultable());
202 	view->AddChild(fDefaultsButton);
203 
204 	rect = fDefaultsButton->Frame();
205 	rect.OffsetBy(rect.Width() + 10, 0);
206 	fRevertButton = new BButton(rect, "revert", "Revert", new BMessage(kMsgRevert));
207 	fRevertButton->ResizeToPreferred();
208 	fRevertButton->SetEnabled(false);
209 	view->AddChild(fRevertButton);
210 
211 	view->ResizeTo(view->Frame().Width(), fRevertButton->Frame().bottom + 10);
212 	ResizeTo(view->Bounds().Width(), view->Bounds().Height());
213 	AddChild(view);
214 		// add view after resizing the window, so that the view's resizing
215 		// mode is not used (we already layed out the views for the new height)
216 
217 	// if the strings don't fit on screen, we enlarge the window now - the
218 	// views are already added to the window and will resize automatically
219 	if (Bounds().Width() < view->StringWidth(fSizeSlider->Label()) * 2)
220 		ResizeTo(view->StringWidth(fSizeSlider->Label()) * 2, Bounds().Height());
221 
222 	_Update();
223 
224 	BScreen screen;
225 	BRect screenFrame = screen.Frame();
226 
227 	if (!screenFrame.Contains(fSettings.WindowPosition())) {
228 		// move on screen, centered
229 		MoveTo(screenFrame.left + (screenFrame.Width() - Bounds().Width()) / 2,
230 			screenFrame.top + (screenFrame.Height() - Bounds().Height()) / 2);
231 	} else
232 		MoveTo(fSettings.WindowPosition());
233 }
234 
235 
236 SettingsWindow::~SettingsWindow()
237 {
238 }
239 
240 
241 void
242 SettingsWindow::_Update()
243 {
244 	if ((fSwapEnabledCheckBox->Value() != 0) != fSettings.SwapEnabled())
245 		fSwapEnabledCheckBox->SetValue(fSettings.SwapEnabled());
246 
247 	off_t minSize, maxSize;
248 	if (_GetSwapFileLimits(minSize, maxSize) == B_OK) {
249 		BString minLabel, maxLabel;
250 		minLabel << byte_string(minSize);
251 		maxLabel << byte_string(maxSize);
252 		if (minLabel != fSizeSlider->MinLimitLabel()
253 			|| maxLabel != fSizeSlider->MaxLimitLabel()) {
254 			fSizeSlider->SetLimitLabels(minLabel.String(), maxLabel.String());
255 #ifdef __HAIKU__
256 			fSizeSlider->SetLimits(minSize / kMegaByte, maxSize / kMegaByte);
257 #endif
258 		}
259 
260 		if (fSizeSlider->Value() != fSettings.SwapSize() / kMegaByte)
261 			fSizeSlider->SetValue(fSettings.SwapSize() / kMegaByte);
262 
263 		fSizeSlider->SetEnabled(true);
264 	} else {
265 		fSizeSlider->SetValue(minSize);
266 		fSizeSlider->SetEnabled(false);
267 	}
268 
269 	// ToDo: set volume
270 
271 	fDefaultsButton->SetEnabled(fSettings.IsDefaultable());
272 
273 	bool changed = fSettings.SwapChanged();
274 	if (fRevertButton->IsEnabled() != changed) {
275 		fRevertButton->SetEnabled(changed);
276 		if (changed)
277 			fWarningStringView->SetText("Changes will take effect on restart!");
278 		else
279 			fWarningStringView->SetText("");
280 	}
281 }
282 
283 
284 status_t
285 SettingsWindow::_GetSwapFileLimits(off_t& minSize, off_t& maxSize)
286 {
287 	// minimum size is the installed memory
288 	system_info info;
289 	get_system_info(&info);
290 	minSize = (off_t)info.max_pages * B_PAGE_SIZE;
291 
292 	// maximum size is the free space on the current volume
293 	// (minus some safety offset, depending on the disk size)
294 
295 	off_t freeSpace = fSettings.SwapVolume().FreeBytes();
296 	off_t safetyFreeSpace = fSettings.SwapVolume().Capacity() / 100;
297 	if (safetyFreeSpace > 1024 * kMegaByte)
298 		safetyFreeSpace = 1024 * kMegaByte;
299 
300 	// check if there already is a page file on this disk and
301 	// adjust the free space accordingly
302 
303 	BPath path;
304 	if (find_directory(B_COMMON_VAR_DIRECTORY, &path, false,
305 			&fSettings.SwapVolume()) == B_OK) {
306 		path.Append("swap");
307 		BEntry swap(path.Path());
308 
309 		off_t size;
310 		if (swap.GetSize(&size) == B_OK)
311 			freeSpace += size;
312 	}
313 
314 	maxSize = freeSpace - safetyFreeSpace;
315 
316 	if (maxSize < minSize) {
317 		maxSize = minSize;
318 		return B_ERROR;
319 	}
320 
321 	return B_OK;
322 }
323 
324 
325 void
326 SettingsWindow::MessageReceived(BMessage* message)
327 {
328 	switch (message->what) {
329 		case kMsgRevert:
330 			fSettings.RevertSwapChanges();
331 			_Update();
332 			break;
333 		case kMsgDefaults:
334 			fSettings.SetSwapDefaults();
335 			_Update();
336 			break;
337 		case kMsgSliderUpdate:
338 			fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte);
339 			_Update();
340 			break;
341 		case kMsgSwapEnabledUpdate:
342 		{
343 			int32 value;
344 			if (message->FindInt32("be:value", &value) != B_OK)
345 				break;
346 
347 			if (value == 0) {
348 				// print out warning, give the user the time to think about it :)
349 				// ToDo: maybe we want to remove this possibility in the GUI
350 				//	as Be did, but I thought a proper warning could be helpful
351 				//	(for those that want to change that anyway)
352 				int32 choice = (new BAlert("VirtualMemory",
353 					"Disabling virtual memory will have unwanted effects on "
354 					"system stability once the memory is used up.\n"
355 					"Virtual memory does not affect system performance "
356 					"until this point is reached.\n\n"
357 					"Are you really sure you want to turn it off?",
358 					"Turn Off", "Keep Enabled", NULL,
359 					B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
360 				if (choice == 1) {
361 					fSwapEnabledCheckBox->SetValue(1);
362 					break;
363 				}
364 			}
365 
366 			fSettings.SetSwapEnabled(value != 0);
367 			_Update();
368 			break;
369 		}
370 
371 		default:
372 			BWindow::MessageReceived(message);
373 	}
374 }
375 
376 
377 bool
378 SettingsWindow::QuitRequested()
379 {
380 	fSettings.SetWindowPosition(Frame().LeftTop());
381 	be_app->PostMessage(B_QUIT_REQUESTED);
382 	return true;
383 }
384 
385