From bc4cef92d8efbf97a9215122abc2d7247c287f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Ankarstr=C3=B6m?= Date: Fri, 2 Sep 2022 20:16:04 +0200 Subject: Improve Window object. --- c/CMakeLists.txt | 10 +-- c/data.cpp | 44 +++++----- c/data.h | 5 +- c/datalistview.cpp | 7 +- c/debug.cpp | 2 +- c/drag.cpp | 97 +++++++++++++++++++++ c/drag.h | 48 +++++++++++ c/episodelistview.cpp | 19 ++--- c/ext.cpp | 17 ++-- c/ext.h | 8 +- c/layout.cpp | 129 ---------------------------- c/layout.h | 54 ------------ c/listview.cpp | 8 +- c/main.cpp | 79 ++++++++++------- c/test.cpp | 16 ++-- c/test.h | 3 +- c/win.cpp | 228 -------------------------------------------------- c/win.h | 153 --------------------------------- c/win32.cpp | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++ c/win32.h | 147 ++++++++++++++++++++++++++++++++ c/window.h | 50 +++++++++++ 21 files changed, 688 insertions(+), 664 deletions(-) create mode 100644 c/drag.cpp create mode 100644 c/drag.h delete mode 100644 c/layout.cpp delete mode 100644 c/layout.h delete mode 100644 c/win.cpp delete mode 100644 c/win.h create mode 100644 c/win32.cpp create mode 100644 c/win32.h create mode 100644 c/window.h (limited to 'c') diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt index 35af6ec..cd86f08 100644 --- a/c/CMakeLists.txt +++ b/c/CMakeLists.txt @@ -20,23 +20,23 @@ target_sources(EpisodeBrowser PRIVATE datalistview.h debug.cpp debug.h + drag.cpp + drag.h episodelistview.cpp episodelistview.h ext.cpp ext.h - layout.cpp - layout.h listview.cpp listview.h main.cpp - main.h resource.h resource.rc test.cpp test.h util.h - win.cpp - win.h + win32.cpp + win32.h + window.h ) if (CMAKE_GENERATOR MATCHES "Visual Studio") diff --git a/c/data.cpp b/c/data.cpp index 1274321..b1c028e 100644 --- a/c/data.cpp +++ b/c/data.cpp @@ -5,12 +5,12 @@ #include #include -#include "resource.h" #include "data.h" #include "episodelistview.h" -#include "main.h" +#include "resource.h" #include "util.h" -#include "win.h" +#include "win32.h" +#include "window.h" UniqueOk RemoteParserCtxt(const wchar_t* wszUrl, const char* szUrl) { @@ -88,9 +88,10 @@ enum Signal : unsigned char ABORT = 1<<2 /* Main -> fetch: exit prematurely! */ }; -void WaitFor(void (*f)(unsigned char*)) +static Window* s_window; + +void WaitFor(Window& window, void (*f)(unsigned char*)) { - extern Window* g_window; static unsigned char sig = READY; static UINT_PTR iTimer = 0; @@ -102,13 +103,13 @@ void WaitFor(void (*f)(unsigned char*)) KillTimer(nullptr, iTimer); i = 0; sig = READY; /* Reset signals. */ - g_window->elv.Update(); /* Reset status bar. */ - EnableMenuItem(GetMenu(g_window->hWnd), IDM_FILE_FETCH_CANCEL, MF_GRAYED); + s_window->elv.Update(); /* Reset status bar. */ + EnableMenuItem(GetMenu(s_window->hWnd), IDM_FILE_FETCH_CANCEL, MF_GRAYED); } else { /* Animate ellipsis in status bar. */ static const wchar_t* text[] = {L".", L"..", L"...", L""}; i = (i+1)%(sizeof(text)/sizeof(*text)); - Status(text[i], 1); + s_window->Status(text[i], 1); } }; @@ -118,7 +119,7 @@ void WaitFor(void (*f)(unsigned char*)) while (!(sig & READY)) Sleep(100); sig = 0; - EnableMenuItem(GetMenu(g_window->hWnd), IDM_FILE_FETCH_CANCEL, MF_ENABLED); + EnableMenuItem(GetMenu(s_window->hWnd), IDM_FILE_FETCH_CANCEL, MF_ENABLED); try { f(&sig); sig |= DONE; @@ -132,7 +133,7 @@ void WaitFor(void (*f)(unsigned char*)) /* Null indicates that any active task should be cancelled. */ if (!f) { sig |= ABORT; - EnableMenuItem(GetMenu(g_window->hWnd), IDM_FILE_FETCH_CANCEL, MF_GRAYED); + EnableMenuItem(GetMenu(s_window->hWnd), IDM_FILE_FETCH_CANCEL, MF_GRAYED); return; } @@ -146,8 +147,9 @@ void WaitFor(void (*f)(unsigned char*)) return; } + s_window = &window; std::thread(procThread, f).detach(); - Status(L".", 1); + s_window->Status(L".", 1); Prefer(iTimer = SetTimer(nullptr, iTimer, 500, procTimer)); } @@ -180,8 +182,6 @@ void FetchData(unsigned char* sig) throw std::runtime_error("could not find remote episode information"); for (int i = 0; i < nodes->nodeNr; i++) { - extern Window* g_window; - if (*sig & ABORT) return; @@ -189,8 +189,8 @@ void FetchData(unsigned char* sig) if (xmlChildElementCount(node) != 8) throw std::runtime_error("unexpected remote data format"); - ElvDataA& e = g_window->fvElv.At(i); - DlvDataA& d = g_window->fvDlv.At(i); + ElvDataA& e = s_window->fvElv.At(i); + DlvDataA& d = s_window->fvDlv.At(i); /* Each datum is contained within a specific cell in * the row. The child element count above ensures that @@ -222,8 +222,6 @@ void FetchData(unsigned char* sig) void FetchScreenwriters(unsigned char* sig) { - extern Window* g_window; - /* Screenwriters are expensive to fetch, so we try to avoid * fetching screenwriters for episodes that already have a * screenwriter. Additionally, in the same session, we don't @@ -231,18 +229,18 @@ 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_window->cfg.cEp-1; + int iMax = s_window->cfg.cEp-1; /* Find the last episode that has a screenwriter. */ if (iLast == -1) - for (size_t i = 0; i < g_window->fvDlv.c; i++) - if (const DlvDataA& d = g_window->fvDlv[i]; !d.date[0]) { + for (size_t i = 0; i < s_window->fvDlv.c; i++) + if (const DlvDataA& d = s_window->fvDlv[i]; !d.date[0]) { iMax = i-1; break; } else if (d.screenwriter[0]) iLast = i; - FINALLY { Status(L""); }; + FINALLY { s_window->Status(L""); }; /* Fetch screenwriters for the rest of the episodes. */ const wchar_t prefix[] = L"https://www.detectiveconanworld.com"; @@ -257,10 +255,10 @@ void FetchScreenwriters(unsigned char* sig) wchar_t msg[48]; Swprintf(msg, L"Fetching screenwriter for episode %d...", iLast+1); - Status(msg); + s_window->Status(msg); /* Retrieve URL for episode's wiki page. */ - DlvDataA& d = g_window->fvDlv[iLast]; + DlvDataA& d = s_window->fvDlv[iLast]; Wcscpy(Buf(url)+Len(prefix), d.wiki); /* Retrieve screenwriter from HTML. */ diff --git a/c/data.h b/c/data.h index 0f00ecb..919e1b4 100644 --- a/c/data.h +++ b/c/data.h @@ -6,7 +6,7 @@ #include #include "util.h" -#include "win.h" +#include "win32.h" struct XmlError : public std::exception { @@ -26,7 +26,8 @@ void FetchData(unsigned char* sig); void FetchScreenwriters(unsigned char* sig); /* Wait for thread. */ -void WaitFor(void (*f)(unsigned char*)); +struct Window; +void WaitFor(Window& window, void (*f)(unsigned char*)); /* The structs ending with A are written as-is to disk. As such, they * should be regarded as immutable. If the format needs to be changed diff --git a/c/datalistview.cpp b/c/datalistview.cpp index cdd8189..92fc05c 100644 --- a/c/datalistview.cpp +++ b/c/datalistview.cpp @@ -3,13 +3,12 @@ #include #include -#include "resource.h" #include "data.h" #include "datalistview.h" #include "episodelistview.h" #include "listview.h" -#include "layout.h" -#include "main.h" +#include "resource.h" +#include "window.h" DataListView::DataListView(Window& parent) : ListView(parent, reinterpret_cast(IDC_DATALISTVIEW), LVS_NOCOLUMNHEADER) @@ -81,7 +80,7 @@ void DataListView::ShowEpisode(const int iEpisode) } } - UpdateLayout(); + parent.UpdateLayout(); LVFINDINFO lvfi; lvfi.flags = LVFI_PARAM; diff --git a/c/debug.cpp b/c/debug.cpp index e8a9d6e..3e20d73 100644 --- a/c/debug.cpp +++ b/c/debug.cpp @@ -3,7 +3,7 @@ #include #include "debug.h" -#include "win.h" +#include "win32.h" struct Avg { int id; diff --git a/c/drag.cpp b/c/drag.cpp new file mode 100644 index 0000000..b92429a --- /dev/null +++ b/c/drag.cpp @@ -0,0 +1,97 @@ +#include + +#include "datalistview.h" +#include "drag.h" +#include "episodelistview.h" +#include "win32.h" +#include "window.h" + +bool Dragger::IsDouble(const long time, const POINT& pt) +{ + const bool dbl = time-m_time0 <= static_cast(GetDoubleClickTime()) + && abs(pt.x-m_pt0.x) <= Metric + && abs(pt.y-m_pt0.y) <= Metric; + 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(parent.dlv.hWnd, &rrDlv)); + + const int pad = EBIsThemeActive()? Dpi(6): 0; + const int extra = EBIsThemeActive()? 0: Dpi(2); + if (x < rrDlv.left || x > rrDlv.right) return false; + if (y < rrDlv.top-pad*2-extra*3 || y > rrDlv.top+extra) return false; + return true; +} + +void DlvDragger::Drag(const int, const int y) const +{ + RECT rrDlv; + Require(GetRelativeRect(parent.dlv.hWnd, &rrDlv)); + + if (y < Dpi(50) || y > rrDlv.bottom-Dpi(20)) return; + + int h; + h = rrDlv.bottom-y; + parent.dlv.SetHeight(h); + parent.UpdateLayout(); + RedrawWindow(parent.hWnd, nullptr, nullptr, + RDW_ERASE|RDW_FRAME|RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_UPDATENOW); +} + +void DlvDragger::Reset() const +{ + parent.dlv.SetHeight(0); + parent.cfg.heightDlv = 0; + parent.UpdateLayout(); +} + +void DlvDragger::Done() const +{ + parent.cfg.heightDlv = parent.dlv.Height(); +} diff --git a/c/drag.h b/c/drag.h new file mode 100644 index 0000000..ac97090 --- /dev/null +++ b/c/drag.h @@ -0,0 +1,48 @@ +#ifndef DRAG_H +#define DRAG_H + +#include + +/* Dragger objects implement draggable portions of the client area, + * such as the split between two list views. HandleLButtonDown and + * 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); + virtual bool InDragArea(int x, int y) const = 0; + /* Perform drag, resizing relevant windows. */ + virtual void Drag(int x, int y) const = 0; + /* Reset dragger to automatic position. */ + virtual void Reset() const = 0; + /* Called after drag, when mouse button is released. */ + virtual void Done() const = 0; +private: + bool m_bActive = false; + long m_time0 = 0; + POINT m_pt0 = {0, 0}; +}; + +/* DlvDragger implements the draggable split between the data list + * view and the episode list view. */ + +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; + void Reset() const override; + void Done() const override; +}; + +#endif diff --git a/c/episodelistview.cpp b/c/episodelistview.cpp index ea4849f..29c015b 100644 --- a/c/episodelistview.cpp +++ b/c/episodelistview.cpp @@ -3,15 +3,15 @@ #include #include -#include "resource.h" #include "data.h" #include "datalistview.h" #include "episodelistview.h" #include "ext.h" #include "listview.h" -#include "main.h" +#include "resource.h" #include "util.h" -#include "win.h" +#include "win32.h" +#include "window.h" EpisodeListView::EpisodeListView(Window& parent) : ListView(parent, reinterpret_cast(IDC_EPISODELISTVIEW), 0) @@ -66,12 +66,12 @@ void EpisodeListView::HandleContextMenu(const WORD command) /* Process other commands. */ switch (command) { case IDM_WATCH_LOCALLY: - if(!OpenLocally(lvi.lParam)) + if(!OpenLocally(parent.cfg, parent.fvElv.At(lvi.lParam-1))) cNotFound++; break; case IDM_WATCH_ONLINE: - OpenOnline(lvi.lParam); + OpenOnline(parent.cfg, lvi.lParam); break; case IDM_TOGGLE: @@ -79,7 +79,7 @@ void EpisodeListView::HandleContextMenu(const WORD command) break; case IDM_WIKI: - OpenWiki(lvi.lParam); + OpenWiki(parent.fvDlv.At(lvi.lParam-1)); break; } } @@ -176,15 +176,14 @@ LRESULT EpisodeListView::HandleNotify(const LPARAM lParam) { LVITEM lvi = {LVIF_PARAM, -1}; while (FindNextItem(&lvi, LVNI_SELECTED)) - OpenLocally(lvi.lParam) || OpenOnline(lvi.lParam); + OpenLocally(parent.cfg, parent.fvElv.At(lvi.lParam-1)) || OpenOnline(parent.cfg, lvi.lParam); return 0; } case NM_RCLICK: { - extern HMENU g_hMenuPopup; const DWORD pos = GetMessagePos(); - Require(TrackPopupMenu(g_hMenuPopup, TPM_RIGHTBUTTON, + Require(TrackPopupMenu(parent.hMenuPopup, TPM_RIGHTBUTTON, LOWORD(pos), HIWORD(pos), 0, parent.hWnd, nullptr)); return 0; } @@ -415,7 +414,7 @@ void EpisodeListView::Update() /* Show number of displayed items in status bar. */ wchar_t disp[16]; Swprintf(disp, L"%d", cItem); - Status(disp, 1); + parent.Status(disp, 1); } Sort(); diff --git a/c/ext.cpp b/c/ext.cpp index 77028d1..ea6718a 100644 --- a/c/ext.cpp +++ b/c/ext.cpp @@ -3,14 +3,12 @@ #include #include "data.h" -#include "main.h" +#include "window.h" -extern Window* g_window; - -bool OpenOnline(int iEp) +bool OpenOnline(const CfgA& cfg, int iEp) { - wchar_t url[sizeof(g_window->cfg.url)+4]; - Swprintf(url, L"%s%d", g_window->cfg.url, iEp); + wchar_t url[sizeof(cfg.url)+4]; + Swprintf(url, L"%s%d", cfg.url, iEp); INT_PTR r = reinterpret_cast( ShellExecuteW(nullptr, L"open", url, nullptr, nullptr, SW_SHOWNORMAL)); if (r <= 32) @@ -18,9 +16,8 @@ bool OpenOnline(int iEp) return true; } -bool OpenWiki(int iEp) +bool OpenWiki(const DlvDataA& d) { - 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( @@ -110,10 +107,10 @@ static bool FindMatchingFile(wchar_t (&file)[MAX_PATH], const wchar_t* const roo return false; } -bool OpenLocally(int iEp) +bool OpenLocally(CfgA& cfg, const ElvDataA& e) { wchar_t file[MAX_PATH]; - if (FindMatchingFile(file, g_window->cfg.root, g_window->fvElv.At(iEp-1).siEp)) { + if (FindMatchingFile(file, cfg.root, e.siEp)) { INT_PTR r = reinterpret_cast( ShellExecuteW(nullptr, L"open", file, nullptr, nullptr, SW_SHOWNORMAL)); if (r <= 32) diff --git a/c/ext.h b/c/ext.h index 3f9ddbd..5925206 100644 --- a/c/ext.h +++ b/c/ext.h @@ -1,8 +1,10 @@ #ifndef EXT_H #define EXT_H -bool OpenOnline(int iEp); -bool OpenWiki(int iEp); -bool OpenLocally(int iEp); +#include "data.h" + +bool OpenOnline(const CfgA& cfg, int iEp); +bool OpenWiki(const DlvDataA& d); +bool OpenLocally(CfgA& cfg, const ElvDataA& e); #endif diff --git a/c/layout.cpp b/c/layout.cpp deleted file mode 100644 index e817b23..0000000 --- a/c/layout.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include - -#include "episodelistview.h" -#include "datalistview.h" -#include "layout.h" -#include "main.h" -#include "win.h" - -extern Window* g_window; -extern HWND g_hWndStatus; - -void UpdateLayout(int w, int h) -{ - if (!g_hWndStatus) return; - - RECT rc, rrStatus; - if (w && h) rc = {0, 0, w, h}; - else Require(GetClientRect(g_window->hWnd, &rc)); - Require(GetRelativeRect(g_hWndStatus, &rrStatus)); - - 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_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(aParts)); - - 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(GetDoubleClickTime()) - && abs(pt.x-m_pt0.x) <= Metric - && abs(pt.y-m_pt0.y) <= Metric; - 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_window->dlv.hWnd, &rrDlv)); - - const int pad = EBIsThemeActive()? Dpi(6): 0; - const int extra = EBIsThemeActive()? 0: Dpi(2); - if (x < rrDlv.left || x > rrDlv.right) return false; - if (y < rrDlv.top-pad*2-extra*3 || y > rrDlv.top+extra) return false; - return true; -} - -void DlvDragger::Drag(const int, const int y) const -{ - RECT 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_window->dlv.SetHeight(h); - UpdateLayout(); - RedrawWindow(g_window->hWnd, nullptr, nullptr, - RDW_ERASE|RDW_FRAME|RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_UPDATENOW); -} - -void DlvDragger::Reset() const -{ - g_window->dlv.SetHeight(0); - g_window->cfg.heightDlv = 0; - UpdateLayout(); -} - -void DlvDragger::Done() const -{ - g_window->cfg.heightDlv = g_window->dlv.Height(); -} diff --git a/c/layout.h b/c/layout.h deleted file mode 100644 index 4780821..0000000 --- a/c/layout.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef LAYOUT_H -#define LAYOUT_H - -#include - -#include "win.h" - -/* Given main window's width and height, set appropriate positions and - * sizes for child windows. */ -void UpdateLayout(int w = 0, int h = 0); - -/* Dragger objects implement draggable portions of the client area, - * such as the split between two list views. HandleLButtonDown and - * 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); - virtual bool InDragArea(int x, int y) const = 0; - /* Perform drag, resizing relevant windows. */ - virtual void Drag(int x, int y) const = 0; - /* Reset dragger to automatic position. */ - virtual void Reset() const = 0; - /* Called after drag, when mouse button is released. */ - virtual void Done() const = 0; -private: - bool m_bActive = false; - long m_time0 = 0; - POINT m_pt0 = {0, 0}; -}; - -/* DlvDragger implements the draggable split between the data list - * view and the episode list view. */ - -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; - void Reset() const override; - void Done() const override; -}; - -#endif diff --git a/c/listview.cpp b/c/listview.cpp index 6074707..03a65ae 100644 --- a/c/listview.cpp +++ b/c/listview.cpp @@ -1,10 +1,10 @@ #include #include +#include "drag.h" #include "listview.h" -#include "layout.h" -#include "main.h" -#include "win.h" +#include "win32.h" +#include "window.h" /* Actual window procedure for all list views, which calls the * appropriate WndProc member function. */ @@ -66,7 +66,7 @@ LRESULT CALLBACK ListView::WndProc(const HWND hWnd, const UINT uMsg, case WM_NOTIFY: switch (reinterpret_cast(lParam)->code) { case HDN_ENDTRACK: - UpdateLayout(); + parent.UpdateLayout(); return TRUE; } break; diff --git a/c/main.cpp b/c/main.cpp index 58c0441..ea12db9 100644 --- a/c/main.cpp +++ b/c/main.cpp @@ -4,14 +4,14 @@ #include #include -#include "debug.h" -#include "resource.h" #include "datalistview.h" +#include "debug.h" +#include "drag.h" #include "episodelistview.h" -#include "layout.h" -#include "main.h" +#include "resource.h" #include "test.h" #include "util.h" +#include "window.h" #ifdef _DEBUG #define XMAIN 30 @@ -21,10 +21,6 @@ #define YMAIN CW_USEDEFAULT #endif -/* main.cpp defines all global (non-template) variables used in the - * program. `extern' is used to access them from other files, when - * need be. */ - /* Looked-up constants. */ int g_dpi = 96; @@ -36,26 +32,19 @@ HFONT g_hfBold; HCURSOR g_hcArrow = LoadCursorW(nullptr, IDC_ARROW); HCURSOR g_hcSizeNs = LoadCursorW(nullptr, IDC_SIZENS); -/* Menus. */ -HMENU g_hMenuPopup; - -/* Windows. */ -HWND g_hWndFocus; -HWND g_hWndStatus; +/* Main window object. */ +Window* g_window; /* 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) noexcept; /* Process parent window commands. */ static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); /* Handle messages to Help > About dialog. */ -INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM); +static INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain( _In_ const HINSTANCE hInstance, @@ -99,7 +88,7 @@ int WINAPI WinMain( XMAIN, YMAIN, 0, 0, nullptr, nullptr, hInstance, nullptr)); - g_hWndStatus = Require(CreateWindowExW( + g_window->hWndStatus = Require(CreateWindowExW( 0, STATUSCLASSNAME, nullptr, @@ -115,7 +104,7 @@ int WINAPI WinMain( g_window->elv.RestoreFocus(); #ifdef _DEBUG - RunTests(); + RunTests(*g_window); #endif MSG msg; @@ -167,10 +156,6 @@ static void InitializeMainWindow_(const HWND hWnd) SetWindowTheme = (decltype(SetWindowTheme))(void*)GetProcAddress(hModule, "SetWindowTheme"); } - /* Load context menu. */ - g_hMenuPopup = Require(LoadMenuW(nullptr, MAKEINTRESOURCE(IDR_POPUPMENU))); - g_hMenuPopup = Require(GetSubMenu(g_hMenuPopup, 0)); - g_window = new Window(hWnd); } @@ -235,7 +220,7 @@ LRESULT CALLBACK Window::WndProc(const HWND hWnd, const UINT uMsg, const WPARAM return 0; case WM_SIZE: - SendMessage(g_hWndStatus, WM_SIZE, wParam, lParam); + SendMessage(hWndStatus, WM_SIZE, wParam, lParam); UpdateLayout(LOWORD(lParam), HIWORD(lParam)); return 0; @@ -272,9 +257,9 @@ LRESULT CALLBACK Window::WndProc(const HWND hWnd, const UINT uMsg, const WPARAM case WM_ACTIVATE: if (wParam == WA_INACTIVE) - g_hWndFocus = GetFocus(); + hWndFocus = GetFocus(); else { - SetFocus(g_hWndFocus); + SetFocus(hWndFocus); /* TODO: Update tracked episodes. */ elv.Redraw(); } @@ -387,16 +372,16 @@ void Window::HandleMainMenu(const HWND hWnd, const WORD command) case IDM_FILE_FETCH_DATA: { - WaitFor(FetchData); + WaitFor(*this, FetchData); break; } case IDM_FILE_FETCH_SCREENWRITERS: - WaitFor(FetchScreenwriters); + WaitFor(*this, FetchScreenwriters); break; case IDM_FILE_FETCH_CANCEL: - WaitFor(nullptr); + WaitFor(*this, nullptr); break; case IDM_FILE_ABOUT: @@ -441,6 +426,40 @@ void Window::HandleMainMenu(const HWND hWnd, const WORD command) } } +void Window::Status(const wchar_t* msg, unsigned short i) +{ + SendMessage(hWndStatus, SB_SETTEXT, MAKEWPARAM(i, 0), reinterpret_cast(msg)); +} + +void Window::UpdateLayout(int w, int h) +{ + if (!hWndStatus) return; + + RECT rc, rrStatus; + if (w && h) rc = {0, 0, w, h}; + else Require(GetClientRect(hWnd, &rc)); + Require(GetRelativeRect(hWndStatus, &rrStatus)); + + SendMessageW(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-dlv.Height()-pad; + Require(SetWindowRect(dlv.hWnd, pad, cyDlv, rc.right-pad, rrStatus.top-pad)); + Require(SetWindowRect(elv.hWnd, pad, pad, rc.right-pad, cyDlv-pad)); + dlv.ResizeColumns(rc.right-pad-pad); + elv.ResizeColumns(rc.right-pad-pad); + + /* Resize status bar parts. */ + const int aParts[] = {rc.right-Dpi(55), rc.right}; + SendMessageW(hWndStatus, SB_SETPARTS, + sizeof(aParts), reinterpret_cast(aParts)); + + SendMessageW(hWnd, WM_SETREDRAW, TRUE, 0); + RedrawWindow(hWnd, nullptr, nullptr, + RDW_ERASE|RDW_FRAME|RDW_INVALIDATE|RDW_ALLCHILDREN); +} + void Window::UpdateTheme() { if (IsThemeActive) { diff --git a/c/test.cpp b/c/test.cpp index 35c6904..c0a9445 100644 --- a/c/test.cpp +++ b/c/test.cpp @@ -4,11 +4,11 @@ #include "data.h" #include "episodelistview.h" #include "ext.h" -#include "main.h" #include "util.h" -#include "win.h" +#include "win32.h" +#include "window.h" -extern Window* g_window; +Window* s_window; struct Test { @@ -36,8 +36,8 @@ TESTS TEST(IO) { - ElvDataA& e1_0 = g_window->fvElv.At(5); - ElvDataA& e2_0 = g_window->fvElv.At(9); + ElvDataA& e1_0 = s_window->fvElv.At(5); + ElvDataA& e2_0 = s_window->fvElv.At(9); /* Write two ElvDataA structs to disk. */ { @@ -91,7 +91,7 @@ TESTS // TEST(MigrateCfg) // { // FileView fvb = FileView::Initialized(L"cfgb.dat", 1); -// CfgA* a = &g_window->cfg; +// CfgA* a = &s_window->cfg; // CfgB* b = fvb+0; // #define CPY(member) b->member = a->member; @@ -113,8 +113,10 @@ TESTS // } }; -int RunTests() +int RunTests(Window& window) { + s_window = &window; + const Test tests[] = { StrcpyWithSmallerDestination(), //IO(), diff --git a/c/test.h b/c/test.h index 50ae9e6..31eb974 100644 --- a/c/test.h +++ b/c/test.h @@ -1,6 +1,7 @@ #ifndef TEST_H #define TEST_H -int RunTests(); +struct Window; +int RunTests(Window& window); #endif diff --git a/c/win.cpp b/c/win.cpp deleted file mode 100644 index 40d68e4..0000000 --- a/c/win.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include -#include -#include -#include -#include - -#include "main.h" -#include "util.h" -#include "win.h" - -std::wstring WideFromNarrow(const std::string_view src, const int cp) -{ - int cchNarrow = src.length()+1; - int cchWide = MultiByteToWideChar(cp, 0, src.data(), cchNarrow, nullptr, 0); - std::wstring dst(cchWide, 0); - if (!MultiByteToWideChar(cp, 0, src.data(), cchNarrow, dst.data(), cchWide)) - throw Win32Error(); - return dst; -} - -void WithNextWindow(void (*proc)(HWND)) -{ - /* WithNextWindow uses a CBT hook to call an arbitrary - * procedure just before the creation of the next window. The - * hook is removed as soon as HCBT_CREATEWND is received. - * Thus, proc is guaranteed to be executed only once. Note - * that static storage must be used, as SetWindowHookEx cannot - * accept a capturing lambda as the hook procedure. */ - - static thread_local auto procNext = proc; - static thread_local HHOOK hHook = Require(SetWindowsHookExW(WH_CBT, [](const int nCode, - const WPARAM wParam, const LPARAM lParam) -> LRESULT CALLBACK - { - if (nCode == HCBT_CREATEWND) { - Require(UnhookWindowsHookEx(hHook)); - procNext(reinterpret_cast(wParam)); - return 0; - } else - return CallNextHookEx(0, nCode, wParam, lParam); - }, nullptr, GetCurrentThreadId())); -} - -/* Display next created window in the center of given window. */ -static void CenterNextWindow(HWND hWnd) -{ - if (!hWnd) - return; - - /* CenterNextWindow employs a trick similar to that above. - * Note, however, that repositioning does not work unless it - * is delayed until the new window is activated. This - * complicates the code somewhat. */ - - static thread_local HWND hWndParent; - static thread_local HWND hWndNext; - static thread_local HHOOK hHook; - - hWndParent = hWnd; - hWndNext = nullptr; - hHook = Require(SetWindowsHookExW(WH_CBT, [](const int nCode, - const WPARAM wParam, const LPARAM lParam) noexcept -> LRESULT CALLBACK - { - if (!hWndNext && nCode == HCBT_CREATEWND) { - hWndNext = reinterpret_cast(wParam); - return 0; - } else if (nCode == HCBT_ACTIVATE - && reinterpret_cast(wParam) == hWndNext) { - Require(UnhookWindowsHookEx(hHook)); - - long lStyle = GetWindowLongW(hWndNext, GWL_STYLE); - if (!(lStyle & WS_POPUP)) return 0; - - RECT rcMain, rcMsg; - GetWindowRect(hWndParent, &rcMain); - GetWindowRect(hWndNext, &rcMsg); - SetWindowPos(hWndNext, nullptr, - rcMain.left+(rcMain.right-rcMain.left)/2-(rcMsg.right-rcMsg.left)/2, - rcMain.top+(rcMain.bottom-rcMain.top)/2-(rcMsg.bottom-rcMsg.top)/2, - -1, -1, - SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE); - - return 0; - } else - return CallNextHookEx(0, nCode, wParam, lParam); - }, nullptr, GetCurrentThreadId())); -} - -int EBMessageBox(const std::wstring_view text, const std::wstring_view caption, const UINT 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 -{ - try { - std::rethrow_exception(std::current_exception()); - } catch (const WideException& e) { - std::wstring msg(wcslen(fmt)+wcslen(e.What()), 0); - Swprintf(msg, fmt, e.What()); - EBMessageBox(msg, title, uType); - } catch (const std::exception& e) { - std::wstring what = WideFromNarrow(e.what()); - std::wstring msg(wcslen(fmt)+what.length(), 0); - Swprintf(msg, fmt, what.c_str()); - EBMessageBox(msg, title, uType); - } catch (...) { - const wchar_t* what = L"an unknown error occurred"; - std::wstring msg(wcslen(fmt)+wcslen(what), 0); - Swprintf(msg, fmt, what); - EBMessageBox(msg, title, uType); - } -} - -int GetRelativeCursorPos(const HWND hWnd, POINT* const pt) noexcept -{ - RECT rc; - if (!GetClientRect(hWnd, &rc)) return 0; - SetLastError(ERROR_SUCCESS); - if (!MapWindowPoints(hWnd, nullptr, (POINT*)&rc, 2)) return 0; - - POINT ptMouse; - if (!GetCursorPos(&ptMouse)) return 0; - pt->x = ptMouse.x-rc.left; - pt->y = ptMouse.y-rc.top; - return 1; -} - -Win32Error::Win32Error(const DWORD code, const HMODULE hModule) noexcept - : code(code), hModule(hModule) {} - -Win32Error::~Win32Error() -{ - if (m_szMsg) - HeapFree(GetProcessHeap(), 0, m_szMsg); - if (m_wszMsg) - HeapFree(GetProcessHeap(), 0, m_wszMsg); -} - -const char* Win32Error::what() const noexcept -{ - if (!m_szMsg) - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER - |FORMAT_MESSAGE_FROM_SYSTEM - |FORMAT_MESSAGE_FROM_HMODULE - |FORMAT_MESSAGE_IGNORE_INSERTS, - hModule, - code, - MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), - (char*)&m_szMsg, - 0, nullptr); - return m_szMsg? m_szMsg: "An unknown error occurred"; -} - -const wchar_t* Win32Error::What() const noexcept -{ - if (!m_wszMsg) - FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER - |FORMAT_MESSAGE_FROM_SYSTEM - |FORMAT_MESSAGE_FROM_HMODULE - |FORMAT_MESSAGE_IGNORE_INSERTS, - hModule, - code, - MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), - (wchar_t*)&m_wszMsg, - 0, nullptr); - return m_wszMsg? m_wszMsg: L"An unknown error occurred"; -} - -InternetError::InternetError(DWORD codeSystem) -{ - if (codeSystem != ERROR_INTERNET_EXTENDED_ERROR) - throw Win32Error(codeSystem, GetModuleHandle(L"wininet.dll")); - - DWORD len, cch; - InternetGetLastResponseInfo(&code, nullptr, &len); - - cch = len+1; - m_szMsg = new char[cch]; - if (!InternetGetLastResponseInfoA(&code, m_szMsg, &cch)) - throw Win32Error(); - - cch = len+1; - m_wszMsg = new wchar_t[cch]; - if (!InternetGetLastResponseInfoW(&code, m_wszMsg, &len)) - throw Win32Error(); -} - -InternetError::~InternetError() -{ - delete m_szMsg; - delete m_wszMsg; -} - -const char* InternetError::what() const noexcept -{ - return m_szMsg; -} - -const wchar_t* InternetError::What() const noexcept -{ - return m_wszMsg; -} - -std::optional Library::Maybe(const wchar_t* const lib) noexcept -{ - HMODULE hModule = LoadLibraryW(lib); - if (!hModule) return {}; - return Library(hModule); -} - -Library::Library(const HMODULE hModule) noexcept - : m_hModule(hModule) {} - -Library::Library(const wchar_t* const lib) -{ - m_hModule = LoadLibraryW(lib); - if (!m_hModule) - throw Win32Error(); -} - -Library::~Library() -{ - FreeLibrary(m_hModule); -} diff --git a/c/win.h b/c/win.h deleted file mode 100644 index 1e09c1f..0000000 --- a/c/win.h +++ /dev/null @@ -1,153 +0,0 @@ -#ifndef WIN_H -#define WIN_H - -#include -#include -#include -#include - -/* Convert narrow to wide string. */ -std::wstring WideFromNarrow(const std::string_view src, const int cp = CP_UTF8); - -/* Run given procedure at creation of next window. */ -void WithNextWindow(void (*proc)(HWND)); - -/* Display message box centered in main window. */ -int EBMessageBox(std::wstring_view text, std::wstring_view data, UINT uType); - -/* Show message box for current exception. */ -void ShowException( - const wchar_t* fmt = L"An error occurred: %s", - const wchar_t* title = L"Error", - UINT uType = MB_ICONWARNING) noexcept; - -/* Retrieve mouse position relative to given window's client area. */ -int GetRelativeCursorPos(HWND hWnd, POINT* pt) noexcept; - -/* Cached values from GetSystemMetrics. */ -template auto Metric = GetSystemMetrics(I); - -struct WideException : public std::exception -{ - virtual const char* what() const noexcept = 0; - virtual const wchar_t* What() const noexcept = 0; -}; - -/* Exception for Windows API errors. */ -struct Win32Error : public WideException -{ - Win32Error(DWORD code = GetLastError(), HMODULE hModule = nullptr) noexcept; - ~Win32Error() noexcept; - const char* what() const noexcept override; - const wchar_t* What() const noexcept override; - DWORD code; -private: - HMODULE hModule; - char* m_szMsg = nullptr; - wchar_t* m_wszMsg = nullptr; -}; - -/* Exception for extended Wininet errors. */ -struct InternetError : public WideException -{ - InternetError(DWORD codeSystem = GetLastError()); - ~InternetError() noexcept; - const char* what() const noexcept override; - const wchar_t* What() const noexcept override; - DWORD code; -private: - char* m_szMsg = nullptr; - wchar_t* m_wszMsg = nullptr; -}; - -/* Wrapper for loading and freeing dynamically linked libraries. */ -struct Library -{ - /* Non-throwing named constructor. */ - static std::optional Maybe(const wchar_t* lib) noexcept; - - Library(const wchar_t* lib); - ~Library() noexcept; - - template T* GetProcAddress(const char* szProc) noexcept; -private: - /* Non-throwing constructor used by Maybe. */ - Library(HMODULE hModule) noexcept; - HMODULE m_hModule; -}; - -template -T* Library::GetProcAddress(const char* const szProc) noexcept -{ - return (T*)(void*)::GetProcAddress(m_hModule, szProc); -} - -/* Check result of Windows API call, throwing error on null. */ -template -inline T Require(const T x) -{ - if (!x) throw Win32Error(); - return x; -} - -/* Check result of Windows API call, showing a warning on null. */ -template -inline T Prefer(const T x) -{ - if (!x) { - EBMessageBox(Win32Error().What(), L"System Error", MB_ICONWARNING); - return (T)0; - } - return x; -} - -/* Return integer scaled for current DPI. */ -inline int Dpi(const int i) -{ - extern int g_dpi; - return MulDiv(i, g_dpi, 96); -} - -/* Retrieve given window's rectangle relative to parent's client area. */ -inline BOOL GetRelativeRect(const HWND hWnd, RECT* const rr) -{ - if (!GetClientRect(hWnd, rr)) return 0; - return MapWindowPoints(hWnd, GetParent(hWnd), reinterpret_cast(rr), 2); -} - -inline BOOL SetWindowRect(const HWND hWnd, - const long left, const long top, const long right, const long bottom) -{ - return MoveWindow(hWnd, - left, top, - right-left, bottom-top, - TRUE); -} - -inline BOOL SetWindowRect(const HWND hWnd, const RECT r) -{ - return SetWindowRect(hWnd, r.left, r.top, r.right, r.bottom); -} - -/* The following two functions call uxtheme.dll functions, if - * uxtheme.dll exists, and otherwise do nothing. */ -inline BOOL EBIsThemeActive() -{ - extern BOOL (__stdcall *IsThemeActive)(); - return IsThemeActive? IsThemeActive(): 0; -} - -inline HRESULT EBSetWindowTheme(const HWND hWnd, const wchar_t* const pszSubAppName, - const wchar_t* const pszSubIdList) -{ - extern HRESULT (__stdcall *SetWindowTheme)(HWND, const wchar_t*, const wchar_t*); - return SetWindowTheme? SetWindowTheme(hWnd, pszSubAppName, pszSubIdList): 0; -} - -inline void Status(const wchar_t* msg, unsigned short i = 0) -{ - extern HWND g_hWndStatus; - SendMessage(g_hWndStatus, SB_SETTEXT, MAKEWPARAM(i,0), reinterpret_cast(msg)); -} - -#endif diff --git a/c/win32.cpp b/c/win32.cpp new file mode 100644 index 0000000..f06f96c --- /dev/null +++ b/c/win32.cpp @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include + +#include "util.h" +#include "win32.h" +#include "window.h" + +std::wstring WideFromNarrow(const std::string_view src, const int cp) +{ + int cchNarrow = src.length()+1; + int cchWide = MultiByteToWideChar(cp, 0, src.data(), cchNarrow, nullptr, 0); + std::wstring dst(cchWide, 0); + if (!MultiByteToWideChar(cp, 0, src.data(), cchNarrow, dst.data(), cchWide)) + throw Win32Error(); + return dst; +} + +void WithNextWindow(void (*proc)(HWND)) +{ + /* WithNextWindow uses a CBT hook to call an arbitrary + * procedure just before the creation of the next window. The + * hook is removed as soon as HCBT_CREATEWND is received. + * Thus, proc is guaranteed to be executed only once. Note + * that static storage must be used, as SetWindowHookEx cannot + * accept a capturing lambda as the hook procedure. */ + + static thread_local auto procNext = proc; + static thread_local HHOOK hHook = Require(SetWindowsHookExW(WH_CBT, [](const int nCode, + const WPARAM wParam, const LPARAM lParam) -> LRESULT CALLBACK + { + if (nCode == HCBT_CREATEWND) { + Require(UnhookWindowsHookEx(hHook)); + procNext(reinterpret_cast(wParam)); + return 0; + } else + return CallNextHookEx(0, nCode, wParam, lParam); + }, nullptr, GetCurrentThreadId())); +} + +/* Display next created window in the center of given window. */ +static void CenterNextWindow(HWND hWnd) +{ + if (!hWnd) + return; + + /* CenterNextWindow employs a trick similar to that above. + * Note, however, that repositioning does not work unless it + * is delayed until the new window is activated. This + * complicates the code somewhat. */ + + static thread_local HWND hWndParent; + static thread_local HWND hWndNext; + static thread_local HHOOK hHook; + + hWndParent = hWnd; + hWndNext = nullptr; + hHook = Require(SetWindowsHookExW(WH_CBT, [](const int nCode, + const WPARAM wParam, const LPARAM lParam) noexcept -> LRESULT CALLBACK + { + if (!hWndNext && nCode == HCBT_CREATEWND) { + hWndNext = reinterpret_cast(wParam); + return 0; + } else if (nCode == HCBT_ACTIVATE + && reinterpret_cast(wParam) == hWndNext) { + Require(UnhookWindowsHookEx(hHook)); + + long lStyle = GetWindowLongW(hWndNext, GWL_STYLE); + if (!(lStyle & WS_POPUP)) return 0; + + RECT rcMain, rcMsg; + GetWindowRect(hWndParent, &rcMain); + GetWindowRect(hWndNext, &rcMsg); + SetWindowPos(hWndNext, nullptr, + rcMain.left+(rcMain.right-rcMain.left)/2-(rcMsg.right-rcMsg.left)/2, + rcMain.top+(rcMain.bottom-rcMain.top)/2-(rcMsg.bottom-rcMsg.top)/2, + -1, -1, + SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE); + + return 0; + } else + return CallNextHookEx(0, nCode, wParam, lParam); + }, nullptr, GetCurrentThreadId())); +} + +int EBMessageBox(const std::wstring_view text, const std::wstring_view caption, const UINT 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 +{ + try { + std::rethrow_exception(std::current_exception()); + } catch (const WideException& e) { + std::wstring msg(wcslen(fmt)+wcslen(e.What()), 0); + Swprintf(msg, fmt, e.What()); + EBMessageBox(msg, title, uType); + } catch (const std::exception& e) { + std::wstring what = WideFromNarrow(e.what()); + std::wstring msg(wcslen(fmt)+what.length(), 0); + Swprintf(msg, fmt, what.c_str()); + EBMessageBox(msg, title, uType); + } catch (...) { + const wchar_t* what = L"an unknown error occurred"; + std::wstring msg(wcslen(fmt)+wcslen(what), 0); + Swprintf(msg, fmt, what); + EBMessageBox(msg, title, uType); + } +} + +int GetRelativeCursorPos(const HWND hWnd, POINT* const pt) noexcept +{ + RECT rc; + if (!GetClientRect(hWnd, &rc)) return 0; + SetLastError(ERROR_SUCCESS); + if (!MapWindowPoints(hWnd, nullptr, (POINT*)&rc, 2)) return 0; + + POINT ptMouse; + if (!GetCursorPos(&ptMouse)) return 0; + pt->x = ptMouse.x-rc.left; + pt->y = ptMouse.y-rc.top; + return 1; +} + +Win32Error::Win32Error(const DWORD code, const HMODULE hModule) noexcept + : code(code), hModule(hModule) {} + +Win32Error::~Win32Error() +{ + if (m_szMsg) + HeapFree(GetProcessHeap(), 0, m_szMsg); + if (m_wszMsg) + HeapFree(GetProcessHeap(), 0, m_wszMsg); +} + +const char* Win32Error::what() const noexcept +{ + if (!m_szMsg) + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER + |FORMAT_MESSAGE_FROM_SYSTEM + |FORMAT_MESSAGE_FROM_HMODULE + |FORMAT_MESSAGE_IGNORE_INSERTS, + hModule, + code, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + (char*)&m_szMsg, + 0, nullptr); + return m_szMsg? m_szMsg: "An unknown error occurred"; +} + +const wchar_t* Win32Error::What() const noexcept +{ + if (!m_wszMsg) + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER + |FORMAT_MESSAGE_FROM_SYSTEM + |FORMAT_MESSAGE_FROM_HMODULE + |FORMAT_MESSAGE_IGNORE_INSERTS, + hModule, + code, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + (wchar_t*)&m_wszMsg, + 0, nullptr); + return m_wszMsg? m_wszMsg: L"An unknown error occurred"; +} + +InternetError::InternetError(DWORD codeSystem) +{ + if (codeSystem != ERROR_INTERNET_EXTENDED_ERROR) + throw Win32Error(codeSystem, GetModuleHandle(L"wininet.dll")); + + DWORD len, cch; + InternetGetLastResponseInfo(&code, nullptr, &len); + + cch = len+1; + m_szMsg = new char[cch]; + if (!InternetGetLastResponseInfoA(&code, m_szMsg, &cch)) + throw Win32Error(); + + cch = len+1; + m_wszMsg = new wchar_t[cch]; + if (!InternetGetLastResponseInfoW(&code, m_wszMsg, &len)) + throw Win32Error(); +} + +InternetError::~InternetError() +{ + delete m_szMsg; + delete m_wszMsg; +} + +const char* InternetError::what() const noexcept +{ + return m_szMsg; +} + +const wchar_t* InternetError::What() const noexcept +{ + return m_wszMsg; +} + +std::optional Library::Maybe(const wchar_t* const lib) noexcept +{ + HMODULE hModule = LoadLibraryW(lib); + if (!hModule) return {}; + return Library(hModule); +} + +Library::Library(const HMODULE hModule) noexcept + : m_hModule(hModule) {} + +Library::Library(const wchar_t* const lib) +{ + m_hModule = LoadLibraryW(lib); + if (!m_hModule) + throw Win32Error(); +} + +Library::~Library() +{ + FreeLibrary(m_hModule); +} diff --git a/c/win32.h b/c/win32.h new file mode 100644 index 0000000..c4e57b7 --- /dev/null +++ b/c/win32.h @@ -0,0 +1,147 @@ +#ifndef WIN32_H +#define WIN32_H + +#include +#include +#include +#include + +/* Convert narrow to wide string. */ +std::wstring WideFromNarrow(const std::string_view src, const int cp = CP_UTF8); + +/* Run given procedure at creation of next window. */ +void WithNextWindow(void (*proc)(HWND)); + +/* Display message box centered in main window. */ +int EBMessageBox(std::wstring_view text, std::wstring_view data, UINT uType); + +/* Show message box for current exception. */ +void ShowException( + const wchar_t* fmt = L"An error occurred: %s", + const wchar_t* title = L"Error", + UINT uType = MB_ICONWARNING) noexcept; + +/* Retrieve mouse position relative to given window's client area. */ +int GetRelativeCursorPos(HWND hWnd, POINT* pt) noexcept; + +/* Cached values from GetSystemMetrics. */ +template auto Metric = GetSystemMetrics(I); + +struct WideException : public std::exception +{ + virtual const char* what() const noexcept = 0; + virtual const wchar_t* What() const noexcept = 0; +}; + +/* Exception for Windows API errors. */ +struct Win32Error : public WideException +{ + Win32Error(DWORD code = GetLastError(), HMODULE hModule = nullptr) noexcept; + ~Win32Error() noexcept; + const char* what() const noexcept override; + const wchar_t* What() const noexcept override; + DWORD code; +private: + HMODULE hModule; + char* m_szMsg = nullptr; + wchar_t* m_wszMsg = nullptr; +}; + +/* Exception for extended Wininet errors. */ +struct InternetError : public WideException +{ + InternetError(DWORD codeSystem = GetLastError()); + ~InternetError() noexcept; + const char* what() const noexcept override; + const wchar_t* What() const noexcept override; + DWORD code; +private: + char* m_szMsg = nullptr; + wchar_t* m_wszMsg = nullptr; +}; + +/* Wrapper for loading and freeing dynamically linked libraries. */ +struct Library +{ + /* Non-throwing named constructor. */ + static std::optional Maybe(const wchar_t* lib) noexcept; + + Library(const wchar_t* lib); + ~Library() noexcept; + + template T* GetProcAddress(const char* szProc) noexcept; +private: + /* Non-throwing constructor used by Maybe. */ + Library(HMODULE hModule) noexcept; + HMODULE m_hModule; +}; + +template +T* Library::GetProcAddress(const char* const szProc) noexcept +{ + return (T*)(void*)::GetProcAddress(m_hModule, szProc); +} + +/* Check result of Windows API call, throwing error on null. */ +template +inline T Require(const T x) +{ + if (!x) throw Win32Error(); + return x; +} + +/* Check result of Windows API call, showing a warning on null. */ +template +inline T Prefer(const T x) +{ + if (!x) { + EBMessageBox(Win32Error().What(), L"System Error", MB_ICONWARNING); + return (T)0; + } + return x; +} + +/* Return integer scaled for current DPI. */ +inline int Dpi(const int i) +{ + extern int g_dpi; + return MulDiv(i, g_dpi, 96); +} + +/* Retrieve given window's rectangle relative to parent's client area. */ +inline BOOL GetRelativeRect(const HWND hWnd, RECT* const rr) +{ + if (!GetClientRect(hWnd, rr)) return 0; + return MapWindowPoints(hWnd, GetParent(hWnd), reinterpret_cast(rr), 2); +} + +inline BOOL SetWindowRect(const HWND hWnd, + const long left, const long top, const long right, const long bottom) +{ + return MoveWindow(hWnd, + left, top, + right-left, bottom-top, + TRUE); +} + +inline BOOL SetWindowRect(const HWND hWnd, const RECT r) +{ + return SetWindowRect(hWnd, r.left, r.top, r.right, r.bottom); +} + +/* The following two functions call uxtheme.dll functions, if + * uxtheme.dll exists, and otherwise do nothing. */ +inline BOOL EBIsThemeActive() +{ + extern BOOL (__stdcall *IsThemeActive)(); + return IsThemeActive? IsThemeActive(): 0; +} + +inline HRESULT EBSetWindowTheme(const HWND hWnd, const wchar_t* const pszSubAppName, + const wchar_t* const pszSubIdList) +{ + extern HRESULT (__stdcall *SetWindowTheme)(HWND, const wchar_t*, const wchar_t*); + return SetWindowTheme? SetWindowTheme(hWnd, pszSubAppName, pszSubIdList): 0; +} + +#endif diff --git a/c/window.h b/c/window.h new file mode 100644 index 0000000..5cdffe4 --- /dev/null +++ b/c/window.h @@ -0,0 +1,50 @@ +#ifndef WINDOW_H +#define WINDOW_H + +#include + +#include "data.h" +#include "datalistview.h" +#include "drag.h" +#include "episodelistview.h" +#include "resource.h" +#include "win32.h" + +struct Window +{ + HWND hWnd; + HWND hWndFocus = nullptr; + HWND hWndStatus = nullptr; + HMENU hMenuPopup = Require(GetSubMenu(Require(LoadMenuW(nullptr, MAKEINTRESOURCE(IDR_POPUPMENU))), 0)); + + /* File views. */ + FileView fvCfg = FileView::Initialized(L"cfg.dat", 1); + CfgA& cfg = fvCfg.At(0); + FileView fvElv{L"elvdata.dat", cfg.cEp+128u}; + FileView fvDlv{L"dlvdata.dat", cfg.cEp+128u}; + + /* Layout handlers. */ + DlvDragger dragDlv = DlvDragger(*this); + + /* Child window objects. */ + DataListView dlv = DataListView(*this); + EpisodeListView elv = EpisodeListView(*this); + + inline Window(HWND hWnd) : hWnd(hWnd) {} + LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); + + /* Given main window's width and height, set appropriate + * positions and sizes for child windows. */ + void UpdateLayout(int w = 0, int h = 0); + + /* Process main menu commands. */ + void HandleMainMenu(HWND, WORD); + + /* Display text in status bar. */ + void Status(const wchar_t* msg, unsigned short i = 0); + + /* Try to style application according to current Windows theme. */ + void UpdateTheme(); +}; + +#endif -- cgit v1.2.3