aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile9
-rw-r--r--episode_browser.manifest30
-rw-r--r--resource.h6
-rw-r--r--resource.rc32
-rw-r--r--win.c386
5 files changed, 418 insertions, 45 deletions
diff --git a/Makefile b/Makefile
index bf04586..c5e8670 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,14 @@
PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
INPUTS = $(PROJECT_ROOT)win.c
+INPUTS += resource.obj
INPUTS += $(PROJECT_ROOT)track_episodes.pl
INPUTS += $(PROJECT_ROOT)local_episodes.pl
INPUTS += $(PROJECT_ROOT)episode_data.pl
+CFLAGS += -v -cc-options,-mwindows,-municode -DUNICODE -D_UNICODE
+LDFLAGS += -lcomctl32 -lgdi32
+
ifeq ($(BUILD_MODE),debug)
CFLAGS += -g
else ifeq ($(BUILD_MODE),run)
@@ -21,9 +25,12 @@ endif
all: episode_browser.exe
episode_browser.exe: $(INPUTS)
- swipl-ld -v $(CFLAGS) $(LDFLAGS) -goal true -o $@ $(INPUTS)
+ swipl-ld $(CFLAGS) $(LDFLAGS) -goal true -o $@ $(INPUTS)
$(EXTRA_CMDS)
+resource.obj: $(PROJECT_ROOT)resource.h $(PROJECT_ROOT)resource.rc
+ windres -i $(PROJECT_ROOT)resource.rc -o resource.obj
+
#%.o: $(PROJECT_ROOT)%.c
# $(CC) -c $(CFLAGS) -o $@ $<
diff --git a/episode_browser.manifest b/episode_browser.manifest
new file mode 100644
index 0000000..978f471
--- /dev/null
+++ b/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/resource.h b/resource.h
new file mode 100644
index 0000000..6d3b85c
--- /dev/null
+++ b/resource.h
@@ -0,0 +1,6 @@
+#define IDR_MENU 101
+#define IDD_ABOUT 201
+#define IDC_ABOUTTEXT 301
+#define IDC_LISTVIEW 302
+#define ID_FILE_EXIT 4001
+#define ID_FILE_ABOUT 4002
diff --git a/resource.rc b/resource.rc
new file mode 100644
index 0000000..9f8af6e
--- /dev/null
+++ b/resource.rc
@@ -0,0 +1,32 @@
+#include <windows.h>
+#include "resource.h"
+
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "episode_browser.manifest"
+
+IDR_MENU MENU
+BEGIN
+ POPUP "&File"
+ BEGIN
+ 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/win.c b/win.c
index 660275d..8764092 100644
--- a/win.c
+++ b/win.c
@@ -1,15 +1,25 @@
#include <windows.h>
-#include <SWI-Prolog.h>
+#include <TCHAR.H>
+#include <commctrl.h>
#include <stdio.h>
+#include <string.h>
+#include <SWI-Prolog.h>
+#include "resource.h"
+
+#define CLASSNAME TEXT("Episode Browser")
+
+HFONT g_GUIFont;
static int Attach(void);
-static void ShowEpisode(int);
-static void UpdateList(void);
+static INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
+static HFONT GetGUIFont();
+static void UpdateName(HWND, int);
+static void UpdateList(HWND);
+static void ShowEpisode(HWND, int);
+static TCHAR *TSZFromSZ(int, char *);
+static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
/*
-int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
- PSTR lpCmdLine, INT nCmdShow)
-*/
int main(int argc, char *argv[])
{
char *rgArgs[2];
@@ -22,13 +32,189 @@ int main(int argc, char *argv[])
Attach();
//UpdateList();
- ShowEpisode(400);
+ //ShowEpisode(400);
PL_halt(0);
}
+*/
+
+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);
+
+ g_GUIFont = GetGUIFont();
+
+ 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_ABOUT:
+ DialogBox(
+ GetModuleHandle(NULL),
+ MAKEINTRESOURCE(IDD_ABOUT),
+ hwnd,
+ AboutDlgProc
+ );
+ break;
+ }
+ break;
+ case WM_CREATE:
+ {
+ HWND hListView;
+ LVCOLUMN lvc;
+
+ hListView = CreateWindowEx(
+ 0,
+ WC_LISTVIEW,
+ TEXT(""),
+ WS_CHILD|WS_VISIBLE|WS_VSCROLL|LVS_REPORT,
+ 0, 0, 100, 100,
+ hwnd,
+ (HMENU)IDC_LISTVIEW,
+ GetModuleHandle(NULL),
+ NULL
+ );
+
+ SendMessage(hListView,LVM_SETEXTENDEDLISTVIEWSTYLE,
+ 0, LVS_EX_FULLROWSELECT);
+
+ SendMessage(hListView, WM_SETFONT,
+ (WPARAM)g_GUIFont, MAKELPARAM(FALSE, 0));
+
+ lvc.mask = LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;
+
+ lvc.iSubItem = 0;
+ lvc.pszText = TEXT("#");
+ lvc.cx = 40;
+ ListView_InsertColumn(hListView, 0, &lvc);
+
+ lvc.iSubItem = 1;
+ lvc.pszText = TEXT("Title");
+ lvc.cx = 500;
+ ListView_InsertColumn(hListView, 1, &lvc);
+
+ UpdateList(hListView);
+ }
+ break;
+ case WM_SIZE:
+ {
+ HWND hListView;
+ RECT rc;
+
+ GetClientRect(hwnd, &rc);
+ hListView = GetDlgItem(hwnd, IDC_LISTVIEW);
+ MoveWindow(hListView, 0, 0,
+ rc.right, rc.bottom,
+ TRUE);
+ }
+ 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;
+}
/* Attach persistent databases. */
-int Attach()
+int
+Attach()
{
int r;
term_t t;
@@ -47,53 +233,40 @@ int Attach()
return 1;
}
-/* Update episode list. */
-void UpdateList()
+HFONT
+GetGUIFont()
{
- term_t t;
- qid_t q;
-
- t = PL_new_term_refs(2);
- PL_call_predicate(NULL, PL_Q_NORMAL,
- PL_predicate("update_tracked_episodes", 0, "track_episode"),
- t);
-
- q = PL_open_query(NULL, PL_Q_NORMAL,
- PL_predicate("episode_file", 2, "local_episodes"),
- t);
-
- while (PL_next_solution(q)) {
- char *sFile;
- int nEpisode;
-
- if (!PL_get_integer(t+0, &nEpisode))
- goto close;
- if (!PL_get_atom_chars(t+1, &sFile))
- goto close;
- printf("%d: %s\n", nEpisode, sFile);
- }
+#ifndef WIN9X
+ NONCLIENTMETRICS m;
-close:
- PL_close_query(q); /* Or PL_cut_query? */
+ m.cbSize = sizeof(NONCLIENTMETRICS);
+ SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS),
+ &m, 0);
+ return CreateFontIndirect(&m.lfMessageFont);
+#else
+ return GetStockObject(DEFAULT_GUI_FONT);
+#endif
}
/* Show episode data. */
-void ShowEpisode(int nEpisode)
+void
+ShowEpisode(HWND hwnd, int iEpisode)
{
int r;
term_t t;
t = PL_new_term_refs(3);
- if(!PL_put_integer(t+0, nEpisode))
+ if(!PL_put_integer(t+0, iEpisode))
return;
r = PL_call_predicate(NULL, PL_Q_NORMAL,
- PL_predicate("lookup_episode_local", 3, "episode_data"),
+ PL_predicate("lookup_episode_local", 3, "episode_data"),
t);
- if (!r) return;
+ if (!r)
+ return;
/* The episode data is a list of unary compounds,
- * whose name is the key and whose value is the value. */
+ * whose functor is the key and whose argument is the value. */
{
term_t tHead, tList;
@@ -103,20 +276,145 @@ void ShowEpisode(int nEpisode)
while (PL_get_list(tList, tHead, tList)) {
atom_t aKey;
- const char *sKey;
- char *sValue;
+ 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;
- sKey = PL_atom_chars(aKey);
- if(!PL_get_atom_chars(tValue, &sValue))
+ if (!PL_get_atom_chars(tValue, &szValue))
continue;
- printf("%s/%d: %s\n", sKey, iArity, sValue);
+ printf("%s/%d: %s\n", szKey, iArity, szValue);
}
}
}
+
+TCHAR *
+TSZFromSZ(int iCp, char *sz)
+{
+ TCHAR *tsz;
+
+#ifdef UNICODE
+ int cb;
+
+ cb = MultiByteToWideChar(iCp, 0, sz, -1, NULL, 0);
+ tsz = malloc(cb*sizeof(wchar_t));
+ if (!tsz)
+ return NULL;
+ if (!MultiByteToWideChar(iCp, 0, sz, -1, tsz, cb))
+ return NULL;
+#else
+ tsz = malloc(strlen(sz)+1);
+ if (!tsz)
+ return NULL;
+ strcpy(tsz, sz);
+#endif
+
+ return tsz;
+}
+
+/* Update episode name. */
+void
+UpdateName(HWND hwnd, int iEpisode)
+{
+ char *szName;
+ term_t t;
+
+ t = PL_new_term_refs(3);
+ if (!PL_put_integer(t+0, iEpisode))
+ 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;
+
+ printf("%s\n", szName);
+}
+
+/* Update episode list. */
+void
+UpdateList(HWND hListView)
+{
+ LVITEM lviEpisode, lviName;
+ qid_t q;
+ term_t t;
+
+ lviEpisode.mask = LVIF_TEXT | LVIF_STATE | LVIF_PARAM;
+ lviEpisode.state = 0;
+ lviEpisode.stateMask = 0;
+
+ lviName.mask = LVIF_TEXT;
+
+ t = PL_new_term_refs(2);
+ PL_call_predicate(NULL, PL_Q_NORMAL,
+ PL_predicate("update_tracked_episodes", 0, "track_episode"),
+ 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(CP_UTF8, szName);
+ 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);
+ }
+ }
+
+close:
+ PL_close_query(q);
+}