/* * PROJECT: ReactOS Applications Manager * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: GUI classes for RAPPS * COPYRIGHT: Copyright 2015 David Quintana (gigaherz@gmail.com) * Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org) * Copyright 2020 He Yang (1160386205@qq.com) */ #include "rapps.h" #include "rosui.h" #include "crichedit.h" #include "asyncinet.h" #include "gui.h" #include #include #include #include #include #include #include #include #include #include #include #define SEARCH_TIMER_ID 'SR' #define TREEVIEW_ICON_SIZE 24 // **** CSideTreeView **** CSideTreeView::CSideTreeView() : CUiWindow(), hImageTreeView(ImageList_Create(TREEVIEW_ICON_SIZE, TREEVIEW_ICON_SIZE, GetSystemColorDepth() | ILC_MASK, 0, 1)) { } HTREEITEM CSideTreeView::AddItem(HTREEITEM hParent, CStringW &Text, INT Image, INT SelectedImage, LPARAM lParam) { return CUiWindow::AddItem(hParent, const_cast(Text.GetString()), Image, SelectedImage, lParam); } HTREEITEM CSideTreeView::AddCategory(HTREEITEM hRootItem, UINT TextIndex, UINT IconIndex) { CStringW szText; INT Index = 0; HICON hIcon; hIcon = (HICON)LoadImageW( hInst, MAKEINTRESOURCE(IconIndex), IMAGE_ICON, TREEVIEW_ICON_SIZE, TREEVIEW_ICON_SIZE, LR_CREATEDIBSECTION); if (hIcon) { Index = ImageList_AddIcon(hImageTreeView, hIcon); DestroyIcon(hIcon); } szText.LoadStringW(TextIndex); return AddItem(hRootItem, szText, Index, Index, TextIndex); } HIMAGELIST CSideTreeView::SetImageList() { return CUiWindow::SetImageList(hImageTreeView, TVSIL_NORMAL); } VOID CSideTreeView::DestroyImageList() { if (hImageTreeView) ImageList_Destroy(hImageTreeView); } CSideTreeView::~CSideTreeView() { DestroyImageList(); } // **** CSideTreeView **** // **** CMainWindow **** CMainWindow::CMainWindow(CAppDB *db, BOOL bAppwiz) : m_ClientPanel(NULL), m_Db(db), bAppwizMode(bAppwiz), SelectedEnumType(ENUM_ALL_INSTALLED) { } CMainWindow::~CMainWindow() { LayoutCleanup(); } VOID CMainWindow::InitCategoriesList() { HTREEITEM hRootItemAvailable; hRootItemInstalled = m_TreeView->AddCategory(TVI_ROOT, IDS_INSTALLED, IDI_CATEGORY); m_TreeView->AddCategory(hRootItemInstalled, IDS_APPLICATIONS, IDI_APPS); m_TreeView->AddCategory(hRootItemInstalled, IDS_UPDATES, IDI_APPUPD); m_TreeView->AddCategory(TVI_ROOT, IDS_SELECTEDFORINST, IDI_SELECTEDFORINST); hRootItemAvailable = m_TreeView->AddCategory(TVI_ROOT, IDS_AVAILABLEFORINST, IDI_CATEGORY); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_AUDIO, IDI_CAT_AUDIO); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_VIDEO, IDI_CAT_VIDEO); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_GRAPHICS, IDI_CAT_GRAPHICS); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_GAMES, IDI_CAT_GAMES); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_INTERNET, IDI_CAT_INTERNET); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_OFFICE, IDI_CAT_OFFICE); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_DEVEL, IDI_CAT_DEVEL); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_EDU, IDI_CAT_EDU); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_ENGINEER, IDI_CAT_ENGINEER); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_FINANCE, IDI_CAT_FINANCE); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_SCIENCE, IDI_CAT_SCIENCE); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_TOOLS, IDI_CAT_TOOLS); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_DRIVERS, IDI_CAT_DRIVERS); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_LIBS, IDI_CAT_LIBS); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_THEMES, IDI_CAT_THEMES); m_TreeView->AddCategory(hRootItemAvailable, IDS_CAT_OTHER, IDI_CAT_OTHER); m_TreeView->SetImageList(); m_TreeView->Expand(hRootItemInstalled, TVE_EXPAND); m_TreeView->Expand(hRootItemAvailable, TVE_EXPAND); m_TreeView->SelectItem(bAppwizMode ? hRootItemInstalled : hRootItemAvailable); } BOOL CMainWindow::CreateStatusBar() { m_StatusBar = new CUiWindow(); m_StatusBar->m_VerticalAlignment = UiAlign_RightBtm; m_StatusBar->m_HorizontalAlignment = UiAlign_Stretch; m_ClientPanel->Children().Append(m_StatusBar); return m_StatusBar->Create(m_hWnd, (HMENU)IDC_STATUSBAR) != NULL; } BOOL CMainWindow::CreateTreeView() { m_TreeView = new CSideTreeView(); m_TreeView->m_VerticalAlignment = UiAlign_Stretch; m_TreeView->m_HorizontalAlignment = UiAlign_Stretch; m_VSplitter->First().Append(m_TreeView); return m_TreeView->Create(m_hWnd) != NULL; } BOOL CMainWindow::CreateApplicationView() { m_ApplicationView = new CApplicationView(this); // pass this to ApplicationView for callback purpose m_ApplicationView->m_VerticalAlignment = UiAlign_Stretch; m_ApplicationView->m_HorizontalAlignment = UiAlign_Stretch; m_VSplitter->Second().Append(m_ApplicationView); return m_ApplicationView->Create(m_hWnd) != NULL; } BOOL CMainWindow::CreateVSplitter() { m_VSplitter = new CUiSplitPanel(); m_VSplitter->m_VerticalAlignment = UiAlign_Stretch; m_VSplitter->m_HorizontalAlignment = UiAlign_Stretch; m_VSplitter->m_DynamicFirst = FALSE; m_VSplitter->m_Horizontal = FALSE; m_VSplitter->m_MinFirst = 0; // TODO: m_MinSecond should be calculate dynamically instead of hard-coded m_VSplitter->m_MinSecond = 480; m_VSplitter->m_Pos = 240; m_ClientPanel->Children().Append(m_VSplitter); return m_VSplitter->Create(m_hWnd) != NULL; } BOOL CMainWindow::CreateLayout() { BOOL b = TRUE; bUpdating = TRUE; m_ClientPanel = new CUiPanel(); m_ClientPanel->m_VerticalAlignment = UiAlign_Stretch; m_ClientPanel->m_HorizontalAlignment = UiAlign_Stretch; // Top level b = b && CreateStatusBar(); b = b && CreateVSplitter(); // Inside V Splitter b = b && CreateTreeView(); b = b && CreateApplicationView(); if (b) { RECT rBottom; /* Size status bar */ m_StatusBar->SendMessageW(WM_SIZE, 0, 0); ::GetWindowRect(m_StatusBar->m_hWnd, &rBottom); m_VSplitter->m_Margin.bottom = rBottom.bottom - rBottom.top; } bUpdating = FALSE; return b; } VOID CMainWindow::LayoutCleanup() { delete m_TreeView; delete m_ApplicationView; delete m_VSplitter; delete m_StatusBar; return; } BOOL CMainWindow::InitControls() { if (CreateLayout()) { InitCategoriesList(); UpdateStatusBarText(); return TRUE; } return FALSE; } VOID CMainWindow::OnSize(HWND hwnd, WPARAM wParam, LPARAM lParam) { if (wParam == SIZE_MINIMIZED) return; /* Size status bar */ m_StatusBar->SendMessage(WM_SIZE, 0, 0); RECT r = {0, 0, LOWORD(lParam), HIWORD(lParam)}; HDWP hdwp = NULL; INT count = m_ClientPanel->CountSizableChildren(); hdwp = BeginDeferWindowPos(count); if (hdwp) { hdwp = m_ClientPanel->OnParentSize(r, hdwp); if (hdwp) { EndDeferWindowPos(hdwp); } } } BOOL CMainWindow::RemoveSelectedAppFromRegistry() { if (!IsInstalledEnum(SelectedEnumType)) return FALSE; CStringW szMsgText, szMsgTitle; if (!szMsgText.LoadStringW(IDS_APP_REG_REMOVE) || !szMsgTitle.LoadStringW(IDS_INFORMATION)) return FALSE; CAppInfo *InstalledApp = (CAppInfo *)m_ApplicationView->GetFocusedItemData(); if (!InstalledApp) return FALSE; if (MessageBoxW(szMsgText, szMsgTitle, MB_YESNO | MB_ICONQUESTION) == IDYES) return m_Db->RemoveInstalledAppFromRegistry(InstalledApp); return FALSE; } BOOL CMainWindow::UninstallSelectedApp(BOOL bModify) { if (!IsInstalledEnum(SelectedEnumType)) return FALSE; CAppInfo *InstalledApp = (CAppInfo *)m_ApplicationView->GetFocusedItemData(); if (!InstalledApp) return FALSE; return InstalledApp->UninstallApplication(bModify); } VOID CMainWindow::CheckAvailable() { if (m_Db->GetAvailableCount() == 0) { m_Db->RemoveCached(); m_Db->UpdateAvailable(); } } BOOL CMainWindow::ProcessWindowMessage(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT &theResult, DWORD dwMapId) { theResult = 0; switch (Msg) { case WM_CREATE: if (!InitControls()) ::PostMessageW(hwnd, WM_CLOSE, 0, 0); break; case WM_DESTROY: { ShowWindow(SW_HIDE); SaveSettings(hwnd, &SettingsInfo); FreeLogs(); delete m_ClientPanel; PostQuitMessage(0); return 0; } case WM_COMMAND: OnCommand(wParam, lParam); break; case WM_NOTIFY: { LPNMHDR data = (LPNMHDR)lParam; switch (data->code) { case TVN_SELCHANGED: { if (data->hwndFrom == m_TreeView->m_hWnd) { switch (((LPNMTREEVIEW)lParam)->itemNew.lParam) { case IDS_INSTALLED: UpdateApplicationsList(ENUM_ALL_INSTALLED); break; case IDS_APPLICATIONS: UpdateApplicationsList(ENUM_INSTALLED_APPLICATIONS); break; case IDS_UPDATES: UpdateApplicationsList(ENUM_UPDATES); break; case IDS_AVAILABLEFORINST: UpdateApplicationsList(ENUM_ALL_AVAILABLE, FALSE, TRUE); break; case IDS_CAT_AUDIO: UpdateApplicationsList(ENUM_CAT_AUDIO, FALSE, TRUE); break; case IDS_CAT_DEVEL: UpdateApplicationsList(ENUM_CAT_DEVEL, FALSE, TRUE); break; case IDS_CAT_DRIVERS: UpdateApplicationsList(ENUM_CAT_DRIVERS, FALSE, TRUE); break; case IDS_CAT_EDU: UpdateApplicationsList(ENUM_CAT_EDU, FALSE, TRUE); break; case IDS_CAT_ENGINEER: UpdateApplicationsList(ENUM_CAT_ENGINEER, FALSE, TRUE); break; case IDS_CAT_FINANCE: UpdateApplicationsList(ENUM_CAT_FINANCE, FALSE, TRUE); break; case IDS_CAT_GAMES: UpdateApplicationsList(ENUM_CAT_GAMES, FALSE, TRUE); break; case IDS_CAT_GRAPHICS: UpdateApplicationsList(ENUM_CAT_GRAPHICS, FALSE, TRUE); break; case IDS_CAT_INTERNET: UpdateApplicationsList(ENUM_CAT_INTERNET, FALSE, TRUE); break; case IDS_CAT_LIBS: UpdateApplicationsList(ENUM_CAT_LIBS, FALSE, TRUE); break; case IDS_CAT_OFFICE: UpdateApplicationsList(ENUM_CAT_OFFICE, FALSE, TRUE); break; case IDS_CAT_OTHER: UpdateApplicationsList(ENUM_CAT_OTHER, FALSE, TRUE); break; case IDS_CAT_SCIENCE: UpdateApplicationsList(ENUM_CAT_SCIENCE, FALSE, TRUE); break; case IDS_CAT_TOOLS: UpdateApplicationsList(ENUM_CAT_TOOLS, FALSE, TRUE); break; case IDS_CAT_VIDEO: UpdateApplicationsList(ENUM_CAT_VIDEO, FALSE, TRUE); break; case IDS_CAT_THEMES: UpdateApplicationsList(ENUM_CAT_THEMES, FALSE, TRUE); break; case IDS_SELECTEDFORINST: UpdateApplicationsList(ENUM_CAT_SELECTED); break; } } } break; } } break; case WM_SIZE: OnSize(hwnd, wParam, lParam); break; case WM_SIZING: { LPRECT pRect = (LPRECT)lParam; if (pRect->right - pRect->left < 565) pRect->right = pRect->left + 565; if (pRect->bottom - pRect->top < 300) pRect->bottom = pRect->top + 300; return TRUE; } case WM_SYSCOLORCHANGE: { /* Forward WM_SYSCOLORCHANGE to common controls */ m_ApplicationView->SendMessageW(WM_SYSCOLORCHANGE, wParam, lParam); m_TreeView->SendMessageW(WM_SYSCOLORCHANGE, wParam, lParam); } break; case WM_TIMER: if (wParam == SEARCH_TIMER_ID) { ::KillTimer(hwnd, SEARCH_TIMER_ID); UpdateApplicationsList(SelectedEnumType); } break; } return FALSE; } VOID CMainWindow::ShowAboutDlg() { CStringW szApp; CStringW szAuthors; szApp.LoadStringW(IDS_APPTITLE); szAuthors.LoadStringW(IDS_APP_AUTHORS); ShellAboutW(m_hWnd, szApp, szAuthors, LoadIconW(hInst, MAKEINTRESOURCEW(IDI_MAIN))); } VOID CMainWindow::OnCommand(WPARAM wParam, LPARAM lParam) { const BOOL bReload = TRUE; WORD wCommand = LOWORD(wParam); if (!lParam) { switch (wCommand) { case ID_SETTINGS: CreateSettingsDlg(m_hWnd); break; case ID_EXIT: PostMessageW(WM_CLOSE, 0, 0); break; case ID_SEARCH: m_ApplicationView->SetFocusOnSearchBar(); break; case ID_INSTALL: if (IsAvailableEnum(SelectedEnumType)) { if (!m_Selected.IsEmpty()) { if (DownloadListOfApplications(m_Selected, FALSE)) { m_Selected.RemoveAll(); UpdateApplicationsList(SelectedEnumType); } } else { CAppInfo *App = (CAppInfo *)m_ApplicationView->GetFocusedItemData(); if (App) { InstallApplication(App); } } } break; case ID_UNINSTALL: if (UninstallSelectedApp(FALSE)) UpdateApplicationsList(SelectedEnumType, bReload); break; case ID_MODIFY: if (UninstallSelectedApp(TRUE)) UpdateApplicationsList(SelectedEnumType, bReload); break; case ID_REGREMOVE: if (RemoveSelectedAppFromRegistry()) UpdateApplicationsList(SelectedEnumType, bReload); break; case ID_REFRESH: UpdateApplicationsList(SelectedEnumType); break; case ID_RESETDB: m_Db->RemoveCached(); UpdateApplicationsList(SelectedEnumType, bReload); break; case ID_HELP: MessageBoxW(L"Help not implemented yet", NULL, MB_OK); break; case ID_ABOUT: ShowAboutDlg(); break; case ID_CHECK_ALL: m_ApplicationView->CheckAll(); break; case ID_ACTIVATE_APPWIZ: if (hRootItemInstalled) m_TreeView->SelectItem(hRootItemInstalled); break; } } } VOID CMainWindow::UpdateStatusBarText() { if (m_StatusBar) { CStringW szBuffer; szBuffer.Format(IDS_APPS_COUNT, m_ApplicationView->GetItemCount(), m_Selected.GetCount()); m_StatusBar->SetText(szBuffer); } } VOID CMainWindow::AddApplicationsToView(CAtlList &List) { POSITION CurrentListPosition = List.GetHeadPosition(); while (CurrentListPosition) { CAppInfo *Info = List.GetNext(CurrentListPosition); if (szSearchPattern.IsEmpty() || SearchPatternMatch(Info->szDisplayName, szSearchPattern) || SearchPatternMatch(Info->szComments, szSearchPattern)) { BOOL bSelected = m_Selected.Find(Info) != NULL; m_ApplicationView->AddApplication(Info, bSelected); } } } VOID CMainWindow::UpdateApplicationsList(AppsCategories EnumType, BOOL bReload, BOOL bCheckAvailable) { bUpdating = TRUE; if (bCheckAvailable) CheckAvailable(); if (SelectedEnumType != EnumType) SelectedEnumType = EnumType; if (bReload) m_Selected.RemoveAll(); m_ApplicationView->SetRedraw(FALSE); if (IsInstalledEnum(EnumType)) { if (bReload) m_Db->UpdateInstalled(); // set the display type of application-view. this will remove all the item in application-view too. m_ApplicationView->SetDisplayAppType(AppViewTypeInstalledApps); CAtlList List; m_Db->GetApps(List, EnumType); AddApplicationsToView(List); } else if (IsAvailableEnum(EnumType)) { if (bReload) m_Db->UpdateAvailable(); // set the display type of application-view. this will remove all the item in application-view too. m_ApplicationView->SetDisplayAppType(AppViewTypeAvailableApps); // enum available softwares if (EnumType == ENUM_CAT_SELECTED) { AddApplicationsToView(m_Selected); } else { CAtlList List; m_Db->GetApps(List, EnumType); AddApplicationsToView(List); } } else { ATLASSERT(0 && "This should be unreachable!"); } m_ApplicationView->SetRedraw(TRUE); m_ApplicationView->RedrawWindow(0, 0, RDW_INVALIDATE | RDW_ALLCHILDREN); // force the child window to repaint UpdateStatusBarText(); CStringW text; if (m_ApplicationView->GetItemCount() == 0 && !szSearchPattern.IsEmpty()) { text.LoadString(IDS_NO_SEARCH_RESULTS); } m_ApplicationView->SetWatermark(text); bUpdating = FALSE; } ATL::CWndClassInfo & CMainWindow::GetWndClassInfo() { DWORD csStyle = CS_VREDRAW | CS_HREDRAW; static ATL::CWndClassInfo wc = { {sizeof(WNDCLASSEX), csStyle, StartWindowProc, 0, 0, NULL, LoadIconW(_AtlBaseModule.GetModuleInstance(), MAKEINTRESOURCEW(IDI_MAIN)), LoadCursorW(NULL, IDC_ARROW), (HBRUSH)(COLOR_BTNFACE + 1), MAKEINTRESOURCEW(IDR_MAINMENU), szWindowClass, NULL}, NULL, NULL, IDC_ARROW, TRUE, 0, _T("")}; return wc; } HWND CMainWindow::Create() { CStringW szWindowName; szWindowName.LoadStringW(IDS_APPTITLE); RECT r = { (SettingsInfo.bSaveWndPos ? SettingsInfo.Left : CW_USEDEFAULT), (SettingsInfo.bSaveWndPos ? SettingsInfo.Top : CW_USEDEFAULT), (SettingsInfo.bSaveWndPos ? SettingsInfo.Width : 680), (SettingsInfo.bSaveWndPos ? SettingsInfo.Height : 450)}; r.right += r.left; r.bottom += r.top; return CWindowImpl::Create( NULL, r, szWindowName.GetString(), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_WINDOWEDGE); } // this function is called when a item of application-view is checked/unchecked // CallbackParam is the param passed to application-view when adding the item (the one getting focus now). VOID CMainWindow::ItemCheckStateChanged(BOOL bChecked, LPVOID CallbackParam) { if (bUpdating) return; CAppInfo *Info = (CAppInfo *)CallbackParam; if (bChecked) { m_Selected.AddTail(Info); } else { POSITION Pos = m_Selected.Find(Info); ATLASSERT(Pos != NULL); if (Pos != NULL) { m_Selected.RemoveAt(Pos); } } UpdateStatusBarText(); } // this function is called when one or more application(s) should be installed // if Info is not zero, this app should be installed. otherwise those checked apps should be installed BOOL CMainWindow::InstallApplication(CAppInfo *Info) { if (Info) { if (DownloadApplication(Info)) { UpdateApplicationsList(SelectedEnumType); return TRUE; } } return FALSE; } BOOL CMainWindow::SearchTextChanged(CStringW &SearchText) { if (szSearchPattern == SearchText) { return FALSE; } szSearchPattern = SearchText; DWORD dwDelay = 0; SystemParametersInfoW(SPI_GETMENUSHOWDELAY, 0, &dwDelay, 0); SetTimer(SEARCH_TIMER_ID, dwDelay); return TRUE; } void CMainWindow::HandleTabOrder(int direction) { ATL::CSimpleArray TabOrderHwndList; m_TreeView->AppendTabOrderWindow(direction, TabOrderHwndList); m_ApplicationView->AppendTabOrderWindow(direction, TabOrderHwndList); if (TabOrderHwndList.GetSize() == 0) { // in case the list is empty return; } int FocusIndex; if ((FocusIndex = TabOrderHwndList.Find(GetFocus())) == -1) { FocusIndex = 0; // focus the first window in the list } else { FocusIndex += direction; FocusIndex += TabOrderHwndList.GetSize(); // FocusIndex might be negative. we don't want to mod a negative number FocusIndex %= TabOrderHwndList.GetSize(); } ::SetFocus(TabOrderHwndList[FocusIndex]); return; } // **** CMainWindow **** VOID MainWindowLoop(CMainWindow *wnd, INT nShowCmd) { HACCEL KeyBrd; MSG Msg; hMainWnd = wnd->Create(); if (!hMainWnd) return; /* Maximize it if we must */ wnd->ShowWindow((SettingsInfo.bSaveWndPos && SettingsInfo.Maximized) ? SW_MAXIMIZE : nShowCmd); wnd->UpdateWindow(); /* Load the menu hotkeys */ KeyBrd = LoadAcceleratorsW(NULL, MAKEINTRESOURCEW(HOTKEYS)); /* Message Loop */ while (GetMessageW(&Msg, NULL, 0, 0)) { if (!TranslateAcceleratorW(hMainWnd, KeyBrd, &Msg)) { if (Msg.message == WM_CHAR && Msg.wParam == VK_TAB) { // Move backwards if shift is held down int direction = (GetKeyState(VK_SHIFT) & 0x8000) ? -1 : 1; wnd->HandleTabOrder(direction); continue; } TranslateMessage(&Msg); DispatchMessageW(&Msg); } } }