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