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++. --- c/episodelistview.cpp | 460 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 c/episodelistview.cpp (limited to 'c/episodelistview.cpp') 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); +} -- cgit v1.2.3