#include #include #include #include #include "resource.h" #include "common.h" #include "datalistview.h" #include "episodelistview.h" #include "listview.h" #include "pl.h" extern DataListView *g_lpDlv; int CALLBACK ElvSort(LPARAM, LPARAM, LPARAM); EpisodeListView::EpisodeListView(const HWND hWndParent) : ListView(hWndParent, (HMENU)IDC_EPISODELISTVIEW, 0) { LVCOLUMN lvc; 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",&m_iSort)) m_iSort = 1; } void EpisodeListView::DoSort() { ListView_SortItemsEx(m_hWnd, ElvSort, (LPARAM)this); } void EpisodeListView::EnsureFocusVisible() { const int iEpFocus = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED); if (iEpFocus == -1) return; ListView_EnsureVisible(m_hWnd, iEpFocus, TRUE); } LRESULT EpisodeListView::HandleNotify(const LPARAM lParam) { const LPNMLISTVIEW lpNmLv = (LPNMLISTVIEW)lParam; switch (lpNmLv->hdr.code) { case LVN_ITEMCHANGED: /* Select/focus episode. */ if ((lpNmLv->uChanged & LVIF_STATE) && (lpNmLv->uNewState & LVIS_FOCUSED)) { m_lviFocus.iItem = lpNmLv->iItem; m_lviFocus.lParam = lpNmLv->lParam; UpdateItem(&m_lviFocus); g_lpDlv->ShowEpisode(lpNmLv->lParam); } break; case LVN_COLUMNCLICK: /* Sort by column. */ { const int iColumn = lpNmLv->iSubItem+1; m_iSort = abs(m_iSort) == iColumn? -m_iSort: iColumn; Pl("cfg","set_sort",m_iSort); DoSort(); ShowFocus(); break; } case LVN_KEYDOWN: /* Navigate episodes by keyboard. */ { const LPNMLVKEYDOWN 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. */ { const LPNMLVCUSTOMDRAW 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",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", m_lviFocus.lParam)) Pl("local_episodes","open_episode_online", m_lviFocus.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", lvi.lParam)) Pl("local_episodes","open_episode_online", lvi.lParam); } b: break; } case NM_RCLICK: { extern HMENU g_hPopupMenu; const DWORD dwPos = GetMessagePos(); TrackPopupMenu(g_hPopupMenu, TPM_RIGHTBUTTON, LOWORD(dwPos), HIWORD(dwPos), 0, m_hWndParent, NULL); break; } } return 0; } void EpisodeListView::Redraw() { RedrawWindow(m_hWnd, NULL, NULL, RDW_ERASE|RDW_FRAME|RDW_INVALIDATE|RDW_ALLCHILDREN); } void EpisodeListView::SaveFocus() { LVITEM lvi; lvi.mask = LVIF_PARAM; if ((lvi.iItem = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED)) != -1 && ListView_GetItem(m_hWnd, &lvi)) Pl("cfg","set_focus",lvi.lParam); } void EpisodeListView::SetTop(const int iItem) { const int iLast = ListView_GetItemCount(m_hWnd)-1; ListView_EnsureVisible(m_hWnd, iLast, TRUE); ListView_EnsureVisible(m_hWnd, iItem, TRUE); } /* Select previously focused episode. */ void EpisodeListView::RestoreFocus() { int i, iEpisode, iItem; LVFINDINFO lvfi; iItem = 0; if (!Pl("cfg","get_focus",&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); m_lviFocus.iItem = iItem; m_lviFocus.lParam = iEpisode; UpdateItem(&m_lviFocus); g_lpDlv->ShowEpisode(iEpisode); } /* Select next/previous unwatched episode. */ void EpisodeListView::SelectUnwatched(int iDir) { /* Get focused episode. */ LVITEM lviFocus; lviFocus.mask = LVIF_PARAM; if ((lviFocus.iItem = ListView_GetNextItem(m_hWnd, -1, LVNI_FOCUSED)) != -1 && ListView_GetItem(m_hWnd, &lviFocus)) ; else return; LVFINDINFO lvfi; int i, iEpNew, iItemNew; i = 0; lvfi.flags = LVFI_PARAM; lvfi.lParam = lviFocus.lParam; do { if (!Pl("track_episodes",iDir > 0? "next_unwatched": "previous_unwatched", 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() { const int 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",&cEp)) return; for (iEp = 1, iItem = 0; iEp <= cEp; iEp++) { extern char g_szLimitScreenwriter[]; extern int g_bViewTVOriginal, g_bViewWatched; if (g_szLimitScreenwriter[0] && !Pl("episode_data","episode_datum",iEp,"Screenwriter",g_szLimitScreenwriter)) continue; if (!g_bViewWatched) if (Pl("track_episodes","watched",iEp)) continue; if (!g_bViewTVOriginal) if (Pl("episode_data","tv_original",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(const LPLVITEM lpLvi) { TCHAR *tszName; int iRating; static TCHAR tszRating[3]; if (!Pl("episode_data","episode_title",lpLvi->lParam,&tszName)) { if (!Pl("episode_data","update_episode_data")) goto r; if (!Pl("episode_data","episode_title",lpLvi->lParam,&tszName)) goto r; } ListView_SetItemText(m_hWnd, lpLvi->iItem, ELVSITITLE, tszName); r: if (!Pl("episode_data","episode_rating",lpLvi->lParam,&iRating)) { ListView_SetItemText(m_hWnd, lpLvi->iItem, ELVSIRATING, TEXT("")); return; } _stprintf_s(tszRating, sizeof(tszRating), TEXT("%d"), iRating); ListView_SetItemText(m_hWnd, lpLvi->iItem, ELVSIRATING, tszRating); } LRESULT CALLBACK EpisodeListView::WndProc(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam) { switch (uMsg) { case WM_GETDLGCODE: { /* 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. */ const LRESULT 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); } int CALLBACK ElvSort(const LPARAM iItem1, const LPARAM iItem2, const LPARAM lExtra) { EpisodeListView *lpElv = (EpisodeListView *)lExtra; LVITEM lvi1, lvi2; lvi1.mask = lvi2.mask = LVIF_PARAM; lvi1.iItem = iItem1; lvi2.iItem = iItem2; if (!ListView_GetItem(lpElv->Handle(), &lvi1)) return 0; if (!ListView_GetItem(lpElv->Handle(), &lvi2)) return 0; /* abs(m_iSort) is the 1-based index of the column to sort by. * If m_iSort is negative, the order is descending. */ const int iOrder = Cmp(lpElv->m_iSort, 0); switch (abs(lpElv->m_iSort)-1) { case ELVSIEPISODE: return iOrder*Cmp(lvi1.lParam, lvi2.lParam); case ELVSIRATING: { int iRating1, iRating2; iRating1 = lpElv->m_iSort > 0? 99: -1; iRating2 = lpElv->m_iSort > 0? 99: -1; Pl("episode_data","episode_rating",lvi1.lParam,&iRating1); Pl("episode_data","episode_rating",lvi2.lParam,&iRating2); if (iRating1 == iRating2) return Cmp(lvi1.lParam, lvi2.lParam); return iOrder*Cmp(iRating1, iRating2); } case ELVSITITLE: { char *sz1, *sz2; int cch, cch1, cch2; if (!Pl("episode_data","episode_title",lvi1.lParam,&sz1)) return 0; if (!Pl("episode_data","episode_title",lvi2.lParam,&sz2)) return 0; cch1 = strlen(sz1); cch2 = strlen(sz2); cch = cch1 > cch2? cch2: cch1; return iOrder*_strnicmp(sz1, sz2, cch); } default: return 0; } }