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 35const int32 kAppNameIndex = 0; 36const int32 kAppEnabledIndex = 1; 37 38 39AppRow::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 52void 53AppRow::SetAllowed(bool allowed) 54{ 55 fAllowed = allowed; 56 RefreshEnabledField(); 57} 58 59 60void 61AppRow::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 70NotificationsView::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 131NotificationsView::~NotificationsView() 132{ 133 delete fAddAppPanel; 134 delete fPanelFilter; 135} 136 137 138void 139NotificationsView::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 151void 152NotificationsView::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 255status_t 256NotificationsView::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 284status_t 285NotificationsView::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 295void 296NotificationsView::_ClearItemSettings() 297{ 298 fMuteAll->SetValue(B_CONTROL_OFF); 299} 300 301 302void 303NotificationsView::_UpdateSelectedItem() 304{ 305 fSelectedRow = dynamic_cast<AppRow*>(fApplications->CurrentSelection()); 306 307} 308 309 310void 311NotificationsView::_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 329status_t 330NotificationsView::Revert() 331{ 332 return B_OK; 333} 334 335 336bool 337NotificationsView::RevertPossible() 338{ 339 return false; 340} 341 342 343status_t 344NotificationsView::Defaults() 345{ 346 return B_OK; 347} 348 349 350bool 351NotificationsView::DefaultsPossible() 352{ 353 return false; 354} 355 356 357bool 358NotificationsView::UseDefaultRevertButtons() 359{ 360 return false; 361} 362 363 364void 365NotificationsView::_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