From 295d423cc47f9ee8a72134dc544892a03b279311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Ankarstr=C3=B6m?= Date: Sun, 10 Jul 2022 23:23:09 +0200 Subject: 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++. --- .dir-locals.el | 4 +- .gitignore | 1 + Makefile | 8 +- README | 12 +- c/common.c | 29 --- c/common.cpp | 29 +++ c/datalistview.c | 89 -------- c/datalistview.cpp | 85 ++++++++ c/defs.h | 71 +++--- c/episodelistview.c | 435 ------------------------------------ c/episodelistview.cpp | 460 +++++++++++++++++++++++++++++++++++++++ c/listview.c | 78 ------- c/listview.cpp | 76 +++++++ c/main.c | 581 ------------------------------------------------- c/main.cpp | 593 ++++++++++++++++++++++++++++++++++++++++++++++++++ c/pl.c | 141 ------------ c/pl.cpp | 141 ++++++++++++ 17 files changed, 1441 insertions(+), 1392 deletions(-) delete mode 100644 c/common.c create mode 100644 c/common.cpp delete mode 100644 c/datalistview.c create mode 100644 c/datalistview.cpp delete mode 100644 c/episodelistview.c create mode 100644 c/episodelistview.cpp delete mode 100644 c/listview.c create mode 100644 c/listview.cpp delete mode 100644 c/main.c create mode 100644 c/main.cpp delete mode 100644 c/pl.c create mode 100644 c/pl.cpp diff --git a/.dir-locals.el b/.dir-locals.el index 62e25f6..6c6dcc0 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1,2 +1,4 @@ ((c-mode . ((flycheck-gcc-include-path . ("C:/Program Files/swipl/include")) - (flycheck-gcc-definitions . ("UNICODE" "_UNICODE"))))) + (flycheck-gcc-definitions . ("UNICODE" "_UNICODE")))) + (c++-mode . ((flycheck-gcc-include-path . ("C:/Program Files/swipl/include")) + (flycheck-gcc-definitions . ("UNICODE" "_UNICODE"))))) diff --git a/.gitignore b/.gitignore index 9270add..44b07c6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /misc/ TAGS *~ +*.db diff --git a/Makefile b/Makefile index 405e8c6..99b5f28 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ B = build/default/ -C = c/main.c +C = c/main.cpp PL = pl/cfg.pl pl/episode_data.pl pl/local_episodes.pl pl/track_episodes.pl OBJ = $(B)common.obj $(B)datalistview.obj $(B)episodelistview.obj $(B)listview.obj $(B)pl.obj $(B)resource.obj @@ -18,11 +18,11 @@ $(B)EpisodeBrowser.exe: $(C) $(OBJ) $(PL) c/defs.h Makefile $(B)resource.obj: c/resource.h c/resource.rc c/application.manifest windres -i c/resource.rc -o $(B)resource.obj -$(B)%.obj: c/%.c c/defs.h +$(B)%.obj: c/%.cpp c/*.h $(CC) -c $(CFLAGS) -o $@ $< -TAGS: c/*.c c/*.h pl/*.pl - etags c/*.c c/*.h -lprolog pl/*.pl +TAGS: c/*.cpp c/*.h pl/*.pl + etags c/*.cpp c/*.h -lprolog pl/*.pl clean: rm -fr $(B)EpisodeBrowser.exe $(B)*.obj diff --git a/README b/README index d6ceca7..489103e 100644 --- a/README +++ b/README @@ -28,16 +28,16 @@ modified. Frontend ~~~~~~~~ -The graphical interface is implemented in C using the Win32 API (see +The graphical interface is implemented in C++ using the Win32 API (see the c folder). The source code is spread across a small number of files. For an overview of the functions provided by each file, see defs.h. In summary: -main.c Entry point and initialization. Contains the main event handler. -listview.c Creates and handles the shared aspects of the two list views. -episodelistview.c Defines the interface to the episode list view. -datalistview.c Defines the interface to the data list view. -common.c Some useful functions that don't fit elsewhere. +main.cpp Entry point and initialization. Contains the main event handler. +listview.cpp Creates and handles the shared aspects of the two list views. +episodelistview.cpp Defines the interface to the episode list view. +datalistview.cpp Defines the interface to the data list view. +common.cpp Some useful functions that don't fit elsewhere. The frontend interacts with the Prolog backend via the SWI-Prolog API. Note, however, that most interaction is done through ten or so macros, diff --git a/c/common.c b/c/common.c deleted file mode 100644 index a056993..0000000 --- a/c/common.c +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include - -#include "resource.h" -#include "defs.h" - -/* Convert normal string to TSTR using given codepage. */ -TCHAR * -TszFromSz(const 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; -} diff --git a/c/common.cpp b/c/common.cpp new file mode 100644 index 0000000..63adb53 --- /dev/null +++ b/c/common.cpp @@ -0,0 +1,29 @@ +#include +#include + +#include "resource.h" +#include "defs.h" + +/* Convert normal string to TSTR using given codepage. */ +TCHAR * +TszFromSz(const char *sz, int iCp) +{ + TCHAR *tsz; + +#ifdef UNICODE + int cbMultiByte, cchWideChar; + + cbMultiByte = strlen(sz)+1; + cchWideChar = MultiByteToWideChar(iCp, 0, sz, cbMultiByte, NULL, 0); + tsz = (TCHAR *)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; +} diff --git a/c/datalistview.c b/c/datalistview.c deleted file mode 100644 index fbb46d0..0000000 --- a/c/datalistview.c +++ /dev/null @@ -1,89 +0,0 @@ -#include -#include -#include -#include - -#include "resource.h" -#include "defs.h" - -HWND HDlv; - -HWND -DlvCreate() -{ - LVCOLUMN lvc; - - HDlv = LvCreate((HMENU)IDC_DATALISTVIEW, LVS_NOCOLUMNHEADER); - - lvc.mask = LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM; - lvc.iSubItem = DLVSIKEY; - lvc.pszText = TEXT("Key"); - - lvc.cx = Dpi(42); - ListView_InsertColumn(HDlv, DLVSIKEY, &lvc); - - lvc.iSubItem = DLVSIVALUE; - lvc.pszText = TEXT("Value"); - lvc.cx = 500; - ListView_InsertColumn(HDlv, DLVSIVALUE, &lvc); - - return HDlv; -} - -/* Show episode data. */ -void -DlvShowEpisode(int iEpisode) -{ - int i, iItem; - LVFINDINFO lvfi; - LVITEM lviKey, lviValue; - term_t t; - qid_t q; - extern HWND HElv; - - ListView_DeleteAllItems(HDlv); - - lviKey.mask = LVIF_TEXT; - lviValue.mask = LVIF_TEXT; - - t = PL_new_term_refs(3); - if (!Plp(t,"I",iEpisode)) return; - q = PL_open_query(NULL, PL_Q_NORMAL, - PL_predicate("episode_datum", 3, "episode_data"), t); - - for (i = 0; PL_next_solution(q); i++) { - char *szKey; - char *szValue; - TCHAR *tszKey, *tszValue; - - if (!Plg(t+1,"ss",&szKey,&szValue)) continue; - - tszKey = TszFromSz(szKey, CP_UTF8); - if (!tszKey) continue; - tszValue = TszFromSz(szValue, CP_UTF8); - if (!tszValue) goto c; - - lviKey.mask = LVIF_TEXT; - lviKey.iItem = i; - lviKey.iSubItem = 0; - lviKey.pszText = tszKey; - ListView_InsertItem(HDlv, &lviKey); - - lviValue.iItem = i; - lviValue.iSubItem = 1; - lviValue.pszText = tszValue; - ListView_SetItem(HDlv, &lviValue); - - free(tszValue); - c: free(tszKey); - } - - PL_cut_query(q); - UpdateLayout(); - - lvfi.flags = LVFI_PARAM; - lvfi.lParam = iEpisode; - iItem = ListView_FindItem(HElv, -1, &lvfi); - if (iItem != -1) - ListView_EnsureVisible(HElv, iItem, TRUE); -} diff --git a/c/datalistview.cpp b/c/datalistview.cpp new file mode 100644 index 0000000..3269dab --- /dev/null +++ b/c/datalistview.cpp @@ -0,0 +1,85 @@ +#include +#include +#include +#include + +#include "resource.h" +#include "defs.h" + +extern EpisodeListView g_elv; + +void +DataListView::Create() +{ + LVCOLUMN lvc; + + ListView::Create((HMENU)IDC_DATALISTVIEW, LVS_NOCOLUMNHEADER); + + lvc.mask = LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM; + lvc.iSubItem = DLVSIKEY; + lvc.pszText = TEXT("Key"); + + lvc.cx = Dpi(42); + ListView_InsertColumn(m_hWnd, DLVSIKEY, &lvc); + + lvc.iSubItem = DLVSIVALUE; + lvc.pszText = TEXT("Value"); + lvc.cx = 500; + ListView_InsertColumn(m_hWnd, DLVSIVALUE, &lvc); +} + +void +DataListView::ShowEpisode(int iEpisode) +{ + int i, iItem; + LVFINDINFO lvfi; + LVITEM lviKey, lviValue; + term_t t; + qid_t q; + + ListView_DeleteAllItems(m_hWnd); + + lviKey.mask = LVIF_TEXT; + lviValue.mask = LVIF_TEXT; + + t = PL_new_term_refs(3); + if (!Plp(t,"I",iEpisode)) return; + q = PL_open_query(NULL, PL_Q_NORMAL, + PL_predicate("episode_datum", 3, "episode_data"), t); + + for (i = 0; PL_next_solution(q); i++) { + char *szKey; + char *szValue; + TCHAR *tszKey, *tszValue; + + if (!Plg(t+1,"ss",&szKey,&szValue)) continue; + + tszKey = TszFromSz(szKey, CP_UTF8); + if (!tszKey) continue; + tszValue = TszFromSz(szValue, CP_UTF8); + if (!tszValue) goto c; + + lviKey.mask = LVIF_TEXT; + lviKey.iItem = i; + lviKey.iSubItem = 0; + lviKey.pszText = tszKey; + ListView_InsertItem(m_hWnd, &lviKey); + + lviValue.iItem = i; + lviValue.iSubItem = 1; + lviValue.pszText = tszValue; + ListView_SetItem(m_hWnd, &lviValue); + + free(tszValue); + c: free(tszKey); + } + + PL_cut_query(q); + UpdateLayout(); + + lvfi.flags = LVFI_PARAM; + lvfi.lParam = iEpisode; + iItem = ListView_FindItem(g_elv.HWnd(), -1, &lvfi); + if (iItem != -1) + ListView_EnsureVisible(g_elv.HWnd(), iItem, TRUE); +} diff --git a/c/defs.h b/c/defs.h index 8196a93..9f53c92 100644 --- a/c/defs.h +++ b/c/defs.h @@ -5,36 +5,51 @@ #include #include -/* common.c */ +/* common.cpp */ TCHAR *TszFromSz(const char *, int); -/* main.c */ +/* main.cpp */ void UpdateLayout(); -/* listview.c */ -HWND LvCreate(HMENU, DWORD); -int LvHeight(HWND, int); - -/* episodelistview.c */ -HWND ElvCreate(); -void ElvDoSort(void); -LRESULT ElvHandleNotify(LPARAM); -void ElvRedraw(void); -void ElvSetTop(int); -void ElvSelectFocus(void); -void ElvSelectUnwatched(int); -void ElvShowFocus(void); -void ElvUpdate(void); -void ElvUpdateItem(LPLVITEM); - -/* datalistview.c */ -HWND DlvCreate(); -void DlvShowEpisode(int); - -/* pl.c */ -int Pl(char *, char *, char *, ...); -int Plp(term_t, char *, ...); -int Plg(term_t, char *, ...); +/* listview.cpp */ +class ListView { +protected: + WNDPROC m_prevProc; + HWND m_hWnd; +public: + void Create(HMENU, DWORD); + int Height(int); + HWND HWnd(void); + virtual LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +}; + +/* episodelistview.cpp */ +class EpisodeListView: public ListView { +public: + void Create(void); + void DoSort(void); + LRESULT HandleNotify(LPARAM); + void Redraw(void); + void SetTop(int); + void SelectFocus(void); + void SelectUnwatched(int); + void ShowFocus(void); + void Update(void); + void UpdateItem(LPLVITEM); + LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +}; + +/* datalistview.cpp */ +class DataListView: public ListView { +public: + void Create(void); + void ShowEpisode(int); +}; + +/* pl.cpp */ +int Pl(const char *, const char *, const char *, ...); +int Plp(term_t, const char *, ...); +int Plg(term_t, const char *, ...); /* defs.h */ #define DLVSIKEY 0 @@ -56,8 +71,8 @@ Cmp(int a, int b) inline int Dpi(int i) { - extern int IDPI; - return MulDiv(i, IDPI, 96); + extern int g_iDPI; + return MulDiv(i, g_iDPI, 96); } #endif diff --git a/c/episodelistview.c b/c/episodelistview.c deleted file mode 100644 index a1973f8..0000000 --- a/c/episodelistview.c +++ /dev/null @@ -1,435 +0,0 @@ -#include -#include -#include -#include - -#include "resource.h" -#include "defs.h" - -HWND HElv; -int ISort; -LVITEM LviElvFocus; /* Focused episode. */ -static int CALLBACK ElvSort(LPARAM, LPARAM, LPARAM); - -HWND -ElvCreate() -{ - LVCOLUMN lvc; - - HElv = LvCreate((HMENU)IDC_EPISODELISTVIEW, 0); - - lvc.mask = LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM; - lvc.iSubItem = ELVSIEPISODE; - lvc.pszText = TEXT("#"); - lvc.cx = Dpi(42); - ListView_InsertColumn(HElv, ELVSIEPISODE, &lvc); - - lvc.iSubItem = ELVSITITLE; - lvc.pszText = TEXT("Title"); - lvc.cx = 500; - ListView_InsertColumn(HElv, ELVSITITLE, &lvc); - - lvc.iSubItem = ELVSIRATING; - lvc.pszText = TEXT("/"); - lvc.cx = Dpi(30); - ListView_InsertColumn(HElv, ELVSIRATING, &lvc); - - if (!Pl("cfg","get_sort","i",&ISort)) - ISort = 1; - - return HElv; -} - -void -ElvDoSort() -{ - ListView_SortItemsEx(HElv, ElvSort, ISort); -} - -LRESULT -ElvHandleNotify(LPARAM lParam) -{ - LPNMLISTVIEW lpNmLv; - lpNmLv = (LPNMLISTVIEW)lParam; - - switch (lpNmLv->hdr.code) { - case LVN_ITEMCHANGED: /* Select/focus episode. */ - if ((lpNmLv->uChanged & LVIF_STATE) && - (lpNmLv->uNewState & LVIS_FOCUSED)) { - LviElvFocus.iItem = lpNmLv->iItem; - LviElvFocus.lParam = lpNmLv->lParam; - ElvUpdateItem(&LviElvFocus); - DlvShowEpisode(lpNmLv->lParam); - } - break; - case LVN_COLUMNCLICK: /* Sort by column. */ - { - int iColumn; - iColumn = lpNmLv->iSubItem+1; - ISort = abs(ISort) == iColumn? -ISort: iColumn; - Pl("cfg","set_sort","I",ISort); - s: ElvDoSort(); - ElvShowFocus(); - break; - } - case LVN_KEYDOWN: /* Navigate episodes by keyboard. */ - { - LPNMLVKEYDOWN lpNmLvKd; - lpNmLvKd = (LPNMLVKEYDOWN)lParam; - switch (lpNmLvKd->wVKey) { - case VK_LEFT: - ElvSelectUnwatched(-1); - break; - case VK_RIGHT: - ElvSelectUnwatched(1); - break; - } - break; - } - case NM_CUSTOMDRAW: /* Make unwatched episodes bold. */ - { - LPNMLVCUSTOMDRAW lpLvCd; - lpLvCd = (LPNMLVCUSTOMDRAW)lParam; - switch (lpLvCd->nmcd.dwDrawStage) { - case CDDS_PREPAINT: - return CDRF_NOTIFYITEMDRAW; - break; - case CDDS_ITEMPREPAINT: - { - extern HFONT HfBold; - if (!Pl("track_episodes","watched","I",lpLvCd->nmcd.lItemlParam)) { - SelectObject(lpLvCd->nmcd.hdc, HfBold); - return CDRF_NEWFONT; - } - break; - } - } - break; - } - case NM_DBLCLK: /* Open clicked episode. */ - { - if (!Pl("local_episodes","open_episode_locally","I", - LviElvFocus.lParam)) - Pl("local_episodes","open_episode_online","I", - LviElvFocus.lParam); - break; - } - case NM_RETURN: /* Open all selected episodes. */ - { - LVITEM lvi; - extern HWND HElv; - 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; - if (!Pl("local_episodes","open_episode_locally","I", - lvi.lParam)) - Pl("local_episodes","open_episode_online","I", - lvi.lParam); - } - b: break; - } - case NM_RCLICK: - { - DWORD dwPos; - extern HWND HWnd; - extern HMENU HPopupMenu; - dwPos = GetMessagePos(); - TrackPopupMenu(HPopupMenu, TPM_RIGHTBUTTON, - LOWORD(dwPos), HIWORD(dwPos), 0, - HWnd, NULL); - break; - } - } - - return 0; -} - -void -ElvRedraw() -{ - RedrawWindow(HElv, NULL, NULL, - RDW_ERASE|RDW_FRAME|RDW_INVALIDATE|RDW_ALLCHILDREN); -} - -void -ElvSetTop(int iItem) -{ - int iLast; - iLast = ListView_GetItemCount(HElv)-1; - ListView_EnsureVisible(HElv, iLast, TRUE); - ListView_EnsureVisible(HElv, iItem, TRUE); -} - -/* Select previously focused episode. */ -void -ElvSelectFocus() -{ - int i, iEpisode, iItem; - LVFINDINFO lvfi; - - iItem = 0; - if (!Pl("cfg","get_focus","i",&iEpisode)) return; - - lvfi.flags = LVFI_PARAM; - lvfi.lParam = iEpisode; - i = 0; - while ((iItem = ListView_FindItem(HElv, -1, &lvfi)) == -1 && i++ < 100) - lvfi.lParam = ++iEpisode; - if (iItem != -1) goto s; - - iEpisode -= 100; - lvfi.lParam = iEpisode; - i = 0; - while ((iItem = ListView_FindItem(HElv, -1, &lvfi)) == -1 && i++ < 100) - lvfi.lParam = --iEpisode; - if (iItem != -1) goto s; - return; - -s: ListView_SetItemState(HElv, -1, LVIF_STATE, LVIS_SELECTED); - ElvSetTop(iItem > 5? iItem-5: 0); - ListView_SetItemState(HElv, iItem, - LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED); - LviElvFocus.iItem = iItem; - LviElvFocus.lParam = iEpisode; - ElvUpdateItem(&LviElvFocus); - DlvShowEpisode(iEpisode); -} - -/* Select next/previous unwatched episode. */ -void -ElvSelectUnwatched(int iDir) -{ - int i, iEpNew, iItemNew; - LVFINDINFO lvfi; - LVITEM lviFocus; - - /* Get focused episode. */ - lviFocus.mask = LVIF_PARAM; - if ((lviFocus.iItem = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED)) != -1 - && ListView_GetItem(HElv, &lviFocus)) - ; - else - return; - - i = 0; - lvfi.flags = LVFI_PARAM; - lvfi.lParam = lviFocus.lParam; - - do { - if (!Pl("track_episodes",iDir > 0? "next_unwatched": "previous_unwatched", - "Ii",lvfi.lParam,&iEpNew)) - return; - - lvfi.lParam = iEpNew; - if ((iItemNew = ListView_FindItem(HElv, -1, &lvfi)) != -1) { - ListView_SetItemState(HElv,-1,LVIF_STATE,LVIS_SELECTED); - ListView_SetSelectionMark(HElv, iItemNew); - ListView_SetItemState(HElv, iItemNew, - LVIS_SELECTED|LVIS_FOCUSED, - LVIS_SELECTED|LVIS_FOCUSED); - ElvRedraw(); - ListView_EnsureVisible(HElv, iItemNew, TRUE); - return; - } - } while (i++ < 1000); -} - -void -ElvShowFocus() -{ - int iEpFocus; - iEpFocus = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED); - if (iEpFocus == -1) return; - ListView_EnsureVisible(HElv, iEpFocus, TRUE); -} - -/* Sort list view items, iSort being the 1-based index of the column - * to sort by. If iSort is negative, the order is descending. */ -int CALLBACK -ElvSort(LPARAM iItem1, LPARAM iItem2, LPARAM iSort) -{ - int iOrder; - LVITEM lvi1, lvi2; - lvi1.mask = lvi2.mask = LVIF_PARAM; - lvi1.iItem = iItem1; lvi2.iItem = iItem2; - if (!ListView_GetItem(HElv, &lvi1)) return 0; - if (!ListView_GetItem(HElv, &lvi2)) return 0; - iOrder = Cmp(iSort, 0); - switch (abs(iSort)-1) { - case ELVSIEPISODE: - return iOrder*Cmp(lvi1.lParam, lvi2.lParam); - break; - case ELVSIRATING: - { - int iRating1, iRating2; - iRating1 = iSort > 0? 99: -1; - iRating2 = iSort > 0? 99: -1; - Pl("episode_data","episode_rating","Ii",lvi1.lParam,&iRating1); - Pl("episode_data","episode_rating","Ii",lvi2.lParam,&iRating2); - if (iRating1 == iRating2) - return Cmp(lvi1.lParam, lvi2.lParam); - return iOrder*Cmp(iRating1, iRating2); - break; - } - case ELVSITITLE: - { - char *sz1, *sz2; - int cch, cch1, cch2; - if (!Pl("episode_data","episode_title","Is",lvi1.lParam,&sz1)) - return 0; - if (!Pl("episode_data","episode_title","Is",lvi2.lParam,&sz2)) - return 0; - cch1 = strlen(sz1); - cch2 = strlen(sz2); - cch = cch1 > cch2? cch2: cch1; - return iOrder*_strnicmp(sz1, sz2, cch); - break; - } - default: - return 0; - } -} - -/* Update episode list. */ -void -ElvUpdate() -{ - int cEp, i, iEp, iEpFocus, iItem, iItemMark, iItemTopNew; - LVITEM lvi, lviEpisode, lviTop; - LVFINDINFO lvfi; - extern HWND HWndStatus; - static TCHAR tszDisp[16], tszEpisode[16], tszTotal[16]; - static int aEpSel[2048]; - - lviEpisode.mask = LVIF_TEXT|LVIF_PARAM; - - /* Save scrolling position. */ - lviTop.iItem = ListView_GetTopIndex(HElv); - lviTop.mask = LVIF_PARAM; - ListView_GetItem(HElv, &lviTop); - - /* Save selected episodes. */ - i = 0; - lvi.mask = LVIF_PARAM; - lvi.iItem = -1; - while ((lvi.iItem = ListView_GetNextItem( - HElv, lvi.iItem, LVNI_SELECTED)) != -1 - && i < 2048) - if (ListView_GetItem(HElv, &lvi)) - aEpSel[i++] = lvi.lParam; - aEpSel[i] = 0; - iItemMark = ListView_GetSelectionMark(HElv); - - /* Save focus. */ - iEpFocus = 0; - if ((lvi.iItem = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED)) != -1 - && ListView_GetItem(HElv, &lvi)) - iEpFocus = lvi.lParam; - - SendMessage(HElv, WM_SETREDRAW, FALSE, 0); - ListView_DeleteAllItems(HElv); - - if (!Pl("episode_data","ensure_episode_data","")) return; - if (!Pl("episode_data","episode_count","i",&cEp)) return; - - for (iEp = 1, iItem = 0; iEp <= cEp; iEp++) { - extern char SzLimitScreenwriter[]; - extern int BViewTVOriginal, BViewWatched; - - if (SzLimitScreenwriter[0]) - if (!Pl("episode_data","episode_datum","ISS", - iEp,"Screenwriter",SzLimitScreenwriter)) - continue; - - if (!BViewWatched) - if (Pl("track_episodes","watched","I",iEp)) continue; - - if (!BViewTVOriginal) - if (Pl("episode_data","tv_original","I",iEp)) continue; - - /* Format episode number string. */ - _stprintf_s(tszEpisode, sizeof(tszEpisode), TEXT("%d"), iEp); - - /* Insert item. */ - lviEpisode.iItem = iItem++; - lviEpisode.iSubItem = ELVSIEPISODE; - lviEpisode.pszText = tszEpisode; - lviEpisode.lParam = iEp; - ListView_InsertItem(HElv, &lviEpisode); - ElvUpdateItem(&lviEpisode); - } - - ElvDoSort(); - - lvfi.flags = LVFI_PARAM; - - /* Reset selection. */ - for (i = 0; aEpSel[i]; i++) { - int iItemSel; - lvfi.lParam = aEpSel[i]; - if ((iItemSel = ListView_FindItem(HElv, -1, &lvfi)) != -1) - ListView_SetItemState(HElv, iItemSel, - LVIS_SELECTED, LVIS_SELECTED); - } - if (iItemMark != -1) - ListView_SetSelectionMark(HElv, iItemMark); - - /* Reset focus. */ - if (iEpFocus) { - int iItemFocus; - i = 0; - do - lvfi.lParam = iEpFocus+i; - while ((iItemFocus = ListView_FindItem(HElv, -1, &lvfi)) == -1 - && i++ < 100); - if (iItemFocus != -1) - ListView_SetItemState(HElv, iItemFocus, - LVIS_FOCUSED, LVIS_FOCUSED); - } - - /* Try to reset scrolling position. Note that this must be - * done last, as focusing an item scrolls it into view. */ - i = 0; - do - lvfi.lParam = lviTop.lParam+i; - while ((iItemTopNew = ListView_FindItem(HElv, -1, &lvfi)) == -1 - && i++ < 100); - if (iItemTopNew != -1) - ElvSetTop(iItemTopNew); - - _stprintf_s(tszDisp, sizeof(tszDisp), TEXT("%d"), iItem); - SendMessage(HWndStatus, SB_SETTEXT, MAKEWPARAM(1,0), (LPARAM)tszDisp); - SendMessage(HElv, WM_SETREDRAW, TRUE, 0); -} - -/* Update episode name and rating. */ -void -ElvUpdateItem(LPLVITEM lpLvi) -{ - char *szName; - int iRating; - TCHAR *tszName; - static TCHAR tszRating[3]; - - tszName = NULL; - if (!Pl("episode_data","episode_title","Is",lpLvi->lParam,&szName)) { - if (!Pl("episode_data","update_episode_data","")) goto r; - if (!Pl("episode_data","episode_title","Is",lpLvi->lParam,&szName)) - goto r; - } - tszName = TszFromSz(szName, CP_UTF8); - if (tszName) - ListView_SetItemText(HElv, lpLvi->iItem, ELVSITITLE, tszName); - -r: if (!Pl("episode_data","episode_rating","Ii",lpLvi->lParam,&iRating)) { - ListView_SetItemText(HElv, lpLvi->iItem, ELVSIRATING, TEXT("")); - goto f; - } - - _stprintf_s(tszRating, sizeof(tszRating), TEXT("%d"), iRating); - ListView_SetItemText(HElv, lpLvi->iItem, ELVSIRATING, tszRating); - -f: if (tszName) free(tszName); -} diff --git a/c/episodelistview.cpp b/c/episodelistview.cpp new file mode 100644 index 0000000..8fb3ec7 --- /dev/null +++ b/c/episodelistview.cpp @@ -0,0 +1,460 @@ +#include +#include +#include +#include + +#include "resource.h" +#include "defs.h" + +extern DataListView g_dlv; + +HWND HSortLv; + +int ISort; +LVITEM LviElvFocus; /* Focused episode. */ +static int CALLBACK ElvSort(LPARAM, LPARAM, LPARAM); + +void +EpisodeListView::Create() +{ + LVCOLUMN lvc; + + ListView::Create((HMENU)IDC_EPISODELISTVIEW, 0); + + lvc.mask = LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM; + lvc.iSubItem = ELVSIEPISODE; + lvc.pszText = TEXT("#"); + lvc.cx = Dpi(42); + ListView_InsertColumn(m_hWnd, ELVSIEPISODE, &lvc); + + lvc.iSubItem = ELVSITITLE; + lvc.pszText = TEXT("Title"); + lvc.cx = 500; + ListView_InsertColumn(m_hWnd, ELVSITITLE, &lvc); + + lvc.iSubItem = ELVSIRATING; + lvc.pszText = TEXT("/"); + lvc.cx = Dpi(30); + ListView_InsertColumn(m_hWnd, ELVSIRATING, &lvc); + + if (!Pl("cfg","get_sort","i",&ISort)) + ISort = 1; +} + +void +EpisodeListView::DoSort() +{ + HSortLv = m_hWnd; + ListView_SortItemsEx(m_hWnd, ElvSort, ISort); +} + +LRESULT +EpisodeListView::HandleNotify(LPARAM lParam) +{ + LPNMLISTVIEW lpNmLv; + lpNmLv = (LPNMLISTVIEW)lParam; + + switch (lpNmLv->hdr.code) { + case LVN_ITEMCHANGED: /* Select/focus episode. */ + if ((lpNmLv->uChanged & LVIF_STATE) && + (lpNmLv->uNewState & LVIS_FOCUSED)) { + LviElvFocus.iItem = lpNmLv->iItem; + LviElvFocus.lParam = lpNmLv->lParam; + UpdateItem(&LviElvFocus); + g_dlv.ShowEpisode(lpNmLv->lParam); + } + break; + case LVN_COLUMNCLICK: /* Sort by column. */ + { + int iColumn; + iColumn = lpNmLv->iSubItem+1; + ISort = abs(ISort) == iColumn? -ISort: iColumn; + Pl("cfg","set_sort","I",ISort); + DoSort(); + ShowFocus(); + break; + } + case LVN_KEYDOWN: /* Navigate episodes by keyboard. */ + { + LPNMLVKEYDOWN lpNmLvKd; + lpNmLvKd = (LPNMLVKEYDOWN)lParam; + switch (lpNmLvKd->wVKey) { + case VK_LEFT: + SelectUnwatched(-1); + break; + case VK_RIGHT: + SelectUnwatched(1); + break; + } + break; + } + case NM_CUSTOMDRAW: /* Make unwatched episodes bold. */ + { + LPNMLVCUSTOMDRAW lpLvCd; + lpLvCd = (LPNMLVCUSTOMDRAW)lParam; + switch (lpLvCd->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + break; + case CDDS_ITEMPREPAINT: + { + extern HFONT g_fBold; + if (!Pl("track_episodes","watched","I",lpLvCd->nmcd.lItemlParam)) { + SelectObject(lpLvCd->nmcd.hdc, g_fBold); + return CDRF_NEWFONT; + } + break; + } + } + break; + } + case NM_DBLCLK: /* Open clicked episode. */ + { + if (!Pl("local_episodes","open_episode_locally","I", + LviElvFocus.lParam)) + Pl("local_episodes","open_episode_online","I", + LviElvFocus.lParam); + break; + } + case NM_RETURN: /* Open all selected episodes. */ + { + LVITEM lvi; + lvi.mask = LVIF_PARAM; + lvi.iItem = -1; + while ((lvi.iItem = ListView_GetNextItem( + m_hWnd, lvi.iItem, LVNI_SELECTED)) != -1) { + if (!ListView_GetItem(m_hWnd, &lvi)) goto b; + if (!Pl("local_episodes","open_episode_locally","I", + lvi.lParam)) + Pl("local_episodes","open_episode_online","I", + lvi.lParam); + } + b: break; + } + case NM_RCLICK: + { + DWORD dwPos; + extern HWND g_hWnd; + extern HMENU g_hPopupMenu; + dwPos = GetMessagePos(); + TrackPopupMenu(g_hPopupMenu, TPM_RIGHTBUTTON, + LOWORD(dwPos), HIWORD(dwPos), 0, + g_hWnd, NULL); + break; + } + } + + return 0; +} + +void +EpisodeListView::Redraw() +{ + RedrawWindow(m_hWnd, NULL, NULL, + RDW_ERASE|RDW_FRAME|RDW_INVALIDATE|RDW_ALLCHILDREN); +} + +void +EpisodeListView::SetTop(int iItem) +{ + int iLast; + iLast = ListView_GetItemCount(m_hWnd)-1; + ListView_EnsureVisible(m_hWnd, iLast, TRUE); + ListView_EnsureVisible(m_hWnd, iItem, TRUE); +} + +/* Select previously focused episode. */ +void +EpisodeListView::SelectFocus() +{ + int i, iEpisode, iItem; + LVFINDINFO lvfi; + + iItem = 0; + if (!Pl("cfg","get_focus","i",&iEpisode)) return; + + lvfi.flags = LVFI_PARAM; + lvfi.lParam = iEpisode; + i = 0; + while ((iItem = ListView_FindItem(m_hWnd, -1, &lvfi)) == -1 && i++ < 100) + lvfi.lParam = ++iEpisode; + if (iItem != -1) goto s; + + iEpisode -= 100; + lvfi.lParam = iEpisode; + i = 0; + while ((iItem = ListView_FindItem(m_hWnd, -1, &lvfi)) == -1 && i++ < 100) + lvfi.lParam = --iEpisode; + if (iItem != -1) goto s; + return; + +s: ListView_SetItemState(m_hWnd, -1, LVIF_STATE, LVIS_SELECTED); + SetTop(iItem > 5? iItem-5: 0); + ListView_SetItemState(m_hWnd, iItem, + LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED); + LviElvFocus.iItem = iItem; + LviElvFocus.lParam = iEpisode; + UpdateItem(&LviElvFocus); + g_dlv.ShowEpisode(iEpisode); +} + +/* Select next/previous unwatched episode. */ +void +EpisodeListView::SelectUnwatched(int iDir) +{ + int i, iEpNew, iItemNew; + LVFINDINFO lvfi; + LVITEM lviFocus; + + /* Get focused episode. */ + lviFocus.mask = LVIF_PARAM; + if ((lviFocus.iItem = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED)) != -1 + && ListView_GetItem(m_hWnd, &lviFocus)) + ; + else + return; + + i = 0; + lvfi.flags = LVFI_PARAM; + lvfi.lParam = lviFocus.lParam; + + do { + if (!Pl("track_episodes",iDir > 0? "next_unwatched": "previous_unwatched", + "Ii",lvfi.lParam,&iEpNew)) + return; + + lvfi.lParam = iEpNew; + if ((iItemNew = ListView_FindItem(m_hWnd, -1, &lvfi)) != -1) { + ListView_SetItemState(m_hWnd,-1,LVIF_STATE,LVIS_SELECTED); + ListView_SetSelectionMark(m_hWnd, iItemNew); + ListView_SetItemState(m_hWnd, iItemNew, + LVIS_SELECTED|LVIS_FOCUSED, + LVIS_SELECTED|LVIS_FOCUSED); + Redraw(); + ListView_EnsureVisible(m_hWnd, iItemNew, TRUE); + return; + } + } while (i++ < 1000); +} + +void +EpisodeListView::ShowFocus() +{ + int iEpFocus; + iEpFocus = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED); + if (iEpFocus == -1) return; + ListView_EnsureVisible(m_hWnd, iEpFocus, TRUE); +} + +/* Update episode list. */ +void +EpisodeListView::Update() +{ + int cEp, i, iEp, iEpFocus, iItem, iItemMark, iItemTopNew; + LVITEM lvi, lviEpisode, lviTop; + LVFINDINFO lvfi; + extern HWND g_hWndStatus; + static TCHAR tszDisp[16], tszEpisode[16]; + static int aEpSel[2048]; + + lviEpisode.mask = LVIF_TEXT|LVIF_PARAM; + + /* Save scrolling position. */ + lviTop.iItem = ListView_GetTopIndex(m_hWnd); + lviTop.mask = LVIF_PARAM; + ListView_GetItem(m_hWnd, &lviTop); + + /* Save selected episodes. */ + i = 0; + lvi.mask = LVIF_PARAM; + lvi.iItem = -1; + while ((lvi.iItem = ListView_GetNextItem( + m_hWnd, lvi.iItem, LVNI_SELECTED)) != -1 + && i < 2048) + if (ListView_GetItem(m_hWnd, &lvi)) + aEpSel[i++] = lvi.lParam; + aEpSel[i] = 0; + iItemMark = ListView_GetSelectionMark(m_hWnd); + + /* Save focus. */ + iEpFocus = 0; + if ((lvi.iItem = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED)) != -1 + && ListView_GetItem(m_hWnd, &lvi)) + iEpFocus = lvi.lParam; + + SendMessage(m_hWnd, WM_SETREDRAW, FALSE, 0); + ListView_DeleteAllItems(m_hWnd); + + if (!Pl("episode_data","ensure_episode_data","")) return; + if (!Pl("episode_data","episode_count","i",&cEp)) return; + + for (iEp = 1, iItem = 0; iEp <= cEp; iEp++) { + extern char g_szLimitScreenwriter[]; + extern int g_bViewTVOriginal, g_bViewWatched; + + if (g_szLimitScreenwriter[0]) + if (!Pl("episode_data","episode_datum","ISS", + iEp,"Screenwriter",g_szLimitScreenwriter)) + continue; + + if (!g_bViewWatched) + if (Pl("track_episodes","watched","I",iEp)) continue; + + if (!g_bViewTVOriginal) + if (Pl("episode_data","tv_original","I",iEp)) continue; + + /* Format episode number string. */ + _stprintf_s(tszEpisode, sizeof(tszEpisode), TEXT("%d"), iEp); + + /* Insert item. */ + lviEpisode.iItem = iItem++; + lviEpisode.iSubItem = ELVSIEPISODE; + lviEpisode.pszText = tszEpisode; + lviEpisode.lParam = iEp; + ListView_InsertItem(m_hWnd, &lviEpisode); + UpdateItem(&lviEpisode); + } + + DoSort(); + + lvfi.flags = LVFI_PARAM; + + /* Reset selection. */ + for (i = 0; aEpSel[i]; i++) { + int iItemSel; + lvfi.lParam = aEpSel[i]; + if ((iItemSel = ListView_FindItem(m_hWnd, -1, &lvfi)) != -1) + ListView_SetItemState(m_hWnd, iItemSel, + LVIS_SELECTED, LVIS_SELECTED); + } + if (iItemMark != -1) + ListView_SetSelectionMark(m_hWnd, iItemMark); + + /* Reset focus. */ + if (iEpFocus) { + int iItemFocus; + i = 0; + do + lvfi.lParam = iEpFocus+i; + while ((iItemFocus = ListView_FindItem(m_hWnd, -1, &lvfi)) == -1 + && i++ < 100); + if (iItemFocus != -1) + ListView_SetItemState(m_hWnd, iItemFocus, + LVIS_FOCUSED, LVIS_FOCUSED); + } + + /* Try to reset scrolling position. Note that this must be + * done last, as focusing an item scrolls it into view. */ + i = 0; + do + lvfi.lParam = lviTop.lParam+i; + while ((iItemTopNew = ListView_FindItem(m_hWnd, -1, &lvfi)) == -1 + && i++ < 100); + if (iItemTopNew != -1) + SetTop(iItemTopNew); + + _stprintf_s(tszDisp, sizeof(tszDisp), TEXT("%d"), iItem); + SendMessage(g_hWndStatus, SB_SETTEXT, MAKEWPARAM(1,0), (LPARAM)tszDisp); + SendMessage(m_hWnd, WM_SETREDRAW, TRUE, 0); +} + +/* Update episode name and rating. */ +void +EpisodeListView::UpdateItem(LPLVITEM lpLvi) +{ + char *szName; + int iRating; + TCHAR *tszName; + static TCHAR tszRating[3]; + + tszName = NULL; + if (!Pl("episode_data","episode_title","Is",lpLvi->lParam,&szName)) { + if (!Pl("episode_data","update_episode_data","")) goto r; + if (!Pl("episode_data","episode_title","Is",lpLvi->lParam,&szName)) + goto r; + } + tszName = TszFromSz(szName, CP_UTF8); + if (tszName) + ListView_SetItemText(m_hWnd, lpLvi->iItem, ELVSITITLE, tszName); + +r: if (!Pl("episode_data","episode_rating","Ii",lpLvi->lParam,&iRating)) { + ListView_SetItemText(m_hWnd, lpLvi->iItem, ELVSIRATING, TEXT("")); + goto f; + } + + _stprintf_s(tszRating, sizeof(tszRating), TEXT("%d"), iRating); + ListView_SetItemText(m_hWnd, lpLvi->iItem, ELVSIRATING, tszRating); + +f: if (tszName) free(tszName); +} + +/* Sort list view items, iSort being the 1-based index of the column + * to sort by. If iSort is negative, the order is descending. */ +int CALLBACK +ElvSort(LPARAM iItem1, LPARAM iItem2, LPARAM iSort) +{ + int iOrder; + LVITEM lvi1, lvi2; + lvi1.mask = lvi2.mask = LVIF_PARAM; + lvi1.iItem = iItem1; lvi2.iItem = iItem2; + if (!ListView_GetItem(HSortLv, &lvi1)) return 0; + if (!ListView_GetItem(HSortLv, &lvi2)) return 0; + iOrder = Cmp(iSort, 0); + switch (abs(iSort)-1) { + case ELVSIEPISODE: + return iOrder*Cmp(lvi1.lParam, lvi2.lParam); + break; + case ELVSIRATING: + { + int iRating1, iRating2; + iRating1 = iSort > 0? 99: -1; + iRating2 = iSort > 0? 99: -1; + Pl("episode_data","episode_rating","Ii",lvi1.lParam,&iRating1); + Pl("episode_data","episode_rating","Ii",lvi2.lParam,&iRating2); + if (iRating1 == iRating2) + return Cmp(lvi1.lParam, lvi2.lParam); + return iOrder*Cmp(iRating1, iRating2); + break; + } + case ELVSITITLE: + { + char *sz1, *sz2; + int cch, cch1, cch2; + if (!Pl("episode_data","episode_title","Is",lvi1.lParam,&sz1)) + return 0; + if (!Pl("episode_data","episode_title","Is",lvi2.lParam,&sz2)) + return 0; + cch1 = strlen(sz1); + cch2 = strlen(sz2); + cch = cch1 > cch2? cch2: cch1; + return iOrder*_strnicmp(sz1, sz2, cch); + break; + } + default: + return 0; + } +} + +LRESULT CALLBACK +EpisodeListView::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) { + case WM_GETDLGCODE: + { + LRESULT lResult; + + /* For the episode list view, the Enter key should not + * be handled by the dialog manager, but instead be sent + * along to the main window procedure, so that it may be + * handled by the NM_RETURN case in ElvHandleNotify. */ + + lResult = CallWindowProc(m_prevProc, hWnd, uMsg, wParam, lParam); + if (lParam && ((MSG *)lParam)->message == WM_KEYDOWN + && ((MSG *)lParam)->wParam == VK_RETURN) + return DLGC_WANTMESSAGE; + return lResult; + } + } + + return ListView::WndProc(hWnd, uMsg, wParam, lParam); +} diff --git a/c/listview.c b/c/listview.c deleted file mode 100644 index 91838da..0000000 --- a/c/listview.c +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include - -#include "resource.h" -#include "defs.h" - -extern HFONT HfNormal; -extern HWND HWnd; -WNDPROC LvPrevProc; -static LRESULT CALLBACK LvProc(HWND, UINT, WPARAM, LPARAM); - -HWND -LvCreate(HMENU hMenu, DWORD dwStyle) -{ - HWND hLv; - - hLv = CreateWindowEx( - WS_EX_CLIENTEDGE, - WC_LISTVIEW, - TEXT(""), - dwStyle|WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_TABSTOP - |LVS_REPORT|LVS_SHOWSELALWAYS, - 0, 0, 0, 0, - HWnd, hMenu, GetModuleHandle(NULL), NULL - ); - - LvPrevProc = (WNDPROC)SetWindowLongPtr(hLv, - GWLP_WNDPROC, (LONG_PTR)LvProc); - - ListView_SetExtendedListViewStyle(hLv, LVS_EX_FULLROWSELECT); - - SendMessage(hLv, WM_SETFONT, (WPARAM)HfNormal, MAKELPARAM(FALSE, 0)); - - return hLv; -} - -LRESULT CALLBACK -LvProc(HWND hLv, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) { - case WM_NOTIFY: - switch (((LPNMHDR)lParam)->code) { - case HDN_ENDTRACK: - UpdateLayout(); - return TRUE; - } - break; - case WM_GETDLGCODE: - { - LRESULT lResult; - extern HWND HElv; - - /* For the episode list view, the Enter key should not - * be handled by the dialog manager, but instead be sent - * along to the main window procedure, so that it may be - * handled by the NM_RETURN case in ElvHandleNotify. */ - - if (hLv != HElv) break; - lResult = CallWindowProc(LvPrevProc, hLv, uMsg, wParam, lParam); - if (lParam && ((MSG *)lParam)->message == WM_KEYDOWN - && ((MSG *)lParam)->wParam == VK_RETURN) - return DLGC_WANTMESSAGE; - return lResult; - } - } - - return CallWindowProc(LvPrevProc, hLv, uMsg, wParam, lParam); -} - -/* Naively calculate height of list view. */ -int -LvHeight(HWND hLv, int bHeader) -{ - int iCount; - iCount = ListView_GetItemCount(hLv); - return iCount? Dpi(bHeader? 27: 4)+iCount*Dpi(19): 0; -} diff --git a/c/listview.cpp b/c/listview.cpp new file mode 100644 index 0000000..9f24dc3 --- /dev/null +++ b/c/listview.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +#include "resource.h" +#include "defs.h" + +extern HFONT g_hfNormal; +extern HWND g_hWnd; +static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); + +void +ListView::Create(HMENU hMenu, DWORD dwStyle) +{ + m_hWnd = CreateWindowEx( + WS_EX_CLIENTEDGE, + WC_LISTVIEW, + TEXT(""), + dwStyle + |WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_TABSTOP|LVS_REPORT|LVS_SHOWSELALWAYS, + 0, 0, 0, 0, + g_hWnd, hMenu, GetModuleHandle(NULL), this + ); + + if (SetProp(m_hWnd, TEXT("this"), (HANDLE)this)) + m_prevProc = (WNDPROC)SetWindowLongPtr(m_hWnd, + GWLP_WNDPROC, (LONG_PTR)::WndProc); + + ListView_SetExtendedListViewStyle(m_hWnd, LVS_EX_FULLROWSELECT); + + SendMessage(m_hWnd, WM_SETFONT, (WPARAM)g_hfNormal, MAKELPARAM(FALSE, 0)); +} + +/* Naively calculate height of list view. */ +int +ListView::Height(int bHeader) +{ + int iCount; + iCount = ListView_GetItemCount(m_hWnd); + return iCount? Dpi(bHeader? 27: 4)+iCount*Dpi(19): 0; +} + +HWND +ListView::HWnd() +{ + return m_hWnd; +} + +LRESULT CALLBACK +ListView::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) { + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->code) { + case HDN_ENDTRACK: + UpdateLayout(); + return TRUE; + } + break; + } + + return CallWindowProc(m_prevProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK +WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) { + case WM_DESTROY: + RemoveProp(hWnd, TEXT("this")); + break; + } + + ListView *lpThis = (ListView *)GetProp(hWnd, TEXT("this")); + return lpThis? lpThis->WndProc(hWnd, uMsg, wParam, lParam): FALSE; +} diff --git a/c/main.c b/c/main.c deleted file mode 100644 index ef3877a..0000000 --- a/c/main.c +++ /dev/null @@ -1,581 +0,0 @@ -#include -#include -#include -#include - -#include "resource.h" -#include "defs.h" - -atom_t AThread; -char SzLimitScreenwriter[64] = {0}; -HFONT HfNormal; -HFONT HfBold; -HMENU HPopupMenu; -HWND HFocus; -HWND HWnd; -HWND HWndStatus; -int BViewTVOriginal = 1; -int BViewWatched = 1; -int BThread = 0; -int IDPI = -1; -static int BThemes; -static int 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")); - BThemes = hModule && GetProcAddress(hModule,"SetWindowTheme"); - if (hModule) FreeLibrary(hModule); - CxVScroll = GetSystemMetrics(SM_CXVSCROLL); - - /* Initialize Prolog. */ - argv[0] = "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; - - HPopupMenu = LoadMenu(NULL, MAKEINTRESOURCE(IDR_POPUPMENU)); - HPopupMenu = GetSubMenu(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; - HWndStatus = CreateStatusBar(hWnd, hInstance); - ShowWindow(hWnd, nCmdShow); - - /* Populate episode list view. */ - Pl("track_episodes", "update_tracked_episodes", ""); - ElvUpdate(); - ElvSelectFocus(); - - 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; - FARPROC GetDpiForWindow; - extern HWND HWnd, HElv; - HWnd = hWnd; - IDPI = 96; - hModule = LoadLibrary(TEXT("User32.dll")); - if (hModule && (GetDpiForWindow = GetProcAddress(hModule, "GetDpiForWindow"))) { - IDPI = GetDpiForWindow(HWnd); - FreeLibrary(hModule); - } - SetWindowPos(hWnd, NULL, -1, -1, Dpi(510), Dpi(400), SWP_NOMOVE); - if (Pl("cfg","get_view_watched","i",&BViewWatched)) - CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED, - BViewWatched? MF_CHECKED: MF_UNCHECKED); - if (Pl("cfg","get_view_tv_original","i",&BViewTVOriginal)) - CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL, - BViewTVOriginal? MF_CHECKED: MF_UNCHECKED); - { - char *sz; - if (!Pl("cfg","get_limit_screenwriter","s",&sz)) - goto s; - strcpy_s(SzLimitScreenwriter, - sizeof(SzLimitScreenwriter), sz); - CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, - SzLimitScreenwriter[0]? MF_UNCHECKED: MF_CHECKED); - } - s: SetupFonts(); - DlvCreate(); - ElvCreate(); - UpdateTheme(); - SetFocus(HElv); - break; - } - case WM_CLOSE: - DestroyWindow(hWnd); - break; - case WM_DESTROY: - { - LVITEM lvi; - extern HWND HElv; - 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(HWndStatus, WM_SIZE, wParam, lParam); - UpdateLayout(); - break; - case WM_GETMINMAXINFO: - { - LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam; - extern HWND HDlv; - lpMMI->ptMinTrackSize.y = LvHeight(HDlv, 0)+80; - break; - } - case WM_THEMECHANGED: - UpdateTheme(); - break; - case 0x02E0: /* WM_DPICHANGED */ - { - LPRECT lpr; - lpr = (LPRECT)lParam; - 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: - HFocus = GetFocus(); - break; - case WA_ACTIVE: - case WA_CLICKACTIVE: - SetFocus(HFocus); - Pl("track_episodes","update_tracked_episodes",""); - ElvRedraw(); - } - break; - case WM_NOTIFY: - switch (((LPNMHDR)lParam)->idFrom) { - case IDC_EPISODELISTVIEW: - return ElvHandleNotify(lParam); - } - break; - case WM_TIMER: - switch (wParam) { - case IDT_TIMER: - { - static int i = 0; - if (Pl("episode_data","thread_running","A",AThread)) { - i = (i+1)%4; - SendMessage(HWndStatus, SB_SETTEXT, MAKEWPARAM(1,0), - (LPARAM)(i==0? TEXT("."): - i==1? TEXT(".."): - i==2? TEXT("..."): - TEXT(""))); - } else { - i = 0; - KillTimer(hWnd, IDT_TIMER); - ElvUpdate(); - } - break; - } - } - break; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDM_FILE_EXIT: - PostMessage(hWnd, WM_CLOSE, 0, 0); - break; - case IDM_FILE_REFRESH: - ElvUpdate(); - break; - case IDM_FILE_FETCH_DATA: - if (BThread) break; - Pl("episode_data","thread_create","Sa","update_episode_data",&AThread); - goto t; - case IDM_FILE_FETCH_SCREENWRITERS: - if (BThread) break; - Pl("episode_data","thread_create","Sa","update_screenwriters",&AThread); - t: KillTimer(hWnd, IDT_TIMER); - SetTimer(hWnd, IDT_TIMER, 500, NULL); - SendMessage(HWndStatus, SB_SETTEXT, MAKEWPARAM(1,0), (LPARAM)TEXT(".")); - BThread = 1; - break; - case IDM_FILE_ABOUT: - DialogBox( - GetModuleHandle(NULL), - MAKEINTRESOURCE(IDD_ABOUT), - hWnd, - AboutDlgProc - ); - break; - case IDM_VIEW_WATCHED: - { - int iEpFocus; - extern HWND HElv; - CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED, - BViewWatched? MF_UNCHECKED: MF_CHECKED); - BViewWatched = !BViewWatched; - ElvUpdate(); - Pl("cfg","set_view_watched","I",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; - extern HWND HElv; - CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL, - BViewTVOriginal? MF_UNCHECKED: MF_CHECKED); - BViewTVOriginal = !BViewTVOriginal; - ElvUpdate(); - Pl("cfg","set_view_tv_original","I",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; - extern HWND HElv; - if (SzLimitScreenwriter[0]) { - CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, - MF_CHECKED); - 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(SzLimitScreenwriter, - sizeof(SzLimitScreenwriter), sz); - CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, - MF_UNCHECKED); - } - ElvUpdate(); - Pl("cfg","set_limit_screenwriter","S", - 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; - extern HWND HElv; - - /* 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); - ElvRedraw(); - break; - case IDM_FORGET: - Pl("track_episodes","forget_episode","I",lvi.lParam); - Pl("track_episodes","update_tracked_episodes",""); - ElvRedraw(); - break; - case IDM_LOOKUP: - Pl("episode_data","retract_episode","I",lvi.lParam); - ElvUpdateItem(&lvi); - ElvRedraw(); - DlvShowEpisode(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); - ElvUpdateItem(&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: - ElvRedraw(); - ElvDoSort(); - ElvShowFocus(); - } - 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); - HfNormal = CreateFontIndirect(&m.lfMessageFont); - FreeLibrary(hModule); - } else - HfNormal = GetStockObject(DEFAULT_GUI_FONT); - - GetObject(HfNormal, sizeof(LOGFONT), &lf); - lf.lfWeight = FW_BOLD; - HfBold = CreateFontIndirect(&lf); -} - -/***/ - -void -UpdateLayout() -{ - int cxColumn, cyDlv, yStatus; - RECT rc, rcStatus; - extern HWND HDlv, HElv; - - GetClientRect(HWnd, &rc); - GetClientRect(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-LvHeight(HDlv, 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-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-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(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; - extern HWND HElv, HDlv; - - if (!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); -} 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 +#include +#include +#include + +#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(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); +} diff --git a/c/pl.c b/c/pl.c deleted file mode 100644 index 50f5429..0000000 --- a/c/pl.c +++ /dev/null @@ -1,141 +0,0 @@ -#include -#include -#include -#include "defs.h" - -static int Plpv(term_t, char *, va_list); -static int Plgv(term_t, char *, va_list); - -/* Call Prolog predicate once. */ -int -Pl(char *szMod, char *szPred, char *szFmt, ...) -{ - int iArity; - term_t t; - va_list vl, vl2; - - iArity = strlen(szFmt); - t = PL_new_term_refs(iArity); - - va_start(vl, szFmt); - va_copy(vl2, vl); - - if (!Plpv(t, szFmt, vl)) goto f; - if (!PL_call_predicate(NULL, PL_Q_NORMAL, - PL_predicate(szPred, iArity, szMod), t)) - goto f; - if (!Plgv(t, szFmt, vl2)) goto f; - - va_end(vl); - va_end(vl2); - return 1; -f: va_end(vl); - va_end(vl2); - return 0; -} - -/* Put known values in term. */ -int -Plp(term_t t, char *szFmt, ...) -{ - int r; - va_list vl; - va_start(vl, szFmt); - r = Plpv(t, szFmt, vl); - va_end(vl); - return r; -} -int -Plpv(term_t t, char *szFmt, va_list vl) -{ - int i; - for (i = 0; szFmt[i]; i++) { - switch (szFmt[i]) { - case 'I': - { - int x; - x = va_arg(vl, int); - if (!PL_put_integer(t+i, x)) return 0; - break; - } - case 'A': - { - atom_t x; - x = va_arg(vl, atom_t); - if (!PL_put_atom(t+i, x)) return 0; - break; - } - case 'S': - { - atom_t a; - char *x; - x = va_arg(vl, char *); - a = PL_new_atom(x); - if (!PL_put_atom(t+i, a)) return 0; - break; - } - case 'i': - va_arg(vl, int *); - break; - case 'a': - va_arg(vl, atom_t *); - break; - case 's': - va_arg(vl, char **); - break; - } - } - return 1; -} - -/* Get unknown values from term. */ -int -Plg(term_t t, char *szFmt, ...) -{ - int r; - va_list vl; - va_start(vl, szFmt); - r = Plgv(t, szFmt, vl); - va_end(vl); - return r; -} -int -Plgv(term_t t, char *szFmt, va_list vl) -{ - int i; - for (i = 0; szFmt[i]; i++) { - switch (szFmt[i]) { - case 'i': - { - int *lp; - lp = va_arg(vl, int *); - if (!PL_get_integer(t+i, lp)) return 0; - break; - } - case 'a': - { - atom_t *lp; - lp = va_arg(vl, atom_t *); - if (!PL_get_atom(t+i, lp)) return 0; - break; - } - case 's': - { - char **lp; - lp = va_arg(vl, char **); - if (!PL_get_atom_chars(t+i, lp)) return 0; - break; - } - case 'I': - va_arg(vl, int); - break; - case 'A': - va_arg(vl, atom_t); - break; - case 'S': - va_arg(vl, char *); - break; - } - } - return 1; -} diff --git a/c/pl.cpp b/c/pl.cpp new file mode 100644 index 0000000..dd69517 --- /dev/null +++ b/c/pl.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include "defs.h" + +static int Plpv(term_t, const char *, va_list); +static int Plgv(term_t, const char *, va_list); + +/* Call Prolog predicate once. */ +int +Pl(const char *szMod, const char *szPred, const char *szFmt, ...) +{ + int iArity; + term_t t; + va_list vl, vl2; + + iArity = strlen(szFmt); + t = PL_new_term_refs(iArity); + + va_start(vl, szFmt); + va_copy(vl2, vl); + + if (!Plpv(t, szFmt, vl)) goto f; + if (!PL_call_predicate(NULL, PL_Q_NORMAL, + PL_predicate(szPred, iArity, szMod), t)) + goto f; + if (!Plgv(t, szFmt, vl2)) goto f; + + va_end(vl); + va_end(vl2); + return 1; +f: va_end(vl); + va_end(vl2); + return 0; +} + +/* Put known values in term. */ +int +Plp(term_t t, const char *szFmt, ...) +{ + int r; + va_list vl; + va_start(vl, szFmt); + r = Plpv(t, szFmt, vl); + va_end(vl); + return r; +} +int +Plpv(term_t t, const char *szFmt, va_list vl) +{ + int i; + for (i = 0; szFmt[i]; i++) { + switch (szFmt[i]) { + case 'I': + { + int x; + x = va_arg(vl, int); + if (!PL_put_integer(t+i, x)) return 0; + break; + } + case 'A': + { + atom_t x; + x = va_arg(vl, atom_t); + if (!PL_put_atom(t+i, x)) return 0; + break; + } + case 'S': + { + atom_t a; + char *x; + x = va_arg(vl, char *); + a = PL_new_atom(x); + if (!PL_put_atom(t+i, a)) return 0; + break; + } + case 'i': + va_arg(vl, int *); + break; + case 'a': + va_arg(vl, atom_t *); + break; + case 's': + va_arg(vl, char **); + break; + } + } + return 1; +} + +/* Get unknown values from term. */ +int +Plg(term_t t, const char *szFmt, ...) +{ + int r; + va_list vl; + va_start(vl, szFmt); + r = Plgv(t, szFmt, vl); + va_end(vl); + return r; +} +int +Plgv(term_t t, const char *szFmt, va_list vl) +{ + int i; + for (i = 0; szFmt[i]; i++) { + switch (szFmt[i]) { + case 'i': + { + int *lp; + lp = va_arg(vl, int *); + if (!PL_get_integer(t+i, lp)) return 0; + break; + } + case 'a': + { + atom_t *lp; + lp = va_arg(vl, atom_t *); + if (!PL_get_atom(t+i, lp)) return 0; + break; + } + case 's': + { + char **lp; + lp = va_arg(vl, char **); + if (!PL_get_atom_chars(t+i, lp)) return 0; + break; + } + case 'I': + va_arg(vl, int); + break; + case 'A': + va_arg(vl, atom_t); + break; + case 'S': + va_arg(vl, char *); + break; + } + } + return 1; +} -- cgit v1.2.3