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