From 5a4388f2b8b9a4291ab168b1193b562336ff7ab5 Mon Sep 17 00:00:00 2001 From: Fernando Nillsson Cidade <128842267+fernandoncidade@users.noreply.github.com> Date: Sat, 21 Mar 2026 18:17:58 -0300 Subject: [PATCH 1/3] v2026.3.21.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Arquivos-Chave** [CompressDialog.cpp](c:/Users/ferna/DEV/CPP/7zip/CPP/7zip/UI/GUI/CompressDialog.cpp), [CompressDialog.h](c:/Users/ferna/DEV/CPP/7zip/CPP/7zip/UI/GUI/CompressDialog.h), [CompressDialog.rc](c:/Users/ferna/DEV/CPP/7zip/CPP/7zip/UI/GUI/CompressDialog.rc), [UpdateGUI.cpp](c:/Users/ferna/DEV/CPP/7zip/CPP/7zip/UI/GUI/UpdateGUI.cpp), [Update.h](c:/Users/ferna/DEV/CPP/7zip/CPP/7zip/UI/Common/Update.h), [LangUtils.cpp](c:/Users/ferna/DEV/CPP/7zip/CPP/7zip/UI/FileManager/LangUtils.cpp), [Build.mak](c:/Users/ferna/DEV/CPP/7zip/CPP/Build.mak), [makefile](c:/Users/ferna/DEV/CPP/7zip/CPP/7zip/Bundles/Fm/makefile), [7zipUninstall.c](c:/Users/ferna/DEV/CPP/7zip/C/Util/7zipUninstall/7zipUninstall.c), [7zipInstall.c](c:/Users/ferna/DEV/CPP/7zip/C/Util/7zipInstall/7zipInstall.c) **English** Suggested title: `Support multiple output archives, per-item packaging flow, and packaging/uninstall hardening` This PR extends the fork’s compression flow to support multiple output archives and, when the operation starts from multiple selected items, to execute per-item packaging with isolated destinations. The change is carried from the UI down to the options/execution layer, with safe initialization of the new state so the legacy behavior remains intact when the feature is not used. - The data model was extended to carry multiple destinations and item→archive mappings through `ArcPaths`, `SeparateItemMode`, `SeparateItemPaths`, `SeparateItemArchivePaths`, `ItemPaths`, `ItemOutputItemPaths`, and `ItemArcPaths`. - The compression dialog was reworked to accept up to 5 output paths, with an output-count selector, extra combo boxes, dynamic layout resizing, and per-item groups when multiple items are selected. - Archive name and extension handling was hardened: when the format or SFX state changes, the code removes the previous extension before appending the new one, preventing outputs such as `.7z.zip` or `.exe.exe`, while preserving `KeepName` formats and hash-based handlers. - Dialog finalization now validates empty and duplicate paths, normalizes final output paths, and blocks unsafe combinations with `Delete after compression`, especially when the same source item could feed more than one output. - The update pipeline no longer assumes a single `CUpdateArchiveCommand`. `UpdateGUI` now accepts multiple targets, recomputes `WorkingDir` per archive path, and, in separate-item mode, calls `UpdateArchive()` once per item/destination so failures stay isolated and the item-to-output mapping remains explicit. - Input items are now collected from the `censor` with explicit deduplication, keeping the actual UI selection aligned with the execution layer. - Language lookup and packaging were adapted for redistributed layouts: `LangUtils.cpp` now searches parent directories for `Lang`, while the File Manager bundle makefile copies language files into the relevant output folders. - The makefiles were hardened with explicit dependencies for the new resources, and `Build.mak` stops passing `$**` to `rc`, using only the target `.rc` file as the actual resource compiler input. - The uninstaller was strengthened for real Windows shell scenarios: it removes stale shell-extension CLSIDs first, tries to unload `7-zip.dll` and `7-zip32.dll` from `explorer.exe`, clears `read-only` attributes, removes `.tmp*` variants, schedules locked files for deletion after reboot, notifies shell association changes, and self-elevates with `runas`. - The installer now writes fork-specific uninstall metadata (`DisplayName`, `DisplayVersion`, `Publisher`), making it easier to distinguish the fork from the official build in Windows “Programs and Features”. Upstream note: the code already contains groundwork for a text-based output-path editor, but the corresponding UI entry point is currently hidden; the visible behavior today is the inline multi-output/per-item layout. The current `AboutDialog.*` files do not show a semantic diff and look like line-ending-only changes, so I would keep them out of the functional PR. Se quiser, eu também posso transformar isso no formato exato de corpo de PR do GitHub, com seções como `Summary`, `Motivation`, `Implementation Details`, `Risks` e `Validation`, já pronto para colar. **Português** Título sugerido: `Support multiple output archives, per-item packaging flow, and packaging/uninstall hardening` Este PR amplia o fluxo de compressão do fork para suportar múltiplos arquivos de saída e, quando a operação parte de múltiplos itens selecionados, executar um empacotamento item a item com isolamento de destino. A mudança foi propagada da UI até a camada de opções/execução, com inicialização segura dos novos estados para preservar o comportamento legado quando o recurso não é utilizado. - A estrutura de dados foi estendida para transportar múltiplos destinos e mapeamentos item→arquivo com `ArcPaths`, `SeparateItemMode`, `SeparateItemPaths`, `SeparateItemArchivePaths`, `ItemPaths`, `ItemOutputItemPaths` e `ItemArcPaths`. - A janela de compressão foi reestruturada para aceitar até 5 caminhos de saída, com contador de destinos, combos adicionais, redimensionamento dinâmico do layout e grupos por item quando há multi-seleção. - O tratamento de nomes e extensões foi endurecido: ao trocar formato ou SFX, o código remove a extensão anterior antes de anexar a nova, evitando resultados como `.7z.zip` ou `.exe.exe`, sem quebrar formatos `KeepName` ou handlers baseados em hash. - O fechamento do diálogo agora valida caminhos vazios e duplicados, normaliza caminhos finais e bloqueia combinações inseguras com `Delete after compression`, especialmente quando o mesmo item poderia alimentar múltiplos outputs. - O pipeline de update deixou de assumir um único `CUpdateArchiveCommand`. O `UpdateGUI` agora aceita vários destinos, recompõe `WorkingDir` por arquivo de saída e, em modo separado, executa `UpdateArchive()` individualmente por item/destino para isolar erros e preservar o vínculo entre item selecionado e arquivo gerado. - A coleta dos itens de entrada passou a ser feita a partir do `censor`, com deduplicação explícita, garantindo consistência entre a seleção real da UI e a execução. - O suporte a idiomas e empacotamento foi ajustado para layouts redistribuídos: `LangUtils.cpp` agora procura a pasta `Lang` subindo a árvore de diretórios, enquanto o bundle do File Manager copia os arquivos de idioma para os diretórios de saída relevantes. - Os makefiles foram fortalecidos com dependências explícitas dos novos recursos, e `Build.mak` deixou de passar `$**` ao `rc`, usando apenas o `.rc` alvo como entrada efetiva do compilador de recursos. - O desinstalador foi reforçado para cenários reais do Windows: remoção prévia de CLSIDs da shell extension, tentativa de descarregar `7-zip.dll` e `7-zip32.dll` do `explorer.exe`, limpeza de arquivos `read-only`, remoção de variantes `.tmp*`, agendamento para exclusão após reboot quando o arquivo está travado, notificação de alteração de associações e autoelevação com `runas`. - O instalador também passou a gravar metadados próprios do fork no registro de desinstalação (`DisplayName`, `DisplayVersion`, `Publisher`), facilitando a distinção entre o fork e a build oficial no painel “Programs and Features”. Nota para a equipe upstream: a infraestrutura de um editor textual de caminhos de saída já existe no código, mas a entrada visual correspondente permanece oculta; hoje, o comportamento efetivamente exposto é o layout inline com múltiplos destinos e o fluxo por item em multi-seleção. Os arquivos `AboutDialog.*` não exibem diff semântico no estado atual e parecem ser apenas normalização de fim de linha, então eu os separaria da PR funcional. --- .gitattributes | 2 + .gitignore | 4 + C/Util/7zipInstall/7zipInstall.c | 9 +- C/Util/7zipUninstall/7zipUninstall.c | 326 ++++- CPP/7zip/Bundles/Fm/makefile | 23 + CPP/7zip/UI/Common/Update.h | 6 +- CPP/7zip/UI/FileManager/LangUtils.cpp | 40 +- CPP/7zip/UI/GUI/CompressDialog.cpp | 1832 ++++++++++++++++++++++++- CPP/7zip/UI/GUI/CompressDialog.h | 108 +- CPP/7zip/UI/GUI/CompressDialog.rc | 115 +- CPP/7zip/UI/GUI/CompressDialogRes.h | 17 + CPP/7zip/UI/GUI/UpdateGUI.cpp | 174 ++- CPP/7zip/UI/GUI/makefile | 7 + CPP/Build.mak | 2 +- README_fernandoncidade.md | 503 +++++++ RELEASE.md | 689 ++++++++++ 16 files changed, 3686 insertions(+), 171 deletions(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README_fernandoncidade.md create mode 100644 RELEASE.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a116a3b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.vscode/ +/dist/ +/InstallerAssets/ +/Lang/ \ No newline at end of file diff --git a/C/Util/7zipInstall/7zipInstall.c b/C/Util/7zipInstall/7zipInstall.c index 7d8e8c4..0501b07 100644 --- a/C/Util/7zipInstall/7zipInstall.c +++ b/C/Util/7zipInstall/7zipInstall.c @@ -59,6 +59,9 @@ typedef enum { static LPCSTR const k_7zip = "7-Zip"; static LPCWSTR const k_Reg_Software_7zip = L"Software\\7-Zip"; +static LPCWSTR const k_Fork_DisplayName = L"7-Zip 2026.3.19.0 (x64)"; +static LPCWSTR const k_Fork_DisplayVersion = L"2026.3.19.0"; +static LPCWSTR const k_Fork_Publisher = L"Fernando Nillsson Cidade"; // #define Z7_64BIT_INSTALLER 1 @@ -943,8 +946,8 @@ static void WriteShellEx(void) LONG res = MyRegistry_CreateKey(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\7-Zip", &destKey); if (res == ERROR_SUCCESS) { - MyRegistry_SetString(destKey, L"DisplayName", k_7zip_with_Ver_str); - MyRegistry_SetString(destKey, L"DisplayVersion", LLL(MY_VERSION_NUMBERS)); + MyRegistry_SetString(destKey, L"DisplayName", k_Fork_DisplayName); + MyRegistry_SetString(destKey, L"DisplayVersion", k_Fork_DisplayVersion); MyRegistry_SetString(destKey, L"DisplayIcon", destPath); MyRegistry_SetString(destKey, L"InstallLocation", path); @@ -964,7 +967,7 @@ static void WriteShellEx(void) MyRegistry_SetDWORD(destKey, L"VersionMajor", MY_VER_MAJOR); MyRegistry_SetDWORD(destKey, L"VersionMinor", MY_VER_MINOR); - MyRegistry_SetString(destKey, L"Publisher", LLL(MY_AUTHOR_NAME)); + MyRegistry_SetString(destKey, L"Publisher", k_Fork_Publisher); // MyRegistry_SetString(destKey, L"HelpLink", L"http://www.7-zip.org/support.html"); // MyRegistry_SetString(destKey, L"URLInfoAbout", L"http://www.7-zip.org/"); diff --git a/C/Util/7zipUninstall/7zipUninstall.c b/C/Util/7zipUninstall/7zipUninstall.c index e7051e2..1419987 100644 --- a/C/Util/7zipUninstall/7zipUninstall.c +++ b/C/Util/7zipUninstall/7zipUninstall.c @@ -27,6 +27,9 @@ typedef enum { #else #include #endif +#ifndef UNDER_CE +#include +#endif #include "../../7zVersion.h" @@ -464,7 +467,7 @@ static BoolInt AreEqual_Path_PrefixName(const wchar_t *s, const wchar_t *prefix, return AreStringsEqual_NoCase(s + wcslen(prefix), name); } -static void WriteCLSID(void) +static void RemoveShellExtensionCLSID(void) { WCHAR s[MAX_PATH + 30]; @@ -540,7 +543,13 @@ static void WriteCLSID(void) } #endif +} +static void WriteCLSID(void) +{ + WCHAR s[MAX_PATH + 30]; + + RemoveShellExtensionCLSID(); if (MyRegistry_QueryString2(HKEY_LOCAL_MACHINE, k_AppPaths_7zFm, NULL, s)) { @@ -616,6 +625,216 @@ static BOOL RemoveFileAfterReboot(void) return RemoveFileAfterReboot2(path); } +static BOOL DeleteFileOrScheduleForReboot(const WCHAR *s, WRes *winRes, BoolInt *needReboot) +{ + const DWORD attrib = GetFileAttributesW(s); + if (attrib == INVALID_FILE_ATTRIBUTES) + return TRUE; + if (attrib & FILE_ATTRIBUTE_READONLY) + SetFileAttributesW(s, 0); + if (DeleteFileW(s)) + return TRUE; + if (RemoveFileAfterReboot2(s)) + { + if (needReboot) + *needReboot = True; + return TRUE; + } + if (winRes) + *winRes = GetLastError(); + return FALSE; +} + +#ifndef UNDER_CE +static HMODULE GetRemoteModuleBase(DWORD processId, const WCHAR *moduleName) +{ + HMODULE res = NULL; + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, processId); + if (snapshot != INVALID_HANDLE_VALUE) + { + MODULEENTRY32W me; + me.dwSize = sizeof(me); + if (Module32FirstW(snapshot, &me)) + do + { + if (AreStringsEqual_NoCase(me.szModule, moduleName)) + { + res = (HMODULE)me.modBaseAddr; + break; + } + } + while (Module32NextW(snapshot, &me)); + CloseHandle(snapshot); + } + return res; +} + +static LPVOID GetRemoteProcAddress(DWORD processId, const WCHAR *moduleName, const char *procName) +{ + HMODULE localModule = GetModuleHandleW(moduleName); + BoolInt needFree = False; + HMODULE remoteModule; + FARPROC localProc; + + if (!localModule) + { + localModule = LoadLibraryW(moduleName); + needFree = (localModule != NULL); + } + if (!localModule) + return NULL; + + localProc = GetProcAddress(localModule, procName); + remoteModule = GetRemoteModuleBase(processId, moduleName); + + if (needFree) + FreeLibrary(localModule); + + if (!localProc || !remoteModule) + return NULL; + + return (LPVOID)((const Byte *)remoteModule + ((const Byte *)localProc - (const Byte *)localModule)); +} + +static BoolInt UnloadModuleInProcess(DWORD processId, const WCHAR *moduleName) +{ + HMODULE remoteModule = GetRemoteModuleBase(processId, moduleName); + LPTHREAD_START_ROUTINE remoteFreeLibrary; + HANDLE process; + HANDLE thread; + DWORD exitCode = 0; + + if (!remoteModule) + return True; + + remoteFreeLibrary = (LPTHREAD_START_ROUTINE)GetRemoteProcAddress(processId, L"kernel32.dll", "FreeLibrary"); + if (!remoteFreeLibrary) + return False; + + process = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION, FALSE, processId); + if (!process) + return False; + + thread = CreateRemoteThread(process, NULL, 0, remoteFreeLibrary, (LPVOID)remoteModule, 0, NULL); + if (!thread) + { + CloseHandle(process); + return False; + } + + if (WaitForSingleObject(thread, 5000) == WAIT_OBJECT_0) + GetExitCodeThread(thread, &exitCode); + + CloseHandle(thread); + CloseHandle(process); + return (exitCode != 0); +} + +static void RequestExplorerToUnloadModule(const WCHAR *moduleName) +{ + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot != INVALID_HANDLE_VALUE) + { + PROCESSENTRY32W pe; + pe.dwSize = sizeof(pe); + if (Process32FirstW(snapshot, &pe)) + do + { + if (AreStringsEqual_NoCase(pe.szExeFile, L"explorer.exe")) + { + unsigned i; + for (i = 0; i < 4; i++) + { + if (!GetRemoteModuleBase(pe.th32ProcessID, moduleName)) + break; + if (!UnloadModuleInProcess(pe.th32ProcessID, moduleName)) + break; + Sleep(100); + } + } + } + while (Process32NextW(snapshot, &pe)); + CloseHandle(snapshot); + } +} + +static void RequestExplorerToUnloadShellExtensions(void) +{ + RequestExplorerToUnloadModule(L"7-zip.dll"); + #ifdef USE_7ZIP_32_DLL + RequestExplorerToUnloadModule(L"7-zip32.dll"); + #endif +} +#endif + +static BoolInt IsTempCleanupTarget(const WCHAR *s) +{ + if (AreStringsEqual_NoCase(s, L"7-zip.dll")) + return True; + #ifdef USE_7ZIP_32_DLL + if (AreStringsEqual_NoCase(s, L"7-zip32.dll")) + return True; + #endif + return False; +} + +static BOOL DeleteTempVariantsForBasePath(const WCHAR *basePath, WRes *winRes, BoolInt *needReboot) +{ + #ifndef UNDER_CE + BOOL result = TRUE; + WCHAR dirPath[MAX_PATH * 2 + 80]; + WCHAR mask[MAX_PATH * 2 + 80]; + WCHAR *name; + WIN32_FIND_DATAW fd; + HANDLE h; + + wcscpy(mask, basePath); + CatAscii(mask, ".tmp*"); + + wcscpy(dirPath, basePath); + name = dirPath; + { + WCHAR *s = dirPath; + for (;;) + { + const WCHAR c = *s++; + if (c == 0) + break; + if (c == WCHAR_PATH_SEPARATOR) + name = s; + } + } + + if (!name) + return TRUE; + + *name = 0; + + h = FindFirstFileW(mask, &fd); + if (h == INVALID_HANDLE_VALUE) + return TRUE; + + do + { + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + wcscpy(name, fd.cFileName); + if (!DeleteFileOrScheduleForReboot(dirPath, winRes, needReboot)) + result = FALSE; + } + } + while (FindNextFileW(h, &fd)); + + FindClose(h); + return result; + #else + UNUSED_VAR(basePath) + UNUSED_VAR(winRes) + UNUSED_VAR(needReboot) + return TRUE; + #endif +} + // #define IS_LIMIT_CHAR(c) (c == 0 || c == ' ') static BoolInt IsThereSpace(const wchar_t *s) @@ -640,6 +859,55 @@ static void AddPathParam(wchar_t *dest, const wchar_t *src) CatAscii(dest, "\""); } +static BoolInt IsProcessElevated(void) +{ + #ifndef UNDER_CE + SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY; + PSID adminGroup = NULL; + BOOL isMember = FALSE; + + if (!AllocateAndInitializeSid(&ntAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, &adminGroup)) + return False; + + CheckTokenMembership(NULL, adminGroup, &isMember); + FreeSid(adminGroup); + return isMember ? True : False; + #else + return True; + #endif +} + +static BoolInt RelaunchAsAdmin(const WCHAR *params, DWORD *errorCode) +{ + #ifndef UNDER_CE + SHELLEXECUTEINFOW sei; + memset(&sei, 0, sizeof(sei)); + sei.cbSize = sizeof(sei); + sei.fMask = SEE_MASK_NOCLOSEPROCESS; + sei.lpVerb = L"runas"; + sei.lpFile = modulePath; + sei.lpParameters = (params[0] == 0 ? NULL : params); + sei.nShow = SW_SHOWNORMAL; + + if (ShellExecuteExW(&sei)) + { + if (sei.hProcess) + CloseHandle(sei.hProcess); + return True; + } + + if (errorCode) + *errorCode = GetLastError(); + return False; + #else + UNUSED_VAR(params) + UNUSED_VAR(errorCode) + return False; + #endif +} + static BoolInt GetErrorMessage(DWORD errorCode, WCHAR *message) @@ -713,7 +981,7 @@ static int Install(void) SRes res = SZ_OK; WRes winRes = 0; - // BoolInt needReboot = False; + BoolInt needReboot = False; const size_t pathLen = wcslen(path); if (!g_SilentMode) @@ -723,6 +991,14 @@ static int Install(void) SendMessage(g_Progress_HWND, PBM_SETRANGE32, 0, NUM_FILES); } + RemoveShellExtensionCLSID(); + #ifndef UNDER_CE + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); + Sleep(200); + RequestExplorerToUnloadShellExtensions(); + Sleep(100); + #endif + { unsigned i; const char *curName = k_Names; @@ -780,24 +1056,17 @@ static int Install(void) if (!g_SilentMode) SetWindowTextW(g_InfoLine_HWND, temp); + if (IsTempCleanupTarget(temp)) { - const DWORD attrib = GetFileAttributesW(path); - if (attrib == INVALID_FILE_ATTRIBUTES) - continue; - if (attrib & FILE_ATTRIBUTE_READONLY) - SetFileAttributesW(path, 0); - if (!DeleteFileW(path)) - { - if (!RemoveFileAfterReboot()) - { - winRes = GetLastError(); - } - /* - else - needReboot = True; - */ - } + DeleteTempVariantsForBasePath(path, &winRes, &needReboot); + #ifndef UNDER_CE + RequestExplorerToUnloadModule(temp); + Sleep(100); + #endif } + + if (!DeleteFileOrScheduleForReboot(path, &winRes, &needReboot)) + break; } CpyAscii(path + pathLen, k_Lang); @@ -824,7 +1093,11 @@ static int Install(void) if (res == SZ_OK) { - // if (!g_SilentMode && needReboot); + if (!g_SilentMode && needReboot) + MessageBoxW(g_HWND, + L"Some files were in use and are scheduled to be removed after restart.", + k_7zip_with_Ver_Uninstall, + MB_ICONINFORMATION | MB_OK); return 0; } @@ -1051,6 +1324,21 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, *name = 0; // keep only prefix for modulePrefix } + if (!IsProcessElevated()) + { + DWORD errorCode = ERROR_SUCCESS; + if (RelaunchAsAdmin(cmdParams, &errorCode)) + return 0; + if (!g_SilentMode && errorCode != ERROR_CANCELLED) + { + WCHAR m[MAX_PATH + 100]; + if (!GetErrorMessage(errorCode, m)) + CpyAscii(m, "Can't relaunch with administrator rights"); + PrintErrorMessage("Administrator rights are required for uninstall.", m); + } + return 1; + } + if (useTemp) { diff --git a/CPP/7zip/Bundles/Fm/makefile b/CPP/7zip/Bundles/Fm/makefile index de74f76..d7d8822 100644 --- a/CPP/7zip/Bundles/Fm/makefile +++ b/CPP/7zip/Bundles/Fm/makefile @@ -1,4 +1,8 @@ PROG = 7zFM.exe +UI_FILEMANAGER_PROGPATH = ..\..\UI\FileManager\$(O)\$(PROG) +REPO_LANG_DIR = ..\..\..\..\Lang +FM_LANG_DIR = $(O)\Lang +UI_FILEMANAGER_LANG_DIR = ..\..\UI\FileManager\$(O)\Lang # CFLAGS = $(CFLAGS) -DZ7_LARGE_PAGES @@ -82,3 +86,22 @@ GUI_OBJS = \ !include "../../7zip.mak" + +all: copy_lang $(UI_FILEMANAGER_PROGPATH) + +copy_lang: + if not exist "$(FM_LANG_DIR)" mkdir "$(FM_LANG_DIR)" + copy /Y "$(REPO_LANG_DIR)\*" "$(FM_LANG_DIR)\" >nul + if not exist "$(UI_FILEMANAGER_LANG_DIR)" mkdir "$(UI_FILEMANAGER_LANG_DIR)" + copy /Y "$(REPO_LANG_DIR)\*" "$(UI_FILEMANAGER_LANG_DIR)\" >nul + +$(UI_FILEMANAGER_PROGPATH): $(PROGPATH) + if not exist "..\..\UI\FileManager\$(O)" mkdir "..\..\UI\FileManager\$(O)" + copy /Y "$(PROGPATH)" "$(UI_FILEMANAGER_PROGPATH)" >nul + +$O\CompressDialog.obj: ../../UI/GUI/CompressDialog.cpp ../../UI/GUI/CompressDialog.h ../../UI/GUI/CompressDialogRes.h +$O\CompressCall2.obj: ../../UI/Common/CompressCall2.cpp ../../UI/Common/Update.h ../../UI/GUI/UpdateGUI.h +$O\Update.obj: ../../UI/Common/Update.cpp ../../UI/Common/Update.h +$O\UpdateCallbackGUI.obj: ../../UI/GUI/UpdateCallbackGUI.cpp ../../UI/Common/Update.h ../../UI/GUI/UpdateCallbackGUI.h +$O\UpdateGUI.obj: ../../UI/GUI/UpdateGUI.cpp ../../UI/Common/Update.h ../../UI/GUI/UpdateGUI.h ../../UI/GUI/CompressDialog.h ../../UI/GUI/CompressDialogRes.h +$O\resource.res: resource.rc ../../UI/GUI/resource2.rc ../../UI/GUI/CompressDialog.rc ../../UI/GUI/CompressDialogRes.h ../../UI/FileManager/AboutDialog.rc ../../UI/FileManager/AboutDialogRes.h diff --git a/CPP/7zip/UI/Common/Update.h b/CPP/7zip/UI/Common/Update.h index ae141e5..0a237f7 100644 --- a/CPP/7zip/UI/Common/Update.h +++ b/CPP/7zip/UI/Common/Update.h @@ -111,6 +111,9 @@ struct CUpdateOptions CObjectVector Commands; CArchivePath ArchivePath; + bool SeparateItemMode; + UStringVector SeparateItemPaths; + UStringVector SeparateItemArchivePaths; FString SfxModule; UString StdInFileName; @@ -143,7 +146,8 @@ struct CUpdateOptions RenameMode(false), ArcNameMode(k_ArcNameMode_Smart), - PathMode(NWildcard::k_RelatPath) + PathMode(NWildcard::k_RelatPath), + SeparateItemMode(false) {} diff --git a/CPP/7zip/UI/FileManager/LangUtils.cpp b/CPP/7zip/UI/FileManager/LangUtils.cpp index 4712192..12a81c3 100644 --- a/CPP/7zip/UI/FileManager/LangUtils.cpp +++ b/CPP/7zip/UI/FileManager/LangUtils.cpp @@ -5,6 +5,8 @@ #include "../../../Common/Lang.h" #include "../../../Windows/DLL.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileName.h" #include "../../../Windows/Synchronization.h" #include "../../../Windows/Window.h" @@ -30,9 +32,45 @@ bool LangOpen(CLang &lang, CFSTR fileName) return lang.Open(fileName, "7-Zip"); } +static bool GetParentDirPrefix(FString &dirPrefix) +{ + if (dirPrefix.IsEmpty()) + return false; + + NFile::NName::NormalizeDirPathPrefix(dirPrefix); + + FString parent = dirPrefix; + if (!NFile::NName::IsDriveRootPath_SuperAllowed(parent)) + parent.DeleteBack(); + + const int pos = parent.ReverseFind_PathSepar(); + if (pos < 0) + return false; + + parent.DeleteFrom((unsigned)(pos + 1)); + if (parent.IsEmpty() || parent == dirPrefix) + return false; + + dirPrefix = parent; + return true; +} + FString GetLangDirPrefix() { - return NDLL::GetModuleDirPrefix() + FTEXT("Lang") FSTRING_PATH_SEPARATOR; + const FString moduleDir = NDLL::GetModuleDirPrefix(); + FString dirPrefix = moduleDir; + NFile::NName::NormalizeDirPathPrefix(dirPrefix); + + for (;;) + { + const FString langDir = dirPrefix + FTEXT("Lang") FSTRING_PATH_SEPARATOR; + if (NFile::NFind::DoesDirExist(langDir)) + return langDir; + if (!GetParentDirPrefix(dirPrefix)) + break; + } + + return moduleDir + FTEXT("Lang") FSTRING_PATH_SEPARATOR; } #ifdef Z7_LANG diff --git a/CPP/7zip/UI/GUI/CompressDialog.cpp b/CPP/7zip/UI/GUI/CompressDialog.cpp index 53e56fe..514cfbc 100644 --- a/CPP/7zip/UI/GUI/CompressDialog.cpp +++ b/CPP/7zip/UI/GUI/CompressDialog.cpp @@ -45,6 +45,7 @@ extern bool g_IsNT; static const UInt32 kLangIDs[] = { IDT_COMPRESS_ARCHIVE, + IDT_COMPRESS_OUTPUT_PATHS_NUM, IDT_COMPRESS_UPDATE_MODE, IDT_COMPRESS_FORMAT, IDT_COMPRESS_LEVEL, @@ -78,6 +79,8 @@ static const UInt32 kLangIDs[] = }; #endif +extern HINSTANCE g_hInstance; + using namespace NWindows; using namespace NFile; using namespace NName; @@ -92,6 +95,99 @@ static const UInt32 kLzmaMaxDictSize = (UInt32)15 << 28; static const UINT k_Message_ArcChanged = WM_APP + 1; +extern void AddUniqueString(UStringVector &strings, const UString &srcString); + +static const unsigned kArchivePathComboIds[] = +{ + IDC_COMPRESS_ARCHIVE, + IDC_COMPRESS_ARCHIVE2, + IDC_COMPRESS_ARCHIVE3, + IDC_COMPRESS_ARCHIVE4, + IDC_COMPRESS_ARCHIVE5 +}; + +static const unsigned kArchivePathButtonIds[] = +{ + IDB_COMPRESS_SET_ARCHIVE, + IDB_COMPRESS_SET_ARCHIVE2, + IDB_COMPRESS_SET_ARCHIVE3, + IDB_COMPRESS_SET_ARCHIVE4, + IDB_COMPRESS_SET_ARCHIVE5 +}; + +static const unsigned kOutputPathLayoutMoveIds[] = +{ + IDT_COMPRESS_FORMAT, + IDC_COMPRESS_FORMAT, + IDT_COMPRESS_LEVEL, + IDC_COMPRESS_LEVEL, + IDT_COMPRESS_METHOD, + IDC_COMPRESS_METHOD, + IDT_COMPRESS_DICTIONARY, + IDC_COMPRESS_DICTIONARY, + IDT_COMPRESS_ORDER, + IDC_COMPRESS_ORDER, + IDT_COMPRESS_SOLID, + IDC_COMPRESS_SOLID, + IDT_COMPRESS_THREADS, + IDC_COMPRESS_THREADS, + IDT_COMPRESS_HARDWARE_THREADS, + IDT_COMPRESS_MEMORY, + IDC_COMPRESS_MEM_USE, + IDT_COMPRESS_MEMORY_VALUE, + IDT_COMPRESS_MEMORY_DE, + IDT_COMPRESS_MEMORY_DE_VALUE, + IDT_SPLIT_TO_VOLUMES, + IDC_COMPRESS_VOLUME, + IDT_COMPRESS_PARAMETERS, + IDE_COMPRESS_PARAMETERS, + IDB_COMPRESS_OPTIONS, + IDT_COMPRESS_OPTIONS, + IDT_COMPRESS_UPDATE_MODE, + IDC_COMPRESS_UPDATE_MODE, + IDT_COMPRESS_PATH_MODE, + IDC_COMPRESS_PATH_MODE, + IDG_COMPRESS_OPTIONS, + IDX_COMPRESS_SFX, + IDX_COMPRESS_SHARED, + IDX_COMPRESS_DEL, + IDG_COMPRESS_ENCRYPTION, + IDT_PASSWORD_ENTER, + IDE_COMPRESS_PASSWORD1, + IDT_PASSWORD_REENTER, + IDE_COMPRESS_PASSWORD2, + IDX_PASSWORD_SHOW, + IDT_COMPRESS_ENCRYPTION_METHOD, + IDC_COMPRESS_ENCRYPTION_METHOD, + IDX_COMPRESS_ENCRYPT_FILE_NAMES, + IDOK, + IDCANCEL, + IDHELP +}; + +static const unsigned kDynamicOutputGroupIdBase = 50000; +static const unsigned kDynamicOutputGroupIdStep = 100; + +static unsigned GetDynamicGroupBase(unsigned groupIndex) +{ + return kDynamicOutputGroupIdBase + groupIndex * kDynamicOutputGroupIdStep; +} + +static unsigned GetDynamicGroupItemLabelId(unsigned groupIndex) { return GetDynamicGroupBase(groupIndex) + 1; } +static unsigned GetDynamicGroupCountLabelId(unsigned groupIndex) { return GetDynamicGroupBase(groupIndex) + 2; } +static unsigned GetDynamicGroupCountComboId(unsigned groupIndex) { return GetDynamicGroupBase(groupIndex) + 3; } +static unsigned GetDynamicGroupArchiveLabelId(unsigned groupIndex) { return GetDynamicGroupBase(groupIndex) + 4; } +static unsigned GetDynamicGroupArchiveComboId(unsigned groupIndex, unsigned pathIndex) { return GetDynamicGroupBase(groupIndex) + 10 + pathIndex; } +static unsigned GetDynamicGroupArchiveButtonId(unsigned groupIndex, unsigned pathIndex) { return GetDynamicGroupBase(groupIndex) + 20 + pathIndex; } + +static int FindArchivePathButtonIndex(unsigned id) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kArchivePathButtonIds); i++) + if (kArchivePathButtonIds[i] == id) + return (int)i; + return -1; +} + /* static const UInt32 kZstd_MAX_DictSize = (UInt32)1 << MY_ZSTD_WINDOWLOG_MAX; */ @@ -466,6 +562,9 @@ bool CCompressDialog::OnInit() _default_encryptionMethod_Index = -1; m_ArchivePath.Attach(GetItem(IDC_COMPRESS_ARCHIVE)); + m_OutputPathCount.Attach(GetItem(IDC_COMPRESS_OUTPUT_PATHS_NUM)); + for (unsigned i = 1; i < Z7_ARRAY_SIZE(kArchivePathComboIds); i++) + m_ExtraArchivePaths[i - 1].Attach(GetItem(kArchivePathComboIds[i])); m_Format.Attach(GetItem(IDC_COMPRESS_FORMAT)); // that combo has CBS_SORT style in resources m_Level.Attach(GetItem(IDC_COMPRESS_LEVEL)); m_Method.Attach(GetItem(IDC_COMPRESS_METHOD)); @@ -523,15 +622,68 @@ bool CCompressDialog::OnInit() CheckButton(IDX_COMPRESS_SFX, Info.SFXMode); + for (unsigned i = 0; i < m_RegistryInfo.ArcPaths.Size() && i < kHistorySize; i++) { + m_ArchivePath.AddString(m_RegistryInfo.ArcPaths[i]); + for (unsigned k = 0; k < Z7_ARRAY_SIZE(m_ExtraArchivePaths); k++) + m_ExtraArchivePaths[k].AddString(m_RegistryInfo.ArcPaths[i]); + } + + for (unsigned i = 1; i <= Z7_ARRAY_SIZE(kArchivePathComboIds); i++) + { + wchar_t s[16]; + ConvertUInt32ToString(i, s); + m_OutputPathCount.AddString_SetItemData(s, (LPARAM)i); + } + + ShowItem_Bool(IDB_COMPRESS_OUTPUT_PATHS, false); + ShowItem_Bool(IDX_COMPRESS_SEPARATE_ITEMS, false); + + if (IsMultiItemMode()) + { + DirPrefix.Empty(); + StartDirPrefix.Empty(); + InitItemOutputGroups(); + if (!_itemOutputGroups.IsEmpty()) + _outputArcPaths = _itemOutputGroups[0].ArcPaths; + if (_outputArcPaths.IsEmpty() && !Info.ItemPaths.IsEmpty()) + { + UString path; + BuildItemArcPath(Info.ItemPaths.Front(), path); + _outputArcPaths.Add(path); + } + if (_outputArcPaths.Size() > Z7_ARRAY_SIZE(kArchivePathComboIds)) + _outputArcPaths.DeleteFrom(Z7_ARRAY_SIZE(kArchivePathComboIds)); + if (!_outputArcPaths.IsEmpty()) + m_ArchivePath.SetText(_outputArcPaths.Front()); + } + else + { + if (Info.ArcPaths.IsEmpty() && !Info.ArcPath.IsEmpty()) + Info.ArcPaths.Add(Info.ArcPath); + _outputArcPaths = Info.ArcPaths; + if (_outputArcPaths.Size() > Z7_ARRAY_SIZE(kArchivePathComboIds)) + _outputArcPaths.DeleteFrom(Z7_ARRAY_SIZE(kArchivePathComboIds)); + + UString initPath = Info.ArcPath; + if (!_outputArcPaths.IsEmpty()) + initPath = _outputArcPaths.Front(); + UString fileName; - SetArcPathFields(Info.ArcPath, fileName, true); + SetArcPathFields(initPath, fileName, true); StartDirPrefix = DirPrefix; SetArchiveName(fileName); + SyncPrimaryArcPathFromControl(); } - - for (unsigned i = 0; i < m_RegistryInfo.ArcPaths.Size() && i < kHistorySize; i++) - m_ArchivePath.AddString(m_RegistryInfo.ArcPaths[i]); + + SetOutputPathCount(_outputArcPaths.IsEmpty() ? 1 : (unsigned)_outputArcPaths.Size(), false); + if (IsMultiItemMode()) + { + CreateDynamicItemOutputControls(); + UpdateOutputPathControls(); + UpdateOutputPathLayout(); + } + UpdateSeparateItemModeControls(); AddComboItems(m_UpdateMode, k_UpdateMode_IDs, Z7_ARRAY_SIZE(k_UpdateMode_IDs), k_UpdateMode_Vals, Info.UpdateMode); @@ -585,13 +737,26 @@ void CCompressDialog::UpdatePasswordControl() bool CCompressDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) { + const int archivePathButtonIndex = FindArchivePathButtonIndex(buttonID); + if (archivePathButtonIndex >= 0) + { + OnButtonSetArchivePath((unsigned)archivePathButtonIndex); + return true; + } + + if (IsMultiItemMode()) + { + for (unsigned groupIndex = 1; groupIndex < _itemOutputGroups.Size(); groupIndex++) + for (unsigned pathIndex = 0; pathIndex < kNumOutputPathRows; pathIndex++) + if (buttonID == GetDynamicGroupArchiveButtonId(groupIndex, pathIndex)) + { + BrowseItemOutputPath(groupIndex, pathIndex); + return true; + } + } + switch (buttonID) { - case IDB_COMPRESS_SET_ARCHIVE: - { - OnButtonSetArchive(); - return true; - } case IDX_COMPRESS_SFX: { SetMethod(GetMethodID()); @@ -778,8 +943,34 @@ static int GetExtDotPos(const UString &s) return -1; } +static void RemoveTailExtensionIfMatches(UString &fileName, const UString &fullExtension) +{ + const unsigned extLen = fullExtension.Len(); + if (fileName.Len() >= extLen) + if (StringsAreEqualNoCase(fileName.RightPtr(extLen), fullExtension)) + fileName.DeleteFrom(fileName.Len() - extLen); +} + +static void AppendExtensionWithoutDup(UString &fileName, const UString &fullExtension) +{ + if (fullExtension.IsEmpty()) + return; + RemoveTailExtensionIfMatches(fileName, fullExtension); + fileName += fullExtension; +} + void CCompressDialog::OnButtonSFX() { + const bool prevWasSFX = !IsSFX(); + const int prevFormat = m_PrevFormat; + + if (IsMultiItemMode()) + { + SyncAllItemOutputGroupsFromControls(); + UpdateExtraArcPathsForFormatChange(prevFormat, prevWasSFX); + return; + } + UString fileName; m_ArchivePath.GetText(fileName); const int dotPos = GetExtDotPos(fileName); @@ -805,6 +996,9 @@ void CCompressDialog::OnButtonSFX() } // CheckVolumeEnable(); + SyncPrimaryArcPathFromControl(); + SyncExtraArcPathsFromControls(); + UpdateExtraArcPathsForFormatChange(prevFormat, prevWasSFX); } @@ -827,6 +1021,966 @@ bool CCompressDialog::GetFinalPath_Smart(UString &resPath) const } +static void SetWindowFont_Simple(HWND hwnd, HFONT font) +{ + if (hwnd != NULL && font != NULL) + ::SendMessage(hwnd, WM_SETFONT, (WPARAM)font, TRUE); +} + + +static bool CreateDialogChildWindow_Simple( + NWindows::CWindow &window, + LPCTSTR className, + LPCTSTR text, + DWORD style, + const RECT &rect, + HWND parent, + unsigned id, + HFONT font) +{ + if (!window.Create(className, text, style, + rect.left, rect.top, RECT_SIZE_X(rect), RECT_SIZE_Y(rect), + parent, (HMENU)(INT_PTR)id, g_hInstance, NULL)) + return false; + SetWindowFont_Simple(window, font); + return true; +} + + +void CCompressDialog::InitItemOutputGroups() +{ + _itemOutputGroups.Clear(); + + FOR_VECTOR (i, Info.ItemPaths) + { + CItemOutputGroup group; + group.ItemPath = Info.ItemPaths[i]; + _itemOutputGroups.Add(group); + } + + if (Info.ItemOutputItemPaths.Size() == Info.ItemArcPaths.Size()) + { + FOR_VECTOR (i, Info.ItemOutputItemPaths) + { + FOR_VECTOR (k, _itemOutputGroups) + if (_itemOutputGroups[k].ItemPath == Info.ItemOutputItemPaths[i]) + { + _itemOutputGroups[k].ArcPaths.Add(Info.ItemArcPaths[i]); + break; + } + } + } + + FOR_VECTOR (i, _itemOutputGroups) + { + CItemOutputGroup &group = _itemOutputGroups[i]; + if (group.ArcPaths.IsEmpty()) + { + UString path; + BuildItemArcPath(group.ItemPath, path); + group.ArcPaths.Add(path); + } + if (group.ArcPaths.Size() > kNumOutputPathRows) + group.ArcPaths.DeleteFrom(kNumOutputPathRows); + } +} + + +void CCompressDialog::InitOutputPathLayout() +{ + if (_outputLayout_Inited) + return; + + RECT windowRect; + if (GetWindowRect(&windowRect)) + { + _outputLayout_BaseWindowX = RECT_SIZE_X(windowRect); + _outputLayout_BaseWindowY = RECT_SIZE_Y(windowRect); + } + + _outputPathRowStep = 0; + if (GetItem(IDC_COMPRESS_ARCHIVE) != NULL && GetItem(IDC_COMPRESS_ARCHIVE2) != NULL) + { + RECT r1; + RECT r2; + GetClientRectOfItem(IDC_COMPRESS_ARCHIVE, r1); + GetClientRectOfItem(IDC_COMPRESS_ARCHIVE2, r2); + _outputPathRowStep = r2.top - r1.top; + } + + _outputLayout_Items.Clear(); + _outputLayout_Items.Reserve(Z7_ARRAY_SIZE(kOutputPathLayoutMoveIds)); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kOutputPathLayoutMoveIds); i++) + { + const unsigned id = kOutputPathLayoutMoveIds[i]; + if (GetItem(id) == NULL) + continue; + CLayoutItem item; + item.Id = id; + GetClientRectOfItem(id, item.Rect); + _outputLayout_Items.Add(item); + } + + _outputLayout_BaseControlsTop = 0; + if (!_outputLayout_Items.IsEmpty()) + _outputLayout_BaseControlsTop = _outputLayout_Items[0].Rect.top; + + GetClientRectOfItem(IDT_COMPRESS_ARCHIVE_FOLDER, _outputTemplate_ItemLabelRect); + GetClientRectOfItem(IDT_COMPRESS_OUTPUT_PATHS_NUM, _outputTemplate_CountLabelRect); + GetClientRectOfItem(IDC_COMPRESS_OUTPUT_PATHS_NUM, _outputTemplate_CountComboRect); + GetClientRectOfItem(IDT_COMPRESS_ARCHIVE, _outputTemplate_ArchiveLabelRect); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kArchivePathComboIds); i++) + { + GetClientRectOfItem(kArchivePathComboIds[i], _outputTemplate_ArchiveComboRects[i]); + GetClientRectOfItem(kArchivePathButtonIds[i], _outputTemplate_ArchiveButtonRects[i]); + } + + _outputGroupGapY = _outputPathRowStep / 2; + if (_outputGroupGapY < 8) + _outputGroupGapY = 8; + + _outputLayout_Inited = true; +} + + +void CCompressDialog::CreateDynamicItemOutputControls() +{ + if (!IsMultiItemMode() || _itemOutputGroups.Size() <= 1) + return; + + InitOutputPathLayout(); + + HFONT labelFont = (HFONT)::SendMessage(GetItem(IDT_COMPRESS_ARCHIVE_FOLDER), WM_GETFONT, 0, 0); + HFONT comboFont = (HFONT)::SendMessage(GetItem(IDC_COMPRESS_ARCHIVE), WM_GETFONT, 0, 0); + + UString countLabel; + UString archiveLabel; + GetItemText(IDT_COMPRESS_OUTPUT_PATHS_NUM, countLabel); + GetItemText(IDT_COMPRESS_ARCHIVE, archiveLabel); + const CSysString countLabelSys = GetSystemString(countLabel); + const CSysString archiveLabelSys = GetSystemString(archiveLabel); + + for (unsigned groupIndex = 1; groupIndex < _itemOutputGroups.Size(); groupIndex++) + { + const unsigned itemLabelId = GetDynamicGroupItemLabelId(groupIndex); + if (GetItem(itemLabelId) != NULL) + continue; + + NWindows::CWindow itemLabel; + CreateDialogChildWindow_Simple(itemLabel, TEXT("STATIC"), TEXT(""), + WS_CHILD | WS_VISIBLE | SS_NOPREFIX, + _outputTemplate_ItemLabelRect, *this, itemLabelId, labelFont); + + NWindows::CWindow countText; + CreateDialogChildWindow_Simple(countText, TEXT("STATIC"), countLabelSys, + WS_CHILD | WS_VISIBLE, + _outputTemplate_CountLabelRect, *this, GetDynamicGroupCountLabelId(groupIndex), labelFont); + + NWindows::NControl::CComboBox countCombo; + CreateDialogChildWindow_Simple(countCombo, TEXT("COMBOBOX"), TEXT(""), + WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | CBS_DROPDOWNLIST, + _outputTemplate_CountComboRect, *this, GetDynamicGroupCountComboId(groupIndex), comboFont); + for (unsigned i = 1; i <= kNumOutputPathRows; i++) + { + wchar_t s[16]; + ConvertUInt32ToString(i, s); + countCombo.AddString_SetItemData(s, (LPARAM)i); + } + + NWindows::CWindow archiveText; + CreateDialogChildWindow_Simple(archiveText, TEXT("STATIC"), archiveLabelSys, + WS_CHILD | WS_VISIBLE, + _outputTemplate_ArchiveLabelRect, *this, GetDynamicGroupArchiveLabelId(groupIndex), labelFont); + + for (unsigned pathIndex = 0; pathIndex < kNumOutputPathRows; pathIndex++) + { + NWindows::NControl::CComboBox combo; + CreateDialogChildWindow_Simple(combo, TEXT("COMBOBOX"), TEXT(""), + WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | CBS_DROPDOWN | CBS_AUTOHSCROLL, + _outputTemplate_ArchiveComboRects[pathIndex], *this, + GetDynamicGroupArchiveComboId(groupIndex, pathIndex), comboFont); + for (unsigned i = 0; i < m_RegistryInfo.ArcPaths.Size() && i < kHistorySize; i++) + combo.AddString(m_RegistryInfo.ArcPaths[i]); + + NWindows::CWindow button; + CreateDialogChildWindow_Simple(button, TEXT("BUTTON"), TEXT("..."), + WS_CHILD | WS_VISIBLE | WS_TABSTOP, + _outputTemplate_ArchiveButtonRects[pathIndex], *this, + GetDynamicGroupArchiveButtonId(groupIndex, pathIndex), labelFont); + } + } +} + + +void CCompressDialog::LayoutItemOutputGroups() +{ + InitOutputPathLayout(); + if (!_outputLayout_Inited || !IsMultiItemMode()) + return; + + const int templateTop = _outputTemplate_ItemLabelRect.top; + const int templateBottom = + _outputTemplate_ArchiveComboRects[kNumOutputPathRows - 1].bottom; + const int baseGapToControls = _outputLayout_BaseControlsTop - templateBottom; + + SetRedraw(false); + + int currentTop = templateTop; + for (unsigned groupIndex = 0; groupIndex < _itemOutputGroups.Size(); groupIndex++) + { + const unsigned count = _itemOutputGroups[groupIndex].ArcPaths.IsEmpty() ? 1 : + (unsigned)_itemOutputGroups[groupIndex].ArcPaths.Size(); + const int deltaY = currentTop - templateTop; + + if (groupIndex > 0 && GetItem(GetDynamicGroupItemLabelId(groupIndex)) != NULL) + { + RECT r = _outputTemplate_ItemLabelRect; + r.top += deltaY; + r.bottom += deltaY; + MoveItem_RECT(GetDynamicGroupItemLabelId(groupIndex), r, false); + + r = _outputTemplate_CountLabelRect; + r.top += deltaY; + r.bottom += deltaY; + MoveItem_RECT(GetDynamicGroupCountLabelId(groupIndex), r, false); + + r = _outputTemplate_CountComboRect; + r.top += deltaY; + r.bottom += deltaY; + MoveItem_RECT(GetDynamicGroupCountComboId(groupIndex), r, false); + + r = _outputTemplate_ArchiveLabelRect; + r.top += deltaY; + r.bottom += deltaY; + MoveItem_RECT(GetDynamicGroupArchiveLabelId(groupIndex), r, false); + + for (unsigned pathIndex = 0; pathIndex < kNumOutputPathRows; pathIndex++) + { + r = _outputTemplate_ArchiveComboRects[pathIndex]; + r.top += deltaY; + r.bottom += deltaY; + MoveItem_RECT(GetDynamicGroupArchiveComboId(groupIndex, pathIndex), r, false); + + r = _outputTemplate_ArchiveButtonRects[pathIndex]; + r.top += deltaY; + r.bottom += deltaY; + MoveItem_RECT(GetDynamicGroupArchiveButtonId(groupIndex, pathIndex), r, false); + } + } + + const int groupBottom = currentTop + + (_outputTemplate_ArchiveComboRects[count - 1].bottom - templateTop); + currentTop = groupBottom + _outputGroupGapY; + } + + const int desiredControlsTop = currentTop + baseGapToControls; + const int deltaY = desiredControlsTop - _outputLayout_BaseControlsTop; + + FOR_VECTOR (i, _outputLayout_Items) + { + const CLayoutItem &item = _outputLayout_Items[i]; + RECT r = item.Rect; + r.top += deltaY; + r.bottom += deltaY; + MoveItem_RECT(item.Id, r, false); + } + + RECT windowRect; + if (GetWindowRect(&windowRect)) + Move(windowRect.left, windowRect.top, + _outputLayout_BaseWindowX, _outputLayout_BaseWindowY + deltaY, false); + + SetRedraw(true); + InvalidateRect(NULL, true); + Update(); +} + + +void CCompressDialog::UpdateOutputPathLayout() +{ + InitOutputPathLayout(); + if (!_outputLayout_Inited || _outputPathRowStep == 0) + return; + + if (IsMultiItemMode()) + { + LayoutItemOutputGroups(); + return; + } + + const int deltaY = ((int)_outputPathCount - (int)kNumOutputPathRows) * _outputPathRowStep; + + SetRedraw(false); + + FOR_VECTOR (i, _outputLayout_Items) + { + const CLayoutItem &item = _outputLayout_Items[i]; + RECT r = item.Rect; + r.top += deltaY; + r.bottom += deltaY; + MoveItem_RECT(item.Id, r, false); + } + + RECT windowRect; + if (GetWindowRect(&windowRect)) + Move(windowRect.left, windowRect.top, _outputLayout_BaseWindowX, _outputLayout_BaseWindowY + deltaY, false); + + SetRedraw(true); + InvalidateRect(NULL, true); + Update(); +} + + +void CCompressDialog::UpdateOutputPathControls() +{ + if (IsMultiItemMode()) + { + UpdateItemOutputGroupControls(0); + for (unsigned i = 1; i < _itemOutputGroups.Size(); i++) + UpdateItemOutputGroupControls(i); + return; + } + + m_OutputPathCount.SetCurSel((int)_outputPathCount - 1); + + for (unsigned i = 1; i < Z7_ARRAY_SIZE(kArchivePathComboIds); i++) + { + const bool show = (i < _outputPathCount); + ShowItem_Bool(kArchivePathComboIds[i], show); + ShowItem_Bool(kArchivePathButtonIds[i], show); + + UString path; + if (i < _outputArcPaths.Size()) + path = _outputArcPaths[i]; + m_ExtraArchivePaths[i - 1].SetText(path); + } +} + + +void CCompressDialog::UpdateItemOutputGroupControls(unsigned groupIndex) +{ + if (groupIndex >= _itemOutputGroups.Size()) + return; + + const CItemOutputGroup &group = _itemOutputGroups[groupIndex]; + const unsigned count = group.ArcPaths.IsEmpty() ? 1 : (unsigned)group.ArcPaths.Size(); + + if (groupIndex == 0) + { + m_OutputPathCount.SetCurSel((int)count - 1); + if (!group.ArcPaths.IsEmpty()) + m_ArchivePath.SetText(group.ArcPaths.Front()); + for (unsigned i = 1; i < Z7_ARRAY_SIZE(kArchivePathComboIds); i++) + { + const bool show = (i < count); + ShowItem_Bool(kArchivePathComboIds[i], show); + ShowItem_Bool(kArchivePathButtonIds[i], show); + + UString path; + if (i < group.ArcPaths.Size()) + path = group.ArcPaths[i]; + m_ExtraArchivePaths[i - 1].SetText(path); + } + return; + } + + if (GetItem(GetDynamicGroupItemLabelId(groupIndex)) == NULL) + return; + + SetItemText(GetDynamicGroupItemLabelId(groupIndex), group.ItemPath); + + NWindows::NControl::CComboBox countCombo; + countCombo.Attach(GetItem(GetDynamicGroupCountComboId(groupIndex))); + countCombo.SetCurSel((int)count - 1); + + for (unsigned pathIndex = 0; pathIndex < kNumOutputPathRows; pathIndex++) + { + const bool show = (pathIndex < count); + ShowItem_Bool(GetDynamicGroupArchiveComboId(groupIndex, pathIndex), show); + ShowItem_Bool(GetDynamicGroupArchiveButtonId(groupIndex, pathIndex), show); + + NWindows::NControl::CComboBox combo; + combo.Attach(GetItem(GetDynamicGroupArchiveComboId(groupIndex, pathIndex))); + + UString path; + if (pathIndex < group.ArcPaths.Size()) + path = group.ArcPaths[pathIndex]; + combo.SetText(path); + } +} + + +void CCompressDialog::UpdateSeparateItemModeControls() +{ + ShowItem_Bool(IDX_COMPRESS_SEPARATE_ITEMS, false); + ShowItem_Bool(IDB_COMPRESS_OUTPUT_PATHS, false); + RefreshArchivePathInfo(); +} + + +void CCompressDialog::BuildItemArcPath(const UString &inputPath, UString &path) +{ + UString dirPrefix; + UString fileName; + SplitPathToParts_Smart(inputPath, dirPrefix, fileName); + if (fileName.IsEmpty()) + fileName = ExtractFileNameFromPath(inputPath); + + const CArcInfoEx &ai = Get_ArcInfoEx(); + if (!ai.Flags_KeepName()) + { + const int dotPos = GetExtDotPos(fileName); + if (dotPos >= 0) + fileName.DeleteFrom(dotPos); + } + + if (IsSFX()) + AppendExtensionWithoutDup(fileName, UString(kExeExt)); + else + { + UString ext = ai.GetMainExt(); + if (ai.Flags_HashHandler()) + { + UString estimatedName; + GetMethodSpec(estimatedName); + if (!estimatedName.IsEmpty()) + { + ext = estimatedName; + ext.MakeLower_Ascii(); + } + } + UString fullExt; + fullExt.Add_Dot(); + fullExt += ext; + AppendExtensionWithoutDup(fileName, fullExt); + } + + path = dirPrefix + fileName; +} + + +void CCompressDialog::SyncItemOutputGroupFromControls(unsigned groupIndex) +{ + if (!IsMultiItemMode() || groupIndex >= _itemOutputGroups.Size()) + return; + + CItemOutputGroup &group = _itemOutputGroups[groupIndex]; + + unsigned count = (group.ArcPaths.IsEmpty() ? 1 : (unsigned)group.ArcPaths.Size()); + if (groupIndex == 0) + count = _outputPathCount; + else + { + NWindows::NControl::CComboBox countCombo; + countCombo.Attach(GetItem(GetDynamicGroupCountComboId(groupIndex))); + const int sel = countCombo.GetCurSel(); + if (sel >= 0) + count = (unsigned)sel + 1; + } + + if (count < 1) + count = 1; + if (count > kNumOutputPathRows) + count = kNumOutputPathRows; + + if (group.ArcPaths.Size() > count) + group.ArcPaths.DeleteFrom(count); + + UString defaultPath; + if (!group.ArcPaths.IsEmpty()) + defaultPath = group.ArcPaths.Front(); + if (defaultPath.IsEmpty()) + BuildItemArcPath(group.ItemPath, defaultPath); + + if (group.ArcPaths.IsEmpty()) + group.ArcPaths.Add(defaultPath); + while (group.ArcPaths.Size() < count) + group.ArcPaths.Add(UString()); + + for (unsigned pathIndex = 0; pathIndex < count; pathIndex++) + { + UString path; + if (groupIndex == 0) + { + if (pathIndex == 0) + { + if (!GetFinalPath_Smart(path)) + { + m_ArchivePath.GetText(path); + path.Trim(); + } + } + else + { + m_ExtraArchivePaths[pathIndex - 1].GetText(path); + path.Trim(); + } + } + else + { + NWindows::NControl::CComboBox combo; + combo.Attach(GetItem(GetDynamicGroupArchiveComboId(groupIndex, pathIndex))); + combo.GetText(path); + path.Trim(); + } + + group.ArcPaths[pathIndex] = path; + } + + if (groupIndex == 0) + _outputArcPaths = group.ArcPaths; +} + + +void CCompressDialog::SyncAllItemOutputGroupsFromControls() +{ + if (!IsMultiItemMode()) + return; + for (unsigned i = 0; i < _itemOutputGroups.Size(); i++) + SyncItemOutputGroupFromControls(i); +} + + +void CCompressDialog::SetItemOutputGroupCount(unsigned groupIndex, unsigned count, bool syncFromControls) +{ + if (!IsMultiItemMode() || groupIndex >= _itemOutputGroups.Size()) + return; + + if (count < 1) + count = 1; + if (count > kNumOutputPathRows) + count = kNumOutputPathRows; + + if (syncFromControls) + SyncItemOutputGroupFromControls(groupIndex); + + CItemOutputGroup &group = _itemOutputGroups[groupIndex]; + UString defaultPath; + if (!group.ArcPaths.IsEmpty()) + defaultPath = group.ArcPaths.Front(); + if (defaultPath.IsEmpty()) + BuildItemArcPath(group.ItemPath, defaultPath); + + if (group.ArcPaths.IsEmpty()) + group.ArcPaths.Add(defaultPath); + if (group.ArcPaths.Size() > count) + group.ArcPaths.DeleteFrom(count); + while (group.ArcPaths.Size() < count) + group.ArcPaths.Add(UString()); + + if (groupIndex == 0) + { + _outputPathCount = count; + _outputArcPaths = group.ArcPaths; + } + + UpdateItemOutputGroupControls(groupIndex); + RefreshArchivePathInfo(); + UpdateOutputPathLayout(); +} + + +bool CCompressDialog::BrowseItemOutputPath(unsigned groupIndex, unsigned pathIndex) +{ + if (!IsMultiItemMode() || groupIndex >= _itemOutputGroups.Size() || pathIndex >= kNumOutputPathRows) + return false; + + SyncAllItemOutputGroupsFromControls(); + + CItemOutputGroup &group = _itemOutputGroups[groupIndex]; + if (group.ArcPaths.IsEmpty()) + { + UString defaultPath; + BuildItemArcPath(group.ItemPath, defaultPath); + group.ArcPaths.Add(defaultPath); + } + while (group.ArcPaths.Size() <= pathIndex) + group.ArcPaths.Add(UString()); + + UString path = group.ArcPaths[pathIndex]; + if (path.IsEmpty()) + BuildItemArcPath(group.ItemPath, path); + + const int prevFormat = m_PrevFormat; + const bool prevWasSFX = IsSFX(); + bool formatWasChanged = false; + if (!BrowseArchivePath(path, true, formatWasChanged)) + return false; + + group.ArcPaths[pathIndex] = path; + if (groupIndex == 0) + _outputArcPaths = group.ArcPaths; + + if (formatWasChanged) + { + SaveOptionsInMem(); + FormatChanged(true); + UpdateExtraArcPathsForFormatChange(prevFormat, prevWasSFX); + } + + UpdateItemOutputGroupControls(groupIndex); + RefreshArchivePathInfo(); + return true; +} + + +bool CCompressDialog::GetItemOutputGroupPaths(UStringVector &itemPaths, UStringVector &paths) const +{ + itemPaths.Clear(); + paths.Clear(); + if (!IsMultiItemMode() || _itemOutputGroups.IsEmpty()) + return false; + + FOR_VECTOR (g, _itemOutputGroups) + { + const CItemOutputGroup &group = _itemOutputGroups[g]; + FOR_VECTOR (i, group.ArcPaths) + { + UString path = group.ArcPaths[i]; + path.Trim(); + if (path.IsEmpty()) + return false; + + FString fullPath; + if (!MyGetFullPathName(us2fs(path), fullPath)) + return false; + + const UString fullPathU = fs2us(fullPath); + const unsigned prevSize = paths.Size(); + AddUniqueString(paths, fullPathU); + if (paths.Size() == prevSize) + return false; + + itemPaths.Add(group.ItemPath); + } + } + + return (paths.Size() == itemPaths.Size() && !paths.IsEmpty()); +} + + +bool CCompressDialog::GetItemArcPaths(UStringVector &itemPaths, UStringVector &paths) const +{ + return GetItemOutputGroupPaths(itemPaths, paths); +} + + +void CCompressDialog::RefreshArchivePathInfo() +{ + if (IsMultiItemMode()) + { + if (!_itemOutputGroups.IsEmpty()) + SetItemText(IDT_COMPRESS_ARCHIVE_FOLDER, _itemOutputGroups[0].ItemPath); + return; + } + + UString s = DirPrefix; + + if (_outputPathCount > 1) + { + if (!s.IsEmpty()) + s.Add_Space(); + s += "(+"; + s.Add_UInt32((UInt32)_outputPathCount - 1); + s += " more)"; + } + SetItemText(IDT_COMPRESS_ARCHIVE_FOLDER, s); +} + + +void CCompressDialog::SyncPrimaryArcPathFromControl() +{ + UString path; + if (!GetFinalPath_Smart(path)) + { + m_ArchivePath.GetText(path); + path.Trim(); + } + + if (_outputArcPaths.IsEmpty()) + _outputArcPaths.Add(path); + else + _outputArcPaths[0] = path; + + if (IsMultiItemMode() && !_itemOutputGroups.IsEmpty()) + { + while (_itemOutputGroups[0].ArcPaths.IsEmpty()) + _itemOutputGroups[0].ArcPaths.Add(UString()); + _itemOutputGroups[0].ArcPaths[0] = path; + } + + RefreshArchivePathInfo(); +} + + +void CCompressDialog::SyncExtraArcPathsFromControls() +{ + if (_outputArcPaths.Size() > _outputPathCount) + _outputArcPaths.DeleteFrom(_outputPathCount); + while (_outputArcPaths.Size() < _outputPathCount) + _outputArcPaths.Add(UString()); + + for (unsigned i = 1; i < _outputPathCount; i++) + { + UString path; + m_ExtraArchivePaths[i - 1].GetText(path); + path.Trim(); + _outputArcPaths[i] = path; + } + + if (IsMultiItemMode() && !_itemOutputGroups.IsEmpty()) + _itemOutputGroups[0].ArcPaths = _outputArcPaths; +} + + +void CCompressDialog::SetOutputPathCount(unsigned count, bool syncFromControls) +{ + const unsigned maxCount = Z7_ARRAY_SIZE(kArchivePathComboIds); + if (count < 1) + count = 1; + if (count > maxCount) + count = maxCount; + + if (IsMultiItemMode()) + { + SetItemOutputGroupCount(0, count, syncFromControls); + return; + } + + if (syncFromControls) + { + SyncPrimaryArcPathFromControl(); + SyncExtraArcPathsFromControls(); + } + + _outputPathCount = count; + + if (_outputArcPaths.Size() > count) + _outputArcPaths.DeleteFrom(count); + while (_outputArcPaths.Size() < count) + _outputArcPaths.Add(UString()); + + UpdateOutputPathControls(); + RefreshArchivePathInfo(); + UpdateOutputPathLayout(); +} + + +void CCompressDialog::SetOutputArcPaths(const UStringVector &paths) +{ + _outputArcPaths.Clear(); + FOR_VECTOR (i, paths) + { + UString s = paths[i]; + s.Trim(); + if (!s.IsEmpty()) + AddUniqueString(_outputArcPaths, s); + } + if (_outputArcPaths.Size() > Z7_ARRAY_SIZE(kArchivePathComboIds)) + _outputArcPaths.DeleteFrom(Z7_ARRAY_SIZE(kArchivePathComboIds)); + + if (IsMultiItemMode() && !_itemOutputGroups.IsEmpty()) + _itemOutputGroups[0].ArcPaths = _outputArcPaths; + + SetOutputPathCount(_outputArcPaths.IsEmpty() ? 1 : (unsigned)_outputArcPaths.Size(), false); +} + + +bool CCompressDialog::GetOutputArcPaths(UStringVector &paths) const +{ + paths.Clear(); + + if (IsMultiItemMode()) + { + if (_itemOutputGroups.IsEmpty()) + return false; + + FOR_VECTOR (i, _itemOutputGroups[0].ArcPaths) + { + UString path = _itemOutputGroups[0].ArcPaths[i]; + path.Trim(); + if (path.IsEmpty()) + return false; + + FString fullPath; + if (!MyGetFullPathName(us2fs(path), fullPath)) + return false; + AddUniqueString(paths, fs2us(fullPath)); + } + return (paths.Size() == _itemOutputGroups[0].ArcPaths.Size()); + } + + UString s; + if (!GetFinalPath_Smart(s)) + return false; + s.Trim(); + if (s.IsEmpty()) + return false; + AddUniqueString(paths, s); + + for (unsigned i = 1; i < _outputPathCount; i++) + { + UString path; + m_ExtraArchivePaths[i - 1].GetText(path); + path.Trim(); + if (path.IsEmpty()) + return false; + + FString fullPath; + if (!MyGetFullPathName(us2fs(path), fullPath)) + return false; + AddUniqueString(paths, fs2us(fullPath)); + } + + return (paths.Size() == _outputPathCount); +} + + +void CCompressDialog::UpdateItemArcPathToCurrentFormat(UString &path, int prevFormat, bool prevWasSFX) +{ + UString trimmed = path; + trimmed.Trim(); + if (trimmed.IsEmpty()) + { + path.Empty(); + return; + } + + UString dirPrefix; + UString fileName; + SplitPathToParts_2(path, dirPrefix, fileName); + + if (prevWasSFX) + RemoveTailExtensionIfMatches(fileName, UString(kExeExt)); + else if (prevFormat >= 0) + { + const CArcInfoEx &prevArchiverInfo = (*ArcFormats)[(unsigned)prevFormat]; + UString prevExtension; + prevExtension.Add_Dot(); + prevExtension += prevArchiverInfo.GetMainExt(); + RemoveTailExtensionIfMatches(fileName, prevExtension); + } + + const CArcInfoEx &ai = Get_ArcInfoEx(); + if (IsSFX()) + AppendExtensionWithoutDup(fileName, UString(kExeExt)); + else + { + UString ext = ai.GetMainExt(); + if (ai.Flags_HashHandler()) + { + UString estimatedName; + GetMethodSpec(estimatedName); + if (!estimatedName.IsEmpty()) + { + ext = estimatedName; + ext.MakeLower_Ascii(); + } + } + UString fullExt; + fullExt.Add_Dot(); + fullExt += ext; + AppendExtensionWithoutDup(fileName, fullExt); + } + + path = dirPrefix + fileName; +} + + +void CCompressDialog::UpdateArcPathToCurrentFormat(UString &path, int prevFormat, bool prevWasSFX) +{ + UString trimmed = path; + trimmed.Trim(); + if (trimmed.IsEmpty()) + { + path.Empty(); + return; + } + + UString dirPrefix; + UString fileName; + SplitPathToParts_2(path, dirPrefix, fileName); + + const CArcInfoEx &prevArchiverInfo = (*ArcFormats)[(unsigned)prevFormat]; + if (prevArchiverInfo.Flags_KeepName() || Info.KeepName) + { + UString prevExtension; + if (prevWasSFX) + prevExtension = kExeExt; + else + { + prevExtension.Add_Dot(); + prevExtension += prevArchiverInfo.GetMainExt(); + } + const unsigned prevExtensionLen = prevExtension.Len(); + if (fileName.Len() >= prevExtensionLen) + if (StringsAreEqualNoCase(fileName.RightPtr(prevExtensionLen), prevExtension)) + fileName.DeleteFrom(fileName.Len() - prevExtensionLen); + } + + const CArcInfoEx &ai = Get_ArcInfoEx(); + if (ai.Flags_KeepName()) + fileName = OriginalFileName; + else if (!Info.KeepName) + { + const int dotPos = GetExtDotPos(fileName); + if (dotPos >= 0) + fileName.DeleteFrom(dotPos); + } + + if (IsSFX()) + { + AppendExtensionWithoutDup(fileName, UString(kExeExt)); + } + else + { + UString ext = ai.GetMainExt(); + if (ai.Flags_HashHandler()) + { + UString estimatedName; + GetMethodSpec(estimatedName); + if (!estimatedName.IsEmpty()) + { + ext = estimatedName; + ext.MakeLower_Ascii(); + } + } + UString fullExt; + fullExt.Add_Dot(); + fullExt += ext; + AppendExtensionWithoutDup(fileName, fullExt); + } + + path = dirPrefix + fileName; +} + + +void CCompressDialog::UpdateExtraArcPathsForFormatChange(int prevFormat, bool prevWasSFX) +{ + if (prevFormat >= 0) + { + if (IsMultiItemMode()) + { + FOR_VECTOR (g, _itemOutputGroups) + FOR_VECTOR (i, _itemOutputGroups[g].ArcPaths) + UpdateItemArcPathToCurrentFormat(_itemOutputGroups[g].ArcPaths[i], prevFormat, prevWasSFX); + + if (!_itemOutputGroups.IsEmpty()) + _outputArcPaths = _itemOutputGroups[0].ArcPaths; + } + else + { + if (_outputPathCount > 1) + for (unsigned i = 1; i < _outputPathCount; i++) + UpdateArcPathToCurrentFormat(_outputArcPaths[i], prevFormat, prevWasSFX); + } + } + + UpdateOutputPathControls(); + RefreshArchivePathInfo(); +} + + bool CCompressDialog::SetArcPathFields(const UString &path) { UString name; @@ -843,6 +1997,19 @@ bool CCompressDialog::SetArcPathFields(const UString &path, UString &name, bool { DirPrefix = fs2us(resDirPrefix); name = fs2us(resFileName); + if (name.IsEmpty() && !DirPrefix.IsEmpty()) + { + UString smartDirPrefix; + UString smartName; + SplitPathToParts_Smart(DirPrefix, smartDirPrefix, smartName); + if (!smartName.IsEmpty()) + { + DirPrefix = smartDirPrefix; + name = smartName; + } + } + if (name.IsEmpty() && !OriginalFileName.IsEmpty()) + name = OriginalFileName; } else { @@ -851,7 +2018,7 @@ bool CCompressDialog::SetArcPathFields(const UString &path, UString &name, bool DirPrefix.Empty(); name = path; } - SetItemText(IDT_COMPRESS_ARCHIVE_FOLDER, DirPrefix); + RefreshArchivePathInfo(); m_ArchivePath.SetText(name); return res; } @@ -876,14 +2043,9 @@ static void AddFilter(CObjectVector &filters, static const char * const k_DontSave_Exts = "xpi odt ods docx xlsx "; -void CCompressDialog::OnButtonSetArchive() +bool CCompressDialog::BrowseArchivePath(UString &path, bool allowFormatChange, bool &formatWasChanged) { - UString path; - if (!GetFinalPath_Smart(path)) - { - ShowErrorMessage(*this, k_IncorrectPathMessage); - return; - } + formatWasChanged = false; int filterIndex; CObjectVector filters; @@ -898,55 +2060,80 @@ void CCompressDialog::OnButtonSetArchive() } else { - filterIndex = m_Format.GetCurSel(); - numFormats = (unsigned)m_Format.GetCount(); - - // filters [0, ... numFormats - 1] corresponds to items in m_Format combo - UString desc; - UStringVector masks; - CStringFinder finder; - - for (unsigned i = 0; i < numFormats; i++) + if (allowFormatChange) { - const CArcInfoEx &ai = (*ArcFormats)[(unsigned)m_Format.GetItemData(i)]; + filterIndex = m_Format.GetCurSel(); + numFormats = (unsigned)m_Format.GetCount(); + + // filters [0, ... numFormats - 1] corresponds to items in m_Format combo + UString desc; + UStringVector masks; + CStringFinder finder; + + for (unsigned i = 0; i < numFormats; i++) + { + const CArcInfoEx &ai = (*ArcFormats)[(unsigned)m_Format.GetItemData(i)]; + CBrowseFilterInfo &f = filters.AddNew(); + f.Description = ai.Name; + f.Description += " ("; + bool needSpace_desc = false; + + FOR_VECTOR (k, ai.Exts) + { + const UString &ext = ai.Exts[k].Ext; + UString mask ("*."); + mask += ext; + + if (finder.FindWord_In_LowCaseAsciiList_NoCase(k_DontSave_Exts, ext)) + continue; + + f.Masks.Add(mask); + masks.Add(mask); + if (needSpace_desc) + f.Description.Add_Space(); + needSpace_desc = true; + f.Description += ext; + } + f.Description += ")"; + if (i != 0) + desc.Add_Space(); + desc += ai.GetMainExt(); + } + + CBrowseFilterInfo &f = filters.AddNew(); + f.Description = LangString(IDT_COMPRESS_ARCHIVE); + if (f.Description.IsEmpty()) + GetItemText(IDT_COMPRESS_ARCHIVE, f.Description); + f.Description.RemoveChar(L'&'); + f.Description += " ("; + f.Description += desc; + f.Description += ")"; + f.Masks = masks; + } + else + { + filterIndex = 0; + const CArcInfoEx &ai = Get_ArcInfoEx(); CBrowseFilterInfo &f = filters.AddNew(); f.Description = ai.Name; f.Description += " ("; bool needSpace_desc = false; - + CStringFinder finder; FOR_VECTOR (k, ai.Exts) { const UString &ext = ai.Exts[k].Ext; UString mask ("*."); mask += ext; - if (finder.FindWord_In_LowCaseAsciiList_NoCase(k_DontSave_Exts, ext)) continue; - f.Masks.Add(mask); - masks.Add(mask); if (needSpace_desc) f.Description.Add_Space(); needSpace_desc = true; f.Description += ext; } f.Description += ")"; - // we use only main ext in desc to reduce the size of list - if (i != 0) - desc.Add_Space(); - desc += ai.GetMainExt(); } - - CBrowseFilterInfo &f = filters.AddNew(); - f.Description = LangString(IDT_COMPRESS_ARCHIVE); // IDS_ARCHIVES_COLON; - if (f.Description.IsEmpty()) - GetItemText(IDT_COMPRESS_ARCHIVE, f.Description); - f.Description.RemoveChar(L'&'); - // f.Description = "archive"; - f.Description += " ("; - f.Description += desc; - f.Description += ")"; - f.Masks = masks; } AddFilter(filters, LangString(IDS_OPEN_TYPE_ALL_FILES), UString("*")); @@ -962,7 +2149,7 @@ void CCompressDialog::OnButtonSetArchive() bi.FilePath = path; if (!bi.BrowseForFile(filters)) - return; + return false; path = bi.FilePath; @@ -974,13 +2161,13 @@ void CCompressDialog::OnButtonSetArchive() path += kExeExt; } else - // if (bi.FilterIndex >= 0) - // if (bi.FilterIndex != filterIndex) - if ((unsigned)bi.FilterIndex < numFormats) + if (allowFormatChange ? ((unsigned)bi.FilterIndex < numFormats) : !filters.IsEmpty()) { - // archive format was confirmed. So we try to set format extension + const unsigned arcIndex = allowFormatChange ? + (unsigned)m_Format.GetItemData((unsigned)bi.FilterIndex) : + GetFormatIndex(); + const CArcInfoEx &ai = (*ArcFormats)[arcIndex]; bool needAddExt = true; - const CArcInfoEx &ai = (*ArcFormats)[(unsigned)m_Format.GetItemData((unsigned)bi.FilterIndex)]; const int dotPos = GetExtDotPos(path); if (dotPos >= 0) { @@ -996,24 +2183,95 @@ void CCompressDialog::OnButtonSetArchive() } } - SetArcPathFields(path); - - if (!isSFX) + if (!isSFX && allowFormatChange) if ((unsigned)bi.FilterIndex < numFormats) if (bi.FilterIndex != m_Format.GetCurSel()) { m_Format.SetCurSel(bi.FilterIndex); - SaveOptionsInMem(); - FormatChanged(true); // isChanged - return; + formatWasChanged = true; } - - ArcPath_WasChanged(path); + + return true; } -// in ExtractDialog.cpp -extern void AddUniqueString(UStringVector &strings, const UString &srcString); +void CCompressDialog::OnButtonOutputPaths() +{ + return; +} + + +void CCompressDialog::OnButtonSetArchivePath(unsigned index) +{ + if (IsMultiItemMode()) + { + BrowseItemOutputPath(0, index); + return; + } + + if (index == 0) + { + UString path; + if (!GetFinalPath_Smart(path)) + { + ShowErrorMessage(*this, k_IncorrectPathMessage); + return; + } + + SyncExtraArcPathsFromControls(); + + const int prevFormat = m_PrevFormat; + const bool prevWasSFX = IsSFX(); + bool formatWasChanged; + if (!BrowseArchivePath(path, true, formatWasChanged)) + return; + + SetArcPathFields(path); + SyncPrimaryArcPathFromControl(); + + if (formatWasChanged) + { + SaveOptionsInMem(); + FormatChanged(true); // isChanged + UpdateExtraArcPathsForFormatChange(prevFormat, prevWasSFX); + m_PrevFormat = (int)GetFormatIndex(); + return; + } + + ArcPath_WasChanged(path); + return; + } + + SyncPrimaryArcPathFromControl(); + SyncExtraArcPathsFromControls(); + + while (_outputArcPaths.Size() <= index) + _outputArcPaths.Add(UString()); + + UString path = _outputArcPaths[index]; + if (path.IsEmpty()) + { + if (index > 0 && index - 1 < _outputArcPaths.Size()) + path = _outputArcPaths[index - 1]; + if (path.IsEmpty()) + GetFinalPath_Smart(path); + } + + bool formatWasChanged; + if (!BrowseArchivePath(path, false, formatWasChanged)) + return; + + _outputArcPaths[index] = path; + m_ExtraArchivePaths[index - 1].SetText(path); + RefreshArchivePathInfo(); +} + + +void CCompressDialog::OnButtonSetArchive() +{ + OnButtonSetArchivePath(0); +} + static bool IsAsciiString(const UString &s) { @@ -1121,16 +2379,54 @@ void CCompressDialog::OnOK() SaveOptionsInMem(); - UStringVector arcPaths; + if (IsMultiItemMode()) + SyncAllItemOutputGroupsFromControls(); + + const bool separateItemMode = IsMultiItemMode(); + Info.SeparateItemArchives = separateItemMode; + + UStringVector outputArcPaths; + if (separateItemMode) { - UString s; - if (!GetFinalPath_Smart(s)) + UStringVector outputItemPaths; + if (!GetItemArcPaths(outputItemPaths, outputArcPaths)) + { + MessageBoxError(L"Specify one unique output path for each selected item output"); + return; + } + Info.ItemOutputItemPaths = outputItemPaths; + Info.ItemArcPaths = outputArcPaths; + Info.ArcPaths = outputArcPaths; + Info.ArcPath = outputArcPaths.Front(); + + if (IsButtonCheckedBool(IDX_COMPRESS_DEL)) + { + FOR_VECTOR (i, outputItemPaths) + FOR_VECTOR (k, outputItemPaths) + if (i != k && outputItemPaths[i] == outputItemPaths[k]) + { + MessageBoxError(L"Delete after compression is not supported when the same item is mapped to multiple outputs"); + return; + } + } + } + else + { + Info.ItemOutputItemPaths.Clear(); + Info.ItemArcPaths.Clear(); + if (!GetOutputArcPaths(outputArcPaths)) { ShowErrorMessage(*this, k_IncorrectPathMessage); return; } - Info.ArcPath = s; - AddUniqueString(arcPaths, s); + Info.ArcPaths = outputArcPaths; + Info.ArcPath = outputArcPaths.Front(); + } + + if (!separateItemMode && outputArcPaths.Size() > 1 && IsButtonCheckedBool(IDX_COMPRESS_DEL)) + { + MessageBoxError(L"Delete after compression is not supported for multiple output paths"); + return; } Info.UpdateMode = (NCompressDialog::NUpdateMode::EEnum)k_UpdateMode_Vals[m_UpdateMode.GetCurSel()]; @@ -1238,6 +2534,8 @@ void CCompressDialog::OnOK() m_RegistryInfo.ArcType = (*ArcFormats)[Info.FormatIndex].Name; m_RegistryInfo.ShowPassword = IsShowPasswordChecked(); + UStringVector arcPaths; + AddUniqueString(arcPaths, Info.ArcPath); FOR_VECTOR (i, m_RegistryInfo.ArcPaths) { if (arcPaths.Size() >= kHistorySize) @@ -1262,6 +2560,9 @@ void CCompressDialog::OnHelp() void CCompressDialog::ArcPath_WasChanged(const UString &path) { + const int prevFormat = m_PrevFormat; + const bool prevWasSFX = IsSFX(); + SyncExtraArcPathsFromControls(); const int dotPos = GetExtDotPos(path); if (dotPos < 0) return; @@ -1281,6 +2582,8 @@ void CCompressDialog::ArcPath_WasChanged(const UString &path) m_Format.SetCurSel(i); SaveOptionsInMem(); FormatChanged(true); // isChanged + UpdateExtraArcPathsForFormatChange(prevFormat, prevWasSFX); + m_PrevFormat = (int)i; return; } } @@ -1300,7 +2603,15 @@ bool CCompressDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) // if (path == m_RegistryInfo.ArcPaths[select]) { const UString &path = m_RegistryInfo.ArcPaths[select]; - SetArcPathFields(path); + if (_outputArcPaths.IsEmpty()) + _outputArcPaths.Add(path); + else + _outputArcPaths[0] = path; + if (IsMultiItemMode()) + m_ArchivePath.SetText(path); + else + SetArcPathFields(path); + SyncPrimaryArcPathFromControl(); // ArcPath_WasChanged(path); } return 0; @@ -1314,10 +2625,38 @@ bool CCompressDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) { if (code == CBN_SELCHANGE) { + if (IsMultiItemMode()) + { + for (unsigned groupIndex = 1; groupIndex < _itemOutputGroups.Size(); groupIndex++) + if (itemID == GetDynamicGroupCountComboId(groupIndex)) + { + NWindows::NControl::CComboBox countCombo; + countCombo.Attach(GetItem(itemID)); + const int select = countCombo.GetCurSel(); + if (select >= 0) + SetItemOutputGroupCount(groupIndex, (unsigned)select + 1); + return true; + } + } + switch (itemID) { + case IDC_COMPRESS_OUTPUT_PATHS_NUM: + { + const int select = m_OutputPathCount.GetCurSel(); + if (select >= 0) + SetOutputPathCount((unsigned)select + 1); + return true; + } + case IDC_COMPRESS_ARCHIVE: { + if (IsMultiItemMode()) + { + SyncPrimaryArcPathFromControl(); + return true; + } + /* CBN_SELCHANGE is called before actual value of combo text will be changed. So GetText() here returns old value (before change) of combo text. So here we can change all controls except of m_ArchivePath. @@ -1339,9 +2678,19 @@ bool CCompressDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) case IDC_COMPRESS_FORMAT: { const bool isSFX = IsSFX(); + const int prevFormat = m_PrevFormat; + if (IsMultiItemMode()) + SyncAllItemOutputGroupsFromControls(); + else + SyncExtraArcPathsFromControls(); SaveOptionsInMem(); FormatChanged(true); // isChanged - SetArchiveName2(isSFX); + if (!IsMultiItemMode()) + { + SetArchiveName2(isSFX); + SyncPrimaryArcPathFromControl(); + } + UpdateExtraArcPathsForFormatChange(prevFormat, isSFX); return true; } @@ -1375,7 +2724,13 @@ bool CCompressDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) CheckSFXNameChange(); SetMemoryUsage(); if (Get_ArcInfoEx().Flags_HashHandler()) - SetArchiveName2(false); + { + if (!IsMultiItemMode()) + { + SetArchiveName2(false); + SyncPrimaryArcPathFromControl(); + } + } return true; } @@ -1442,9 +2797,20 @@ bool CCompressDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) void CCompressDialog::CheckSFXNameChange() { const bool isSFX = IsSFX(); + const int prevFormat = m_PrevFormat; CheckSFXControlsEnable(); if (isSFX != IsSFX()) - SetArchiveName2(isSFX); + { + if (IsMultiItemMode()) + SyncAllItemOutputGroupsFromControls(); + else + { + SetArchiveName2(isSFX); + SyncPrimaryArcPathFromControl(); + SyncExtraArcPathsFromControls(); + } + UpdateExtraArcPathsForFormatChange(prevFormat, isSFX); + } } void CCompressDialog::SetArchiveName2(bool prevWasSFX) @@ -1495,10 +2861,9 @@ void CCompressDialog::SetArchiveName(const UString &name) } if (IsSFX()) - fileName += kExeExt; + AppendExtensionWithoutDup(fileName, UString(kExeExt)); else { - fileName.Add_Dot(); UString ext = ai.GetMainExt(); if (ai.Flags_HashHandler()) { @@ -1510,7 +2875,10 @@ void CCompressDialog::SetArchiveName(const UString &name) ext.MakeLower_Ascii(); } } - fileName += ext; + UString fullExt; + fullExt.Add_Dot(); + fullExt += ext; + AppendExtensionWithoutDup(fileName, fullExt); } m_ArchivePath.SetText(fileName); } @@ -3377,8 +4745,324 @@ void CCompressDialog::ShowOptionsString() } +static void OutputPathsToText(const UStringVector &paths, UString &s) +{ + s.Empty(); + FOR_VECTOR (i, paths) + { + if (i != 0) + s.Add_LF(); + s += paths[i]; + } +} +static void GetItemDisplayName(const UString &itemPath, UString &name) +{ + UString dirPrefix; + SplitPathToParts_Smart(itemPath, dirPrefix, name); + if (name.IsEmpty()) + name = ExtractFileNameFromPath(itemPath); + if (name.IsEmpty()) + name = itemPath; +} + + +static void BuildItemDisplayNames(const UStringVector &itemPaths, UStringVector &displayNames) +{ + UStringVector baseNames; + FOR_VECTOR (i, itemPaths) + { + UString name; + GetItemDisplayName(itemPaths[i], name); + baseNames.Add(name); + } + + displayNames.Clear(); + FOR_VECTOR (i, itemPaths) + { + bool needFullPath = false; + FOR_VECTOR (k, baseNames) + if (i != k && baseNames[i].IsEqualTo_NoCase(baseNames[k])) + { + needFullPath = true; + break; + } + displayNames.Add(needFullPath ? itemPaths[i] : baseNames[i]); + } +} + + +static bool ResolveItemDisplayName( + const UStringVector &availableItems, + const UStringVector &displayNames, + const UString &token, + UString &itemPath) +{ + FOR_VECTOR (i, availableItems) + if (token.IsEqualTo_NoCase(displayNames[i]) || + token.IsEqualTo_NoCase(availableItems[i])) + { + itemPath = availableItems[i]; + return true; + } + return false; +} + + +static void OutputItemArcPathsToText( + const UStringVector &owners, + const UStringVector &paths, + const UStringVector &availableItems, + UString &s) +{ + s.Empty(); + + UStringVector displayNames; + BuildItemDisplayNames(availableItems, displayNames); + + unsigned numPairs = owners.Size(); + if (numPairs > paths.Size()) + numPairs = paths.Size(); + + for (unsigned i = 0; i < numPairs; i++) + { + if (i != 0) + s.Add_LF(); + + UString owner = owners[i]; + FOR_VECTOR (k, availableItems) + if (availableItems[k] == owners[i]) + { + owner = displayNames[k]; + break; + } + + s += owner; + s += L'>'; + s += paths[i]; + } +} + + +static void TextToOutputPaths(const UString &text, UStringVector &paths) +{ + paths.Clear(); + + UString cur; + for (unsigned i = 0;; i++) + { + const wchar_t c = (i == text.Len()) ? 0 : text[i]; + if (c == '\r') + continue; + if (c == '\n' || c == 0) + { + cur.Trim(); + if (!cur.IsEmpty()) + AddUniqueString(paths, cur); + cur.Empty(); + if (c == 0) + break; + continue; + } + cur += c; + } +} + + +static bool TextToOutputItemArcPaths( + const UString &text, + const UStringVector &availableItems, + UStringVector &owners, + UStringVector &paths, + UString &errorMessage) +{ + owners.Clear(); + paths.Clear(); + errorMessage.Empty(); + + UStringVector displayNames; + BuildItemDisplayNames(availableItems, displayNames); + + UString cur; + unsigned lineNumber = 1; + for (unsigned i = 0;; i++) + { + const wchar_t c = (i == text.Len()) ? 0 : text[i]; + if (c == '\r') + continue; + if (c == '\n' || c == 0) + { + cur.Trim(); + if (!cur.IsEmpty()) + { + const int sep = cur.Find(L'>'); + if (sep < 0) + { + errorMessage = L"Line "; + errorMessage.Add_UInt32((UInt32)lineNumber); + errorMessage += L": use item>output path"; + return false; + } + + UString itemToken = cur.Left((unsigned)sep); + UString path = cur.Ptr(sep + 1); + itemToken.Trim(); + path.Trim(); + + if (!itemToken.IsEmpty() && itemToken.Back() == L'-') + { + itemToken.DeleteBack(); + itemToken.Trim(); + } + + if (!path.IsEmpty() && path.Back() == L';') + { + path.DeleteBack(); + path.Trim(); + } + + if (itemToken.Len() >= 2 && itemToken[0] == L'"' && itemToken.Back() == L'"') + { + itemToken.DeleteBack(); + itemToken.DeleteFrontal(1); + itemToken.Trim(); + } + + if (path.Len() >= 2 && path[0] == L'"' && path.Back() == L'"') + { + path.DeleteBack(); + path.DeleteFrontal(1); + path.Trim(); + } + + if (itemToken.IsEmpty() || path.IsEmpty()) + { + errorMessage = L"Line "; + errorMessage.Add_UInt32((UInt32)lineNumber); + errorMessage += L": use item>output path"; + return false; + } + + UString itemPath; + if (!ResolveItemDisplayName(availableItems, displayNames, itemToken, itemPath)) + { + errorMessage = L"Line "; + errorMessage.Add_UInt32((UInt32)lineNumber); + errorMessage += L": unknown selected item: "; + errorMessage += itemToken; + return false; + } + + owners.Add(itemPath); + paths.Add(path); + } + + cur.Empty(); + if (c == 0) + break; + lineNumber++; + continue; + } + cur += c; + } + + return true; +} + + +bool COutputArcPathsDialog::OnInit() +{ + _pathsEdit.Attach(GetItem(IDE_COMPRESS_OUTPUT_PATHS)); + + if (PerItemMode) + { + UString info = L"Use item>output path, one mapping per line:"; + SetItemText(IDT_COMPRESS_OUTPUT_PATHS_INFO, info); + ShowItem_Bool(IDB_COMPRESS_OUTPUT_PATHS_ADD, false); + } + + UString s; + if (PerItemMode) + OutputItemArcPathsToText(Owners, Paths, AvailableItems, s); + else + OutputPathsToText(Paths, s); + _pathsEdit.SetText(s); + + NormalizePosition(); + return CModalDialog::OnInit(); +} + + +void COutputArcPathsDialog::AddPathFromBrowse() +{ + UString currentText; + _pathsEdit.GetText(currentText); + UStringVector currentPaths; + TextToOutputPaths(currentText, currentPaths); + + UString path; + if (!currentPaths.IsEmpty()) + path = currentPaths.Back(); + else if (!Paths.IsEmpty()) + path = Paths.Back(); + else if (!cd->_outputArcPaths.IsEmpty()) + path = cd->_outputArcPaths.Back(); + else + cd->GetFinalPath_Smart(path); + + bool formatWasChanged; + if (!cd->BrowseArchivePath(path, false, formatWasChanged)) + return; + + UString s; + _pathsEdit.GetText(s); + s.TrimRight(); + if (!s.IsEmpty()) + s.Add_LF(); + s += path; + _pathsEdit.SetText(s); +} + + +bool COutputArcPathsDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDB_COMPRESS_OUTPUT_PATHS_ADD: + { + AddPathFromBrowse(); + return true; + } + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} + + +void COutputArcPathsDialog::OnOK() +{ + UString s; + _pathsEdit.GetText(s); + if (PerItemMode) + { + UString errorMessage; + if (!TextToOutputItemArcPaths(s, AvailableItems, Owners, Paths, errorMessage)) + { + MessageBoxW(*this, errorMessage, L"7-Zip", MB_ICONERROR); + return; + } + } + else + TextToOutputPaths(s, Paths); + + if (Paths.IsEmpty()) + { + MessageBoxW(*this, L"Specify at least one output path", L"7-Zip", MB_ICONERROR); + return; + } + CModalDialog::OnOK(); +} + // ---------- OPTIONS ---------- diff --git a/CPP/7zip/UI/GUI/CompressDialog.h b/CPP/7zip/UI/GUI/CompressDialog.h index e0f3aa5..955c5c0 100644 --- a/CPP/7zip/UI/GUI/CompressDialog.h +++ b/CPP/7zip/UI/GUI/CompressDialog.h @@ -70,6 +70,11 @@ namespace NCompressDialog CBoolPair SetArcMTime; UString ArcPath; // in: Relative or abs ; out: Relative or abs + UStringVector ArcPaths; + UStringVector ItemPaths; + UStringVector ItemOutputItemPaths; + UStringVector ItemArcPaths; + bool SeparateItemArchives; // FString CurrentDirPrefix; bool KeepName; @@ -88,6 +93,7 @@ namespace NCompressDialog SFXMode(false), OpenShareForWrite(false), DeleteAfterCompressing(false), + SeparateItemArchives(false), FormatIndex(-1) { Level = Order = (UInt32)(Int32)-1; @@ -142,15 +148,48 @@ struct CBool1 class CCompressDialog: public NWindows::NControl::CModalDialog { public: + friend class COutputArcPathsDialog; CBool1 SymLinks; CBool1 HardLinks; CBool1 AltStreams; CBool1 NtSecurity; CBool1 PreserveATime; private: + struct CLayoutItem + { + unsigned Id; + RECT Rect; + }; + + struct CItemOutputGroup + { + UString ItemPath; + UStringVector ArcPaths; + }; + + static const unsigned kNumOutputPathRows = 5; + bool _ramSize_Defined; + bool _outputLayout_Inited; + UStringVector _outputArcPaths; + CObjectVector _itemOutputGroups; + CRecordVector _outputLayout_Items; + unsigned _outputPathCount; + int _outputPathRowStep; + int _outputLayout_BaseWindowX; + int _outputLayout_BaseWindowY; + int _outputLayout_BaseControlsTop; + int _outputGroupGapY; + RECT _outputTemplate_ItemLabelRect; + RECT _outputTemplate_CountLabelRect; + RECT _outputTemplate_CountComboRect; + RECT _outputTemplate_ArchiveLabelRect; + RECT _outputTemplate_ArchiveComboRects[kNumOutputPathRows]; + RECT _outputTemplate_ArchiveButtonRects[kNumOutputPathRows]; NWindows::NControl::CComboBox m_ArchivePath; + NWindows::NControl::CComboBox m_OutputPathCount; + NWindows::NControl::CComboBox m_ExtraArchivePaths[kNumOutputPathRows - 1]; NWindows::NControl::CComboBox m_Format; NWindows::NControl::CComboBox m_Level; NWindows::NControl::CComboBox m_Method; @@ -339,8 +378,34 @@ public: void UpdatePasswordControl(); bool IsShowPasswordChecked() const { return IsButtonCheckedBool(IDX_PASSWORD_SHOW); } + bool IsMultiItemMode() const { return Info.ItemPaths.Size() > 1; } unsigned GetFormatIndex(); + void InitOutputPathLayout(); + void UpdateOutputPathLayout(); + void UpdateOutputPathControls(); + void RefreshArchivePathInfo(); + void SyncPrimaryArcPathFromControl(); + void SyncExtraArcPathsFromControls(); + void SetOutputPathCount(unsigned count, bool syncFromControls = true); + void SetOutputArcPaths(const UStringVector &paths); + bool GetOutputArcPaths(UStringVector &paths) const; + void InitItemOutputGroups(); + void CreateDynamicItemOutputControls(); + void LayoutItemOutputGroups(); + void UpdateItemOutputGroupControls(unsigned groupIndex); + void SyncItemOutputGroupFromControls(unsigned groupIndex); + void SyncAllItemOutputGroupsFromControls(); + void SetItemOutputGroupCount(unsigned groupIndex, unsigned count, bool syncFromControls = true); + bool GetItemOutputGroupPaths(UStringVector &itemPaths, UStringVector &paths) const; + bool BrowseItemOutputPath(unsigned groupIndex, unsigned pathIndex); + void UpdateSeparateItemModeControls(); + bool GetItemArcPaths(UStringVector &itemPaths, UStringVector &paths) const; + void BuildItemArcPath(const UString &inputPath, UString &path); + void UpdateItemArcPathToCurrentFormat(UString &path, int prevFormat, bool prevWasSFX); + void UpdateArcPathToCurrentFormat(UString &path, int prevFormat, bool prevWasSFX); + void UpdateExtraArcPathsForFormatChange(int prevFormat, bool prevWasSFX); + bool BrowseArchivePath(UString &path, bool allowFormatChange, bool &formatWasChanged); bool SetArcPathFields(const UString &path, UString &name, bool always); bool SetArcPathFields(const UString &path); bool GetFinalPath_Smart(UString &resPath) const; @@ -351,6 +416,8 @@ public: void EnableMultiCombo(unsigned id); void FormatChanged(bool isChanged); + void OnButtonOutputPaths(); + void OnButtonSetArchivePath(unsigned index); void OnButtonSetArchive(); bool IsSFX(); void OnButtonSFX(); @@ -381,11 +448,48 @@ public: INT_PTR Create(HWND wndParent = NULL) { - BIG_DIALOG_SIZE(400, 320); + BIG_DIALOG_SIZE(400, 430); return CModalDialog::Create(SIZED_DIALOG(IDD_COMPRESS), wndParent); } - CCompressDialog() {} + CCompressDialog(): + _ramSize_Defined(false), + _outputLayout_Inited(false), + _outputPathCount(1), + _outputPathRowStep(0), + _outputLayout_BaseWindowX(0), + _outputLayout_BaseWindowY(0) + {} +}; + + +class COutputArcPathsDialog: public NWindows::NControl::CModalDialog +{ + CCompressDialog *cd; + NWindows::NControl::CEdit _pathsEdit; + + void AddPathFromBrowse(); + + virtual bool OnInit() Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + virtual void OnOK() Z7_override; + +public: + UStringVector Owners; + UStringVector Paths; + bool PerItemMode; + UStringVector AvailableItems; + + INT_PTR Create(HWND wndParent = NULL) + { + BIG_DIALOG_SIZE(320, 200); + return CModalDialog::Create(SIZED_DIALOG(IDD_COMPRESS_OUTPUT_PATHS), wndParent); + } + + COutputArcPathsDialog(CCompressDialog *cdLoc): + cd(cdLoc), + PerItemMode(false) + {} }; diff --git a/CPP/7zip/UI/GUI/CompressDialog.rc b/CPP/7zip/UI/GUI/CompressDialog.rc index df1516c..eb11aba 100644 --- a/CPP/7zip/UI/GUI/CompressDialog.rc +++ b/CPP/7zip/UI/GUI/CompressDialog.rc @@ -2,7 +2,7 @@ #include "../../GuiCommon.rc" #define xc 400 -#define yc 320 +#define yc 430 #undef gSize #undef gSpace @@ -34,7 +34,9 @@ #define g4xs (xc - gSize - gSpace) #define g4xs2 (g4xs - m - m) -#define yOpt 80 +#define yTopShift 105 + +#define yOpt (80 + yTopShift) #define xArcFolderOffs 40 @@ -59,61 +61,79 @@ IDD_COMPRESS DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT CAPTION "Add to Archive" BEGIN LTEXT "", IDT_COMPRESS_ARCHIVE_FOLDER, m + xArcFolderOffs, m, xc - xArcFolderOffs, 8 - LTEXT "&Archive:", IDT_COMPRESS_ARCHIVE, m, 12, xArcFolderOffs, 8 - COMBOBOX IDC_COMPRESS_ARCHIVE, m + xArcFolderOffs, 18, xc - bxsDots - 12 - xArcFolderOffs, 126, MY_COMBO_WITH_EDIT - PUSHBUTTON "...", IDB_COMPRESS_SET_ARCHIVE, xs - m - bxsDots, 16, bxsDots, bys, WS_GROUP + LTEXT "&Count:", IDT_COMPRESS_OUTPUT_PATHS_NUM, m, 20, xArcFolderOffs, 8 + COMBOBOX IDC_COMPRESS_OUTPUT_PATHS_NUM, m + xArcFolderOffs, 18, 56, 80, MY_COMBO + PUSHBUTTON "Pa&ths...", IDB_COMPRESS_OUTPUT_PATHS, m + xArcFolderOffs + 62, 18, 54, bys + CONTROL "Map selected items to output archives", IDX_COMPRESS_SEPARATE_ITEMS, MY_CHECKBOX, + m + xArcFolderOffs + 122, 20, xc - xArcFolderOffs - 122, 10 - LTEXT "Archive &format:", IDT_COMPRESS_FORMAT, m, 41, g0xs, 8 - COMBOBOX IDC_COMPRESS_FORMAT, g1x, 39, g1xs, 80, MY_COMBO | CBS_SORT - - LTEXT "Compression &level:", IDT_COMPRESS_LEVEL, m, 62, g0xs, 8 - COMBOBOX IDC_COMPRESS_LEVEL, g1x, 60, g1xs, 80, MY_COMBO - - LTEXT "Compression &method:", IDT_COMPRESS_METHOD, m, 83, g0xs, 8 - COMBOBOX IDC_COMPRESS_METHOD, g1x, 81, g1xs, 80, MY_COMBO + LTEXT "&Archive:", IDT_COMPRESS_ARCHIVE, m, 41, xArcFolderOffs, 8 + COMBOBOX IDC_COMPRESS_ARCHIVE, m + xArcFolderOffs, 39, xc - bxsDots - 12 - xArcFolderOffs, 126, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_COMPRESS_SET_ARCHIVE, xs - m - bxsDots, 37, bxsDots, bys, WS_GROUP - LTEXT "&Dictionary size:", IDT_COMPRESS_DICTIONARY, m, 104, g0xs, 8 - COMBOBOX IDC_COMPRESS_DICTIONARY, g1x, 102, g1xs, 167, MY_COMBO + COMBOBOX IDC_COMPRESS_ARCHIVE2, m + xArcFolderOffs, 60, xc - bxsDots - 12 - xArcFolderOffs, 126, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_COMPRESS_SET_ARCHIVE2, xs - m - bxsDots, 58, bxsDots, bys + + COMBOBOX IDC_COMPRESS_ARCHIVE3, m + xArcFolderOffs, 81, xc - bxsDots - 12 - xArcFolderOffs, 126, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_COMPRESS_SET_ARCHIVE3, xs - m - bxsDots, 79, bxsDots, bys + + COMBOBOX IDC_COMPRESS_ARCHIVE4, m + xArcFolderOffs, 102, xc - bxsDots - 12 - xArcFolderOffs, 126, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_COMPRESS_SET_ARCHIVE4, xs - m - bxsDots, 100, bxsDots, bys + + COMBOBOX IDC_COMPRESS_ARCHIVE5, m + xArcFolderOffs, 123, xc - bxsDots - 12 - xArcFolderOffs, 126, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_COMPRESS_SET_ARCHIVE5, xs - m - bxsDots, 121, bxsDots, bys + + LTEXT "Archive &format:", IDT_COMPRESS_FORMAT, m, 41 + yTopShift, g0xs, 8 + COMBOBOX IDC_COMPRESS_FORMAT, g1x, 39 + yTopShift, g1xs, 80, MY_COMBO | CBS_SORT + + LTEXT "Compression &level:", IDT_COMPRESS_LEVEL, m, 62 + yTopShift, g0xs, 8 + COMBOBOX IDC_COMPRESS_LEVEL, g1x, 60 + yTopShift, g1xs, 80, MY_COMBO + + LTEXT "Compression &method:", IDT_COMPRESS_METHOD, m, 83 + yTopShift, g0xs, 8 + COMBOBOX IDC_COMPRESS_METHOD, g1x, 81 + yTopShift, g1xs, 80, MY_COMBO + + LTEXT "&Dictionary size:", IDT_COMPRESS_DICTIONARY, m, 104 + yTopShift, g0xs, 8 + COMBOBOX IDC_COMPRESS_DICTIONARY, g1x, 102 + yTopShift, g1xs, 167, MY_COMBO // LTEXT "&Dictionary size:", IDT_COMPRESS_DICTIONARY, m, 104, DICT_x - m, 16 // 8, SS_LEFTNOWORDWRAP // LTEXT "", IDT_COMPRESS_PARAMS_INFO, m, 283, xs, MY_TEXT_NOPREFIX // CTEXT "-", 0, DICT_x - DICT_SIZE_SPACE, 104, DICT_SIZE_SPACE, 8 // COMBOBOX IDC_COMPRESS_DICTIONARY2, DICT2_x, 102, DICT_SIZE, 140, MY_COMBO // COMBOBOX IDC_COMPRESS_DICTIONARY, DICT_x, 102, DICT_SIZE, 140, MY_COMBO - LTEXT "&Word size:", IDT_COMPRESS_ORDER, m, 125, g0xs, 8 - COMBOBOX IDC_COMPRESS_ORDER, g1x, 123, g1xs, 140, MY_COMBO + LTEXT "&Word size:", IDT_COMPRESS_ORDER, m, 125 + yTopShift, g0xs, 8 + COMBOBOX IDC_COMPRESS_ORDER, g1x, 123 + yTopShift, g1xs, 140, MY_COMBO - LTEXT "&Solid Block size:", IDT_COMPRESS_SOLID, m, 146, g0xs, 8 - COMBOBOX IDC_COMPRESS_SOLID, g1x, 144, g1xs, 140, MY_COMBO + LTEXT "&Solid Block size:", IDT_COMPRESS_SOLID, m, 146 + yTopShift, g0xs, 8 + COMBOBOX IDC_COMPRESS_SOLID, g1x, 144 + yTopShift, g1xs, 140, MY_COMBO - LTEXT "Number of CPU &threads:", IDT_COMPRESS_THREADS, m, 167, g0xs, 8 - COMBOBOX IDC_COMPRESS_THREADS, g1x, 165, g1xs - 40, 140, MY_COMBO - RTEXT "", IDT_COMPRESS_HARDWARE_THREADS, g1x + g1xs - 40, 167, 40, 16, SS_NOPREFIX + LTEXT "Number of CPU &threads:", IDT_COMPRESS_THREADS, m, 167 + yTopShift, g0xs, 8 + COMBOBOX IDC_COMPRESS_THREADS, g1x, 165 + yTopShift, g1xs - 40, 140, MY_COMBO + RTEXT "", IDT_COMPRESS_HARDWARE_THREADS, g1x + g1xs - 40, 167 + yTopShift, 40, 16, SS_NOPREFIX - LTEXT "Memory usage for Compressing:", IDT_COMPRESS_MEMORY, m, 184, g2xs, 8 - COMBOBOX IDC_COMPRESS_MEM_USE, g3x, 188, g3xs, 140, MY_COMBO - LTEXT "", IDT_COMPRESS_MEMORY_VALUE, m, 194, g2xs, MY_TEXT_NOPREFIX + LTEXT "Memory usage for Compressing:", IDT_COMPRESS_MEMORY, m, 184 + yTopShift, g2xs, 8 + COMBOBOX IDC_COMPRESS_MEM_USE, g3x, 188 + yTopShift, g3xs, 140, MY_COMBO + LTEXT "", IDT_COMPRESS_MEMORY_VALUE, m, 194 + yTopShift, g2xs, MY_TEXT_NOPREFIX - LTEXT "Memory usage for Decompressing:", IDT_COMPRESS_MEMORY_DE, m, 208, g2xs, 8 - RTEXT "", IDT_COMPRESS_MEMORY_DE_VALUE, g3x, 208, g3xs, MY_TEXT_NOPREFIX + LTEXT "Memory usage for Decompressing:", IDT_COMPRESS_MEMORY_DE, m, 208 + yTopShift, g2xs, 8 + RTEXT "", IDT_COMPRESS_MEMORY_DE_VALUE, g3x, 208 + yTopShift, g3xs, MY_TEXT_NOPREFIX - LTEXT "Split to &volumes, bytes:", IDT_SPLIT_TO_VOLUMES, m, 225, gSize, 8 - COMBOBOX IDC_COMPRESS_VOLUME, m, 237, gSize, 73, MY_COMBO_WITH_EDIT + LTEXT "Split to &volumes, bytes:", IDT_SPLIT_TO_VOLUMES, m, 225 + yTopShift, gSize, 8 + COMBOBOX IDC_COMPRESS_VOLUME, m, 237 + yTopShift, gSize, 73, MY_COMBO_WITH_EDIT - LTEXT "Parameters:", IDT_COMPRESS_PARAMETERS, m, 256, gSize, 8 - EDITTEXT IDE_COMPRESS_PARAMETERS, m, 268, gSize, 14, ES_AUTOHSCROLL + LTEXT "Parameters:", IDT_COMPRESS_PARAMETERS, m, 256 + yTopShift, gSize, 8 + EDITTEXT IDE_COMPRESS_PARAMETERS, m, 268 + yTopShift, gSize, 14, ES_AUTOHSCROLL - PUSHBUTTON "Options", IDB_COMPRESS_OPTIONS, m, 292, bxs, bys - LTEXT "", IDT_COMPRESS_OPTIONS, m + bxs + m, 294, gSize - bxs - m, 16, SS_NOPREFIX + PUSHBUTTON "Options", IDB_COMPRESS_OPTIONS, m, 292 + yTopShift, bxs, bys + LTEXT "", IDT_COMPRESS_OPTIONS, m + bxs + m, 294 + yTopShift, gSize - bxs - m, 16, SS_NOPREFIX - LTEXT "&Update mode:", IDT_COMPRESS_UPDATE_MODE, g4x, 41, 80, 8 - COMBOBOX IDC_COMPRESS_UPDATE_MODE, g4x + 84, 39, g4xs - 84, 80, MY_COMBO + LTEXT "&Update mode:", IDT_COMPRESS_UPDATE_MODE, g4x, 41 + yTopShift, 80, 8 + COMBOBOX IDC_COMPRESS_UPDATE_MODE, g4x + 84, 39 + yTopShift, g4xs - 84, 80, MY_COMBO - LTEXT "Path mode:", IDT_COMPRESS_PATH_MODE, g4x, 61, 80, 8 - COMBOBOX IDC_COMPRESS_PATH_MODE, g4x + 84, 59, g4xs - 84, 80, MY_COMBO + LTEXT "Path mode:", IDT_COMPRESS_PATH_MODE, g4x, 61 + yTopShift, 80, 8 + COMBOBOX IDC_COMPRESS_PATH_MODE, g4x + 84, 59 + yTopShift, g4xs - 84, 80, MY_COMBO GROUPBOX "Options", IDG_COMPRESS_OPTIONS, g4x, yOpt, g4xs, GROUP_Y_SIZE @@ -148,6 +168,27 @@ BEGIN END +#define xcPaths 320 +#define ycPaths 200 +#define xsPaths (xcPaths + m + m) +#define ysPaths (ycPaths + m + m) +#define bxPaths1 (xsPaths - m - bxs) +#define bxPaths2 (bxPaths1 - m - bxs) +#define byPaths (ysPaths - m - bys) + +IDD_COMPRESS_OUTPUT_PATHS DIALOG 0, 0, xsPaths, ysPaths MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "Output Paths" +BEGIN + LTEXT "One archive output path per line:", IDT_COMPRESS_OUTPUT_PATHS_INFO, m, m, xcPaths, 8 + EDITTEXT IDE_COMPRESS_OUTPUT_PATHS, m, 20, xcPaths, ycPaths - bys - m - 24, + ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | WS_HSCROLL | ES_WANTRETURN + + PUSHBUTTON "&Add...", IDB_COMPRESS_OUTPUT_PATHS_ADD, m, byPaths, bxs, bys + DEFPUSHBUTTON "OK", IDOK, bxPaths2, byPaths, bxs, bys, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, bxPaths1, byPaths, bxs, bys +END + + #ifdef UNDER_CE #undef m diff --git a/CPP/7zip/UI/GUI/CompressDialogRes.h b/CPP/7zip/UI/GUI/CompressDialogRes.h index d04d4b9..7e996b4 100644 --- a/CPP/7zip/UI/GUI/CompressDialogRes.h +++ b/CPP/7zip/UI/GUI/CompressDialogRes.h @@ -1,6 +1,7 @@ #define IDD_COMPRESS 4000 #define IDD_COMPRESS_2 14000 #define IDD_COMPRESS_OPTIONS 14001 +#define IDD_COMPRESS_OUTPUT_PATHS 14002 #define IDC_COMPRESS_ARCHIVE 100 #define IDB_COMPRESS_SET_ARCHIVE 101 @@ -22,14 +23,27 @@ #define IDG_COMPRESS_NTFS 115 #define IDC_COMPRESS_PATH_MODE 116 #define IDC_COMPRESS_MEM_USE 117 +#define IDB_COMPRESS_OUTPUT_PATHS 118 // #define IDC_COMPRESS_DICTIONARY2 118 #define IDE_COMPRESS_PASSWORD1 120 #define IDE_COMPRESS_PASSWORD2 121 #define IDC_COMPRESS_ENCRYPTION_METHOD 122 +#define IDE_COMPRESS_OUTPUT_PATHS 123 +#define IDB_COMPRESS_OUTPUT_PATHS_ADD 124 +#define IDC_COMPRESS_OUTPUT_PATHS_NUM 125 #define IDT_COMPRESS_ARCHIVE_FOLDER 130 +#define IDC_COMPRESS_ARCHIVE2 180 +#define IDB_COMPRESS_SET_ARCHIVE2 181 +#define IDC_COMPRESS_ARCHIVE3 182 +#define IDB_COMPRESS_SET_ARCHIVE3 183 +#define IDC_COMPRESS_ARCHIVE4 184 +#define IDB_COMPRESS_SET_ARCHIVE4 185 +#define IDC_COMPRESS_ARCHIVE5 186 +#define IDB_COMPRESS_SET_ARCHIVE5 187 + // #define IDB_COMPRESS_OPTIONS 140 #define IDB_COMPRESS_OPTIONS 2100 #define IDT_COMPRESS_OPTIONS 141 @@ -67,6 +81,9 @@ #define IDT_COMPRESS_MEMORY_DE 4018 #define IDX_COMPRESS_DEL 4019 +#define IDT_COMPRESS_OUTPUT_PATHS_INFO 4020 +#define IDT_COMPRESS_OUTPUT_PATHS_NUM 4021 +#define IDX_COMPRESS_SEPARATE_ITEMS 4022 #define IDX_COMPRESS_NT_SYM_LINKS 4040 #define IDX_COMPRESS_NT_HARD_LINKS 4041 diff --git a/CPP/7zip/UI/GUI/UpdateGUI.cpp b/CPP/7zip/UI/GUI/UpdateGUI.cpp index a600a8b..90c5ee4 100644 --- a/CPP/7zip/UI/GUI/UpdateGUI.cpp +++ b/CPP/7zip/UI/GUI/UpdateGUI.cpp @@ -29,12 +29,34 @@ using namespace NFile; using namespace NDir; static const char * const kDefaultSfxModule = "7z.sfx"; -static const char * const kSFXExtension = "exe"; extern void AddMessageToString(UString &dest, const UString &src); UString HResultToMessage(HRESULT errorCode); +static void AddUniquePath(UStringVector &paths, const UString &path) +{ + FOR_VECTOR (i, paths) + if (paths[i] == path) + return; + paths.Add(path); +} + +static void PrepareWorkingDirForArchivePath(const UString &userArchivePath, CUpdateOptions &options) +{ + NWorkDir::CInfo workDirInfo; + workDirInfo.Load(); + options.WorkingDir.Empty(); + if (workDirInfo.Mode != NWorkDir::NMode::kCurrent) + { + FString fullPath; + MyGetFullPathName(us2fs(userArchivePath), fullPath); + FString namePart; + options.WorkingDir = GetWorkDir(workDirInfo, fullPath, namePart); + CreateComplexDir(options.WorkingDir); + } +} + class CThreadUpdating: public CProgressThreadVirt { HRESULT ProcessVirt() Z7_override; @@ -50,15 +72,67 @@ public: HRESULT CThreadUpdating::ProcessVirt() { - CUpdateErrorInfo ei; - HRESULT res = UpdateArchive(codecs, *formatIndices, *cmdArcPath, - *WildcardCensor, *Options, - ei, UpdateCallbackGUI, UpdateCallbackGUI, needSetPath); - FinalMessage.ErrorMessage.Message = ei.Message.Ptr(); - ErrorPaths = ei.FileNames; - if (res != S_OK) - return res; - return HRESULT_FROM_WIN32(ei.SystemError); + if (!Options->SeparateItemMode) + { + CUpdateErrorInfo ei; + HRESULT res = UpdateArchive(codecs, *formatIndices, *cmdArcPath, + *WildcardCensor, *Options, + ei, UpdateCallbackGUI, UpdateCallbackGUI, needSetPath); + FinalMessage.ErrorMessage.Message = ei.Message.Ptr(); + ErrorPaths = ei.FileNames; + if (res != S_OK) + return res; + return HRESULT_FROM_WIN32(ei.SystemError); + } + + if (Options->SeparateItemPaths.IsEmpty() || + Options->SeparateItemPaths.Size() != Options->SeparateItemArchivePaths.Size()) + { + FinalMessage.ErrorMessage.Message = L"Invalid per-item output configuration"; + return E_FAIL; + } + + NUpdateArchive::CActionSet actionSet = NUpdateArchive::k_ActionSet_Add; + if (!Options->Commands.IsEmpty()) + actionSet = Options->Commands.Front().ActionSet; + + FOR_VECTOR (i, Options->SeparateItemPaths) + { + CUpdateOptions options = *Options; + options.SeparateItemMode = false; + options.SeparateItemPaths.Clear(); + options.SeparateItemArchivePaths.Clear(); + options.Commands.Clear(); + + CUpdateArchiveCommand command; + command.ActionSet = actionSet; + command.UserArchivePath = Options->SeparateItemArchivePaths[i]; + options.Commands.Add(command); + + if (!options.SetArcPath(codecs, command.UserArchivePath)) + { + FinalMessage.ErrorMessage.Message = L"Update is not supported"; + return E_NOTIMPL; + } + + PrepareWorkingDirForArchivePath(command.UserArchivePath, options); + + NWildcard::CCensor censor; + censor.AddPreItem_NoWildcard(Options->SeparateItemPaths[i]); + + CUpdateErrorInfo ei; + HRESULT res = UpdateArchive(codecs, *formatIndices, command.UserArchivePath, + censor, options, + ei, UpdateCallbackGUI, UpdateCallbackGUI, false); + FinalMessage.ErrorMessage.Message = ei.Message.Ptr(); + ErrorPaths = ei.FileNames; + if (res != S_OK) + return res; + if (ei.ThereIsError()) + return ei.Get_HRESULT_Error(); + } + + return S_OK; } @@ -318,8 +392,8 @@ static HRESULT ShowDialog( CUpdateOptions &options, CUpdateCallbackGUI *callback, HWND hwndParent) { - if (options.Commands.Size() != 1) - throw "It must be one command"; + if (options.Commands.IsEmpty()) + options.SetActionCommand_Add(); /* FString currentDirPrefix; #ifndef UNDER_CE @@ -418,9 +492,34 @@ static HRESULT ShowDialog( return E_FAIL; } - // di.ArchiveName = options.ArchivePath.GetFinalPath(); - di.ArcPath = options.ArchivePath.GetPathWithoutExt(); + di.ArcPaths.Clear(); + FOR_VECTOR (i, options.Commands) + { + const CUpdateArchiveCommand &command = options.Commands[i]; + UString path = command.UserArchivePath; + if (path.IsEmpty()) + path = command.ArchivePath.GetFinalPath(); + if (!path.IsEmpty()) + di.ArcPaths.Add(path); + } + if (di.ArcPaths.IsEmpty()) + di.ArcPaths.Add(options.ArchivePath.GetFinalPath()); + di.ArcPath = di.ArcPaths.Front(); dialog.OriginalFileName = fs2us(fileInfo.Name); + di.ItemPaths.Clear(); + FOR_VECTOR (i, censor) + { + const NWildcard::CCensorPath &cp = censor[i]; + if (!cp.Include) + continue; + UString path = cp.Path; + path.Trim(); + if (!path.IsEmpty()) + AddUniquePath(di.ItemPaths, path); + } + di.ItemOutputItemPaths = options.SeparateItemPaths; + di.ItemArcPaths = options.SeparateItemArchivePaths; + di.SeparateItemArchives = options.SeparateItemMode; di.PathMode = options.PathMode; @@ -443,7 +542,7 @@ static HRESULT ShowDialog( di.KeepName = !oneFile; - NUpdateArchive::CActionSet &actionSet = options.Commands.Front().ActionSet; + NUpdateArchive::CActionSet actionSet = options.Commands.Front().ActionSet; { int index = FindActionSet(actionSet); @@ -513,30 +612,39 @@ static HRESULT ShowDialog( options.OpenShareForWrite = di.OpenShareForWrite; ParseAndAddPropertires(options.MethodMode.Properties, optionStrings); - if (di.SFXMode) - options.SfxMode = true; + options.SfxMode = di.SFXMode; options.MethodMode.Type = COpenType(); options.MethodMode.Type_Defined = true; options.MethodMode.Type.FormatIndex = di.FormatIndex; - options.ArchivePath.VolExtension = archiverInfo.GetMainExt(); - if (di.SFXMode) - options.ArchivePath.BaseExtension = kSFXExtension; - else - options.ArchivePath.BaseExtension = options.ArchivePath.VolExtension; - options.ArchivePath.ParseFromPath(di.ArcPath, k_ArcNameMode_Smart); - - NWorkDir::CInfo workDirInfo; - workDirInfo.Load(); - options.WorkingDir.Empty(); - if (workDirInfo.Mode != NWorkDir::NMode::kCurrent) + options.Commands.Clear(); + FOR_VECTOR (i, di.ArcPaths) { - FString fullPath; - MyGetFullPathName(us2fs(di.ArcPath), fullPath); - FString namePart; - options.WorkingDir = GetWorkDir(workDirInfo, fullPath, namePart); - CreateComplexDir(options.WorkingDir); + CUpdateArchiveCommand command; + command.ActionSet = actionSet; + command.UserArchivePath = di.ArcPaths[i]; + options.Commands.Add(command); } + if (options.Commands.IsEmpty()) + { + CUpdateArchiveCommand command; + command.ActionSet = actionSet; + command.UserArchivePath = di.ArcPath; + options.Commands.Add(command); + } + + // Paths returned by the dialog are explicit user choices. Re-parse them in + // smart mode so names like "Download" get one ".7z", while "Download.7z" + // doesn't get the archive extension appended twice. + options.ArcNameMode = k_ArcNameMode_Smart; + options.SeparateItemMode = di.SeparateItemArchives; + options.SeparateItemPaths = di.ItemOutputItemPaths; + options.SeparateItemArchivePaths = di.ItemArcPaths; + + if (!options.SetArcPath(codecs, options.Commands.Front().UserArchivePath)) + return E_NOTIMPL; + + PrepareWorkingDirForArchivePath(options.Commands.Front().UserArchivePath, options); return S_OK; } diff --git a/CPP/7zip/UI/GUI/makefile b/CPP/7zip/UI/GUI/makefile index b879a5d..336f104 100644 --- a/CPP/7zip/UI/GUI/makefile +++ b/CPP/7zip/UI/GUI/makefile @@ -147,3 +147,10 @@ C_OBJS = \ !include "../../Sort.mak" !include "../../7zip.mak" + +$O\CompressDialog.obj: CompressDialog.cpp CompressDialog.h CompressDialogRes.h +$O\ArchiveCommandLine.obj: ..\Common\ArchiveCommandLine.cpp ..\Common\Update.h +$O\Update.obj: ..\Common\Update.cpp ..\Common\Update.h +$O\UpdateCallbackGUI.obj: UpdateCallbackGUI.cpp ..\Common\Update.h UpdateCallbackGUI.h +$O\UpdateGUI.obj: UpdateGUI.cpp ..\Common\Update.h UpdateGUI.h CompressDialog.h CompressDialogRes.h +$O\resource.res: resource.rc resource2.rc CompressDialog.rc CompressDialogRes.h diff --git a/CPP/Build.mak b/CPP/Build.mak index 86cc2af..b06ff2a 100644 --- a/CPP/Build.mak +++ b/CPP/Build.mak @@ -237,7 +237,7 @@ $(PROGPATH): $O $O/asm $(OBJS) $(DEF_FILE) !IFNDEF NO_DEFAULT_RES $O\resource.res: $(*B).rc - rc $(RFLAGS) -fo$@ $** + rc $(RFLAGS) -fo$@ $(@B).rc !ENDIF $O\StdAfx.obj: $(*B).cpp $(COMPL_PCH) diff --git a/README_fernandoncidade.md b/README_fernandoncidade.md new file mode 100644 index 0000000..92a157b --- /dev/null +++ b/README_fernandoncidade.md @@ -0,0 +1,503 @@ +# 7-Zip Fork 2026.3.19.0 + +Fork customizado do 7-Zip, desenvolvido para consolidar melhorias funcionais, visuais e operacionais sobre a base do projeto original. + +Custom 7-Zip fork created to consolidate functional, visual, and operational improvements on top of the original project. + +Projeto upstream: [7-zip.org](https://7-zip.org) +Fork repository: + +## Languages + +- [Português](#portugues) +- [English](#english) + +--- + +## Português + +## Visão geral + +Este repositório reúne um fork do 7-Zip com foco em ampliar o fluxo de empacotamento pela GUI, personalizar a identidade do projeto, consolidar a geração do instalador com ativos locais e melhorar a manutenção do build e da desinstalação. + +## Objetivos deste fork + +- permitir múltiplos caminhos de saída em uma única operação de compressão +- permitir múltiplos grupos independentes na janela `Adicionar ao arquivo compactado` +- manter identidade própria do fork na janela `Sobre`, no instalador e no Painel de Controle +- usar `Lang\` na raiz como base principal de traduções +- gerar o instalador localmente sem depender do download do instalador oficial +- tornar o processo de desinstalação mais limpo e menos intrusivo + +## Implementações consolidadas + +### 1. Janela `Adicionar ao arquivo compactado` + +A interface de compressão foi ampliada para suportar cenários multi-saída e multi-item de forma nativa. + +#### Múltiplas saídas por grupo + +- o campo `Quantidade` define quantos caminhos de saída o grupo terá +- cada saída possui seu próprio campo `Arquivo` e seu próprio botão `...` +- o grupo suporta até 5 saídas +- apenas a primeira saída nasce preenchida com o caminho sugerido +- saídas adicionais passam a nascer vazias para preenchimento manual +- isso corrige o comportamento de replicar automaticamente `Download.7z` para todas as linhas extras + +#### Grupos independentes por item selecionado + +- quando o usuário seleciona mais de um arquivo ou pasta na GUI principal e clica em `Adicionar`, a janela de compressão abre um grupo por item selecionado +- cada grupo possui: + - identificação do item de origem + - campo `Quantidade` + - conjunto próprio de campos `Arquivo` +- exemplo: + - `D:\DOWNLOADS\Download` + - `D:\DOWNLOADS\teams-files-2026-03-19` + - a janela abre dois grupos independentes, um para cada item + +#### Layout dinâmico em tempo de execução + +- a altura da janela se ajusta conforme a quantidade de saídas habilitadas +- a interface deixa de reservar espaço fixo para cinco linhas o tempo todo +- ao aumentar ou reduzir a `Quantidade`, os controles são reposicionados dinamicamente + +#### Regras e validações + +- a primeira saída continua usando o caminho padrão sugerido +- saídas extras não são preenchidas automaticamente +- o sistema valida inconsistências entre caminhos antes de concluir a operação +- mudanças de formato e SFX atualizam os nomes de forma segura sem preencher linhas vazias artificialmente + +#### Correções de nomes e extensões + +- correção do problema `Download.7z.7z` +- correção do problema inicial `.7z..7z` +- correção do tratamento de diretórios com barra final +- atualização segura de extensões quando o formato muda +- preservação correta do nome-base do item selecionado + +### 2. Janela `Sobre o 7-Zip` + +A janela `Sobre o 7-Zip` foi customizada para manter as informações originais do projeto e incluir a identidade do fork. + +Ela passou a exibir: + +- bloco original do 7-Zip +- observação informando que esta versão refere-se a um fork customizado +- identificação de versão do fork +- data do fork +- copyright do desenvolvedor +- linha de repositório do fork +- linha final informando que o 7-Zip é software grátis + +Conteudo adicional exibido: + +- `Esta versão refere-se a um fork, customizado para atender as necessidades do desenvolvedor abaixo.` +- `7-Zip 2026.3.19.0 (x64)` +- `19/03/2026` +- `Copyright (c) 2026 Fernando Nillsson Cidade` +- `Repositório: https://github.com/fernandoncidade/7zip` +- `O 7-Zip é um software grátis` + +### 3. Idiomas e localização + +O sistema de idiomas foi reorganizado para adotar `Lang\` na raiz do repositório como fonte principal de verdade. + +#### Estrutura adotada + +- `Lang\` na raiz é a base canônica de idiomas +- as cópias usadas em runtime são sincronizadas para: + - `CPP\7zip\Bundles\Fm\x64\Lang` + - `CPP\7zip\UI\FileManager\x64\Lang` + - `dist\installer-full\stage\Lang` + +#### Entradas novas de tradução + +- `4021`: tradução de `Quantidade` +- bloco `2900`: extensão do conteúdo da janela `Sobre` + +#### Ajustes realizados + +- inclusão das novas traduções para a interface expandida +- normalização dos arquivos para evitar `Error in Lang file` +- sincronização automática dos diretórios de idioma usados em runtime +- alinhamento entre a janela `Sobre` e os arquivos `Lang` + +### 4. Pipeline de build e geração do instalador + +O fluxo de empacotamento foi reorganizado para produzir o instalador localmente, sem depender do download do instalador oficial. + +#### O que mudou + +- `dist\` passou a ser o diretório principal de saída +- o `stage` do instalador é montado a partir dos binários compilados localmente +- o processo deixou de depender de `Invoke-WebRequest` e da extração do instalador oficial +- arquivos estáticos passaram a ser obtidos do próprio repositório + +#### Fontes locais usadas + +- `InstallerAssets\7-zip.chm` +- `InstallerAssets\History.txt` +- `InstallerAssets\descript.ion` +- `DOC\License.txt` +- `DOC\readme.txt` +- `Lang\*` +- binários compilados localmente + +#### Saída final do instalador + +O build deste fork gera: + +- `dist\7z2026.3.19.0-x64.exe` + +### 5. Metadados no Painel de Controle + +Ao instalar o pacote gerado por este fork, a entrada do Windows passa a exibir: + +- Nome: `7-Zip 2026.3.19.0 (x64)` +- Editor: `Fernando Nillsson Cidade` +- Versão: `2026.3.19.0` + +Esses dados são gravados pelo instalador na chave: + +- `Software\Microsoft\Windows\CurrentVersion\Uninstall\7-Zip` + +### 6. Desinstalador + +O desinstalador foi ajustado para melhorar a remoção dos arquivos do produto, com foco especial nas DLLs usadas pelo shell. + +#### Melhorias aplicadas + +- limpeza explícita de arquivos temporários como `7-zip.dll.tmp` +- tentativa de elevação administrativa quando necessário +- descarregamento menos intrusivo da shell extension +- remoção mais segura de DLLs em uso +- preservação do Windows Explorer, evitando fechamento global das janelas e desaparecimento temporário da barra de tarefas + +#### Comportamento esperado + +- desinstalação mais limpa +- menor chance de sobras após remoção +- fallback para reinicialização apenas quando realmente necessário + +### 7. Robustez do build local + +Foram adicionados ajustes para reduzir problemas de build incremental inconsistente. + +#### Consolidações feitas + +- dependências explícitas entre `CompressDialog`, `UpdateGUI` e `Update.h` +- dependências explícitas de `AboutDialog.rc` e `AboutDialogRes.h` para regenerar `resource.res` +- espelhamento do `7zFM.exe` final para `CPP\7zip\UI\FileManager\x64\7zFM.exe` +- sincronização automática dos idiomas usados pelos binários locais + +Isso reduz cenários como: + +- alteração de recurso `.rc` sem refletir no binário +- relink com objetos antigos e cabeçalhos novos +- uso de diretórios `Lang` desatualizados ao lado do executável + +## Estrutura relevante do projeto + +- `CPP\` + - código-fonte principal do 7-Zip +- `C\Util\7zipInstall\` + - instalador +- `C\Util\7zipUninstall\` + - desinstalador +- `Lang\` + - base principal de idiomas +- `InstallerAssets\` + - arquivos estáticos do instalador +- `DOC\` + - documentação empacotada +- `dist\` + - saída final de build e distribuição + +## Como gerar o projeto + +O fluxo principal de build do instalador está no script: + +- `.vscode\7zip-installer-task.ps1` + +### Ações disponíveis + +- `build` + - compila os binários, monta o stage e gera o instalador +- `run` + - executa o instalador já gerado +- `build-run` + - compila e executa +- `sync-lang` + - normaliza e sincroniza os arquivos de idioma + +### Exemplo + +```powershell +pwsh -NoProfile -ExecutionPolicy Bypass -File .vscode\7zip-installer-task.ps1 build +``` + +## Resultado consolidado deste fork + +Este fork entrega um 7-Zip com: + +- múltiplas saídas por compressão +- grupos independentes por item selecionado +- interface adaptativa em tempo de execução +- identidade própria na janela `Sobre`, no instalador e no sistema +- suporte de idioma para os novos elementos da interface +- instalador gerado localmente +- metadados personalizados no Painel de Controle +- desinstalador mais seguro +- fluxo de build mais confiável para manutenção local + +## Créditos + +- Projeto original: Igor Pavlov +- Fork e customizações deste repositório: Fernando Nillsson Cidade + +--- + +## English + +## Overview + +This repository contains a 7-Zip fork focused on expanding the GUI compression workflow, customizing project identity, consolidating installer generation with local assets, and improving build and uninstall maintenance. + +## Goals of this fork + +- allow multiple output paths in a single compression operation +- allow multiple independent groups inside the `Add to Archive` window +- keep a custom fork identity in the About dialog, installer, and Control Panel entry +- use root `Lang\` as the main translation source +- generate the installer locally without depending on the official installer download +- make uninstall behavior cleaner and less intrusive + +## Consolidated implementations + +### 1. `Add to Archive` window + +The compression interface was extended to support multi-output and multi-item workflows natively. + +#### Multiple outputs per group + +- the `Quantity` field defines how many output paths a group will have +- each output has its own `Archive` field and its own `...` button +- each group supports up to 5 outputs +- only the first output is prefilled with the suggested default path +- additional outputs start blank and ready for manual input +- this fixes the previous behavior where `Download.7z` was duplicated to all extra lines + +#### Independent groups per selected item + +- when the user selects more than one file or folder in the main GUI and clicks `Add`, the compression window opens one group per selected item +- each group has: + - source item identification + - `Quantity` + - its own `Archive` fields +- example: + - `D:\DOWNLOADS\Download` + - `D:\DOWNLOADS\teams-files-2026-03-19` + - the dialog opens two independent groups, one for each item + +#### Dynamic runtime layout + +- the window height adapts to the number of enabled outputs +- the UI no longer reserves space for five fixed lines all the time +- when `Quantity` changes, controls are repositioned dynamically + +#### Rules and validations + +- the first output keeps using the suggested default path +- extra outputs are not auto-filled +- the system validates conflicting paths before completing the operation +- format and SFX changes update names safely without artificially filling empty lines + +#### Name and extension fixes + +- fixed `Download.7z.7z` +- fixed the initial `.7z..7z` +- fixed directory names with trailing separators +- safely updates extensions when format changes +- preserves the correct base name of the selected item + +### 2. `About 7-Zip` window + +The `About 7-Zip` dialog was customized to preserve the original project information while adding the fork identity. + +It now displays: + +- the original 7-Zip block +- a note explaining that this build is a customized fork +- fork version identification +- fork date +- developer copyright +- fork repository line +- a final line stating that 7-Zip is free software + +Additional fork content: + +- `This version refers to a fork customized to meet the needs of the developer below.` +- `7-Zip 2026.3.19.0 (x64)` +- `19/03/2026` +- `Copyright (c) 2026 Fernando Nillsson Cidade` +- `Repository: https://github.com/fernandoncidade/7zip` +- `7-Zip is free software` + +### 3. Languages and localization + +The language system was reorganized so that root `Lang\` becomes the primary source of truth. + +#### Adopted structure + +- root `Lang\` is the canonical language base +- runtime copies are synchronized to: + - `CPP\7zip\Bundles\Fm\x64\Lang` + - `CPP\7zip\UI\FileManager\x64\Lang` + - `dist\installer-full\stage\Lang` + +#### New translation entries + +- `4021`: translation for `Quantity` +- `2900` block: extended content for the About dialog + +#### Applied adjustments + +- added translations for the expanded interface +- normalized language files to avoid `Error in Lang file` +- synchronized runtime language directories automatically +- aligned the About dialog content with `Lang` resources + +### 4. Build pipeline and installer generation + +The packaging flow was reorganized to produce the installer locally without relying on the official installer download. + +#### What changed + +- `dist\` became the main output directory +- the installer `stage` is assembled from locally compiled binaries +- the process no longer depends on `Invoke-WebRequest` or extracting the official installer +- static files now come from the repository itself + +#### Local sources used by the installer + +- `InstallerAssets\7-zip.chm` +- `InstallerAssets\History.txt` +- `InstallerAssets\descript.ion` +- `DOC\License.txt` +- `DOC\readme.txt` +- `Lang\*` +- locally compiled binaries + +#### Final installer output + +This fork now generates: + +- `dist\7z2026.3.19.0-x64.exe` + +### 5. Control Panel metadata + +When the package generated by this fork is installed, the Windows entry shows: + +- Name: `7-Zip 2026.3.19.0 (x64)` +- Publisher: `Fernando Nillsson Cidade` +- Version: `2026.3.19.0` + +These values are written by the installer to: + +- `Software\Microsoft\Windows\CurrentVersion\Uninstall\7-Zip` + +### 6. Uninstaller + +The uninstaller was adjusted to improve product file removal, with special attention to shell-related DLLs. + +#### Applied improvements + +- explicit cleanup of temporary files such as `7-zip.dll.tmp` +- administrative elevation attempt when needed +- less intrusive shell extension unloading +- safer removal of DLLs in use +- preserves Windows Explorer behavior, avoiding global window shutdown and temporary taskbar disappearance + +#### Expected behavior + +- cleaner uninstall flow +- lower chance of leftovers after removal +- reboot fallback only when truly necessary + +### 7. Local build robustness + +Additional safeguards were added to reduce inconsistent incremental build issues. + +#### Consolidated changes + +- explicit dependencies between `CompressDialog`, `UpdateGUI`, and `Update.h` +- explicit dependencies from `AboutDialog.rc` and `AboutDialogRes.h` to regenerate `resource.res` +- mirroring of the final `7zFM.exe` to `CPP\7zip\UI\FileManager\x64\7zFM.exe` +- automatic synchronization of the language folders used by local test binaries + +This helps avoid cases such as: + +- `.rc` changes not being reflected in the final binary +- relinking old objects with new headers +- running local binaries with outdated `Lang` folders next to the executable + +## Relevant project structure + +- `CPP\` + - main 7-Zip source code +- `C\Util\7zipInstall\` + - installer +- `C\Util\7zipUninstall\` + - uninstaller +- `Lang\` + - main language base +- `InstallerAssets\` + - local installer assets +- `DOC\` + - packaged documentation +- `dist\` + - final build and distribution output + +## How to build + +The main installer build flow is handled by: + +- `.vscode\7zip-installer-task.ps1` + +### Available actions + +- `build` + - compiles binaries, assembles the stage, and generates the installer +- `run` + - runs the generated installer +- `build-run` + - builds and runs +- `sync-lang` + - normalizes and synchronizes language files + +### Example + +```powershell +pwsh -NoProfile -ExecutionPolicy Bypass -File .vscode\7zip-installer-task.ps1 build +``` + +## Consolidated result of this fork + +This fork provides a 7-Zip build with: + +- multiple outputs per compression operation +- independent groups per selected item +- runtime-adaptive UI behavior +- custom fork identity in the About dialog, installer, and system metadata +- language support for the new UI elements +- locally generated installer output +- customized Control Panel metadata +- safer uninstall behavior +- a more reliable local maintenance and build workflow + +## Credits + +- Original project: Igor Pavlov +- Fork and customizations in this repository: Fernando Nillsson Cidade diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..410a79c --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,689 @@ +# Release Notes: 7-Zip Fork + +**Version / Versão:** 2026.3.19.0 +**Author / Autor:** Fernando Nillsson Cidade + +## Languages + +- [Português](#português) +- [English](#english) + +--- + +## Português + +# Notas de Lançamento: 7-Zip Fork + +**Versão:** 2026.3.19.0 +**Autor:** Fernando Nillsson Cidade + +## Resumo técnico da release + +Esta release consolida alterações estruturais e funcionais relevantes sobre a base do 7-Zip, com foco principal na expansão do fluxo de compressão pela GUI, customização institucional do fork, reorganização do pipeline de build, sincronização local dos idiomas, geração local do instalador e fortalecimento do ciclo de desinstalação. + +Do ponto de vista arquitetural, a versão `2026.3.19.0` altera diretamente estes subsistemas: + +- interface de compressão da GUI +- backend de roteamento de operações de atualização e compressão +- diálogo `Sobre o 7-Zip` +- mecanismo de localização e sincronização de idiomas +- pipeline de build do File Manager e do instalador +- instalador e metadados persistidos no Windows +- desinstalador e limpeza de DLLs e artefatos temporários + +## Escopo da release + +### Componentes impactados + +- `CPP/7zip/UI/GUI/CompressDialog.cpp` +- `CPP/7zip/UI/GUI/CompressDialog.h` +- `CPP/7zip/UI/GUI/CompressDialog.rc` +- `CPP/7zip/UI/GUI/CompressDialogRes.h` +- `CPP/7zip/UI/GUI/UpdateGUI.cpp` +- `CPP/7zip/UI/Common/Update.h` +- `CPP/7zip/UI/FileManager/AboutDialog.cpp` +- `CPP/7zip/UI/FileManager/AboutDialog.rc` +- `CPP/7zip/UI/FileManager/AboutDialogRes.h` +- `CPP/7zip/UI/FileManager/LangUtils.cpp` +- `C/Util/7zipInstall/7zipInstall.c` +- `C/Util/7zipUninstall/7zipUninstall.c` +- `CPP/7zip/Bundles/Fm/makefile` +- `CPP/7zip/UI/GUI/makefile` +- `CPP/Build.mak` +- `.vscode/7zip-installer-task.ps1` +- `Lang/*` +- `InstallerAssets/*` + +## Alterações funcionais consolidadas + +### 1. Expansão da janela `Adicionar ao arquivo compactado` + +O fluxo de compressão da GUI foi ampliado para permitir cenários multi-item e multi-destino dentro do mesmo diálogo, sem exigir uma etapa externa de mapeamento manual. + +#### 1.1 Grupos independentes por item selecionado + +Quando múltiplos arquivos ou diretórios são selecionados na GUI principal e o usuário aciona `Adicionar`, o diálogo passa a construir um grupo lógico por item selecionado. Cada grupo mantém isolamento de estado e de widgets, incluindo: + +- identificação visual do item de origem +- controle individual da quantidade de saídas +- campos independentes de caminho de saída +- persistência local do nome base do item selecionado + +Essa alteração substitui a lógica anterior, que operava sobre uma única coleção agregada, por um modelo de grupos independentes associados diretamente ao item de origem. + +#### 1.2 Múltiplas saídas por grupo + +Cada grupo suporta até 5 saídas de empacotamento. Para isso, a camada de recursos e a camada de diálogo foram alteradas para instanciar múltiplos campos `Arquivo` e múltiplos botões `...`, mantendo sincronização entre estado visual e estado interno. + +Comportamento consolidado: + +- a primeira saída do grupo é inicializada com o caminho padrão sugerido +- as saídas adicionais permanecem vazias até ação explícita do usuário +- mudanças em `Quantidade` redimensionam o conjunto efetivamente ativo de saídas +- a navegação por `...` atua sobre a linha correta do grupo correspondente + +Essa alteração elimina o comportamento anterior em que saídas adicionais herdavam indevidamente o mesmo caminho inicial do primeiro item. + +#### 1.3 Layout dinâmico em runtime + +O diálogo deixa de trabalhar com uma reserva fixa de espaço para cinco saídas sempre visíveis. A janela agora ajusta a disposição vertical dos controles conforme: + +- número de grupos carregados +- quantidade de saídas habilitadas em cada grupo +- necessidade de exibir ou ocultar linhas extras + +Na prática, houve reestruturação do cálculo de posicionamento, da altura do diálogo e da visibilidade dos widgets associados a cada linha de saída. + +#### 1.4 Correções de geração de nome e extensão + +O fluxo de composição do nome do arquivo compactado foi corrigido para resolver inconsistências de normalização de extensão, especialmente em cenários com nomes já terminados no sufixo do formato corrente. + +Problemas corrigidos: + +- `Download.7z.7z` +- `.7z..7z` +- nomes vazios derivados de diretórios com barra final +- reaplicação indevida de extensão quando o formato era alterado + +Também foi ajustada a lógica de atualização dos caminhos extras para que linhas vazias não passem a receber extensões automaticamente. + +### 2. Ajustes no backend de compressão + +O backend que prepara e dispara as operações de compressão foi expandido para receber o novo modelo de saídas por grupo e por item. + +#### 2.1 Propagação de estado entre GUI e camada de atualização + +O conjunto de opções transportado entre o diálogo e o backend foi estendido para suportar: + +- múltiplos caminhos por grupo +- associação entre item selecionado e saídas correspondentes +- preservação do comportamento legado quando a operação continua sendo simples + +Os principais pontos de integração dessa consolidação foram: + +- `CPP/7zip/UI/GUI/UpdateGUI.cpp` +- `CPP/7zip/UI/Common/Update.h` + +#### 2.2 Correção de reaplicação indevida de extensão + +O fluxo do File Manager que originalmente entrava com modo de nome automático passou a respeitar corretamente caminhos já explicitados na GUI, evitando dupla reaplicação de extensão em operações confirmadas pelo usuário. + +### 3. Customização da janela `Sobre o 7-Zip` + +O diálogo `Sobre o 7-Zip` foi expandido para preservar o bloco institucional original do projeto e adicionar um bloco institucional do fork, com identidade própria do mantenedor. + +#### 3.1 Conteúdo adicional incorporado + +O diálogo agora incorpora: + +- observação de que a build refere-se a um fork +- identificação da versão do fork +- data de referência do fork +- copyright específico do autor +- link para o repositório do fork +- manutenção da linha informando que o 7-Zip é software grátis + +#### 3.2 Integração com recursos e runtime + +As alterações envolveram: + +- aumento e reorganização do layout do recurso `.rc` +- novos controles dedicados no diálogo +- resolução de textos por idioma +- fallback seguro para strings padrão quando necessário +- tratamento do link do repositório como ação clicável + +### 4. Sistema de idiomas e localização + +O fork consolida `Lang/` na raiz do repositório como a fonte principal de verdade dos arquivos de idioma utilizados na GUI, no File Manager local e no stage do instalador. + +#### 4.1 Novas entradas de idioma + +Foram incorporadas novas chaves e novos blocos de tradução para suportar: + +- o rótulo `Quantidade` +- o conteúdo expandido da janela `Sobre` +- a linha de repositório do fork + +Na prática, o rótulo `Quantidade` foi associado ao ID `4021`, enquanto o bloco `2900` passou a acomodar a expansão das informações exibidas no diálogo `Sobre`. + +#### 4.2 Normalização dos arquivos de idioma + +Foi implementado um fluxo de normalização para evitar que a aba `Language` da GUI acuse erros como `Error in Lang file`. + +Os principais cuidados adotados foram: + +- inserção ordenada dos IDs +- remoção de duplicidades inválidas +- preservação da estrutura esperada pelo parser do 7-Zip +- sincronização automática entre raiz `Lang/` e diretórios de runtime + +#### 4.3 Cobertura ampliada de idiomas + +Os idiomas presentes no projeto passaram a receber suporte para os novos elementos da janela `Sobre` e para o rótulo `Quantidade`, com sincronização para: + +- `CPP/7zip/UI/FileManager/x64/Lang` +- `CPP/7zip/Bundles/Fm/x64/Lang` +- `dist/installer-full/stage/Lang` + +### 5. Reorganização do pipeline de build e do instalador + +O pipeline do instalador foi redesenhado para deixar de depender do download do instalador oficial como base de montagem. + +#### 5.1 Substituição do modelo baseado em download + +Antes, o fluxo de empacotamento dependia de: + +- download do instalador oficial +- extração de payload externo +- sobreposição posterior de binários locais + +Agora, o `stage` do instalador passa a ser montado localmente a partir de: + +- binários compilados no workspace +- ativos estáticos versionados no repositório +- base local de idiomas +- documentação local do projeto + +#### 5.2 Introdução de ativos locais do instalador + +Os arquivos antes obtidos da extração do instalador oficial foram movidos para o repositório, com uso direto a partir de: + +- `InstallerAssets/7-zip.chm` +- `InstallerAssets/History.txt` +- `InstallerAssets/descript.ion` + +#### 5.3 Saída final renomeada do instalador + +O artefato final gerado por este fork passa a ser: + +- `dist/7z2026.3.19.0-x64.exe` + +Isso substitui o nome padronizado anterior derivado da versão upstream. + +### 6. Personalização do instalador e do Painel de Controle + +O instalador foi ajustado para registrar metadados próprios do fork no Windows. + +#### 6.1 Metadados de registro alterados + +Ao instalar o produto, a chave de desinstalação passa a gravar: + +- `DisplayName = 7-Zip 2026.3.19.0 (x64)` +- `DisplayVersion = 2026.3.19.0` +- `Publisher = Fernando Nillsson Cidade` + +Esses dados são persistidos na entrada: + +- `Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\7-Zip` + +#### 6.2 Coerência entre binário, instalador e identidade visual + +A release alinha: + +- nome do instalador +- versão apresentada ao usuário +- identidade do fork na janela `Sobre` +- metadados exibidos pelo Painel de Controle + +### 7. Fortalecimento do desinstalador + +O subsistema de remoção foi endurecido para reduzir sobras pós-desinstalação e para evitar comportamento agressivo sobre o Windows Explorer. + +#### 7.1 Limpeza explícita de arquivos temporários + +O desinstalador passou a tratar de forma explícita sobras como: + +- `7-zip.dll.tmp` +- variantes temporárias correlatas + +#### 7.2 Elevação administrativa e remoção mais segura + +O fluxo agora tenta operar com privilégios apropriados quando necessário, evitando falhas silenciosas na remoção de arquivos instalados em diretórios protegidos do sistema. + +#### 7.3 Correção da estratégia de remoção de DLLs em uso + +Uma solução intermediária baseada em encerramento amplo de processos do Explorer mostrou-se intrusiva, pois podia fechar janelas do Explorador de Arquivos e afetar temporariamente a barra de tarefas. A implementação consolidada substitui esse comportamento por uma estratégia menos agressiva de descarregamento da shell extension, preservando o ambiente do usuário. + +Resultado esperado: + +- menos sobras de DLLs +- menor necessidade de reinicialização +- ausência do efeito colateral de fechar globalmente o Explorer + +### 8. Robustez de build incremental + +Foram incluídas correções de dependência para evitar composições inválidas entre objetos antigos e cabeçalhos novos, bem como para garantir a regeneração de recursos quando arquivos `.rc` são alterados. + +#### 8.1 Ajustes aplicados + +- dependências explícitas para módulos que consomem `CompressDialog.h` +- dependências explícitas para módulos que consomem `Update.h` +- dependências explícitas de `AboutDialog.rc` e `AboutDialogRes.h` para regeneração de `resource.res` +- sincronização automática das bases locais de idioma usadas pelos binários de teste + +#### 8.2 Benefícios práticos + +Essas alterações reduzem falhas como: + +- fechamento inesperado do `7zFM.exe` após mudança estrutural de diálogo +- binários relincados com recursos antigos +- runtime carregando idiomas desatualizados + +## Impacto técnico por subsistema + +### GUI de compressão + +- novo modelo de grupos por item +- novo modelo de múltiplas saídas por grupo +- layout recalculado em runtime +- correção de normalização de nomes e extensões + +### Backend de update e compressão + +- expansão da estrutura de opções +- repasse correto dos caminhos configurados pela GUI +- preservação de compatibilidade com fluxo simples + +### Recursos e localização + +- expansão de recursos `.rc` +- adição de novos IDs +- sincronização de idioma raiz para runtime +- normalização automática dos arquivos de tradução + +### Instalador + +- geração local do `stage` +- uso de ativos versionados no próprio repositório +- nome final do instalador alinhado à identidade do fork +- metadados do produto customizados no Windows + +### Desinstalador + +- limpeza reforçada de arquivos temporários +- remoção mais segura de DLLs +- comportamento menos intrusivo sobre shell e Explorer + +## Artefatos relevantes desta release + +- `CPP/7zip/UI/FileManager/x64/7zFM.exe` +- `CPP/7zip/Bundles/Fm/x64/7zFM.exe` +- `dist/7z2026.3.19.0-x64.exe` +- `Lang/*` +- `InstallerAssets/*` + +## Compatibilidade e observações operacionais + +- a base upstream do 7-Zip foi preservada, com extensões direcionadas principalmente ao File Manager e ao pipeline local de build e instalação +- a nova lógica de compressão afeta principalmente o fluxo acionado pela GUI `Adicionar ao arquivo compactado` +- a coerência visual e institucional do fork depende da sincronização correta entre recursos, idiomas e binários locais +- a normalização de `Lang/` deve continuar sendo executada pelo script de build para evitar regressões na aba `Language` + +## Resultado consolidado + +A release `7-Zip Fork 2026.3.19.0` transforma o projeto em uma variante operacionalmente mais orientada a fluxos avançados de empacotamento, com suporte nativo a múltiplos destinos por item, identidade institucional do fork, pipeline de distribuição autocontido, cobertura de localização ampliada e ciclo de instalação e desinstalação mais controlado. + +## Autoria + +**Fernando Nillsson Cidade** + +--- + +## English + +# Release Notes: 7-Zip Fork + +**Version:** 2026.3.19.0 +**Author:** Fernando Nillsson Cidade + +## Technical release summary + +This release consolidates a set of structural and functional changes on top of the 7-Zip codebase, with primary focus on expanding the GUI compression workflow, establishing fork-specific product identity, reorganizing the build pipeline, synchronizing local language assets, producing the installer locally, and strengthening the uninstall lifecycle. + +From an architectural perspective, version `2026.3.19.0` directly affects these subsystems: + +- GUI compression interface +- update and compression backend routing +- `About 7-Zip` dialog +- language lookup and synchronization mechanism +- File Manager and installer build pipeline +- installer and Windows product metadata +- uninstaller and cleanup of DLLs and temporary artifacts + +## Release scope + +### Impacted components + +- `CPP/7zip/UI/GUI/CompressDialog.cpp` +- `CPP/7zip/UI/GUI/CompressDialog.h` +- `CPP/7zip/UI/GUI/CompressDialog.rc` +- `CPP/7zip/UI/GUI/CompressDialogRes.h` +- `CPP/7zip/UI/GUI/UpdateGUI.cpp` +- `CPP/7zip/UI/Common/Update.h` +- `CPP/7zip/UI/FileManager/AboutDialog.cpp` +- `CPP/7zip/UI/FileManager/AboutDialog.rc` +- `CPP/7zip/UI/FileManager/AboutDialogRes.h` +- `CPP/7zip/UI/FileManager/LangUtils.cpp` +- `C/Util/7zipInstall/7zipInstall.c` +- `C/Util/7zipUninstall/7zipUninstall.c` +- `CPP/7zip/Bundles/Fm/makefile` +- `CPP/7zip/UI/GUI/makefile` +- `CPP/Build.mak` +- `.vscode/7zip-installer-task.ps1` +- `Lang/*` +- `InstallerAssets/*` + +## Consolidated functional changes + +### 1. `Add to Archive` dialog expansion + +The GUI compression flow was expanded to support both multi-item and multi-destination scenarios within the same dialog, without requiring an external manual mapping step. + +#### 1.1 Independent groups per selected item + +When multiple files or directories are selected in the main GUI and the user triggers `Add`, the dialog now creates one logical group per selected item. Each group maintains isolated state and widgets, including: + +- visual identification of the source item +- per-group output quantity control +- independent output path fields +- local persistence of the selected item's base name + +This change replaces the previous model, which operated over a single aggregated collection, with a structure of independent groups directly associated with the source item. + +#### 1.2 Multiple outputs per group + +Each group supports up to 5 archive outputs. To enable this, both the resource layer and the dialog layer were expanded to instantiate multiple `Archive` fields and multiple `...` buttons while keeping the visual state synchronized with the internal state. + +Consolidated behavior: + +- the first group output is initialized with the suggested default path +- additional outputs remain blank until explicit user action +- changes to `Quantity` resize the active set of outputs +- the `...` browser acts on the correct line inside the corresponding group + +This removes the previous behavior where extra outputs incorrectly inherited the same initial path from the first item. + +#### 1.3 Dynamic runtime layout + +The dialog no longer reserves a fixed block of space for five always-visible outputs. Instead, the window now adapts its vertical layout according to: + +- number of loaded groups +- number of enabled outputs in each group +- whether extra lines need to be shown or hidden + +In practical terms, this required restructuring control positioning, dialog height calculation, and visibility handling for widgets tied to each output line. + +#### 1.4 File name and extension generation fixes + +The archive name composition flow was corrected to resolve extension normalization issues, especially in cases where the user-provided name already ended with the current format suffix. + +Fixed issues: + +- `Download.7z.7z` +- `.7z..7z` +- empty names derived from directories with trailing separators +- incorrect extension reapplication when the format changed + +The extra-output update flow was also adjusted so that blank lines do not receive artificial extensions automatically. + +### 2. Compression backend adjustments + +The backend responsible for preparing and dispatching compression operations was expanded to accept the new model of outputs per group and per item. + +#### 2.1 State propagation between GUI and update layer + +The options structure transported between the dialog and the backend was extended to support: + +- multiple paths per group +- association between selected items and their corresponding outputs +- preservation of legacy behavior when the operation remains simple + +The main integration points for this consolidation were: + +- `CPP/7zip/UI/GUI/UpdateGUI.cpp` +- `CPP/7zip/UI/Common/Update.h` + +#### 2.2 Fix for incorrect extension reapplication + +The File Manager flow that originally entered with automatic archive-name mode was adjusted to respect paths already made explicit in the GUI, preventing double extension reapplication after user confirmation. + +### 3. `About 7-Zip` dialog customization + +The `About 7-Zip` dialog was expanded to preserve the original project block while adding a fork-specific institutional block with maintainer identity. + +#### 3.1 Additional content introduced + +The dialog now includes: + +- a note that the build refers to a fork +- fork version identification +- fork reference date +- author-specific copyright +- a link to the fork repository +- preservation of the line stating that 7-Zip is free software + +#### 3.2 Integration with resources and runtime + +The implementation involved: + +- resizing and reorganizing the `.rc` dialog layout +- adding dedicated dialog controls +- resolving texts through language resources +- safe fallback to default strings when needed +- handling the repository entry as a clickable action + +### 4. Language and localization system + +The fork consolidates root `Lang/` as the single source of truth for language files consumed by the GUI, the local File Manager runtime, and the installer stage. + +#### 4.1 New language entries + +New translation keys and blocks were added to support: + +- the `Quantity` label +- the expanded `About` dialog content +- the fork repository line + +In practice, the `Quantity` label was bound to ID `4021`, while block `2900` was extended to accommodate the additional information displayed by the `About` dialog. + +#### 4.2 Language file normalization + +A normalization flow was implemented to prevent the GUI `Language` tab from reporting errors such as `Error in Lang file`. + +Key safeguards included: + +- ordered insertion of IDs +- removal of invalid duplicates +- preservation of the structure expected by the 7-Zip parser +- automatic synchronization between root `Lang/` and runtime language directories + +#### 4.3 Expanded language coverage + +Existing project languages now support the new `About` dialog elements and the `Quantity` label, with synchronization to: + +- `CPP/7zip/UI/FileManager/x64/Lang` +- `CPP/7zip/Bundles/Fm/x64/Lang` +- `dist/installer-full/stage/Lang` + +### 5. Build pipeline and installer reorganization + +The installer pipeline was redesigned to remove the dependency on downloading the official installer as the assembly base. + +#### 5.1 Replacing the download-based model + +Previously, the packaging flow depended on: + +- downloading the official installer +- extracting an external payload +- overlaying local binaries afterwards + +Now, the installer `stage` is assembled locally from: + +- binaries compiled in the workspace +- static assets versioned in the repository +- local language assets +- local project documentation + +#### 5.2 Introduction of local installer assets + +Files previously obtained from the extracted official installer were moved into the repository and are now consumed directly from: + +- `InstallerAssets/7-zip.chm` +- `InstallerAssets/History.txt` +- `InstallerAssets/descript.ion` + +#### 5.3 Renamed final installer output + +The final artifact generated by this fork is now: + +- `dist/7z2026.3.19.0-x64.exe` + +This replaces the previous default name derived from the upstream release number. + +### 6. Installer and Control Panel customization + +The installer was adjusted to persist fork-specific product metadata in Windows. + +#### 6.1 Modified registry metadata + +When the product is installed, the uninstall key now writes: + +- `DisplayName = 7-Zip 2026.3.19.0 (x64)` +- `DisplayVersion = 2026.3.19.0` +- `Publisher = Fernando Nillsson Cidade` + +These values are stored at: + +- `Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\7-Zip` + +#### 6.2 Consistency across binary, installer, and visual identity + +This release aligns: + +- installer file name +- user-facing version string +- fork identity shown in the `About` dialog +- metadata displayed in Windows Control Panel + +### 7. Uninstaller hardening + +The removal subsystem was reinforced to reduce uninstall leftovers while avoiding aggressive behavior against Windows Explorer. + +#### 7.1 Explicit cleanup of temporary files + +The uninstaller now explicitly handles leftovers such as: + +- `7-zip.dll.tmp` +- related temporary variants + +#### 7.2 Administrative elevation and safer removal + +The flow now attempts to operate with proper privileges when needed, avoiding silent failures while removing files installed under protected system directories. + +#### 7.3 Fix for the DLL-in-use removal strategy + +An intermediate solution based on broad Explorer process termination proved too intrusive because it could close File Explorer windows and temporarily affect the taskbar. The consolidated implementation replaces that behavior with a less aggressive shell-extension unload strategy that preserves the user environment. + +Expected result: + +- fewer leftover DLLs +- lower need for reboot fallback +- no side effect of globally closing Explorer + +### 8. Incremental build robustness + +Dependency corrections were added to avoid invalid combinations of old object files with newer headers and to guarantee resource regeneration whenever `.rc` files change. + +#### 8.1 Applied adjustments + +- explicit dependencies for modules consuming `CompressDialog.h` +- explicit dependencies for modules consuming `Update.h` +- explicit dependencies from `AboutDialog.rc` and `AboutDialogRes.h` to regenerate `resource.res` +- automatic synchronization of local language bases used by test binaries + +#### 8.2 Practical benefits + +These changes reduce issues such as: + +- unexpected `7zFM.exe` termination after structural dialog changes +- relinking binaries with outdated resources +- runtime loading stale language files + +## Technical impact by subsystem + +### Compression GUI + +- new per-item grouping model +- new multiple-output-per-group model +- runtime-recalculated layout +- file name and extension normalization fixes + +### Update and compression backend + +- expanded options structure +- proper propagation of GUI-configured output paths +- preserved compatibility with simple workflows + +### Resources and localization + +- expanded `.rc` resources +- new control/resource IDs +- synchronization from root language base to runtime +- automatic normalization of translation files + +### Installer + +- local stage generation +- use of repository-versioned assets +- final installer name aligned with fork identity +- custom product metadata in Windows + +### Uninstaller + +- stronger temporary-file cleanup +- safer DLL removal flow +- less intrusive shell and Explorer behavior + +## Relevant artifacts in this release + +- `CPP/7zip/UI/FileManager/x64/7zFM.exe` +- `CPP/7zip/Bundles/Fm/x64/7zFM.exe` +- `dist/7z2026.3.19.0-x64.exe` +- `Lang/*` +- `InstallerAssets/*` + +## Compatibility and operational notes + +- the upstream 7-Zip base was preserved, with extensions focused mainly on the File Manager and the local build/install pipeline +- the new compression logic primarily affects the GUI path driven by `Add to Archive` +- visual and institutional consistency of the fork depends on correct synchronization between resources, languages, and local binaries +- `Lang/` normalization should continue to be executed by the build script to avoid regressions in the `Language` tab + +## Consolidated result + +Release `7-Zip Fork 2026.3.19.0` turns the project into a variant more strongly oriented toward advanced packaging workflows, with native support for multiple output destinations per item, fork-specific product identity, a self-contained distribution pipeline, expanded localization coverage, and a more controlled installation and uninstallation cycle. + +## Authorship + +**Fernando Nillsson Cidade** From 1eca44e7a7314c0624dd5ff57d4cd482bfdb930d Mon Sep 17 00:00:00 2001 From: Fernando Nillsson Cidade <128842267+fernandoncidade@users.noreply.github.com> Date: Sun, 22 Mar 2026 01:36:40 -0300 Subject: [PATCH 2/3] v2026.3.22.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit English This patch addresses the review feedback around Windows installer metadata and update-path handling, and removes a partially implemented UI surface that duplicated the existing multi-output path controls. Installer / ARP metadata - The installer previously wrote fork-specific hardcoded values into the Windows Uninstall registry entry (`DisplayName`, `DisplayVersion`, `Publisher`). - That created drift between the installed product metadata and the actual upstream 7-Zip version/author macros used elsewhere in the tree. - The patch removes the hardcoded fork values and derives the registry values from the existing 7-Zip definitions: - `DisplayName` now uses the existing versioned product string (`7-Zip 26.00 (x64)` on 64-bit builds). - `DisplayVersion` now comes from `MY_VERSION`. - `Publisher` now comes from `MY_AUTHOR_NAME`. - This makes the Add/Remove Programs entry consistent with the rest of the build metadata and removes the need for manual source edits on each branch-specific release. Compression dialog cleanup - The `Paths...` button and its dedicated dialog were not part of the stable workflow. - At this point the main compression dialog already exposes the supported multi-output behavior through the output-count selector plus the existing archive path rows and browse buttons. - Keeping a second path-editing dialog in parallel would add maintenance overhead and duplicate UI/state management without providing new functionality. - For that reason, this patch removes the incomplete/duplicate surface entirely: - the `Paths...` button was removed from the dialog resource, - the handler declaration/definition was removed, - the auxiliary `COutputArcPathsDialog` class was removed, - the associated resource IDs and helper parsing/serialization code were removed. - Functional capability is unchanged: users still configure multiple output targets through the existing archive path controls already present in the compression dialog. Update path deduplication - In `UpdateGUI.cpp`, `AddUniquePath()` previously compared paths with a case-sensitive equality check. - On Windows, this could fail to deduplicate logically identical paths that differ only by casing. - The comparison is now case-insensitive (`IsEqualTo_NoCase()`), which matches expected Windows path behavior more closely and prevents duplicate item entries in the update/compression path collection stage. - This is intentionally a minimal fix: it addresses case-only duplication without changing the broader path normalization rules. Working directory safety - `PrepareWorkingDirForArchivePath()` previously ignored the return value of `MyGetFullPathName()`. - If full-path resolution failed, the code could continue with an invalid or empty path and derive an incorrect working directory. - The function now exits early when `MyGetFullPathName()` fails, leaving `options.WorkingDir` empty. - This is a conservative change: the downstream update flow already has fallback behavior for an empty working directory, so the patch avoids propagating bad path state without altering the existing fallback model. Header consistency / non-functional cleanup - `LangUtils.h` now provides a non-`Z7_LANG` fallback declaration for `LangString_OnlyFromLangFile()`. - This is not a functional runtime change for normal localized builds, but it keeps the header interface consistent across configurations and avoids IDE/static-analysis mismatches where `Z7_LANG` is not visible to the parser. Behavioral impact - No archive format logic was changed. - No codec behavior was changed. - No new persistence format was introduced. - The only runtime behavior changes are: - correct Windows ARP metadata, - removal of dead/duplicate UI, - case-insensitive deduplication of update input paths, - safer handling of full-path resolution failure before working directory derivation. Validation - The change set was validated with targeted code search and diff review to ensure the removed dialog/button plumbing no longer has remaining references. - `git diff --check` was also run on the touched files. - A full build/test pass has not yet been run in this environment. Portuguese Este patch responde ao feedback de revisão relacionado aos metadados do instalador no Windows e ao tratamento de caminhos no fluxo de atualização, além de remover uma superfície de UI parcialmente implementada que duplicava os controles já existentes para múltiplos caminhos de saída. Instalador / metadados do item no Windows - O instalador estava gravando valores hardcoded e específicos do fork na chave de desinstalação do Windows (`DisplayName`, `DisplayVersion`, `Publisher`). - Isso criava divergência entre os metadados exibidos em “Aplicativos instalados / Programas e Recursos” e as macros oficiais de versão/autoria já utilizadas no restante do código-fonte. - O patch remove esses valores fixos do fork e passa a derivar os valores diretamente das definições já existentes do 7-Zip: - `DisplayName` agora usa a string versionada já existente do produto (`7-Zip 26.00 (x64)` em builds 64-bit). - `DisplayVersion` agora vem de `MY_VERSION`. - `Publisher` agora vem de `MY_AUTHOR_NAME`. - Com isso, a entrada instalada no Windows fica consistente com os demais metadados do build e deixa de exigir edição manual do código a cada release da branch. Limpeza do diálogo de compressão - O botão `Paths...` e a dialog auxiliar associada não faziam parte de um fluxo estável/finalizado. - Neste momento, a janela principal de compressão já expõe o comportamento suportado de múltiplas saídas por meio do seletor de quantidade de saídas, dos campos de caminho de arquivo e dos botões de browse já existentes. - Manter uma segunda dialog para edição textual desses caminhos aumentaria o custo de manutenção e duplicaria a lógica de estado/UI sem adicionar capacidade funcional nova. - Por esse motivo, este patch remove completamente essa superfície incompleta/duplicada: - o botão `Paths...` foi removido do recurso da dialog, - a declaração e a implementação do handler foram removidas, - a classe auxiliar `COutputArcPathsDialog` foi removida, - os IDs de recurso associados e os helpers internos de parsing/serialização dessa dialog também foram removidos. - A capacidade funcional permanece a mesma: o usuário continua podendo configurar múltiplos destinos pelos controles de caminhos de saída já existentes na própria janela de compressão. Deduplicação de caminhos no fluxo de atualização - Em `UpdateGUI.cpp`, a função `AddUniquePath()` comparava caminhos usando igualdade case-sensitive. - Em Windows, isso podia falhar em deduplicar caminhos logicamente idênticos que diferiam apenas por maiúsculas/minúsculas. - A comparação agora é case-insensitive (`IsEqualTo_NoCase()`), o que se alinha melhor ao comportamento esperado para caminhos no Windows e evita entradas duplicadas durante a coleta dos caminhos de itens no fluxo de atualização/compressão. - A alteração foi mantida propositalmente mínima: ela corrige a duplicação por diferença de casing sem alterar as regras mais amplas de normalização de caminhos. Segurança na preparação do diretório de trabalho - `PrepareWorkingDirForArchivePath()` ignorava o valor de retorno de `MyGetFullPathName()`. - Se a resolução para caminho absoluto falhasse, o código poderia continuar com um caminho inválido ou vazio e, a partir disso, derivar um diretório de trabalho incorreto. - Agora a função retorna imediatamente quando `MyGetFullPathName()` falha, deixando `options.WorkingDir` vazio. - Essa mudança é conservadora: o fluxo subsequente de atualização já possui fallback quando o diretório de trabalho está vazio, então o patch evita propagar um estado inconsistente sem alterar o modelo de fallback já existente. Consistência de headers / limpeza não funcional - `LangUtils.h` agora fornece um fallback para `LangString_OnlyFromLangFile()` também quando `Z7_LANG` não está definido. - Isso não altera o comportamento funcional em builds localizados normais, mas mantém a interface do header consistente entre configurações e evita divergências em IDEs/analisadores estáticos quando `Z7_LANG` não está visível para o parser. Impacto comportamental - Nenhuma lógica de formato de arquivo foi alterada. - Nenhum comportamento de codec foi alterado. - Nenhum novo formato de persistência foi introduzido. - As únicas mudanças observáveis em runtime são: - correção dos metadados exibidos pelo Windows para a instalação, - remoção de uma UI morta/duplicada, - deduplicação case-insensitive de caminhos no fluxo de atualização, - tratamento mais seguro de falha ao resolver caminho absoluto antes de derivar o diretório de trabalho. Validação - O conjunto de alterações foi validado com buscas direcionadas no código e revisão de diff para garantir que a plumbing removida da dialog/botão não deixou referências residuais. - Também foi executado `git diff --check` nos arquivos alterados. - Ainda não foi executado um ciclo completo de build/testes neste ambiente. --- .gitattributes | 2 - .gitignore | 4 - C/Util/7zipInstall/7zipInstall.c | 11 +- CPP/7zip/UI/FileManager/LangUtils.h | 1 + CPP/7zip/UI/GUI/CompressDialog.cpp | 327 ------------------ CPP/7zip/UI/GUI/CompressDialog.h | 32 -- CPP/7zip/UI/GUI/CompressDialog.rc | 23 -- CPP/7zip/UI/GUI/CompressDialogRes.h | 5 - CPP/7zip/UI/GUI/UpdateGUI.cpp | 5 +- README.md | 2 - README_fernandoncidade.md | 503 ---------------------------- 11 files changed, 9 insertions(+), 906 deletions(-) delete mode 100644 .gitattributes delete mode 100644 .gitignore delete mode 100644 README.md delete mode 100644 README_fernandoncidade.md diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index dfe0770..0000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a116a3b..0000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/.vscode/ -/dist/ -/InstallerAssets/ -/Lang/ \ No newline at end of file diff --git a/C/Util/7zipInstall/7zipInstall.c b/C/Util/7zipInstall/7zipInstall.c index 0501b07..d7733e1 100644 --- a/C/Util/7zipInstall/7zipInstall.c +++ b/C/Util/7zipInstall/7zipInstall.c @@ -59,9 +59,8 @@ typedef enum { static LPCSTR const k_7zip = "7-Zip"; static LPCWSTR const k_Reg_Software_7zip = L"Software\\7-Zip"; -static LPCWSTR const k_Fork_DisplayName = L"7-Zip 2026.3.19.0 (x64)"; -static LPCWSTR const k_Fork_DisplayVersion = L"2026.3.19.0"; -static LPCWSTR const k_Fork_Publisher = L"Fernando Nillsson Cidade"; +static LPCWSTR const k_7zip_DisplayVersion = LLL(MY_VERSION); +static LPCWSTR const k_7zip_Publisher = LLL(MY_AUTHOR_NAME); // #define Z7_64BIT_INSTALLER 1 @@ -946,8 +945,8 @@ static void WriteShellEx(void) LONG res = MyRegistry_CreateKey(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\7-Zip", &destKey); if (res == ERROR_SUCCESS) { - MyRegistry_SetString(destKey, L"DisplayName", k_Fork_DisplayName); - MyRegistry_SetString(destKey, L"DisplayVersion", k_Fork_DisplayVersion); + MyRegistry_SetString(destKey, L"DisplayName", k_7zip_with_Ver_str); + MyRegistry_SetString(destKey, L"DisplayVersion", k_7zip_DisplayVersion); MyRegistry_SetString(destKey, L"DisplayIcon", destPath); MyRegistry_SetString(destKey, L"InstallLocation", path); @@ -967,7 +966,7 @@ static void WriteShellEx(void) MyRegistry_SetDWORD(destKey, L"VersionMajor", MY_VER_MAJOR); MyRegistry_SetDWORD(destKey, L"VersionMinor", MY_VER_MINOR); - MyRegistry_SetString(destKey, L"Publisher", k_Fork_Publisher); + MyRegistry_SetString(destKey, L"Publisher", k_7zip_Publisher); // MyRegistry_SetString(destKey, L"HelpLink", L"http://www.7-zip.org/support.html"); // MyRegistry_SetString(destKey, L"URLInfoAbout", L"http://www.7-zip.org/"); diff --git a/CPP/7zip/UI/FileManager/LangUtils.h b/CPP/7zip/UI/FileManager/LangUtils.h index d53d270..9c96994 100644 --- a/CPP/7zip/UI/FileManager/LangUtils.h +++ b/CPP/7zip/UI/FileManager/LangUtils.h @@ -37,6 +37,7 @@ void LangString_OnlyFromLangFile(UInt32 langID, UString &dest); inline UString LangString(UInt32 langID) { return NWindows::MyLoadString(langID); } inline void LangString(UInt32 langID, UString &dest) { NWindows::MyLoadString(langID, dest); } inline void AddLangString(UString &s, UInt32 langID) { s += NWindows::MyLoadString(langID); } +inline void LangString_OnlyFromLangFile(UInt32, UString &dest) { dest.Empty(); } #endif diff --git a/CPP/7zip/UI/GUI/CompressDialog.cpp b/CPP/7zip/UI/GUI/CompressDialog.cpp index 514cfbc..7db697b 100644 --- a/CPP/7zip/UI/GUI/CompressDialog.cpp +++ b/CPP/7zip/UI/GUI/CompressDialog.cpp @@ -636,7 +636,6 @@ bool CCompressDialog::OnInit() m_OutputPathCount.AddString_SetItemData(s, (LPARAM)i); } - ShowItem_Bool(IDB_COMPRESS_OUTPUT_PATHS, false); ShowItem_Bool(IDX_COMPRESS_SEPARATE_ITEMS, false); if (IsMultiItemMode()) @@ -1413,7 +1412,6 @@ void CCompressDialog::UpdateItemOutputGroupControls(unsigned groupIndex) void CCompressDialog::UpdateSeparateItemModeControls() { ShowItem_Bool(IDX_COMPRESS_SEPARATE_ITEMS, false); - ShowItem_Bool(IDB_COMPRESS_OUTPUT_PATHS, false); RefreshArchivePathInfo(); } @@ -2195,12 +2193,6 @@ bool CCompressDialog::BrowseArchivePath(UString &path, bool allowFormatChange, b } -void CCompressDialog::OnButtonOutputPaths() -{ - return; -} - - void CCompressDialog::OnButtonSetArchivePath(unsigned index) { if (IsMultiItemMode()) @@ -4745,325 +4737,6 @@ void CCompressDialog::ShowOptionsString() } -static void OutputPathsToText(const UStringVector &paths, UString &s) -{ - s.Empty(); - FOR_VECTOR (i, paths) - { - if (i != 0) - s.Add_LF(); - s += paths[i]; - } -} - - -static void GetItemDisplayName(const UString &itemPath, UString &name) -{ - UString dirPrefix; - SplitPathToParts_Smart(itemPath, dirPrefix, name); - if (name.IsEmpty()) - name = ExtractFileNameFromPath(itemPath); - if (name.IsEmpty()) - name = itemPath; -} - - -static void BuildItemDisplayNames(const UStringVector &itemPaths, UStringVector &displayNames) -{ - UStringVector baseNames; - FOR_VECTOR (i, itemPaths) - { - UString name; - GetItemDisplayName(itemPaths[i], name); - baseNames.Add(name); - } - - displayNames.Clear(); - FOR_VECTOR (i, itemPaths) - { - bool needFullPath = false; - FOR_VECTOR (k, baseNames) - if (i != k && baseNames[i].IsEqualTo_NoCase(baseNames[k])) - { - needFullPath = true; - break; - } - displayNames.Add(needFullPath ? itemPaths[i] : baseNames[i]); - } -} - - -static bool ResolveItemDisplayName( - const UStringVector &availableItems, - const UStringVector &displayNames, - const UString &token, - UString &itemPath) -{ - FOR_VECTOR (i, availableItems) - if (token.IsEqualTo_NoCase(displayNames[i]) || - token.IsEqualTo_NoCase(availableItems[i])) - { - itemPath = availableItems[i]; - return true; - } - return false; -} - - -static void OutputItemArcPathsToText( - const UStringVector &owners, - const UStringVector &paths, - const UStringVector &availableItems, - UString &s) -{ - s.Empty(); - - UStringVector displayNames; - BuildItemDisplayNames(availableItems, displayNames); - - unsigned numPairs = owners.Size(); - if (numPairs > paths.Size()) - numPairs = paths.Size(); - - for (unsigned i = 0; i < numPairs; i++) - { - if (i != 0) - s.Add_LF(); - - UString owner = owners[i]; - FOR_VECTOR (k, availableItems) - if (availableItems[k] == owners[i]) - { - owner = displayNames[k]; - break; - } - - s += owner; - s += L'>'; - s += paths[i]; - } -} - - -static void TextToOutputPaths(const UString &text, UStringVector &paths) -{ - paths.Clear(); - - UString cur; - for (unsigned i = 0;; i++) - { - const wchar_t c = (i == text.Len()) ? 0 : text[i]; - if (c == '\r') - continue; - if (c == '\n' || c == 0) - { - cur.Trim(); - if (!cur.IsEmpty()) - AddUniqueString(paths, cur); - cur.Empty(); - if (c == 0) - break; - continue; - } - cur += c; - } -} - - -static bool TextToOutputItemArcPaths( - const UString &text, - const UStringVector &availableItems, - UStringVector &owners, - UStringVector &paths, - UString &errorMessage) -{ - owners.Clear(); - paths.Clear(); - errorMessage.Empty(); - - UStringVector displayNames; - BuildItemDisplayNames(availableItems, displayNames); - - UString cur; - unsigned lineNumber = 1; - for (unsigned i = 0;; i++) - { - const wchar_t c = (i == text.Len()) ? 0 : text[i]; - if (c == '\r') - continue; - if (c == '\n' || c == 0) - { - cur.Trim(); - if (!cur.IsEmpty()) - { - const int sep = cur.Find(L'>'); - if (sep < 0) - { - errorMessage = L"Line "; - errorMessage.Add_UInt32((UInt32)lineNumber); - errorMessage += L": use item>output path"; - return false; - } - - UString itemToken = cur.Left((unsigned)sep); - UString path = cur.Ptr(sep + 1); - itemToken.Trim(); - path.Trim(); - - if (!itemToken.IsEmpty() && itemToken.Back() == L'-') - { - itemToken.DeleteBack(); - itemToken.Trim(); - } - - if (!path.IsEmpty() && path.Back() == L';') - { - path.DeleteBack(); - path.Trim(); - } - - if (itemToken.Len() >= 2 && itemToken[0] == L'"' && itemToken.Back() == L'"') - { - itemToken.DeleteBack(); - itemToken.DeleteFrontal(1); - itemToken.Trim(); - } - - if (path.Len() >= 2 && path[0] == L'"' && path.Back() == L'"') - { - path.DeleteBack(); - path.DeleteFrontal(1); - path.Trim(); - } - - if (itemToken.IsEmpty() || path.IsEmpty()) - { - errorMessage = L"Line "; - errorMessage.Add_UInt32((UInt32)lineNumber); - errorMessage += L": use item>output path"; - return false; - } - - UString itemPath; - if (!ResolveItemDisplayName(availableItems, displayNames, itemToken, itemPath)) - { - errorMessage = L"Line "; - errorMessage.Add_UInt32((UInt32)lineNumber); - errorMessage += L": unknown selected item: "; - errorMessage += itemToken; - return false; - } - - owners.Add(itemPath); - paths.Add(path); - } - - cur.Empty(); - if (c == 0) - break; - lineNumber++; - continue; - } - cur += c; - } - - return true; -} - - -bool COutputArcPathsDialog::OnInit() -{ - _pathsEdit.Attach(GetItem(IDE_COMPRESS_OUTPUT_PATHS)); - - if (PerItemMode) - { - UString info = L"Use item>output path, one mapping per line:"; - SetItemText(IDT_COMPRESS_OUTPUT_PATHS_INFO, info); - ShowItem_Bool(IDB_COMPRESS_OUTPUT_PATHS_ADD, false); - } - - UString s; - if (PerItemMode) - OutputItemArcPathsToText(Owners, Paths, AvailableItems, s); - else - OutputPathsToText(Paths, s); - _pathsEdit.SetText(s); - - NormalizePosition(); - return CModalDialog::OnInit(); -} - - -void COutputArcPathsDialog::AddPathFromBrowse() -{ - UString currentText; - _pathsEdit.GetText(currentText); - UStringVector currentPaths; - TextToOutputPaths(currentText, currentPaths); - - UString path; - if (!currentPaths.IsEmpty()) - path = currentPaths.Back(); - else if (!Paths.IsEmpty()) - path = Paths.Back(); - else if (!cd->_outputArcPaths.IsEmpty()) - path = cd->_outputArcPaths.Back(); - else - cd->GetFinalPath_Smart(path); - - bool formatWasChanged; - if (!cd->BrowseArchivePath(path, false, formatWasChanged)) - return; - - UString s; - _pathsEdit.GetText(s); - s.TrimRight(); - if (!s.IsEmpty()) - s.Add_LF(); - s += path; - _pathsEdit.SetText(s); -} - - -bool COutputArcPathsDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) -{ - switch (buttonID) - { - case IDB_COMPRESS_OUTPUT_PATHS_ADD: - { - AddPathFromBrowse(); - return true; - } - } - return CModalDialog::OnButtonClicked(buttonID, buttonHWND); -} - - -void COutputArcPathsDialog::OnOK() -{ - UString s; - _pathsEdit.GetText(s); - if (PerItemMode) - { - UString errorMessage; - if (!TextToOutputItemArcPaths(s, AvailableItems, Owners, Paths, errorMessage)) - { - MessageBoxW(*this, errorMessage, L"7-Zip", MB_ICONERROR); - return; - } - } - else - TextToOutputPaths(s, Paths); - - if (Paths.IsEmpty()) - { - MessageBoxW(*this, L"Specify at least one output path", L"7-Zip", MB_ICONERROR); - return; - } - CModalDialog::OnOK(); -} - - // ---------- OPTIONS ---------- diff --git a/CPP/7zip/UI/GUI/CompressDialog.h b/CPP/7zip/UI/GUI/CompressDialog.h index 955c5c0..20a2a32 100644 --- a/CPP/7zip/UI/GUI/CompressDialog.h +++ b/CPP/7zip/UI/GUI/CompressDialog.h @@ -148,7 +148,6 @@ struct CBool1 class CCompressDialog: public NWindows::NControl::CModalDialog { public: - friend class COutputArcPathsDialog; CBool1 SymLinks; CBool1 HardLinks; CBool1 AltStreams; @@ -416,7 +415,6 @@ public: void EnableMultiCombo(unsigned id); void FormatChanged(bool isChanged); - void OnButtonOutputPaths(); void OnButtonSetArchivePath(unsigned index); void OnButtonSetArchive(); bool IsSFX(); @@ -463,36 +461,6 @@ public: }; -class COutputArcPathsDialog: public NWindows::NControl::CModalDialog -{ - CCompressDialog *cd; - NWindows::NControl::CEdit _pathsEdit; - - void AddPathFromBrowse(); - - virtual bool OnInit() Z7_override; - virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; - virtual void OnOK() Z7_override; - -public: - UStringVector Owners; - UStringVector Paths; - bool PerItemMode; - UStringVector AvailableItems; - - INT_PTR Create(HWND wndParent = NULL) - { - BIG_DIALOG_SIZE(320, 200); - return CModalDialog::Create(SIZED_DIALOG(IDD_COMPRESS_OUTPUT_PATHS), wndParent); - } - - COutputArcPathsDialog(CCompressDialog *cdLoc): - cd(cdLoc), - PerItemMode(false) - {} -}; - - class COptionsDialog: public NWindows::NControl::CModalDialog diff --git a/CPP/7zip/UI/GUI/CompressDialog.rc b/CPP/7zip/UI/GUI/CompressDialog.rc index eb11aba..89a0b02 100644 --- a/CPP/7zip/UI/GUI/CompressDialog.rc +++ b/CPP/7zip/UI/GUI/CompressDialog.rc @@ -63,7 +63,6 @@ BEGIN LTEXT "", IDT_COMPRESS_ARCHIVE_FOLDER, m + xArcFolderOffs, m, xc - xArcFolderOffs, 8 LTEXT "&Count:", IDT_COMPRESS_OUTPUT_PATHS_NUM, m, 20, xArcFolderOffs, 8 COMBOBOX IDC_COMPRESS_OUTPUT_PATHS_NUM, m + xArcFolderOffs, 18, 56, 80, MY_COMBO - PUSHBUTTON "Pa&ths...", IDB_COMPRESS_OUTPUT_PATHS, m + xArcFolderOffs + 62, 18, 54, bys CONTROL "Map selected items to output archives", IDX_COMPRESS_SEPARATE_ITEMS, MY_CHECKBOX, m + xArcFolderOffs + 122, 20, xc - xArcFolderOffs - 122, 10 @@ -167,28 +166,6 @@ BEGIN PUSHBUTTON "Help", IDHELP, bx1, by, bxs, bys END - -#define xcPaths 320 -#define ycPaths 200 -#define xsPaths (xcPaths + m + m) -#define ysPaths (ycPaths + m + m) -#define bxPaths1 (xsPaths - m - bxs) -#define bxPaths2 (bxPaths1 - m - bxs) -#define byPaths (ysPaths - m - bys) - -IDD_COMPRESS_OUTPUT_PATHS DIALOG 0, 0, xsPaths, ysPaths MY_MODAL_DIALOG_STYLE MY_FONT -CAPTION "Output Paths" -BEGIN - LTEXT "One archive output path per line:", IDT_COMPRESS_OUTPUT_PATHS_INFO, m, m, xcPaths, 8 - EDITTEXT IDE_COMPRESS_OUTPUT_PATHS, m, 20, xcPaths, ycPaths - bys - m - 24, - ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | WS_HSCROLL | ES_WANTRETURN - - PUSHBUTTON "&Add...", IDB_COMPRESS_OUTPUT_PATHS_ADD, m, byPaths, bxs, bys - DEFPUSHBUTTON "OK", IDOK, bxPaths2, byPaths, bxs, bys, WS_GROUP - PUSHBUTTON "Cancel", IDCANCEL, bxPaths1, byPaths, bxs, bys -END - - #ifdef UNDER_CE #undef m diff --git a/CPP/7zip/UI/GUI/CompressDialogRes.h b/CPP/7zip/UI/GUI/CompressDialogRes.h index 7e996b4..c90ce90 100644 --- a/CPP/7zip/UI/GUI/CompressDialogRes.h +++ b/CPP/7zip/UI/GUI/CompressDialogRes.h @@ -1,7 +1,6 @@ #define IDD_COMPRESS 4000 #define IDD_COMPRESS_2 14000 #define IDD_COMPRESS_OPTIONS 14001 -#define IDD_COMPRESS_OUTPUT_PATHS 14002 #define IDC_COMPRESS_ARCHIVE 100 #define IDB_COMPRESS_SET_ARCHIVE 101 @@ -23,14 +22,11 @@ #define IDG_COMPRESS_NTFS 115 #define IDC_COMPRESS_PATH_MODE 116 #define IDC_COMPRESS_MEM_USE 117 -#define IDB_COMPRESS_OUTPUT_PATHS 118 // #define IDC_COMPRESS_DICTIONARY2 118 #define IDE_COMPRESS_PASSWORD1 120 #define IDE_COMPRESS_PASSWORD2 121 #define IDC_COMPRESS_ENCRYPTION_METHOD 122 -#define IDE_COMPRESS_OUTPUT_PATHS 123 -#define IDB_COMPRESS_OUTPUT_PATHS_ADD 124 #define IDC_COMPRESS_OUTPUT_PATHS_NUM 125 #define IDT_COMPRESS_ARCHIVE_FOLDER 130 @@ -81,7 +77,6 @@ #define IDT_COMPRESS_MEMORY_DE 4018 #define IDX_COMPRESS_DEL 4019 -#define IDT_COMPRESS_OUTPUT_PATHS_INFO 4020 #define IDT_COMPRESS_OUTPUT_PATHS_NUM 4021 #define IDX_COMPRESS_SEPARATE_ITEMS 4022 diff --git a/CPP/7zip/UI/GUI/UpdateGUI.cpp b/CPP/7zip/UI/GUI/UpdateGUI.cpp index 90c5ee4..74ca145 100644 --- a/CPP/7zip/UI/GUI/UpdateGUI.cpp +++ b/CPP/7zip/UI/GUI/UpdateGUI.cpp @@ -37,7 +37,7 @@ UString HResultToMessage(HRESULT errorCode); static void AddUniquePath(UStringVector &paths, const UString &path) { FOR_VECTOR (i, paths) - if (paths[i] == path) + if (path.IsEqualTo_NoCase(paths[i])) return; paths.Add(path); } @@ -50,7 +50,8 @@ static void PrepareWorkingDirForArchivePath(const UString &userArchivePath, CUpd if (workDirInfo.Mode != NWorkDir::NMode::kCurrent) { FString fullPath; - MyGetFullPathName(us2fs(userArchivePath), fullPath); + if (!MyGetFullPathName(us2fs(userArchivePath), fullPath)) + return; FString namePart; options.WorkingDir = GetWorkDir(workDirInfo, fullPath, namePart); CreateComplexDir(options.WorkingDir); diff --git a/README.md b/README.md deleted file mode 100644 index e1e4514..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# 7-Zip on GitHub -7-Zip website: [7-zip.org](https://7-zip.org) diff --git a/README_fernandoncidade.md b/README_fernandoncidade.md deleted file mode 100644 index 92a157b..0000000 --- a/README_fernandoncidade.md +++ /dev/null @@ -1,503 +0,0 @@ -# 7-Zip Fork 2026.3.19.0 - -Fork customizado do 7-Zip, desenvolvido para consolidar melhorias funcionais, visuais e operacionais sobre a base do projeto original. - -Custom 7-Zip fork created to consolidate functional, visual, and operational improvements on top of the original project. - -Projeto upstream: [7-zip.org](https://7-zip.org) -Fork repository: - -## Languages - -- [Português](#portugues) -- [English](#english) - ---- - -## Português - -## Visão geral - -Este repositório reúne um fork do 7-Zip com foco em ampliar o fluxo de empacotamento pela GUI, personalizar a identidade do projeto, consolidar a geração do instalador com ativos locais e melhorar a manutenção do build e da desinstalação. - -## Objetivos deste fork - -- permitir múltiplos caminhos de saída em uma única operação de compressão -- permitir múltiplos grupos independentes na janela `Adicionar ao arquivo compactado` -- manter identidade própria do fork na janela `Sobre`, no instalador e no Painel de Controle -- usar `Lang\` na raiz como base principal de traduções -- gerar o instalador localmente sem depender do download do instalador oficial -- tornar o processo de desinstalação mais limpo e menos intrusivo - -## Implementações consolidadas - -### 1. Janela `Adicionar ao arquivo compactado` - -A interface de compressão foi ampliada para suportar cenários multi-saída e multi-item de forma nativa. - -#### Múltiplas saídas por grupo - -- o campo `Quantidade` define quantos caminhos de saída o grupo terá -- cada saída possui seu próprio campo `Arquivo` e seu próprio botão `...` -- o grupo suporta até 5 saídas -- apenas a primeira saída nasce preenchida com o caminho sugerido -- saídas adicionais passam a nascer vazias para preenchimento manual -- isso corrige o comportamento de replicar automaticamente `Download.7z` para todas as linhas extras - -#### Grupos independentes por item selecionado - -- quando o usuário seleciona mais de um arquivo ou pasta na GUI principal e clica em `Adicionar`, a janela de compressão abre um grupo por item selecionado -- cada grupo possui: - - identificação do item de origem - - campo `Quantidade` - - conjunto próprio de campos `Arquivo` -- exemplo: - - `D:\DOWNLOADS\Download` - - `D:\DOWNLOADS\teams-files-2026-03-19` - - a janela abre dois grupos independentes, um para cada item - -#### Layout dinâmico em tempo de execução - -- a altura da janela se ajusta conforme a quantidade de saídas habilitadas -- a interface deixa de reservar espaço fixo para cinco linhas o tempo todo -- ao aumentar ou reduzir a `Quantidade`, os controles são reposicionados dinamicamente - -#### Regras e validações - -- a primeira saída continua usando o caminho padrão sugerido -- saídas extras não são preenchidas automaticamente -- o sistema valida inconsistências entre caminhos antes de concluir a operação -- mudanças de formato e SFX atualizam os nomes de forma segura sem preencher linhas vazias artificialmente - -#### Correções de nomes e extensões - -- correção do problema `Download.7z.7z` -- correção do problema inicial `.7z..7z` -- correção do tratamento de diretórios com barra final -- atualização segura de extensões quando o formato muda -- preservação correta do nome-base do item selecionado - -### 2. Janela `Sobre o 7-Zip` - -A janela `Sobre o 7-Zip` foi customizada para manter as informações originais do projeto e incluir a identidade do fork. - -Ela passou a exibir: - -- bloco original do 7-Zip -- observação informando que esta versão refere-se a um fork customizado -- identificação de versão do fork -- data do fork -- copyright do desenvolvedor -- linha de repositório do fork -- linha final informando que o 7-Zip é software grátis - -Conteudo adicional exibido: - -- `Esta versão refere-se a um fork, customizado para atender as necessidades do desenvolvedor abaixo.` -- `7-Zip 2026.3.19.0 (x64)` -- `19/03/2026` -- `Copyright (c) 2026 Fernando Nillsson Cidade` -- `Repositório: https://github.com/fernandoncidade/7zip` -- `O 7-Zip é um software grátis` - -### 3. Idiomas e localização - -O sistema de idiomas foi reorganizado para adotar `Lang\` na raiz do repositório como fonte principal de verdade. - -#### Estrutura adotada - -- `Lang\` na raiz é a base canônica de idiomas -- as cópias usadas em runtime são sincronizadas para: - - `CPP\7zip\Bundles\Fm\x64\Lang` - - `CPP\7zip\UI\FileManager\x64\Lang` - - `dist\installer-full\stage\Lang` - -#### Entradas novas de tradução - -- `4021`: tradução de `Quantidade` -- bloco `2900`: extensão do conteúdo da janela `Sobre` - -#### Ajustes realizados - -- inclusão das novas traduções para a interface expandida -- normalização dos arquivos para evitar `Error in Lang file` -- sincronização automática dos diretórios de idioma usados em runtime -- alinhamento entre a janela `Sobre` e os arquivos `Lang` - -### 4. Pipeline de build e geração do instalador - -O fluxo de empacotamento foi reorganizado para produzir o instalador localmente, sem depender do download do instalador oficial. - -#### O que mudou - -- `dist\` passou a ser o diretório principal de saída -- o `stage` do instalador é montado a partir dos binários compilados localmente -- o processo deixou de depender de `Invoke-WebRequest` e da extração do instalador oficial -- arquivos estáticos passaram a ser obtidos do próprio repositório - -#### Fontes locais usadas - -- `InstallerAssets\7-zip.chm` -- `InstallerAssets\History.txt` -- `InstallerAssets\descript.ion` -- `DOC\License.txt` -- `DOC\readme.txt` -- `Lang\*` -- binários compilados localmente - -#### Saída final do instalador - -O build deste fork gera: - -- `dist\7z2026.3.19.0-x64.exe` - -### 5. Metadados no Painel de Controle - -Ao instalar o pacote gerado por este fork, a entrada do Windows passa a exibir: - -- Nome: `7-Zip 2026.3.19.0 (x64)` -- Editor: `Fernando Nillsson Cidade` -- Versão: `2026.3.19.0` - -Esses dados são gravados pelo instalador na chave: - -- `Software\Microsoft\Windows\CurrentVersion\Uninstall\7-Zip` - -### 6. Desinstalador - -O desinstalador foi ajustado para melhorar a remoção dos arquivos do produto, com foco especial nas DLLs usadas pelo shell. - -#### Melhorias aplicadas - -- limpeza explícita de arquivos temporários como `7-zip.dll.tmp` -- tentativa de elevação administrativa quando necessário -- descarregamento menos intrusivo da shell extension -- remoção mais segura de DLLs em uso -- preservação do Windows Explorer, evitando fechamento global das janelas e desaparecimento temporário da barra de tarefas - -#### Comportamento esperado - -- desinstalação mais limpa -- menor chance de sobras após remoção -- fallback para reinicialização apenas quando realmente necessário - -### 7. Robustez do build local - -Foram adicionados ajustes para reduzir problemas de build incremental inconsistente. - -#### Consolidações feitas - -- dependências explícitas entre `CompressDialog`, `UpdateGUI` e `Update.h` -- dependências explícitas de `AboutDialog.rc` e `AboutDialogRes.h` para regenerar `resource.res` -- espelhamento do `7zFM.exe` final para `CPP\7zip\UI\FileManager\x64\7zFM.exe` -- sincronização automática dos idiomas usados pelos binários locais - -Isso reduz cenários como: - -- alteração de recurso `.rc` sem refletir no binário -- relink com objetos antigos e cabeçalhos novos -- uso de diretórios `Lang` desatualizados ao lado do executável - -## Estrutura relevante do projeto - -- `CPP\` - - código-fonte principal do 7-Zip -- `C\Util\7zipInstall\` - - instalador -- `C\Util\7zipUninstall\` - - desinstalador -- `Lang\` - - base principal de idiomas -- `InstallerAssets\` - - arquivos estáticos do instalador -- `DOC\` - - documentação empacotada -- `dist\` - - saída final de build e distribuição - -## Como gerar o projeto - -O fluxo principal de build do instalador está no script: - -- `.vscode\7zip-installer-task.ps1` - -### Ações disponíveis - -- `build` - - compila os binários, monta o stage e gera o instalador -- `run` - - executa o instalador já gerado -- `build-run` - - compila e executa -- `sync-lang` - - normaliza e sincroniza os arquivos de idioma - -### Exemplo - -```powershell -pwsh -NoProfile -ExecutionPolicy Bypass -File .vscode\7zip-installer-task.ps1 build -``` - -## Resultado consolidado deste fork - -Este fork entrega um 7-Zip com: - -- múltiplas saídas por compressão -- grupos independentes por item selecionado -- interface adaptativa em tempo de execução -- identidade própria na janela `Sobre`, no instalador e no sistema -- suporte de idioma para os novos elementos da interface -- instalador gerado localmente -- metadados personalizados no Painel de Controle -- desinstalador mais seguro -- fluxo de build mais confiável para manutenção local - -## Créditos - -- Projeto original: Igor Pavlov -- Fork e customizações deste repositório: Fernando Nillsson Cidade - ---- - -## English - -## Overview - -This repository contains a 7-Zip fork focused on expanding the GUI compression workflow, customizing project identity, consolidating installer generation with local assets, and improving build and uninstall maintenance. - -## Goals of this fork - -- allow multiple output paths in a single compression operation -- allow multiple independent groups inside the `Add to Archive` window -- keep a custom fork identity in the About dialog, installer, and Control Panel entry -- use root `Lang\` as the main translation source -- generate the installer locally without depending on the official installer download -- make uninstall behavior cleaner and less intrusive - -## Consolidated implementations - -### 1. `Add to Archive` window - -The compression interface was extended to support multi-output and multi-item workflows natively. - -#### Multiple outputs per group - -- the `Quantity` field defines how many output paths a group will have -- each output has its own `Archive` field and its own `...` button -- each group supports up to 5 outputs -- only the first output is prefilled with the suggested default path -- additional outputs start blank and ready for manual input -- this fixes the previous behavior where `Download.7z` was duplicated to all extra lines - -#### Independent groups per selected item - -- when the user selects more than one file or folder in the main GUI and clicks `Add`, the compression window opens one group per selected item -- each group has: - - source item identification - - `Quantity` - - its own `Archive` fields -- example: - - `D:\DOWNLOADS\Download` - - `D:\DOWNLOADS\teams-files-2026-03-19` - - the dialog opens two independent groups, one for each item - -#### Dynamic runtime layout - -- the window height adapts to the number of enabled outputs -- the UI no longer reserves space for five fixed lines all the time -- when `Quantity` changes, controls are repositioned dynamically - -#### Rules and validations - -- the first output keeps using the suggested default path -- extra outputs are not auto-filled -- the system validates conflicting paths before completing the operation -- format and SFX changes update names safely without artificially filling empty lines - -#### Name and extension fixes - -- fixed `Download.7z.7z` -- fixed the initial `.7z..7z` -- fixed directory names with trailing separators -- safely updates extensions when format changes -- preserves the correct base name of the selected item - -### 2. `About 7-Zip` window - -The `About 7-Zip` dialog was customized to preserve the original project information while adding the fork identity. - -It now displays: - -- the original 7-Zip block -- a note explaining that this build is a customized fork -- fork version identification -- fork date -- developer copyright -- fork repository line -- a final line stating that 7-Zip is free software - -Additional fork content: - -- `This version refers to a fork customized to meet the needs of the developer below.` -- `7-Zip 2026.3.19.0 (x64)` -- `19/03/2026` -- `Copyright (c) 2026 Fernando Nillsson Cidade` -- `Repository: https://github.com/fernandoncidade/7zip` -- `7-Zip is free software` - -### 3. Languages and localization - -The language system was reorganized so that root `Lang\` becomes the primary source of truth. - -#### Adopted structure - -- root `Lang\` is the canonical language base -- runtime copies are synchronized to: - - `CPP\7zip\Bundles\Fm\x64\Lang` - - `CPP\7zip\UI\FileManager\x64\Lang` - - `dist\installer-full\stage\Lang` - -#### New translation entries - -- `4021`: translation for `Quantity` -- `2900` block: extended content for the About dialog - -#### Applied adjustments - -- added translations for the expanded interface -- normalized language files to avoid `Error in Lang file` -- synchronized runtime language directories automatically -- aligned the About dialog content with `Lang` resources - -### 4. Build pipeline and installer generation - -The packaging flow was reorganized to produce the installer locally without relying on the official installer download. - -#### What changed - -- `dist\` became the main output directory -- the installer `stage` is assembled from locally compiled binaries -- the process no longer depends on `Invoke-WebRequest` or extracting the official installer -- static files now come from the repository itself - -#### Local sources used by the installer - -- `InstallerAssets\7-zip.chm` -- `InstallerAssets\History.txt` -- `InstallerAssets\descript.ion` -- `DOC\License.txt` -- `DOC\readme.txt` -- `Lang\*` -- locally compiled binaries - -#### Final installer output - -This fork now generates: - -- `dist\7z2026.3.19.0-x64.exe` - -### 5. Control Panel metadata - -When the package generated by this fork is installed, the Windows entry shows: - -- Name: `7-Zip 2026.3.19.0 (x64)` -- Publisher: `Fernando Nillsson Cidade` -- Version: `2026.3.19.0` - -These values are written by the installer to: - -- `Software\Microsoft\Windows\CurrentVersion\Uninstall\7-Zip` - -### 6. Uninstaller - -The uninstaller was adjusted to improve product file removal, with special attention to shell-related DLLs. - -#### Applied improvements - -- explicit cleanup of temporary files such as `7-zip.dll.tmp` -- administrative elevation attempt when needed -- less intrusive shell extension unloading -- safer removal of DLLs in use -- preserves Windows Explorer behavior, avoiding global window shutdown and temporary taskbar disappearance - -#### Expected behavior - -- cleaner uninstall flow -- lower chance of leftovers after removal -- reboot fallback only when truly necessary - -### 7. Local build robustness - -Additional safeguards were added to reduce inconsistent incremental build issues. - -#### Consolidated changes - -- explicit dependencies between `CompressDialog`, `UpdateGUI`, and `Update.h` -- explicit dependencies from `AboutDialog.rc` and `AboutDialogRes.h` to regenerate `resource.res` -- mirroring of the final `7zFM.exe` to `CPP\7zip\UI\FileManager\x64\7zFM.exe` -- automatic synchronization of the language folders used by local test binaries - -This helps avoid cases such as: - -- `.rc` changes not being reflected in the final binary -- relinking old objects with new headers -- running local binaries with outdated `Lang` folders next to the executable - -## Relevant project structure - -- `CPP\` - - main 7-Zip source code -- `C\Util\7zipInstall\` - - installer -- `C\Util\7zipUninstall\` - - uninstaller -- `Lang\` - - main language base -- `InstallerAssets\` - - local installer assets -- `DOC\` - - packaged documentation -- `dist\` - - final build and distribution output - -## How to build - -The main installer build flow is handled by: - -- `.vscode\7zip-installer-task.ps1` - -### Available actions - -- `build` - - compiles binaries, assembles the stage, and generates the installer -- `run` - - runs the generated installer -- `build-run` - - builds and runs -- `sync-lang` - - normalizes and synchronizes language files - -### Example - -```powershell -pwsh -NoProfile -ExecutionPolicy Bypass -File .vscode\7zip-installer-task.ps1 build -``` - -## Consolidated result of this fork - -This fork provides a 7-Zip build with: - -- multiple outputs per compression operation -- independent groups per selected item -- runtime-adaptive UI behavior -- custom fork identity in the About dialog, installer, and system metadata -- language support for the new UI elements -- locally generated installer output -- customized Control Panel metadata -- safer uninstall behavior -- a more reliable local maintenance and build workflow - -## Credits - -- Original project: Igor Pavlov -- Fork and customizations in this repository: Fernando Nillsson Cidade From 94a386e0f47af218cbc1504a9e9c12934cb2fe92 Mon Sep 17 00:00:00 2001 From: Fernando Nillsson Cidade <128842267+fernandoncidade@users.noreply.github.com> Date: Sun, 22 Mar 2026 01:40:38 -0300 Subject: [PATCH 3/3] v2026.3.22.0 --- README.md | 2 + RELEASE.md | 689 ----------------------------------------------------- 2 files changed, 2 insertions(+), 689 deletions(-) create mode 100644 README.md delete mode 100644 RELEASE.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1e4514 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# 7-Zip on GitHub +7-Zip website: [7-zip.org](https://7-zip.org) diff --git a/RELEASE.md b/RELEASE.md deleted file mode 100644 index 410a79c..0000000 --- a/RELEASE.md +++ /dev/null @@ -1,689 +0,0 @@ -# Release Notes: 7-Zip Fork - -**Version / Versão:** 2026.3.19.0 -**Author / Autor:** Fernando Nillsson Cidade - -## Languages - -- [Português](#português) -- [English](#english) - ---- - -## Português - -# Notas de Lançamento: 7-Zip Fork - -**Versão:** 2026.3.19.0 -**Autor:** Fernando Nillsson Cidade - -## Resumo técnico da release - -Esta release consolida alterações estruturais e funcionais relevantes sobre a base do 7-Zip, com foco principal na expansão do fluxo de compressão pela GUI, customização institucional do fork, reorganização do pipeline de build, sincronização local dos idiomas, geração local do instalador e fortalecimento do ciclo de desinstalação. - -Do ponto de vista arquitetural, a versão `2026.3.19.0` altera diretamente estes subsistemas: - -- interface de compressão da GUI -- backend de roteamento de operações de atualização e compressão -- diálogo `Sobre o 7-Zip` -- mecanismo de localização e sincronização de idiomas -- pipeline de build do File Manager e do instalador -- instalador e metadados persistidos no Windows -- desinstalador e limpeza de DLLs e artefatos temporários - -## Escopo da release - -### Componentes impactados - -- `CPP/7zip/UI/GUI/CompressDialog.cpp` -- `CPP/7zip/UI/GUI/CompressDialog.h` -- `CPP/7zip/UI/GUI/CompressDialog.rc` -- `CPP/7zip/UI/GUI/CompressDialogRes.h` -- `CPP/7zip/UI/GUI/UpdateGUI.cpp` -- `CPP/7zip/UI/Common/Update.h` -- `CPP/7zip/UI/FileManager/AboutDialog.cpp` -- `CPP/7zip/UI/FileManager/AboutDialog.rc` -- `CPP/7zip/UI/FileManager/AboutDialogRes.h` -- `CPP/7zip/UI/FileManager/LangUtils.cpp` -- `C/Util/7zipInstall/7zipInstall.c` -- `C/Util/7zipUninstall/7zipUninstall.c` -- `CPP/7zip/Bundles/Fm/makefile` -- `CPP/7zip/UI/GUI/makefile` -- `CPP/Build.mak` -- `.vscode/7zip-installer-task.ps1` -- `Lang/*` -- `InstallerAssets/*` - -## Alterações funcionais consolidadas - -### 1. Expansão da janela `Adicionar ao arquivo compactado` - -O fluxo de compressão da GUI foi ampliado para permitir cenários multi-item e multi-destino dentro do mesmo diálogo, sem exigir uma etapa externa de mapeamento manual. - -#### 1.1 Grupos independentes por item selecionado - -Quando múltiplos arquivos ou diretórios são selecionados na GUI principal e o usuário aciona `Adicionar`, o diálogo passa a construir um grupo lógico por item selecionado. Cada grupo mantém isolamento de estado e de widgets, incluindo: - -- identificação visual do item de origem -- controle individual da quantidade de saídas -- campos independentes de caminho de saída -- persistência local do nome base do item selecionado - -Essa alteração substitui a lógica anterior, que operava sobre uma única coleção agregada, por um modelo de grupos independentes associados diretamente ao item de origem. - -#### 1.2 Múltiplas saídas por grupo - -Cada grupo suporta até 5 saídas de empacotamento. Para isso, a camada de recursos e a camada de diálogo foram alteradas para instanciar múltiplos campos `Arquivo` e múltiplos botões `...`, mantendo sincronização entre estado visual e estado interno. - -Comportamento consolidado: - -- a primeira saída do grupo é inicializada com o caminho padrão sugerido -- as saídas adicionais permanecem vazias até ação explícita do usuário -- mudanças em `Quantidade` redimensionam o conjunto efetivamente ativo de saídas -- a navegação por `...` atua sobre a linha correta do grupo correspondente - -Essa alteração elimina o comportamento anterior em que saídas adicionais herdavam indevidamente o mesmo caminho inicial do primeiro item. - -#### 1.3 Layout dinâmico em runtime - -O diálogo deixa de trabalhar com uma reserva fixa de espaço para cinco saídas sempre visíveis. A janela agora ajusta a disposição vertical dos controles conforme: - -- número de grupos carregados -- quantidade de saídas habilitadas em cada grupo -- necessidade de exibir ou ocultar linhas extras - -Na prática, houve reestruturação do cálculo de posicionamento, da altura do diálogo e da visibilidade dos widgets associados a cada linha de saída. - -#### 1.4 Correções de geração de nome e extensão - -O fluxo de composição do nome do arquivo compactado foi corrigido para resolver inconsistências de normalização de extensão, especialmente em cenários com nomes já terminados no sufixo do formato corrente. - -Problemas corrigidos: - -- `Download.7z.7z` -- `.7z..7z` -- nomes vazios derivados de diretórios com barra final -- reaplicação indevida de extensão quando o formato era alterado - -Também foi ajustada a lógica de atualização dos caminhos extras para que linhas vazias não passem a receber extensões automaticamente. - -### 2. Ajustes no backend de compressão - -O backend que prepara e dispara as operações de compressão foi expandido para receber o novo modelo de saídas por grupo e por item. - -#### 2.1 Propagação de estado entre GUI e camada de atualização - -O conjunto de opções transportado entre o diálogo e o backend foi estendido para suportar: - -- múltiplos caminhos por grupo -- associação entre item selecionado e saídas correspondentes -- preservação do comportamento legado quando a operação continua sendo simples - -Os principais pontos de integração dessa consolidação foram: - -- `CPP/7zip/UI/GUI/UpdateGUI.cpp` -- `CPP/7zip/UI/Common/Update.h` - -#### 2.2 Correção de reaplicação indevida de extensão - -O fluxo do File Manager que originalmente entrava com modo de nome automático passou a respeitar corretamente caminhos já explicitados na GUI, evitando dupla reaplicação de extensão em operações confirmadas pelo usuário. - -### 3. Customização da janela `Sobre o 7-Zip` - -O diálogo `Sobre o 7-Zip` foi expandido para preservar o bloco institucional original do projeto e adicionar um bloco institucional do fork, com identidade própria do mantenedor. - -#### 3.1 Conteúdo adicional incorporado - -O diálogo agora incorpora: - -- observação de que a build refere-se a um fork -- identificação da versão do fork -- data de referência do fork -- copyright específico do autor -- link para o repositório do fork -- manutenção da linha informando que o 7-Zip é software grátis - -#### 3.2 Integração com recursos e runtime - -As alterações envolveram: - -- aumento e reorganização do layout do recurso `.rc` -- novos controles dedicados no diálogo -- resolução de textos por idioma -- fallback seguro para strings padrão quando necessário -- tratamento do link do repositório como ação clicável - -### 4. Sistema de idiomas e localização - -O fork consolida `Lang/` na raiz do repositório como a fonte principal de verdade dos arquivos de idioma utilizados na GUI, no File Manager local e no stage do instalador. - -#### 4.1 Novas entradas de idioma - -Foram incorporadas novas chaves e novos blocos de tradução para suportar: - -- o rótulo `Quantidade` -- o conteúdo expandido da janela `Sobre` -- a linha de repositório do fork - -Na prática, o rótulo `Quantidade` foi associado ao ID `4021`, enquanto o bloco `2900` passou a acomodar a expansão das informações exibidas no diálogo `Sobre`. - -#### 4.2 Normalização dos arquivos de idioma - -Foi implementado um fluxo de normalização para evitar que a aba `Language` da GUI acuse erros como `Error in Lang file`. - -Os principais cuidados adotados foram: - -- inserção ordenada dos IDs -- remoção de duplicidades inválidas -- preservação da estrutura esperada pelo parser do 7-Zip -- sincronização automática entre raiz `Lang/` e diretórios de runtime - -#### 4.3 Cobertura ampliada de idiomas - -Os idiomas presentes no projeto passaram a receber suporte para os novos elementos da janela `Sobre` e para o rótulo `Quantidade`, com sincronização para: - -- `CPP/7zip/UI/FileManager/x64/Lang` -- `CPP/7zip/Bundles/Fm/x64/Lang` -- `dist/installer-full/stage/Lang` - -### 5. Reorganização do pipeline de build e do instalador - -O pipeline do instalador foi redesenhado para deixar de depender do download do instalador oficial como base de montagem. - -#### 5.1 Substituição do modelo baseado em download - -Antes, o fluxo de empacotamento dependia de: - -- download do instalador oficial -- extração de payload externo -- sobreposição posterior de binários locais - -Agora, o `stage` do instalador passa a ser montado localmente a partir de: - -- binários compilados no workspace -- ativos estáticos versionados no repositório -- base local de idiomas -- documentação local do projeto - -#### 5.2 Introdução de ativos locais do instalador - -Os arquivos antes obtidos da extração do instalador oficial foram movidos para o repositório, com uso direto a partir de: - -- `InstallerAssets/7-zip.chm` -- `InstallerAssets/History.txt` -- `InstallerAssets/descript.ion` - -#### 5.3 Saída final renomeada do instalador - -O artefato final gerado por este fork passa a ser: - -- `dist/7z2026.3.19.0-x64.exe` - -Isso substitui o nome padronizado anterior derivado da versão upstream. - -### 6. Personalização do instalador e do Painel de Controle - -O instalador foi ajustado para registrar metadados próprios do fork no Windows. - -#### 6.1 Metadados de registro alterados - -Ao instalar o produto, a chave de desinstalação passa a gravar: - -- `DisplayName = 7-Zip 2026.3.19.0 (x64)` -- `DisplayVersion = 2026.3.19.0` -- `Publisher = Fernando Nillsson Cidade` - -Esses dados são persistidos na entrada: - -- `Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\7-Zip` - -#### 6.2 Coerência entre binário, instalador e identidade visual - -A release alinha: - -- nome do instalador -- versão apresentada ao usuário -- identidade do fork na janela `Sobre` -- metadados exibidos pelo Painel de Controle - -### 7. Fortalecimento do desinstalador - -O subsistema de remoção foi endurecido para reduzir sobras pós-desinstalação e para evitar comportamento agressivo sobre o Windows Explorer. - -#### 7.1 Limpeza explícita de arquivos temporários - -O desinstalador passou a tratar de forma explícita sobras como: - -- `7-zip.dll.tmp` -- variantes temporárias correlatas - -#### 7.2 Elevação administrativa e remoção mais segura - -O fluxo agora tenta operar com privilégios apropriados quando necessário, evitando falhas silenciosas na remoção de arquivos instalados em diretórios protegidos do sistema. - -#### 7.3 Correção da estratégia de remoção de DLLs em uso - -Uma solução intermediária baseada em encerramento amplo de processos do Explorer mostrou-se intrusiva, pois podia fechar janelas do Explorador de Arquivos e afetar temporariamente a barra de tarefas. A implementação consolidada substitui esse comportamento por uma estratégia menos agressiva de descarregamento da shell extension, preservando o ambiente do usuário. - -Resultado esperado: - -- menos sobras de DLLs -- menor necessidade de reinicialização -- ausência do efeito colateral de fechar globalmente o Explorer - -### 8. Robustez de build incremental - -Foram incluídas correções de dependência para evitar composições inválidas entre objetos antigos e cabeçalhos novos, bem como para garantir a regeneração de recursos quando arquivos `.rc` são alterados. - -#### 8.1 Ajustes aplicados - -- dependências explícitas para módulos que consomem `CompressDialog.h` -- dependências explícitas para módulos que consomem `Update.h` -- dependências explícitas de `AboutDialog.rc` e `AboutDialogRes.h` para regeneração de `resource.res` -- sincronização automática das bases locais de idioma usadas pelos binários de teste - -#### 8.2 Benefícios práticos - -Essas alterações reduzem falhas como: - -- fechamento inesperado do `7zFM.exe` após mudança estrutural de diálogo -- binários relincados com recursos antigos -- runtime carregando idiomas desatualizados - -## Impacto técnico por subsistema - -### GUI de compressão - -- novo modelo de grupos por item -- novo modelo de múltiplas saídas por grupo -- layout recalculado em runtime -- correção de normalização de nomes e extensões - -### Backend de update e compressão - -- expansão da estrutura de opções -- repasse correto dos caminhos configurados pela GUI -- preservação de compatibilidade com fluxo simples - -### Recursos e localização - -- expansão de recursos `.rc` -- adição de novos IDs -- sincronização de idioma raiz para runtime -- normalização automática dos arquivos de tradução - -### Instalador - -- geração local do `stage` -- uso de ativos versionados no próprio repositório -- nome final do instalador alinhado à identidade do fork -- metadados do produto customizados no Windows - -### Desinstalador - -- limpeza reforçada de arquivos temporários -- remoção mais segura de DLLs -- comportamento menos intrusivo sobre shell e Explorer - -## Artefatos relevantes desta release - -- `CPP/7zip/UI/FileManager/x64/7zFM.exe` -- `CPP/7zip/Bundles/Fm/x64/7zFM.exe` -- `dist/7z2026.3.19.0-x64.exe` -- `Lang/*` -- `InstallerAssets/*` - -## Compatibilidade e observações operacionais - -- a base upstream do 7-Zip foi preservada, com extensões direcionadas principalmente ao File Manager e ao pipeline local de build e instalação -- a nova lógica de compressão afeta principalmente o fluxo acionado pela GUI `Adicionar ao arquivo compactado` -- a coerência visual e institucional do fork depende da sincronização correta entre recursos, idiomas e binários locais -- a normalização de `Lang/` deve continuar sendo executada pelo script de build para evitar regressões na aba `Language` - -## Resultado consolidado - -A release `7-Zip Fork 2026.3.19.0` transforma o projeto em uma variante operacionalmente mais orientada a fluxos avançados de empacotamento, com suporte nativo a múltiplos destinos por item, identidade institucional do fork, pipeline de distribuição autocontido, cobertura de localização ampliada e ciclo de instalação e desinstalação mais controlado. - -## Autoria - -**Fernando Nillsson Cidade** - ---- - -## English - -# Release Notes: 7-Zip Fork - -**Version:** 2026.3.19.0 -**Author:** Fernando Nillsson Cidade - -## Technical release summary - -This release consolidates a set of structural and functional changes on top of the 7-Zip codebase, with primary focus on expanding the GUI compression workflow, establishing fork-specific product identity, reorganizing the build pipeline, synchronizing local language assets, producing the installer locally, and strengthening the uninstall lifecycle. - -From an architectural perspective, version `2026.3.19.0` directly affects these subsystems: - -- GUI compression interface -- update and compression backend routing -- `About 7-Zip` dialog -- language lookup and synchronization mechanism -- File Manager and installer build pipeline -- installer and Windows product metadata -- uninstaller and cleanup of DLLs and temporary artifacts - -## Release scope - -### Impacted components - -- `CPP/7zip/UI/GUI/CompressDialog.cpp` -- `CPP/7zip/UI/GUI/CompressDialog.h` -- `CPP/7zip/UI/GUI/CompressDialog.rc` -- `CPP/7zip/UI/GUI/CompressDialogRes.h` -- `CPP/7zip/UI/GUI/UpdateGUI.cpp` -- `CPP/7zip/UI/Common/Update.h` -- `CPP/7zip/UI/FileManager/AboutDialog.cpp` -- `CPP/7zip/UI/FileManager/AboutDialog.rc` -- `CPP/7zip/UI/FileManager/AboutDialogRes.h` -- `CPP/7zip/UI/FileManager/LangUtils.cpp` -- `C/Util/7zipInstall/7zipInstall.c` -- `C/Util/7zipUninstall/7zipUninstall.c` -- `CPP/7zip/Bundles/Fm/makefile` -- `CPP/7zip/UI/GUI/makefile` -- `CPP/Build.mak` -- `.vscode/7zip-installer-task.ps1` -- `Lang/*` -- `InstallerAssets/*` - -## Consolidated functional changes - -### 1. `Add to Archive` dialog expansion - -The GUI compression flow was expanded to support both multi-item and multi-destination scenarios within the same dialog, without requiring an external manual mapping step. - -#### 1.1 Independent groups per selected item - -When multiple files or directories are selected in the main GUI and the user triggers `Add`, the dialog now creates one logical group per selected item. Each group maintains isolated state and widgets, including: - -- visual identification of the source item -- per-group output quantity control -- independent output path fields -- local persistence of the selected item's base name - -This change replaces the previous model, which operated over a single aggregated collection, with a structure of independent groups directly associated with the source item. - -#### 1.2 Multiple outputs per group - -Each group supports up to 5 archive outputs. To enable this, both the resource layer and the dialog layer were expanded to instantiate multiple `Archive` fields and multiple `...` buttons while keeping the visual state synchronized with the internal state. - -Consolidated behavior: - -- the first group output is initialized with the suggested default path -- additional outputs remain blank until explicit user action -- changes to `Quantity` resize the active set of outputs -- the `...` browser acts on the correct line inside the corresponding group - -This removes the previous behavior where extra outputs incorrectly inherited the same initial path from the first item. - -#### 1.3 Dynamic runtime layout - -The dialog no longer reserves a fixed block of space for five always-visible outputs. Instead, the window now adapts its vertical layout according to: - -- number of loaded groups -- number of enabled outputs in each group -- whether extra lines need to be shown or hidden - -In practical terms, this required restructuring control positioning, dialog height calculation, and visibility handling for widgets tied to each output line. - -#### 1.4 File name and extension generation fixes - -The archive name composition flow was corrected to resolve extension normalization issues, especially in cases where the user-provided name already ended with the current format suffix. - -Fixed issues: - -- `Download.7z.7z` -- `.7z..7z` -- empty names derived from directories with trailing separators -- incorrect extension reapplication when the format changed - -The extra-output update flow was also adjusted so that blank lines do not receive artificial extensions automatically. - -### 2. Compression backend adjustments - -The backend responsible for preparing and dispatching compression operations was expanded to accept the new model of outputs per group and per item. - -#### 2.1 State propagation between GUI and update layer - -The options structure transported between the dialog and the backend was extended to support: - -- multiple paths per group -- association between selected items and their corresponding outputs -- preservation of legacy behavior when the operation remains simple - -The main integration points for this consolidation were: - -- `CPP/7zip/UI/GUI/UpdateGUI.cpp` -- `CPP/7zip/UI/Common/Update.h` - -#### 2.2 Fix for incorrect extension reapplication - -The File Manager flow that originally entered with automatic archive-name mode was adjusted to respect paths already made explicit in the GUI, preventing double extension reapplication after user confirmation. - -### 3. `About 7-Zip` dialog customization - -The `About 7-Zip` dialog was expanded to preserve the original project block while adding a fork-specific institutional block with maintainer identity. - -#### 3.1 Additional content introduced - -The dialog now includes: - -- a note that the build refers to a fork -- fork version identification -- fork reference date -- author-specific copyright -- a link to the fork repository -- preservation of the line stating that 7-Zip is free software - -#### 3.2 Integration with resources and runtime - -The implementation involved: - -- resizing and reorganizing the `.rc` dialog layout -- adding dedicated dialog controls -- resolving texts through language resources -- safe fallback to default strings when needed -- handling the repository entry as a clickable action - -### 4. Language and localization system - -The fork consolidates root `Lang/` as the single source of truth for language files consumed by the GUI, the local File Manager runtime, and the installer stage. - -#### 4.1 New language entries - -New translation keys and blocks were added to support: - -- the `Quantity` label -- the expanded `About` dialog content -- the fork repository line - -In practice, the `Quantity` label was bound to ID `4021`, while block `2900` was extended to accommodate the additional information displayed by the `About` dialog. - -#### 4.2 Language file normalization - -A normalization flow was implemented to prevent the GUI `Language` tab from reporting errors such as `Error in Lang file`. - -Key safeguards included: - -- ordered insertion of IDs -- removal of invalid duplicates -- preservation of the structure expected by the 7-Zip parser -- automatic synchronization between root `Lang/` and runtime language directories - -#### 4.3 Expanded language coverage - -Existing project languages now support the new `About` dialog elements and the `Quantity` label, with synchronization to: - -- `CPP/7zip/UI/FileManager/x64/Lang` -- `CPP/7zip/Bundles/Fm/x64/Lang` -- `dist/installer-full/stage/Lang` - -### 5. Build pipeline and installer reorganization - -The installer pipeline was redesigned to remove the dependency on downloading the official installer as the assembly base. - -#### 5.1 Replacing the download-based model - -Previously, the packaging flow depended on: - -- downloading the official installer -- extracting an external payload -- overlaying local binaries afterwards - -Now, the installer `stage` is assembled locally from: - -- binaries compiled in the workspace -- static assets versioned in the repository -- local language assets -- local project documentation - -#### 5.2 Introduction of local installer assets - -Files previously obtained from the extracted official installer were moved into the repository and are now consumed directly from: - -- `InstallerAssets/7-zip.chm` -- `InstallerAssets/History.txt` -- `InstallerAssets/descript.ion` - -#### 5.3 Renamed final installer output - -The final artifact generated by this fork is now: - -- `dist/7z2026.3.19.0-x64.exe` - -This replaces the previous default name derived from the upstream release number. - -### 6. Installer and Control Panel customization - -The installer was adjusted to persist fork-specific product metadata in Windows. - -#### 6.1 Modified registry metadata - -When the product is installed, the uninstall key now writes: - -- `DisplayName = 7-Zip 2026.3.19.0 (x64)` -- `DisplayVersion = 2026.3.19.0` -- `Publisher = Fernando Nillsson Cidade` - -These values are stored at: - -- `Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\7-Zip` - -#### 6.2 Consistency across binary, installer, and visual identity - -This release aligns: - -- installer file name -- user-facing version string -- fork identity shown in the `About` dialog -- metadata displayed in Windows Control Panel - -### 7. Uninstaller hardening - -The removal subsystem was reinforced to reduce uninstall leftovers while avoiding aggressive behavior against Windows Explorer. - -#### 7.1 Explicit cleanup of temporary files - -The uninstaller now explicitly handles leftovers such as: - -- `7-zip.dll.tmp` -- related temporary variants - -#### 7.2 Administrative elevation and safer removal - -The flow now attempts to operate with proper privileges when needed, avoiding silent failures while removing files installed under protected system directories. - -#### 7.3 Fix for the DLL-in-use removal strategy - -An intermediate solution based on broad Explorer process termination proved too intrusive because it could close File Explorer windows and temporarily affect the taskbar. The consolidated implementation replaces that behavior with a less aggressive shell-extension unload strategy that preserves the user environment. - -Expected result: - -- fewer leftover DLLs -- lower need for reboot fallback -- no side effect of globally closing Explorer - -### 8. Incremental build robustness - -Dependency corrections were added to avoid invalid combinations of old object files with newer headers and to guarantee resource regeneration whenever `.rc` files change. - -#### 8.1 Applied adjustments - -- explicit dependencies for modules consuming `CompressDialog.h` -- explicit dependencies for modules consuming `Update.h` -- explicit dependencies from `AboutDialog.rc` and `AboutDialogRes.h` to regenerate `resource.res` -- automatic synchronization of local language bases used by test binaries - -#### 8.2 Practical benefits - -These changes reduce issues such as: - -- unexpected `7zFM.exe` termination after structural dialog changes -- relinking binaries with outdated resources -- runtime loading stale language files - -## Technical impact by subsystem - -### Compression GUI - -- new per-item grouping model -- new multiple-output-per-group model -- runtime-recalculated layout -- file name and extension normalization fixes - -### Update and compression backend - -- expanded options structure -- proper propagation of GUI-configured output paths -- preserved compatibility with simple workflows - -### Resources and localization - -- expanded `.rc` resources -- new control/resource IDs -- synchronization from root language base to runtime -- automatic normalization of translation files - -### Installer - -- local stage generation -- use of repository-versioned assets -- final installer name aligned with fork identity -- custom product metadata in Windows - -### Uninstaller - -- stronger temporary-file cleanup -- safer DLL removal flow -- less intrusive shell and Explorer behavior - -## Relevant artifacts in this release - -- `CPP/7zip/UI/FileManager/x64/7zFM.exe` -- `CPP/7zip/Bundles/Fm/x64/7zFM.exe` -- `dist/7z2026.3.19.0-x64.exe` -- `Lang/*` -- `InstallerAssets/*` - -## Compatibility and operational notes - -- the upstream 7-Zip base was preserved, with extensions focused mainly on the File Manager and the local build/install pipeline -- the new compression logic primarily affects the GUI path driven by `Add to Archive` -- visual and institutional consistency of the fork depends on correct synchronization between resources, languages, and local binaries -- `Lang/` normalization should continue to be executed by the build script to avoid regressions in the `Language` tab - -## Consolidated result - -Release `7-Zip Fork 2026.3.19.0` turns the project into a variant more strongly oriented toward advanced packaging workflows, with native support for multiple output destinations per item, fork-specific product identity, a self-contained distribution pipeline, expanded localization coverage, and a more controlled installation and uninstallation cycle. - -## Authorship - -**Fernando Nillsson Cidade**