aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Ankarström <john@ankarstrom.se>2022-09-02 02:11:49 +0200
committerJohn Ankarström <john@ankarstrom.se>2022-09-02 02:14:11 +0200
commit90c7bc237c9cf964c16f0cb48c308a92a8193a5c (patch)
tree53f165056dffa061a9dfe39b76913edab87056f4
parentbb9280267bfb78a8d69adea02f5ed7894833b19d (diff)
downloadEpisodeBrowser-90c7bc237c9cf964c16f0cb48c308a92a8193a5c.tar.gz
Use global Window object.
This makes it easier to control initialization and maintain RAII.
-rw-r--r--c/CMakeLists.txt1
-rw-r--r--c/data.cpp30
-rw-r--r--c/datalistview.cpp17
-rw-r--r--c/datalistview.h2
-rw-r--r--c/episodelistview.cpp53
-rw-r--r--c/episodelistview.h2
-rw-r--r--c/ext.cpp13
-rw-r--r--c/layout.cpp90
-rw-r--r--c/layout.h61
-rw-r--r--c/listview.cpp11
-rw-r--r--c/listview.h6
-rw-r--r--c/main.cpp167
-rw-r--r--c/test.cpp10
-rw-r--r--c/win.cpp7
14 files changed, 224 insertions, 246 deletions
diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt
index d8c3ce5..35af6ec 100644
--- a/c/CMakeLists.txt
+++ b/c/CMakeLists.txt
@@ -29,6 +29,7 @@ target_sources(EpisodeBrowser PRIVATE
listview.cpp
listview.h
main.cpp
+ main.h
resource.h
resource.rc
test.cpp
diff --git a/c/data.cpp b/c/data.cpp
index d9df68c..1274321 100644
--- a/c/data.cpp
+++ b/c/data.cpp
@@ -8,6 +8,7 @@
#include "resource.h"
#include "data.h"
#include "episodelistview.h"
+#include "main.h"
#include "util.h"
#include "win.h"
@@ -89,7 +90,7 @@ enum Signal : unsigned char
void WaitFor(void (*f)(unsigned char*))
{
- extern HWND g_hWnd;
+ extern Window* g_window;
static unsigned char sig = READY;
static UINT_PTR iTimer = 0;
@@ -98,12 +99,11 @@ void WaitFor(void (*f)(unsigned char*))
static int i = 0;
if (sig & DONE) {
- extern EpisodeListView* g_elv;
KillTimer(nullptr, iTimer);
i = 0;
sig = READY; /* Reset signals. */
- g_elv->Update(); /* Reset status bar. */
- EnableMenuItem(GetMenu(g_hWnd), IDM_FILE_FETCH_CANCEL, MF_GRAYED);
+ g_window->elv.Update(); /* Reset status bar. */
+ EnableMenuItem(GetMenu(g_window->hWnd), IDM_FILE_FETCH_CANCEL, MF_GRAYED);
} else {
/* Animate ellipsis in status bar. */
static const wchar_t* text[] = {L".", L"..", L"...", L""};
@@ -118,7 +118,7 @@ void WaitFor(void (*f)(unsigned char*))
while (!(sig & READY))
Sleep(100);
sig = 0;
- EnableMenuItem(GetMenu(g_hWnd), IDM_FILE_FETCH_CANCEL, MF_ENABLED);
+ EnableMenuItem(GetMenu(g_window->hWnd), IDM_FILE_FETCH_CANCEL, MF_ENABLED);
try {
f(&sig);
sig |= DONE;
@@ -132,7 +132,7 @@ void WaitFor(void (*f)(unsigned char*))
/* Null indicates that any active task should be cancelled. */
if (!f) {
sig |= ABORT;
- EnableMenuItem(GetMenu(g_hWnd), IDM_FILE_FETCH_CANCEL, MF_GRAYED);
+ EnableMenuItem(GetMenu(g_window->hWnd), IDM_FILE_FETCH_CANCEL, MF_GRAYED);
return;
}
@@ -180,8 +180,7 @@ void FetchData(unsigned char* sig)
throw std::runtime_error("could not find remote episode information");
for (int i = 0; i < nodes->nodeNr; i++) {
- extern FileView<ElvDataA> g_fvElv;
- extern FileView<DlvDataA> g_fvDlv;
+ extern Window* g_window;
if (*sig & ABORT)
return;
@@ -190,8 +189,8 @@ void FetchData(unsigned char* sig)
if (xmlChildElementCount(node) != 8)
throw std::runtime_error("unexpected remote data format");
- ElvDataA& e = g_fvElv.At(i);
- DlvDataA& d = g_fvDlv.At(i);
+ ElvDataA& e = g_window->fvElv.At(i);
+ DlvDataA& d = g_window->fvDlv.At(i);
/* Each datum is contained within a specific cell in
* the row. The child element count above ensures that
@@ -223,8 +222,7 @@ void FetchData(unsigned char* sig)
void FetchScreenwriters(unsigned char* sig)
{
- extern FileView<DlvDataA> g_fvDlv;
- extern CfgA& g_cfg;
+ extern Window* g_window;
/* Screenwriters are expensive to fetch, so we try to avoid
* fetching screenwriters for episodes that already have a
@@ -233,12 +231,12 @@ void FetchScreenwriters(unsigned char* sig)
* already tried to fetch screenwriters. We keep track of
* these states using the iLast variable. */
static int iLast = -1;
- int iMax = g_cfg.cEp-1;
+ int iMax = g_window->cfg.cEp-1;
/* Find the last episode that has a screenwriter. */
if (iLast == -1)
- for (size_t i = 0; i < g_fvDlv.c; i++)
- if (const DlvDataA& d = g_fvDlv[i]; !d.date[0]) {
+ for (size_t i = 0; i < g_window->fvDlv.c; i++)
+ if (const DlvDataA& d = g_window->fvDlv[i]; !d.date[0]) {
iMax = i-1;
break;
} else if (d.screenwriter[0])
@@ -262,7 +260,7 @@ void FetchScreenwriters(unsigned char* sig)
Status(msg);
/* Retrieve URL for episode's wiki page. */
- DlvDataA& d = g_fvDlv[iLast];
+ DlvDataA& d = g_window->fvDlv[iLast];
Wcscpy(Buf(url)+Len(prefix), d.wiki);
/* Retrieve screenwriter from HTML. */
diff --git a/c/datalistview.cpp b/c/datalistview.cpp
index b63a342..cdd8189 100644
--- a/c/datalistview.cpp
+++ b/c/datalistview.cpp
@@ -9,12 +9,10 @@
#include "episodelistview.h"
#include "listview.h"
#include "layout.h"
+#include "main.h"
-extern CfgA& g_cfg;
-extern FileView<DlvDataA> g_fvDlv;
-
-DataListView::DataListView(const HWND hWndParent)
- : ListView(hWndParent, reinterpret_cast<HMENU>(IDC_DATALISTVIEW), LVS_NOCOLUMNHEADER)
+DataListView::DataListView(Window& parent)
+ : ListView(parent, reinterpret_cast<HMENU>(IDC_DATALISTVIEW), LVS_NOCOLUMNHEADER)
{
LVCOLUMN lvc;
@@ -30,7 +28,7 @@ DataListView::DataListView(const HWND hWndParent)
lvc.cx = 500;
ListView_InsertColumn(hWnd, DLVSIVALUE, &lvc);
- m_height = g_cfg.heightDlv;
+ m_height = parent.cfg.heightDlv;
}
int DataListView::Height()
@@ -53,7 +51,7 @@ void DataListView::SetHeight(int h)
void DataListView::ShowEpisode(const int iEpisode)
{
- const DlvDataA& d = g_fvDlv[iEpisode-1];
+ const DlvDataA& d = parent.fvDlv[iEpisode-1];
ListView_DeleteAllItems(hWnd);
if (d.version == Version<DlvDataA>) {
@@ -88,8 +86,7 @@ void DataListView::ShowEpisode(const int iEpisode)
LVFINDINFO lvfi;
lvfi.flags = LVFI_PARAM;
lvfi.lParam = iEpisode;
- extern EpisodeListView* g_elv;
- int iItem = ListView_FindItem(g_elv->hWnd, -1, &lvfi);
+ int iItem = ListView_FindItem(parent.elv.hWnd, -1, &lvfi);
if (iItem != -1)
- ListView_EnsureVisible(g_elv->hWnd, iItem, TRUE);
+ ListView_EnsureVisible(parent.elv.hWnd, iItem, TRUE);
}
diff --git a/c/datalistview.h b/c/datalistview.h
index 57cb565..21d6f05 100644
--- a/c/datalistview.h
+++ b/c/datalistview.h
@@ -9,7 +9,7 @@
struct DataListView : public ListView
{
- DataListView(HWND hWndParent);
+ DataListView(Window& parent);
/* Return manual height, if set, or calculate height
* appropriate for number of items. */
int Height() override;
diff --git a/c/episodelistview.cpp b/c/episodelistview.cpp
index 8581890..ea4849f 100644
--- a/c/episodelistview.cpp
+++ b/c/episodelistview.cpp
@@ -9,14 +9,12 @@
#include "episodelistview.h"
#include "ext.h"
#include "listview.h"
+#include "main.h"
#include "util.h"
#include "win.h"
-extern CfgA& g_cfg;
-extern FileView<ElvDataA> g_fvElv;
-
-EpisodeListView::EpisodeListView(const HWND hWndParent)
- : ListView(hWndParent, reinterpret_cast<HMENU>(IDC_EPISODELISTVIEW), 0)
+EpisodeListView::EpisodeListView(Window& parent)
+ : ListView(parent, reinterpret_cast<HMENU>(IDC_EPISODELISTVIEW), 0)
{
LVCOLUMN lvc;
@@ -37,7 +35,7 @@ EpisodeListView::EpisodeListView(const HWND hWndParent)
ListView_InsertColumn(hWnd, ELVSIRATING, &lvc);
/* Get saved sort-by-column setting. */
- m_iSortCol = g_cfg.iSortCol;
+ m_iSortCol = parent.cfg.iSortCol;
}
void EpisodeListView::EnsureFocusVisible()
@@ -56,7 +54,7 @@ void EpisodeListView::HandleContextMenu(const WORD command)
LVITEM lvi = {LVIF_PARAM, -1};
while (FindNextItem(&lvi, LVNI_SELECTED)) {
- ElvDataA& e = g_fvElv.At(lvi.lParam-1);
+ ElvDataA& e = parent.fvElv.At(lvi.lParam-1);
if (ID_SUBGROUP(command) == IDG_CTX_RATE) {
/* Process rate commands. */
@@ -113,7 +111,7 @@ LRESULT EpisodeListView::HandleNotify(const LPARAM lParam)
case LVN_GETDISPINFO: /* Display item. */
{
NMLVDISPINFO* const nm = reinterpret_cast<NMLVDISPINFO*>(lParam);
- ElvDataA& e = g_fvElv.At(nm->item.lParam-1);
+ ElvDataA& e = parent.fvElv.At(nm->item.lParam-1);
wchar_t* vs[] = {e.siEp, e.title, e.sRating}; /* ELVSIEPISODE, ELVSITITLE, ELVSIRATING */
nm->item.pszText = vs[nm->item.iSubItem];
return 0;
@@ -121,8 +119,7 @@ LRESULT EpisodeListView::HandleNotify(const LPARAM lParam)
case LVN_ITEMCHANGED: /* Select/focus episode. */
if ((nm->uChanged & LVIF_STATE) && (nm->uNewState & LVIS_FOCUSED)) {
- extern DataListView* g_dlv;
- g_dlv->ShowEpisode(nm->lParam);
+ parent.dlv.ShowEpisode(nm->lParam);
}
return 0;
@@ -133,7 +130,7 @@ LRESULT EpisodeListView::HandleNotify(const LPARAM lParam)
/* The sign of m_iSortCol decides the sort order. */
m_iSortCol = abs(m_iSortCol) == iCol? -m_iSortCol: iCol;
- g_cfg.iSortCol = m_iSortCol;
+ parent.cfg.iSortCol = m_iSortCol;
Sort();
ShowFocus();
return 0;
@@ -162,7 +159,7 @@ LRESULT EpisodeListView::HandleNotify(const LPARAM lParam)
break;
case CDDS_ITEMPREPAINT:
{
- const ElvDataA& e = g_fvElv.At(nm->nmcd.lItemlParam-1);
+ const ElvDataA& e = parent.fvElv.At(nm->nmcd.lItemlParam-1);
if (!e.bWatched) {
extern HFONT g_hfBold;
Require(SelectObject(nm->nmcd.hdc, g_hfBold));
@@ -188,7 +185,7 @@ LRESULT EpisodeListView::HandleNotify(const LPARAM lParam)
extern HMENU g_hMenuPopup;
const DWORD pos = GetMessagePos();
Require(TrackPopupMenu(g_hMenuPopup, TPM_RIGHTBUTTON,
- LOWORD(pos), HIWORD(pos), 0, m_hWndParent, nullptr));
+ LOWORD(pos), HIWORD(pos), 0, parent.hWnd, nullptr));
return 0;
}
@@ -217,10 +214,9 @@ void EpisodeListView::RestoreFocus()
{
int i, iEpisode, iItem;
LVFINDINFO lvfi;
- extern DataListView* g_dlv;
iItem = 0;
- iEpisode = g_cfg.iFocus;
+ iEpisode = parent.cfg.iFocus;
lvfi.flags = LVFI_PARAM;
lvfi.lParam = iEpisode;
@@ -237,7 +233,7 @@ void EpisodeListView::RestoreFocus()
if (iItem != -1) goto s;
return;
-s: g_dlv->ShowEpisode(iEpisode);
+s: parent.dlv.ShowEpisode(iEpisode);
ListView_SetItemState(hWnd, -1, LVIF_STATE, LVIS_SELECTED);
SetTop(iItem > 5? iItem-5: 0);
ListView_SetItemState(hWnd, iItem,
@@ -248,7 +244,7 @@ void EpisodeListView::SaveFocus()
{
LVITEM lvi = {LVIF_PARAM, -1};
if (FindNextItem(&lvi, LVNI_FOCUSED))
- g_cfg.iFocus = lvi.lParam;
+ parent.cfg.iFocus = lvi.lParam;
}
void EpisodeListView::SetTop(const int iItem)
@@ -280,10 +276,10 @@ void EpisodeListView::SelectUnwatched(int dir)
for (;;) {
iEpNew += dir;
if (iEpNew == 0
- || static_cast<size_t>(iEpNew) > g_fvElv.c
- || !g_fvElv[iEpNew-1].siEp[0])
+ || static_cast<size_t>(iEpNew) > parent.fvElv.c
+ || !parent.fvElv[iEpNew-1].siEp[0])
return;
- if (!g_fvElv[iEpNew-1].bWatched)
+ if (!parent.fvElv[iEpNew-1].bWatched)
break;
}
@@ -329,8 +325,8 @@ int CALLBACK EpisodeListView::SortProc(const LPARAM iItem1, const LPARAM iItem2,
* If m_iSortCol is negative, the order is descending. */
const int order = Cmp(elv->m_iSortCol, 0);
- const ElvDataA& e1 = g_fvElv[lvi1.lParam-1];
- const ElvDataA& e2 = g_fvElv.At(lvi2.lParam-1);
+ const ElvDataA& e1 = elv->parent.fvElv[lvi1.lParam-1];
+ const ElvDataA& e2 = elv->parent.fvElv.At(lvi2.lParam-1);
switch (abs(elv->m_iSortCol)-1) {
case ELVSIEPISODE:
@@ -389,21 +385,20 @@ void EpisodeListView::Update()
/* Retrieve episode data and add list view items. */
SendMessageW(hWnd, WM_SETREDRAW, FALSE, 0);
ListView_DeleteAllItems(hWnd);
- for (int iEp = 1; iEp <= g_cfg.cEp; iEp++) {
- ElvDataA& e = g_fvElv.At(iEp-1);
+ for (int iEp = 1; iEp <= parent.cfg.cEp; iEp++) {
+ ElvDataA& e = parent.fvElv.At(iEp-1);
if (!e.siEp[0])
continue;
- if (extern FileView<DlvDataA> g_fvDlv;
- g_cfg.limitToScreenwriter[0]
- && wcscmp(g_fvDlv.At(iEp-1).screenwriter, g_cfg.limitToScreenwriter) != 0)
+ if (parent.cfg.limitToScreenwriter[0]
+ && wcscmp(parent.fvDlv.At(iEp-1).screenwriter, parent.cfg.limitToScreenwriter) != 0)
continue;
- if (!g_cfg.bViewWatched && e.bWatched)
+ if (!parent.cfg.bViewWatched && e.bWatched)
continue;
- if (!g_cfg.bViewTVOriginal && e.bTVOriginal)
+ if (!parent.cfg.bViewTVOriginal && e.bTVOriginal)
continue;
/* Insert item. */
diff --git a/c/episodelistview.h b/c/episodelistview.h
index e6f2858..52175a0 100644
--- a/c/episodelistview.h
+++ b/c/episodelistview.h
@@ -14,7 +14,7 @@
struct EpisodeListView : public ListView
{
- EpisodeListView(HWND hWndParent);
+ EpisodeListView(Window& parent);
void EnsureFocusVisible();
void HandleContextMenu(WORD);
LRESULT HandleNotify(LPARAM lParam);
diff --git a/c/ext.cpp b/c/ext.cpp
index 694581d..77028d1 100644
--- a/c/ext.cpp
+++ b/c/ext.cpp
@@ -3,15 +3,14 @@
#include <string_view>
#include "data.h"
+#include "main.h"
-extern CfgA& g_cfg;
-extern FileView<ElvDataA> g_fvElv;
-extern FileView<DlvDataA> g_fvDlv;
+extern Window* g_window;
bool OpenOnline(int iEp)
{
- wchar_t url[sizeof(g_cfg.url)+4];
- Swprintf(url, L"%s%d", g_cfg.url, iEp);
+ wchar_t url[sizeof(g_window->cfg.url)+4];
+ Swprintf(url, L"%s%d", g_window->cfg.url, iEp);
INT_PTR r = reinterpret_cast<INT_PTR>(
ShellExecuteW(nullptr, L"open", url, nullptr, nullptr, SW_SHOWNORMAL));
if (r <= 32)
@@ -21,7 +20,7 @@ bool OpenOnline(int iEp)
bool OpenWiki(int iEp)
{
- const DlvDataA& d = g_fvDlv.At(iEp-1);
+ const DlvDataA& d = g_window->fvDlv.At(iEp-1);
wchar_t url[sizeof(d.wiki)+35];
Swprintf(url, L"https://www.detectiveconanworld.com%s", d.wiki);
INT_PTR r = reinterpret_cast<INT_PTR>(
@@ -114,7 +113,7 @@ static bool FindMatchingFile(wchar_t (&file)[MAX_PATH], const wchar_t* const roo
bool OpenLocally(int iEp)
{
wchar_t file[MAX_PATH];
- if (FindMatchingFile(file, g_cfg.root, g_fvElv.At(iEp-1).siEp)) {
+ if (FindMatchingFile(file, g_window->cfg.root, g_window->fvElv.At(iEp-1).siEp)) {
INT_PTR r = reinterpret_cast<INT_PTR>(
ShellExecuteW(nullptr, L"open", file, nullptr, nullptr, SW_SHOWNORMAL));
if (r <= 32)
diff --git a/c/layout.cpp b/c/layout.cpp
index 23ee490..e817b23 100644
--- a/c/layout.cpp
+++ b/c/layout.cpp
@@ -3,12 +3,11 @@
#include "episodelistview.h"
#include "datalistview.h"
#include "layout.h"
+#include "main.h"
#include "win.h"
-extern HWND g_hWnd;
+extern Window* g_window;
extern HWND g_hWndStatus;
-extern EpisodeListView* g_elv;
-extern DataListView* g_dlv;
void UpdateLayout(int w, int h)
{
@@ -16,33 +15,84 @@ void UpdateLayout(int w, int h)
RECT rc, rrStatus;
if (w && h) rc = {0, 0, w, h};
- else Require(GetClientRect(g_hWnd, &rc));
+ else Require(GetClientRect(g_window->hWnd, &rc));
Require(GetRelativeRect(g_hWndStatus, &rrStatus));
- SendMessageW(g_hWnd, WM_SETREDRAW, FALSE, 0);
+ SendMessageW(g_window->hWnd, WM_SETREDRAW, FALSE, 0);
/* Resize list views. */
const long pad = EBIsThemeActive()? Dpi(6): 0; /* Add padding in modern themes. */
- const long cyDlv = rrStatus.top-g_dlv->Height()-pad;
- Require(SetWindowRect(g_dlv->hWnd, pad, cyDlv, rc.right-pad, rrStatus.top-pad));
- Require(SetWindowRect(g_elv->hWnd, pad, pad, rc.right-pad, cyDlv-pad));
- g_dlv->ResizeColumns(rc.right-pad-pad);
- g_elv->ResizeColumns(rc.right-pad-pad);
+ const long cyDlv = rrStatus.top-g_window->dlv.Height()-pad;
+ Require(SetWindowRect(g_window->dlv.hWnd, pad, cyDlv, rc.right-pad, rrStatus.top-pad));
+ Require(SetWindowRect(g_window->elv.hWnd, pad, pad, rc.right-pad, cyDlv-pad));
+ g_window->dlv.ResizeColumns(rc.right-pad-pad);
+ g_window->elv.ResizeColumns(rc.right-pad-pad);
/* Resize status bar parts. */
const int aParts[] = {rc.right-Dpi(55), rc.right};
SendMessageW(g_hWndStatus, SB_SETPARTS,
sizeof(aParts), reinterpret_cast<LPARAM>(aParts));
- SendMessageW(g_hWnd, WM_SETREDRAW, TRUE, 0);
- RedrawWindow(g_hWnd, nullptr, nullptr,
+ SendMessageW(g_window->hWnd, WM_SETREDRAW, TRUE, 0);
+ RedrawWindow(g_window->hWnd, nullptr, nullptr,
RDW_ERASE|RDW_FRAME|RDW_INVALIDATE|RDW_ALLCHILDREN);
}
+bool Dragger::IsDouble(const long time, const POINT& pt)
+{
+ const bool dbl = time-m_time0 <= static_cast<long>(GetDoubleClickTime())
+ && abs(pt.x-m_pt0.x) <= Metric<SM_CXDOUBLECLK>
+ && abs(pt.y-m_pt0.y) <= Metric<SM_CYDOUBLECLK>;
+ m_time0 = time;
+ m_pt0 = std::move(pt);
+ return dbl;
+}
+
+bool Dragger::IsDown() const
+{
+ return GetKeyState(VK_LBUTTON) & 0x8000;
+}
+
+bool Dragger::HandleLButtonDown()
+{
+ POINT pt;
+ Require(GetRelativeCursorPos(parent.hWnd, &pt));
+ if (!InDragArea(pt.x, pt.y)) return false;
+
+ if (IsDouble(GetMessageTime(), pt)) {
+ m_bActive = false;
+ Reset();
+ } else
+ m_bActive = true;
+
+ return m_bActive;
+}
+
+bool Dragger::HandleSetCursor()
+{
+ POINT pt;
+ Require(GetRelativeCursorPos(parent.hWnd, &pt));
+
+ extern HCURSOR g_hcSizeNs;
+ bool r = true;
+ if (InDragArea(pt.x, pt.y))
+ SetCursor(g_hcSizeNs);
+ else
+ r = false;
+ if (!m_bActive)
+ return r;
+ Drag(pt.x, pt.y);
+ if (!IsDown()) {
+ m_bActive = false;
+ Done();
+ }
+ return r;
+}
+
bool DlvDragger::InDragArea(const int x, const int y) const
{
RECT rrDlv;
- Require(GetRelativeRect(g_dlv->hWnd, &rrDlv));
+ Require(GetRelativeRect(g_window->dlv.hWnd, &rrDlv));
const int pad = EBIsThemeActive()? Dpi(6): 0;
const int extra = EBIsThemeActive()? 0: Dpi(2);
@@ -54,28 +104,26 @@ bool DlvDragger::InDragArea(const int x, const int y) const
void DlvDragger::Drag(const int, const int y) const
{
RECT rrDlv;
- Require(GetRelativeRect(g_dlv->hWnd, &rrDlv));
+ Require(GetRelativeRect(g_window->dlv.hWnd, &rrDlv));
if (y < Dpi(50) || y > rrDlv.bottom-Dpi(20)) return;
int h;
h = rrDlv.bottom-y;
- g_dlv->SetHeight(h);
+ g_window->dlv.SetHeight(h);
UpdateLayout();
- RedrawWindow(g_hWnd, nullptr, nullptr,
+ RedrawWindow(g_window->hWnd, nullptr, nullptr,
RDW_ERASE|RDW_FRAME|RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_UPDATENOW);
}
void DlvDragger::Reset() const
{
- extern CfgA& g_cfg;
- g_dlv->SetHeight(0);
- g_cfg.heightDlv = 0;
+ g_window->dlv.SetHeight(0);
+ g_window->cfg.heightDlv = 0;
UpdateLayout();
}
void DlvDragger::Done() const
{
- extern CfgA& g_cfg;
- g_cfg.heightDlv = g_dlv->Height();
+ g_window->cfg.heightDlv = g_window->dlv.Height();
}
diff --git a/c/layout.h b/c/layout.h
index 07eee56..4780821 100644
--- a/c/layout.h
+++ b/c/layout.h
@@ -14,10 +14,14 @@ void UpdateLayout(int w = 0, int h = 0);
* HandleSetCursor are called by relevant window procedures upon
* WM_(NC)LBUTTONDOWN and WM_SETCURSOR. */
+struct Window;
+
struct Dragger
{
+ Window& parent;
bool HandleLButtonDown();
bool HandleSetCursor();
+ inline Dragger(Window& parent) : parent(parent) {}
protected:
bool IsDown() const;
bool IsDouble(const long time, const POINT& pt);
@@ -39,6 +43,7 @@ private:
struct DlvDragger : public Dragger
{
+ inline DlvDragger(Window& parent) : Dragger(parent) {}
private:
bool InDragArea(int x, int y) const override;
void Drag(int x, int y) const override;
@@ -46,60 +51,4 @@ private:
void Done() const override;
};
-/* Below follows the implementation of the non-virtual member
- * functions of Dragger, on which derived objects rely. */
-
-inline bool Dragger::IsDouble(const long time, const POINT& pt)
-{
- const bool dbl = time-m_time0 <= static_cast<long>(GetDoubleClickTime())
- && abs(pt.x-m_pt0.x) <= Metric<SM_CXDOUBLECLK>
- && abs(pt.y-m_pt0.y) <= Metric<SM_CYDOUBLECLK>;
- m_time0 = time;
- m_pt0 = std::move(pt);
- return dbl;
-}
-
-inline bool Dragger::IsDown() const
-{
- return GetKeyState(VK_LBUTTON) & 0x8000;
-}
-
-inline bool Dragger::HandleLButtonDown()
-{
- extern HWND g_hWnd;
- POINT pt;
- Require(GetRelativeCursorPos(g_hWnd, &pt));
- if (!InDragArea(pt.x, pt.y)) return false;
-
- if (IsDouble(GetMessageTime(), pt)) {
- m_bActive = false;
- Reset();
- } else
- m_bActive = true;
-
- return m_bActive;
-}
-
-inline bool Dragger::HandleSetCursor()
-{
- extern HWND g_hWnd;
- POINT pt;
- Require(GetRelativeCursorPos(g_hWnd, &pt));
-
- extern HCURSOR g_hcSizeNs;
- bool r = true;
- if (InDragArea(pt.x, pt.y))
- SetCursor(g_hcSizeNs);
- else
- r = false;
- if (!m_bActive)
- return r;
- Drag(pt.x, pt.y);
- if (!IsDown()) {
- m_bActive = false;
- Done();
- }
- return r;
-}
-
#endif
diff --git a/c/listview.cpp b/c/listview.cpp
index 41f68a7..6074707 100644
--- a/c/listview.cpp
+++ b/c/listview.cpp
@@ -3,24 +3,24 @@
#include "listview.h"
#include "layout.h"
+#include "main.h"
#include "win.h"
/* Actual window procedure for all list views, which calls the
* appropriate WndProc member function. */
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
-ListView::ListView(const HWND hWndParent, const HMENU hMenu, const DWORD dwStyle)
+ListView::ListView(Window& parent, const HMENU hMenu, const DWORD dwStyle) : parent(parent)
{
extern HFONT g_hfNormal;
- m_hWndParent = hWndParent;
hWnd = Require(CreateWindowExW(
WS_EX_CLIENTEDGE,
WC_LISTVIEW,
L"",
dwStyle|WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_TABSTOP|LVS_REPORT|LVS_SHOWSELALWAYS,
0, 0, 0, 0,
- hWndParent, hMenu, GetModuleHandle(nullptr), this));
+ parent.hWnd, hMenu, GetModuleHandle(nullptr), this));
m_proc0 = reinterpret_cast<WNDPROC>(SetWindowLongPtr(
hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(::WndProc)));
@@ -41,7 +41,7 @@ void ListView::UpdateTheme(const bool bThemeActive)
{
const wchar_t* theme;
WORD action;
- extern BOOL (*SetWindowTheme)(HWND, const wchar_t*, const wchar_t*);
+ extern HRESULT (__stdcall *SetWindowTheme)(HWND, const wchar_t*, const wchar_t*);
if (!SetWindowTheme) return;
if (bThemeActive) {
@@ -62,7 +62,6 @@ void ListView::UpdateTheme(const bool bThemeActive)
LRESULT CALLBACK ListView::WndProc(const HWND hWnd, const UINT uMsg,
const WPARAM wParam, const LPARAM lParam)
{
- extern DlvDragger g_dragDlv;
switch (uMsg) {
case WM_NOTIFY:
switch (reinterpret_cast<NMHDR*>(lParam)->code) {
@@ -73,7 +72,7 @@ LRESULT CALLBACK ListView::WndProc(const HWND hWnd, const UINT uMsg,
break;
case WM_LBUTTONDOWN:
case WM_NCLBUTTONDOWN:
- if (g_dragDlv.HandleLButtonDown())
+ if (parent.dragDlv.HandleLButtonDown())
return 0;
break;
}
diff --git a/c/listview.h b/c/listview.h
index b0e49ee..30164f7 100644
--- a/c/listview.h
+++ b/c/listview.h
@@ -4,11 +4,14 @@
#include <windows.h>
#include <commctrl.h>
+struct Window;
+
struct ListView
{
HWND hWnd;
+ Window& parent;
- ListView(HWND hWndParent, HMENU hMenu, DWORD dwStyle);
+ ListView(Window& parent, HMENU hMenu, DWORD dwStyle);
/* Retrieve next matching list view item. */
bool FindNextItem(LVITEM* lvi, LPARAM lParam);
/* Naively calculate height appropriate for number of items. */
@@ -21,7 +24,6 @@ struct ListView
virtual LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
protected:
WNDPROC m_proc0;
- HWND m_hWndParent;
};
inline bool ListView::FindNextItem(LVITEM* const lvi, const LPARAM lParam)
diff --git a/c/main.cpp b/c/main.cpp
index 203a24d..58c0441 100644
--- a/c/main.cpp
+++ b/c/main.cpp
@@ -9,6 +9,7 @@
#include "datalistview.h"
#include "episodelistview.h"
#include "layout.h"
+#include "main.h"
#include "test.h"
#include "util.h"
@@ -24,56 +25,37 @@
* program. `extern' is used to access them from other files, when
* need be. */
-/* Exit gracefully on uncaught exception. */
-static auto _ = SET_TERMINATE;
-
/* Looked-up constants. */
int g_dpi = 96;
-/* Cursors. */
-HCURSOR g_hcArrow = LoadCursorW(nullptr, IDC_ARROW);
-HCURSOR g_hcSizeNs = LoadCursorW(nullptr, IDC_SIZENS);
-
/* Fonts. */
HFONT g_hfNormal;
HFONT g_hfBold;
+/* Cursors. */
+HCURSOR g_hcArrow = LoadCursorW(nullptr, IDC_ARROW);
+HCURSOR g_hcSizeNs = LoadCursorW(nullptr, IDC_SIZENS);
+
/* Menus. */
HMENU g_hMenuPopup;
/* Windows. */
HWND g_hWndFocus;
-HWND g_hWnd;
HWND g_hWndStatus;
-/* Child window objects. */
-DataListView* g_dlv;
-EpisodeListView* g_elv;
-
-/* Layout handlers. */
-DlvDragger g_dragDlv;
-
-/* File views. */
-FileView<CfgA> g_fvCfg = FileView<CfgA>::Initialized(L"cfg.dat", 1);
-CfgA& g_cfg = g_fvCfg.At(0);
-FileView<ElvDataA> g_fvElv(L"elvdata.dat", g_cfg.cEp+128u);
-FileView<DlvDataA> g_fvDlv(L"dlvdata.dat", g_cfg.cEp+128u);
-
/* Optional Windows functions. */
BOOL (__stdcall *IsThemeActive)();
HRESULT (__stdcall *SetWindowTheme)(HWND, const wchar_t*, const wchar_t*);
+/* Main window object. */
+Window* g_window;
+
/* Initialize important global state on parent window creation. */
-static void InitializeMainWindow(HWND);
+static void InitializeMainWindow(HWND) noexcept;
/* Process parent window commands. */
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
-static LRESULT CALLBACK HandleMsg(HWND, UINT, WPARAM, LPARAM);
-/* Process main menu commands. */
-static void HandleMainMenu(HWND, WORD);
/* Handle messages to Help > About dialog. */
-static INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
-/* Try to style application according to current Windows theme. */
-static void UpdateTheme();
+INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(
_In_ const HINSTANCE hInstance,
@@ -81,6 +63,9 @@ int WINAPI WinMain(
_In_ char* const,
_In_ const int nCmdShow)
{
+ /* Exit gracefully on uncaught exception. */
+ SET_TERMINATE;
+
setbuf(stdout, nullptr);
LIBXML_TEST_VERSION;
@@ -126,8 +111,8 @@ int WINAPI WinMain(
/* Populate episode list view. */
/* TODO: Update tracked episodes. */
- g_elv->Update();
- g_elv->RestoreFocus();
+ g_window->elv.Update();
+ g_window->elv.RestoreFocus();
#ifdef _DEBUG
RunTests();
@@ -143,7 +128,7 @@ int WINAPI WinMain(
return 0;
}
-void InitializeMainWindow(const HWND hWnd)
+static void InitializeMainWindow_(const HWND hWnd)
{
/* This code is run ONCE, at the creation of the top-level
* window -- before WndProc! This is important, as it
@@ -186,37 +171,58 @@ void InitializeMainWindow(const HWND hWnd)
g_hMenuPopup = Require(LoadMenuW(nullptr, MAKEINTRESOURCE(IDR_POPUPMENU)));
g_hMenuPopup = Require(GetSubMenu(g_hMenuPopup, 0));
- /* Create child windows. */
- g_dlv = new DataListView(hWnd);
- g_elv = new EpisodeListView(hWnd);
+ g_window = new Window(hWnd);
+}
- /* The global main window handle must only be set AFTER
- * successful initialization. */
- g_hWnd = hWnd;
+void InitializeMainWindow(const HWND hWnd) noexcept
+{
+ try {
+ InitializeMainWindow_(hWnd);
+ } catch (...) {
+ ShowException(L"Initialization failed due to an error: %s");
+ exit(1);
+ }
}
LRESULT CALLBACK WndProc(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam)
{
try {
- return HandleMsg(hWnd, uMsg, wParam, lParam);
+ return g_window->WndProc(hWnd, uMsg, wParam, lParam);
} catch (...) {
ShowException(L"The action was cancelled due to an error: %s");
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
-LRESULT CALLBACK HandleMsg(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam)
+INT_PTR CALLBACK AboutDlgProc(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM)
+{
+ switch (uMsg) {
+ case WM_CLOSE:
+ EndDialog(hWnd, IDOK);
+ return TRUE;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDOK)
+ EndDialog(hWnd, IDOK);
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+LRESULT CALLBACK Window::WndProc(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam)
{
switch (uMsg) {
case WM_CREATE:
UpdateTheme();
SetWindowPos(hWnd, nullptr, -1, -1, Dpi(510), Dpi(412), SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE);
- SetFocus(g_elv->hWnd);
+ SetFocus(elv.hWnd);
/* Set menu item checkmarks according to saved settings. */
- CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED, g_cfg.bViewWatched? MF_CHECKED: MF_UNCHECKED);
- CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL, g_cfg.bViewTVOriginal? MF_CHECKED: MF_UNCHECKED);
- CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, g_cfg.limitToScreenwriter[0]? MF_UNCHECKED: MF_CHECKED);
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED, cfg.bViewWatched? MF_CHECKED: MF_UNCHECKED);
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL, cfg.bViewTVOriginal? MF_CHECKED: MF_UNCHECKED);
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, cfg.limitToScreenwriter[0]? MF_UNCHECKED: MF_CHECKED);
return 0;
case WM_CLOSE:
@@ -224,7 +230,7 @@ LRESULT CALLBACK HandleMsg(const HWND hWnd, const UINT uMsg, const WPARAM wParam
return 0;
case WM_DESTROY:
- g_elv->SaveFocus();
+ elv.SaveFocus();
PostQuitMessage(0);
return 0;
@@ -270,14 +276,14 @@ LRESULT CALLBACK HandleMsg(const HWND hWnd, const UINT uMsg, const WPARAM wParam
else {
SetFocus(g_hWndFocus);
/* TODO: Update tracked episodes. */
- g_elv->Redraw();
+ elv.Redraw();
}
return 0;
case WM_NOTIFY:
switch (reinterpret_cast<NMHDR*>(lParam)->idFrom) {
case IDC_EPISODELISTVIEW:
- return g_elv->HandleNotify(lParam);
+ return elv.HandleNotify(lParam);
}
return 0;
@@ -289,7 +295,7 @@ LRESULT CALLBACK HandleMsg(const HWND hWnd, const UINT uMsg, const WPARAM wParam
HandleMainMenu(hWnd, command);
return 0;
case IDG_CTX:
- g_elv->HandleContextMenu(command);
+ elv.HandleContextMenu(command);
return 0;
default:
return 0;
@@ -309,13 +315,13 @@ LRESULT CALLBACK HandleMsg(const HWND hWnd, const UINT uMsg, const WPARAM wParam
/*IDM_FILE_FETCH_SCREENWRITERS*/L"Fetch screenwriters from the web (may take a minute).",
/*IDM_FILE_FETCH_CANCEL*/L"Stop fetching data from the web.",
/*IDM_FILE_ABOUT*/L"Show information about Episode Browser.",
- /*IDM_VIEW_WATCHED*/(g_cfg.bViewWatched?
+ /*IDM_VIEW_WATCHED*/(cfg.bViewWatched?
L"Click to hide watched episodes.":
L"Click to show watched episodes."),
- /*IDM_VIEW_TV_ORIGINAL*/(g_cfg.bViewTVOriginal?
+ /*IDM_VIEW_TV_ORIGINAL*/(cfg.bViewTVOriginal?
L"Click to hide TV original episodes.":
L"Click to show TV original episodes."),
- /*IDM_VIEW_OTHERS*/(g_cfg.limitToScreenwriter?
+ /*IDM_VIEW_OTHERS*/(cfg.limitToScreenwriter?
L"Click to hide episodes by other screenwriters.":
L"Click to show episodes by other screenwriters.")
};
@@ -349,11 +355,11 @@ LRESULT CALLBACK HandleMsg(const HWND hWnd, const UINT uMsg, const WPARAM wParam
}
case WM_LBUTTONDOWN:
- g_dragDlv.HandleLButtonDown();
+ dragDlv.HandleLButtonDown();
return 0;
case WM_SETCURSOR:
- if (g_dragDlv.HandleSetCursor())
+ if (dragDlv.HandleSetCursor())
return 1;
else {
/* Use default cursor. */
@@ -368,7 +374,7 @@ LRESULT CALLBACK HandleMsg(const HWND hWnd, const UINT uMsg, const WPARAM wParam
}
}
-void HandleMainMenu(const HWND hWnd, const WORD command)
+void Window::HandleMainMenu(const HWND hWnd, const WORD command)
{
switch (command) {
case IDM_FILE_EXIT:
@@ -376,7 +382,7 @@ void HandleMainMenu(const HWND hWnd, const WORD command)
break;
case IDM_FILE_REFRESH:
- g_elv->Update();
+ elv.Update();
break;
case IDM_FILE_FETCH_DATA:
@@ -402,61 +408,44 @@ void HandleMainMenu(const HWND hWnd, const WORD command)
break;
case IDM_VIEW_WATCHED:
- CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED, g_cfg.bViewWatched? MF_UNCHECKED: MF_CHECKED);
- g_cfg.bViewWatched = !g_cfg.bViewWatched;
- g_elv->Update();
- g_elv->EnsureFocusVisible();
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED, cfg.bViewWatched? MF_UNCHECKED: MF_CHECKED);
+ cfg.bViewWatched = !cfg.bViewWatched;
+ elv.Update();
+ elv.EnsureFocusVisible();
/* TODO: Remember last valid focus. In case of
* non-existing focus, use the last valid focus. */
break;
case IDM_VIEW_TV_ORIGINAL:
- CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL, g_cfg.bViewTVOriginal? MF_UNCHECKED: MF_CHECKED);
- g_cfg.bViewTVOriginal = !g_cfg.bViewTVOriginal;
- g_elv->Update();
- g_elv->EnsureFocusVisible();
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL, cfg.bViewTVOriginal? MF_UNCHECKED: MF_CHECKED);
+ cfg.bViewTVOriginal = !cfg.bViewTVOriginal;
+ elv.Update();
+ elv.EnsureFocusVisible();
break;
case IDM_VIEW_OTHERS:
- if (g_cfg.limitToScreenwriter[0]) { /* Show episodes by all screenwriters. */
+ if (cfg.limitToScreenwriter[0]) { /* Show episodes by all screenwriters. */
CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, MF_CHECKED);
- g_cfg.limitToScreenwriter[0] = 0;
+ cfg.limitToScreenwriter[0] = 0;
} else { /* Hide episodes by other screenwriters than current. */
LVITEM lvi = {LVIF_PARAM, -1};
- if (g_elv->FindNextItem(&lvi, LVNI_FOCUSED)
- && g_fvDlv.At(lvi.lParam-1).screenwriter[0]) {
- Wcscpy(g_cfg.limitToScreenwriter, g_fvDlv.At(lvi.lParam-1).screenwriter);
+ if (elv.FindNextItem(&lvi, LVNI_FOCUSED)
+ && fvDlv.At(lvi.lParam-1).screenwriter[0]) {
+ Wcscpy(cfg.limitToScreenwriter, fvDlv.At(lvi.lParam-1).screenwriter);
CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, MF_UNCHECKED);
}
}
- g_elv->Update();
- g_elv->EnsureFocusVisible();
+ elv.Update();
+ elv.EnsureFocusVisible();
break;
}
}
-INT_PTR CALLBACK AboutDlgProc(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM)
-{
- switch (uMsg) {
- case WM_CLOSE:
- EndDialog(hWnd, IDOK);
- return TRUE;
-
- case WM_COMMAND:
- if (LOWORD(wParam) == IDOK)
- EndDialog(hWnd, IDOK);
- return TRUE;
-
- default:
- return FALSE;
- }
-}
-
-void UpdateTheme()
+void Window::UpdateTheme()
{
if (IsThemeActive) {
const bool bThemeActive = IsThemeActive();
- g_dlv->UpdateTheme(bThemeActive);
- g_elv->UpdateTheme(bThemeActive);
+ dlv.UpdateTheme(bThemeActive);
+ elv.UpdateTheme(bThemeActive);
}
}
diff --git a/c/test.cpp b/c/test.cpp
index 6e291c7..35c6904 100644
--- a/c/test.cpp
+++ b/c/test.cpp
@@ -4,10 +4,11 @@
#include "data.h"
#include "episodelistview.h"
#include "ext.h"
+#include "main.h"
#include "util.h"
#include "win.h"
-extern CfgA& g_cfg;
+extern Window* g_window;
struct Test
{
@@ -35,9 +36,8 @@ TESTS
TEST(IO)
{
- extern FileView<ElvDataA> g_fvElv;
- ElvDataA& e1_0 = g_fvElv.At(5);
- ElvDataA& e2_0 = g_fvElv.At(9);
+ ElvDataA& e1_0 = g_window->fvElv.At(5);
+ ElvDataA& e2_0 = g_window->fvElv.At(9);
/* Write two ElvDataA structs to disk. */
{
@@ -91,7 +91,7 @@ TESTS
// TEST(MigrateCfg)
// {
// FileView<CfgB> fvb = FileView<CfgB>::Initialized(L"cfgb.dat", 1);
-// CfgA* a = &g_cfg;
+// CfgA* a = &g_window->cfg;
// CfgB* b = fvb+0;
// #define CPY(member) b->member = a->member;
diff --git a/c/win.cpp b/c/win.cpp
index e1349bc..40d68e4 100644
--- a/c/win.cpp
+++ b/c/win.cpp
@@ -4,6 +4,7 @@
#include <windows.h>
#include <wininet.h>
+#include "main.h"
#include "util.h"
#include "win.h"
@@ -86,9 +87,9 @@ static void CenterNextWindow(HWND hWnd)
int EBMessageBox(const std::wstring_view text, const std::wstring_view caption, const UINT uType)
{
- extern HWND g_hWnd;
- CenterNextWindow(g_hWnd);
- return MessageBox(g_hWnd, text.data(), caption.data(), uType);
+ extern Window* g_window;
+ CenterNextWindow(g_window->hWnd);
+ return MessageBox(g_window->hWnd, text.data(), caption.data(), uType);
}
void ShowException(const wchar_t* const fmt, const wchar_t* const title, const UINT uType) noexcept