/*
 * Copyright 2003-2016 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Axel Dörfler, axeld@pinc-software.de
 *		Jérôme Duval, jerome.duval@free.fr
 *		Filip Maryjański, widelec@morphos.pl
 *		Puck Meerburg, puck@puckipedia.nl
 *		Michael Phipps
 *		John Scipione, jscipione@gmail.com
 */
 
 
#include "ScreenSaverWindow.h"
 
#include <stdio.h>
#include <strings.h>
 
#include <Alignment.h>
#include <Application.h>
#include <Box.h>
#include <Button.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <ControlLook.h>
#include <DefaultSettingsView.h>
#include <Directory.h>
#include <DurationFormat.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Font.h>
#include <GroupLayout.h>
#include <LayoutBuilder.h>
#include <ListItem.h>
#include <ListView.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <Rect.h>
#include <Roster.h>
#include <Screen.h>
#include <ScreenSaver.h>
#include <ScreenSaverRunner.h>
#include <ScrollView.h>
#include <Size.h>
#include <Slider.h>
#include <StringView.h>
#include <TabView.h>
#include <TextView.h>
 
#include <algorithm>
	// for std::max and std::min
 
#include "PreviewView.h"
#include "ScreenCornerSelector.h"
#include "ScreenSaverItem.h"
#include "ScreenSaverShared.h"
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ScreenSaver"
 
 
const uint32 kPreviewMonitorGap = 16;
const uint32 kMinSettingsWidth = 230;
const uint32 kMinSettingsHeight = 120;
 
const int32 kMsgSaverSelected = 'SSEL';
const int32 kMsgPasswordCheckBox = 'PWCB';
const int32 kMsgRunSliderChanged = 'RSch';
const int32 kMsgRunSliderUpdate = 'RSup';
const int32 kMsgPasswordSliderChanged = 'PWch';
const int32 kMsgPasswordSliderUpdate = 'PWup';
const int32 kMsgChangePassword = 'PWBT';
const int32 kMsgEnableScreenSaverBox = 'ESCH';
 
const int32 kMsgTurnOffCheckBox = 'TUOF';
const int32 kMsgTurnOffSliderChanged = 'TUch';
const int32 kMsgTurnOffSliderUpdate = 'TUup';
 
const int32 kMsgFadeCornerChanged = 'fdcc';
const int32 kMsgNeverFadeCornerChanged = 'nfcc';
 
const float kWindowWidth = 446.0f;
const float kWindowHeight = 325.0f;
const float kDefaultItemSpacingAt12pt = 12.0f * 0.85;
 
 
class TimeSlider : public BSlider {
public:
								TimeSlider(const char* name,
									uint32 changedMessage,
									uint32 updateMessage);
	virtual						~TimeSlider();
 
	virtual	void				SetValue(int32 value);
 
			void				SetTime(bigtime_t useconds);
			bigtime_t			Time() const;
 
private:
			void				_TimeToString(bigtime_t useconds,
									BString& string);
};
 
 
class TabView : public BTabView {
public:
								TabView();
 
	virtual	void				MouseDown(BPoint where);
};
 
 
class FadeView : public BView {
public:
								FadeView(const char* name,
									ScreenSaverSettings& settings);
 
	virtual	void				AttachedToWindow();
	virtual	void				MessageReceived(BMessage* message);
 
			void				UpdateTurnOffScreen();
			void				UpdateStatus();
 
private:
			void				_UpdateColors();
			ScreenSaverSettings&	fSettings;
			uint32				fTurnOffScreenFlags;
 
			BCheckBox*			fEnableCheckBox;
			TimeSlider*			fRunSlider;
 
			BTextView*			fTurnOffNotSupported;
			BCheckBox*			fTurnOffCheckBox;
			TimeSlider*			fTurnOffSlider;
 
			BTextView*			fFadeNeverText;
			BTextView*			fFadeNowText;
 
			BCheckBox*			fPasswordCheckBox;
			TimeSlider*			fPasswordSlider;
			BButton*			fPasswordButton;
 
			ScreenCornerSelector*	fFadeNow;
			ScreenCornerSelector*	fFadeNever;
};
 
 
class ModulesView : public BView {
public:
								ModulesView(const char* name,
									ScreenSaverSettings& settings);
	virtual						~ModulesView();
 
	virtual	void				DetachedFromWindow();
	virtual	void				AttachedToWindow();
	virtual	void				AllAttached();
	virtual	void				MessageReceived(BMessage* message);
 
			void				EmptyScreenSaverList();
			void				PopulateScreenSaverList();
 
			void				SaveState();
 
			BScreenSaver*		ScreenSaver();
 
private:
	friend class TabView;
 
	static	int					_CompareScreenSaverItems(const void* left,
									const void* right);
 
			void				_CloseSaver();
			void				_OpenSaver();
			void				_AddNewScreenSaverToList(const char* name,
								BPath* path);
			void				_RemoveScreenSaverFromList(const char* name);
 
private:
		ScreenSaverSettings&	fSettings;
 
			BListView*			fScreenSaversListView;
			BButton*			fTestButton;
 
			ScreenSaverRunner*	fSaverRunner;
			BString				fCurrentName;
 
			BBox*				fSettingsBox;
			BView*				fSettingsView;
 
			PreviewView*		fPreviewView;
 
			team_id				fScreenSaverTestTeam;
};
 
 
//	#pragma mark - TimeSlider
 
 
static const int32 kTimeInUnits[] = {
	30,    60,   90,
	120,   150,  180,
	240,   300,  360,
	420,   480,  540,
	600,   900,  1200,
	1500,  1800, 2400,
	3000,  3600, 5400,
	7200,  9000, 10800,
	14400, 18000
};
 
static const int32 kTimeUnitCount
	= sizeof(kTimeInUnits) / sizeof(kTimeInUnits[0]);
 
 
TimeSlider::TimeSlider(const char* name, uint32 changedMessage,
	uint32 updateMessage)
	:
	BSlider(name, B_TRANSLATE("30 seconds"), new BMessage(changedMessage),
		0, kTimeUnitCount - 1, B_HORIZONTAL, B_TRIANGLE_THUMB)
{
	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	SetModificationMessage(new BMessage(updateMessage));
	SetBarThickness(10);
}
 
 
TimeSlider::~TimeSlider()
{
}
 
 
void
TimeSlider::SetValue(int32 value)
{
	int32 oldValue = Value();
	BSlider::SetValue(value);
 
	if (oldValue != Value()) {
		BString label;
		_TimeToString(kTimeInUnits[Value()] * 1000000LL, label);
		SetLabel(label.String());
	}
}
 
 
void
TimeSlider::SetTime(bigtime_t useconds)
{
	for (int t = 0; t < kTimeUnitCount; t++) {
		if (kTimeInUnits[t] * 1000000LL == useconds) {
			SetValue(t);
			break;
		}
	}
}
 
 
bigtime_t
TimeSlider::Time() const
{
	return 1000000LL * kTimeInUnits[Value()];
}
 
 
void
TimeSlider::_TimeToString(bigtime_t useconds, BString& string)
{
	BDurationFormat formatter;
	formatter.Format(string, 0, useconds);
}
 
 
//	#pragma mark - FadeView
 
 
FadeView::FadeView(const char* name, ScreenSaverSettings& settings)
	:
	BView(name, B_WILL_DRAW),
	fSettings(settings)
{
	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
 
	font_height fontHeight;
	be_plain_font->GetHeight(&fontHeight);
	float textHeight = ceilf(fontHeight.ascent + fontHeight.descent);
 
	fEnableCheckBox = new BCheckBox("EnableCheckBox",
		B_TRANSLATE("Enable screensaver"),
		new BMessage(kMsgEnableScreenSaverBox));
 
	BBox* box = new BBox("EnableScreenSaverBox");
	box->SetLabel(fEnableCheckBox);
 
	// Start Screensaver
	BStringView* startScreenSaver = new BStringView("startScreenSaver",
		B_TRANSLATE("Start screensaver"));
	startScreenSaver->SetAlignment(B_ALIGN_RIGHT);
 
	fRunSlider = new TimeSlider("RunSlider", kMsgRunSliderChanged,
		kMsgRunSliderUpdate);
 
	// Turn Off
	rgb_color textColor = disable_color(ui_color(B_PANEL_TEXT_COLOR),
		ViewColor());
 
	fTurnOffNotSupported = new BTextView("not_supported", be_plain_font,
		&textColor, B_WILL_DRAW);
	fTurnOffNotSupported->SetExplicitMinSize(BSize(B_SIZE_UNSET,
		3 + textHeight * 3));
	fTurnOffNotSupported->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	fTurnOffNotSupported->MakeEditable(false);
	fTurnOffNotSupported->MakeSelectable(false);
	fTurnOffNotSupported->SetText(
		B_TRANSLATE("Display Power Management Signaling not available"));
 
	fTurnOffCheckBox = new BCheckBox("TurnOffScreenCheckBox",
		B_TRANSLATE("Turn off screen"), new BMessage(kMsgTurnOffCheckBox));
	fTurnOffCheckBox->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
		B_ALIGN_VERTICAL_CENTER));
 
	fTurnOffSlider = new TimeSlider("TurnOffSlider", kMsgTurnOffSliderChanged,
		kMsgTurnOffSliderUpdate);
 
	// Password
	fPasswordCheckBox = new BCheckBox("PasswordCheckbox",
		B_TRANSLATE("Password lock"), new BMessage(kMsgPasswordCheckBox));
	fPasswordCheckBox->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
		B_ALIGN_VERTICAL_CENTER));
 
	fPasswordSlider = new TimeSlider("PasswordSlider",
		kMsgPasswordSliderChanged, kMsgPasswordSliderUpdate);
 
	fPasswordButton = new BButton("PasswordButton",
		B_TRANSLATE("Password" B_UTF8_ELLIPSIS),
		new BMessage(kMsgChangePassword));
 
	// Bottom
	float monitorHeight = 10 + textHeight * 3;
	float aspectRatio = 4.0f / 3.0f;
	float monitorWidth = monitorHeight * aspectRatio;
	BRect monitorRect = BRect(0, 0, monitorWidth, monitorHeight);
 
	fFadeNow = new ScreenCornerSelector(monitorRect, "FadeNow",
		new BMessage(kMsgFadeCornerChanged), B_FOLLOW_NONE);
	fFadeNowText = new BTextView("FadeNowText", B_WILL_DRAW);
	fFadeNowText->SetExplicitMinSize(BSize(B_SIZE_UNSET,
		4 + textHeight * 4));
	fFadeNowText->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	fFadeNowText->MakeEditable(false);
	fFadeNowText->MakeSelectable(false);
	fFadeNowText->SetText(B_TRANSLATE("Fade now when mouse is here"));
 
	fFadeNever = new ScreenCornerSelector(monitorRect, "FadeNever",
		new BMessage(kMsgNeverFadeCornerChanged), B_FOLLOW_NONE);
	fFadeNeverText = new BTextView("FadeNeverText", B_WILL_DRAW);
	fFadeNeverText->SetExplicitMinSize(BSize(B_SIZE_UNSET,
		4 + textHeight * 4));
	fFadeNeverText->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	fFadeNeverText->MakeEditable(false);
	fFadeNeverText->MakeSelectable(false);
	fFadeNeverText->SetText(B_TRANSLATE("Don't fade when mouse is here"));
 
	box->AddChild(BLayoutBuilder::Group<>(B_VERTICAL, 0)
		.SetInsets(B_USE_DEFAULT_SPACING, 0, B_USE_DEFAULT_SPACING,
			B_USE_DEFAULT_SPACING)
		.AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
			.Add(startScreenSaver, 0, 0)
			.Add(fRunSlider, 1, 0)
			.Add(fTurnOffCheckBox, 0, 1)
			.Add(BLayoutBuilder::Group<>(B_VERTICAL)
				.Add(fTurnOffNotSupported)
				.Add(fTurnOffSlider)
				.View(), 1, 1)
			.Add(fPasswordCheckBox, 0, 2)
			.Add(fPasswordSlider, 1, 2)
			.End()
		.AddGroup(B_HORIZONTAL)
			.AddGlue()
			.Add(fPasswordButton)
			.End()
		.AddGlue()
		.AddGroup(B_HORIZONTAL)
			.Add(fFadeNow)
			.AddGroup(B_VERTICAL, 0)
				.Add(fFadeNowText)
				.AddGlue()
				.End()
			.Add(fFadeNever)
			.AddGroup(B_VERTICAL, 0)
				.Add(fFadeNeverText)
				.AddGlue()
				.End()
			.End()
		.AddGlue()
		.View());
 
	BLayoutBuilder::Group<>(this, B_HORIZONTAL)
		.SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
			B_USE_WINDOW_SPACING, 0)
		.Add(box)
		.End();
 
}
 
 
void
FadeView::AttachedToWindow()
{
	fEnableCheckBox->SetTarget(this);
	fRunSlider->SetTarget(this);
	fTurnOffCheckBox->SetTarget(this);
	fTurnOffSlider->SetTarget(this);
	fFadeNow->SetTarget(this);
	fFadeNever->SetTarget(this);
	fPasswordCheckBox->SetTarget(this);
	fPasswordSlider->SetTarget(this);
 
	fEnableCheckBox->SetValue(
		fSettings.TimeFlags() & ENABLE_SAVER ? B_CONTROL_ON : B_CONTROL_OFF);
	fRunSlider->SetTime(fSettings.BlankTime());
	fTurnOffSlider->SetTime(fSettings.OffTime() + fSettings.BlankTime());
	fFadeNow->SetCorner(fSettings.BlankCorner());
	fFadeNever->SetCorner(fSettings.NeverBlankCorner());
	fPasswordCheckBox->SetValue(fSettings.LockEnable());
	fPasswordSlider->SetTime(fSettings.PasswordTime());
 
	_UpdateColors();
	UpdateTurnOffScreen();
	UpdateStatus();
}
 
 
void
FadeView::MessageReceived(BMessage *message)
{
	switch (message->what) {
		case B_COLORS_UPDATED:
			_UpdateColors();
			break;
		case kMsgRunSliderChanged:
		case kMsgRunSliderUpdate:
			if (fRunSlider->Value() > fTurnOffSlider->Value())
				fTurnOffSlider->SetValue(fRunSlider->Value());
 
			if (fRunSlider->Value() > fPasswordSlider->Value())
				fPasswordSlider->SetValue(fRunSlider->Value());
			break;
 
		case kMsgTurnOffSliderChanged:
		case kMsgTurnOffSliderUpdate:
			if (fRunSlider->Value() > fTurnOffSlider->Value())
				fRunSlider->SetValue(fTurnOffSlider->Value());
			break;
 
		case kMsgPasswordSliderChanged:
		case kMsgPasswordSliderUpdate:
			if (fPasswordSlider->Value() < fRunSlider->Value())
				fRunSlider->SetValue(fPasswordSlider->Value());
			break;
 
		case kMsgTurnOffCheckBox:
			fTurnOffSlider->SetEnabled(
				fTurnOffCheckBox->Value() == B_CONTROL_ON);
			break;
	}
 
	switch (message->what) {
		case kMsgRunSliderChanged:
		case kMsgTurnOffSliderChanged:
		case kMsgPasswordSliderChanged:
		case kMsgPasswordCheckBox:
		case kMsgEnableScreenSaverBox:
		case kMsgFadeCornerChanged:
		case kMsgNeverFadeCornerChanged:
			UpdateStatus();
			fSettings.Save();
			break;
 
		default:
			BView::MessageReceived(message);
	}
}
 
 
void
FadeView::UpdateTurnOffScreen()
{
	bool enabled = (fSettings.TimeFlags() & ENABLE_DPMS_MASK) != 0;
 
	BScreen screen(Window());
	uint32 dpmsCapabilities = screen.DPMSCapabilites();
 
	fTurnOffScreenFlags = 0;
	if (dpmsCapabilities & B_DPMS_OFF)
		fTurnOffScreenFlags |= ENABLE_DPMS_OFF;
	if (dpmsCapabilities & B_DPMS_STAND_BY)
		fTurnOffScreenFlags |= ENABLE_DPMS_STAND_BY;
	if (dpmsCapabilities & B_DPMS_SUSPEND)
		fTurnOffScreenFlags |= ENABLE_DPMS_SUSPEND;
 
	fTurnOffCheckBox->SetValue(enabled && fTurnOffScreenFlags != 0
		? B_CONTROL_ON : B_CONTROL_OFF);
 
	enabled = fEnableCheckBox->Value() == B_CONTROL_ON;
	fTurnOffCheckBox->SetEnabled(enabled && fTurnOffScreenFlags != 0);
	if (fTurnOffScreenFlags != 0) {
		fTurnOffNotSupported->Hide();
		fTurnOffSlider->Show();
	} else {
		fTurnOffSlider->Hide();
		fTurnOffNotSupported->Show();
	}
}
 
 
void
FadeView::UpdateStatus()
{
	Window()->DisableUpdates();
 
	bool enabled = fEnableCheckBox->Value() == B_CONTROL_ON;
	fPasswordCheckBox->SetEnabled(enabled);
	fTurnOffCheckBox->SetEnabled(enabled && fTurnOffScreenFlags != 0);
	fRunSlider->SetEnabled(enabled);
	fTurnOffSlider->SetEnabled(enabled && fTurnOffCheckBox->Value());
	fPasswordSlider->SetEnabled(enabled && fPasswordCheckBox->Value());
	fPasswordButton->SetEnabled(enabled && fPasswordCheckBox->Value());
 
	Window()->EnableUpdates();
 
	// Update the saved preferences
	fSettings.SetWindowFrame(Frame());
	fSettings.SetTimeFlags((enabled ? ENABLE_SAVER : 0)
		| (fTurnOffCheckBox->Value() ? fTurnOffScreenFlags : 0));
	fSettings.SetBlankTime(fRunSlider->Time());
	bigtime_t offTime = fTurnOffSlider->Time() - fSettings.BlankTime();
	fSettings.SetOffTime(offTime);
	fSettings.SetSuspendTime(offTime);
	fSettings.SetStandByTime(offTime);
	fSettings.SetBlankCorner(fFadeNow->Corner());
	fSettings.SetNeverBlankCorner(fFadeNever->Corner());
	fSettings.SetLockEnable(fPasswordCheckBox->Value());
	fSettings.SetPasswordTime(fPasswordSlider->Time());
 
	// TODO - Tell the password window to update its stuff
}
 
 
void
FadeView::_UpdateColors()
{
	rgb_color color = ui_color(B_PANEL_TEXT_COLOR);
	fFadeNeverText->SetFontAndColor(be_plain_font, 0, &color);
	fFadeNowText->SetFontAndColor(be_plain_font, 0, &color);
}
 
 
//	#pragma mark - ModulesView
 
 
ModulesView::ModulesView(const char* name, ScreenSaverSettings& settings)
	:
	BView(name, B_WILL_DRAW),
	fSettings(settings),
	fScreenSaversListView(new BListView("SaversListView")),
	fTestButton(new BButton("TestButton", B_TRANSLATE("Test"),
		new BMessage(kMsgTestSaver))),
	fSaverRunner(NULL),
	fSettingsBox(new BBox("SettingsBox")),
	fSettingsView(NULL),
	fPreviewView(new PreviewView("preview")),
	fScreenSaverTestTeam(-1)
{
	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
 
	fScreenSaversListView->SetSelectionMessage(
		new BMessage(kMsgSaverSelected));
	BScrollView* saversListScrollView = new BScrollView("scroll_list",
		fScreenSaversListView, 0, false, true);
 
	fSettingsBox->SetLabel(B_TRANSLATE("Screensaver settings"));
	fSettingsBox->SetExplicitMinSize(BSize(
		floorf(be_control_look->DefaultItemSpacing()
			* ((kWindowWidth - 157.0f) / kDefaultItemSpacingAt12pt)),
		B_SIZE_UNSET));
 
	BLayoutBuilder::Group<>(this, B_HORIZONTAL)
		.SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
			B_USE_WINDOW_SPACING, 0)
		.AddGroup(B_VERTICAL)
			.Add(fPreviewView)
			.Add(saversListScrollView)
			.AddGroup(B_HORIZONTAL)
				.Add(fTestButton)
				.AddGlue()
				.End()
			.End()
		.Add(fSettingsBox)
		.End();
}
 
 
ModulesView::~ModulesView()
{
	stop_watching(this);
 
	delete fTestButton;
	delete fSettingsBox;
	delete fPreviewView;
}
 
 
void
ModulesView::DetachedFromWindow()
{
	SaveState();
	EmptyScreenSaverList();
 
	_CloseSaver();
}
 
 
void
ModulesView::AttachedToWindow()
{
	fScreenSaversListView->SetTarget(this);
	fTestButton->SetTarget(this);
}
 
 
void
ModulesView::AllAttached()
{
	PopulateScreenSaverList();
	fScreenSaversListView->Invoke();
}
 
 
void
ModulesView::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case kMsgSaverSelected:
		{
			int32 selection = fScreenSaversListView->CurrentSelection();
			if (selection < 0)
				break;
 
			ScreenSaverItem* item
				= (ScreenSaverItem*)fScreenSaversListView->ItemAt(selection);
			if (item == NULL)
				break;
 
			if (strcmp(item->Text(), B_TRANSLATE("Blackness")) == 0)
				fSettings.SetModuleName("");
			else
				fSettings.SetModuleName(item->Text());
 
			SaveState();
			_CloseSaver();
			_OpenSaver();
			fSettings.Save();
			break;
		}
 
		case kMsgTestSaver:
		{
			SaveState();
			fSettings.Save();
 
			_CloseSaver();
 
			be_roster->StartWatching(BMessenger(this, Looper()),
				B_REQUEST_QUIT);
			BMessage message(kMsgTestSaver);
			if (be_roster->Launch(SCREEN_BLANKER_SIG, &message,
					&fScreenSaverTestTeam) == B_OK) {
				break;
			}
 
			// Try really hard to launch it. It's very likely that this fails
			// when we run from the CD, and there is only an incomplete mime
			// database for example...
			BPath path;
			if (find_directory(B_SYSTEM_BIN_DIRECTORY, &path) != B_OK
				|| path.Append("screen_blanker") != B_OK) {
				path.SetTo("/bin/screen_blanker");
			}
 
			BEntry entry(path.Path());
			entry_ref ref;
			if (entry.GetRef(&ref) == B_OK) {
				be_roster->Launch(&ref, &message,
					&fScreenSaverTestTeam);
			}
			break;
		}
 
		case B_NODE_MONITOR:
		{
			switch (message->GetInt32("opcode", 0)) {
				case B_ENTRY_CREATED:
				{
					const char* name;
					node_ref nodeRef;
 
					message->FindString("name", &name);
					message->FindInt32("device", &nodeRef.device);
					message->FindInt64("directory", &nodeRef.node);
 
					BDirectory dir(&nodeRef);
 
					if (dir.InitCheck() == B_OK) {
						BPath path(&dir);
						_AddNewScreenSaverToList(name, &path);
					}
					break;
				}
 
 
				case B_ENTRY_MOVED:
				case B_ENTRY_REMOVED:
				{
					const char* name;
 
					message->FindString("name", &name);
					_RemoveScreenSaverFromList(name);
 
					break;
				}
 
				default:
					// ignore any other operations
					break;
			}
			break;
		}
 
		case B_SOME_APP_QUIT:
		{
			team_id team;
			if (message->FindInt32("be:team", &team) == B_OK
				&& team == fScreenSaverTestTeam) {
				be_roster->StopWatching(this);
				_OpenSaver();
			}
			break;
		}
 
		default:
			BView::MessageReceived(message);
	}
}
 
 
void
ModulesView::SaveState()
{
	BScreenSaver* saver = ScreenSaver();
	if (saver == NULL)
		return;
 
	BMessage state;
	if (saver->SaveState(&state) == B_OK)
		fSettings.SetModuleState(fCurrentName.String(), &state);
}
 
 
void
ModulesView::EmptyScreenSaverList()
{
	fScreenSaversListView->DeselectAll();
	while (BListItem* item = fScreenSaversListView->RemoveItem((int32)0))
		delete item;
}
 
 
void
ModulesView::PopulateScreenSaverList()
{
	// Blackness is a built-in screen saver
	ScreenSaverItem* defaultItem
		= new ScreenSaverItem(B_TRANSLATE("Blackness"), "");
	fScreenSaversListView->AddItem(defaultItem);
 
	// Iterate over add-on directories, and add their files to the list view
 
	directory_which which[] = {
		B_USER_NONPACKAGED_ADDONS_DIRECTORY,
		B_USER_ADDONS_DIRECTORY,
		B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY,
		B_SYSTEM_ADDONS_DIRECTORY,
	};
	ScreenSaverItem* selectedItem = NULL;
 
	for (uint32 i = 0; i < sizeof(which) / sizeof(which[0]); i++) {
		BPath basePath;
		if (find_directory(which[i], &basePath) != B_OK)
			continue;
		else if (basePath.Append("Screen Savers", true) != B_OK)
			continue;
 
		BDirectory dir(basePath.Path());
		BEntry entry;
		node_ref nodeRef;
 
		dir.GetNodeRef(&nodeRef);
		watch_node(&nodeRef, B_WATCH_DIRECTORY, this);
 
		while (dir.GetNextEntry(&entry, true) == B_OK) {
			char name[B_FILE_NAME_LENGTH];
			if (entry.GetName(name) != B_OK)
				continue;
 
			BPath path(basePath);
			if (path.Append(name) != B_OK)
				continue;
 
			ScreenSaverItem* item = new ScreenSaverItem(name, path.Path());
			fScreenSaversListView->AddItem(item);
 
			if (selectedItem != NULL)
				continue;
 
			if (strcmp(fSettings.ModuleName(), item->Text()) == 0)
				selectedItem = item;
		}
	}
 
	fScreenSaversListView->SortItems(_CompareScreenSaverItems);
	if (selectedItem == NULL)
		selectedItem = defaultItem;
 
	fScreenSaversListView->Select(fScreenSaversListView->IndexOf(selectedItem));
	fScreenSaversListView->ScrollToSelection();
}
 
 
//! Sorting function for ScreenSaverItems
int
ModulesView::_CompareScreenSaverItems(const void* left, const void* right)
{
	ScreenSaverItem* leftItem  = *(ScreenSaverItem **)left;
	ScreenSaverItem* rightItem = *(ScreenSaverItem **)right;
 
	return strcasecmp(leftItem->Text(), rightItem->Text());
}
 
 
BScreenSaver*
ModulesView::ScreenSaver()
{
	if (fSaverRunner != NULL)
		return fSaverRunner->ScreenSaver();
 
	return NULL;
}
 
 
void
ModulesView::_CloseSaver()
{
	// remove old screen saver preview & config
 
	BScreenSaver* saver = ScreenSaver();
	BView* view = fPreviewView->RemovePreview();
	if (fSettingsView != NULL)
		fSettingsBox->RemoveChild(fSettingsView);
 
	if (fSaverRunner != NULL)
		fSaverRunner->Quit();
 
	if (saver != NULL)
		saver->StopConfig();
 
	delete view;
	delete fSettingsView;
	delete fSaverRunner;
		// the saver runner also unloads the add-on, so it must
		// be deleted last
 
	fSettingsView = NULL;
	fSaverRunner = NULL;
}
 
 
void
ModulesView::_OpenSaver()
{
	// create new screen saver preview & config
 
	BView* view = fPreviewView->AddPreview();
	fCurrentName = fSettings.ModuleName();
	fSaverRunner = new ScreenSaverRunner(view->Window(), view, fSettings);
 
#ifdef __HAIKU__
	BRect rect = fSettingsBox->InnerFrame().InsetByCopy(4, 4);
#else
	BRect rect = fSettingsBox->Bounds().InsetByCopy(4, 4);
	rect.top += 14;
#endif
	fSettingsView = new BView(rect, "SettingsView", B_FOLLOW_ALL, B_WILL_DRAW);
 
	fSettingsView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	fSettingsBox->AddChild(fSettingsView);
 
	BScreenSaver* saver = ScreenSaver();
	if (saver != NULL && fSettingsView != NULL) {
		saver->StartConfig(fSettingsView);
		if (saver->StartSaver(view, true) == B_OK) {
			fPreviewView->HideNoPreview();
			fSaverRunner->Run();
		} else
			fPreviewView->ShowNoPreview();
	} else {
		// Failed to load OR this is the "Blackness" screensaver. Show a black
		// preview (this is what will happen in both cases when screen_blanker
		// runs).
		fPreviewView->HideNoPreview();
	}
 
	if (fSettingsView->ChildAt(0) == NULL) {
		// There are no settings at all, we add the module name here to
		// let it look a bit better at least.
		BPrivate::BuildDefaultSettingsView(fSettingsView,
			fSettings.ModuleName()[0] ? fSettings.ModuleName()
				: B_TRANSLATE("Blackness"),
				saver != NULL || !fSettings.ModuleName()[0]
					? B_TRANSLATE("No options available")
					: B_TRANSLATE("Could not load screen saver"));
	}
}
 
 
void
ModulesView::_AddNewScreenSaverToList(const char* name, BPath* path)
{
	int32 oldSelected = fScreenSaversListView->CurrentSelection();
	ScreenSaverItem* selectedItem = (ScreenSaverItem*)fScreenSaversListView->ItemAt(
		oldSelected);
 
	path->Append(name);
	fScreenSaversListView->AddItem(new ScreenSaverItem(name, path->Path()));
	fScreenSaversListView->SortItems(_CompareScreenSaverItems);
 
	if (selectedItem != NULL) {
		fScreenSaversListView->Select(fScreenSaversListView->IndexOf(
			selectedItem));
		fScreenSaversListView->ScrollToSelection();
	}
}
 
 
void
ModulesView::_RemoveScreenSaverFromList(const char* name)
{
	int32 oldSelected = fScreenSaversListView->CurrentSelection();
	ScreenSaverItem* selectedItem = (ScreenSaverItem*)fScreenSaversListView->ItemAt(
		oldSelected);
 
	if (strcasecmp(selectedItem->Text(), name) == 0) {
		fScreenSaversListView->RemoveItem(selectedItem);
		fScreenSaversListView->SortItems(_CompareScreenSaverItems);
		fScreenSaversListView->Select(0);
		fScreenSaversListView->ScrollToSelection();
		return;
	}
 
	for (int i = 0, max = fScreenSaversListView->CountItems(); i < max; i++) {
		ScreenSaverItem* item = (ScreenSaverItem*)fScreenSaversListView->ItemAt(
			i);
 
		if (strcasecmp(item->Text(), name) == 0) {
			fScreenSaversListView->RemoveItem(item);
			delete item;
			break;
		}
	}
 
	fScreenSaversListView->SortItems(_CompareScreenSaverItems);
 
	oldSelected = fScreenSaversListView->IndexOf(selectedItem);
	fScreenSaversListView->Select(oldSelected);
	fScreenSaversListView->ScrollToSelection();
}
 
 
//	#pragma mark - TabView
 
 
TabView::TabView()
	:
	BTabView("tab_view", B_WIDTH_FROM_LABEL)
{
}
 
 
void
TabView::MouseDown(BPoint where)
{
	BTab* fadeTab = TabAt(0);
	BRect fadeTabFrame(TabFrame(0));
	BTab* modulesTab = TabAt(1);
	BRect modulesTabFrame(TabFrame(1));
	ModulesView* modulesView = dynamic_cast<ModulesView*>(modulesTab->View());
 
	if (fadeTab != NULL && Selection() != 0 && fadeTabFrame.Contains(where)
		&& modulesView != NULL) {
		// clicked on the fade tab
		modulesView->SaveState();
		modulesView->_CloseSaver();
	} else if (modulesTab != NULL && Selection() != 1
		&& modulesTabFrame.Contains(where) && modulesView != NULL) {
		// clicked on the modules tab
		BMessage message(kMsgSaverSelected);
		modulesView->MessageReceived(&message);
	}
 
	BTabView::MouseDown(where);
}
 
 
//	#pragma mark - ScreenSaverWindow
 
 
ScreenSaverWindow::ScreenSaverWindow()
	:
	BWindow(BRect(50.0f, 50.0f, 50.0f + kWindowWidth, 50.0f + kWindowHeight),
		B_TRANSLATE_SYSTEM_NAME("ScreenSaver"), B_TITLED_WINDOW,
		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS)
{
	fSettings.Load();
 
	fMinWidth = floorf(be_control_look->DefaultItemSpacing()
		* (kWindowWidth / kDefaultItemSpacingAt12pt));
 
	font_height fontHeight;
	be_plain_font->GetHeight(&fontHeight);
	float textHeight = ceilf(fontHeight.ascent + fontHeight.descent);
 
	fMinHeight = ceilf(std::max(kWindowHeight, textHeight * 28));
 
	// Create the password editing window
	fPasswordWindow = new PasswordWindow(fSettings);
	fPasswordWindow->Run();
 
	// Create the tab view
	fTabView = new TabView();
	fTabView->SetBorder(B_NO_BORDER);
 
	// Create the controls inside the tabs
	fFadeView = new FadeView(B_TRANSLATE("General"), fSettings);
	fModulesView = new ModulesView(B_TRANSLATE("Screensavers"), fSettings);
 
	fTabView->AddTab(fFadeView);
	fTabView->AddTab(fModulesView);
 
	// Create the topmost background view
	BView* topView = new BView("topView", B_WILL_DRAW);
	topView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	topView->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
		B_ALIGN_USE_FULL_HEIGHT));
	topView->SetExplicitMinSize(BSize(fMinWidth, fMinHeight));
	BLayoutBuilder::Group<>(topView, B_VERTICAL)
		.SetInsets(0, B_USE_DEFAULT_SPACING, 0, B_USE_WINDOW_SPACING)
		.Add(fTabView)
		.End();
 
	SetLayout(new BGroupLayout(B_VERTICAL));
	GetLayout()->AddView(topView);
 
	fTabView->Select(fSettings.WindowTab());
 
	if (fSettings.WindowFrame().left > 0 && fSettings.WindowFrame().top > 0)
		MoveTo(fSettings.WindowFrame().left, fSettings.WindowFrame().top);
 
	if (fSettings.WindowFrame().Width() > 0
		&& fSettings.WindowFrame().Height() > 0) {
		ResizeTo(fSettings.WindowFrame().Width(),
			fSettings.WindowFrame().Height());
	}
 
	CenterOnScreen();
}
 
 
ScreenSaverWindow::~ScreenSaverWindow()
{
	Hide();
	fFadeView->UpdateStatus();
	fSettings.SetWindowTab(fTabView->Selection());
 
	delete fTabView->RemoveTab(1);
		// We delete this here in order to make sure the module view saves its
		// state while the window is still intact.
 
	fSettings.Save();
}
 
 
void
ScreenSaverWindow::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case kMsgChangePassword:
			fPasswordWindow->CenterIn(Frame());
			fPasswordWindow->Show();
			break;
 
		case kMsgUpdateList:
			fModulesView->EmptyScreenSaverList();
			fModulesView->PopulateScreenSaverList();
			break;
 
		default:
			BWindow::MessageReceived(message);
	}
}
 
 
void
ScreenSaverWindow::ScreenChanged(BRect frame, color_space colorSpace)
{
	fFadeView->UpdateTurnOffScreen();
}
 
 
bool
ScreenSaverWindow::QuitRequested()
{
	be_app->PostMessage(B_QUIT_REQUESTED);
	return true;
}

V595 The 'modulesTab' pointer was utilized before it was verified against nullptr. Check lines: 1007, 1014.