1 /* 2 * Copyright 2010-2017, Haiku, Inc. All Rights Reserved. 3 * Copyright 2009, Pier Luigi Fiorini. 4 * Distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * Pier Luigi Fiorini, pierluigi.fiorini@gmail.com 8 * Brian Hill, supernova@tycho.email 9 */ 10 11 #include <stdio.h> 12 #include <stdlib.h> 13 14 #include <vector> 15 16 #include <Alert.h> 17 #include <Box.h> 18 #include <Button.h> 19 #include <Catalog.h> 20 #include <Directory.h> 21 #include <File.h> 22 #include <FindDirectory.h> 23 #include <Font.h> 24 #include <LayoutBuilder.h> 25 #include <Node.h> 26 #include <Path.h> 27 #include <Query.h> 28 #include <Roster.h> 29 #include <String.h> 30 #include <StringFormat.h> 31 #include <SymLink.h> 32 #include <Volume.h> 33 #include <VolumeRoster.h> 34 35 #include <notification/Notifications.h> 36 37 #include "GeneralView.h" 38 #include "NotificationsConstants.h" 39 #include "SettingsHost.h" 40 41 #undef B_TRANSLATION_CONTEXT 42 #define B_TRANSLATION_CONTEXT "GeneralView" 43 44 const uint32 kToggleNotifications = '_TSR'; 45 const uint32 kWidthChanged = '_WIC'; 46 const uint32 kTimeoutChanged = '_TIC'; 47 const uint32 kPositionChanged = '_NPC'; 48 const uint32 kServerChangeTriggered = '_SCT'; 49 const BString kSampleMessageID("NotificationsSample"); 50 51 52 static int32 53 notification_position_to_index(uint32 notification_position) { 54 if (notification_position == B_FOLLOW_NONE) 55 return 0; 56 else if (notification_position == (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM)) 57 return 1; 58 else if (notification_position == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM)) 59 return 2; 60 else if (notification_position == (B_FOLLOW_RIGHT | B_FOLLOW_TOP)) 61 return 3; 62 else if (notification_position == (B_FOLLOW_LEFT | B_FOLLOW_TOP)) 63 return 4; 64 return 0; 65 } 66 67 68 GeneralView::GeneralView(SettingsHost* host) 69 : 70 SettingsPane("general", host) 71 { 72 // Notification server 73 fNotificationBox = new BCheckBox("server", 74 B_TRANSLATE("Enable notifications"), 75 new BMessage(kToggleNotifications)); 76 BBox* box = new BBox("box"); 77 box->SetLabel(fNotificationBox); 78 79 // Window width 80 float ratio = be_plain_font->Size() / 12.f; 81 int32 minWidth = int32(kMinimumWidth / kWidthStep * ratio); 82 int32 maxWidth = int32(kMaximumWidth / kWidthStep * ratio); 83 fWidthSlider = new BSlider("width", B_TRANSLATE("Window width"), 84 new BMessage(kWidthChanged), minWidth, maxWidth, B_HORIZONTAL); 85 fWidthSlider->SetHashMarks(B_HASH_MARKS_BOTTOM); 86 fWidthSlider->SetHashMarkCount(maxWidth - minWidth + 1); 87 fWidthSlider->SetLimitLabels( 88 B_TRANSLATE_COMMENT("narrow", "Window width: Slider low text"), 89 B_TRANSLATE_COMMENT("wide", "Window width: Slider high text")); 90 91 // Display time 92 fDurationSlider = new BSlider("duration", B_TRANSLATE("Duration:"), 93 new BMessage(kTimeoutChanged), kMinimumTimeout, kMaximumTimeout, 94 B_HORIZONTAL); 95 fDurationSlider->SetHashMarks(B_HASH_MARKS_BOTTOM); 96 fDurationSlider->SetHashMarkCount(kMaximumTimeout - kMinimumTimeout + 1); 97 BString minLabel; 98 minLabel << kMinimumTimeout; 99 BString maxLabel; 100 maxLabel << kMaximumTimeout; 101 fDurationSlider->SetLimitLabels( 102 B_TRANSLATE_COMMENT(minLabel.String(), "Slider low text"), 103 B_TRANSLATE_COMMENT(maxLabel.String(), "Slider high text")); 104 105 // Notification Position 106 fPositionMenu = new BPopUpMenu(B_TRANSLATE("Follow Deskbar")); 107 const char* positionLabels[] = { 108 B_TRANSLATE_MARK("Follow Deskbar"), 109 B_TRANSLATE_MARK("Lower right"), 110 B_TRANSLATE_MARK("Lower left"), 111 B_TRANSLATE_MARK("Upper right"), 112 B_TRANSLATE_MARK("Upper left") 113 }; 114 const uint32 positions[] = { 115 B_FOLLOW_DESKBAR, // Follow Deskbar 116 B_FOLLOW_BOTTOM | B_FOLLOW_RIGHT, // Lower right 117 B_FOLLOW_BOTTOM | B_FOLLOW_LEFT, // Lower left 118 B_FOLLOW_TOP | B_FOLLOW_RIGHT, // Upper right 119 B_FOLLOW_TOP | B_FOLLOW_LEFT // Upper left 120 }; 121 for (int i=0; i < 5; i++) { 122 BMessage* message = new BMessage(kPositionChanged); 123 message->AddInt32(kNotificationPositionName, positions[i]); 124 125 fPositionMenu->AddItem(new BMenuItem(B_TRANSLATE_NOCOLLECT( 126 positionLabels[i]), message)); 127 } 128 BMenuField* positionField = new BMenuField(B_TRANSLATE("Position:"), 129 fPositionMenu); 130 131 box->AddChild(BLayoutBuilder::Group<>(B_VERTICAL) 132 .SetInsets(B_USE_DEFAULT_SPACING) 133 .Add(fWidthSlider) 134 .Add(fDurationSlider) 135 .Add(positionField) 136 .AddGlue() 137 .View()); 138 139 BLayoutBuilder::Group<>(this, B_VERTICAL) 140 .SetInsets(B_USE_WINDOW_SPACING) 141 .Add(box) 142 .End(); 143 } 144 145 146 void 147 GeneralView::AttachedToWindow() 148 { 149 BView::AttachedToWindow(); 150 fNotificationBox->SetTarget(this); 151 fWidthSlider->SetTarget(this); 152 fDurationSlider->SetTarget(this); 153 fPositionMenu->SetTargetForItems(this); 154 } 155 156 157 void 158 GeneralView::MessageReceived(BMessage* msg) 159 { 160 switch (msg->what) { 161 case kToggleNotifications: 162 { 163 SettingsPane::SettingsChanged(false); 164 _EnableControls(); 165 break; 166 } 167 case kWidthChanged: { 168 SettingsPane::SettingsChanged(true); 169 break; 170 } 171 case kTimeoutChanged: 172 { 173 int32 value = fDurationSlider->Value(); 174 _SetTimeoutLabel(value); 175 SettingsPane::SettingsChanged(true); 176 break; 177 } 178 case kPositionChanged: 179 { 180 int32 position; 181 if (msg->FindInt32(kNotificationPositionName, &position) == B_OK) { 182 fNewPosition = position; 183 SettingsPane::SettingsChanged(true); 184 } 185 break; 186 } 187 default: 188 BView::MessageReceived(msg); 189 break; 190 } 191 } 192 193 194 status_t 195 GeneralView::Load(BMessage& settings) 196 { 197 bool autoStart = settings.GetBool(kAutoStartName, true); 198 fNotificationBox->SetValue(autoStart ? B_CONTROL_ON : B_CONTROL_OFF); 199 200 if (settings.FindFloat(kWidthName, &fOriginalWidth) != B_OK 201 || fOriginalWidth > kMaximumWidth 202 || fOriginalWidth < kMinimumWidth) 203 fOriginalWidth = kDefaultWidth; 204 205 if (settings.FindInt32(kTimeoutName, &fOriginalTimeout) != B_OK 206 || fOriginalTimeout > kMaximumTimeout 207 || fOriginalTimeout < kMinimumTimeout) 208 fOriginalTimeout = kDefaultTimeout; 209 // TODO need to save again if values outside of expected range 210 int32 setting; 211 if (settings.FindInt32(kIconSizeName, &setting) != B_OK) 212 fOriginalIconSize = kDefaultIconSize; 213 else 214 fOriginalIconSize = (icon_size)setting; 215 216 int32 position; 217 if (settings.FindInt32(kNotificationPositionName, &position) != B_OK) 218 fOriginalPosition = kDefaultNotificationPosition; 219 else 220 fOriginalPosition = position; 221 222 _EnableControls(); 223 224 return Revert(); 225 } 226 227 228 status_t 229 GeneralView::Save(BMessage& settings) 230 { 231 bool autoStart = (fNotificationBox->Value() == B_CONTROL_ON); 232 settings.AddBool(kAutoStartName, autoStart); 233 234 int32 timeout = fDurationSlider->Value(); 235 settings.AddInt32(kTimeoutName, timeout); 236 237 float width = fWidthSlider->Value() * kWidthStep; 238 settings.AddFloat(kWidthName, width); 239 240 icon_size iconSize = B_LARGE_ICON; 241 settings.AddInt32(kIconSizeName, (int32)iconSize); 242 243 settings.AddInt32(kNotificationPositionName, (int32)fNewPosition); 244 245 return B_OK; 246 } 247 248 249 status_t 250 GeneralView::Revert() 251 { 252 fDurationSlider->SetValue(fOriginalTimeout); 253 _SetTimeoutLabel(fOriginalTimeout); 254 255 fWidthSlider->SetValue(fOriginalWidth / kWidthStep); 256 257 fNewPosition = fOriginalPosition; 258 BMenuItem* item = fPositionMenu->ItemAt( 259 notification_position_to_index(fNewPosition)); 260 if (item != NULL) 261 item->SetMarked(true); 262 263 return B_OK; 264 } 265 266 267 bool 268 GeneralView::RevertPossible() 269 { 270 int32 timeout = fDurationSlider->Value(); 271 if (fOriginalTimeout != timeout) 272 return true; 273 274 int32 width = fWidthSlider->Value() * kWidthStep; 275 if (fOriginalWidth != width) 276 return true; 277 278 if (fOriginalPosition != fNewPosition) 279 return true; 280 281 return false; 282 } 283 284 285 status_t 286 GeneralView::Defaults() 287 { 288 fDurationSlider->SetValue(kDefaultTimeout); 289 _SetTimeoutLabel(kDefaultTimeout); 290 291 fWidthSlider->SetValue(kDefaultWidth / kWidthStep); 292 293 fNewPosition = kDefaultNotificationPosition; 294 BMenuItem* item = fPositionMenu->ItemAt( 295 notification_position_to_index(fNewPosition)); 296 if (item != NULL) 297 item->SetMarked(true); 298 299 return B_OK; 300 } 301 302 303 bool 304 GeneralView::DefaultsPossible() 305 { 306 int32 timeout = fDurationSlider->Value(); 307 if (kDefaultTimeout != timeout) 308 return true; 309 310 int32 width = fWidthSlider->Value() * kWidthStep; 311 if (kDefaultWidth != width) 312 return true; 313 314 if (kDefaultNotificationPosition != fNewPosition) 315 return true; 316 317 return false; 318 } 319 320 321 bool 322 GeneralView::UseDefaultRevertButtons() 323 { 324 return true; 325 } 326 327 328 void 329 GeneralView::_EnableControls() 330 { 331 bool enabled = fNotificationBox->Value() == B_CONTROL_ON; 332 fWidthSlider->SetEnabled(enabled); 333 fDurationSlider->SetEnabled(enabled); 334 BMenuItem* item = fPositionMenu->ItemAt( 335 notification_position_to_index(fOriginalPosition)); 336 if (item != NULL) 337 item->SetMarked(true); 338 } 339 340 341 void 342 GeneralView::_SetTimeoutLabel(int32 value) 343 { 344 static BStringFormat format(B_TRANSLATE("{0, plural, " 345 "=1{Timeout: # second}" 346 "other{Timeout: # seconds}}")); 347 BString label; 348 format.Format(label, value); 349 fDurationSlider->SetLabel(label.String()); 350 } 351 352 353 bool 354 GeneralView::_IsServerRunning() 355 { 356 return be_roster->IsRunning(kNotificationServerSignature); 357 } 358