media:cpp --uinext win32xx 1/2

This commit is contained in:
lovebird 2026-04-15 20:00:17 +02:00
parent 57488b529f
commit 08e690631e
25 changed files with 1580 additions and 0 deletions

View File

@ -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.

Binary file not shown.

Binary file not shown.

View File

@ -35,6 +35,7 @@ std::string join_src_semicolons(const std::vector<std::string> &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<std::string> 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";

View File

@ -0,0 +1,8 @@
#include "stdafx.h"
#include "App.h"
BOOL CPmImageApp::InitInstance()
{
m_frame.Create();
return TRUE;
}

View File

@ -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

View File

@ -0,0 +1,49 @@
#include "stdafx.h"
#include "DropView.h"
#include "Resource.h"
#include <shellapi.h>
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;
}

View File

@ -0,0 +1,27 @@
#ifndef PM_UI_DROPVIEW_H
#define PM_UI_DROPVIEW_H
#include "stdafx.h"
#include <shellapi.h>
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

View File

@ -0,0 +1,170 @@
#include "stdafx.h"
#include "FileQueue.h"
#include "Resource.h"
#include <filesystem>
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<HDROP>(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<size_t>(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<wchar_t>(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;
}

View File

@ -0,0 +1,73 @@
#ifndef PM_UI_FILEQUEUE_H
#define PM_UI_FILEQUEUE_H
#include "stdafx.h"
#include <shellapi.h>
// 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

View File

@ -0,0 +1,331 @@
#include "stdafx.h"
#include "Mainfrm.h"
#include "Resource.h"
#include "core/resize.hpp"
#include <shlobj.h>
#include <filesystem>
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<IUIRibbon*>(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<CDockQueue>(),
DS_DOCKED_BOTTOM | style, DpiScaleInt(280));
m_pDockQueue = static_cast<CDockQueue*>(pDockQ);
m_pDockQueue->GetQueueContainer().SetHideSingleTab(TRUE);
auto pDockS = AddDockedChild(std::make_unique<CDockSettings>(),
DS_DOCKED_RIGHT | style, DpiScaleInt(320));
m_pDockSettings = static_cast<CDockSettings*>(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<std::wstring> 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<std::wstring> v = { path };
AddFilesToQueue(v);
}
CoTaskMemFree(pidl);
}
void CMainFrame::AddFilesToQueue(const std::vector<std::wstring>& 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<wchar_t>(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<std::pair<int, std::string>> 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<LPMINMAXINFO>(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<int>(wparam);
int state = static_cast<int>(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<int>(wparam);
int fail = static_cast<int>(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<HDROP>(wparam);
UINT count = DragQueryFileW(hDrop, 0xFFFFFFFF, nullptr, 0);
std::vector<std::wstring> 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;
}

View File

@ -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<std::wstring>& 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

View File

@ -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

View File

@ -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.

Binary file not shown.

View File

@ -0,0 +1,123 @@
<?xml version='1.0' encoding='utf-8'?>
<Application xmlns='http://schemas.microsoft.com/windows/2009/Ribbon'>
<Application.Commands>
<!-- Tab -->
<Command Name="cmdTabHome" Id="400">
<Command.LabelTitle>
<String Id="4001">Home</String>
</Command.LabelTitle>
</Command>
<!-- Groups -->
<Command Name="cmdGroupQueue" Id="401">
<Command.LabelTitle>
<String Id="4011">Queue</String>
</Command.LabelTitle>
</Command>
<Command Name="cmdGroupActions" Id="402">
<Command.LabelTitle>
<String Id="4021">Actions</String>
</Command.LabelTitle>
</Command>
<!-- Buttons -->
<Command Name="cmdAddFiles" Symbol="IDC_CMD_ADD_FILES" Id="302">
<Command.LabelTitle>
<String Id="3021">Add Files</String>
</Command.LabelTitle>
</Command>
<Command Name="cmdAddFolder" Symbol="IDC_CMD_ADD_FOLDER" Id="303">
<Command.LabelTitle>
<String Id="3031">Add Folder</String>
</Command.LabelTitle>
</Command>
<Command Name="cmdClear" Symbol="IDC_CMD_CLEAR" Id="301">
<Command.LabelTitle>
<String Id="3011">Clear</String>
</Command.LabelTitle>
</Command>
<Command Name="cmdProcess" Symbol="IDC_CMD_PROCESS" Id="300">
<Command.LabelTitle>
<String Id="3001">Process</String>
</Command.LabelTitle>
</Command>
<Command Name="cmdExit" Symbol="IDC_CMD_EXIT" Id="304">
<Command.LabelTitle>
<String Id="3041">Exit</String>
</Command.LabelTitle>
</Command>
<Command Name="cmdAbout" Symbol="IDC_CMD_ABOUT" Id="305">
<Command.LabelTitle>
<String Id="3051">About</String>
</Command.LabelTitle>
</Command>
<!-- Misc -->
<Command Name="cmdAppMenu" Id="710" />
<Command Name="cmdHelp" Symbol="IDC_RIBBONHELP" Id="700" />
<Command Name="cmdQAT" Symbol="IDC_QAT" Id="701" />
<Command Name="cmdCustomizeQAT" Symbol="IDC_CUSTOMIZE_QAT" Id="702" />
</Application.Commands>
<Application.Views>
<Ribbon>
<!-- Application Menu -->
<Ribbon.ApplicationMenu>
<ApplicationMenu CommandName="cmdAppMenu">
<MenuGroup Class="MajorItems">
<Button CommandName="cmdAddFiles" />
<Button CommandName="cmdAddFolder" />
</MenuGroup>
<MenuGroup>
<Button CommandName="cmdAbout" />
</MenuGroup>
<MenuGroup>
<Button CommandName="cmdExit" />
</MenuGroup>
</ApplicationMenu>
</Ribbon.ApplicationMenu>
<!-- Tabs -->
<Ribbon.Tabs>
<Tab CommandName="cmdTabHome">
<Tab.ScalingPolicy>
<ScalingPolicy>
<ScalingPolicy.IdealSizes>
<Scale Group="cmdGroupQueue" Size="Large" />
<Scale Group="cmdGroupActions" Size="Large" />
</ScalingPolicy.IdealSizes>
<Scale Group="cmdGroupQueue" Size="Medium" />
<Scale Group="cmdGroupActions" Size="Popup" />
</ScalingPolicy>
</Tab.ScalingPolicy>
<Group CommandName="cmdGroupQueue" SizeDefinition="ThreeButtons">
<Button CommandName="cmdAddFiles" />
<Button CommandName="cmdAddFolder" />
<Button CommandName="cmdClear" />
</Group>
<Group CommandName="cmdGroupActions" SizeDefinition="OneButton">
<Button CommandName="cmdProcess" />
</Group>
</Tab>
</Ribbon.Tabs>
<!-- Help button -->
<Ribbon.HelpButton>
<HelpButton CommandName="cmdHelp" />
</Ribbon.HelpButton>
<!-- Quick Access toolbar -->
<Ribbon.QuickAccessToolbar>
<QuickAccessToolbar CommandName="cmdQAT" CustomizeCommandName="cmdCustomizeQAT">
<QuickAccessToolbar.ApplicationDefaults>
<Button CommandName="cmdProcess" ApplicationDefaults.IsChecked="true" />
</QuickAccessToolbar.ApplicationDefaults>
</QuickAccessToolbar>
</Ribbon.QuickAccessToolbar>
</Ribbon>
</Application.Views>
</Application>

View File

@ -0,0 +1,29 @@
// *****************************************************************************
// * This is an automatically generated header file for UI Element definition *
// * resource symbols and values. Please do not modify manually. *
// *****************************************************************************
#pragma once
#define cmdTabHome 400
#define cmdTabHome_LabelTitle_RESID 4001
#define cmdGroupQueue 401
#define cmdGroupQueue_LabelTitle_RESID 4011
#define cmdGroupActions 402
#define cmdGroupActions_LabelTitle_RESID 4021
#define IDC_CMD_ADD_FILES 302
#define IDC_CMD_ADD_FILES_LabelTitle_RESID 3021
#define IDC_CMD_ADD_FOLDER 303
#define IDC_CMD_ADD_FOLDER_LabelTitle_RESID 3031
#define IDC_CMD_CLEAR 301
#define IDC_CMD_CLEAR_LabelTitle_RESID 3011
#define IDC_CMD_PROCESS 300
#define IDC_CMD_PROCESS_LabelTitle_RESID 3001
#define IDC_CMD_EXIT 304
#define IDC_CMD_EXIT_LabelTitle_RESID 3041
#define IDC_CMD_ABOUT 305
#define IDC_CMD_ABOUT_LabelTitle_RESID 3051
#define cmdAppMenu 710
#define IDC_RIBBONHELP 700
#define IDC_QAT 701
#define IDC_CUSTOMIZE_QAT 702

View File

@ -0,0 +1,53 @@
// ******************************************************************************
// * This is an automatically generated file containing the ribbon resource for *
// * your application. *
// ******************************************************************************
#include ".\RibbonUI.h"
STRINGTABLE
BEGIN
cmdTabHome_LabelTitle_RESID L"Home" /* LabelTitle cmdTabHome_LabelTitle_RESID: (null) */
END
STRINGTABLE
BEGIN
cmdGroupQueue_LabelTitle_RESID L"Queue" /* LabelTitle cmdGroupQueue_LabelTitle_RESID: (null) */
END
STRINGTABLE
BEGIN
cmdGroupActions_LabelTitle_RESID L"Actions" /* LabelTitle cmdGroupActions_LabelTitle_RESID: (null) */
END
STRINGTABLE
BEGIN
IDC_CMD_ADD_FILES_LabelTitle_RESID L"Add Files" /* LabelTitle IDC_CMD_ADD_FILES_LabelTitle_RESID: (null) */
END
STRINGTABLE
BEGIN
IDC_CMD_ADD_FOLDER_LabelTitle_RESID L"Add Folder" /* LabelTitle IDC_CMD_ADD_FOLDER_LabelTitle_RESID: (null) */
END
STRINGTABLE
BEGIN
IDC_CMD_CLEAR_LabelTitle_RESID L"Clear" /* LabelTitle IDC_CMD_CLEAR_LabelTitle_RESID: (null) */
END
STRINGTABLE
BEGIN
IDC_CMD_PROCESS_LabelTitle_RESID L"Process" /* LabelTitle IDC_CMD_PROCESS_LabelTitle_RESID: (null) */
END
STRINGTABLE
BEGIN
IDC_CMD_EXIT_LabelTitle_RESID L"Exit" /* LabelTitle IDC_CMD_EXIT_LabelTitle_RESID: (null) */
END
STRINGTABLE
BEGIN
IDC_CMD_ABOUT_LabelTitle_RESID L"About" /* LabelTitle IDC_CMD_ABOUT_LabelTitle_RESID: (null) */
END
APPLICATION_RIBBON UIFILE "C:/Users/zx/Desktop/polymech/polymech-mono/packages/media/cpp/src/win/ui_next/Ribbon.bml"

View File

@ -0,0 +1,254 @@
#include "stdafx.h"
#include "SettingsPanel.h"
#include "Resource.h"
#include <shlobj.h>
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;
}
static std::wstring utf8_to_wide(const std::string& s) {
if (s.empty()) return {};
int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), nullptr, 0);
if (n <= 0) return {};
std::wstring w(n, L'\0');
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), w.data(), n);
return w;
}
static std::wstring get_edit_text(HWND h) {
int n = ::GetWindowTextLengthW(h);
if (n <= 0) return {};
std::wstring w(n + 1, L'\0');
::GetWindowTextW(h, w.data(), n + 1);
w.resize(n);
return w;
}
//////////////////////////////////////////
// CSettingsView
//////////////////////////////////////////
int CSettingsView::OnCreate(CREATESTRUCT&)
{
CreateControls();
return 0;
}
void CSettingsView::CreateControls()
{
const int x0 = 8, lw = 100, gap = 6;
const int ew = 140, eh = 22;
int y = 10;
HINSTANCE inst = GetModuleHandleW(nullptr);
auto label = [&](LPCWSTR text, int yoff = 0) {
CreateWindowExW(0, L"STATIC", text, WS_CHILD | WS_VISIBLE,
x0, y + yoff, lw, 18, GetHwnd(), nullptr, inst, nullptr);
};
// Output preset
label(L"Output preset:");
m_hPreset = CreateWindowExW(WS_EX_CLIENTEDGE, L"COMBOBOX", L"",
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS,
x0 + lw + gap, y - 2, ew + 40, 140, GetHwnd(), (HMENU)(UINT_PTR)IDC_COMBO_PRESET_OUT, inst, nullptr);
::SendMessageW(m_hPreset, CB_ADDSTRING, 0, (LPARAM)L"Next to source");
::SendMessageW(m_hPreset, CB_ADDSTRING, 0, (LPARAM)L"Next to source (_resized)");
::SendMessageW(m_hPreset, CB_ADDSTRING, 0, (LPARAM)L"Custom folder");
::SendMessageW(m_hPreset, CB_SETCURSEL, 0, 0);
y += 30;
// Output directory
label(L"Output folder:");
m_hOutDir = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"",
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
x0 + lw + gap, y, ew, eh, GetHwnd(), (HMENU)(UINT_PTR)IDC_EDIT_OUT_DIR, inst, nullptr);
m_hBtnBrowse = CreateWindowExW(0, L"BUTTON", L"...",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
x0 + lw + gap + ew + 4, y, 28, eh, GetHwnd(), (HMENU)(UINT_PTR)IDC_BTN_BROWSE_OUT, inst, nullptr);
y += 30;
// Max width / height
label(L"Max width:");
m_hMaxW = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"0",
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_NUMBER,
x0 + lw + gap, y, 60, eh, GetHwnd(), (HMENU)(UINT_PTR)IDC_EDIT_MAX_W, inst, nullptr);
CreateWindowExW(0, L"STATIC", L"Max height:", WS_CHILD | WS_VISIBLE,
x0 + lw + gap + 68, y, 80, 18, GetHwnd(), nullptr, inst, nullptr);
m_hMaxH = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"0",
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_NUMBER,
x0 + lw + gap + 68 + 84, y, 60, eh, GetHwnd(), (HMENU)(UINT_PTR)IDC_EDIT_MAX_H, inst, nullptr);
y += 30;
// Fit
label(L"Fit:");
m_hFit = CreateWindowExW(WS_EX_CLIENTEDGE, L"COMBOBOX", L"",
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS,
x0 + lw + gap, y - 2, ew, 140, GetHwnd(), (HMENU)(UINT_PTR)IDC_COMBO_FIT, inst, nullptr);
const LPCWSTR fits[] = { L"inside", L"cover", L"contain", L"fill", L"outside" };
for (auto f : fits) ::SendMessageW(m_hFit, CB_ADDSTRING, 0, (LPARAM)f);
::SendMessageW(m_hFit, CB_SETCURSEL, 0, 0);
y += 30;
// Quality
label(L"Quality (1-100):");
m_hQuality = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"85",
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_NUMBER,
x0 + lw + gap, y, 48, eh, GetHwnd(), (HMENU)(UINT_PTR)IDC_EDIT_QUALITY, inst, nullptr);
y += 30;
// Checkboxes
m_hEnlarge = CreateWindowExW(0, L"BUTTON", L"Allow enlargement",
WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
x0, y, 180, 22, GetHwnd(), (HMENU)(UINT_PTR)IDC_CHK_ENLARGE, inst, nullptr);
y += 24;
m_hAutorot = CreateWindowExW(0, L"BUTTON", L"EXIF autorotate",
WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
x0, y, 180, 22, GetHwnd(), (HMENU)(UINT_PTR)IDC_CHK_AUTOROT, inst, nullptr);
::SendMessageW(m_hAutorot, BM_SETCHECK, BST_CHECKED, 0);
y += 24;
m_hStrip = CreateWindowExW(0, L"BUTTON", L"Strip metadata on save",
WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
x0, y, 200, 22, GetHwnd(), (HMENU)(UINT_PTR)IDC_CHK_STRIP, inst, nullptr);
::SendMessageW(m_hStrip, BM_SETCHECK, BST_CHECKED, 0);
}
void CSettingsView::ReadOptions(media::ResizeOptions& opt, std::string& out_dir) const
{
int preset = (int)::SendMessageW(m_hPreset, CB_GETCURSEL, 0, 0);
opt.output_stem_suffix.clear();
if (preset == 1) opt.output_stem_suffix = "_resized";
out_dir.clear();
if (preset == 2 || !get_edit_text(m_hOutDir).empty())
out_dir = wide_to_utf8(get_edit_text(m_hOutDir));
wchar_t buf[32]{};
::GetWindowTextW(m_hMaxW, buf, 32);
opt.max_width = _wtoi(buf);
if (opt.max_width < 0) opt.max_width = 0;
::GetWindowTextW(m_hMaxH, buf, 32);
opt.max_height = _wtoi(buf);
if (opt.max_height < 0) opt.max_height = 0;
int fi = (int)::SendMessageW(m_hFit, CB_GETCURSEL, 0, 0);
const char* fits[] = { "inside", "cover", "contain", "fill", "outside" };
if (fi >= 0 && fi < 5) opt.fit = fits[fi];
::GetWindowTextW(m_hQuality, buf, 32);
opt.quality = _wtoi(buf);
if (opt.quality < 1) opt.quality = 1;
if (opt.quality > 100) opt.quality = 100;
opt.without_enlargement = ::SendMessageW(m_hEnlarge, BM_GETCHECK, 0, 0) != BST_CHECKED;
opt.autorotate = ::SendMessageW(m_hAutorot, BM_GETCHECK, 0, 0) == BST_CHECKED;
opt.strip_metadata = ::SendMessageW(m_hStrip, BM_GETCHECK, 0, 0) == BST_CHECKED;
}
void CSettingsView::WriteOptions(const media::ResizeOptions& opt, const std::string& out_dir)
{
int preset = 0;
if (!out_dir.empty()) preset = 2;
else if (opt.output_stem_suffix == "_resized") preset = 1;
::SendMessageW(m_hPreset, CB_SETCURSEL, preset, 0);
::SetWindowTextW(m_hOutDir, utf8_to_wide(out_dir).c_str());
wchar_t buf[32]{};
swprintf_s(buf, L"%d", opt.max_width);
::SetWindowTextW(m_hMaxW, buf);
swprintf_s(buf, L"%d", opt.max_height);
::SetWindowTextW(m_hMaxH, buf);
swprintf_s(buf, L"%d", opt.quality);
::SetWindowTextW(m_hQuality, buf);
::SendMessageW(m_hEnlarge, BM_SETCHECK, opt.without_enlargement ? BST_UNCHECKED : BST_CHECKED, 0);
::SendMessageW(m_hAutorot, BM_SETCHECK, opt.autorotate ? BST_CHECKED : BST_UNCHECKED, 0);
::SendMessageW(m_hStrip, BM_SETCHECK, opt.strip_metadata ? BST_CHECKED : BST_UNCHECKED, 0);
}
void CSettingsView::OnBrowseOutput()
{
BROWSEINFOW bi{};
bi.hwndOwner = GetHwnd();
bi.lpszTitle = L"Choose output folder";
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))
::SetWindowTextW(m_hOutDir, path);
CoTaskMemFree(pidl);
}
LRESULT CSettingsView::WndProc(UINT msg, WPARAM wparam, LPARAM lparam)
{
try {
if (msg == WM_COMMAND) {
if (LOWORD(wparam) == IDC_BTN_BROWSE_OUT) {
OnBrowseOutput();
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);
}
return 0;
}
//////////////////////////////////////////
// CSettingsContainer
//////////////////////////////////////////
CSettingsContainer::CSettingsContainer()
{
SetTabText(L"Settings");
SetDockCaption(L"Resize Settings");
SetView(m_view);
}
LRESULT CSettingsContainer::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;
}
//////////////////////////////////////////
// CDockSettings
//////////////////////////////////////////
CDockSettings::CDockSettings()
{
SetView(m_container);
SetBarWidth(8);
}
LRESULT CDockSettings::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;
}

View File

@ -0,0 +1,78 @@
#ifndef PM_UI_SETTINGSPANEL_H
#define PM_UI_SETTINGSPANEL_H
#include "stdafx.h"
#include "core/resize.hpp"
/////////////////////////////////////////////////////////
// CSettingsView — child dialog-like panel for resize options.
// Hosted inside a dock container on the right.
class CSettingsView : public CWnd
{
public:
CSettingsView() = default;
virtual ~CSettingsView() override = default;
void ReadOptions(media::ResizeOptions& opt, std::string& out_dir) const;
void WriteOptions(const media::ResizeOptions& opt, const std::string& out_dir);
protected:
virtual int OnCreate(CREATESTRUCT& cs) override;
virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override;
private:
CSettingsView(const CSettingsView&) = delete;
CSettingsView& operator=(const CSettingsView&) = delete;
void OnBrowseOutput();
void CreateControls();
HWND m_hPreset{};
HWND m_hOutDir{};
HWND m_hBtnBrowse{};
HWND m_hMaxW{};
HWND m_hMaxH{};
HWND m_hFit{};
HWND m_hQuality{};
HWND m_hEnlarge{};
HWND m_hAutorot{};
HWND m_hStrip{};
};
/////////////////////////////////////////////////////////
// CSettingsContainer — dock container hosting CSettingsView.
class CSettingsContainer : public CDockContainer
{
public:
CSettingsContainer();
virtual ~CSettingsContainer() override = default;
CSettingsView& GetSettingsView() { return m_view; }
protected:
virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override;
private:
CSettingsContainer(const CSettingsContainer&) = delete;
CSettingsContainer& operator=(const CSettingsContainer&) = delete;
CSettingsView m_view;
};
/////////////////////////////////////////////////////////
// CDockSettings — docker wrapping CSettingsContainer.
class CDockSettings : public CDocker
{
public:
CDockSettings();
virtual ~CDockSettings() override = default;
CSettingsContainer& GetSettingsContainer() { return m_container; }
protected:
virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override;
private:
CDockSettings(const CDockSettings&) = delete;
CDockSettings& operator=(const CDockSettings&) = delete;
CSettingsContainer m_container;
};
#endif // PM_UI_SETTINGSPANEL_H

View File

@ -0,0 +1,49 @@
#include "stdafx.h"
#include "launch_ui_next.h"
#include "App.h"
#include <objbase.h>
static std::wstring utf8_to_wide(const std::string& s) {
if (s.empty()) return {};
int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), nullptr, 0);
if (n <= 0) return {};
std::wstring w(n, L'\0');
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), w.data(), n);
return w;
}
namespace media::win {
int launch_ui_next(const std::vector<std::string>& initial_files)
{
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
try {
CPmImageApp app;
if (!initial_files.empty()) {
std::vector<std::wstring> wpaths;
wpaths.reserve(initial_files.size());
for (auto& f : initial_files)
wpaths.push_back(utf8_to_wide(f));
app.GetMainFrame().AddFilesToQueue(wpaths);
}
int rc = app.Run();
CoUninitialize();
return rc;
}
catch (const CException& e) {
CString s;
s << e.GetText() << L'\n' << e.GetErrorString();
::MessageBox(nullptr, s, L"pm-image startup error", MB_ICONERROR);
}
catch (const std::exception& e) {
::MessageBoxA(nullptr, e.what(), "pm-image startup error", MB_ICONERROR);
}
CoUninitialize();
return -1;
}
} // namespace media::win

View File

@ -0,0 +1,13 @@
#pragma once
#include <string>
#include <vector>
namespace media::win {
/// Launch the Win32++ "next" UI. Runs the message loop; returns when window is closed.
/// @param initial_files Optional pre-seeded file paths to add to the queue on launch.
/// @return 0 on normal exit.
int launch_ui_next(const std::vector<std::string>& initial_files = {});
} // namespace media::win

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls"
version="6.0.0.0" processorArchitecture="*"
publicKeyToken="6595b64144ccf1df" language="*" />
</dependentAssembly>
</dependency>
</assembly>

View File

@ -0,0 +1,53 @@
#ifndef PM_UI_STDAFX_H
#define PM_UI_STDAFX_H
// Standard library
#include <vector>
#include <string>
#include <map>
#include <queue>
#include <mutex>
#include <thread>
#include <functional>
#include <cassert>
#include <sstream>
// Win32++ — full set in the order required by the library.
// (mirrors the RibbonDockFrame sample's stdafx.h)
#include <wxx_appcore.h>
#include <wxx_archive.h>
#include <wxx_controls.h>
#include <wxx_cstring.h>
#include <wxx_dialog.h>
#include <wxx_dockframe.h>
#include <wxx_docking.h>
#include <wxx_exception.h>
#include <wxx_file.h>
#include <wxx_folderdialog.h>
#include <wxx_frame.h>
#include <wxx_gdi.h>
#include <wxx_imagelist.h>
#include <wxx_listview.h>
#include <wxx_menu.h>
#include <wxx_menubar.h>
#include <wxx_propertysheet.h>
#include <wxx_rebar.h>
#include <wxx_rect.h>
#include <wxx_regkey.h>
#include <wxx_statusbar.h>
#include <wxx_stdcontrols.h>
#include <wxx_tab.h>
#include <wxx_themes.h>
#include <wxx_thread.h>
#include <wxx_toolbar.h>
#include <wxx_wincore.h>
#ifndef WIN32_LEAN_AND_MEAN
#include <wxx_commondlg.h>
#include <wxx_taskdialog.h>
#if defined(_MSC_VER)
#include <wxx_ribbon.h>
#endif
#endif
#endif // PM_UI_STDAFX_H

View File