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
44const uint32 kToggleNotifications = '_TSR';
45const uint32 kWidthChanged = '_WIC';
46const uint32 kTimeoutChanged = '_TIC';
47const uint32 kPositionChanged = '_NPC';
48const uint32 kServerChangeTriggered = '_SCT';
49const BString kSampleMessageID("NotificationsSample");
50
51
52static int32
53notification_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
68GeneralView::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
146void
147GeneralView::AttachedToWindow()
148{
149	BView::AttachedToWindow();
150	fNotificationBox->SetTarget(this);
151	fWidthSlider->SetTarget(this);
152	fDurationSlider->SetTarget(this);
153	fPositionMenu->SetTargetForItems(this);
154}
155
156
157void
158GeneralView::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
194status_t
195GeneralView::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
228status_t
229GeneralView::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
249status_t
250GeneralView::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
267bool
268GeneralView::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
285status_t
286GeneralView::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
303bool
304GeneralView::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
321bool
322GeneralView::UseDefaultRevertButtons()
323{
324	return true;
325}
326
327
328void
329GeneralView::_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
341void
342GeneralView::_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
353bool
354GeneralView::_IsServerRunning()
355{
356	return be_roster->IsRunning(kNotificationServerSignature);
357}
358