1/*
2 * Copyright 2009, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Clemens Zeidler, haiku@clemens-zeidler.de
7 */
8#ifndef PREFERENCES_WINDOW_h
9#define PREFERENCES_WINDOW_h
10
11
12#include <Application.h>
13#include <Button.h>
14#include <Catalog.h>
15#include <Debug.h>
16#include <Entry.h>
17#include <FindDirectory.h>
18#include <File.h>
19#include <GroupView.h>
20#include <Locale.h>
21#include <MessageFilter.h>
22#include <NodeMonitor.h>
23#include <Path.h>
24#include <Screen.h>
25#include <SpaceLayoutItem.h>
26#include <String.h>
27#include <Window.h>
28
29
30#if DEBUG
31#	define LOG(text...) PRINT((text))
32#else
33#	define LOG(text...)
34#endif
35
36
37#undef B_TRANSLATION_CONTEXT
38#define B_TRANSLATION_CONTEXT "Pref Window"
39
40// messages PrefFileWatcher
41const uint32 kUpdatedPreferences = '&UdP';
42
43
44template<typename Preferences>
45class PreferencesStorage {
46public:
47								PreferencesStorage(const char* file,
48									const Preferences& defaultPreferences);
49								~PreferencesStorage();
50
51			void				Revert();
52			void				Defaults();
53
54			BPoint 				WindowPosition() {return fWindowPosition; }
55			void				SetWindowPosition(BPoint position)
56									{ fWindowPosition = position; }
57
58			Preferences*		GetPreferences() { return &fPreferences; }
59
60			status_t			LoadPreferences();
61			status_t			SavePreferences();
62
63			BString&			PreferencesFile() {	return fPreferencesFile; }
64			status_t			GetPreferencesPath(BPath &path);
65
66			bool				DefaultsSet();
67			bool				StartPrefsSet();
68
69private:
70			BString				fPreferencesFile;
71
72			Preferences			fPreferences;
73			Preferences			fStartPreferences;
74			const Preferences&	fDefaultPreferences;
75			BPoint				fWindowPosition;
76};
77
78
79template<typename Preferences>
80class PrefFileWatcher : public BMessageFilter {
81public:
82								PrefFileWatcher(
83									PreferencesStorage<Preferences>* storage,
84									BHandler* target);
85	virtual						~PrefFileWatcher();
86
87	virtual filter_result		Filter(BMessage* message, BHandler** _target);
88
89private:
90			PreferencesStorage<Preferences>* fPreferencesStorage;
91			node_ref			fPreferencesNode;
92			node_ref			fPreferencesDirectoryNode;
93
94			BHandler*			fTarget;
95};
96
97
98template<typename Preferences>
99class PreferencesWindow : public BWindow,
100	public PreferencesStorage<Preferences> {
101public:
102								PreferencesWindow(const char* title,
103									const char* file,
104									const Preferences& defaultPreferences);
105	virtual						~PreferencesWindow();
106	virtual void				MessageReceived(BMessage *msg);
107	virtual bool				QuitRequested();
108
109	virtual bool				SetPreferencesView(BView* prefView);
110
111private:
112			void				_MoveToPosition();
113			void				_UpdateButtons();
114
115			BView*				fPreferencesView;
116			BButton*			fRevertButton;
117			BButton*			fDefaultButton;
118			BGroupLayout*		fRootLayout;
119};
120
121
122const uint32 kDefaultMsg = 'dems';
123const uint32 kRevertMsg = 'rems';
124const uint32 kConfigChangedMsg = '&cgh';
125
126
127template<typename Preferences>
128PreferencesStorage<Preferences>::PreferencesStorage(const char* file,
129	const Preferences& defaultPreferences)
130	:
131	fDefaultPreferences(defaultPreferences)
132{
133	// default center position
134	fWindowPosition.x = -1;
135	fWindowPosition.y = -1;
136
137	fPreferencesFile = file;
138	if (LoadPreferences() != B_OK)
139		Defaults();
140	fStartPreferences = fPreferences;
141}
142
143
144template<typename Preferences>
145PreferencesStorage<Preferences>::~PreferencesStorage()
146{
147	SavePreferences();
148}
149
150
151template<typename Preferences>
152void
153PreferencesStorage<Preferences>::Revert()
154{
155	fPreferences = fStartPreferences;
156}
157
158
159template<typename Preferences>
160void
161PreferencesStorage<Preferences>::Defaults()
162{
163	fPreferences = fDefaultPreferences;
164}
165
166
167template<typename Preferences>
168status_t
169PreferencesStorage<Preferences>::GetPreferencesPath(BPath &path)
170{
171	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
172	if (status < B_OK)
173		return status;
174
175	return path.Append(fPreferencesFile.String());
176}
177
178
179template<typename Preferences>
180status_t
181PreferencesStorage<Preferences>::LoadPreferences()
182{
183	BPath path;
184	status_t status = GetPreferencesPath(path);
185	if (status != B_OK)
186		return status;
187
188	BFile settingsFile(path.Path(), B_READ_ONLY);
189	status = settingsFile.InitCheck();
190	if (status != B_OK)
191		return status;
192
193	if (settingsFile.Read(&fWindowPosition, sizeof(BPoint))
194			!= sizeof(BPoint)) {
195		LOG("failed to load settings\n");
196		return B_ERROR;
197	}
198
199	if (settingsFile.Read(&fPreferences, sizeof(Preferences))
200			!= sizeof(Preferences)) {
201		LOG("failed to load settings\n");
202		return B_ERROR;
203	}
204
205	return B_OK;
206}
207
208
209template<typename Preferences>
210status_t
211PreferencesStorage<Preferences>::SavePreferences()
212{
213	BPath path;
214	status_t status = GetPreferencesPath(path);
215	if (status != B_OK)
216		return status;
217
218	BFile settingsFile(path.Path(), B_READ_WRITE | B_CREATE_FILE);
219	status = settingsFile.InitCheck();
220	if (status != B_OK) {
221		LOG("InitCheck() settings file failed \n");
222		return status;
223	}
224
225	if (settingsFile.Write(&fWindowPosition, sizeof(BPoint))
226			!= sizeof(BPoint)) {
227		LOG("can't save window position\n");
228		return B_ERROR;
229	}
230
231	if (settingsFile.Write(&fPreferences, sizeof(Preferences))
232			!= sizeof(Preferences)) {
233		LOG("can't save settings\n");
234		return B_ERROR;
235	}
236
237	return B_OK;
238}
239
240
241template<typename Preferences>
242bool
243PreferencesStorage<Preferences>::DefaultsSet()
244{
245	return fPreferences.IsEqual(fDefaultPreferences);
246}
247
248
249template<typename Preferences>
250bool
251PreferencesStorage<Preferences>::StartPrefsSet()
252{
253	return fPreferences.IsEqual(fStartPreferences);
254}
255
256
257template<typename Preferences>
258PrefFileWatcher<Preferences>::PrefFileWatcher(
259	PreferencesStorage<Preferences>* storage, BHandler* target)
260	:
261	BMessageFilter(B_PROGRAMMED_DELIVERY, B_ANY_SOURCE),
262	fPreferencesStorage(storage),
263	fTarget(target)
264{
265	BPath path;
266	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
267
268	BEntry entry(path.Path());
269	if (entry.GetNodeRef(&fPreferencesDirectoryNode) == B_OK)
270		watch_node(&fPreferencesDirectoryNode, B_WATCH_DIRECTORY, fTarget);
271
272	path.Append(fPreferencesStorage->PreferencesFile().String());
273	entry.SetTo(path.Path());
274
275	if (entry.GetNodeRef(&fPreferencesNode) == B_OK)
276		watch_node(&fPreferencesNode, B_WATCH_STAT, fTarget);
277}
278
279
280template<typename Preferences>
281PrefFileWatcher<Preferences>::~PrefFileWatcher()
282{
283	stop_watching(fTarget);
284}
285
286
287template<typename Preferences>
288filter_result
289PrefFileWatcher<Preferences>::Filter(BMessage *msg, BHandler **target)
290{
291	const char *name;
292	ino_t dir = -1;
293	filter_result result = B_DISPATCH_MESSAGE;
294	int32 opcode;
295	BPath path;
296	node_ref nref;
297
298	if (msg->what != B_NODE_MONITOR
299			|| msg->FindInt32("opcode", &opcode) != B_OK)
300		return result;
301
302	switch (opcode) {
303		case B_ENTRY_MOVED:
304			msg->FindInt64("to directory", dir);
305			if (dir != fPreferencesDirectoryNode.node)
306				break;
307			// supposed to fall through
308
309		case B_ENTRY_CREATED:
310			msg->FindString("name", &name);
311			fPreferencesStorage->GetPreferencesPath(path);
312			if (path.Path() == name) {
313				msg->FindInt32("device", &fPreferencesNode.device);
314				msg->FindInt64("node", &fPreferencesNode.node);
315				watch_node(&fPreferencesNode, B_WATCH_STAT, fTarget);
316			}
317			fPreferencesStorage->LoadPreferences();
318			msg->what = kUpdatedPreferences;
319			break;
320
321		case B_ENTRY_REMOVED:
322			msg->FindInt32("device", &nref.device);
323			msg->FindInt64("node", &nref.node);
324			if (fPreferencesNode == nref) {
325				// stop all watching
326				stop_watching(fTarget);
327				// and start watching the directory again
328				watch_node(&fPreferencesDirectoryNode, B_WATCH_DIRECTORY,
329					fTarget);
330				msg->what = kUpdatedPreferences;
331			}
332			break;
333
334		case B_STAT_CHANGED:
335			msg->FindInt32("device", &nref.device);
336			msg->FindInt64("node", &nref.node);
337			if (fPreferencesNode == nref) {
338				fPreferencesStorage->LoadPreferences();
339				msg->what = kUpdatedPreferences;
340			}
341			break;
342	}
343	return result;
344}
345
346
347template<typename Preferences>
348PreferencesWindow<Preferences>::PreferencesWindow(const char* title,
349	const char* file, const Preferences& defaultPreferences)
350	:
351	BWindow(BRect(50, 50, 400, 350), title, B_TITLED_WINDOW,
352		B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS),
353	PreferencesStorage<Preferences>(file, defaultPreferences),
354	fPreferencesView(NULL)
355{
356	BGroupView* buttonView = new BGroupView(B_HORIZONTAL);
357	fDefaultButton = new BButton(B_TRANSLATE("Defaults"),
358		new BMessage(kDefaultMsg));
359
360	buttonView->AddChild(fDefaultButton);
361	buttonView->GetLayout()->AddItem(
362		BSpaceLayoutItem::CreateHorizontalStrut(7));
363	fRevertButton = new BButton(B_TRANSLATE("Revert"),
364		new BMessage(kRevertMsg));
365
366	buttonView->AddChild(fRevertButton);
367	buttonView->GetLayout()->AddItem(BSpaceLayoutItem::CreateGlue());
368
369	_UpdateButtons();
370
371	SetLayout(new BGroupLayout(B_VERTICAL));
372	fRootLayout = new BGroupLayout(B_VERTICAL);
373	fRootLayout->SetInsets(10, 10, 10, 10);
374	fRootLayout->SetSpacing(10);
375	BView* rootView = new BView("root view", 0, fRootLayout);
376	AddChild(rootView);
377	rootView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
378
379	fRootLayout->AddView(buttonView);
380
381	BSize size = fRootLayout->PreferredSize();
382	ResizeTo(size.width, size.height);
383	_MoveToPosition();
384}
385
386
387template<typename Preferences>
388PreferencesWindow<Preferences>::~PreferencesWindow()
389{
390	PreferencesStorage<Preferences>::SetWindowPosition(Frame().LeftTop());
391}
392
393
394template<typename Preferences>
395void
396PreferencesWindow<Preferences>::MessageReceived(BMessage *msg)
397{
398	switch(msg->what)
399	{
400		case kConfigChangedMsg:
401			_UpdateButtons();
402			break;
403
404		case kDefaultMsg:
405			PreferencesStorage<Preferences>::Defaults();
406			_UpdateButtons();
407			if (fPreferencesView)
408				PostMessage(kDefaultMsg, fPreferencesView);
409			break;
410
411		case kRevertMsg:
412			PreferencesStorage<Preferences>::Revert();
413			_UpdateButtons();
414			if (fPreferencesView)
415				PostMessage(kRevertMsg, fPreferencesView);
416			break;
417
418		default:
419			BWindow::MessageReceived(msg);
420	}
421}
422
423
424template<typename Preferences>
425bool
426PreferencesWindow<Preferences>::QuitRequested()
427{
428	be_app->PostMessage(B_QUIT_REQUESTED);
429	return true;
430}
431
432
433template<typename Preferences>
434bool
435PreferencesWindow<Preferences>::SetPreferencesView(BView* prefView)
436{
437	if (fPreferencesView)
438		return false;
439
440	fPreferencesView = prefView;
441	fRootLayout->AddView(0, fPreferencesView);
442
443	BSize size = fRootLayout->PreferredSize();
444	ResizeTo(size.width, size.height);
445	_MoveToPosition();
446
447	return true;
448}
449
450
451template<typename Preferences>
452void
453PreferencesWindow<Preferences>::_MoveToPosition()
454{
455	BPoint position = PreferencesStorage<Preferences>::WindowPosition();
456	// center window on screen if it had a bad position
457	if (position.x < 0 && position.y < 0){
458		BRect rect = BScreen().Frame();
459		BRect windowFrame = Frame();
460		position.x = (rect.Width() - windowFrame.Width()) / 2;
461		position.y = (rect.Height() - windowFrame.Height()) / 2;
462	}
463	MoveTo(position);
464}
465
466
467template<typename Preferences>
468void
469PreferencesWindow<Preferences>::_UpdateButtons()
470{
471	if (!PreferencesStorage<Preferences>::DefaultsSet())
472		fDefaultButton->SetEnabled(true);
473	else
474		fDefaultButton->SetEnabled(false);
475	if (!PreferencesStorage<Preferences>::StartPrefsSet())
476		fRevertButton->SetEnabled(true);
477	else
478		fRevertButton->SetEnabled(false);
479}
480
481
482#undef B_TRANSLATION_CONTEXT
483
484#endif	// PREFERENCES_WINDOW_h
485