1 /* 2 * Copyright 2005-2009, 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 const 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 const char * 95 SizeSlider::UpdateText() const 96 { 97 fText = byte_string(Value() * kMegaByte); 98 99 return 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 an arbitrarily chosen MB 288 minSize = kMegaByte; 289 290 // maximum size is the free space on the current volume 291 // (minus some safety offset, depending on the disk size) 292 293 off_t freeSpace = fSettings.SwapVolume().FreeBytes(); 294 off_t safetyFreeSpace = fSettings.SwapVolume().Capacity() / 100; 295 if (safetyFreeSpace > 1024 * kMegaByte) 296 safetyFreeSpace = 1024 * kMegaByte; 297 298 // check if there already is a page file on this disk and 299 // adjust the free space accordingly 300 301 BPath path; 302 if (find_directory(B_COMMON_VAR_DIRECTORY, &path, false, 303 &fSettings.SwapVolume()) == B_OK) { 304 path.Append("swap"); 305 BEntry swap(path.Path()); 306 307 off_t size; 308 if (swap.GetSize(&size) == B_OK) 309 freeSpace += size; 310 } 311 312 maxSize = freeSpace - safetyFreeSpace; 313 314 if (maxSize < minSize) { 315 maxSize = minSize; 316 return B_ERROR; 317 } 318 319 return B_OK; 320 } 321 322 323 void 324 SettingsWindow::MessageReceived(BMessage* message) 325 { 326 switch (message->what) { 327 case kMsgRevert: 328 fSettings.RevertSwapChanges(); 329 _Update(); 330 break; 331 case kMsgDefaults: 332 fSettings.SetSwapDefaults(); 333 _Update(); 334 break; 335 case kMsgSliderUpdate: 336 fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte); 337 _Update(); 338 break; 339 case kMsgSwapEnabledUpdate: 340 { 341 int32 value; 342 if (message->FindInt32("be:value", &value) != B_OK) 343 break; 344 345 if (value == 0) { 346 // print out warning, give the user the time to think about it :) 347 // ToDo: maybe we want to remove this possibility in the GUI 348 // as Be did, but I thought a proper warning could be helpful 349 // (for those that want to change that anyway) 350 int32 choice = (new BAlert("VirtualMemory", 351 "Disabling virtual memory will have unwanted effects on " 352 "system stability once the memory is used up.\n" 353 "Virtual memory does not affect system performance " 354 "until this point is reached.\n\n" 355 "Are you really sure you want to turn it off?", 356 "Turn Off", "Keep Enabled", NULL, 357 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 358 if (choice == 1) { 359 fSwapEnabledCheckBox->SetValue(1); 360 break; 361 } 362 } 363 364 fSettings.SetSwapEnabled(value != 0); 365 _Update(); 366 break; 367 } 368 369 default: 370 BWindow::MessageReceived(message); 371 } 372 } 373 374 375 bool 376 SettingsWindow::QuitRequested() 377 { 378 fSettings.SetWindowPosition(Frame().LeftTop()); 379 be_app->PostMessage(B_QUIT_REQUESTED); 380 return true; 381 } 382 383