mirror of
https://github.com/ip7z/7zip.git
synced 2026-04-21 06:03:40 +00:00
**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.
371 lines
7.7 KiB
C++
371 lines
7.7 KiB
C++
// LangUtils.cpp
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "../../../Common/Lang.h"
|
|
|
|
#include "../../../Windows/DLL.h"
|
|
#include "../../../Windows/FileFind.h"
|
|
#include "../../../Windows/FileName.h"
|
|
#include "../../../Windows/Synchronization.h"
|
|
#include "../../../Windows/Window.h"
|
|
|
|
#include "LangUtils.h"
|
|
#include "RegistryUtils.h"
|
|
|
|
using namespace NWindows;
|
|
|
|
#ifndef _UNICODE
|
|
extern bool g_IsNT;
|
|
#endif
|
|
|
|
UString g_LangID;
|
|
|
|
// static
|
|
CLang g_Lang;
|
|
static bool g_Loaded = false;
|
|
static NSynchronization::CCriticalSection g_CriticalSection;
|
|
|
|
bool LangOpen(CLang &lang, CFSTR fileName);
|
|
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()
|
|
{
|
|
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
|
|
|
|
void LoadLangOneTime()
|
|
{
|
|
NSynchronization::CCriticalSectionLock lock(g_CriticalSection);
|
|
if (g_Loaded)
|
|
return;
|
|
g_Loaded = true;
|
|
ReloadLang();
|
|
}
|
|
|
|
void LangSetDlgItemText(HWND dialog, UInt32 controlID, UInt32 langID)
|
|
{
|
|
const wchar_t *s = g_Lang.Get(langID);
|
|
if (s)
|
|
{
|
|
CWindow window(GetDlgItem(dialog, (int)controlID));
|
|
window.SetText(s);
|
|
}
|
|
}
|
|
|
|
#ifndef IDCONTINUE
|
|
#define IDCONTINUE 11
|
|
#endif
|
|
|
|
static const CIDLangPair kLangPairs[] =
|
|
{
|
|
{ IDOK, 401 },
|
|
{ IDCANCEL, 402 },
|
|
{ IDYES, 406 },
|
|
{ IDNO, 407 },
|
|
{ IDCLOSE, 408 },
|
|
{ IDHELP, 409 },
|
|
{ IDCONTINUE, 411 }
|
|
};
|
|
|
|
|
|
void LangSetDlgItems(HWND dialog, const UInt32 *ids, unsigned numItems)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < Z7_ARRAY_SIZE(kLangPairs); i++)
|
|
{
|
|
const CIDLangPair &pair = kLangPairs[i];
|
|
CWindow window(GetDlgItem(dialog, (int)pair.ControlID));
|
|
if (window)
|
|
{
|
|
const wchar_t *s = g_Lang.Get(pair.LangID);
|
|
if (s)
|
|
window.SetText(s);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < numItems; i++)
|
|
{
|
|
const UInt32 id = ids[i];
|
|
LangSetDlgItemText(dialog, id, id);
|
|
}
|
|
}
|
|
|
|
void LangSetDlgItems_Colon(HWND dialog, const UInt32 *ids, unsigned numItems)
|
|
{
|
|
for (unsigned i = 0; i < numItems; i++)
|
|
{
|
|
const UInt32 id = ids[i];
|
|
const wchar_t *s = g_Lang.Get(id);
|
|
if (s)
|
|
{
|
|
CWindow window(GetDlgItem(dialog, (int)id));
|
|
UString s2 = s;
|
|
s2.Add_Colon();
|
|
window.SetText(s2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LangSetDlgItems_RemoveColon(HWND dialog, const UInt32 *ids, unsigned numItems)
|
|
{
|
|
for (unsigned i = 0; i < numItems; i++)
|
|
{
|
|
const UInt32 id = ids[i];
|
|
const wchar_t *s = g_Lang.Get(id);
|
|
if (s)
|
|
{
|
|
CWindow window(GetDlgItem(dialog, (int)id));
|
|
UString s2 = s;
|
|
if (!s2.IsEmpty() && s2.Back() == ':')
|
|
s2.DeleteBack();
|
|
window.SetText(s2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LangSetWindowText(HWND window, UInt32 langID)
|
|
{
|
|
const wchar_t *s = g_Lang.Get(langID);
|
|
if (s)
|
|
MySetWindowText(window, s);
|
|
}
|
|
|
|
UString LangString(UInt32 langID)
|
|
{
|
|
const wchar_t *s = g_Lang.Get(langID);
|
|
if (s)
|
|
return s;
|
|
return MyLoadString(langID);
|
|
}
|
|
|
|
void AddLangString(UString &s, UInt32 langID)
|
|
{
|
|
s += LangString(langID);
|
|
}
|
|
|
|
void LangString(UInt32 langID, UString &dest)
|
|
{
|
|
const wchar_t *s = g_Lang.Get(langID);
|
|
if (s)
|
|
{
|
|
dest = s;
|
|
return;
|
|
}
|
|
MyLoadString(langID, dest);
|
|
}
|
|
|
|
void LangString_OnlyFromLangFile(UInt32 langID, UString &dest)
|
|
{
|
|
dest.Empty();
|
|
const wchar_t *s = g_Lang.Get(langID);
|
|
if (s)
|
|
dest = s;
|
|
}
|
|
|
|
static const char * const kLangs =
|
|
"ar.bg.ca.zh.-tw.-cn.cs.da.de.el.en.es.fi.fr.he.hu.is."
|
|
"it.ja.ko.nl.no.=nb.=nn.pl.pt.-br.rm.ro.ru.sr.=hr.-spl.-spc.=hr.=bs.sk.sq.sv.th.tr."
|
|
"ur.id.uk.be.sl.et.lv.lt.tg.fa.vi.hy.az.eu.hsb.mk."
|
|
"st.ts.tn.ve.xh.zu.af.ka.fo.hi.mt.se.ga.yi.ms.kk."
|
|
"ky.sw.tk.uz.-latn.-cyrl.tt.bn.pa.-in.gu.or.ta.te.kn.ml.as.mr.sa."
|
|
"mn.=mn.=mng.bo.cy.kh.lo.my.gl.kok..sd.syr.si..iu.am.tzm."
|
|
"ks.ne.fy.ps.tl.dv..ff.ha..yo.qu.st.ba.lb.kl."
|
|
"ig.kr.om.ti.gn..la.so.ii..arn..moh..br.."
|
|
"ug.mi.oc.co."
|
|
// "gsw.sah.qut.rw.wo....prs...."
|
|
// ".gd."
|
|
;
|
|
|
|
static void FindShortNames(UInt32 primeLang, AStringVector &names)
|
|
{
|
|
UInt32 index = 0;
|
|
for (const char *p = kLangs; *p != 0;)
|
|
{
|
|
const char *p2 = p;
|
|
for (; *p2 != '.'; p2++);
|
|
bool isSub = (p[0] == '-' || p[0] == '=');
|
|
if (!isSub)
|
|
index++;
|
|
if (index >= primeLang)
|
|
{
|
|
if (index > primeLang)
|
|
break;
|
|
AString s;
|
|
if (isSub)
|
|
{
|
|
if (p[0] == '-')
|
|
s = names[0];
|
|
else
|
|
p++;
|
|
}
|
|
while (p != p2)
|
|
s.Add_Char((char)(Byte)*p++);
|
|
names.Add(s);
|
|
}
|
|
p = p2 + 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
#include "../../../Common/IntToString.h"
|
|
|
|
static struct CC1Lang
|
|
{
|
|
CC1Lang()
|
|
{
|
|
for (int i = 1; i < 150; i++)
|
|
{
|
|
UString s;
|
|
char ttt[32];
|
|
ConvertUInt32ToHex(i, ttt);
|
|
s += ttt;
|
|
UStringVector names;
|
|
FindShortNames(i, names);
|
|
|
|
FOR_VECTOR (k, names)
|
|
{
|
|
s.Add_Space();
|
|
s += names[k];
|
|
}
|
|
OutputDebugStringW(s);
|
|
}
|
|
}
|
|
} g_cc1;
|
|
*/
|
|
|
|
// typedef LANGID (WINAPI *GetUserDefaultUILanguageP)();
|
|
|
|
void Lang_GetShortNames_for_DefaultLang(AStringVector &names, unsigned &subLang)
|
|
{
|
|
names.Clear();
|
|
subLang = 0;
|
|
// Region / Administative / Language for non-Unicode programs:
|
|
const LANGID sysLang = GetSystemDefaultLangID();
|
|
|
|
// Region / Formats / Format:
|
|
const LANGID userLang = GetUserDefaultLangID();
|
|
|
|
if (PRIMARYLANGID(sysLang) !=
|
|
PRIMARYLANGID(userLang))
|
|
return;
|
|
const LANGID langID = userLang;
|
|
|
|
// const LANGID langID = MAKELANGID(0x1a, 1); // for debug
|
|
|
|
/*
|
|
LANGID sysUILang; // english in XP64
|
|
LANGID userUILang; // english in XP64
|
|
|
|
GetUserDefaultUILanguageP fn = (GetUserDefaultUILanguageP)GetProcAddress(
|
|
GetModuleHandle("kernel32"), "GetUserDefaultUILanguage");
|
|
if (fn)
|
|
userUILang = fn();
|
|
fn = (GetUserDefaultUILanguageP)GetProcAddress(
|
|
GetModuleHandle("kernel32"), "GetSystemDefaultUILanguage");
|
|
if (fn)
|
|
sysUILang = fn();
|
|
*/
|
|
|
|
const WORD primLang = (WORD)(PRIMARYLANGID(langID));
|
|
subLang = SUBLANGID(langID);
|
|
FindShortNames(primLang, names);
|
|
}
|
|
|
|
|
|
static void OpenDefaultLang()
|
|
{
|
|
AStringVector names;
|
|
unsigned subLang;
|
|
Lang_GetShortNames_for_DefaultLang(names, subLang);
|
|
{
|
|
const FString dirPrefix (GetLangDirPrefix());
|
|
for (unsigned i = 0; i < 2; i++)
|
|
{
|
|
const unsigned index = (i == 0 ? subLang : 0);
|
|
if (index < names.Size())
|
|
{
|
|
const AString &name = names[index];
|
|
if (!name.IsEmpty())
|
|
{
|
|
FString path (dirPrefix);
|
|
path += name;
|
|
path += ".txt";
|
|
if (LangOpen(g_Lang, path))
|
|
{
|
|
g_LangID = name;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ReloadLang()
|
|
{
|
|
g_Lang.Clear();
|
|
ReadRegLang(g_LangID);
|
|
if (g_LangID.IsEmpty())
|
|
{
|
|
#ifndef _UNICODE
|
|
if (g_IsNT)
|
|
#endif
|
|
OpenDefaultLang();
|
|
return;
|
|
}
|
|
if (g_LangID.Len() > 1 || g_LangID[0] != L'-')
|
|
{
|
|
FString s = us2fs(g_LangID);
|
|
if (s.ReverseFind_PathSepar() < 0)
|
|
{
|
|
if (s.ReverseFind_Dot() < 0)
|
|
s += ".txt";
|
|
s.Insert(0, GetLangDirPrefix());
|
|
LangOpen(g_Lang, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|