aboutsummaryrefslogtreecommitdiff
path: root/c/main.cpp
diff options
context:
space:
mode:
authorJohn Ankarström <john@ankarstrom.se>2022-07-10 23:23:09 +0200
committerJohn Ankarström <john@ankarstrom.se>2022-07-10 23:25:09 +0200
commit295d423cc47f9ee8a72134dc544892a03b279311 (patch)
tree3e89b0bbcf42b3053225eb0dff88b887dd16df48 /c/main.cpp
parent85a4ad2c184ed915915a2fb630415a80ed9a286f (diff)
downloadEpisodeBrowser-295d423cc47f9ee8a72134dc544892a03b279311.tar.gz
Convert to C++.
I already hit upon some object-oriented programming patterns in *listview.c, so I felt that it would be natural to use this as an opportunity to learn C++.
Diffstat (limited to 'c/main.cpp')
-rw-r--r--c/main.cpp593
1 files changed, 593 insertions, 0 deletions
diff --git a/c/main.cpp b/c/main.cpp
new file mode 100644
index 0000000..6799280
--- /dev/null
+++ b/c/main.cpp
@@ -0,0 +1,593 @@
+#include <windows.h>
+#include <commctrl.h>
+#include <uxtheme.h>
+#include <SWI-Prolog.h>
+
+#include "resource.h"
+#include "defs.h"
+
+EpisodeListView g_elv;
+DataListView g_dlv;
+
+atom_t g_aThread;
+char g_szLimitScreenwriter[64] = {0};
+HFONT g_hfNormal;
+HFONT g_fBold;
+HMENU g_hPopupMenu;
+HWND g_hFocus;
+HWND g_hWnd;
+HWND g_hWndStatus;
+int g_bViewTVOriginal = 1;
+int g_bViewWatched = 1;
+int g_bThread = 0;
+int g_iDPI = -1;
+static int g_bThemes;
+static int g_cxVScroll;
+static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
+static INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
+static HWND CreateStatusBar(HWND, HINSTANCE);
+static int Attach(void);
+static void SetupFonts(void);
+static void UpdateTheme(void);
+
+int WINAPI
+WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+ LPSTR lpCmdLine, INT nCmdShow)
+{
+ char *argv[2];
+ HMODULE hModule;
+ HWND hWnd;
+ LPTSTR tszErr;
+ MSG msg;
+ INITCOMMONCONTROLSEX icc;
+ WNDCLASSEX wc;
+
+ /* Set constant values. */
+ hModule = LoadLibrary(TEXT("uxtheme.dll"));
+ g_bThemes = hModule && GetProcAddress(hModule,"SetWindowTheme");
+ if (hModule) FreeLibrary(hModule);
+ g_cxVScroll = GetSystemMetrics(SM_CXVSCROLL);
+
+ /* Initialize Prolog. */
+ argv[0] = (char *)"EpisodeBrowser";
+ argv[1] = NULL;
+ tszErr = TEXT("Could not initialize Prolog.");
+ if (!PL_initialise(1, argv)) goto f;
+ tszErr = TEXT("Could not attach databases.");
+ if (!Attach()) goto f;
+
+ /* Initialize common controls, load menu and register window class. */
+ icc.dwSize = sizeof(icc);
+ icc.dwICC = ICC_WIN95_CLASSES;
+ tszErr = TEXT("Could not initialize common controls.");
+ if (!InitCommonControlsEx(&icc)) goto f;
+
+ g_hPopupMenu = LoadMenu(NULL, MAKEINTRESOURCE(IDR_POPUPMENU));
+ g_hPopupMenu = GetSubMenu(g_hPopupMenu, 0);
+
+ memset(&wc, 0, sizeof(WNDCLASSEX));
+ wc.cbSize = sizeof(WNDCLASSEX);
+ wc.lpfnWndProc = WndProc;
+ wc.hInstance = hInstance;
+ wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
+ wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
+ wc.lpszClassName = TEXT("Episode Browser");
+ wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
+ tszErr = TEXT("Could not register window class.");
+ if (!RegisterClassEx(&wc)) goto f;
+
+ /* Create window. */
+ hWnd = CreateWindowEx(
+ 0,
+ TEXT("Episode Browser"),
+ TEXT("Episode Browser"),
+ WS_OVERLAPPEDWINDOW,
+ CW_USEDEFAULT, CW_USEDEFAULT, 0, 0,
+ NULL, NULL, hInstance, NULL
+ );
+ tszErr = TEXT("Could not create main window.");
+ if (!hWnd) goto f;
+ g_hWndStatus = CreateStatusBar(hWnd, hInstance);
+ ShowWindow(hWnd, nCmdShow);
+
+ /* Populate episode list view. */
+ Pl("track_episodes", "update_tracked_episodes", "");
+ g_elv.Update();
+ g_elv.SelectFocus();
+
+ while (GetMessage(&msg, NULL, 0, 0) > 0) {
+ if (IsDialogMessage(hWnd, &msg)) continue;
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ PL_halt(0);
+ return 0;
+f: MessageBox(NULL, tszErr, TEXT("Error"), MB_ICONERROR);
+ PL_halt(1);
+ return 1;
+}
+
+LRESULT CALLBACK
+WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg) {
+ case WM_CREATE:
+ {
+ HMODULE hModule;
+ UINT (*GetDpiForWindow)(HWND);
+ g_hWnd = hWnd;
+ g_iDPI = 96;
+ hModule = LoadLibrary(TEXT("User32.dll"));
+ if (hModule && (GetDpiForWindow = (UINT (*)(HWND))GetProcAddress(hModule, "GetDpiForWindow"))) {
+ g_iDPI = GetDpiForWindow(g_hWnd);
+ FreeLibrary(hModule);
+ }
+ SetWindowPos(hWnd, NULL, -1, -1, Dpi(510), Dpi(400), SWP_NOMOVE);
+ if (Pl("cfg","get_view_watched","i",&g_bViewWatched))
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED,
+ g_bViewWatched? MF_CHECKED: MF_UNCHECKED);
+ if (Pl("cfg","get_view_tv_original","i",&g_bViewTVOriginal))
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL,
+ g_bViewTVOriginal? MF_CHECKED: MF_UNCHECKED);
+ {
+ char *sz;
+ if (!Pl("cfg","get_limit_screenwriter","s",&sz))
+ goto s;
+ strcpy_s(g_szLimitScreenwriter,
+ sizeof(g_szLimitScreenwriter), sz);
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS,
+ g_szLimitScreenwriter[0]? MF_UNCHECKED: MF_CHECKED);
+ }
+ s: SetupFonts();
+ g_dlv.Create();
+ g_elv.Create();
+ UpdateTheme();
+ SetFocus(g_elv.HWnd());
+ break;
+ }
+ case WM_CLOSE:
+ DestroyWindow(hWnd);
+ break;
+ case WM_DESTROY:
+ {
+ LVITEM lvi;
+ HWND hElv;
+ hElv = g_elv.HWnd();
+ lvi.mask = LVIF_PARAM;
+ if ((lvi.iItem=ListView_GetNextItem(hElv,-1,LVNI_FOCUSED)) != -1
+ && ListView_GetItem(hElv, &lvi))
+ Pl("cfg","set_focus","I",lvi.lParam);
+ PostQuitMessage(0);
+ break;
+ }
+ case WM_SIZE:
+ SendMessage(g_hWndStatus, WM_SIZE, wParam, lParam);
+ UpdateLayout();
+ break;
+ case WM_GETMINMAXINFO:
+ {
+ LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam;
+ lpMMI->ptMinTrackSize.y = g_dlv.Height(0)+80;
+ break;
+ }
+ case WM_THEMECHANGED:
+ UpdateTheme();
+ break;
+ case 0x02E0: /* WM_DPICHANGED */
+ {
+ LPRECT lpr;
+ lpr = (LPRECT)lParam;
+ g_iDPI = HIWORD(wParam);
+ SetWindowPos(hWnd, NULL,
+ lpr->left, lpr->top,
+ lpr->right-lpr->left,
+ lpr->bottom-lpr->top,
+ SWP_NOZORDER | SWP_NOACTIVATE);
+ UpdateLayout();
+ break;
+ }
+ case WM_ACTIVATE:
+ switch (wParam) {
+ case WA_INACTIVE:
+ g_hFocus = GetFocus();
+ break;
+ case WA_ACTIVE:
+ case WA_CLICKACTIVE:
+ SetFocus(g_hFocus);
+ Pl("track_episodes","update_tracked_episodes","");
+ g_elv.Redraw();
+ }
+ break;
+ case WM_NOTIFY:
+ switch (((LPNMHDR)lParam)->idFrom) {
+ case IDC_EPISODELISTVIEW:
+ return g_elv.HandleNotify(lParam);
+ }
+ break;
+ case WM_TIMER:
+ switch (wParam) {
+ case IDT_TIMER:
+ {
+ static int i = 0;
+ if (Pl("episode_data","thread_running","A",g_aThread)) {
+ i = (i+1)%4;
+ SendMessage(g_hWndStatus, SB_SETTEXT, MAKEWPARAM(1,0),
+ (LPARAM)(i==0? TEXT("."):
+ i==1? TEXT(".."):
+ i==2? TEXT("..."):
+ TEXT("")));
+ } else {
+ i = 0;
+ KillTimer(hWnd, IDT_TIMER);
+ g_elv.Update();
+ }
+ break;
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDM_FILE_EXIT:
+ PostMessage(hWnd, WM_CLOSE, 0, 0);
+ break;
+ case IDM_FILE_REFRESH:
+ g_elv.Update();
+ break;
+ case IDM_FILE_FETCH_DATA:
+ if (g_bThread) break;
+ Pl("episode_data","thread_create","Sa","update_episode_data",&g_aThread);
+ goto t;
+ case IDM_FILE_FETCH_SCREENWRITERS:
+ if (g_bThread) break;
+ Pl("episode_data","thread_create","Sa","update_screenwriters",&g_aThread);
+ t: KillTimer(hWnd, IDT_TIMER);
+ SetTimer(hWnd, IDT_TIMER, 500, NULL);
+ SendMessage(g_hWndStatus, SB_SETTEXT, MAKEWPARAM(1,0), (LPARAM)TEXT("."));
+ g_bThread = 1;
+ break;
+ case IDM_FILE_ABOUT:
+ DialogBox(
+ GetModuleHandle(NULL),
+ MAKEINTRESOURCE(IDD_ABOUT),
+ hWnd,
+ AboutDlgProc
+ );
+ break;
+ case IDM_VIEW_WATCHED:
+ {
+ int iEpFocus;
+ HWND hElv;
+ hElv = g_elv.HWnd();
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED,
+ g_bViewWatched? MF_UNCHECKED: MF_CHECKED);
+ g_bViewWatched = !g_bViewWatched;
+ g_elv.Update();
+ Pl("cfg","set_view_watched","I",g_bViewWatched);
+ iEpFocus = ListView_GetNextItem(hElv, -1, LVNI_FOCUSED);
+ if (iEpFocus == -1) break;
+ ListView_EnsureVisible(hElv, iEpFocus, TRUE);
+ break;
+ }
+ case IDM_VIEW_TV_ORIGINAL:
+ {
+ int iEpFocus;
+ HWND hElv;
+ hElv = g_elv.HWnd();
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL,
+ g_bViewTVOriginal? MF_UNCHECKED: MF_CHECKED);
+ g_bViewTVOriginal = !g_bViewTVOriginal;
+ g_elv.Update();
+ Pl("cfg","set_view_tv_original","I",g_bViewTVOriginal);
+ iEpFocus = ListView_GetNextItem(hElv, -1, LVNI_FOCUSED);
+ if (iEpFocus == -1) break;
+ ListView_EnsureVisible(hElv, iEpFocus, TRUE);
+ break;
+ }
+ case IDM_VIEW_OTHERS: /* Show/hide other screenwriters. */
+ {
+ int iEpFocus;
+ HWND hElv;
+ hElv = g_elv.HWnd();
+ if (g_szLimitScreenwriter[0]) {
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS,
+ MF_CHECKED);
+ g_szLimitScreenwriter[0] = 0;
+ } else {
+ char *sz;
+ LVITEM lvi;
+ iEpFocus = ListView_GetNextItem(hElv, -1, LVNI_FOCUSED);
+ if (iEpFocus == -1) break;
+ lvi.iItem = iEpFocus;
+ lvi.mask = LVIF_PARAM;
+ if (!ListView_GetItem(hElv, &lvi)) break;
+ if (!Pl("episode_data","episode_datum","ISs",
+ lvi.lParam,"Screenwriter",&sz))
+ break;
+ strcpy_s(g_szLimitScreenwriter,
+ sizeof(g_szLimitScreenwriter), sz);
+ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS,
+ MF_UNCHECKED);
+ }
+ g_elv.Update();
+ Pl("cfg","set_limit_screenwriter","S",
+ g_szLimitScreenwriter);
+ iEpFocus = ListView_GetNextItem(hElv, -1, LVNI_FOCUSED);
+ if (iEpFocus == -1) break;
+ ListView_EnsureVisible(hElv, iEpFocus, TRUE);
+ break;
+ }
+ case IDM_WATCH_LOCALLY:
+ case IDM_WATCH_ONLINE:
+ case IDM_TOGGLE:
+ case IDM_FORGET:
+ case IDM_LOOKUP:
+ case IDM_WIKI:
+ case IDM_RATE10:
+ case IDM_RATE9:
+ case IDM_RATE8:
+ case IDM_RATE7:
+ case IDM_RATE6:
+ case IDM_RATE5:
+ case IDM_RATE4:
+ case IDM_RATE3:
+ case IDM_RATE2:
+ case IDM_RATE1:
+ case IDM_RATE0:
+ {
+ int iRating;
+ LVITEM lvi;
+ HWND hElv;
+ hElv = g_elv.HWnd();
+
+ /* Look through selected items, applying the
+ * selected command to each one. */
+
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = -1;
+ while ((lvi.iItem = ListView_GetNextItem(
+ hElv, lvi.iItem, LVNI_SELECTED)) != -1) {
+ if (!ListView_GetItem(hElv, &lvi)) goto b;
+
+ switch (LOWORD(wParam)) {
+ case IDM_WATCH_LOCALLY:
+ Pl("local_episode","open_episode_locally","I",lvi.lParam);
+ break;
+ case IDM_WATCH_ONLINE:
+ Pl("local_episode","open_episode_online","I",lvi.lParam);
+ break;
+ case IDM_TOGGLE:
+ Pl("track_episodes","toggle_episode","I",lvi.lParam);
+ g_elv.Redraw();
+ break;
+ case IDM_FORGET:
+ Pl("track_episodes","forget_episode","I",lvi.lParam);
+ Pl("track_episodes","update_tracked_episodes","");
+ g_elv.Redraw();
+ break;
+ case IDM_LOOKUP:
+ Pl("episode_data","retract_episode","I",lvi.lParam);
+ g_elv.UpdateItem(&lvi);
+ g_elv.Redraw();
+ g_dlv.ShowEpisode(lvi.lParam);
+ break;
+ case IDM_WIKI:
+ Pl("episode_data","open_episode_wiki","I",lvi.lParam);
+ break;
+ case IDM_RATE10:
+ iRating = 10;
+ goto r;
+ case IDM_RATE9:
+ iRating = 9;
+ goto r;
+ case IDM_RATE8:
+ iRating = 8;
+ goto r;
+ case IDM_RATE7:
+ iRating = 7;
+ goto r;
+ case IDM_RATE6:
+ iRating = 6;
+ goto r;
+ case IDM_RATE5:
+ iRating = 5;
+ goto r;
+ case IDM_RATE4:
+ iRating = 4;
+ goto r;
+ case IDM_RATE3:
+ iRating = 3;
+ goto r;
+ case IDM_RATE2:
+ iRating = 2;
+ goto r;
+ case IDM_RATE1:
+ iRating = 1;
+ goto r;
+ case IDM_RATE0:
+ iRating = 0;
+ r: Pl("episode_data","rate_episode","II",lvi.lParam,iRating);
+ g_elv.UpdateItem(&lvi);
+ break;
+ }
+ }
+ switch (LOWORD(wParam)) {
+ case IDM_RATE10:
+ case IDM_RATE9:
+ case IDM_RATE8:
+ case IDM_RATE7:
+ case IDM_RATE6:
+ case IDM_RATE5:
+ case IDM_RATE4:
+ case IDM_RATE3:
+ case IDM_RATE2:
+ case IDM_RATE1:
+ case IDM_RATE0:
+ g_elv.Redraw();
+ g_elv.DoSort();
+ g_elv.ShowFocus();
+ }
+ b: break;
+ }
+ }
+ break;
+ default:
+ return DefWindowProc(hWnd, uMsg, wParam, lParam);
+ }
+
+ return 0;
+}
+
+INT_PTR CALLBACK
+AboutDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg) {
+ case WM_CLOSE:
+ EndDialog(hWnd, IDOK);
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ EndDialog(hWnd, IDOK);
+ break;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+HWND
+CreateStatusBar(HWND hWndParent, HINSTANCE hInstance)
+{
+ HWND hWnd;
+ hWnd = CreateWindowEx(
+ 0,
+ STATUSCLASSNAME,
+ (LPCTSTR) NULL,
+ WS_CHILD|WS_VISIBLE|SBARS_SIZEGRIP,
+ 0, 0, 0, 0,
+ hWndParent,
+ (HMENU)ID_STATUS,
+ hInstance,
+ NULL
+ );
+ return hWnd;
+}
+
+/***/
+
+/* Attach persistent databases. */
+int
+Attach()
+{
+ if (!Pl("track_episodes","attach","")) return 0;
+ if (!Pl("episode_data","attach","")) return 0;
+ return 1;
+}
+
+void
+SetupFonts()
+{
+ HMODULE hModule;
+ LOGFONT lf;
+
+ hModule = LoadLibrary(TEXT("User32.dll"));
+ if (hModule && GetProcAddress(hModule, "SystemParametersInfoW")) {
+ NONCLIENTMETRICS m;
+ m.cbSize = sizeof(NONCLIENTMETRICS);
+ SystemParametersInfo(SPI_GETNONCLIENTMETRICS,
+ sizeof(NONCLIENTMETRICS), &m, 0);
+ g_hfNormal = CreateFontIndirect(&m.lfMessageFont);
+ FreeLibrary(hModule);
+ } else
+ g_hfNormal = static_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
+
+ GetObject(g_hfNormal, sizeof(LOGFONT), &lf);
+ lf.lfWeight = FW_BOLD;
+ g_fBold = CreateFontIndirect(&lf);
+}
+
+/***/
+
+void
+UpdateLayout()
+{
+ int cxColumn, cyDlv, yStatus;
+ RECT rc, rcStatus;
+ HWND hDlv, hElv;
+
+ hDlv = g_dlv.HWnd();
+ hElv = g_elv.HWnd();
+
+ GetClientRect(g_hWnd, &rc);
+ GetClientRect(g_hWndStatus, &rcStatus);
+ yStatus = rcStatus.bottom-rcStatus.top;
+
+ /* Resize data list view. */
+ SendMessage(hDlv, WM_SETREDRAW, FALSE, 0);
+ SendMessage(hElv, WM_SETREDRAW, FALSE, 0);
+ cyDlv = rc.bottom-yStatus-g_dlv.Height(DLVSIKEY);
+ MoveWindow(hDlv, 0, cyDlv, rc.right, rc.bottom-yStatus-cyDlv, TRUE);
+ ListView_SetColumnWidth(hDlv, DLVSIKEY, LVSCW_AUTOSIZE);
+ cxColumn = ListView_GetColumnWidth(hDlv, 0)+4;
+ ListView_SetColumnWidth(hDlv, DLVSIKEY, cxColumn);
+ ListView_SetColumnWidth(hDlv, DLVSIVALUE, rc.right-cxColumn-g_cxVScroll-4);
+
+ /* Resize episode list view. */
+ MoveWindow(hElv, 0, 0, rc.right, cyDlv+1, TRUE);
+ ListView_SetColumnWidth(hElv, ELVSIEPISODE, LVSCW_AUTOSIZE);
+ cxColumn = ListView_GetColumnWidth(hElv, ELVSIEPISODE)+4;
+ ListView_SetColumnWidth(hElv, ELVSIEPISODE, cxColumn);
+ cxColumn += ListView_GetColumnWidth(hElv, ELVSIRATING);
+ ListView_SetColumnWidth(hElv, ELVSITITLE, rc.right-cxColumn-g_cxVScroll-4);
+ SendMessage(hElv, WM_SETREDRAW, TRUE, 0);
+ SendMessage(hDlv, WM_SETREDRAW, TRUE, 0);
+
+ /* Resize status bar parts. */
+ {
+ int aParts[] = {rc.right-Dpi(55), rc.right};
+ SendMessage(g_hWndStatus, SB_SETPARTS,
+ (WPARAM)sizeof(aParts), (LPARAM)aParts);
+ }
+}
+
+/* Try to style application according to current Windows theme. */
+void
+UpdateTheme()
+{
+ DWORD dwStyle;
+ LPTSTR tszTheme;
+ WORD wAction;
+ HWND hDlv, hElv;
+
+ hDlv = g_dlv.HWnd();
+ hElv = g_elv.HWnd();
+
+ if (!g_bThemes) return;
+ if (IsThemeActive()) {
+ dwStyle = LVS_EX_DOUBLEBUFFER;
+ tszTheme = TEXT("Explorer");
+ wAction = UIS_SET;
+ } else {
+ dwStyle = 0;
+ tszTheme = NULL;
+ wAction = UIS_CLEAR;
+ }
+
+ /* Use modern "Explorer" theme. */
+ SetWindowTheme(hElv, tszTheme, NULL);
+ SetWindowTheme(hDlv, tszTheme, NULL);
+
+ /* The modern theme requires double buffering. */
+ ListView_SetExtendedListViewStyleEx(hElv, LVS_EX_DOUBLEBUFFER, dwStyle);
+ ListView_SetExtendedListViewStyleEx(hDlv, LVS_EX_DOUBLEBUFFER, dwStyle);
+
+ /* Hide focus rectangles. */
+ SendMessage(hElv, WM_UPDATEUISTATE, MAKEWPARAM(wAction, UISF_HIDEFOCUS), 0);
+ SendMessage(hDlv, WM_UPDATEUISTATE, MAKEWPARAM(wAction, UISF_HIDEFOCUS), 0);
+}