From 09ec144a99f1234a37d04854bdfb81485540be97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Ankarstr=C3=B6m?= Date: Tue, 15 Feb 2022 15:38:57 +0100 Subject: Put C code and Prolog code in separate directories. --- Makefile | 12 +- atom_dcg.pl | 11 - c/episode_browser.manifest | 30 +++ c/resource.h | 7 + c/resource.rc | 33 +++ c/win.c | 560 +++++++++++++++++++++++++++++++++++++++++++++ episode_browser.manifest | 30 --- episode_data.pl | 68 ------ local_episodes.pl | 31 --- mk.bat | 4 - pl/atom_dcg.pl | 11 + pl/episode_data.pl | 68 ++++++ pl/local_episodes.pl | 31 +++ pl/track_episodes.pl | 96 ++++++++ resource.h | 7 - resource.rc | 33 --- track_episodes.pl | 96 -------- win.c | 560 --------------------------------------------- 18 files changed, 842 insertions(+), 846 deletions(-) delete mode 100644 atom_dcg.pl create mode 100644 c/episode_browser.manifest create mode 100644 c/resource.h create mode 100644 c/resource.rc create mode 100644 c/win.c delete mode 100644 episode_browser.manifest delete mode 100644 episode_data.pl delete mode 100644 local_episodes.pl delete mode 100644 mk.bat create mode 100644 pl/atom_dcg.pl create mode 100644 pl/episode_data.pl create mode 100644 pl/local_episodes.pl create mode 100644 pl/track_episodes.pl delete mode 100644 resource.h delete mode 100644 resource.rc delete mode 100644 track_episodes.pl delete mode 100644 win.c diff --git a/Makefile b/Makefile index 21d5584..a8b8276 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) -INPUTS = $(PROJECT_ROOT)win.c +INPUTS = $(PROJECT_ROOT)c/win.c INPUTS += resource.obj -INPUTS += $(PROJECT_ROOT)track_episodes.pl -INPUTS += $(PROJECT_ROOT)local_episodes.pl -INPUTS += $(PROJECT_ROOT)episode_data.pl +INPUTS += $(PROJECT_ROOT)pl/track_episodes.pl +INPUTS += $(PROJECT_ROOT)pl/local_episodes.pl +INPUTS += $(PROJECT_ROOT)pl/episode_data.pl CFLAGS += -DUNICODE -D_UNICODE LDFLAGS += -lcomctl32 -lgdi32 -luxtheme @@ -28,8 +28,8 @@ episode_browser.exe: $(INPUTS) $(PROJECT_ROOT)Makefile swipl-ld -v $(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 +resource.obj: $(PROJECT_ROOT)c/resource.h $(PROJECT_ROOT)c/resource.rc + windres -i $(PROJECT_ROOT)c/resource.rc -o resource.obj #%.o: $(PROJECT_ROOT)%.c # $(CC) -c $(CFLAGS) -o $@ $< diff --git a/atom_dcg.pl b/atom_dcg.pl deleted file mode 100644 index 74689d5..0000000 --- a/atom_dcg.pl +++ /dev/null @@ -1,11 +0,0 @@ -:- module(atom_dcg, [atom_phrase/2]). - -:- meta_predicate atom_phrase(2, ?). - -atom_phrase(G, A) :- - ( var(A) - -> phrase(G, C), - atom_codes(A, C) - ; atom_codes(A, C), - phrase(G, C) - ). 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 +#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 +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/episode_browser.manifest b/episode_browser.manifest deleted file mode 100644 index 978f471..0000000 --- a/episode_browser.manifest +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/episode_data.pl b/episode_data.pl deleted file mode 100644 index 49dddd0..0000000 --- a/episode_data.pl +++ /dev/null @@ -1,68 +0,0 @@ -:- module(episode_data, [retract_episode/1, - lookup_episode/3, - lookup_episode_local/3, - lookup_episode_remote/3]). - -:- use_module(library(dcg/basics)). -:- use_module(library(http/http_open)). -:- use_module(library(sgml)). -:- use_module(library(xpath)). -:- use_module(library(persistency)). -:- use_module(atom_dcg). - -:- persistent episode_name_data(episode:integer, name:atom, data:list). - -attach :- - absolute_file_name('episode_data.db', F, [access(write)]), - db_attach(F, []). - -detach :- - db_detach. - -retract_episode(Ep) :- - ( episode_name_data(Ep, _, _) - -> retractall_episode_name_data(Ep, _, _) - ; true - ). - -padding(Ep) --> { Ep < 10 }, "00". -padding(Ep) --> { Ep >= 10, Ep < 100 }, "0". -padding(Ep) --> { Ep >= 100 }. - -episode_number(Ep) --> padding(Ep), integer(Ep). - -lookup_episode(Ep, Name, Data) :- lookup_episode_local(Ep, Name, Data), !. -lookup_episode(Ep, Name, Data) :- lookup_episode_remote(Ep, Name, Data). - -lookup_episode_local(Ep, Name, Data) :- - episode_name_data(Ep, Name, Data). - -lookup_episode_remote(Ep, Name, Data) :- - catch(http_load_html( - 'https://www.detectiveconanworld.com/wiki/Next_Conan%27s_Hint', - D), - _, - fail), - xpath(D, //tr, D1), - xpath(D1, td(index(1),text), T), - atom_phrase(episode_number(Ep), T), - xpath(D1, td(index(2),text), Name), - xpath(D1, td(index(3),text), Hint), - Data = [hint(Hint)], - assert_episode_name_data(Ep, Name, Data). - -http_load_html(URL, DOM) :- - setup_call_cleanup(http_open(URL, In, - [ timeout(60) - ]), - ( dtd(html, DTD), - load_structure(stream(In), - DOM, - [ dtd(DTD), - dialect(sgml), - shorttag(false), - max_errors(-1), - syntax_errors(quiet) - ]) - ), - close(In)). diff --git a/local_episodes.pl b/local_episodes.pl deleted file mode 100644 index 283ccb7..0000000 --- a/local_episodes.pl +++ /dev/null @@ -1,31 +0,0 @@ -:- module(local_episodes, [local_episode//1, - episode_file/2, - open_episode/1]). - -:- use_module(library(dcg/basics)). -:- use_module(atom_dcg). - -local_episode --> - local_episode(_). -local_episode(N) --> - string(_), "Detective_Conan_-_", integer(N), string(_). -local_episode(N) --> - string(_), "Detective_Conan_-_", - integer(First), "-", integer(Last), string(_), - { Second is First + 1, between(Second, Last, N) }. - -% Find episode on disk. - -episode_file(N, F) :- - expand_file_name('C:/Users/John/Nedladdningar/Detective Conan season 1 to season 22 + season 23(incomplete)/*/*.*', - F1), - ( nonvar(N) - -> include(atom_phrase(local_episode(N)), F1, [F|_]) - ; include(atom_phrase(local_episode), F1, F2), - member(F, F2), - atom_phrase(local_episode(N), F) - ). - -open_episode(N) :- - episode_file(N, F), - win_shell(open, F). diff --git a/mk.bat b/mk.bat deleted file mode 100644 index a737559..0000000 --- a/mk.bat +++ /dev/null @@ -1,4 +0,0 @@ -@REM -ld ld -ld-options,--subsystem,windows ^ -swipl-ld -v -g ^ --goal true -o build/default/episode_browser win.c ^ -track_episodes.pl local_episodes.pl episode_data.pl \ No newline at end of file diff --git a/pl/atom_dcg.pl b/pl/atom_dcg.pl new file mode 100644 index 0000000..74689d5 --- /dev/null +++ b/pl/atom_dcg.pl @@ -0,0 +1,11 @@ +:- module(atom_dcg, [atom_phrase/2]). + +:- meta_predicate atom_phrase(2, ?). + +atom_phrase(G, A) :- + ( var(A) + -> phrase(G, C), + atom_codes(A, C) + ; atom_codes(A, C), + phrase(G, C) + ). diff --git a/pl/episode_data.pl b/pl/episode_data.pl new file mode 100644 index 0000000..49dddd0 --- /dev/null +++ b/pl/episode_data.pl @@ -0,0 +1,68 @@ +:- module(episode_data, [retract_episode/1, + lookup_episode/3, + lookup_episode_local/3, + lookup_episode_remote/3]). + +:- use_module(library(dcg/basics)). +:- use_module(library(http/http_open)). +:- use_module(library(sgml)). +:- use_module(library(xpath)). +:- use_module(library(persistency)). +:- use_module(atom_dcg). + +:- persistent episode_name_data(episode:integer, name:atom, data:list). + +attach :- + absolute_file_name('episode_data.db', F, [access(write)]), + db_attach(F, []). + +detach :- + db_detach. + +retract_episode(Ep) :- + ( episode_name_data(Ep, _, _) + -> retractall_episode_name_data(Ep, _, _) + ; true + ). + +padding(Ep) --> { Ep < 10 }, "00". +padding(Ep) --> { Ep >= 10, Ep < 100 }, "0". +padding(Ep) --> { Ep >= 100 }. + +episode_number(Ep) --> padding(Ep), integer(Ep). + +lookup_episode(Ep, Name, Data) :- lookup_episode_local(Ep, Name, Data), !. +lookup_episode(Ep, Name, Data) :- lookup_episode_remote(Ep, Name, Data). + +lookup_episode_local(Ep, Name, Data) :- + episode_name_data(Ep, Name, Data). + +lookup_episode_remote(Ep, Name, Data) :- + catch(http_load_html( + 'https://www.detectiveconanworld.com/wiki/Next_Conan%27s_Hint', + D), + _, + fail), + xpath(D, //tr, D1), + xpath(D1, td(index(1),text), T), + atom_phrase(episode_number(Ep), T), + xpath(D1, td(index(2),text), Name), + xpath(D1, td(index(3),text), Hint), + Data = [hint(Hint)], + assert_episode_name_data(Ep, Name, Data). + +http_load_html(URL, DOM) :- + setup_call_cleanup(http_open(URL, In, + [ timeout(60) + ]), + ( dtd(html, DTD), + load_structure(stream(In), + DOM, + [ dtd(DTD), + dialect(sgml), + shorttag(false), + max_errors(-1), + syntax_errors(quiet) + ]) + ), + close(In)). diff --git a/pl/local_episodes.pl b/pl/local_episodes.pl new file mode 100644 index 0000000..283ccb7 --- /dev/null +++ b/pl/local_episodes.pl @@ -0,0 +1,31 @@ +:- module(local_episodes, [local_episode//1, + episode_file/2, + open_episode/1]). + +:- use_module(library(dcg/basics)). +:- use_module(atom_dcg). + +local_episode --> + local_episode(_). +local_episode(N) --> + string(_), "Detective_Conan_-_", integer(N), string(_). +local_episode(N) --> + string(_), "Detective_Conan_-_", + integer(First), "-", integer(Last), string(_), + { Second is First + 1, between(Second, Last, N) }. + +% Find episode on disk. + +episode_file(N, F) :- + expand_file_name('C:/Users/John/Nedladdningar/Detective Conan season 1 to season 22 + season 23(incomplete)/*/*.*', + F1), + ( nonvar(N) + -> include(atom_phrase(local_episode(N)), F1, [F|_]) + ; include(atom_phrase(local_episode), F1, F2), + member(F, F2), + atom_phrase(local_episode(N), F) + ). + +open_episode(N) :- + episode_file(N, F), + win_shell(open, F). diff --git a/pl/track_episodes.pl b/pl/track_episodes.pl new file mode 100644 index 0000000..ec1785b --- /dev/null +++ b/pl/track_episodes.pl @@ -0,0 +1,96 @@ +:- module(track_episodes, [update_tracked_episodes/0, + toggle_episode/1, + forget_episode/1, + most_recently_watched/1, + watched/1]). + +:- use_module(library(dcg/basics)). +:- use_module(library(registry)). +:- use_module(library(persistency)). +:- use_module(local_episodes). + +attach :- + absolute_file_name('track_episodes.db', F, [access(write)]), + db_attach(F, []). + +detach :- + db_detach. + +:- persistent episode_watched(episode:integer, watched:atom). + +% Helpers. + +swapbc(P, A, B, C, D) :- call(P, A, C, B, D). +compound(F, A, B, T) :- T =.. [F, A, B]. + +% Get data from registry. + +file(N) --> "File Name ", integer(N). + +pos(N) --> "File Position ", integer(N). + +mpc_reg(Phrase, Value) :- + phrase(Phrase, Name), + atom_codes(Name1, Name), + registry_get_key(current_user/software/'mpc-hc'/'mpc-hc'/settings, + Name1, Value). + +% Create episode-position list. + +epipos(L) :- epipos_x(0, L). + +epipos_x(N, L) :- + ( catch((mpc_reg(file(N), File), + mpc_reg(pos(N), Pos)), + _, + fail) + -> M is N + 1, + ( atom_codes(File, File1), + findall(Ep, phrase(local_episode(Ep), File1), Eps) + -> atom_codes(Pos, Pos1), + phrase(integer(Pos2), Pos1), + maplist(swapbc(compound, '-', Pos2), Eps, Eps1), + append(Eps1, T, L), + epipos_x(M, T) + ; epipos_x(M, L)) + ; L = [] + ). + +considered_watched(_-Pos) :- + Pos > 11000000000. + +% Update database. + +update_tracked_episodes :- + epipos(All), + include(considered_watched, All, Watched), + maplist(add, Watched). + +add(Ep-_) :- episode_watched(Ep, _), !. +add(Ep-_) :- assert_episode_watched(Ep, true). + +forget_episode(Ep) :- + retractall_episode_watched(Ep, true), + retractall_episode_watched(Ep, false). + +toggle_episode(Ep) :- + episode_watched(Ep, true), !, + retractall_episode_watched(Ep, true), + assert_episode_watched(Ep, false). + +toggle_episode(Ep) :- + episode_watched(Ep, false), !, + retractall_episode_watched(Ep, false), + assert_episode_watched(Ep, true). + +toggle_episode(Ep) :- + assert_episode_watched(Ep, true). + +% Utility. + +most_recently_watched(Ep) :- + findall(N, episode_watched(N, true), All), + sort(0, @>, All, [Ep|_]). + +watched(Ep) :- + episode_watched(Ep, true). diff --git a/resource.h b/resource.h deleted file mode 100644 index 8167760..0000000 --- a/resource.h +++ /dev/null @@ -1,7 +0,0 @@ -#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/resource.rc b/resource.rc deleted file mode 100644 index 36fa014..0000000 --- a/resource.rc +++ /dev/null @@ -1,33 +0,0 @@ -#include -#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/track_episodes.pl b/track_episodes.pl deleted file mode 100644 index ec1785b..0000000 --- a/track_episodes.pl +++ /dev/null @@ -1,96 +0,0 @@ -:- module(track_episodes, [update_tracked_episodes/0, - toggle_episode/1, - forget_episode/1, - most_recently_watched/1, - watched/1]). - -:- use_module(library(dcg/basics)). -:- use_module(library(registry)). -:- use_module(library(persistency)). -:- use_module(local_episodes). - -attach :- - absolute_file_name('track_episodes.db', F, [access(write)]), - db_attach(F, []). - -detach :- - db_detach. - -:- persistent episode_watched(episode:integer, watched:atom). - -% Helpers. - -swapbc(P, A, B, C, D) :- call(P, A, C, B, D). -compound(F, A, B, T) :- T =.. [F, A, B]. - -% Get data from registry. - -file(N) --> "File Name ", integer(N). - -pos(N) --> "File Position ", integer(N). - -mpc_reg(Phrase, Value) :- - phrase(Phrase, Name), - atom_codes(Name1, Name), - registry_get_key(current_user/software/'mpc-hc'/'mpc-hc'/settings, - Name1, Value). - -% Create episode-position list. - -epipos(L) :- epipos_x(0, L). - -epipos_x(N, L) :- - ( catch((mpc_reg(file(N), File), - mpc_reg(pos(N), Pos)), - _, - fail) - -> M is N + 1, - ( atom_codes(File, File1), - findall(Ep, phrase(local_episode(Ep), File1), Eps) - -> atom_codes(Pos, Pos1), - phrase(integer(Pos2), Pos1), - maplist(swapbc(compound, '-', Pos2), Eps, Eps1), - append(Eps1, T, L), - epipos_x(M, T) - ; epipos_x(M, L)) - ; L = [] - ). - -considered_watched(_-Pos) :- - Pos > 11000000000. - -% Update database. - -update_tracked_episodes :- - epipos(All), - include(considered_watched, All, Watched), - maplist(add, Watched). - -add(Ep-_) :- episode_watched(Ep, _), !. -add(Ep-_) :- assert_episode_watched(Ep, true). - -forget_episode(Ep) :- - retractall_episode_watched(Ep, true), - retractall_episode_watched(Ep, false). - -toggle_episode(Ep) :- - episode_watched(Ep, true), !, - retractall_episode_watched(Ep, true), - assert_episode_watched(Ep, false). - -toggle_episode(Ep) :- - episode_watched(Ep, false), !, - retractall_episode_watched(Ep, false), - assert_episode_watched(Ep, true). - -toggle_episode(Ep) :- - assert_episode_watched(Ep, true). - -% Utility. - -most_recently_watched(Ep) :- - findall(N, episode_watched(N, true), All), - sort(0, @>, All, [Ep|_]). - -watched(Ep) :- - episode_watched(Ep, true). diff --git a/win.c b/win.c deleted file mode 100644 index 6f82645..0000000 --- a/win.c +++ /dev/null @@ -1,560 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#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); -} -- cgit v1.2.3