1/*
2 * Copyright 2003-2016 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Axel D��rfler, axeld@pinc-software.de
7 *		J��r��me Duval, jerome.duval@free.fr
8 *		Filip Maryja��ski, widelec@morphos.pl
9 *		Puck Meerburg, puck@puckipedia.nl
10 *		Michael Phipps
11 *		John Scipione, jscipione@gmail.com
12 */
13
14
15#include "ScreenSaverWindow.h"
16
17#include <stdio.h>
18#include <strings.h>
19
20#include <Alignment.h>
21#include <Application.h>
22#include <Box.h>
23#include <Button.h>
24#include <Catalog.h>
25#include <CheckBox.h>
26#include <ControlLook.h>
27#include <DefaultSettingsView.h>
28#include <Directory.h>
29#include <DurationFormat.h>
30#include <Entry.h>
31#include <File.h>
32#include <FindDirectory.h>
33#include <Font.h>
34#include <GroupLayout.h>
35#include <LayoutBuilder.h>
36#include <ListItem.h>
37#include <ListView.h>
38#include <NodeMonitor.h>
39#include <Path.h>
40#include <Rect.h>
41#include <Roster.h>
42#include <Screen.h>
43#include <ScreenSaver.h>
44#include <ScreenSaverRunner.h>
45#include <ScrollView.h>
46#include <Size.h>
47#include <Slider.h>
48#include <StringView.h>
49#include <TabView.h>
50#include <TextView.h>
51
52#include <algorithm>
53	// for std::max and std::min
54
55#include "PreviewView.h"
56#include "ScreenCornerSelector.h"
57#include "ScreenSaverItem.h"
58#include "ScreenSaverShared.h"
59
60#undef B_TRANSLATION_CONTEXT
61#define B_TRANSLATION_CONTEXT "ScreenSaver"
62
63
64const uint32 kPreviewMonitorGap = 16;
65const uint32 kMinSettingsWidth = 230;
66const uint32 kMinSettingsHeight = 120;
67
68const int32 kMsgSaverSelected = 'SSEL';
69const int32 kMsgPasswordCheckBox = 'PWCB';
70const int32 kMsgRunSliderChanged = 'RSch';
71const int32 kMsgRunSliderUpdate = 'RSup';
72const int32 kMsgPasswordSliderChanged = 'PWch';
73const int32 kMsgPasswordSliderUpdate = 'PWup';
74const int32 kMsgChangePassword = 'PWBT';
75const int32 kMsgEnableScreenSaverBox = 'ESCH';
76
77const int32 kMsgTurnOffCheckBox = 'TUOF';
78const int32 kMsgTurnOffSliderChanged = 'TUch';
79const int32 kMsgTurnOffSliderUpdate = 'TUup';
80
81const int32 kMsgFadeCornerChanged = 'fdcc';
82const int32 kMsgNeverFadeCornerChanged = 'nfcc';
83
84const float kWindowWidth = 446.0f;
85const float kWindowHeight = 325.0f;
86const float kDefaultItemSpacingAt12pt = 12.0f * 0.85;
87
88
89class TimeSlider : public BSlider {
90public:
91								TimeSlider(const char* name,
92									uint32 changedMessage,
93									uint32 updateMessage);
94	virtual						~TimeSlider();
95
96	virtual	void				SetValue(int32 value);
97
98			void				SetTime(bigtime_t useconds);
99			bigtime_t			Time() const;
100
101private:
102			void				_TimeToString(bigtime_t useconds,
103									BString& string);
104};
105
106
107class TabView : public BTabView {
108public:
109								TabView();
110
111	virtual	void				MouseDown(BPoint where);
112};
113
114
115class FadeView : public BView {
116public:
117								FadeView(const char* name,
118									ScreenSaverSettings& settings);
119
120	virtual	void				AttachedToWindow();
121	virtual	void				MessageReceived(BMessage* message);
122
123			void				UpdateTurnOffScreen();
124			void				UpdateStatus();
125
126private:
127			void				_UpdateColors();
128			ScreenSaverSettings&	fSettings;
129			uint32				fTurnOffScreenFlags;
130
131			BCheckBox*			fEnableCheckBox;
132			TimeSlider*			fRunSlider;
133
134			BTextView*			fTurnOffNotSupported;
135			BCheckBox*			fTurnOffCheckBox;
136			TimeSlider*			fTurnOffSlider;
137
138			BTextView*			fFadeNeverText;
139			BTextView*			fFadeNowText;
140
141			BCheckBox*			fPasswordCheckBox;
142			TimeSlider*			fPasswordSlider;
143			BButton*			fPasswordButton;
144
145			ScreenCornerSelector*	fFadeNow;
146			ScreenCornerSelector*	fFadeNever;
147};
148
149
150class ModulesView : public BView {
151public:
152								ModulesView(const char* name,
153									ScreenSaverSettings& settings);
154	virtual						~ModulesView();
155
156	virtual	void				DetachedFromWindow();
157	virtual	void				AttachedToWindow();
158	virtual	void				AllAttached();
159	virtual	void				MessageReceived(BMessage* message);
160
161			void				EmptyScreenSaverList();
162			void				PopulateScreenSaverList();
163
164			void				SaveState();
165
166			BScreenSaver*		ScreenSaver();
167
168private:
169	friend class TabView;
170
171	static	int					_CompareScreenSaverItems(const void* left,
172									const void* right);
173
174			void				_CloseSaver();
175			void				_OpenSaver();
176			void				_AddNewScreenSaverToList(const char* name,
177								BPath* path);
178			void				_RemoveScreenSaverFromList(const char* name);
179
180private:
181		ScreenSaverSettings&	fSettings;
182
183			BListView*			fScreenSaversListView;
184			BButton*			fTestButton;
185
186			ScreenSaverRunner*	fSaverRunner;
187			BString				fCurrentName;
188
189			BBox*				fSettingsBox;
190			BView*				fSettingsView;
191
192			PreviewView*		fPreviewView;
193
194			team_id				fScreenSaverTestTeam;
195};
196
197
198//	#pragma mark - TimeSlider
199
200
201static const int32 kTimeInUnits[] = {
202	30,    60,   90,
203	120,   150,  180,
204	240,   300,  360,
205	420,   480,  540,
206	600,   900,  1200,
207	1500,  1800, 2400,
208	3000,  3600, 5400,
209	7200,  9000, 10800,
210	14400, 18000
211};
212
213static const int32 kTimeUnitCount
214	= sizeof(kTimeInUnits) / sizeof(kTimeInUnits[0]);
215
216
217TimeSlider::TimeSlider(const char* name, uint32 changedMessage,
218	uint32 updateMessage)
219	:
220	BSlider(name, B_TRANSLATE("30 seconds"), new BMessage(changedMessage),
221		0, kTimeUnitCount - 1, B_HORIZONTAL, B_TRIANGLE_THUMB)
222{
223	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
224	SetModificationMessage(new BMessage(updateMessage));
225	SetBarThickness(10);
226}
227
228
229TimeSlider::~TimeSlider()
230{
231}
232
233
234void
235TimeSlider::SetValue(int32 value)
236{
237	int32 oldValue = Value();
238	BSlider::SetValue(value);
239
240	if (oldValue != Value()) {
241		BString label;
242		_TimeToString(kTimeInUnits[Value()] * 1000000LL, label);
243		SetLabel(label.String());
244	}
245}
246
247
248void
249TimeSlider::SetTime(bigtime_t useconds)
250{
251	for (int t = 0; t < kTimeUnitCount; t++) {
252		if (kTimeInUnits[t] * 1000000LL == useconds) {
253			SetValue(t);
254			break;
255		}
256	}
257}
258
259
260bigtime_t
261TimeSlider::Time() const
262{
263	return 1000000LL * kTimeInUnits[Value()];
264}
265
266
267void
268TimeSlider::_TimeToString(bigtime_t useconds, BString& string)
269{
270	BDurationFormat formatter;
271	formatter.Format(string, 0, useconds);
272}
273
274
275//	#pragma mark - FadeView
276
277
278FadeView::FadeView(const char* name, ScreenSaverSettings& settings)
279	:
280	BView(name, B_WILL_DRAW),
281	fSettings(settings)
282{
283	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
284
285	font_height fontHeight;
286	be_plain_font->GetHeight(&fontHeight);
287	float textHeight = ceilf(fontHeight.ascent + fontHeight.descent);
288
289	fEnableCheckBox = new BCheckBox("EnableCheckBox",
290		B_TRANSLATE("Enable screensaver"),
291		new BMessage(kMsgEnableScreenSaverBox));
292
293	BBox* box = new BBox("EnableScreenSaverBox");
294	box->SetLabel(fEnableCheckBox);
295
296	// Start Screensaver
297	BStringView* startScreenSaver = new BStringView("startScreenSaver",
298		B_TRANSLATE("Start screensaver"));
299	startScreenSaver->SetAlignment(B_ALIGN_RIGHT);
300
301	fRunSlider = new TimeSlider("RunSlider", kMsgRunSliderChanged,
302		kMsgRunSliderUpdate);
303
304	// Turn Off
305	rgb_color textColor = disable_color(ui_color(B_PANEL_TEXT_COLOR),
306		ViewColor());
307
308	fTurnOffNotSupported = new BTextView("not_supported", be_plain_font,
309		&textColor, B_WILL_DRAW);
310	fTurnOffNotSupported->SetExplicitMinSize(BSize(B_SIZE_UNSET,
311		3 + textHeight * 3));
312	fTurnOffNotSupported->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
313	fTurnOffNotSupported->MakeEditable(false);
314	fTurnOffNotSupported->MakeSelectable(false);
315	fTurnOffNotSupported->SetText(
316		B_TRANSLATE("Display Power Management Signaling not available"));
317
318	fTurnOffCheckBox = new BCheckBox("TurnOffScreenCheckBox",
319		B_TRANSLATE("Turn off screen"), new BMessage(kMsgTurnOffCheckBox));
320	fTurnOffCheckBox->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
321		B_ALIGN_VERTICAL_CENTER));
322
323	fTurnOffSlider = new TimeSlider("TurnOffSlider", kMsgTurnOffSliderChanged,
324		kMsgTurnOffSliderUpdate);
325
326	// Password
327	fPasswordCheckBox = new BCheckBox("PasswordCheckbox",
328		B_TRANSLATE("Password lock"), new BMessage(kMsgPasswordCheckBox));
329	fPasswordCheckBox->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
330		B_ALIGN_VERTICAL_CENTER));
331
332	fPasswordSlider = new TimeSlider("PasswordSlider",
333		kMsgPasswordSliderChanged, kMsgPasswordSliderUpdate);
334
335	fPasswordButton = new BButton("PasswordButton",
336		B_TRANSLATE("Password" B_UTF8_ELLIPSIS),
337		new BMessage(kMsgChangePassword));
338
339	// Bottom
340	float monitorHeight = 10 + textHeight * 3;
341	float aspectRatio = 4.0f / 3.0f;
342	float monitorWidth = monitorHeight * aspectRatio;
343	BRect monitorRect = BRect(0, 0, monitorWidth, monitorHeight);
344
345	fFadeNow = new ScreenCornerSelector(monitorRect, "FadeNow",
346		new BMessage(kMsgFadeCornerChanged), B_FOLLOW_NONE);
347	fFadeNowText = new BTextView("FadeNowText", B_WILL_DRAW);
348	fFadeNowText->SetExplicitMinSize(BSize(B_SIZE_UNSET,
349		4 + textHeight * 4));
350	fFadeNowText->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
351	fFadeNowText->MakeEditable(false);
352	fFadeNowText->MakeSelectable(false);
353	fFadeNowText->SetText(B_TRANSLATE("Fade now when mouse is here"));
354
355	fFadeNever = new ScreenCornerSelector(monitorRect, "FadeNever",
356		new BMessage(kMsgNeverFadeCornerChanged), B_FOLLOW_NONE);
357	fFadeNeverText = new BTextView("FadeNeverText", B_WILL_DRAW);
358	fFadeNeverText->SetExplicitMinSize(BSize(B_SIZE_UNSET,
359		4 + textHeight * 4));
360	fFadeNeverText->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
361	fFadeNeverText->MakeEditable(false);
362	fFadeNeverText->MakeSelectable(false);
363	fFadeNeverText->SetText(B_TRANSLATE("Don't fade when mouse is here"));
364
365	box->AddChild(BLayoutBuilder::Group<>(B_VERTICAL, 0)
366		.SetInsets(B_USE_DEFAULT_SPACING, 0, B_USE_DEFAULT_SPACING,
367			B_USE_DEFAULT_SPACING)
368		.AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
369			.Add(startScreenSaver, 0, 0)
370			.Add(fRunSlider, 1, 0)
371			.Add(fTurnOffCheckBox, 0, 1)
372			.Add(BLayoutBuilder::Group<>(B_VERTICAL)
373				.Add(fTurnOffNotSupported)
374				.Add(fTurnOffSlider)
375				.View(), 1, 1)
376			.Add(fPasswordCheckBox, 0, 2)
377			.Add(fPasswordSlider, 1, 2)
378			.End()
379		.AddGroup(B_HORIZONTAL)
380			.AddGlue()
381			.Add(fPasswordButton)
382			.End()
383		.AddGlue()
384		.AddGroup(B_HORIZONTAL)
385			.Add(fFadeNow)
386			.AddGroup(B_VERTICAL, 0)
387				.Add(fFadeNowText)
388				.AddGlue()
389				.End()
390			.Add(fFadeNever)
391			.AddGroup(B_VERTICAL, 0)
392				.Add(fFadeNeverText)
393				.AddGlue()
394				.End()
395			.End()
396		.AddGlue()
397		.View());
398
399	BLayoutBuilder::Group<>(this, B_HORIZONTAL)
400		.SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
401			B_USE_WINDOW_SPACING, 0)
402		.Add(box)
403		.End();
404
405}
406
407
408void
409FadeView::AttachedToWindow()
410{
411	fEnableCheckBox->SetTarget(this);
412	fRunSlider->SetTarget(this);
413	fTurnOffCheckBox->SetTarget(this);
414	fTurnOffSlider->SetTarget(this);
415	fFadeNow->SetTarget(this);
416	fFadeNever->SetTarget(this);
417	fPasswordCheckBox->SetTarget(this);
418	fPasswordSlider->SetTarget(this);
419
420	fEnableCheckBox->SetValue(
421		fSettings.TimeFlags() & ENABLE_SAVER ? B_CONTROL_ON : B_CONTROL_OFF);
422	fRunSlider->SetTime(fSettings.BlankTime());
423	fTurnOffSlider->SetTime(fSettings.OffTime() + fSettings.BlankTime());
424	fFadeNow->SetCorner(fSettings.BlankCorner());
425	fFadeNever->SetCorner(fSettings.NeverBlankCorner());
426	fPasswordCheckBox->SetValue(fSettings.LockEnable());
427	fPasswordSlider->SetTime(fSettings.PasswordTime());
428
429	_UpdateColors();
430	UpdateTurnOffScreen();
431	UpdateStatus();
432}
433
434
435void
436FadeView::MessageReceived(BMessage *message)
437{
438	switch (message->what) {
439		case B_COLORS_UPDATED:
440			_UpdateColors();
441			break;
442		case kMsgRunSliderChanged:
443		case kMsgRunSliderUpdate:
444			if (fRunSlider->Value() > fTurnOffSlider->Value())
445				fTurnOffSlider->SetValue(fRunSlider->Value());
446
447			if (fRunSlider->Value() > fPasswordSlider->Value())
448				fPasswordSlider->SetValue(fRunSlider->Value());
449			break;
450
451		case kMsgTurnOffSliderChanged:
452		case kMsgTurnOffSliderUpdate:
453			if (fRunSlider->Value() > fTurnOffSlider->Value())
454				fRunSlider->SetValue(fTurnOffSlider->Value());
455			break;
456
457		case kMsgPasswordSliderChanged:
458		case kMsgPasswordSliderUpdate:
459			if (fPasswordSlider->Value() < fRunSlider->Value())
460				fRunSlider->SetValue(fPasswordSlider->Value());
461			break;
462
463		case kMsgTurnOffCheckBox:
464			fTurnOffSlider->SetEnabled(
465				fTurnOffCheckBox->Value() == B_CONTROL_ON);
466			break;
467	}
468
469	switch (message->what) {
470		case kMsgRunSliderChanged:
471		case kMsgTurnOffSliderChanged:
472		case kMsgPasswordSliderChanged:
473		case kMsgPasswordCheckBox:
474		case kMsgEnableScreenSaverBox:
475		case kMsgFadeCornerChanged:
476		case kMsgNeverFadeCornerChanged:
477			UpdateStatus();
478			fSettings.Save();
479			break;
480
481		default:
482			BView::MessageReceived(message);
483	}
484}
485
486
487void
488FadeView::UpdateTurnOffScreen()
489{
490	bool enabled = (fSettings.TimeFlags() & ENABLE_DPMS_MASK) != 0;
491
492	BScreen screen(Window());
493	uint32 dpmsCapabilities = screen.DPMSCapabilites();
494
495	fTurnOffScreenFlags = 0;
496	if (dpmsCapabilities & B_DPMS_OFF)
497		fTurnOffScreenFlags |= ENABLE_DPMS_OFF;
498	if (dpmsCapabilities & B_DPMS_STAND_BY)
499		fTurnOffScreenFlags |= ENABLE_DPMS_STAND_BY;
500	if (dpmsCapabilities & B_DPMS_SUSPEND)
501		fTurnOffScreenFlags |= ENABLE_DPMS_SUSPEND;
502
503	fTurnOffCheckBox->SetValue(enabled && fTurnOffScreenFlags != 0
504		? B_CONTROL_ON : B_CONTROL_OFF);
505
506	enabled = fEnableCheckBox->Value() == B_CONTROL_ON;
507	fTurnOffCheckBox->SetEnabled(enabled && fTurnOffScreenFlags != 0);
508	if (fTurnOffScreenFlags != 0) {
509		fTurnOffNotSupported->Hide();
510		fTurnOffSlider->Show();
511	} else {
512		fTurnOffSlider->Hide();
513		fTurnOffNotSupported->Show();
514	}
515}
516
517
518void
519FadeView::UpdateStatus()
520{
521	Window()->DisableUpdates();
522
523	bool enabled = fEnableCheckBox->Value() == B_CONTROL_ON;
524	fPasswordCheckBox->SetEnabled(enabled);
525	fTurnOffCheckBox->SetEnabled(enabled && fTurnOffScreenFlags != 0);
526	fRunSlider->SetEnabled(enabled);
527	fTurnOffSlider->SetEnabled(enabled && fTurnOffCheckBox->Value());
528	fPasswordSlider->SetEnabled(enabled && fPasswordCheckBox->Value());
529	fPasswordButton->SetEnabled(enabled && fPasswordCheckBox->Value());
530
531	Window()->EnableUpdates();
532
533	// Update the saved preferences
534	fSettings.SetWindowFrame(Frame());
535	fSettings.SetTimeFlags((enabled ? ENABLE_SAVER : 0)
536		| (fTurnOffCheckBox->Value() ? fTurnOffScreenFlags : 0));
537	fSettings.SetBlankTime(fRunSlider->Time());
538	bigtime_t offTime = fTurnOffSlider->Time() - fSettings.BlankTime();
539	fSettings.SetOffTime(offTime);
540	fSettings.SetSuspendTime(offTime);
541	fSettings.SetStandByTime(offTime);
542	fSettings.SetBlankCorner(fFadeNow->Corner());
543	fSettings.SetNeverBlankCorner(fFadeNever->Corner());
544	fSettings.SetLockEnable(fPasswordCheckBox->Value());
545	fSettings.SetPasswordTime(fPasswordSlider->Time());
546
547	// TODO - Tell the password window to update its stuff
548}
549
550
551void
552FadeView::_UpdateColors()
553{
554	rgb_color color = ui_color(B_PANEL_TEXT_COLOR);
555	fFadeNeverText->SetFontAndColor(be_plain_font, 0, &color);
556	fFadeNowText->SetFontAndColor(be_plain_font, 0, &color);
557}
558
559
560//	#pragma mark - ModulesView
561
562
563ModulesView::ModulesView(const char* name, ScreenSaverSettings& settings)
564	:
565	BView(name, B_WILL_DRAW),
566	fSettings(settings),
567	fScreenSaversListView(new BListView("SaversListView")),
568	fTestButton(new BButton("TestButton", B_TRANSLATE("Test"),
569		new BMessage(kMsgTestSaver))),
570	fSaverRunner(NULL),
571	fSettingsBox(new BBox("SettingsBox")),
572	fSettingsView(NULL),
573	fPreviewView(new PreviewView("preview")),
574	fScreenSaverTestTeam(-1)
575{
576	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
577
578	fScreenSaversListView->SetSelectionMessage(
579		new BMessage(kMsgSaverSelected));
580	fScreenSaversListView->SetInvocationMessage(
581		new BMessage(kMsgTestSaver));
582	BScrollView* saversListScrollView = new BScrollView("scroll_list",
583		fScreenSaversListView, 0, false, true);
584
585	fSettingsBox->SetLabel(B_TRANSLATE("Screensaver settings"));
586	fSettingsBox->SetExplicitMinSize(BSize(
587		floorf(be_control_look->DefaultItemSpacing()
588			* ((kWindowWidth - 157.0f) / kDefaultItemSpacingAt12pt)),
589		B_SIZE_UNSET));
590
591	BLayoutBuilder::Group<>(this, B_HORIZONTAL)
592		.SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
593			B_USE_WINDOW_SPACING, 0)
594		.AddGroup(B_VERTICAL)
595			.Add(fPreviewView)
596			.Add(saversListScrollView)
597			.Add(fTestButton)
598			.End()
599		.Add(fSettingsBox)
600		.End();
601}
602
603
604ModulesView::~ModulesView()
605{
606	stop_watching(this);
607
608	delete fTestButton;
609	delete fSettingsBox;
610	delete fPreviewView;
611}
612
613
614void
615ModulesView::DetachedFromWindow()
616{
617	SaveState();
618	EmptyScreenSaverList();
619
620	_CloseSaver();
621}
622
623
624void
625ModulesView::AttachedToWindow()
626{
627	fScreenSaversListView->SetTarget(this);
628	fTestButton->SetTarget(this);
629}
630
631
632void
633ModulesView::AllAttached()
634{
635	PopulateScreenSaverList();
636}
637
638
639void
640ModulesView::MessageReceived(BMessage* message)
641{
642	switch (message->what) {
643		case kMsgSaverSelected:
644		{
645			int32 selection = fScreenSaversListView->CurrentSelection();
646			if (selection < 0)
647				break;
648
649			ScreenSaverItem* item
650				= (ScreenSaverItem*)fScreenSaversListView->ItemAt(selection);
651			if (item == NULL)
652				break;
653
654			if (strcmp(item->Text(), B_TRANSLATE("Blackness")) == 0)
655				fSettings.SetModuleName("");
656			else
657				fSettings.SetModuleName(item->Text());
658
659			SaveState();
660			_CloseSaver();
661			_OpenSaver();
662			fSettings.Save();
663			break;
664		}
665
666		case kMsgTestSaver:
667		{
668			SaveState();
669			fSettings.Save();
670
671			_CloseSaver();
672
673			be_roster->StartWatching(BMessenger(this, Looper()),
674				B_REQUEST_QUIT);
675			BMessage message(kMsgTestSaver);
676			if (be_roster->Launch(SCREEN_BLANKER_SIG, &message,
677					&fScreenSaverTestTeam) == B_OK) {
678				break;
679			}
680
681			// Try really hard to launch it. It's very likely that this fails
682			// when we run from the CD, and there is only an incomplete mime
683			// database for example...
684			BPath path;
685			if (find_directory(B_SYSTEM_BIN_DIRECTORY, &path) != B_OK
686				|| path.Append("screen_blanker") != B_OK) {
687				path.SetTo("/bin/screen_blanker");
688			}
689
690			BEntry entry(path.Path());
691			entry_ref ref;
692			if (entry.GetRef(&ref) == B_OK) {
693				be_roster->Launch(&ref, &message,
694					&fScreenSaverTestTeam);
695			}
696			break;
697		}
698
699		case B_NODE_MONITOR:
700		{
701			switch (message->GetInt32("opcode", 0)) {
702				case B_ENTRY_CREATED:
703				{
704					const char* name;
705					node_ref nodeRef;
706
707					message->FindString("name", &name);
708					message->FindInt32("device", &nodeRef.device);
709					message->FindInt64("directory", &nodeRef.node);
710
711					BDirectory dir(&nodeRef);
712
713					if (dir.InitCheck() == B_OK) {
714						BPath path(&dir);
715						_AddNewScreenSaverToList(name, &path);
716					}
717					break;
718				}
719
720
721				case B_ENTRY_MOVED:
722				case B_ENTRY_REMOVED:
723				{
724					const char* name;
725
726					message->FindString("name", &name);
727					_RemoveScreenSaverFromList(name);
728
729					break;
730				}
731
732				default:
733					// ignore any other operations
734					break;
735			}
736			break;
737		}
738
739		case B_SOME_APP_QUIT:
740		{
741			team_id team;
742			if (message->FindInt32("be:team", &team) == B_OK
743				&& team == fScreenSaverTestTeam) {
744				be_roster->StopWatching(this);
745				_OpenSaver();
746			}
747			break;
748		}
749
750		default:
751			BView::MessageReceived(message);
752	}
753}
754
755
756void
757ModulesView::SaveState()
758{
759	BScreenSaver* saver = ScreenSaver();
760	if (saver == NULL)
761		return;
762
763	BMessage state;
764	if (saver->SaveState(&state) == B_OK)
765		fSettings.SetModuleState(fCurrentName.String(), &state);
766}
767
768
769void
770ModulesView::EmptyScreenSaverList()
771{
772	fScreenSaversListView->DeselectAll();
773	while (BListItem* item = fScreenSaversListView->RemoveItem((int32)0))
774		delete item;
775}
776
777
778void
779ModulesView::PopulateScreenSaverList()
780{
781	// Blackness is a built-in screen saver
782	ScreenSaverItem* defaultItem
783		= new ScreenSaverItem(B_TRANSLATE("Blackness"), "");
784	fScreenSaversListView->AddItem(defaultItem);
785
786	// Iterate over add-on directories, and add their files to the list view
787
788	directory_which which[] = {
789		B_USER_NONPACKAGED_ADDONS_DIRECTORY,
790		B_USER_ADDONS_DIRECTORY,
791		B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY,
792		B_SYSTEM_ADDONS_DIRECTORY,
793	};
794	ScreenSaverItem* selectedItem = NULL;
795
796	for (uint32 i = 0; i < sizeof(which) / sizeof(which[0]); i++) {
797		BPath basePath;
798		if (find_directory(which[i], &basePath) != B_OK)
799			continue;
800		else if (basePath.Append("Screen Savers", true) != B_OK)
801			continue;
802
803		BDirectory dir(basePath.Path());
804		BEntry entry;
805		node_ref nodeRef;
806
807		dir.GetNodeRef(&nodeRef);
808		watch_node(&nodeRef, B_WATCH_DIRECTORY, this);
809
810		while (dir.GetNextEntry(&entry, true) == B_OK) {
811			char name[B_FILE_NAME_LENGTH];
812			if (entry.GetName(name) != B_OK)
813				continue;
814
815			BPath path(basePath);
816			if (path.Append(name) != B_OK)
817				continue;
818
819			ScreenSaverItem* item = new ScreenSaverItem(name, path.Path());
820			fScreenSaversListView->AddItem(item);
821
822			if (selectedItem != NULL)
823				continue;
824
825			if (strcmp(fSettings.ModuleName(), item->Text()) == 0)
826				selectedItem = item;
827		}
828	}
829
830	fScreenSaversListView->SortItems(_CompareScreenSaverItems);
831	if (selectedItem == NULL)
832		selectedItem = defaultItem;
833
834	fScreenSaversListView->Select(fScreenSaversListView->IndexOf(selectedItem));
835	fScreenSaversListView->ScrollToSelection();
836}
837
838
839//! Sorting function for ScreenSaverItems
840int
841ModulesView::_CompareScreenSaverItems(const void* left, const void* right)
842{
843	ScreenSaverItem* leftItem  = *(ScreenSaverItem **)left;
844	ScreenSaverItem* rightItem = *(ScreenSaverItem **)right;
845
846	return strcasecmp(leftItem->Text(), rightItem->Text());
847}
848
849
850BScreenSaver*
851ModulesView::ScreenSaver()
852{
853	if (fSaverRunner != NULL)
854		return fSaverRunner->ScreenSaver();
855
856	return NULL;
857}
858
859
860void
861ModulesView::_CloseSaver()
862{
863	// remove old screen saver preview & config
864
865	BScreenSaver* saver = ScreenSaver();
866	BView* view = fPreviewView->RemovePreview();
867	if (fSettingsView != NULL)
868		fSettingsBox->RemoveChild(fSettingsView);
869
870	if (fSaverRunner != NULL)
871		fSaverRunner->Quit();
872
873	if (saver != NULL)
874		saver->StopConfig();
875
876	delete view;
877	delete fSettingsView;
878	delete fSaverRunner;
879		// the saver runner also unloads the add-on, so it must
880		// be deleted last
881
882	fSettingsView = NULL;
883	fSaverRunner = NULL;
884}
885
886
887void
888ModulesView::_OpenSaver()
889{
890	// create new screen saver preview & config
891
892	BView* view = fPreviewView->AddPreview();
893	fCurrentName = fSettings.ModuleName();
894	fSaverRunner = new ScreenSaverRunner(view->Window(), view, fSettings);
895
896#ifdef __HAIKU__
897	BRect rect = fSettingsBox->InnerFrame().InsetByCopy(4, 4);
898#else
899	BRect rect = fSettingsBox->Bounds().InsetByCopy(4, 4);
900	rect.top += 14;
901#endif
902	fSettingsView = new BView(rect, "SettingsView", B_FOLLOW_ALL, B_WILL_DRAW);
903
904	fSettingsView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
905	fSettingsBox->AddChild(fSettingsView);
906
907	BScreenSaver* saver = ScreenSaver();
908	if (saver != NULL && fSettingsView != NULL) {
909		saver->StartConfig(fSettingsView);
910		if (saver->StartSaver(view, true) == B_OK) {
911			fPreviewView->HideNoPreview();
912			fSaverRunner->Run();
913		} else
914			fPreviewView->ShowNoPreview();
915	} else {
916		// Failed to load OR this is the "Blackness" screensaver. Show a black
917		// preview (this is what will happen in both cases when screen_blanker
918		// runs).
919		fPreviewView->HideNoPreview();
920	}
921
922	if (fSettingsView->ChildAt(0) == NULL) {
923		// There are no settings at all, we add the module name here to
924		// let it look a bit better at least.
925		BPrivate::BuildDefaultSettingsView(fSettingsView,
926			fSettings.ModuleName()[0] ? fSettings.ModuleName()
927				: B_TRANSLATE("Blackness"),
928				saver != NULL || !fSettings.ModuleName()[0]
929					? B_TRANSLATE("No options available")
930					: B_TRANSLATE("Could not load screen saver"));
931	}
932}
933
934
935void
936ModulesView::_AddNewScreenSaverToList(const char* name, BPath* path)
937{
938	int32 oldSelected = fScreenSaversListView->CurrentSelection();
939	ScreenSaverItem* selectedItem = (ScreenSaverItem*)fScreenSaversListView->ItemAt(
940		oldSelected);
941
942	path->Append(name);
943	fScreenSaversListView->AddItem(new ScreenSaverItem(name, path->Path()));
944	fScreenSaversListView->SortItems(_CompareScreenSaverItems);
945
946	if (selectedItem != NULL) {
947		fScreenSaversListView->Select(fScreenSaversListView->IndexOf(
948			selectedItem));
949		fScreenSaversListView->ScrollToSelection();
950	}
951}
952
953
954void
955ModulesView::_RemoveScreenSaverFromList(const char* name)
956{
957	int32 oldSelected = fScreenSaversListView->CurrentSelection();
958	ScreenSaverItem* selectedItem = (ScreenSaverItem*)fScreenSaversListView->ItemAt(
959		oldSelected);
960
961	if (strcasecmp(selectedItem->Text(), name) == 0) {
962		fScreenSaversListView->RemoveItem(selectedItem);
963		fScreenSaversListView->SortItems(_CompareScreenSaverItems);
964		fScreenSaversListView->Select(0);
965		fScreenSaversListView->ScrollToSelection();
966		return;
967	}
968
969	for (int i = 0, max = fScreenSaversListView->CountItems(); i < max; i++) {
970		ScreenSaverItem* item = (ScreenSaverItem*)fScreenSaversListView->ItemAt(
971			i);
972
973		if (strcasecmp(item->Text(), name) == 0) {
974			fScreenSaversListView->RemoveItem(item);
975			delete item;
976			break;
977		}
978	}
979
980	fScreenSaversListView->SortItems(_CompareScreenSaverItems);
981
982	oldSelected = fScreenSaversListView->IndexOf(selectedItem);
983	fScreenSaversListView->Select(oldSelected);
984	fScreenSaversListView->ScrollToSelection();
985}
986
987
988//	#pragma mark - TabView
989
990
991TabView::TabView()
992	:
993	BTabView("tab_view", B_WIDTH_FROM_LABEL)
994{
995}
996
997
998void
999TabView::MouseDown(BPoint where)
1000{
1001	BTab* fadeTab = TabAt(0);
1002	BRect fadeTabFrame(TabFrame(0));
1003	BTab* modulesTab = TabAt(1);
1004	BRect modulesTabFrame(TabFrame(1));
1005	ModulesView* modulesView = NULL;
1006
1007	if (modulesTab != NULL)
1008		modulesView = dynamic_cast<ModulesView*>(modulesTab->View());
1009
1010	if (fadeTab != NULL && Selection() != 0 && fadeTabFrame.Contains(where)
1011		&& modulesView != NULL) {
1012		// clicked on the fade tab
1013		modulesView->SaveState();
1014		modulesView->_CloseSaver();
1015	} else if (modulesTab != NULL && Selection() != 1
1016		&& modulesTabFrame.Contains(where) && modulesView != NULL) {
1017		// clicked on the modules tab
1018		BMessage message(kMsgSaverSelected);
1019		modulesView->MessageReceived(&message);
1020	}
1021
1022	BTabView::MouseDown(where);
1023}
1024
1025
1026//	#pragma mark - ScreenSaverWindow
1027
1028
1029ScreenSaverWindow::ScreenSaverWindow()
1030	:
1031	BWindow(BRect(50.0f, 50.0f, 50.0f + kWindowWidth, 50.0f + kWindowHeight),
1032		B_TRANSLATE_SYSTEM_NAME("ScreenSaver"), B_TITLED_WINDOW,
1033		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS)
1034{
1035	fSettings.Load();
1036
1037	fMinWidth = floorf(be_control_look->DefaultItemSpacing()
1038		* (kWindowWidth / kDefaultItemSpacingAt12pt));
1039
1040	font_height fontHeight;
1041	be_plain_font->GetHeight(&fontHeight);
1042	float textHeight = ceilf(fontHeight.ascent + fontHeight.descent);
1043
1044	fMinHeight = ceilf(std::max(kWindowHeight, textHeight * 28));
1045
1046	// Create the password editing window
1047	fPasswordWindow = new PasswordWindow(fSettings);
1048	fPasswordWindow->Run();
1049
1050	// Create the tab view
1051	fTabView = new TabView();
1052	fTabView->SetBorder(B_NO_BORDER);
1053
1054	// Create the controls inside the tabs
1055	fFadeView = new FadeView(B_TRANSLATE("General"), fSettings);
1056	fModulesView = new ModulesView(B_TRANSLATE("Screensavers"), fSettings);
1057
1058	fTabView->AddTab(fFadeView);
1059	fTabView->AddTab(fModulesView);
1060
1061	// Create the topmost background view
1062	BView* topView = new BView("topView", B_WILL_DRAW);
1063	topView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1064	topView->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
1065		B_ALIGN_USE_FULL_HEIGHT));
1066	topView->SetExplicitMinSize(BSize(fMinWidth, fMinHeight));
1067	BLayoutBuilder::Group<>(topView, B_VERTICAL)
1068		.SetInsets(0, B_USE_DEFAULT_SPACING, 0, B_USE_WINDOW_SPACING)
1069		.Add(fTabView)
1070		.End();
1071
1072	SetLayout(new BGroupLayout(B_VERTICAL));
1073	GetLayout()->AddView(topView);
1074
1075	fTabView->Select(fSettings.WindowTab());
1076
1077	if (fSettings.WindowFrame().left > 0 && fSettings.WindowFrame().top > 0)
1078		MoveTo(fSettings.WindowFrame().left, fSettings.WindowFrame().top);
1079
1080	if (fSettings.WindowFrame().Width() > 0
1081		&& fSettings.WindowFrame().Height() > 0) {
1082		ResizeTo(fSettings.WindowFrame().Width(),
1083			fSettings.WindowFrame().Height());
1084	}
1085
1086	CenterOnScreen();
1087}
1088
1089
1090ScreenSaverWindow::~ScreenSaverWindow()
1091{
1092	Hide();
1093	fFadeView->UpdateStatus();
1094	fSettings.SetWindowTab(fTabView->Selection());
1095
1096	delete fTabView->RemoveTab(1);
1097		// We delete this here in order to make sure the module view saves its
1098		// state while the window is still intact.
1099
1100	fSettings.Save();
1101}
1102
1103
1104void
1105ScreenSaverWindow::MessageReceived(BMessage* message)
1106{
1107	switch (message->what) {
1108		case kMsgChangePassword:
1109			fPasswordWindow->CenterIn(Frame());
1110			fPasswordWindow->Show();
1111			break;
1112
1113		case kMsgUpdateList:
1114			fModulesView->EmptyScreenSaverList();
1115			fModulesView->PopulateScreenSaverList();
1116			break;
1117
1118		default:
1119			BWindow::MessageReceived(message);
1120	}
1121}
1122
1123
1124void
1125ScreenSaverWindow::ScreenChanged(BRect frame, color_space colorSpace)
1126{
1127	fFadeView->UpdateTurnOffScreen();
1128}
1129
1130
1131bool
1132ScreenSaverWindow::QuitRequested()
1133{
1134	be_app->PostMessage(B_QUIT_REQUESTED);
1135	return true;
1136}
1137