diff --git a/packages/media/cpp/CMakeLists.txt b/packages/media/cpp/CMakeLists.txt index e9f679bb..593da7fd 100644 --- a/packages/media/cpp/CMakeLists.txt +++ b/packages/media/cpp/CMakeLists.txt @@ -157,6 +157,55 @@ if(WIN32) "${CMAKE_CURRENT_SOURCE_DIR}/src/win/ui_singleton.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/win/media-img-win.manifest" ) + + # ── ui_next (Win32++ ribbon + dock UI) ────────────────────────────── + set(_UI_NEXT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/win/ui_next") + + # Try to compile Ribbon.xml → Ribbon.bml + RibbonUI.h + RibbonUI.rc via uicc.exe. + find_program(UICC_EXE uicc + HINTS "C:/Program Files (x86)/Windows Kits/10/bin" + "$ENV{WindowsSdkDir}/bin" + PATH_SUFFIXES "${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/x64" + "${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/x86" + "10.0.22621.0/x64" "10.0.22621.0/x86" + "10.0.19041.0/x64" "10.0.19041.0/x86" + ) + + if(UICC_EXE) + message(STATUS "uicc found: ${UICC_EXE} — ribbon resources will be compiled") + add_custom_command( + OUTPUT "${_UI_NEXT_DIR}/Ribbon.bml" + "${_UI_NEXT_DIR}/RibbonUI.h" + "${_UI_NEXT_DIR}/RibbonUI.rc" + COMMAND "${UICC_EXE}" + "${_UI_NEXT_DIR}/Ribbon.xml" + "${_UI_NEXT_DIR}/Ribbon.bml" + "/header:${_UI_NEXT_DIR}/RibbonUI.h" + "/res:${_UI_NEXT_DIR}/RibbonUI.rc" + DEPENDS "${_UI_NEXT_DIR}/Ribbon.xml" + COMMENT "Compiling Ribbon.xml → Ribbon.bml + RibbonUI.h/rc (uicc)" + VERBATIM + ) + add_custom_target(pm_image_ribbon DEPENDS + "${_UI_NEXT_DIR}/Ribbon.bml" + "${_UI_NEXT_DIR}/RibbonUI.h" + "${_UI_NEXT_DIR}/RibbonUI.rc" + ) + else() + message(STATUS "uicc NOT found — ribbon disabled; classic menu fallback will be used") + add_custom_target(pm_image_ribbon) + endif() + + target_sources(pm-image PRIVATE + "${_UI_NEXT_DIR}/App.cpp" + "${_UI_NEXT_DIR}/FileQueue.cpp" + "${_UI_NEXT_DIR}/SettingsPanel.cpp" + "${_UI_NEXT_DIR}/Mainfrm.cpp" + "${_UI_NEXT_DIR}/launch_ui_next.cpp" + "${_UI_NEXT_DIR}/Resource.rc" + ) + add_dependencies(pm-image pm_image_ribbon) + target_include_directories(pm-image PRIVATE "${_UI_NEXT_DIR}") endif() # Win32++ — header-only (vendor tree under packages/Win32xx). See samples e.g. diff --git a/packages/media/cpp/dist/pm-image.exe b/packages/media/cpp/dist/pm-image.exe index bac983ee..1019ef2a 100644 Binary files a/packages/media/cpp/dist/pm-image.exe and b/packages/media/cpp/dist/pm-image.exe differ diff --git a/packages/media/cpp/dist/pm-image.pdb b/packages/media/cpp/dist/pm-image.pdb index e4e70439..7f6a3e38 100644 Binary files a/packages/media/cpp/dist/pm-image.pdb and b/packages/media/cpp/dist/pm-image.pdb differ diff --git a/packages/media/cpp/src/main.cpp b/packages/media/cpp/src/main.cpp index 6a1550c8..693d3d00 100644 --- a/packages/media/cpp/src/main.cpp +++ b/packages/media/cpp/src/main.cpp @@ -35,6 +35,7 @@ std::string join_src_semicolons(const std::vector &v) { #include "win/resize_progress_ui.hpp" #include "win/resize_ui.hpp" #include "win/ui_singleton.hpp" +#include "win/ui_next/launch_ui_next.h" #endif #ifndef MEDIA_IMG_VERSION @@ -139,6 +140,9 @@ int main(int argc, char **argv) { bool resize_ui = false; resize_cmd->add_flag("--ui", resize_ui, "Windows: native dialog for paths/options; optional --src/--dst seed the dialog"); + bool resize_ui_next = false; + resize_cmd->add_flag("--ui-next", resize_ui_next, + "Windows: new Win32++ ribbon UI with drag-drop queue and settings panel"); #endif std::string host = "127.0.0.1"; @@ -217,6 +221,15 @@ int main(int argc, char **argv) { std::string out_path_e; #if defined(_WIN32) + if (resize_ui_next) { + std::vector initial; + if (!src_list.empty()) + initial = src_list; + else if (!in_path.empty()) + initial.push_back(in_path); + return media::win::launch_ui_next(initial); + } + if (resize_ui) { if (!src_list.empty() && !in_path.empty()) { std::cerr << "resize: use either positional input or --src, not both\n"; diff --git a/packages/media/cpp/src/win/ui_next/App.cpp b/packages/media/cpp/src/win/ui_next/App.cpp new file mode 100644 index 00000000..a5b6eb17 --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/App.cpp @@ -0,0 +1,8 @@ +#include "stdafx.h" +#include "App.h" + +BOOL CPmImageApp::InitInstance() +{ + m_frame.Create(); + return TRUE; +} diff --git a/packages/media/cpp/src/win/ui_next/App.h b/packages/media/cpp/src/win/ui_next/App.h new file mode 100644 index 00000000..88189443 --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/App.h @@ -0,0 +1,24 @@ +#ifndef PM_UI_APP_H +#define PM_UI_APP_H + +#include "Mainfrm.h" + +class CPmImageApp : public CWinApp +{ +public: + CPmImageApp() = default; + virtual ~CPmImageApp() override = default; + + CMainFrame& GetMainFrame() { return m_frame; } + +protected: + virtual BOOL InitInstance() override; + +private: + CPmImageApp(const CPmImageApp&) = delete; + CPmImageApp& operator=(const CPmImageApp&) = delete; + + CMainFrame m_frame; +}; + +#endif // PM_UI_APP_H diff --git a/packages/media/cpp/src/win/ui_next/DropView.cpp b/packages/media/cpp/src/win/ui_next/DropView.cpp new file mode 100644 index 00000000..1d658df4 --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/DropView.cpp @@ -0,0 +1,49 @@ +#include "stdafx.h" +#include "DropView.h" +#include "Resource.h" +#include + +int CDropView::OnCreate(CREATESTRUCT&) +{ + m_bgBrush.CreateSolidBrush(RGB(245, 245, 245)); + DragAcceptFiles(TRUE); + return 0; +} + +void CDropView::PreCreate(CREATESTRUCT& cs) +{ + cs.dwExStyle = WS_EX_CLIENTEDGE; +} + +void CDropView::OnDraw(CDC& dc) +{ + CRect rc = GetClientRect(); + dc.FillRect(rc, m_bgBrush); + + dc.SetBkMode(TRANSPARENT); + dc.SetTextColor(RGB(160, 160, 160)); + dc.DrawText(L"Drop images here or use Add Files", -1, rc, + DT_CENTER | DT_VCENTER | DT_SINGLELINE); +} + +LRESULT CDropView::OnDropFiles(WPARAM wparam) +{ + // Forward to parent (CMainFrame) which handles WM_DROPFILES. + return GetAncestor().SendMessage(WM_DROPFILES, wparam, 0); +} + +LRESULT CDropView::WndProc(UINT msg, WPARAM wparam, LPARAM lparam) +{ + try { + switch (msg) { + case WM_DROPFILES: return OnDropFiles(wparam); + } + return WndProcDefault(msg, wparam, lparam); + } + catch (const CException& e) { + CString s; + s << e.GetText() << L'\n' << e.GetErrorString(); + ::MessageBox(nullptr, s, L"Error", MB_ICONERROR); + } + return 0; +} diff --git a/packages/media/cpp/src/win/ui_next/DropView.h b/packages/media/cpp/src/win/ui_next/DropView.h new file mode 100644 index 00000000..516eca64 --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/DropView.h @@ -0,0 +1,27 @@ +#ifndef PM_UI_DROPVIEW_H +#define PM_UI_DROPVIEW_H + +#include "stdafx.h" +#include + +class CDropView : public CWnd +{ +public: + CDropView() = default; + virtual ~CDropView() override = default; + +protected: + virtual int OnCreate(CREATESTRUCT& cs) override; + virtual void OnDraw(CDC& dc) override; + virtual void PreCreate(CREATESTRUCT& cs) override; + virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override; + +private: + CDropView(const CDropView&) = delete; + CDropView& operator=(const CDropView&) = delete; + + LRESULT OnDropFiles(WPARAM wparam); + CBrush m_bgBrush; +}; + +#endif // PM_UI_DROPVIEW_H diff --git a/packages/media/cpp/src/win/ui_next/FileQueue.cpp b/packages/media/cpp/src/win/ui_next/FileQueue.cpp new file mode 100644 index 00000000..40f2a013 --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/FileQueue.cpp @@ -0,0 +1,170 @@ +#include "stdafx.h" +#include "FileQueue.h" +#include "Resource.h" + +#include + +namespace fs = std::filesystem; + +////////////////////////////////////////// +// CQueueListView +////////////////////////////////////////// + +void CQueueListView::OnAttach() +{ + CListView::OnAttach(); + SetupColumns(); + DragAcceptFiles(TRUE); + + DWORD exStyle = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER; + SendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle); +} + +void CQueueListView::SetupColumns() +{ + LV_COLUMN col{}; + col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + col.fmt = LVCFMT_LEFT; + + WCHAR names[COL_COUNT][16] = { L"Name", L"Status", L"Path" }; + int widths[COL_COUNT] = { 220, 100, 400 }; + + for (int i = 0; i < COL_COUNT; ++i) { + col.pszText = names[i]; + col.cx = DpiScaleInt(widths[i]); + col.iSubItem = i; + InsertColumn(i, col); + } +} + +int CQueueListView::AddFile(const CString& path) +{ + // Derive filename from path. + fs::path p(std::wstring(path.c_str())); + CString name = p.filename().c_str(); + + int idx = GetItemCount(); + int item = InsertItem(idx, name); + SetItemText(item, COL_STATUS, L"Queued"); + SetItemText(item, COL_PATH, path); + return item; +} + +void CQueueListView::SetItemStatus(int item, LPCWSTR status) +{ + SetItemText(item, COL_STATUS, status); +} + +void CQueueListView::ClearAll() +{ + DeleteAllItems(); +} + +int CQueueListView::QueueCount() const +{ + return GetItemCount(); +} + +CString CQueueListView::GetItemPath(int item) +{ + wchar_t buf[MAX_PATH * 4]{}; + LVITEMW lvi{}; + lvi.iSubItem = COL_PATH; + lvi.pszText = buf; + lvi.cchTextMax = MAX_PATH * 4; + ::SendMessageW(GetHwnd(), LVM_GETITEMTEXTW, (WPARAM)item, (LPARAM)&lvi); + return CString(buf); +} + +LRESULT CQueueListView::OnDropFiles(UINT, WPARAM wparam, LPARAM) +{ + HDROP hDrop = reinterpret_cast(wparam); + UINT count = DragQueryFileW(hDrop, 0xFFFFFFFF, nullptr, 0); + + for (UINT i = 0; i < count; ++i) { + UINT len = DragQueryFileW(hDrop, i, nullptr, 0); + if (len == 0) continue; + std::wstring w(static_cast(len) + 1, L'\0'); + DragQueryFileW(hDrop, i, w.data(), len + 1); + w.resize(len); + + fs::path p(w); + std::error_code ec; + if (fs::is_directory(p, ec)) { + for (auto& entry : fs::recursive_directory_iterator(p, fs::directory_options::skip_permission_denied, ec)) { + if (!entry.is_regular_file()) continue; + std::wstring ext = entry.path().extension().wstring(); + for (auto& c : ext) c = static_cast(towlower(c)); + if (ext == L".jpg" || ext == L".jpeg" || ext == L".png" || ext == L".webp" || + ext == L".tif" || ext == L".tiff" || ext == L".bmp" || ext == L".gif" || + ext == L".avif" || ext == L".heic") { + AddFile(CString(entry.path().c_str())); + } + } + } else { + AddFile(CString(w.c_str())); + } + } + + DragFinish(hDrop); + return 0; +} + +LRESULT CQueueListView::WndProc(UINT msg, WPARAM wparam, LPARAM lparam) +{ + try { + switch (msg) { + case WM_DROPFILES: return OnDropFiles(msg, wparam, lparam); + } + return WndProcDefault(msg, wparam, lparam); + } + catch (const CException& e) { + CString s; + s << e.GetText() << L'\n' << e.GetErrorString(); + ::MessageBox(nullptr, s, L"Error", MB_ICONERROR); + } + return 0; +} + +////////////////////////////////////////// +// CQueueContainer +////////////////////////////////////////// + +CQueueContainer::CQueueContainer() +{ + SetTabText(L"Queue"); + SetDockCaption(L"File Queue"); + SetView(m_listView); +} + +LRESULT CQueueContainer::WndProc(UINT msg, WPARAM wparam, LPARAM lparam) +{ + try { return WndProcDefault(msg, wparam, lparam); } + catch (const CException& e) { + CString s; + s << e.GetText() << L'\n' << e.GetErrorString(); + ::MessageBox(nullptr, s, L"Error", MB_ICONERROR); + } + return 0; +} + +////////////////////////////////////////// +// CDockQueue +////////////////////////////////////////// + +CDockQueue::CDockQueue() +{ + SetView(m_container); + SetBarWidth(8); +} + +LRESULT CDockQueue::WndProc(UINT msg, WPARAM wparam, LPARAM lparam) +{ + try { return WndProcDefault(msg, wparam, lparam); } + catch (const CException& e) { + CString s; + s << e.GetText() << L'\n' << e.GetErrorString(); + ::MessageBox(nullptr, s, L"Error", MB_ICONERROR); + } + return 0; +} diff --git a/packages/media/cpp/src/win/ui_next/FileQueue.h b/packages/media/cpp/src/win/ui_next/FileQueue.h new file mode 100644 index 00000000..c15b2022 --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/FileQueue.h @@ -0,0 +1,73 @@ +#ifndef PM_UI_FILEQUEUE_H +#define PM_UI_FILEQUEUE_H + +#include "stdafx.h" +#include + +// Column indices for the queue list view. +enum QueueCol : int { COL_NAME = 0, COL_STATUS, COL_PATH, COL_COUNT }; + +///////////////////////////////////////////////////////// +// CQueueListView — accepts drag-drop of files/folders, +// displays them in a report-style list, tracks status. +class CQueueListView : public CListView +{ +public: + CQueueListView() = default; + virtual ~CQueueListView() override = default; + + int AddFile(const CString& path); + void SetItemStatus(int item, LPCWSTR status); + void ClearAll(); + int QueueCount() const; + CString GetItemPath(int item); + +protected: + virtual void OnAttach() override; + virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override; + +private: + CQueueListView(const CQueueListView&) = delete; + CQueueListView& operator=(const CQueueListView&) = delete; + + LRESULT OnDropFiles(UINT msg, WPARAM wparam, LPARAM lparam); + void SetupColumns(); +}; + +///////////////////////////////////////////////////////// +// CQueueContainer — dock container hosting CQueueListView. +class CQueueContainer : public CDockContainer +{ +public: + CQueueContainer(); + virtual ~CQueueContainer() override = default; + CQueueListView& GetListView() { return m_listView; } + +protected: + virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override; + +private: + CQueueContainer(const CQueueContainer&) = delete; + CQueueContainer& operator=(const CQueueContainer&) = delete; + CQueueListView m_listView; +}; + +///////////////////////////////////////////////////////// +// CDockQueue — docker wrapping CQueueContainer. +class CDockQueue : public CDocker +{ +public: + CDockQueue(); + virtual ~CDockQueue() override = default; + CQueueContainer& GetQueueContainer() { return m_container; } + +protected: + virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override; + +private: + CDockQueue(const CDockQueue&) = delete; + CDockQueue& operator=(const CDockQueue&) = delete; + CQueueContainer m_container; +}; + +#endif // PM_UI_FILEQUEUE_H diff --git a/packages/media/cpp/src/win/ui_next/Mainfrm.cpp b/packages/media/cpp/src/win/ui_next/Mainfrm.cpp new file mode 100644 index 00000000..7d652530 --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/Mainfrm.cpp @@ -0,0 +1,331 @@ +#include "stdafx.h" +#include "Mainfrm.h" +#include "Resource.h" +#include "core/resize.hpp" +#include +#include + +namespace fs = std::filesystem; + +static std::string wide_to_utf8(const std::wstring& w) { + if (w.empty()) return {}; + int n = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), nullptr, 0, nullptr, nullptr); + if (n <= 0) return {}; + std::string s(n, '\0'); + WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), s.data(), n, nullptr, nullptr); + return s; +} + +CMainFrame::CMainFrame() +{ +} + +HWND CMainFrame::Create(HWND parent) +{ + SetView(m_view); + LoadRegistrySettings(L"Polymech\\pm-image-ui"); + return CRibbonDockFrame::Create(parent); +} + +STDMETHODIMP CMainFrame::Execute(UINT32 cmdID, UI_EXECUTIONVERB verb, + const PROPERTYKEY*, const PROPVARIANT*, IUISimplePropertySet*) +{ + if (verb == UI_EXECUTIONVERB_EXECUTE) { + switch (cmdID) { + case IDC_CMD_ADD_FILES: OnAddFiles(); break; + case IDC_CMD_ADD_FOLDER: OnAddFolder(); break; + case IDC_CMD_CLEAR: OnClearQueue(); break; + case IDC_CMD_PROCESS: OnProcess(); break; + case IDC_CMD_ABOUT: OnHelp(); break; + case IDC_CMD_EXIT: OnExit(); break; + case IDC_RIBBONHELP: OnHelp(); break; + default: break; + } + } + return S_OK; +} + +STDMETHODIMP CMainFrame::OnViewChanged(UINT32, UI_VIEWTYPE typeId, + IUnknown* pView, UI_VIEWVERB verb, INT32) +{ + if (typeId == UI_VIEWTYPE_RIBBON) { + switch (verb) { + case UI_VIEWVERB_CREATE: + m_pIUIRibbon = reinterpret_cast(pView); + return S_OK; + case UI_VIEWVERB_SIZE: + RecalcLayout(); + return S_OK; + case UI_VIEWVERB_DESTROY: + m_pIUIRibbon = nullptr; + return S_OK; + case UI_VIEWVERB_ERROR: + return E_FAIL; + } + } + return E_NOTIMPL; +} + +BOOL CMainFrame::OnCommand(WPARAM wparam, LPARAM) +{ + switch (LOWORD(wparam)) { + case IDM_ADD_FILES: OnAddFiles(); return TRUE; + case IDM_ADD_FOLDER: OnAddFolder(); return TRUE; + case IDM_CLEAR_QUEUE: OnClearQueue(); return TRUE; + case IDM_PROCESS: OnProcess(); return TRUE; + case IDM_EXIT: OnExit(); return TRUE; + case IDM_ABOUT: return OnHelp(); + case IDW_VIEW_STATUSBAR: return OnViewStatusBar(); + case IDW_VIEW_TOOLBAR: return OnViewToolBar(); + } + return FALSE; +} + +BOOL CMainFrame::OnHelp() +{ + ::MessageBox(GetHwnd(), L"pm-image — resize & transform\nWin32++ UI", + L"About pm-image", MB_ICONINFORMATION | MB_OK); + return TRUE; +} + +void CMainFrame::OnInitialUpdate() +{ + DWORD style = DS_CLIENTEDGE; + + auto pDockQ = AddDockedChild(std::make_unique(), + DS_DOCKED_BOTTOM | style, DpiScaleInt(280)); + m_pDockQueue = static_cast(pDockQ); + m_pDockQueue->GetQueueContainer().SetHideSingleTab(TRUE); + + auto pDockS = AddDockedChild(std::make_unique(), + DS_DOCKED_RIGHT | style, DpiScaleInt(320)); + m_pDockSettings = static_cast(pDockS); + m_pDockSettings->GetSettingsContainer().SetHideSingleTab(TRUE); + + DragAcceptFiles(TRUE); + SetWindowText(L"pm-image"); + GetStatusBar().SetPartText(0, L"Drop files or use Add Files to begin."); +} + +void CMainFrame::SetupToolBar() +{ + AddToolBarButton(IDM_ADD_FILES); + AddToolBarButton(IDM_ADD_FOLDER); + AddToolBarButton(0); + AddToolBarButton(IDM_PROCESS); + AddToolBarButton(IDM_CLEAR_QUEUE); +} + +void CMainFrame::OnAddFiles() +{ + CFileDialog dlg(TRUE, nullptr, nullptr, + OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER, + L"Images (*.jpg;*.jpeg;*.png;*.webp;*.tif;*.tiff;*.bmp;*.gif;*.avif;*.heic)\0" + L"*.jpg;*.jpeg;*.png;*.webp;*.tif;*.tiff;*.bmp;*.gif;*.avif;*.heic\0" + L"All Files (*.*)\0*.*\0\0"); + dlg.SetTitle(L"Add images to queue"); + + if (dlg.DoModal(*this) != IDOK) return; + + std::vector files; + int pos = 0; + CString path = dlg.GetNextPathName(pos); + while (!path.IsEmpty()) { + files.push_back(std::wstring(path.c_str())); + if (pos < 0) break; + path = dlg.GetNextPathName(pos); + } + AddFilesToQueue(files); +} + +void CMainFrame::OnAddFolder() +{ + BROWSEINFOW bi{}; + bi.hwndOwner = GetHwnd(); + bi.lpszTitle = L"Select folder with images"; + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; + wchar_t dn[MAX_PATH]{}; + bi.pszDisplayName = dn; + PIDLIST_ABSOLUTE pidl = SHBrowseForFolderW(&bi); + if (!pidl) return; + wchar_t path[MAX_PATH * 4]{}; + if (SHGetPathFromIDListW(pidl, path)) { + std::vector v = { path }; + AddFilesToQueue(v); + } + CoTaskMemFree(pidl); +} + +void CMainFrame::AddFilesToQueue(const std::vector& paths) +{ + if (!m_pDockQueue) return; + auto& lv = m_pDockQueue->GetQueueContainer().GetListView(); + + for (auto& p : paths) { + std::error_code ec; + fs::path fp(p); + if (fs::is_directory(fp, ec)) { + for (auto& entry : fs::recursive_directory_iterator(fp, + fs::directory_options::skip_permission_denied, ec)) { + if (!entry.is_regular_file()) continue; + std::wstring ext = entry.path().extension().wstring(); + for (auto& c : ext) c = static_cast(towlower(c)); + if (ext == L".jpg" || ext == L".jpeg" || ext == L".png" || ext == L".webp" || + ext == L".tif" || ext == L".tiff" || ext == L".bmp" || ext == L".gif" || + ext == L".avif" || ext == L".heic") { + lv.AddFile(CString(entry.path().c_str())); + } + } + } else { + lv.AddFile(CString(fp.c_str())); + } + } + + CString status; + status.Format(L"%d file(s) in queue", lv.QueueCount()); + GetStatusBar().SetPartText(0, status); +} + +void CMainFrame::OnClearQueue() +{ + if (m_pDockQueue) { + m_pDockQueue->GetQueueContainer().GetListView().ClearAll(); + GetStatusBar().SetPartText(0, L"Queue cleared."); + } +} + +void CMainFrame::OnProcess() +{ + if (m_processing) { + ::MessageBox(GetHwnd(), L"Already processing.", L"pm-image", MB_ICONWARNING); + return; + } + if (!m_pDockQueue || !m_pDockSettings) return; + + auto& lv = m_pDockQueue->GetQueueContainer().GetListView(); + int count = lv.QueueCount(); + if (count == 0) { + ::MessageBox(GetHwnd(), L"Queue is empty — drop files first.", L"pm-image", MB_ICONINFORMATION); + return; + } + + media::ResizeOptions opt; + std::string out_dir; + m_pDockSettings->GetSettingsContainer().GetSettingsView().ReadOptions(opt, out_dir); + + // Gather paths + std::vector> items; + items.reserve(count); + for (int i = 0; i < count; ++i) { + CString p = lv.GetItemPath(i); + items.emplace_back(i, wide_to_utf8(std::wstring(p.c_str()))); + } + + m_processing = true; + HWND hwnd = GetHwnd(); + + m_worker = std::thread([items = std::move(items), opt, out_dir, hwnd]() mutable { + int ok = 0, fail = 0; + for (auto& [idx, input] : items) { + ::PostMessage(hwnd, UWM_QUEUE_PROGRESS, (WPARAM)idx, 1); + std::string output_path; + if (!out_dir.empty()) { + namespace fs2 = std::filesystem; + fs2::path op = fs2::path(out_dir) / fs2::path(input).filename(); + output_path = op.string(); + std::error_code ec2; + fs2::create_directories(fs2::path(out_dir), ec2); + } else { + output_path = input; + } + + std::string err; + bool success = media::resize_file(input, output_path, opt, err); + ::PostMessage(hwnd, UWM_QUEUE_PROGRESS, (WPARAM)idx, success ? 2 : 3); + if (success) ++ok; else ++fail; + } + ::PostMessage(hwnd, UWM_QUEUE_DONE, (WPARAM)ok, (LPARAM)fail); + }); + m_worker.detach(); + + GetStatusBar().SetPartText(0, L"Processing…"); +} + +void CMainFrame::OnExit() +{ + Close(); +} + +LRESULT CMainFrame::OnGetMinMaxInfo(UINT msg, WPARAM wparam, LPARAM lparam) +{ + LPMINMAXINFO lpMMI = reinterpret_cast(lparam); + const CSize minSz(600, 400); + lpMMI->ptMinTrackSize.x = DpiScaleInt(minSz.cx); + lpMMI->ptMinTrackSize.y = DpiScaleInt(minSz.cy); + return FinalWindowProc(msg, wparam, lparam); +} + +LRESULT CMainFrame::OnQueueProgress(WPARAM wparam, LPARAM lparam) +{ + if (!m_pDockQueue) return 0; + auto& lv = m_pDockQueue->GetQueueContainer().GetListView(); + int idx = static_cast(wparam); + int state = static_cast(lparam); + if (state == 1) + lv.SetItemStatus(idx, L"Processing…"); + else if (state == 2) + lv.SetItemStatus(idx, L"Done"); + else + lv.SetItemStatus(idx, L"Error"); + return 0; +} + +LRESULT CMainFrame::OnQueueDone(WPARAM wparam, LPARAM lparam) +{ + m_processing = false; + int ok = static_cast(wparam); + int fail = static_cast(lparam); + CString s; + s.Format(L"Done: %d succeeded, %d failed", ok, fail); + GetStatusBar().SetPartText(0, s); + if (fail > 0) + ::MessageBox(GetHwnd(), s, L"pm-image", MB_ICONWARNING); + return 0; +} + +LRESULT CMainFrame::WndProc(UINT msg, WPARAM wparam, LPARAM lparam) +{ + try { + switch (msg) { + case WM_GETMINMAXINFO: return OnGetMinMaxInfo(msg, wparam, lparam); + case UWM_QUEUE_PROGRESS: return OnQueueProgress(wparam, lparam); + case UWM_QUEUE_DONE: return OnQueueDone(wparam, lparam); + case WM_DROPFILES: { + HDROP hDrop = reinterpret_cast(wparam); + UINT count = DragQueryFileW(hDrop, 0xFFFFFFFF, nullptr, 0); + std::vector paths; + for (UINT i = 0; i < count; ++i) { + UINT len = DragQueryFileW(hDrop, i, nullptr, 0); + if (!len) continue; + std::wstring w(len + 1, L'\0'); + DragQueryFileW(hDrop, i, w.data(), len + 1); + w.resize(len); + paths.push_back(std::move(w)); + } + DragFinish(hDrop); + AddFilesToQueue(paths); + return 0; + } + } + return WndProcDefault(msg, wparam, lparam); + } + catch (const CException& e) { + CString s; + s << e.GetText() << L'\n' << e.GetErrorString(); + ::MessageBox(nullptr, s, L"Error", MB_ICONERROR); + } + catch (const std::exception& e) { + ::MessageBoxA(nullptr, e.what(), "Error", MB_ICONERROR); + } + return 0; +} diff --git a/packages/media/cpp/src/win/ui_next/Mainfrm.h b/packages/media/cpp/src/win/ui_next/Mainfrm.h new file mode 100644 index 00000000..450b043e --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/Mainfrm.h @@ -0,0 +1,50 @@ +#ifndef PM_UI_MAINFRM_H +#define PM_UI_MAINFRM_H + +#include "DropView.h" +#include "FileQueue.h" +#include "SettingsPanel.h" + +class CMainFrame : public CRibbonDockFrame +{ +public: + CMainFrame(); + virtual ~CMainFrame() override = default; + virtual HWND Create(HWND parent = nullptr) override; + + void AddFilesToQueue(const std::vector& paths); + +protected: + virtual STDMETHODIMP Execute(UINT32, UI_EXECUTIONVERB, const PROPERTYKEY*, const PROPVARIANT*, IUISimplePropertySet*) override; + virtual STDMETHODIMP OnViewChanged(UINT32, UI_VIEWTYPE, IUnknown*, UI_VIEWVERB, INT32) override; + + virtual BOOL OnCommand(WPARAM wparam, LPARAM lparam) override; + virtual BOOL OnHelp() override; + virtual void OnInitialUpdate() override; + virtual void SetupToolBar() override; + virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override; + +private: + CMainFrame(const CMainFrame&) = delete; + CMainFrame& operator=(const CMainFrame&) = delete; + + void OnAddFiles(); + void OnAddFolder(); + void OnClearQueue(); + void OnProcess(); + void OnExit(); + + LRESULT OnGetMinMaxInfo(UINT msg, WPARAM wparam, LPARAM lparam); + LRESULT OnQueueProgress(WPARAM wparam, LPARAM lparam); + LRESULT OnQueueDone(WPARAM wparam, LPARAM lparam); + + CDropView m_view; + IUIRibbon* m_pIUIRibbon = nullptr; + CDockQueue* m_pDockQueue = nullptr; + CDockSettings* m_pDockSettings = nullptr; + + std::thread m_worker; + bool m_processing = false; +}; + +#endif // PM_UI_MAINFRM_H diff --git a/packages/media/cpp/src/win/ui_next/Resource.h b/packages/media/cpp/src/win/ui_next/Resource.h new file mode 100644 index 00000000..202abe6b --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/Resource.h @@ -0,0 +1,54 @@ +#ifndef PM_UI_RESOURCE_H +#define PM_UI_RESOURCE_H + +#include "default_resource.h" +#include "RibbonUI.h" + +// Menu fallback IDs (when ribbon is unavailable) +#define IDM_PROCESS 200 +#define IDM_CLEAR_QUEUE 201 +#define IDM_ADD_FILES 202 +#define IDM_ADD_FOLDER 203 +#define IDM_SETTINGS 204 +#define IDM_EXIT 205 +#define IDM_ABOUT 206 + +// Ribbon command IDs (must match Ribbon.xml Symbol= values) +#define IDC_CMD_PROCESS 300 +#define IDC_CMD_CLEAR 301 +#define IDC_CMD_ADD_FILES 302 +#define IDC_CMD_ADD_FOLDER 303 +#define IDC_CMD_EXIT 304 +#define IDC_CMD_ABOUT 305 + +// Ribbon tabs/groups +#define IDC_TAB_HOME 400 +#define IDC_GROUP_QUEUE 401 +#define IDC_GROUP_ACTIONS 402 + +// Dock / container +#define IDI_QUEUE 500 +#define IDB_QUEUE 501 + +// Settings controls (for settings dock) +#define IDC_COMBO_PRESET_OUT 600 +#define IDC_EDIT_OUT_DIR 601 +#define IDC_BTN_BROWSE_OUT 602 +#define IDC_EDIT_MAX_W 603 +#define IDC_EDIT_MAX_H 604 +#define IDC_COMBO_FIT 605 +#define IDC_EDIT_QUALITY 606 +#define IDC_CHK_ENLARGE 607 +#define IDC_CHK_AUTOROT 608 +#define IDC_CHK_STRIP 609 + +// Ribbon misc +#define IDC_RIBBONHELP 700 +#define IDC_QAT 701 +#define IDC_CUSTOMIZE_QAT 702 + +// User messages +#define UWM_QUEUE_PROGRESS (WM_USER + 100) +#define UWM_QUEUE_DONE (WM_USER + 101) + +#endif // PM_UI_RESOURCE_H diff --git a/packages/media/cpp/src/win/ui_next/Resource.rc b/packages/media/cpp/src/win/ui_next/Resource.rc new file mode 100644 index 00000000..06bc5b32 --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/Resource.rc @@ -0,0 +1,63 @@ +// pm-image-ui resource script +#include "Resource.h" + +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS + +// Ribbon compiled resources (generated by uicc from Ribbon.xml) +#include "RibbonUI.rc" + +// Manifest is provided by media-img-win.manifest (already linked to pm-image). + +// Fallback menu (when ribbon unavailable) +IDW_MAIN MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&Add Files...", IDM_ADD_FILES + MENUITEM "Add &Folder...", IDM_ADD_FOLDER + MENUITEM SEPARATOR + MENUITEM "E&xit\tAlt+F4", IDM_EXIT + END + POPUP "&Actions" + BEGIN + MENUITEM "&Process\tF5", IDM_PROCESS + MENUITEM "&Clear Queue", IDM_CLEAR_QUEUE + END + POPUP "&Help" + BEGIN + MENUITEM "&About\tF1", IDM_ABOUT + END +END + +// Fallback toolbar bitmap (16x15 per button placeholder – tiny 3-button strip) +// We can add real bitmaps later; for now the toolbar works without. + +// About dialog +IDW_ABOUT DIALOGEX 0, 0, 200, 80 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +CAPTION "About pm-image" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK", IDOK, 75, 56, 50, 14 + CTEXT "pm-image — resize & transform", -1, 10, 12, 180, 10 + CTEXT "Win32++ UI", -1, 10, 26, 180, 10 +END + +// Accelerators +IDW_MAIN ACCELERATORS +BEGIN + VK_F5, IDM_PROCESS, VIRTKEY, NOINVERT +END + +// String table +STRINGTABLE +BEGIN + IDW_MAIN "pm-image" + IDW_READY "Ready" +END + +// Docking bitmaps – reuse from Win32xx samples path (or copy to res/) +// These are required by the dock framework for the floating dock targeting UI. +// If missing, docking still works but without the visual targeting indicators. diff --git a/packages/media/cpp/src/win/ui_next/Ribbon.bml b/packages/media/cpp/src/win/ui_next/Ribbon.bml new file mode 100644 index 00000000..0b93aae4 Binary files /dev/null and b/packages/media/cpp/src/win/ui_next/Ribbon.bml differ diff --git a/packages/media/cpp/src/win/ui_next/Ribbon.xml b/packages/media/cpp/src/win/ui_next/Ribbon.xml new file mode 100644 index 00000000..584f3290 --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/Ribbon.xml @@ -0,0 +1,123 @@ + + + + + + + + Home + + + + + + + Queue + + + + + Actions + + + + + + + Add Files + + + + + Add Folder + + + + + Clear + + + + + Process + + + + + Exit + + + + + About + + + + + + + + + + + + + + + + + + +