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 <Alert.h> 12 #include <Button.h> 13 #include <Catalog.h> 14 #include <CheckBox.h> 15 #include <ColumnListView.h> 16 #include <ColumnTypes.h> 17 #include <Directory.h> 18 #include <FindDirectory.h> 19 #include <LayoutBuilder.h> 20 #include <Notification.h> 21 #include <Path.h> 22 #include <TextControl.h> 23 #include <Window.h> 24 25 #include <notification/Notifications.h> 26 #include <notification/NotificationReceived.h> 27 28 #include "NotificationsConstants.h" 29 #include "NotificationsView.h" 30 31 #undef B_TRANSLATION_CONTEXT 32 #define B_TRANSLATION_CONTEXT "NotificationView" 33 34 // Applications column indexes 35 const int32 kAppNameIndex = 0; 36 const int32 kAppEnabledIndex = 1; 37 38 39 AppRow::AppRow(const char* name, const char* signature, bool allowed) 40 : 41 BRow(), 42 fName(name), 43 fSignature(signature), 44 fAllowed(allowed) 45 { 46 SetField(new BStringField(fName.String()), kAppNameIndex); 47 BString text = fAllowed ? B_TRANSLATE("Allowed") : B_TRANSLATE("Muted"); 48 SetField(new BStringField(text.String()), kAppEnabledIndex); 49 } 50 51 52 void 53 AppRow::SetAllowed(bool allowed) 54 { 55 fAllowed = allowed; 56 RefreshEnabledField(); 57 } 58 59 60 void 61 AppRow::RefreshEnabledField() 62 { 63 BStringField* field = (BStringField*)GetField(kAppEnabledIndex); 64 BString text = fAllowed ? B_TRANSLATE("Allowed") : B_TRANSLATE("Muted"); 65 field->SetString(text.String()); 66 Invalidate(); 67 } 68 69 70 NotificationsView::NotificationsView(SettingsHost* host) 71 : 72 SettingsPane("apps", host), 73 fSelectedRow(NULL) 74 { 75 // Applications list 76 fApplications = new BColumnListView(B_TRANSLATE("Applications"), 77 0, B_FANCY_BORDER, false); 78 fApplications->SetSelectionMode(B_SINGLE_SELECTION_LIST); 79 fApplications->SetSelectionMessage(new BMessage(kApplicationSelected)); 80 81 float colWidth = be_plain_font->StringWidth(B_TRANSLATE("Application")) 82 + (kCLVTitlePadding * 2); 83 fAppCol = new BStringColumn(B_TRANSLATE("Application"), colWidth * 2, 84 colWidth, colWidth * 4, B_TRUNCATE_END, B_ALIGN_LEFT); 85 fApplications->AddColumn(fAppCol, kAppNameIndex); 86 87 colWidth = be_plain_font->StringWidth(B_TRANSLATE("Status")) 88 + (kCLVTitlePadding * 2); 89 fAppEnabledCol = new BStringColumn(B_TRANSLATE("Status"), colWidth * 1.5, 90 colWidth, colWidth * 3, B_TRUNCATE_END, B_ALIGN_LEFT); 91 fApplications->AddColumn(fAppEnabledCol, kAppEnabledIndex); 92 fApplications->SetSortColumn(fAppCol, true, true); 93 94 fAddButton = new BButton("add_app", B_TRANSLATE("Add" B_UTF8_ELLIPSIS), 95 new BMessage(kAddApplication)); 96 fRemoveButton = new BButton("add_app", B_TRANSLATE("Remove"), 97 new BMessage(kRemoveApplication)); 98 fRemoveButton->SetEnabled(false); 99 100 fMuteAll = new BCheckBox("block", B_TRANSLATE("Mute notifications from " 101 "this application"), 102 new BMessage(kMuteChanged)); 103 104 // Add views 105 BLayoutBuilder::Group<>(this, B_VERTICAL) 106 .AddGroup(B_HORIZONTAL) 107 .Add(fApplications) 108 .AddGroup(B_VERTICAL) 109 .Add(fAddButton) 110 .Add(fRemoveButton) 111 .AddGlue() 112 .End() 113 .End() 114 .Add(fMuteAll) 115 .SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING, 116 B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING); 117 118 // Set button sizes 119 float maxButtonWidth = std::max(fAddButton->PreferredSize().Width(), 120 fRemoveButton->PreferredSize().Width()); 121 fAddButton->SetExplicitMaxSize(BSize(maxButtonWidth, B_SIZE_UNSET)); 122 fRemoveButton->SetExplicitMaxSize(BSize(maxButtonWidth, B_SIZE_UNSET)); 123 124 // File Panel 125 fPanelFilter = new AppRefFilter(); 126 fAddAppPanel = new BFilePanel(B_OPEN_PANEL, NULL, NULL, B_FILE_NODE, false, 127 NULL, fPanelFilter); 128 } 129 130 131 NotificationsView::~NotificationsView() 132 { 133 delete fAddAppPanel; 134 delete fPanelFilter; 135 } 136 137 138 void 139 NotificationsView::AttachedToWindow() 140 { 141 fApplications->SetTarget(this); 142 fApplications->SetInvocationMessage(new BMessage(kApplicationSelected)); 143 fAddButton->SetTarget(this); 144 fRemoveButton->SetTarget(this); 145 fMuteAll->SetTarget(this); 146 fAddAppPanel->SetTarget(this); 147 _RecallItemSettings(); 148 } 149 150 151 void 152 NotificationsView::MessageReceived(BMessage* msg) 153 { 154 switch (msg->what) { 155 case kApplicationSelected: 156 { 157 Window()->Lock(); 158 _ClearItemSettings(); 159 _UpdateSelectedItem(); 160 _RecallItemSettings(); 161 Window()->Unlock(); 162 break; 163 } 164 case kMuteChanged: 165 { 166 bool allowed = fMuteAll->Value() == B_CONTROL_OFF; 167 fSelectedRow->SetAllowed(allowed); 168 appusage_t::iterator it = fAppFilters.find(fSelectedRow->Signature()); 169 if (it != fAppFilters.end()) 170 it->second->SetAllowed(allowed); 171 Window()->PostMessage(kApply); 172 break; 173 } 174 case kAddApplication: 175 { 176 BMessage addmsg(kAddApplicationRef); 177 fAddAppPanel->SetMessage(&addmsg); 178 fAddAppPanel->Show(); 179 break; 180 } 181 case kAddApplicationRef: 182 { 183 entry_ref srcRef; 184 msg->FindRef("refs", &srcRef); 185 BEntry srcEntry(&srcRef, true); 186 BPath path(&srcEntry); 187 BNode node(&srcEntry); 188 char *buf = new char[B_ATTR_NAME_LENGTH]; 189 ssize_t size; 190 if ( (size = node.ReadAttr("BEOS:APP_SIG", 0, 0, buf, 191 B_ATTR_NAME_LENGTH)) > 0 ) 192 { 193 // Search for already existing app 194 appusage_t::iterator it = fAppFilters.find(buf); 195 if (it != fAppFilters.end()) { 196 BString text(path.Leaf()); 197 text.Append(B_TRANSLATE_COMMENT(" is already listed", 198 "Alert message")); 199 BAlert* alert = new BAlert("", text.String(), 200 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 201 B_WARNING_ALERT); 202 alert->Go(NULL); 203 } else { 204 AppUsage* appUsage = new AppUsage(path.Leaf(), buf, true); 205 fAppFilters[appUsage->Signature()] = appUsage; 206 AppRow* row = new AppRow(appUsage->AppName(), 207 appUsage->Signature(), appUsage->Allowed()); 208 fApplications->AddRow(row); 209 fApplications->DeselectAll(); 210 fApplications->AddToSelection(row); 211 fApplications->ScrollTo(row); 212 _UpdateSelectedItem(); 213 _RecallItemSettings(); 214 //row->Invalidate(); 215 //fApplications->InvalidateRow(row); 216 // TODO redraw row properly 217 Window()->PostMessage(kApply); 218 } 219 } else { 220 BAlert* alert = new BAlert("", 221 B_TRANSLATE_COMMENT("Application does not have " 222 "a valid signature", "Alert message"), 223 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 224 B_WARNING_ALERT); 225 alert->Go(NULL); 226 } 227 delete[] buf; 228 break; 229 } 230 case kRemoveApplication: 231 { 232 if (fSelectedRow) { 233 appusage_t::iterator it = fAppFilters.find(fSelectedRow->Signature()); 234 if (it != fAppFilters.end()) { 235 delete it->second; 236 fAppFilters.erase(it); 237 } 238 fApplications->RemoveRow(fSelectedRow); 239 delete fSelectedRow; 240 fSelectedRow = NULL; 241 _ClearItemSettings(); 242 _UpdateSelectedItem(); 243 _RecallItemSettings(); 244 Window()->PostMessage(kApply); 245 } 246 break; 247 } 248 default: 249 BView::MessageReceived(msg); 250 break; 251 } 252 } 253 254 255 status_t 256 NotificationsView::Load(BMessage& settings) 257 { 258 type_code type; 259 int32 count = 0; 260 261 if (settings.GetInfo("app_usage", &type, &count) != B_OK) 262 return B_ERROR; 263 264 // Clean filters 265 appusage_t::iterator auIt; 266 for (auIt = fAppFilters.begin(); auIt != fAppFilters.end(); auIt++) 267 delete auIt->second; 268 fAppFilters.clear(); 269 270 // Add new filters 271 for (int32 i = 0; i < count; i++) { 272 AppUsage* app = new AppUsage(); 273 settings.FindFlat("app_usage", i, app); 274 fAppFilters[app->Signature()] = app; 275 } 276 277 // Load the applications list 278 _PopulateApplications(); 279 280 return B_OK; 281 } 282 283 284 status_t 285 NotificationsView::Save(BMessage& storage) 286 { 287 appusage_t::iterator fIt; 288 for (fIt = fAppFilters.begin(); fIt != fAppFilters.end(); fIt++) 289 storage.AddFlat("app_usage", fIt->second); 290 291 return B_OK; 292 } 293 294 295 void 296 NotificationsView::_ClearItemSettings() 297 { 298 fMuteAll->SetValue(B_CONTROL_OFF); 299 } 300 301 302 void 303 NotificationsView::_UpdateSelectedItem() 304 { 305 fSelectedRow = dynamic_cast<AppRow*>(fApplications->CurrentSelection()); 306 307 } 308 309 310 void 311 NotificationsView::_RecallItemSettings() 312 { 313 // No selected item 314 if(fSelectedRow == NULL) 315 { 316 fMuteAll->SetValue(B_CONTROL_OFF); 317 fMuteAll->SetEnabled(false); 318 fRemoveButton->SetEnabled(false); 319 } else { 320 fMuteAll->SetEnabled(true); 321 fRemoveButton->SetEnabled(true); 322 appusage_t::iterator it = fAppFilters.find(fSelectedRow->Signature()); 323 if (it != fAppFilters.end()) 324 fMuteAll->SetValue(!(it->second->Allowed())); 325 } 326 } 327 328 329 status_t 330 NotificationsView::Revert() 331 { 332 return B_OK; 333 } 334 335 336 bool 337 NotificationsView::RevertPossible() 338 { 339 return false; 340 } 341 342 343 status_t 344 NotificationsView::Defaults() 345 { 346 return B_OK; 347 } 348 349 350 bool 351 NotificationsView::DefaultsPossible() 352 { 353 return false; 354 } 355 356 357 bool 358 NotificationsView::UseDefaultRevertButtons() 359 { 360 return false; 361 } 362 363 364 void 365 NotificationsView::_PopulateApplications() 366 { 367 fApplications->Clear(); 368 369 appusage_t::iterator it; 370 for (it = fAppFilters.begin(); it != fAppFilters.end(); ++it) { 371 AppUsage* appUsage = it->second; 372 AppRow* row = new AppRow(appUsage->AppName(), 373 appUsage->Signature(), appUsage->Allowed()); 374 fApplications->AddRow(row); 375 } 376 } 377