aboutsummaryrefslogtreecommitdiff
path: root/c
diff options
context:
space:
mode:
authorJohn Ankarström <john@ankarstrom.se>2022-02-15 15:38:57 +0100
committerJohn Ankarström <john@ankarstrom.se>2022-02-15 15:38:57 +0100
commit09ec144a99f1234a37d04854bdfb81485540be97 (patch)
treece3050a062a76dd12980b08ef36eb7ac35b7e6f8 /c
parentfed19b9942575e7c0360a0d77a3c544afdbaeb6c (diff)
downloadEpisodeBrowser-09ec144a99f1234a37d04854bdfb81485540be97.tar.gz
Put C code and Prolog code in separate directories.
Diffstat (limited to 'c')
-rw-r--r--c/episode_browser.manifest30
-rw-r--r--c/resource.h7
-rw-r--r--c/resource.rc33
-rw-r--r--c/win.c560
4 files changed, 630 insertions, 0 deletions
diff --git a/c/episode_browser.manifest b/c/episode_browser.manifest
new file mode 100644
index 0000000..978f471
--- /dev/null
+++ b/c/episode_browser.manifest
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Supports Windows Vista / Server 2008 -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ <!-- Supports Windows 7 / Server 2008 R2 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <!-- Supports Windows 8 / Server 2012 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <!-- Supports Windows 8.1 / Server 2012 R2 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <!-- Supports Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ </application>
+ </compatibility>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity type="Win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
+ </dependentAssembly>
+ </dependency>
+</assembly>
diff --git a/c/resource.h b/c/resource.h
new file mode 100644
index 0000000..8167760
--- /dev/null
+++ b/c/resource.h
@@ -0,0 +1,7 @@
+#define IDR_MENU 101
+#define IDD_ABOUT 201
+#define IDC_ABOUTTEXT 301
+#define IDC_LISTVIEW 302
+#define ID_FILE_EXIT 4001
+#define ID_FILE_REFRESH 4002
+#define ID_FILE_ABOUT 4011
diff --git a/c/resource.rc b/c/resource.rc
new file mode 100644
index 0000000..36fa014
--- /dev/null
+++ b/c/resource.rc
@@ -0,0 +1,33 @@
+#include <windows.h>
+#include "resource.h"
+
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "episode_browser.manifest"
+
+IDR_MENU MENU
+BEGIN
+ POPUP "&File"
+ BEGIN
+ MENUITEM "&Refresh", ID_FILE_REFRESH
+ MENUITEM "&Exit", ID_FILE_EXIT
+ END
+ POPUP "&Help"
+ BEGIN
+ MENUITEM "&About", ID_FILE_ABOUT
+ END
+END
+
+#define PAD 7
+#define ABOUTW 190
+#define ABOUTH 40
+#define OKW 48
+#define OKH 16
+
+IDD_ABOUT DIALOGEX DISCARDABLE 20, 20, ABOUTW, ABOUTH
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "About"
+FONT 8, "MS Shell Dlg 2"
+BEGIN
+ DEFPUSHBUTTON "&OK", IDOK, ABOUTW-OKW-PAD, ABOUTH-OKH-PAD, OKW, OKH
+ LTEXT TEXT("Episode Browser\r\nCopyright 2021 John Ankarström"),
+ IDC_ABOUTTEXT, PAD, PAD, ABOUTW-OKW-PAD*2, ABOUTH-PAD
+END \ No newline at end of file
diff --git a/c/win.c b/c/win.c
new file mode 100644
index 0000000..6f82645
--- /dev/null
+++ b/c/win.c
@@ -0,0 +1,560 @@
+#include <windows.h>
+#include <TCHAR.H>
+#include <commctrl.h>
+#include <uxtheme.h>
+#include <stdio.h>
+#include <string.h>
+#include <SWI-Prolog.h>
+#include "resource.h"
+
+#define CLASSNAME TEXT("Episode Browser")
+
+HFONT g_GUIFont;
+HFONT g_GUIFontBold;
+WNDPROC g_PrevLvProc;
+
+int g_SelectedItem = -1; /* Remembered after refresh. */
+
+static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
+static INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
+static LRESULT CALLBACK LvProc(HWND, UINT, WPARAM, LPARAM);
+
+static void CreateListView(HWND);
+static LRESULT HandleListViewNotify(HWND, LPARAM);
+static void UpdateLayout(HWND);
+
+static void SetupFonts();
+static TCHAR *TSZFromSZ(char *, int);
+
+static int Attach(void);
+static void UpdateName(HWND, NMLISTVIEW *);
+static void UpdateList(HWND);
+static void ShowEpisode(HWND, int);
+static int Watched(int);
+
+int WINAPI
+WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+ LPSTR lpCmdLine, INT nCmdShow)
+{
+ char *rgArgs[2];
+ HWND hWnd;
+ MSG msg;
+ INITCOMMONCONTROLSEX icc;
+ WNDCLASSEX wc;
+
+ /* Initialize Prolog. */
+
+ rgArgs[0] = "episode_browser";
+ rgArgs[1] = NULL;
+
+ if (!PL_initialise(1, rgArgs))
+ PL_halt(1);
+
+ Attach();
+
+ /* Create window. */
+
+ icc.dwSize = sizeof(icc);
+ icc.dwICC = ICC_WIN95_CLASSES;
+ InitCommonControlsEx(&icc);
+
+ SetupFonts();
+
+ wc.cbSize = sizeof(WNDCLASSEX);
+ wc.style = 0;
+ wc.lpfnWndProc = WndProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ 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 = CLASSNAME;
+ wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
+
+ RegisterClassEx(&wc);
+
+ hWnd = CreateWindowEx(
+ 0,
+ CLASSNAME,
+ TEXT("Episode Browser"),
+ WS_OVERLAPPEDWINDOW,
+ CW_USEDEFAULT, CW_USEDEFAULT, /* Position */
+ 510, 300, /* Size */
+ NULL, /* Parent window */
+ NULL, /* Menu */
+ hInstance,
+ NULL
+ );
+
+ if (!hWnd)
+ return 0;
+
+ ShowWindow(hWnd, nCmdShow);
+
+ while (GetMessage(&msg, NULL, 0, 0) > 0) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ return 0;
+}
+
+LRESULT CALLBACK
+WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg) {
+ case WM_CLOSE:
+ DestroyWindow(hWnd);
+ break;
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case ID_FILE_EXIT:
+ PostMessage(hWnd, WM_CLOSE, 0, 0);
+ break;
+ case ID_FILE_REFRESH:
+ UpdateList(hWnd);
+ break;
+ case ID_FILE_ABOUT:
+ DialogBox(
+ GetModuleHandle(NULL),
+ MAKEINTRESOURCE(IDD_ABOUT),
+ hWnd,
+ AboutDlgProc
+ );
+ break;
+ }
+ break;
+ case WM_CREATE:
+ CreateListView(hWnd);
+ break;
+ case WM_SIZE:
+ UpdateLayout(hWnd);
+ break;
+ case WM_NOTIFY:
+ switch (((NMHDR *)lParam)->idFrom) {
+ case IDC_LISTVIEW:
+ return HandleListViewNotify(hWnd, lParam);
+ }
+ 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_INITDIALOG:
+ return TRUE;
+ 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;
+}
+
+LRESULT CALLBACK
+LvProc(HWND hListView, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg) {
+ case WM_NOTIFY:
+ switch (((NMHDR *)lParam)->code) {
+ case HDN_ENDTRACK:
+ UpdateLayout(GetParent(hListView));
+ return TRUE;
+ }
+ break;
+ }
+
+ return CallWindowProc(g_PrevLvProc,
+ hListView, uMsg, wParam, lParam);
+}
+
+/***/
+
+void
+CreateListView(HWND hWnd)
+{
+ HMODULE hModule;
+ HWND hListView;
+ LVCOLUMN lvc;
+
+ hListView = CreateWindowEx(
+ 0,
+ WC_LISTVIEW,
+ TEXT(""),
+ WS_CHILD|WS_VISIBLE|WS_VSCROLL|LVS_REPORT,
+ 0, 0, 0, 0,
+ hWnd,
+ (HMENU)IDC_LISTVIEW,
+ GetModuleHandle(NULL),
+ NULL
+ );
+
+ g_PrevLvProc = (WNDPROC)SetWindowLongPtr(hListView,
+ GWLP_WNDPROC, (LONG_PTR)LvProc);
+
+ SendMessage(hListView, WM_SETFONT,
+ (WPARAM)g_GUIFont, MAKELPARAM(FALSE, 0));
+
+ ListView_SetExtendedListViewStyle(hListView,
+ LVS_EX_DOUBLEBUFFER);
+
+ hModule = LoadLibrary(TEXT("uxtheme.dll"));
+ if (hModule && GetProcAddress(hModule, "SetWindowTheme")) {
+ ListView_SetExtendedListViewStyle(hListView,
+ LVS_EX_FULLROWSELECT|LVS_EX_DOUBLEBUFFER);
+ SendMessage(hListView, WM_CHANGEUISTATE,
+ MAKEWPARAM(UIS_SET, UISF_HIDEFOCUS), 0);
+ SetWindowTheme(hListView, TEXT("Explorer"), NULL);
+ }
+
+ lvc.mask = LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;
+
+ lvc.iSubItem = 0;
+ lvc.pszText = TEXT("#");
+ lvc.cx = 42;
+ ListView_InsertColumn(hListView, 0, &lvc);
+
+ lvc.iSubItem = 1;
+ lvc.pszText = TEXT("Title");
+ lvc.cx = 500;
+ ListView_InsertColumn(hListView, 1, &lvc);
+
+ UpdateList(hWnd);
+}
+
+LRESULT
+HandleListViewNotify(HWND hWnd, LPARAM lParam)
+{
+ NMLISTVIEW *pNmLv;
+
+ pNmLv = (NMLISTVIEW *)lParam;
+
+ switch (pNmLv->hdr.code) {
+ case LVN_ITEMCHANGED:
+ if ((pNmLv->uChanged & LVIF_STATE)
+ && (pNmLv->uNewState & LVIS_FOCUSED)) {
+ g_SelectedItem = pNmLv->iItem;
+ UpdateName(hWnd, pNmLv);
+ //ShowEpisode(hWnd, pNmLv->lParam);
+ }
+ break;
+ case NM_CUSTOMDRAW:
+ {
+ NMLVCUSTOMDRAW *pLvCd;
+ pLvCd = (NMLVCUSTOMDRAW *)lParam;
+ switch (pLvCd->nmcd.dwDrawStage) {
+ case CDDS_PREPAINT:
+ return CDRF_NOTIFYITEMDRAW;
+ break;
+ case CDDS_ITEMPREPAINT:
+ if (!Watched(pLvCd->nmcd.lItemlParam)) {
+ SelectObject(pLvCd->nmcd.hdc,
+ g_GUIFontBold);
+ return CDRF_NEWFONT;
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ return 0;
+}
+
+void
+UpdateLayout(HWND hWnd)
+{
+ HWND hListView;
+ int cxColumn;
+ RECT rc;
+ static int cxVScroll = 0;
+
+ if (cxVScroll == 0)
+ cxVScroll = GetSystemMetrics(SM_CXVSCROLL);
+
+ GetClientRect(hWnd, &rc);
+ hListView = GetDlgItem(hWnd, IDC_LISTVIEW);
+ MoveWindow(hListView, 0, 0,
+ rc.right, rc.bottom,
+ TRUE);
+
+ cxColumn = ListView_GetColumnWidth(hListView, 0);
+ ListView_SetColumnWidth(hListView, 1,
+ rc.right-cxColumn-cxVScroll);
+
+ ListView_EnsureVisible(hListView, g_SelectedItem, TRUE);
+}
+
+/***/
+
+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_GUIFont = CreateFontIndirect(&m.lfMessageFont);
+ } else {
+ g_GUIFont = GetStockObject(DEFAULT_GUI_FONT);
+ }
+
+ GetObject(g_GUIFont, sizeof(LOGFONT), &lf);
+ lf.lfWeight = FW_BOLD;
+ g_GUIFontBold = CreateFontIndirect(&lf);
+}
+
+/* Convert zero-terminated non-wide (multi-byte) string to
+ * zero-terminated wide/non-wide string depending on UNICODE. */
+TCHAR *
+TSZFromSZ(char *sz, int iCp)
+{
+ TCHAR *tsz;
+
+#ifdef UNICODE
+ int cbMultiByte, cchWideChar;
+
+ cbMultiByte = strlen(sz)+1;
+ cchWideChar = MultiByteToWideChar(iCp, 0, sz, cbMultiByte, NULL, 0);
+ tsz = malloc(cchWideChar*sizeof(WCHAR));
+ if (!tsz)
+ return NULL;
+ if (!MultiByteToWideChar(iCp, 0, sz, cbMultiByte, tsz, cchWideChar))
+ return NULL;
+#else
+ tsz = malloc(strlen(sz)+1);
+ if (!tsz)
+ return NULL;
+ strcpy(tsz, sz);
+#endif
+
+ return tsz;
+}
+
+/***/
+
+/* Attach persistent databases. */
+int
+Attach()
+{
+ int r;
+ term_t t;
+
+ t = PL_new_term_refs(2);
+ r = PL_call_predicate(NULL, PL_Q_NORMAL,
+ PL_predicate("attach", 0, "track_episodes"),
+ t);
+ if (!r) return r;
+
+ r = PL_call_predicate(NULL, PL_Q_NORMAL,
+ PL_predicate("attach", 0, "episode_data"),
+ t);
+ if (!r) return r;
+
+ return 1;
+}
+
+/* Show episode data. */
+void
+ShowEpisode(HWND hWnd, int iEpisode)
+{
+ int r;
+ term_t t;
+
+ t = PL_new_term_refs(3);
+ if(!PL_put_integer(t+0, iEpisode))
+ return;
+
+ r = PL_call_predicate(NULL, PL_Q_NORMAL,
+ PL_predicate("lookup_episode_local", 3, "episode_data"),
+ t);
+ if (!r)
+ return;
+
+ /* The episode data is a list of unary compounds,
+ * whose functor is the key and whose argument is the value. */
+
+ {
+ term_t tHead, tList;
+
+ tHead = PL_new_term_ref();
+ tList = PL_copy_term_ref(t+2);
+
+ while (PL_get_list(tList, tHead, tList)) {
+ atom_t aKey;
+ const char *szKey;
+ char *szValue;
+ term_t tValue;
+ size_t iArity;
+
+ if (!PL_get_name_arity(tHead, &aKey, &iArity))
+ continue;
+ szKey = PL_atom_chars(aKey);
+
+ if (!PL_get_arg(1, tHead, tValue))
+ continue;
+ if (!PL_get_atom_chars(tValue, &szValue))
+ continue;
+
+ printf("%s/%d: %s\n", szKey, iArity, szValue);
+ }
+ }
+}
+
+/* Update episode name. */
+void
+UpdateName(HWND hWnd, NMLISTVIEW *pNmLv)
+{
+ char *szName;
+ HWND hListView;
+ TCHAR *tszName;
+ term_t t;
+
+ t = PL_new_term_refs(3);
+ if (!PL_put_integer(t+0, pNmLv->lParam))
+ return;
+
+ PL_call_predicate(NULL, PL_Q_NORMAL,
+ PL_predicate("lookup_episode", 3, "episode_data"),
+ t);
+
+ if (!PL_get_atom_chars(t+1, &szName))
+ return;
+
+ tszName = TSZFromSZ(szName, CP_UTF8);
+ if (!tszName)
+ return;
+
+ hListView = GetDlgItem(hWnd, IDC_LISTVIEW);
+ ListView_SetItemText(hListView, pNmLv->iItem, 1, tszName);
+}
+
+/* Update episode list. */
+void
+UpdateList(HWND hWnd)
+{
+ HWND hListView;
+ LVITEM lviEpisode, lviName;
+ qid_t q;
+ term_t t;
+
+ hListView = GetDlgItem(hWnd, IDC_LISTVIEW);
+
+ ListView_DeleteAllItems(hListView);
+
+ lviEpisode.mask = LVIF_TEXT|LVIF_PARAM;
+ lviName.mask = LVIF_TEXT;
+
+ t = PL_new_term_refs(2);
+ PL_call_predicate(NULL, PL_Q_NORMAL,
+ PL_predicate("update_tracked_episodes", 0, "track_episodes"),
+ t);
+
+ q = PL_open_query(NULL, PL_Q_NORMAL,
+ PL_predicate("episode_file", 2, "local_episodes"),
+ t);
+
+ for (int i = 0; PL_next_solution(q); i++) {
+ char *szName;
+ int cb, iEpisode, iItem, r;
+ TCHAR *tszEpisode, *tszName;
+ term_t t2;
+
+ /* Lookup episode name. */
+
+ if (!PL_get_integer(t+0, &iEpisode))
+ goto close;
+
+ t2 = PL_new_term_refs(3);
+ if(!PL_put_integer(t2+0, iEpisode))
+ return;
+
+ r = PL_call_predicate(NULL, PL_Q_NORMAL,
+ PL_predicate("lookup_episode_local", 3, "episode_data"),
+ t2);
+
+ /* Format episode, name strings. */
+
+ tszName = NULL;
+ if (r && PL_get_atom_chars(t2+1, &szName)) {
+ tszName = TSZFromSZ(szName, CP_UTF8);
+ if (!tszName)
+ continue;
+ }
+
+ cb = 100;
+ tszEpisode = malloc(cb*sizeof(TCHAR));
+ if (!tszEpisode)
+ continue;
+ _stprintf_s(tszEpisode, cb, TEXT("%d"),
+ iEpisode);
+
+ /* Insert item. */
+
+ lviEpisode.mask = LVIF_TEXT|LVIF_STATE|LVIF_PARAM;
+ lviEpisode.iItem = i;
+ lviEpisode.iSubItem = 0;
+ lviEpisode.pszText = tszEpisode;
+ lviEpisode.lParam = iEpisode;
+ ListView_InsertItem(hListView, &lviEpisode);
+
+ if (tszName) {
+ lviName.iItem = i;
+ lviName.iSubItem = 1;
+ lviName.pszText = tszName;
+ ListView_SetItem(hListView, &lviName);
+ }
+
+ free(tszName);
+ free(tszEpisode);
+ }
+
+ if (g_SelectedItem != -1) {
+ ListView_SetItemState(hListView, g_SelectedItem,
+ LVIS_SELECTED, LVIS_SELECTED);
+ ListView_EnsureVisible(hListView, g_SelectedItem, TRUE);
+ }
+
+close:
+ PL_close_query(q);
+}
+
+int
+Watched(int iEpisode)
+{
+ term_t t;
+
+ t = PL_new_term_refs(1);
+ if (!PL_put_integer(t+0, iEpisode))
+ return 0;
+
+ return PL_call_predicate(NULL, PL_Q_NORMAL,
+ PL_predicate("watched", 1, "track_episodes"),
+ t);
+}