From 573d04521d0203480108247c9a6984917b3ed878 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Mon, 1 Jun 2026 18:11:04 +0900 Subject: [PATCH] =?UTF-8?q?feat(shell):=20=EC=9E=85=EB=A0=A5=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=EB=B3=84=20=EC=B6=94=EC=B2=9C=20=EC=B6=9C=EB=A0=A5=20?= =?UTF-8?q?=E2=80=94=20=EC=98=81=EC=83=81=E2=86=92=EC=98=81=EC=83=81/?= =?UTF-8?q?=EC=98=A4=EB=94=94=EC=98=A4,=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=E2=86=92=EB=8D=B0=EC=9D=B4=ED=84=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 셸 카스케이드 서브메뉴가 입력 형식과 무관하게 이미지 출력만 보이던 문제. EnumSubCommands에 전 카테고리 출력(이미지·영상·오디오·데이터·마크업)을 나열하고, 각 SubVerbCommand.GetState가 첫 파일 확장자를 보고 추천되지 않는 출력을 ECS_HIDDEN으로 숨김. - CatOf/Recommend/FirstItemExt 헬퍼 추가 - 영상→mp4/webm/mkv/mov+mp3/m4a/flac/wav, 오디오→오디오, 데이터→json/csv/xlsx, 이미지/벡터/문서→이미지+pdf+OCR - '변환…(옵션 선택)'은 항상 표시 (만능 경로) --- src/Everything2Everything.Shell/dllmain.cpp | 98 +++++++++++++++++---- 1 file changed, 82 insertions(+), 16 deletions(-) diff --git a/src/Everything2Everything.Shell/dllmain.cpp b/src/Everything2Everything.Shell/dllmain.cpp index 2c4e300..f1927d9 100644 --- a/src/Everything2Everything.Shell/dllmain.cpp +++ b/src/Everything2Everything.Shell/dllmain.cpp @@ -107,6 +107,50 @@ HRESULT LaunchAppWithItems(const wchar_t* verb, IShellItemArray* items) { return S_OK; } +// --------------------------------------------------------------------- +// 입력 형식별 추천 출력 — 영상 우클릭엔 영상/오디오 출력만, 데이터엔 데이터 출력만 보이도록 +// --------------------------------------------------------------------- +enum class Cat { Image, Video, Audio, Data, Vector, Doc, Markup, Text }; + +inline Cat CatOf(const std::wstring& e) { + if (e == L".mp4" || e == L".webm" || e == L".mkv" || e == L".mov" || e == L".avi") return Cat::Video; + if (e == L".mp3" || e == L".aac" || e == L".m4a" || e == L".opus" || e == L".ogg" || e == L".flac" || e == L".wav") return Cat::Audio; + if (e == L".csv" || e == L".json" || e == L".xlsx") return Cat::Data; + if (e == L".svg") return Cat::Vector; + if (e == L".md" || e == L".markdown" || e == L".html" || e == L".htm") return Cat::Markup; + if (e == L".txt") return Cat::Text; + if (e == L".pdf" || e == L".docx" || e == L".doc" || e == L".hwp" || e == L".hwpx") return Cat::Doc; + return Cat::Image; // 이미지/RAW/HEIC/PSD 등 나머지는 이미지로 취급 +} + +inline bool Recommend(const std::wstring& inExt, const std::wstring& outExt) { + const Cat in = CatOf(inExt), out = CatOf(outExt); + if (in == Cat::Video) return out == Cat::Video || out == Cat::Audio; + if (in == Cat::Audio) return out == Cat::Audio; + if (in == Cat::Data) return out == Cat::Data; + if (in == Cat::Image || in == Cat::Vector) + return out == Cat::Image || outExt == L".pdf" || outExt == L".txt" || outExt == L".docx"; + if (in == Cat::Doc) + return out == Cat::Image || outExt == L".pdf" || outExt == L".txt" || outExt == L".docx" || outExt == L".html" || outExt == L".md"; + if (in == Cat::Markup || in == Cat::Text) + return outExt == L".html" || outExt == L".md" || outExt == L".txt" || outExt == L".docx" || outExt == L".pdf"; + return true; +} + +inline std::wstring FirstItemExt(IShellItemArray* items) { + if (!items) return L""; + ComPtr item; + if (FAILED(items->GetItemAt(0, &item))) return L""; + wil::unique_cotaskmem_string path; + if (FAILED(item->GetDisplayName(SIGDN_FILESYSPATH, &path))) return L""; + std::wstring p(path.get()); + const auto dot = p.find_last_of(L'.'); + if (dot == std::wstring::npos) return L""; + std::wstring ext = p.substr(dot); + for (auto& c : ext) c = static_cast(towlower(c)); + return ext; +} + // --------------------------------------------------------------------- // Sub-command — leaf in the cascade (e.g., "JPEG (.jpg)" → "to jpg") // --------------------------------------------------------------------- @@ -117,9 +161,10 @@ class SubVerbCommand : public RuntimeClass< public: HRESULT RuntimeClassInitialize() { return S_OK; } - void Configure(std::wstring title, std::wstring verb) { + void Configure(std::wstring title, std::wstring verb, std::wstring outExt = L"") { title_ = std::move(title); verb_ = std::move(verb); + outExt_ = std::move(outExt); } IFACEMETHODIMP GetTitle(IShellItemArray*, PWSTR* name) override { @@ -141,8 +186,10 @@ public: return S_OK; } - IFACEMETHODIMP GetState(IShellItemArray*, BOOL, EXPCMDSTATE* cmdState) override { - *cmdState = ECS_ENABLED; + IFACEMETHODIMP GetState(IShellItemArray* items, BOOL, EXPCMDSTATE* cmdState) override { + if (outExt_.empty()) { *cmdState = ECS_ENABLED; return S_OK; } // "변환…" 항목은 항상 표시 + const std::wstring inExt = FirstItemExt(items); + *cmdState = (inExt.empty() || Recommend(inExt, outExt_)) ? ECS_ENABLED : ECS_HIDDEN; return S_OK; } @@ -163,6 +210,7 @@ public: private: std::wstring title_; std::wstring verb_; + std::wstring outExt_; }; // --------------------------------------------------------------------- @@ -224,10 +272,10 @@ struct SubItem { const wchar_t* Verb; }; -inline ComPtr MakeSub(const wchar_t* title, const wchar_t* verb) { +inline ComPtr MakeSub(const wchar_t* title, const wchar_t* verb, const wchar_t* outExt = L"") { ComPtr cmd; MakeAndInitialize(&cmd); - cmd->Configure(title, verb); + cmd->Configure(title, verb, outExt); return cmd; } @@ -274,17 +322,35 @@ public: *enumCommands = nullptr; std::vector> cmds; - cmds.reserve(11); - cmds.push_back(MakeSub(L"JPEG (.jpg)", L"to jpg")); - cmds.push_back(MakeSub(L"PNG (.png)", L"to png")); - cmds.push_back(MakeSub(L"WebP (.webp)", L"to webp")); - cmds.push_back(MakeSub(L"PDF (.pdf)", L"to pdf")); - cmds.push_back(MakeSub(L"텍스트 (.txt) — OCR", L"to txt")); - cmds.push_back(MakeSub(L"Word (.docx) — OCR", L"to docx")); - cmds.push_back(MakeSub(L"AVIF (.avif)", L"to avif")); - cmds.push_back(MakeSub(L"GIF (.gif)", L"to gif")); - cmds.push_back(MakeSub(L"TIFF (.tif)", L"to tif")); - cmds.push_back(MakeSub(L"BMP (.bmp)", L"to bmp")); + cmds.reserve(26); + // 이미지 (입력이 이미지/벡터/문서일 때 표시) + cmds.push_back(MakeSub(L"JPEG (.jpg)", L"to jpg", L".jpg")); + cmds.push_back(MakeSub(L"PNG (.png)", L"to png", L".png")); + cmds.push_back(MakeSub(L"WebP (.webp)", L"to webp", L".webp")); + cmds.push_back(MakeSub(L"AVIF (.avif)", L"to avif", L".avif")); + cmds.push_back(MakeSub(L"GIF (.gif)", L"to gif", L".gif")); + cmds.push_back(MakeSub(L"TIFF (.tif)", L"to tif", L".tif")); + cmds.push_back(MakeSub(L"BMP (.bmp)", L"to bmp", L".bmp")); + cmds.push_back(MakeSub(L"PDF (.pdf)", L"to pdf", L".pdf")); + cmds.push_back(MakeSub(L"텍스트 (.txt)", L"to txt", L".txt")); + cmds.push_back(MakeSub(L"Word (.docx)", L"to docx", L".docx")); + cmds.push_back(MakeSub(L"HTML (.html)", L"to html", L".html")); + cmds.push_back(MakeSub(L"Markdown (.md)", L"to md", L".md")); + // 영상 (입력이 영상일 때 표시) + cmds.push_back(MakeSub(L"MP4 (.mp4)", L"to mp4", L".mp4")); + cmds.push_back(MakeSub(L"WebM (.webm)", L"to webm", L".webm")); + cmds.push_back(MakeSub(L"MKV (.mkv)", L"to mkv", L".mkv")); + cmds.push_back(MakeSub(L"MOV (.mov)", L"to mov", L".mov")); + // 오디오 (입력이 영상/오디오일 때 표시) + cmds.push_back(MakeSub(L"MP3 (.mp3)", L"to mp3", L".mp3")); + cmds.push_back(MakeSub(L"M4A (.m4a)", L"to m4a", L".m4a")); + cmds.push_back(MakeSub(L"FLAC (.flac)", L"to flac", L".flac")); + cmds.push_back(MakeSub(L"WAV (.wav)", L"to wav", L".wav")); + // 데이터 (입력이 데이터일 때 표시) + cmds.push_back(MakeSub(L"JSON (.json)", L"to json", L".json")); + cmds.push_back(MakeSub(L"CSV (.csv)", L"to csv", L".csv")); + cmds.push_back(MakeSub(L"Excel (.xlsx)", L"to xlsx", L".xlsx")); + // 항상 표시 cmds.push_back(MakeSub(L"변환… (옵션 선택)", L"dialog")); ComPtr enumerator;