#include #include #include #include #include #include "resource.h" #include "common.h" #include "datalistview.h" #include "episodelistview.h" #include "main.h" #include "pl.h" DataListView *g_lpDlv = nullptr; EpisodeListView *g_lpElv = nullptr; 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_bThemes = 0; int g_bViewTVOriginal = 1; int g_bViewWatched = 1; int g_bThread = 0; int g_iDPI = 96; static int g_cxVScroll; static void OnTerminate(void); static LRESULT CALLBACK CBTProc(int, WPARAM, LPARAM); static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); static INT_PTR CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM); static HWND CreateStatusBar(HWND, HINSTANCE); static void UpdateTheme(void); int WINAPI WinMain(const HINSTANCE hInstance, const HINSTANCE, const LPSTR lpCmdLine, const INT nCmdShow) { /* Exit gracefully on uncaught exception. */ std::set_terminate(OnTerminate); /* Set constant values. */ if (auto opLib = try_make(TEXT("uxtheme.dll"))) if (opLib->GetProcAddress("SetWindowTheme")) g_bThemes = 1; g_cxVScroll = GetSystemMetrics(SM_CXVSCROLL); /* Setup fonts. */ if (auto opLib = try_make(TEXT("User32.dll"))) { if (opLib->GetProcAddress("SystemParametersInfo" WA)) { NONCLIENTMETRICS m; m.cbSize = sizeof(NONCLIENTMETRICS); SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &m, 0); g_hfNormal = CreateFontIndirect(&m.lfMessageFont); } } else g_hfNormal = static_cast(GetStockObject(DEFAULT_GUI_FONT)); LOGFONT lf; GetObject(g_hfNormal, sizeof(LOGFONT), &lf); lf.lfWeight = FW_BOLD; g_fBold = CreateFontIndirect(&lf); /* Initialize Prolog. */ char *argv[] = { (char *)"EpisodeBrowser", NULL }; if (!PL_initialise(1, argv)) throw std::runtime_error("Could not initialize Prolog."); if (!Pl("track_episodes","attach") || !Pl("episode_data","attach")) throw std::runtime_error("Could not attach databases."); /* Initialize common controls, load menu and register window class. */ INITCOMMONCONTROLSEX icc; icc.dwSize = sizeof(icc); icc.dwICC = ICC_WIN95_CLASSES; if (!InitCommonControlsEx(&icc)) throw std::runtime_error("Could not initialize common controls."); g_hPopupMenu = LoadMenu(NULL, MAKEINTRESOURCE(IDR_POPUPMENU)); g_hPopupMenu = GetSubMenu(g_hPopupMenu, 0); WNDCLASSEX wc = {0}; 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); if (!RegisterClassEx(&wc)) throw std::runtime_error("Could not register window class."); /* Create window. Note that a CBT hook is used to initialize * important global variables before any messages are sent to * the new window. */ const HHOOK hHook = SetWindowsHookEx(WH_CBT, CBTProc, 0, GetCurrentThreadId()); const HWND hWnd = CreateWindowEx( 0, TEXT("Episode Browser"), TEXT("Episode Browser"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, NULL, hInstance, NULL ); UnhookWindowsHookEx(hHook); if (!hWnd) throw std::runtime_error("Could not create main window."); g_hWndStatus = CreateStatusBar(hWnd, hInstance); ShowWindow(hWnd, nCmdShow); /* Populate episode list view. */ Pl("track_episodes","update_tracked_episodes"); g_lpElv->Update(); g_lpElv->RestoreFocus(); MSG msg; while (GetMessage(&msg, NULL, 0, 0) > 0) { if (IsDialogMessage(hWnd, &msg)) continue; TranslateMessage(&msg); DispatchMessage(&msg); } PL_halt(0); return 0; } void OnTerminate() { try { std::rethrow_exception(std::current_exception()); } catch (const term_t &t) { TCHAR *tsz; if (PL_get_tchars(t, &tsz, CVT_WRITE)) { MessageBox(NULL, tsz, TEXT("Fatal Error"), MB_ICONERROR); } else MessageBoxA(NULL, "The program was terminated due to a Prolog exception.", "Fatal Error", MB_ICONERROR); } catch (std::exception &e) { MessageBoxA(NULL, e.what(), "Fatal Error", MB_ICONERROR); } catch (...) { MessageBoxA(NULL, "The program was terminated due to an exception.", "Fatal Error", MB_ICONERROR); } std::_Exit(1); } static LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode != HCBT_CREATEWND) return 0; if (g_hWnd) return 0; /* This code is run once at the creation of the top-level * window -- before WndProc! This is important, as it * initializes global variables that are used by WndProc. */ g_hWnd = (HWND)wParam; /* Get DPI. */ if (auto opLib = try_make(TEXT("User32.dll"))) if (auto GetDpiForWindow = (UINT (*)(HWND))opLib->GetProcAddress("GetDpiForWindow")) g_iDPI = GetDpiForWindow(g_hWnd); /* Create child windows. */ g_lpDlv = new DataListView(g_hWnd); g_lpElv = new EpisodeListView(g_hWnd); /* Get saved view settings. */ char *sz; Pl("cfg","get_view_watched",&g_bViewWatched); Pl("cfg","get_view_tv_original",&g_bViewTVOriginal); if (Pl("cfg","get_limit_screenwriter",&sz)) strcpy_s(g_szLimitScreenwriter, sizeof(g_szLimitScreenwriter), sz); return 0; } LRESULT CALLBACK WndProc(const HWND hWnd, const UINT uMsg, const WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: { UpdateTheme(); SetWindowPos(hWnd, NULL, -1, -1, Dpi(510), Dpi(400), SWP_NOMOVE); SetFocus(g_lpElv->Handle()); /* Set menu item checkmarks according to saved settings. */ CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED, g_bViewWatched? MF_CHECKED: MF_UNCHECKED); CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL, g_bViewTVOriginal? MF_CHECKED: MF_UNCHECKED); CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, g_szLimitScreenwriter[0]? MF_UNCHECKED: MF_CHECKED); break; } case WM_CLOSE: DestroyWindow(hWnd); break; case WM_DESTROY: g_lpElv->SaveFocus(); PostQuitMessage(0); break; case WM_SIZE: SendMessage(g_hWndStatus, WM_SIZE, wParam, lParam); UpdateLayout(); break; case WM_GETMINMAXINFO: { LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam; lpMMI->ptMinTrackSize.x = Dpi(220); lpMMI->ptMinTrackSize.y = Dpi(220); break; } case WM_THEMECHANGED: UpdateTheme(); break; case 0x02E0: /* WM_DPICHANGED */ { const LPRECT 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_lpElv->Redraw(); } break; case WM_NOTIFY: switch (((LPNMHDR)lParam)->idFrom) { case IDC_EPISODELISTVIEW: return g_lpElv->HandleNotify(lParam); } break; case WM_TIMER: switch (wParam) { case IDT_TIMER: { static int i = 0; /* Animate ellipsis in status bar while doing * work in other thread. */ if (Pl("episode_data","thread_running",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_lpElv->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_lpElv->Update(); break; case IDM_FILE_FETCH_DATA: if (g_bThread) break; Pl("episode_data","thread_create","update_episode_data",&g_aThread); goto t; case IDM_FILE_FETCH_SCREENWRITERS: if (g_bThread) break; Pl("episode_data","thread_create","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: CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED, g_bViewWatched? MF_UNCHECKED: MF_CHECKED); g_bViewWatched = !g_bViewWatched; Pl("cfg","set_view_watched",g_bViewWatched); g_lpElv->Update(); g_lpElv->EnsureFocusVisible(); break; case IDM_VIEW_TV_ORIGINAL: CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL, g_bViewTVOriginal? MF_UNCHECKED: MF_CHECKED); g_bViewTVOriginal = !g_bViewTVOriginal; Pl("cfg","set_view_tv_original",g_bViewTVOriginal); g_lpElv->Update(); g_lpElv->EnsureFocusVisible(); break; case IDM_VIEW_OTHERS: /* Show/hide other screenwriters. */ if (g_szLimitScreenwriter[0]) { CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, MF_CHECKED); g_szLimitScreenwriter[0] = 0; } else { const int iEpFocus = ListView_GetNextItem(g_lpElv->Handle(), -1, LVNI_FOCUSED); if (iEpFocus == -1) break; LVITEM lvi; lvi.iItem = iEpFocus; lvi.mask = LVIF_PARAM; if (!ListView_GetItem(g_lpElv->Handle(), &lvi)) break; char *sz; if (!Pl("episode_data","episode_datum",lvi.lParam,"Screenwriter",&sz)) break; strcpy_s(g_szLimitScreenwriter, sizeof(g_szLimitScreenwriter), sz); CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, MF_UNCHECKED); } Pl("cfg","set_limit_screenwriter",g_szLimitScreenwriter); g_lpElv->Update(); g_lpElv->EnsureFocusVisible(); 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; /* Look through selected items, applying the * selected command to each one. */ lvi.mask = LVIF_PARAM; lvi.iItem = -1; while ((lvi.iItem = ListView_GetNextItem( g_lpElv->Handle(), lvi.iItem, LVNI_SELECTED)) != -1) { if (!ListView_GetItem(g_lpElv->Handle(), &lvi)) goto b; switch (LOWORD(wParam)) { case IDM_WATCH_LOCALLY: Pl("local_episode","open_episode_locally",lvi.lParam); break; case IDM_WATCH_ONLINE: Pl("local_episode","open_episode_online",lvi.lParam); break; case IDM_TOGGLE: Pl("track_episodes","toggle_episode",lvi.lParam); g_lpElv->Redraw(); break; case IDM_FORGET: Pl("track_episodes","forget_episode",lvi.lParam); Pl("track_episodes","update_tracked_episodes"); g_lpElv->Redraw(); break; case IDM_LOOKUP: Pl("episode_data","retract_episode",lvi.lParam); g_lpElv->UpdateItem(&lvi); g_lpElv->Redraw(); g_lpDlv->ShowEpisode(lvi.lParam); break; case IDM_WIKI: Pl("episode_data","open_episode_wiki",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",lvi.lParam,iRating); g_lpElv->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_lpElv->Redraw(); g_lpElv->DoSort(); g_lpElv->ShowFocus(); } b: break; } } break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; } INT_PTR CALLBACK AboutDlgProc(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const 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(const HWND hWndParent, const HINSTANCE hInstance) { return CreateWindowEx( 0, STATUSCLASSNAME, (LPCTSTR) NULL, WS_CHILD|WS_VISIBLE|SBARS_SIZEGRIP, 0, 0, 0, 0, hWndParent, (HMENU)ID_STATUS, hInstance, NULL ); } /***/ void UpdateLayout() { int cxColumn, cyDlv, yStatus; RECT rc, rcStatus; GetClientRect(g_hWnd, &rc); GetClientRect(g_hWndStatus, &rcStatus); yStatus = rcStatus.bottom-rcStatus.top; /* Resize data list view. */ SendMessage(g_lpDlv->Handle(), WM_SETREDRAW, FALSE, 0); SendMessage(g_lpElv->Handle(), WM_SETREDRAW, FALSE, 0); cyDlv = rc.bottom-yStatus-g_lpDlv->Height(); MoveWindow(g_lpDlv->Handle(), 0, cyDlv, rc.right, rc.bottom-yStatus-cyDlv, TRUE); ListView_SetColumnWidth(g_lpDlv->Handle(), DLVSIKEY, LVSCW_AUTOSIZE); cxColumn = ListView_GetColumnWidth(g_lpDlv->Handle(), 0)+4; ListView_SetColumnWidth(g_lpDlv->Handle(), DLVSIKEY, cxColumn); ListView_SetColumnWidth(g_lpDlv->Handle(), DLVSIVALUE, rc.right-cxColumn-g_cxVScroll-4); /* Resize episode list view. */ MoveWindow(g_lpElv->Handle(), 0, 0, rc.right, cyDlv+1, TRUE); ListView_SetColumnWidth(g_lpElv->Handle(), ELVSIEPISODE, LVSCW_AUTOSIZE); cxColumn = ListView_GetColumnWidth(g_lpElv->Handle(), ELVSIEPISODE)+4; ListView_SetColumnWidth(g_lpElv->Handle(), ELVSIEPISODE, cxColumn); cxColumn += ListView_GetColumnWidth(g_lpElv->Handle(), ELVSIRATING); ListView_SetColumnWidth(g_lpElv->Handle(), ELVSITITLE, rc.right-cxColumn-g_cxVScroll-4); SendMessage(g_lpElv->Handle(), WM_SETREDRAW, TRUE, 0); SendMessage(g_lpDlv->Handle(), WM_SETREDRAW, TRUE, 0); /* Resize status bar parts. */ const 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() { if (!g_bThemes) return; const int bThemeActive = IsThemeActive(); g_lpDlv->UpdateTheme(bThemeActive); g_lpElv->UpdateTheme(bThemeActive); }