#include "stdafx.h"
#include "resource.h"
#include "App.h"
#include "Config.h"
#include "Engine.h"
#include "Options.h"
#include "Platform.h"
#include "SettingsDialog.h"
#include "NotificationAreaIcon.h"
#include "UserMessage.h"
#include "WindowsStartup.h"
namespace
{
constexpr auto trackTitleLength = 64;
HWND hwndSettingsDialog;
CharBuffer<trackTitleLength> czsDisplayDelayText;
[[nodiscard]] constexpr UINT GetCheck(bool checked) noexcept { return checked ? BST_CHECKED : BST_UNCHECKED; };
[[nodiscard]] bool IsChecked(HWND hwnd, int buttonId) { return IsDlgButtonChecked(hwnd, buttonId) == BST_CHECKED; };
INT_PTR CALLBACK SettingsDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam);
}
namespace Standalone
{
namespace
{
[[nodiscard]] bool Standalone() { return !Options::AsScreensaver(); }
[[nodiscard]] bool NotificationAreaIcon() { return Options::NotificationAreaIcon(); }
bool defaultRunAtStartUp;
}
void UpdateTriggerCheckText(HWND hwnd)
{
if (NotificationAreaIcon())
{
CharBuffer<256> cszText;
App::LoadString(IsChecked(hwnd, IDC_ALWAYS_LAUNCH_CHECK) ? IDS_SCREENSAVER_ON_LOCK_CHECK : IDS_TURN_OFF_ON_LOCK_CHECK, cszText);
VERIFY(SetDlgItemText(hwnd, IDC_TRIGGER_ON_LOCK_CHECK, cszText));
}
}
void UpdateControls(HWND hwnd, bool init, bool defaults)
{
const auto standalone = Standalone();
if (init || standalone)
{
CheckDlgButton(hwnd, IDC_ALWAYS_LAUNCH_CHECK, defaults ? Config::DefaultAlwaysLaunchScreensaver : GetCheck(Config::AlwaysLaunchScreensaver()));
CheckDlgButton(hwnd, IDC_ALWAYS_ENABLE_CHECK, defaults ? Config::DefaultAlwaysEnableScreensaver : GetCheck(Config::AlwaysEnableScreensaver()));
if(init || NotificationAreaIcon())
{
CheckDlgButton(hwnd, IDC_TRIGGER_ON_LOCK_CHECK, defaults ? Config::DefaultTriggerOnLockWorkStation : GetCheck(Config::TriggerOnLockWorkStation()));
#pragma warning(suppress: 6011)
CheckDlgButton(hwnd, IDC_RUN_AT_STARTUP_CHECK, defaults ? WindowsStartup::DefaultEnabled : defaultRunAtStartUp = GetCheck(WindowsStartup::Enabled()));
UpdateTriggerCheckText(hwnd);
}
}
if (!standalone)
{
VERIFY(ShowWindow(GetDlgItem(hwnd, IDC_EDIT_SYSLINK), SW_HIDE));
}
}
template<int count>
void AdjustControls(HWND hwnd, const Array<int, count> idsToHide)
{
const auto hideControls = [=]()
{
struct { HWND firstToHide; HWND lastToHide; } result = {};
for (const auto id : idsToHide.Elems)
{
auto hwndControl = GetDlgItem(hwnd, id);
ASSERT(hwndControl);
if (!result.firstToHide) result.firstToHide = hwndControl;
result.lastToHide = hwndControl;
VERIFY(ShowWindow(hwndControl, SW_HIDE));
}
return result;
};
const auto moveControls = [=](HWND firstToHide, HWND lastToHide)
{
const auto getControlRect = [=](HWND hwndFrom)
{
RECT rect;
VERIFY(GetClientRect(hwndFrom, &rect));
VERIFY(MapWindowPoints(hwndFrom, hwnd, reinterpret_cast<LPPOINT>(&rect), 2));
return rect;
};
const auto rectTop = getControlRect(firstToHide);
const auto rectBottom = getControlRect(lastToHide);
const auto deltaY = rectBottom.bottom - rectTop.top;
const int idsToMove[]{ IDC_DEFAULTS_SYSLINK, IDC_REVERT_SYSLINK, IDC_EDIT_SYSLINK, IDOK, IDCANCEL };
for (const int id : idsToMove)
{
const auto hwndControl = GetDlgItem(hwnd, id);
ASSERT(hwndControl);
RECT rect = getControlRect(hwndControl);
rect.top -= deltaY;
VERIFY(SetWindowPos(hwndControl, nullptr, rect.left, rect.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER));
}
return deltaY;
};
const auto [firstToHide, lastToHide] = hideControls();
const auto deltaY = moveControls(firstToHide, lastToHide);
RECT rectHwnd;
VERIFY(GetWindowRect(hwnd, &rectHwnd));
VERIFY(SetWindowPos(hwnd, nullptr, 0, 0, rectHwnd.right - rectHwnd.left, rectHwnd.bottom - rectHwnd.top - deltaY, SWP_NOREPOSITION | SWP_NOZORDER));
}
void OnInitDialog(HWND hwnd)
{
const auto setTitle = [=](int id)
{
CharBuffer<64> szTitle;
App::LoadString(id, szTitle);
VERIFY(SetWindowText(hwnd, szTitle));
};
if (Options::ShowHelp())
{
AdjustControls(hwnd, Array<int, 2>{ IDC_TRIGGER_ON_LOCK_CHECK, IDC_RUN_AT_STARTUP_CHECK });
}
if (Standalone())
{
if (NotificationAreaIcon())
{
setTitle(IDS_NOTIFICATION_AREA_SETTINGS);
}
VERIFY(GetDlgItemText(hwnd, IDC_DISPLAY_DELAY_STATIC, czsDisplayDelayText, czsDisplayDelayText));
return;
}
App::LoadString(IDS_DISPLAY_DELAY, czsDisplayDelayText);
setTitle(IDS_SCREENSAVER_SETTINGS);
AdjustControls(hwnd, Array<int, 4>{ IDC_ALWAYS_LAUNCH_CHECK, IDC_ALWAYS_ENABLE_CHECK, IDC_TRIGGER_ON_LOCK_CHECK, IDC_RUN_AT_STARTUP_CHECK });
}
void OnOk(HWND hwnd)
{
if (NotificationAreaIcon())
{
NotificationAreaIcon::ApplySettings(GetParent(hwnd));
const auto runAtStartUp = IsChecked(hwnd, IDC_RUN_AT_STARTUP_CHECK);
if (defaultRunAtStartUp != runAtStartUp)
{
WindowsStartup::Enable(runAtStartUp);
}
}
}
}
void SettingsDialog::SetTriggerOnWorkStationLockCheck(bool set)
{
if (IsWindow(hwndSettingsDialog))
{
CheckDlgButton(hwndSettingsDialog, IDC_TRIGGER_ON_LOCK_CHECK, GetCheck(set));
}
}
void SettingsDialog::SetRunAtStartupCheck(bool set)
{
if (IsWindow(hwndSettingsDialog))
{
CheckDlgButton(hwndSettingsDialog, IDC_RUN_AT_STARTUP_CHECK, GetCheck(set));
}
}
void SettingsDialog::MakeForeground()
{
if (IsWindow(hwndSettingsDialog))
{
ShowWindow(hwndSettingsDialog, SW_SHOWNORMAL);
VERIFY(SetForegroundWindow(hwndSettingsDialog));
}
}
void SettingsDialog::Show(HWND hwnd)
{
if (!IsWindow(hwndSettingsDialog))
{
hwndSettingsDialog = CreateDialog(App::GetInstance(), MAKEINTRESOURCE(IDD_SETTINGS), hwnd, SettingsDialogProc);
Platform::SetIcon(hwndSettingsDialog, App::GetInstance(), IDI_APP_ICON);
}
ASSERT(IsWindow(hwndSettingsDialog));
MakeForeground();
}
bool SettingsDialog::Process(MSG& msg)
{
return hwndSettingsDialog != nullptr && IsDialogMessage(hwndSettingsDialog, &msg);
}
namespace
{
constexpr auto tick = 500;
constexpr auto quant = 100;
constexpr auto trackHeaderLength = 32;
CharBuffer<trackTitleLength> czsLockDelayText;
CharBuffer<trackHeaderLength> czsDelayMs;
CharBuffer<trackHeaderLength> czsDelaySec;
CharBuffer<trackHeaderLength> czsNoDelay;
constexpr auto millisecondsInSecond = 1000;
constexpr auto millisecondsFraction = 100;
[[nodiscard]] auto FormatPosition(int nPosition)
{
CharBuffer<8, ClearBuffer::All> szText;
if (nPosition < millisecondsInSecond)
{
VERIFY_SPRINTF(wnsprintf(szText, szText, TEXT("%d"), nPosition));
return szText;
}
if (nPosition == millisecondsInSecond)
{
VERIFY_STR(StringCchCopy(szText, szText, TEXT("1")));
return szText;
}
const auto div = nPosition / millisecondsInSecond;
const auto mod = nPosition % millisecondsInSecond;
const auto fraction = mod / millisecondsFraction;
VERIFY_SPRINTF(wnsprintf(szText, szText, fraction ? TEXT("%d.%d") : TEXT("%d"), div, fraction));
return szText;
}
void UpdateTrackBarTitle(HWND hwnd, UINT uId, int nPosition)
{
CharBuffer<trackHeaderLength+trackTitleLength+16, ClearBuffer::All> szText;
const auto getDelayHeader = [nPosition]()
{
return nPosition
? nPosition < millisecondsInSecond
? czsDelayMs
: czsDelaySec
: czsNoDelay;
};
VERIFY_SPRINTF(wnsprintf(szText, szText,
getDelayHeader(),
FormatPosition(nPosition).Chars,
nPosition <= 1 || nPosition == millisecondsInSecond ? TEXT("") : TEXT("s")));
VERIFY_STR(StringCchCat(szText, szText, TEXT(" ")));
VERIFY_STR(StringCchCat(szText, szText, uId == IDC_DISPLAY_DELAY_STATIC ? czsDisplayDelayText : czsLockDelayText));
SetDlgItemText(hwnd, uId, szText);
}
void UpdateTrackBar(HWND hwnd, UINT uId, int nPosition)
{
SendDlgItemMessage(hwnd, uId, TBM_SETRANGE, FALSE, MAKELPARAM(0, Config::MaxTimeout));
SendDlgItemMessage(hwnd, uId, TBM_SETLINESIZE, 0, 1);
SendDlgItemMessage(hwnd, uId, TBM_SETPAGESIZE, 0, quant);
SendDlgItemMessage(hwnd, uId, TBM_SETTICFREQ, tick, 0);
SendDlgItemMessage(hwnd, uId, TBM_SETPOS, TRUE, nPosition);
}
enum class UpdateControlsAction
{
None,
Init,
Defaults
};
void UpdateControls(HWND hwnd, UpdateControlsAction updateControlsAction = UpdateControlsAction::None)
{
const bool defaults = updateControlsAction == UpdateControlsAction::Defaults;
const auto startupDelay = defaults ? Config::DefaultStartupDelay : Config::StartupDelay();
const auto lockWorkstationDelay = defaults ? Config::DefaultLockWorkStationDelay : Config::LockWorkStationDelay();
UpdateTrackBar(hwnd, IDC_DISPLAY_DELAY_SLIDER, startupDelay);
UpdateTrackBar(hwnd, IDC_LOCK_DELAY_SLIDER, lockWorkstationDelay);
UpdateTrackBarTitle(hwnd, IDC_DISPLAY_DELAY_STATIC, startupDelay);
UpdateTrackBarTitle(hwnd, IDC_LOCK_DELAY_STATIC, lockWorkstationDelay);
CheckDlgButton(hwnd, IDC_ALWAYS_LOCK_CHECK, defaults ? Config::DefaultAlwaysLockWorkStation : GetCheck(Config::AlwaysLockWorkStation()));
Standalone::UpdateControls(hwnd, updateControlsAction == UpdateControlsAction::Init, defaults);
}
[[nodiscard]] int GetTrackBarPosition(HWND hwndCtrl)
{
#pragma warning(suppress: 26472)
const auto nPosition = static_cast<int>(SendMessage(hwndCtrl, TBM_GETPOS, 0, 0));
return nPosition < quant ? nPosition : nPosition / quant * quant;
}
[[nodiscard]] int GetTrackBarPosition(HWND hwnd, int id)
{
return GetTrackBarPosition(GetDlgItem(hwnd, id));
}
void OnHScroll(HWND hwnd, HWND hwndCtl, int, int)
{
UpdateTrackBarTitle(
hwnd,
GetDlgCtrlID(hwndCtl) == IDC_DISPLAY_DELAY_SLIDER ? IDC_DISPLAY_DELAY_STATIC : IDC_LOCK_DELAY_STATIC,
GetTrackBarPosition(hwndCtl));
}
BOOL OnInitDialog(HWND hwnd, HWND, LPARAM)
{
Config::Read(true);
Standalone::OnInitDialog(hwnd);
App::LoadString(IDS_DELAY_MS, czsDelayMs);
App::LoadString(IDS_DELAY_SEC, czsDelaySec);
App::LoadString(IDS_NO_DELAY, czsNoDelay);
VERIFY(GetDlgItemText(hwnd, IDC_LOCK_DELAY_STATIC, czsLockDelayText, czsLockDelayText));
UpdateControls(hwnd, UpdateControlsAction::Init);
Platform::CenterWindow(hwnd);
return TRUE;
}
void OnCommand(HWND hwnd, int id, HWND, UINT)
{
const auto destroyWindow = [=]() { VERIFY(DestroyWindow(hwnd)); hwndSettingsDialog = nullptr; };
const auto writeConfig = [&, hwnd]()
{
const auto succeeded = Config::Write(
GetTrackBarPosition(hwnd, IDC_DISPLAY_DELAY_SLIDER),
GetTrackBarPosition(hwnd, IDC_LOCK_DELAY_SLIDER),
IsChecked(hwnd, IDC_ALWAYS_LOCK_CHECK),
IsChecked(hwnd, IDC_ALWAYS_LAUNCH_CHECK),
IsChecked(hwnd, IDC_ALWAYS_ENABLE_CHECK),
IsChecked(hwnd, IDC_TRIGGER_ON_LOCK_CHECK)
);
if (!succeeded)
{
UserMessage::ConfigSaveSucceeded(false, hwndSettingsDialog);
}
};
const auto onOk = [&, hwnd]()
{
if (Engine::Busy())
{
UserMessage::InProgress(hwndSettingsDialog);
return;
}
writeConfig();
Standalone::OnOk(hwnd);
destroyWindow();
};
HANDLE_COMMAND(IDOK, onOk())
HANDLE_COMMAND(IDCANCEL, destroyWindow())
HANDLE_COMMAND(IDC_ALWAYS_LAUNCH_CHECK, Standalone::UpdateTriggerCheckText(hwnd))
}
BOOL OnNotify(HWND hwnd, int id, const NMHDR* pnmhdr)
{
const auto code = pnmhdr->code;
#pragma warning(push)
#pragma warning(disable: 26454)
if (code == NM_CLICK || code == NM_RETURN)
#pragma warning(pop)
{
HANDLE_NOTIFY_COMMAND(IDC_EDIT_SYSLINK, Config::Open())
HANDLE_NOTIFY_COMMAND(IDC_REVERT_SYSLINK, Config::Read(true); UpdateControls(hwnd))
HANDLE_NOTIFY_COMMAND(IDC_DEFAULTS_SYSLINK, UpdateControls(hwnd, UpdateControlsAction::Defaults))
}
return TRUE;
}
INT_PTR CALLBACK SettingsDialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
#pragma warning (suppress: 26818)
switch (uMsg)
{
HANDLE_MSG(hwnd, WM_INITDIALOG, OnInitDialog);
HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
HANDLE_MSG(hwnd, WM_NOTIFY, OnNotify);
HANDLE_MSG(hwnd, WM_HSCROLL, OnHScroll);
}
return FALSE;
}
}