diff --git a/packages/media/cpp/CMakeLists.txt b/packages/media/cpp/CMakeLists.txt index f6ccc9c4..2febd5d6 100644 --- a/packages/media/cpp/CMakeLists.txt +++ b/packages/media/cpp/CMakeLists.txt @@ -203,6 +203,7 @@ if(WIN32) "${_UI_NEXT_DIR}/DropView.cpp" "${_UI_NEXT_DIR}/FileQueue.cpp" "${_UI_NEXT_DIR}/LogPanel.cpp" + "${_UI_NEXT_DIR}/FileInfoPanel.cpp" "${_UI_NEXT_DIR}/SettingsPanel.cpp" "${_UI_NEXT_DIR}/Mainfrm.cpp" "${_UI_NEXT_DIR}/launch_ui_next.cpp" diff --git a/packages/media/cpp/dist/pm-image.exe b/packages/media/cpp/dist/pm-image.exe index 28d0c888..1163ee93 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 6ed2df42..a73849ce 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/docs/screenshot-v0.PNG b/packages/media/cpp/docs/screenshot-v0.PNG new file mode 100644 index 00000000..5c8430ca Binary files /dev/null and b/packages/media/cpp/docs/screenshot-v0.PNG differ diff --git a/packages/media/cpp/src/win/ui_next/DropView.h b/packages/media/cpp/src/win/ui_next/DropView.h index ae18d508..6a1d1cee 100644 --- a/packages/media/cpp/src/win/ui_next/DropView.h +++ b/packages/media/cpp/src/win/ui_next/DropView.h @@ -31,4 +31,55 @@ private: CString m_label; }; +// Generated preview dock (for showing AI-generated images on the right) +class CGenPreviewContainer : public CDockContainer +{ +public: + CGenPreviewContainer() { + SetTabText(L"Generated"); + SetDockCaption(L"Generated Preview"); + SetView(m_preview); + } + virtual ~CGenPreviewContainer() override = default; + CImagePreview& GetPreview() { return m_preview; } + +protected: + virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override { + 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; + } + +private: + CGenPreviewContainer(const CGenPreviewContainer&) = delete; + CGenPreviewContainer& operator=(const CGenPreviewContainer&) = delete; + CImagePreview m_preview; +}; + +class CDockGenPreview : public CDocker +{ +public: + CDockGenPreview() { SetView(m_container); SetBarWidth(8); } + virtual ~CDockGenPreview() override = default; + CGenPreviewContainer& GetContainer() { return m_container; } + +protected: + virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override { + 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; + } + +private: + CDockGenPreview(const CDockGenPreview&) = delete; + CDockGenPreview& operator=(const CDockGenPreview&) = delete; + CGenPreviewContainer m_container; +}; + #endif // PM_UI_DROPVIEW_H diff --git a/packages/media/cpp/src/win/ui_next/FileInfoPanel.cpp b/packages/media/cpp/src/win/ui_next/FileInfoPanel.cpp new file mode 100644 index 00000000..b8acaafe --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/FileInfoPanel.cpp @@ -0,0 +1,235 @@ +#include "stdafx.h" +#include "FileInfoPanel.h" +#include +#include +#include +#include + +#pragma comment(lib, "gdiplus.lib") + +namespace fs = std::filesystem; + +static std::wstring FormatFileSize(uintmax_t bytes) { + std::wostringstream oss; + if (bytes < 1024) + oss << bytes << L" B"; + else if (bytes < 1024 * 1024) + oss << std::fixed << std::setprecision(1) << (bytes / 1024.0) << L" KB"; + else if (bytes < 1024ULL * 1024 * 1024) + oss << std::fixed << std::setprecision(1) << (bytes / (1024.0 * 1024.0)) << L" MB"; + else + oss << std::fixed << std::setprecision(2) << (bytes / (1024.0 * 1024.0 * 1024.0)) << L" GB"; + return oss.str(); +} + +static std::wstring FormatFileTime(const fs::file_time_type& ft) { + auto sctp = std::chrono::time_point_cast( + ft - fs::file_time_type::clock::now() + std::chrono::system_clock::now()); + std::time_t tt = std::chrono::system_clock::to_time_t(sctp); + struct tm local; + localtime_s(&local, &tt); + wchar_t buf[64]; + wcsftime(buf, 64, L"%Y-%m-%d %H:%M:%S", &local); + return buf; +} + +static std::wstring ReadExifString(Gdiplus::Image* img, PROPID id) { + UINT sz = img->GetPropertyItemSize(id); + if (sz == 0) return {}; + std::vector buf(sz); + auto* item = reinterpret_cast(buf.data()); + if (img->GetPropertyItem(id, sz, item) != Gdiplus::Ok) return {}; + if (item->type == 2 && item->value) { + std::string s(reinterpret_cast(item->value)); + return std::wstring(s.begin(), s.end()); + } + return {}; +} + +static std::wstring ReadExifRational(Gdiplus::Image* img, PROPID id) { + UINT sz = img->GetPropertyItemSize(id); + if (sz == 0) return {}; + std::vector buf(sz); + auto* item = reinterpret_cast(buf.data()); + if (img->GetPropertyItem(id, sz, item) != Gdiplus::Ok) return {}; + if (item->type == 5 && item->length >= 8) { + ULONG* r = reinterpret_cast(item->value); + if (r[1] == 0) return L"0"; + double v = (double)r[0] / r[1]; + if (v < 1.0) { + std::wostringstream oss; + oss << r[0] << L"/" << r[1]; + return oss.str(); + } + std::wostringstream oss; + oss << std::fixed << std::setprecision(1) << v; + return oss.str(); + } + return {}; +} + +static std::wstring ReadExifShort(Gdiplus::Image* img, PROPID id) { + UINT sz = img->GetPropertyItemSize(id); + if (sz == 0) return {}; + std::vector buf(sz); + auto* item = reinterpret_cast(buf.data()); + if (img->GetPropertyItem(id, sz, item) != Gdiplus::Ok) return {}; + if (item->type == 3 && item->length >= 2) { + USHORT val = *reinterpret_cast(item->value); + return std::to_wstring(val); + } + return {}; +} + +////////////////////////////////////////// +// CFileInfoView +////////////////////////////////////////// + +int CFileInfoView::OnCreate(CREATESTRUCT&) +{ + m_hEdit = ::CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL, + 0, 0, 100, 100, GetHwnd(), nullptr, GetModuleHandleW(nullptr), nullptr); + + HFONT hFont = static_cast(::GetStockObject(DEFAULT_GUI_FONT)); + ::SendMessage(m_hEdit, WM_SETFONT, (WPARAM)hFont, TRUE); + return 0; +} + +void CFileInfoView::Clear() +{ + if (m_hEdit) + ::SetWindowTextW(m_hEdit, L""); +} + +void CFileInfoView::ShowFileInfo(LPCWSTR path) +{ + if (!m_hEdit || !path || !path[0]) { Clear(); return; } + + std::wostringstream info; + + std::error_code ec; + fs::path fp(path); + + info << L"File: " << fp.filename().wstring() << L"\r\n"; + info << L"Path: " << fp.parent_path().wstring() << L"\r\n"; + + if (fs::exists(fp, ec)) { + auto sz = fs::file_size(fp, ec); + if (!ec) info << L"Size: " << FormatFileSize(sz) << L"\r\n"; + + auto lwt = fs::last_write_time(fp, ec); + if (!ec) info << L"Modified: " << FormatFileTime(lwt) << L"\r\n"; + } + + info << L"\r\n"; + + // Read image dimensions and EXIF via GDI+ + Gdiplus::GdiplusStartupInput si; + ULONG_PTR token = 0; + Gdiplus::GdiplusStartup(&token, &si, nullptr); + + auto* img = Gdiplus::Image::FromFile(path); + if (img && img->GetLastStatus() == Gdiplus::Ok) { + info << L"Dimensions: " << img->GetWidth() << L" x " << img->GetHeight() << L"\r\n"; + + GUID rawFmt; + img->GetRawFormat(&rawFmt); + auto pixFmt = img->GetPixelFormat(); + int bpp = (pixFmt >> 8) & 0xFF; + info << L"Bit depth: " << bpp << L"\r\n"; + + info << L"\r\n--- EXIF ---\r\n"; + + auto make = ReadExifString(img, 0x010F); + auto model = ReadExifString(img, 0x0110); + auto date = ReadExifString(img, 0x9003); + auto fnum = ReadExifRational(img, 0x829D); + auto expT = ReadExifRational(img, 0x829A); + auto iso = ReadExifShort(img, 0x8827); + auto focal = ReadExifRational(img, 0x920A); + auto focal35= ReadExifShort(img, 0xA405); + auto sw = ReadExifString(img, 0x0131); + + if (!make.empty()) info << L"Make: " << make << L"\r\n"; + if (!model.empty()) info << L"Camera: " << model << L"\r\n"; + if (!date.empty()) info << L"Date taken: " << date << L"\r\n"; + if (!fnum.empty()) info << L"Aperture: f/" << fnum << L"\r\n"; + if (!expT.empty()) info << L"Shutter: " << expT << L"s\r\n"; + if (!iso.empty()) info << L"ISO: " << iso << L"\r\n"; + if (!focal.empty()) info << L"Focal length: " << focal << L"mm\r\n"; + if (!focal35.empty()) info << L"Focal (35mm eq): " << focal35 << L"mm\r\n"; + if (!sw.empty()) info << L"Software: " << sw << L"\r\n"; + + if (make.empty() && model.empty() && date.empty()) + info << L"(no EXIF data)\r\n"; + } else { + info << L"(could not read image)\r\n"; + } + + delete img; + if (token) Gdiplus::GdiplusShutdown(token); + + ::SetWindowTextW(m_hEdit, info.str().c_str()); +} + +LRESULT CFileInfoView::WndProc(UINT msg, WPARAM wparam, LPARAM lparam) +{ + try { + if (msg == WM_SIZE) { + int w = LOWORD(lparam), h = HIWORD(lparam); + if (m_hEdit) + ::MoveWindow(m_hEdit, 0, 0, w, h, TRUE); + } + 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; +} + +////////////////////////////////////////// +// CFileInfoContainer +////////////////////////////////////////// + +CFileInfoContainer::CFileInfoContainer() +{ + SetTabText(L"File Info"); + SetDockCaption(L"File Info"); + SetView(m_view); +} + +LRESULT CFileInfoContainer::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; +} + +////////////////////////////////////////// +// CDockFileInfo +////////////////////////////////////////// + +CDockFileInfo::CDockFileInfo() +{ + SetView(m_container); + SetBarWidth(8); +} + +LRESULT CDockFileInfo::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/FileInfoPanel.h b/packages/media/cpp/src/win/ui_next/FileInfoPanel.h new file mode 100644 index 00000000..960313db --- /dev/null +++ b/packages/media/cpp/src/win/ui_next/FileInfoPanel.h @@ -0,0 +1,59 @@ +#ifndef PM_UI_FILEINFOPANEL_H +#define PM_UI_FILEINFOPANEL_H + +#include "stdafx.h" +#include + +class CFileInfoView : public CWnd +{ +public: + CFileInfoView() = default; + virtual ~CFileInfoView() override = default; + + void ShowFileInfo(LPCWSTR path); + void Clear(); + +protected: + virtual int OnCreate(CREATESTRUCT& cs) override; + virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override; + +private: + CFileInfoView(const CFileInfoView&) = delete; + CFileInfoView& operator=(const CFileInfoView&) = delete; + + HWND m_hEdit = nullptr; +}; + +class CFileInfoContainer : public CDockContainer +{ +public: + CFileInfoContainer(); + virtual ~CFileInfoContainer() override = default; + CFileInfoView& GetInfoView() { return m_view; } + +protected: + virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override; + +private: + CFileInfoContainer(const CFileInfoContainer&) = delete; + CFileInfoContainer& operator=(const CFileInfoContainer&) = delete; + CFileInfoView m_view; +}; + +class CDockFileInfo : public CDocker +{ +public: + CDockFileInfo(); + virtual ~CDockFileInfo() override = default; + CFileInfoContainer& GetInfoContainer() { return m_container; } + +protected: + virtual LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam) override; + +private: + CDockFileInfo(const CDockFileInfo&) = delete; + CDockFileInfo& operator=(const CDockFileInfo&) = delete; + CFileInfoContainer m_container; +}; + +#endif // PM_UI_FILEINFOPANEL_H diff --git a/packages/media/cpp/src/win/ui_next/Mainfrm.cpp b/packages/media/cpp/src/win/ui_next/Mainfrm.cpp index ac2e6f7e..72b386f9 100644 --- a/packages/media/cpp/src/win/ui_next/Mainfrm.cpp +++ b/packages/media/cpp/src/win/ui_next/Mainfrm.cpp @@ -75,9 +75,13 @@ STDMETHODIMP CMainFrame::Execute(UINT32 cmdID, UI_EXECUTIONVERB verb, SwitchSettingsMode(CSettingsView::MODE_RESIZE); OnResize(); break; - case IDC_CMD_TRANSFORM: + case IDC_CMD_PROMPT: SwitchSettingsMode(CSettingsView::MODE_TRANSFORM); - OnTransform(); + OnPrompt(); + break; + case IDC_CMD_RUN: + SwitchSettingsMode(CSettingsView::MODE_TRANSFORM); + OnRun(); break; case IDC_CMD_PRESETS: OnPresets(); break; case IDC_CMD_ABOUT: OnHelp(); break; @@ -243,7 +247,6 @@ void CMainFrame::OnInitialUpdate() m_pDockQueue = static_cast(pDockQ); m_pDockQueue->GetQueueContainer().SetHideSingleTab(TRUE); - // Log panel docked below the queue auto pDockL = m_pDockQueue->AddDockedChild(std::make_unique(), DS_DOCKED_RIGHT | style, DpiScaleInt(360)); m_pDockLog = static_cast(pDockL); @@ -254,6 +257,18 @@ void CMainFrame::OnInitialUpdate() m_pDockSettings = static_cast(pDockS); m_pDockSettings->GetSettingsContainer().SetHideSingleTab(TRUE); + // Generated preview (transform mode) below settings + auto pDockGP = m_pDockSettings->AddDockedChild(std::make_unique(), + DS_DOCKED_BOTTOM | style, DpiScaleInt(200)); + m_pDockGenPreview = static_cast(pDockGP); + m_pDockGenPreview->GetContainer().SetHideSingleTab(TRUE); + + // File Info panel below generated preview + auto pDockFI = m_pDockGenPreview->AddDockedChild(std::make_unique(), + DS_DOCKED_BOTTOM | style, DpiScaleInt(160)); + m_pDockFileInfo = static_cast(pDockFI); + m_pDockFileInfo->GetInfoContainer().SetHideSingleTab(TRUE); + DragAcceptFiles(TRUE); SetWindowText(L"pm-image"); GetStatusBar().SetPartText(0, L"Drop files or use Add Files to begin."); @@ -540,7 +555,21 @@ static HWND CreatePromptDialog(HWND parent, std::wstring& prompt) return (result == IDOK) ? parent : nullptr; } -void CMainFrame::OnTransform() +void CMainFrame::OnPrompt() +{ + std::wstring prompt = m_lastPrompt; + if (!CreatePromptDialog(GetHwnd(), prompt)) + return; + m_lastPrompt = prompt; + if (!prompt.empty()) { + CString s; + s.Format(L"Prompt set: %.60s%s", prompt.c_str(), prompt.size() > 60 ? L"\u2026" : L""); + GetStatusBar().SetPartText(0, s); + LogMessage(s); + } +} + +void CMainFrame::OnRun() { if (m_processing) { ::MessageBox(GetHwnd(), L"Already processing.", L"pm-image", MB_ICONWARNING); @@ -554,18 +583,13 @@ void CMainFrame::OnTransform() return; } - std::wstring prompt = m_lastPrompt; - if (!CreatePromptDialog(GetHwnd(), prompt)) - return; - if (prompt.empty()) { - ::MessageBox(GetHwnd(), L"Prompt cannot be empty.", L"pm-image", MB_ICONWARNING); - return; + if (m_lastPrompt.empty()) { + OnPrompt(); + if (m_lastPrompt.empty()) return; } - m_lastPrompt = prompt; - std::string promptUtf8 = wide_to_utf8(prompt); + std::string promptUtf8 = wide_to_utf8(m_lastPrompt); - // Resolve API key std::string api_key; const char* env_key = std::getenv("IMAGE_TRANSFORM_GOOGLE_API_KEY"); if (env_key && env_key[0] != '\0') api_key = env_key; @@ -618,11 +642,17 @@ void CMainFrame::OnTransform() if (result.ok) { ++ok; ::PostMessage(hwnd, UWM_TRANSFORM_PROGRESS, (WPARAM)idx, 2); - // Notify main thread about generated file - std::wstring wout = utf8_to_wide_mf(result.output_path); - auto* ws = new wchar_t[wout.size() + 1]; - wcscpy_s(ws, wout.size() + 1, wout.c_str()); - ::PostMessage(hwnd, UWM_GENERATED_FILE, (WPARAM)ws, 0); + + // Send both source path and generated path as a pair + std::wstring wsrc = std::wstring(input.begin(), input.end()); + std::wstring wout; + int n = MultiByteToWideChar(CP_UTF8, 0, result.output_path.c_str(), + (int)result.output_path.size(), nullptr, 0); + if (n > 0) { wout.resize(n); MultiByteToWideChar(CP_UTF8, 0, + result.output_path.c_str(), (int)result.output_path.size(), wout.data(), n); } + + auto* pair = new std::pair(wsrc, wout); + ::PostMessage(hwnd, UWM_GENERATED_FILE, (WPARAM)pair, 0); } else { ++fail; ::PostMessage(hwnd, UWM_TRANSFORM_PROGRESS, (WPARAM)idx, 3); @@ -723,17 +753,45 @@ LRESULT CMainFrame::OnLogMessage(WPARAM wparam) LRESULT CMainFrame::OnGeneratedFile(WPARAM wparam) { - auto* ws = reinterpret_cast(wparam); - if (ws && m_pDockQueue) { + auto* pair = reinterpret_cast*>(wparam); + if (pair && m_pDockQueue) { + m_generatedMap[pair->first] = pair->second; + auto& lv = m_pDockQueue->GetQueueContainer().GetListView(); - int idx = lv.AddFile(CString(ws)); + int idx = lv.AddFile(CString(pair->second.c_str())); lv.SetItemStatus(idx, L"\u2728 Generated"); - LogMessage(CString(L"Generated: ") + ws); - delete[] ws; + LogMessage(CString(L"Generated: ") + pair->second.c_str()); + delete pair; } return 0; } +void CMainFrame::UpdateFileInfoForSelection(int idx) +{ + if (!m_pDockQueue) return; + CString path = m_pDockQueue->GetQueueContainer().GetListView().GetItemPath(idx); + if (path.IsEmpty()) return; + + // Update file info panel + if (m_pDockFileInfo) + m_pDockFileInfo->GetInfoContainer().GetInfoView().ShowFileInfo(path.c_str()); + + // In transform mode, check if a generated version exists and show it + if (m_pDockGenPreview && m_pDockSettings) { + auto& sv = m_pDockSettings->GetSettingsContainer().GetSettingsView(); + if (sv.GetMode() == CSettingsView::MODE_TRANSFORM) { + std::wstring srcKey(path.c_str()); + auto it = m_generatedMap.find(srcKey); + auto& gp = m_pDockGenPreview->GetContainer().GetPreview(); + if (it != m_generatedMap.end()) { + gp.LoadPicture(it->second.c_str()); + } else { + gp.ClearPicture(); + } + } + } +} + // ── Presets ───────────────────────────────────────────── void CMainFrame::LoadPresets() @@ -938,6 +996,7 @@ LRESULT CMainFrame::WndProc(UINT msg, WPARAM wparam, LPARAM lparam) CString path = m_pDockQueue->GetQueueContainer().GetListView().GetItemPath(idx); if (!path.IsEmpty()) m_view.LoadPicture(path.c_str()); + UpdateFileInfoForSelection(idx); } return 0; } diff --git a/packages/media/cpp/src/win/ui_next/Mainfrm.h b/packages/media/cpp/src/win/ui_next/Mainfrm.h index c444fd07..201236ba 100644 --- a/packages/media/cpp/src/win/ui_next/Mainfrm.h +++ b/packages/media/cpp/src/win/ui_next/Mainfrm.h @@ -5,9 +5,11 @@ #include "FileQueue.h" #include "SettingsPanel.h" #include "LogPanel.h" +#include "FileInfoPanel.h" #include "Resource.h" #include "core/transform.hpp" #include +#include struct PromptPreset { std::string name; @@ -43,10 +45,12 @@ private: void OnAddFolder(); void OnClearQueue(); void OnResize(); - void OnTransform(); + void OnPrompt(); + void OnRun(); void OnExit(); void OnPresets(); void SwitchSettingsMode(CSettingsView::Mode mode); + void UpdateFileInfoForSelection(int idx); void InvalidateToggle(UINT32 cmdID); bool IsToggleSelected(UINT32 cmdID) const; @@ -76,6 +80,9 @@ private: CDockQueue* m_pDockQueue = nullptr; CDockSettings* m_pDockSettings = nullptr; CDockLog* m_pDockLog = nullptr; + CDockFileInfo* m_pDockFileInfo = nullptr; + + CDockGenPreview* m_pDockGenPreview = nullptr; std::thread m_worker; bool m_processing = false; @@ -89,6 +96,9 @@ private: // Prompt presets std::vector m_presets; std::string m_settingsPath; + + // Source → generated file mapping + std::map m_generatedMap; }; #endif // PM_UI_MAINFRM_H diff --git a/packages/media/cpp/src/win/ui_next/Ribbon.xml b/packages/media/cpp/src/win/ui_next/Ribbon.xml index 60b88401..e3aa5d20 100644 --- a/packages/media/cpp/src/win/ui_next/Ribbon.xml +++ b/packages/media/cpp/src/win/ui_next/Ribbon.xml @@ -90,26 +90,44 @@ Presets - + - Transform + Prompt - + - Transform + Run + + + + + Prompt - res/TransformL.bmp + res/PromptL.bmp - res/TransformS.bmp + res/PromptS.bmp + + + + + Run + + + res/RunL.bmp + + + res/RunS.bmp Model + res/ModelL.bmp + res/ModelS.bmp Gemini 3 Pro Image @@ -120,6 +138,8 @@ Aspect + res/AspectL.bmp + res/AspectS.bmp (auto) @@ -142,6 +162,8 @@ Size + res/SizeL.bmp + res/SizeS.bmp (default) @@ -158,6 +180,8 @@ Presets + res/PresetsL.bmp + res/PresetsS.bmp @@ -212,12 +236,12 @@ - - + + - - + + @@ -246,11 +270,12 @@ - + +