diff options
author | John Ankarström <john@ankarstrom.se> | 2022-09-02 02:11:49 +0200 |
---|---|---|
committer | John Ankarström <john@ankarstrom.se> | 2022-09-02 02:14:11 +0200 |
commit | 90c7bc237c9cf964c16f0cb48c308a92a8193a5c (patch) | |
tree | 53f165056dffa061a9dfe39b76913edab87056f4 | |
parent | bb9280267bfb78a8d69adea02f5ed7894833b19d (diff) | |
download | EpisodeBrowser-90c7bc237c9cf964c16f0cb48c308a92a8193a5c.tar.gz |
Use global Window object.
This makes it easier to control initialization and maintain RAII.
-rw-r--r-- | c/CMakeLists.txt | 1 | ||||
-rw-r--r-- | c/data.cpp | 30 | ||||
-rw-r--r-- | c/datalistview.cpp | 17 | ||||
-rw-r--r-- | c/datalistview.h | 2 | ||||
-rw-r--r-- | c/episodelistview.cpp | 53 | ||||
-rw-r--r-- | c/episodelistview.h | 2 | ||||
-rw-r--r-- | c/ext.cpp | 13 | ||||
-rw-r--r-- | c/layout.cpp | 90 | ||||
-rw-r--r-- | c/layout.h | 61 | ||||
-rw-r--r-- | c/listview.cpp | 11 | ||||
-rw-r--r-- | c/listview.h | 6 | ||||
-rw-r--r-- | c/main.cpp | 167 | ||||
-rw-r--r-- | c/test.cpp | 10 | ||||
-rw-r--r-- | c/win.cpp | 7 |
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 @@ -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); @@ -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(); } @@ -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) @@ -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); } } @@ -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; @@ -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 |