-/////////////////////////////////////////////////////////////////////////////
-// WinMerge: an interactive diff/merge utility
-// Copyright (C) 1997-2000 Thingamahoochie Software
-// Author: Dean Grimm
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation; either version 2 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-//
-/////////////////////////////////////////////////////////////////////////////
-
-/* 7zCommon.cpp: Implement 7z related classes and functions
- * Copyright (c) 2003 Jochen Tucht
- *
- * Remarks: Different versions of 7-Zip are interfaced through specific
- * versions of Merge7z (Merge7z311.dll, Merge7z312.dll, etc.)
- * WinMerge can either use an installed copy of the 7-Zip software,
- * or fallback to a local set of 7-Zip DLLs, which are to be included
- * in the WinMerge binary distribution.
- *
- * Fallback policies are as follows:
- *
- * 1. Detect 7-Zip version installed (XXX).
- * 2. If there is a Merge7zXXX.dll, be happy to use it
- * 3. Detect 7-Zip version from WinMerge distribution (YYY).
- * 4. If there is a Merge7zYYY.dll, be happy to use it.
- * 5. Sorry, no way.
- *
- * These rules can be customized by setting a registry variable
- * *Merge7z/Enable* of type DWORD to one of the following values:
- *
- * 0 - Entirely disable 7-Zip integration.
- * 1 - Use installed 7-Zip if present. Otherwise, use local 7-Zip.
- * 2 - Always use local 7-Zip.
- *
-
-Please mind 2. a) of the GNU General Public License, and log your changes below.
-
-DATE: BY: DESCRIPTION:
-========== ================== ================================================
-2003-12-09 Jochen Tucht Created
-2003-12-16 Jochen Tucht Properly generate .tar.gz and .tar.bz2
-2003-12-16 Jochen Tucht Obtain long path to temporary folder
-2004-01-20 Jochen Tucht Complain only once if Merge7z*.dll is missing
-2004-01-25 Jochen Tucht Fix bad default for OPENFILENAME::nFilterIndex
-2004-03-15 Jochen Tucht Fix Visual Studio 2003 build issue
-2004-04-13 Jochen Tucht Avoid StrNCat to get away with shlwapi 4.70
-2004-08-25 Jochen Tucht More explicit error message
-2004-10-17 Jochen Tucht Leave decision whether to recurse into folders
- to enumerator (Mask.Recurse)
-2004-11-03 Jochen Tucht FIX [1048997] as proposed by Kimmo 2004-11-02
-2005-01-15 Jochen Tucht Read 7-Zip version from 7zip_pad.xml
- Set Merge7z UI language if DllBuild_Merge7z >= 9
-2005-01-22 Jochen Tucht Better explain what's present/missing/outdated
-2005-02-05 Jochen Tucht Fall back to IDD_MERGE7ZMISMATCH template from
- .exe if .lang file isn't up to date.
-2005-02-26 Jochen Tucht Add download link to error message
-2005-02-26 Jochen Tucht Use WinAPI to obtain ISO language/region codes
-2005-02-27 Jochen Tucht FIX [1152375]
-2005-04-24 Kimmo Varis Don't use DiffContext exported from DirView
-2005-06-08 Kimmo Varis Use DIFFITEM, not reference to it (hopefully only
- temporarily, to sort out new directory compare)
-2005-06-22 Jochen Tucht Change recommended version of 7-Zip to 4.20
- Remove noise from Nagbox
-2005-07-03 Jochen Tucht DIFFITEM has changed due to RFE [ 1205516 ]
-2005-07-04 Jochen Tucht New global ArchiveGuessFormat() checks for
- formats to be handled by external command line
- tools. These take precedence over Merge7z
- internal handlers.
-2005-07-05 Jochen Tucht Move to Merge7z::Format::GetDefaultName() to
- build intermediate filenames for multi-step
- compression.
-2005-07-15 Jochen Tucht Remove external command line tool integration
- for now. Rethink about it after 2.4 branch.
-2005-08-20 Jochen Tucht Option to guess archive format by signature
- Map extensions through ExternalArchiveFormat.ini
-2005-08-23 Jochen Tucht Option to entirely disable 7-Zip integration
-2007-01-04 Kimmo Varis Convert using COptionsMgr for options.
-2007-06-16 Jochen Neubeck FIX [1723263] "Zip --> Both" operation...
-2007-12-22 Jochen Neubeck Fix Merge7z UI lang for new translation system
- Change recommended version of 7-Zip to 4.57
-2010-05-16 Jochen Neubeck Read 7-Zip version from 7z.dll (which has long
- ago replaced the various format and codec DLLs)
- Change recommended version of 7-Zip to 4.65
-*/
-
-// ID line follows -- this is updated by SVN
-// $Id: 7zCommon.cpp 7169 2010-05-16 14:44:19Z jtuc $
-
-#include "stdafx.h"
-#include "7zCommon.h"
-#include <afxinet.h>
-#include <shlwapi.h>
-#include <paths.h>
-#include "OptionsDef.h"
-#include "OptionsMgr.h"
-#include "Merge.h" // DirDocFilter theApp GetOptionsMgr()
-#include "resource.h"
-#include "DirDoc.h"
-//#include "ExternalArchiveFormat.h"
-#include "version.h"
-#include "Environment.h"
-
-#ifdef _DEBUG
-#define new DEBUG_NEW
-#undef THIS_FILE
-static char THIS_FILE[] = __FILE__;
-#endif
-
-/**
- * @brief Proxy for Merge7z
- */
-static __declspec(thread) Merge7z::Proxy m_Merge7z =
-{
- { 0, 0, DllBuild_Merge7z, },
- "Merge7z%u%02u"DECORATE_U".dll",
- "Merge7z",
- NULL
-};
-
-bool IsArchiveFile(const String& pszFile)
-{
- try {
- Merge7z::Format *piHandler = ArchiveGuessFormat(pszFile);
- if (piHandler)
- return TRUE;
- else
- return FALSE;
- }
- catch (CException *e)
- {
- e->Delete();
- return FALSE;
- }
- return FALSE;
-}
-
-/**
- * @brief Wrap Merge7z::GuessFormat() to allow for some customizing:
- * - Check if 7-Zip integration is enabled.
- * - Check for filename extension mappings.
- */
-Merge7z::Format *ArchiveGuessFormat(const String& path)
-{
- if (GetOptionsMgr()->GetInt(OPT_ARCHIVE_ENABLE) == 0)
- return NULL;
- if (paths_IsDirectory(path))
- return NULL;
- String path2(path);
- // Map extensions through ExternalArchiveFormat.ini
- static TCHAR null[] = _T("");
- static const TCHAR section[] = _T("extensions");
- String entry = paths_FindExtension(path);
- TCHAR value[20];
- static LPCTSTR filename = NULL;
- if (filename == NULL)
- {
- TCHAR cPath[INTERNET_MAX_PATH_LENGTH];
- DWORD cchPath = SearchPath(NULL, _T("ExternalArchiveFormat.ini"), NULL,
- INTERNET_MAX_PATH_LENGTH, cPath, NULL);
- filename = cchPath && cchPath < INTERNET_MAX_PATH_LENGTH ? StrDup(cPath) : null;
- }
- if (*filename &&
- GetPrivateProfileString(section, entry.c_str(), null, value, 20, filename) &&
- *value == '.')
- {
- // Remove end-of-line comments (in string returned from GetPrivateProfileString)
- // that is, remove semicolon & whatever follows it
- if (LPTSTR p = StrChr(value, ';'))
- {
- *p = '\0';
- StrTrim(value, _T(" \t"));
- }
- path2 = value;
- }
-
- // PATCH [ 1229867 ] RFE [ 1205516 ], RFE [ 887948 ], and other issues
- // command line integration portion is not yet applied
- // so following code not yet valid, so temporarily commented out
- // Look for command line tool first
- /*Merge7z::Format *pFormat;
- if (CExternalArchiveFormat::GuessFormat(path, pFormat))
- {
- return pFormat;
- }*/
- // Default to Merge7z*.dll
-
- return m_Merge7z->GuessFormat(path2.c_str());
-}
-
-/**
- * @brief Self-initializing raw C character buffer class.
- */
-template<class TYPE, size_t SIZE> struct CRawString
-{
- enum { Size = SIZE };
- TYPE Data[SIZE];
- CRawString()
- {
- Data[0] = 0;
- }
-};
-
-/**
- * @brief Exception class for more explicit error message.
- */
-class C7ZipMismatchException : public CException
-{
-public:
- C7ZipMismatchException(DWORD dwVer7zInstalled, DWORD dwVer7zLocal, CException *pCause)
- {
- m_dwVer7zInstalled = dwVer7zInstalled;
- m_dwVer7zLocal = dwVer7zLocal;
- m_pCause = pCause;
- }
- ~C7ZipMismatchException()
- {
- if (m_pCause)
- m_pCause->Delete();
- }
- virtual int ReportError(UINT nType = MB_OK, UINT nMessageID = 0);
-protected:
- DWORD m_dwVer7zInstalled;
- DWORD m_dwVer7zLocal;
- CException *m_pCause;
- BOOL m_bShowAllways;
- static const DWORD m_dwVer7zRecommended;
- static const TCHAR m_strRegistryKey[];
- static const TCHAR m_strDownloadURL[];
- static INT_PTR CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);
- static DWORD FormatVersion(LPTSTR, LPTSTR, DWORD);
-};
-
-/**
- * @brief Recommended version of 7-Zip.
- */
-const DWORD C7ZipMismatchException::m_dwVer7zRecommended = DWORD MAKELONG(65,4);
-
-/**
- * @brief Registry key for C7ZipMismatchException's ReportError() popup.
- */
-const TCHAR C7ZipMismatchException::m_strRegistryKey[] = _T("7ZipMismatch");
-
-/**
- * @brief Download URL for C7ZipMismatchException's ReportError() popup.
- */
-const TCHAR C7ZipMismatchException::m_strDownloadURL[] = _T("https://sourceforge.net/project/showfiles.php?group_id=13216&package_id=143957");
-
-/**
- * @brief Retrieve build number of given DLL.
- */
-static DWORD NTAPI GetDllBuild(LPCTSTR cPath)
-{
- HMODULE hModule = LoadLibrary(cPath);
- DLLVERSIONINFO dvi;
- dvi.cbSize = sizeof dvi;
- dvi.dwBuildNumber = ~0UL;
- if (hModule)
- {
- dvi.dwBuildNumber = 0UL;
- DLLGETVERSIONPROC DllGetVersion = (DLLGETVERSIONPROC)GetProcAddress(hModule, "DllGetVersion");
- if (DllGetVersion)
- {
- DllGetVersion(&dvi);
- }
- FreeLibrary(hModule);
- }
- return dvi.dwBuildNumber;
-}
-
-/**
- * @brief Format Merge7z version number and plugin name, and retrieve DllBuild.
- */
-DWORD C7ZipMismatchException::FormatVersion(LPTSTR pcVersion, LPTSTR pcPluginName, DWORD dwVersion)
-{
- *pcVersion = '\0';
- *pcPluginName = '\0';
- if (dwVersion)
- {
- wsprintf
- (
- pcVersion, _T("%u.%02u"),
- UINT HIWORD(dwVersion),
- UINT LOWORD(dwVersion)
- );
- wsprintf
- (
- pcPluginName,
- sizeof(TCHAR) == 1 ? _T("Merge7z%u%02u.dll") : _T("Merge7z%u%02uU.dll"),
- UINT HIWORD(dwVersion),
- UINT LOWORD(dwVersion)
- );
- }
- return GetDllBuild(pcPluginName);
-}
-
-/**
- * @brief Populate ListBox with names/revisions of DLLs matching given pattern.
- */
-static void NTAPI DlgDirListDLLs(HWND hWnd, LPTSTR cPattern, int nIDListBox)
-{
- HDC hDC = GetDC(hWnd);
- HFONT hFont = (HFONT)SendDlgItemMessage(hWnd, nIDListBox, WM_GETFONT, 0, 0);
- int cxView = (int)SendDlgItemMessage(hWnd, nIDListBox, LB_GETHORIZONTALEXTENT, 0, 0) - 8;
- HGDIOBJ hObject = SelectObject(hDC, hFont);
- WIN32_FIND_DATA ff;
- HANDLE h = FindFirstFile(cPattern, &ff);
- if (h != INVALID_HANDLE_VALUE)
- {
- do
- {
- PathRemoveFileSpec(cPattern);
- PathAppend(cPattern, ff.cFileName);
- wsprintf(ff.cFileName, _T(" (dllbuild %04u)"), GetDllBuild(cPattern));
- lstrcat(cPattern, ff.cFileName);
- int cxText = (int)(WORD)GetTabbedTextExtent(hDC, cPattern, lstrlen(cPattern), 0, 0);
- if (cxView < cxText)
- cxView = cxText;
- SendDlgItemMessage(hWnd, nIDListBox, LB_ADDSTRING, 0, (LPARAM)cPattern);
- } while (FindNextFile(h, &ff));
- FindClose(h);
- }
- SelectObject(hDC, hObject);
- ReleaseDC(hWnd, hDC);
- SendDlgItemMessage(hWnd, nIDListBox, LB_SETHORIZONTALEXTENT, cxView + 8, 0);
-}
-
-/**
- * @brief OwnerDraw states from recent SDK.
- */
-#define ODS_NOACCEL 0x0100
-#define ODS_NOFOCUSRECT 0x0200
-
-/**
- * @brief WM_DRAWITEM notification handlers.
- */
-struct CDrawItemStruct : DRAWITEMSTRUCT
-{
- typedef CDrawItemStruct *From;
- void DrawWebLinkButton();
-};
-
-void CDrawItemStruct::DrawWebLinkButton()
-{
- CRawString<TCHAR,INTERNET_MAX_PATH_LENGTH> cText;
- int cchText = ::GetWindowText(hwndItem, cText.Data, cText.Size);
- COLORREF clrText = RGB(0,0,255);
- if (::GetWindowLong(hwndItem, GWL_STYLE) & BS_LEFTTEXT)
- {
- clrText = RGB(128,0,128);
- }
- RECT rcText = rcItem;
- ::DrawText(hDC, cText.Data, cchText, &rcText, DT_LEFT|DT_CALCRECT);
- ::OffsetRect(&rcText, 1, 0);
- rcItem.right = rcText.right + 1;
- rcItem.bottom = rcText.bottom + 1;
- switch (itemAction)
- {
- case ODA_DRAWENTIRE:
- ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcItem, 0, 0, 0);
- ::SetBkMode(hDC, TRANSPARENT);
- ::SetTextColor(hDC, clrText);
- ::DrawText(hDC, cText.Data, cchText, &rcText, DT_LEFT);
- rcText.top = rcText.bottom - 1;
- ::SetBkColor(hDC, clrText);
- ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcText, 0, 0, 0);
- if (itemState & ODS_FOCUS)
- {
- case ODA_FOCUS:
- if (!(itemState & ODS_NOFOCUSRECT))
- {
- ::SetTextColor(hDC, 0);
- ::SetBkColor(hDC, RGB(255,255,255));
- ::SetBkMode(hDC, OPAQUE);
- DrawFocusRect(hDC, &rcItem);
- }
- }
- break;
- }
-}
-
-/**
- * @brief Load a cursor from COMCTL32.DLL.
- */
-HCURSOR NTAPI CommCtrl_LoadCursor(LPCTSTR lpCursorName)
-{
- HMODULE hModule = GetModuleHandle(_T("COMCTL32.DLL"));
- return hModule ? LoadCursor(hModule, lpCursorName) : NULL;
-}
-
-/**
- * @brief DLGPROC for C7ZipMismatchException's ReportError() popup.
- */
-INT_PTR CALLBACK C7ZipMismatchException::DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
-{
- switch (uMsg)
- {
- case WM_INITDIALOG:
- {
- theApp.TranslateDialog(hWnd);
- if (GetDlgItem(hWnd, 9001) == NULL)
- {
- // Dialog template isn't up to date. Give it a second chance.
- EndDialog(hWnd, -1);
- return FALSE;
- }
- C7ZipMismatchException *pThis = (C7ZipMismatchException *)lParam;
- CRawString<TCHAR,2600> cText;
- CRawString<TCHAR,80> cPresent, cMissing, cOutdated, cNone, cPlugin;
- if (pThis->m_pCause)
- {
- pThis->m_pCause->GetErrorMessage(cText.Data, cText.Size);
- SetDlgItemText(hWnd, 107, cText.Data);
- }
- else
- {
- GetDlgItemText(hWnd, 107, cText.Data, cText.Size);
- switch (GetOptionsMgr()->GetInt(OPT_ARCHIVE_ENABLE))
- {
- case 0:
- lstrcat(cText.Data, theApp.LoadString(IDS_MERGE7Z_ENABLE_0).c_str());
- break;
- case 2:
- lstrcat(cText.Data, theApp.LoadString(IDS_MERGE7Z_ENABLE_2).c_str());
- break;
- }
- SetDlgItemText(hWnd, 107, cText.Data);
- }
- GetDlgItemText(hWnd, 112, cPresent.Data, cPresent.Size);
- GetDlgItemText(hWnd, 122, cMissing.Data, cMissing.Size);
- GetDlgItemText(hWnd, 132, cOutdated.Data, cOutdated.Size);
- GetDlgItemText(hWnd, 120, cNone.Data, cNone.Size);
- GetDlgItemText(hWnd, 102, cPlugin.Data, cPlugin.Size);
- wsprintf(cText.Data, cPlugin.Data, DllBuild_Merge7z);
- SetDlgItemText(hWnd, 102, cText.Data);
- SetDlgItemText
- (
- hWnd, 109,
- (
- pThis->m_dwVer7zRecommended == pThis->m_dwVer7zInstalled
- || pThis->m_dwVer7zRecommended == pThis->m_dwVer7zLocal
- ) ? cPresent.Data : cMissing.Data
- );
- DWORD dwDllBuild = FormatVersion(cText.Data, cPlugin.Data, pThis->m_dwVer7zRecommended);
- SetDlgItemText(hWnd, 110, *cText.Data ? cText.Data : cNone.Data);
- SetDlgItemText(hWnd, 111, cPlugin.Data);
- SetDlgItemText(hWnd, 112, *cPlugin.Data == '\0' ? cPlugin.Data :
- dwDllBuild == ~0 ? cMissing.Data : dwDllBuild < DllBuild_Merge7z ? cOutdated.Data : cPresent.Data);
- dwDllBuild = FormatVersion(cText.Data, cPlugin.Data, pThis->m_dwVer7zInstalled);
- SetDlgItemText(hWnd, 120, *cText.Data ? cText.Data : cNone.Data);
- SetDlgItemText(hWnd, 121, cPlugin.Data);
- SetDlgItemText(hWnd, 122, *cPlugin.Data == '\0' ? cPlugin.Data :
- dwDllBuild == ~0 ? cMissing.Data : dwDllBuild < DllBuild_Merge7z ? cOutdated.Data : cPresent.Data);
- dwDllBuild = FormatVersion(cText.Data, cPlugin.Data, pThis->m_dwVer7zLocal);
- SetDlgItemText(hWnd, 130, *cText.Data ? cText.Data : cNone.Data);
- SetDlgItemText(hWnd, 131, cPlugin.Data);
- SetDlgItemText(hWnd, 132, *cPlugin.Data == '\0' ? cPlugin.Data :
- dwDllBuild == ~0 ? cMissing.Data : dwDllBuild < DllBuild_Merge7z ? cOutdated.Data : cPresent.Data);
- GetModuleFileName(0, cText.Data, MAX_PATH);
- PathRemoveFileSpec(cText.Data);
- PathAppend(cText.Data, _T("Merge7z*.dll"));
- DlgDirListDLLs(hWnd, cText.Data, 105);
- if (DWORD cchPath = GetEnvironmentVariable(_T("path"), 0, 0))
- {
- static const TCHAR cSep[] = _T(";");
- LPTSTR pchPath = new TCHAR[cchPath];
- GetEnvironmentVariable(_T("PATH"), pchPath, cchPath);
- LPTSTR pchItem = pchPath;
- while (int cchItem = StrCSpn(pchItem += StrSpn(pchItem, cSep), cSep))
- {
- if (cchItem < MAX_PATH)
- {
- CopyMemory(cText.Data, pchItem, cchItem*sizeof*pchItem);
- cText.Data[cchItem] = 0;
- PathAppend(cText.Data, _T("Merge7z*.dll"));
- DlgDirListDLLs(hWnd, cText.Data, 105);
- }
- pchItem += cchItem;
- }
- delete[] pchPath;
- }
- if (SendDlgItemMessage(hWnd, 105, LB_GETCOUNT, 0, 0) == 0)
- {
- SendDlgItemMessage(hWnd, 105, LB_ADDSTRING, 0, (LPARAM) cNone.Data);
- }
- HICON hIcon = LoadIcon(0, IDI_EXCLAMATION);
- SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM) hIcon);
- SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM) hIcon);
- if (pThis->m_bShowAllways)
- {
- ShowWindow(GetDlgItem(hWnd, 106), SW_HIDE);
- }
- } return TRUE;
- case WM_DRAWITEM:
- {
- switch (wParam)
- {
- case 108:
- CDrawItemStruct::From(lParam)->DrawWebLinkButton();
- break;
- }
- } return TRUE;
- case WM_SETCURSOR:
- {
- HCURSOR hCursor = 0;
- switch (GetDlgCtrlID((HWND)wParam))
- {
- case 108:
- hCursor = CommCtrl_LoadCursor(MAKEINTRESOURCE(108));
- break;
- }
- if (hCursor)
- {
- SetCursor(hCursor);
- SetWindowLongPtr(hWnd, DWLP_MSGRESULT, 1);
- return TRUE;
- }
- } return FALSE;
- case WM_COMMAND:
- {
- switch (wParam)
- {
- case IDOK:
- case IDCANCEL:
- {
- LRESULT nDontShowAgain = SendDlgItemMessage(hWnd, 106, BM_GETCHECK, 0, 0);
- EndDialog(hWnd, MAKEWORD(IDOK, nDontShowAgain));
- } break;
- case 108:
- {
- HINSTANCE h = ShellExecute(hWnd, _T("open"), m_strDownloadURL, 0, 0, SW_SHOWNORMAL);
- if ((UINT)h > 32)
- {
- LONG lStyle = ::GetWindowLong((HWND)lParam, GWL_STYLE);
- ::SetWindowLong((HWND)lParam, GWL_STYLE, lStyle|BS_LEFTTEXT);
- ::InvalidateRect((HWND)lParam, 0, TRUE);
- }
- else
- {
- MessageBeep(0);
- }
- } break;
- }
- } return TRUE;
- }
- return FALSE;
-}
-
-/**
- * @brief Tell user what went wrong and how she can help.
- */
-int C7ZipMismatchException::ReportError(UINT nType, UINT nMessageID)
-{
- UINT_PTR response = -1;
- m_bShowAllways = nMessageID;
- if (!m_bShowAllways)
- {
- // Suppress error message in case 7-Zip is not installed.
- response =
- (
- m_dwVer7zInstalled || m_dwVer7zLocal
- ? theApp.GetProfileInt(REGISTRY_SECTION_MESSAGEBOX, m_strRegistryKey, -1)
- : IDOK
- );
- }
- if (response == -1)
- {
- HWND hwndOwner = CWnd::GetSafeOwner()->GetSafeHwnd();
- response = DialogBoxParam(AfxGetResourceHandle(), MAKEINTRESOURCE(IDD_MERGE7ZMISMATCH), hwndOwner, DlgProc, (LPARAM)this);
- if (response == -1)
- {
- response = DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_MERGE7ZMISMATCH), hwndOwner, DlgProc, (LPARAM)this);
- ASSERT(response != -1);
- }
- if (HIBYTE(response) == 1)
- {
- theApp.WriteProfileInt(REGISTRY_SECTION_MESSAGEBOX, m_strRegistryKey, response = int LOBYTE(response));
- }
- }
- return response;
-}
-
-/**
- * @brief Check whether archive support is available.
- */
-int NTAPI HasZipSupport()
-{
- static int HasZipSupport = -1;
- if (HasZipSupport == -1)
- {
- try
- {
- m_Merge7z.operator->();
- HasZipSupport = 1;
- }
- catch (CException *e)
- {
- e->Delete();
- HasZipSupport = 0;
- }
- }
- return HasZipSupport;
-}
-
-/**
- * @brief Tell user why archive support is not available.
- */
-void NTAPI Recall7ZipMismatchError()
-{
- try
- {
- m_Merge7z.operator->();
- }
- catch (CException *e)
- {
- e->ReportError(MB_ICONEXCLAMATION, TRUE);
- e->Delete();
- }
-}
-
-/**
- * @brief Delete head of temp path context list, and return its parent context.
- */
-CTempPathContext *CTempPathContext::DeleteHead()
-{
- CTempPathContext *pParent = m_pParent;
- delete this;
- return pParent;
-}
-
-BOOL NTAPI IsMerge7zEnabled()
-{
- return AfxGetApp()->GetProfileInt(_T("Merge7z"), _T("Enable"), 0);
-}
-
-/**
- * @brief Return installed or local version of 7-Zip.
- */
-DWORD NTAPI VersionOf7z(BOOL bLocal)
-{
- TCHAR path[MAX_PATH];
- if (bLocal)
- {
- GetModuleFileName(0, path, sizeof path/sizeof*path);
- PathRemoveFileSpec(path);
- }
- else
- {
- static const TCHAR szSubKey[] = _T("Software\\7-Zip");
- static const TCHAR szValue[] = _T("Path");
- DWORD type = 0;
- DWORD size = sizeof path;
- SHGetValue(HKEY_LOCAL_MACHINE, szSubKey, szValue, &type, path, &size);
- }
- PathAppend(path, _T("7z.dll"));
- unsigned versionMS = 0;
- unsigned versionLS = 0;
- CVersionInfo(path).GetFixedFileVersion(versionMS, versionLS);
- return versionMS;
-}
-
-/**
- * @brief Access dll functions through proxy.
- */
-interface Merge7z *Merge7z::Proxy::operator->()
-{
- // As long as the Merge7z*.DLL has not yet been loaded, Merge7z
- // [0] points to the name of the DLL (with placeholders for 7-
- // Zip major and minor version numbers). Once the DLL has been
- // loaded successfully, Merge7z[0] is set to NULL, causing the
- // if to fail on subsequent calls.
-
- if (const char *format = Merge7z[0])
- {
- // Merge7z has not yet been loaded
-
- char name[MAX_PATH];
- DWORD flags = ~0;
- CException *pCause = NULL;
- switch (GetOptionsMgr()->GetInt(OPT_ARCHIVE_ENABLE))
- {
- case 1: //Use installed 7-Zip if present. Otherwise, use local 7-Zip.
- if (DWORD ver = VersionOf7z(FALSE))
- {
- flags = Initialize::Default;
- try
- {
- wsprintfA(name, format, UINT HIWORD(ver), UINT LOWORD(ver));
- Merge7z[0] = name;
- stub.Load();
- break;
- }
- catch (CException *e)
- {
- Merge7z[0] = format;
- pCause = e;
- }
- }
- case 2: //Always use local 7-Zip.
- if (DWORD ver = VersionOf7z(TRUE))
- {
- flags = Initialize::Default | Initialize::Local7z;
- try
- {
- wsprintfA(name, format, UINT HIWORD(ver), UINT LOWORD(ver));
- Merge7z[0] = name;
- stub.Load();
- break;
- }
- catch (CException *e)
- {
- Merge7z[0] = format;
- if (pCause) pCause->Delete();
- pCause = e;
- }
- }
- default:
- throw new C7ZipMismatchException
- (
- VersionOf7z(FALSE),
- VersionOf7z(TRUE),
- pCause
- );
- }
- LANGID wLangID = (LANGID)GetThreadLocale();
- flags |= wLangID << 16;
- if (GetOptionsMgr()->GetBool(OPT_ARCHIVE_PROBETYPE))
- {
- flags |= Initialize::GuessFormatBySignature | Initialize::GuessFormatByExtension;
- }
- if (Merge7z[1])
- ((interface Merge7z *)Merge7z[1])->Initialize(flags);
- }
- return ((interface Merge7z *)Merge7z[1]);
-}
-
-/**
- * @brief Tell Merge7z we are going to enumerate just 1 item.
- */
-UINT SingleItemEnumerator::Open()
-{
- return 1;
-}
-
-/**
- * @brief Pass information about the item to Merge7z.
- */
-Merge7z::Envelope *SingleItemEnumerator::Enum(Item &item)
-{
- item.Mask.Item = item.Mask.Name|item.Mask.FullPath|item.Mask.Recurse;
- item.Name = Name;
- item.FullPath = FullPath;
- return 0;
-}
-
-/**
- * @brief SingleFileEnumerator constructor.
- */
-SingleItemEnumerator::SingleItemEnumerator(LPCTSTR path, LPCTSTR FullPath, LPCTSTR Name)
-: FullPath(FullPath)
-, Name(Name)
-{
-}
-
-/**
- * @brief Construct a CDirView::DirItemEnumerator.
- *
- * Argument *nFlags* controls operation as follows:
- * LVNI_ALL: Enumerate all items.
- * LVNI_SELECTED: Enumerate selected items only.
- * Original: Set folder prefix for first iteration to "original"
- * Altered: Set folder prefix for second iteration to "altered"
- * BalanceFolders: Ensure that all nonempty folders on either side have a
- * corresponding folder on the other side, even if it is
- * empty (DirScan doesn't recurse into folders which
- * appear only on one side).
- * DiffsOnly: Enumerate diffs only.
- */
-CDirView::DirItemEnumerator::DirItemEnumerator(CDirView *pView, int nFlags)
-: m_pView(pView)
-, m_nFlags(nFlags)
-{
- if (m_nFlags & Original)
- {
- m_rgFolderPrefix.push_back(_T("original"));
- }
- if (m_nFlags & Altered)
- {
- m_rgFolderPrefix.push_back(_T("altered"));
- }
- if (m_nFlags & BalanceFolders)
- {
- // Collect implied folders
- for (UINT i = Open() ; i-- ; )
- {
- const DIFFITEM &di = Next();
- if ((m_nFlags & DiffsOnly) && !m_pView->IsItemNavigableDiff(di))
- {
- continue;
- }
- if (m_bRight)
- {
- // Enumerating items on right side
- if (!di.diffcode.isSideFirstOnly())
- {
- // Item is present on right side, i.e. folder is implied
- m_rgImpliedFoldersRight[di.diffFileInfo[1].path.get()] = PVOID(1);
- }
- }
- else
- {
- // Enumerating items on left side
- if (!di.diffcode.isSideSecondOnly())
- {
- // Item is present on left side, i.e. folder is implied
- m_rgImpliedFoldersLeft[di.diffFileInfo[0].path.get()] = PVOID(1);
- }
- }
- }
- }
-}
-
-/**
- * @brief Initialize enumerator, return number of items to be enumerated.
- */
-UINT CDirView::DirItemEnumerator::Open()
-{
- m_nIndex = -1;
- m_curFolderPrefix = m_rgFolderPrefix.begin();
- m_bRight = (m_nFlags & Right) != 0;
- size_t nrgFolderPrefix = m_rgFolderPrefix.size();
- if (nrgFolderPrefix)
- {
- m_strFolderPrefix = *m_curFolderPrefix++;
- }
- else
- {
- nrgFolderPrefix = 1;
- }
- return
- (
- m_nFlags & LVNI_SELECTED
- ? pView(m_pView)->GetSelectedCount()
- : pView(m_pView)->GetItemCount()
- ) * nrgFolderPrefix;
-}
-
-/**
- * @brief Return next item.
- */
-const DIFFITEM &CDirView::DirItemEnumerator::Next()
-{
- enum {nMask = LVNI_FOCUSED|LVNI_SELECTED|LVNI_CUT|LVNI_DROPHILITED};
- while ((m_nIndex = pView(m_pView)->GetNextItem(m_nIndex, m_nFlags & nMask)) == -1)
- {
- m_strFolderPrefix = *m_curFolderPrefix++;
- m_bRight = TRUE;
- }
- return m_pView->GetDiffItem(m_nIndex);
-}
-
-/**
- * @brief Pass information about an item to Merge7z.
- *
- * Information is passed through struct Merge7z::DirItemEnumerator::Item.
- * The *mask* member denotes which of the other members contain valid data.
- * If *mask* is zero upon return, which will be the case if Enum() decides to
- * leave the struct untouched, Merge7z will ignore the item.
- * If Enum() allocates temporary storage for string members, it must also
- * allocate an Envelope, providing a Free() method to free the temporary
- * storage, along with the Envelope itself. The Envelope pointer is passed to
- * Merge7z as the return value of the function. It is not meant to be a success
- * indicator, so if no temporary storage is required, it is perfectly alright
- * to return NULL.
- */
-Merge7z::Envelope *CDirView::DirItemEnumerator::Enum(Item &item)
-{
- CDirDoc * pDoc = m_pView->GetDocument();
- const DIFFITEM &di = Next();
-
- if ((m_nFlags & DiffsOnly) && !m_pView->IsItemNavigableDiff(di))
- {
- return 0;
- }
-
- bool isSideFirst = di.diffcode.isSideFirstOnly();
- bool isSideSecond = di.diffcode.isSideSecondOnly();
-
- Envelope *envelope = new Envelope;
-
- const String &sFilename = m_bRight ? di.diffFileInfo[1].filename : di.diffFileInfo[0].filename;
- const String &sSubdir = m_bRight ? di.diffFileInfo[1].path : di.diffFileInfo[0].path;
- envelope->Name = sFilename;
- if (sSubdir.length())
- {
- envelope->Name.insert(0, _T("\\"));
- envelope->Name.insert(0, sSubdir);
- }
- envelope->FullPath = sFilename;
- envelope->FullPath.insert(0, _T("\\"));
- envelope->FullPath.insert(0, m_bRight ?
- di.getFilepath(1, pDoc->GetBasePath(1)) :
- di.getFilepath(0, pDoc->GetBasePath(0)));
-
- UINT32 Recurse = item.Mask.Recurse;
-
- if (m_nFlags & BalanceFolders)
- {
- if (m_bRight)
- {
- // Enumerating items on right side
- if (isSideFirst)
- {
- // Item is missing on right side
- PVOID &implied = m_rgImpliedFoldersRight[di.diffFileInfo[0].path.get()];
- if (!implied)
- {
- // Folder is not implied by some other file, and has
- // not been enumerated so far, so enumerate it now!
- envelope->Name = di.diffFileInfo[0].path;
- envelope->FullPath = di.getFilepath(0, pDoc->GetBasePath(0));
- implied = PVOID(2); // Don't enumerate same folder twice!
- isSideFirst = false;
- Recurse = 0;
- }
- }
- }
- else
- {
- // Enumerating items on left side
- if (isSideSecond)
- {
- // Item is missing on left side
- PVOID &implied = m_rgImpliedFoldersLeft[di.diffFileInfo[1].path.get()];
- if (!implied)
- {
- // Folder is not implied by some other file, and has
- // not been enumerated so far, so enumerate it now!
- envelope->Name = di.diffFileInfo[1].path;
- envelope->FullPath = di.getFilepath(1, pDoc->GetBasePath(1));
- implied = PVOID(2); // Don't enumerate same folder twice!
- isSideSecond = false;
- Recurse = 0;
- }
- }
- }
- }
-
- if (m_bRight ? isSideFirst : isSideSecond)
- {
- return envelope;
- }
-
- if (m_strFolderPrefix.length())
- {
- if (envelope->Name.length())
- envelope->Name.insert(0, _T("\\"));
- envelope->Name.insert(0, m_strFolderPrefix);
- }
-
- item.Mask.Item = item.Mask.Name|item.Mask.FullPath|item.Mask.CheckIfPresent|Recurse;
- item.Name = envelope->Name.c_str();
- item.FullPath = envelope->FullPath.c_str();
- return envelope;
-}
-
-/**
- * @brief Apply appropriate handlers from left to right.
- */
-bool CDirView::DirItemEnumerator::MultiStepCompressArchive(LPCTSTR path)
-{
- DeleteFile(path);
- Merge7z::Format *piHandler = ArchiveGuessFormat(path);
- if (piHandler)
- {
- HWND hwndOwner = CWnd::GetSafeOwner()->GetSafeHwnd();
- CString pathIntermediate;
- SysFreeString(Assign(pathIntermediate, piHandler->GetDefaultName(hwndOwner, path)));
- String pathPrepend = path;
- pathPrepend.resize(pathPrepend.rfind('\\') + 1);
- pathIntermediate.Insert(0, pathPrepend.c_str());
- bool bDone = MultiStepCompressArchive(pathIntermediate);
- if (bDone)
- {
- piHandler->CompressArchive(hwndOwner, path,
- &SingleItemEnumerator(path, pathIntermediate));
- DeleteFile(pathIntermediate);
- }
- else
- {
- piHandler->CompressArchive(hwndOwner, path, this);
- }
- return true;
- }
- return false;
-}
-
-/**
- * @brief Generate archive from DirView items.
- */
-void CDirView::DirItemEnumerator::CompressArchive(LPCTSTR path)
-{
- String strPath;
- if (path == 0)
- {
- // No path given, so prompt for path!
- static const TCHAR _T_Merge7z[] = _T("Merge7z");
- static const TCHAR _T_FilterIndex[] = _T("FilterIndex");
- // 7z311 can only write 7z, zip, and tar(.gz|.bz2) archives, so don't
- // offer other formats here!
- static const TCHAR _T_Filter[]
- (
- _T("7z|*.7z|")
- //_T("z|*.z|")
- _T("zip|*.zip|")
- _T("jar (zip)|*.jar|")
- _T("ear (zip)|*.ear|")
- _T("war (zip)|*.war|")
- _T("xpi (zip)|*.xpi|")
- //_T("rar|*.rar|")
- _T("tar|*.tar|")
- _T("tar.z|*.tar.z|")
- _T("tar.gz|*.tar.gz|")
- _T("tar.bz2|*.tar.bz2|")
- //_T("tz|*.tz|")
- _T("tgz|*.tgz|")
- _T("tbz2|*.tbz2|")
- //_T("lzh|*.lzh|")
- //_T("cab|*.cab|")
- //_T("arj|*.arj|")
- //_T("deb|*.deb|")
- //_T("rpm|*.rpm|")
- //_T("cpio|*.cpio|")
- //_T("|")
- );
- String strFilter; // = CExternalArchiveFormat::GetOpenFileFilterString();
- strFilter.insert(0, _T_Filter);
- strFilter += _T("|");
- CFileDialog dlg
- (
- FALSE,
- 0,
- 0,
- OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN,
- strFilter.c_str()
- );
- dlg.m_ofn.nFilterIndex = AfxGetApp()->GetProfileInt(_T_Merge7z, _T_FilterIndex, 1);
- // Use extension from current filter as default extension:
- if (int i = dlg.m_ofn.nFilterIndex)
- {
- dlg.m_ofn.lpstrDefExt = dlg.m_ofn.lpstrFilter;
- while (*dlg.m_ofn.lpstrDefExt && --i)
- {
- dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 1;
- dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 1;
- }
- if (*dlg.m_ofn.lpstrDefExt)
- {
- dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 3;
- }
- }
- if (dlg.DoModal() == IDOK)
- {
- strPath = dlg.GetPathName();
- path = strPath.c_str();
- AfxGetApp()->WriteProfileInt(_T_Merge7z, _T_FilterIndex, dlg.m_ofn.nFilterIndex);
- }
- }
- if (path && !MultiStepCompressArchive(path))
- {
- LangMessageBox(IDS_UNKNOWN_ARCHIVE_FORMAT, MB_ICONEXCLAMATION);
- }
-}
-
-/**
- * @brief Collect files for SHFileOperation
- */
-void CDirView::DirItemEnumerator::CollectFiles(String &strBuffer)
-{
- CDirDoc *pDoc = m_pView->GetDocument();
- const String sLeftRootPath = pDoc->GetBasePath(0);
- const String sRightRootPath = pDoc->GetBasePath(1);
- UINT i;
- int cchBuffer = 0;
- for (i = Open() ; i-- ; )
- {
- const DIFFITEM &di = Next();
- if (m_bRight ? m_pView->IsItemOpenableOnRightWith(di) : m_pView->IsItemOpenableOnLeftWith(di))
- {
- cchBuffer +=
- (
- m_bRight ? di.getFilepath(1, sLeftRootPath) : di.getFilepath(0, sRightRootPath)
- ).length() + (m_bRight ? di.diffFileInfo[1].filename : di.diffFileInfo[0].filename).get().length() + 2;
- }
- }
- strBuffer.resize(cchBuffer + 1);
- LPTSTR pchBuffer = &strBuffer[0];
- for (i = Open() ; i-- ; )
- {
- const DIFFITEM &di = Next();
- if (m_bRight ? m_pView->IsItemOpenableOnRightWith(di) : m_pView->IsItemOpenableOnLeftWith(di))
- {
- pchBuffer += wsprintf
- (
- pchBuffer,
- _T("%s\\%s"),
- m_bRight ? di.getFilepath(1, sLeftRootPath).c_str() : di.getFilepath(0, sRightRootPath).c_str(),
- m_bRight ? di.diffFileInfo[1].filename.get().c_str() : di.diffFileInfo[0].filename.get().c_str()
- ) + 1;
- }
- }
- ASSERT(pchBuffer - &strBuffer[0] == cchBuffer);
-}
+/////////////////////////////////////////////////////////////////////////////\r
+// WinMerge: an interactive diff/merge utility\r
+// Copyright (C) 1997-2000 Thingamahoochie Software\r
+// Author: Dean Grimm\r
+//\r
+// This program is free software; you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation; either version 2 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software\r
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\r
+//\r
+/////////////////////////////////////////////////////////////////////////////\r
+\r
+/* 7zCommon.cpp: Implement 7z related classes and functions\r
+ * Copyright (c) 2003 Jochen Tucht\r
+ *\r
+ * Remarks: Different versions of 7-Zip are interfaced through specific\r
+ * versions of Merge7z (Merge7z311.dll, Merge7z312.dll, etc.)\r
+ * WinMerge can either use an installed copy of the 7-Zip software,\r
+ * or fallback to a local set of 7-Zip DLLs, which are to be included\r
+ * in the WinMerge binary distribution.\r
+ *\r
+ * Fallback policies are as follows:\r
+ *\r
+ * 1. Detect 7-Zip version installed (XXX).\r
+ * 2. If there is a Merge7zXXX.dll, be happy to use it\r
+ * 3. Detect 7-Zip version from WinMerge distribution (YYY).\r
+ * 4. If there is a Merge7zYYY.dll, be happy to use it.\r
+ * 5. Sorry, no way.\r
+ *\r
+ * These rules can be customized by setting a registry variable\r
+ * *Merge7z/Enable* of type DWORD to one of the following values:\r
+ *\r
+ * 0 - Entirely disable 7-Zip integration.\r
+ * 1 - Use installed 7-Zip if present. Otherwise, use local 7-Zip.\r
+ * 2 - Always use local 7-Zip.\r
+ *\r
+\r
+Please mind 2. a) of the GNU General Public License, and log your changes below.\r
+\r
+DATE: BY: DESCRIPTION:\r
+========== ================== ================================================\r
+2003-12-09 Jochen Tucht Created\r
+2003-12-16 Jochen Tucht Properly generate .tar.gz and .tar.bz2\r
+2003-12-16 Jochen Tucht Obtain long path to temporary folder\r
+2004-01-20 Jochen Tucht Complain only once if Merge7z*.dll is missing\r
+2004-01-25 Jochen Tucht Fix bad default for OPENFILENAME::nFilterIndex\r
+2004-03-15 Jochen Tucht Fix Visual Studio 2003 build issue\r
+2004-04-13 Jochen Tucht Avoid StrNCat to get away with shlwapi 4.70\r
+2004-08-25 Jochen Tucht More explicit error message\r
+2004-10-17 Jochen Tucht Leave decision whether to recurse into folders\r
+ to enumerator (Mask.Recurse)\r
+2004-11-03 Jochen Tucht FIX [1048997] as proposed by Kimmo 2004-11-02\r
+2005-01-15 Jochen Tucht Read 7-Zip version from 7zip_pad.xml\r
+ Set Merge7z UI language if DllBuild_Merge7z >= 9\r
+2005-01-22 Jochen Tucht Better explain what's present/missing/outdated\r
+2005-02-05 Jochen Tucht Fall back to IDD_MERGE7ZMISMATCH template from\r
+ .exe if .lang file isn't up to date.\r
+2005-02-26 Jochen Tucht Add download link to error message\r
+2005-02-26 Jochen Tucht Use WinAPI to obtain ISO language/region codes\r
+2005-02-27 Jochen Tucht FIX [1152375]\r
+2005-04-24 Kimmo Varis Don't use DiffContext exported from DirView\r
+2005-06-08 Kimmo Varis Use DIFFITEM, not reference to it (hopefully only\r
+ temporarily, to sort out new directory compare)\r
+2005-06-22 Jochen Tucht Change recommended version of 7-Zip to 4.20\r
+ Remove noise from Nagbox\r
+2005-07-03 Jochen Tucht DIFFITEM has changed due to RFE [ 1205516 ]\r
+2005-07-04 Jochen Tucht New global ArchiveGuessFormat() checks for\r
+ formats to be handled by external command line\r
+ tools. These take precedence over Merge7z\r
+ internal handlers.\r
+2005-07-05 Jochen Tucht Move to Merge7z::Format::GetDefaultName() to\r
+ build intermediate filenames for multi-step\r
+ compression.\r
+2005-07-15 Jochen Tucht Remove external command line tool integration\r
+ for now. Rethink about it after 2.4 branch.\r
+2005-08-20 Jochen Tucht Option to guess archive format by signature\r
+ Map extensions through ExternalArchiveFormat.ini\r
+2005-08-23 Jochen Tucht Option to entirely disable 7-Zip integration\r
+2007-01-04 Kimmo Varis Convert using COptionsMgr for options.\r
+2007-06-16 Jochen Neubeck FIX [1723263] "Zip --> Both" operation...\r
+2007-12-22 Jochen Neubeck Fix Merge7z UI lang for new translation system\r
+ Change recommended version of 7-Zip to 4.57\r
+2010-05-16 Jochen Neubeck Read 7-Zip version from 7z.dll (which has long\r
+ ago replaced the various format and codec DLLs)\r
+ Change recommended version of 7-Zip to 4.65\r
+*/\r
+\r
+// ID line follows -- this is updated by SVN\r
+// $Id: 7zCommon.cpp 7169 2010-05-16 14:44:19Z jtuc $\r
+\r
+#include "stdafx.h"\r
++#include "7zCommon.h"\r
++#include <afxinet.h>\r
++#include <shlwapi.h>\r
+#include "OptionsDef.h"\r
++#include "OptionsMgr.h"\r
+#include "Merge.h" // DirDocFilter theApp GetOptionsMgr()\r
+#include "resource.h"\r
- #include "DirDoc.h"\r
- #include "MainFrm.h"\r
+#include "DirView.h"\r
- #include "7zCommon.h"\r
++#include "DirDoc.h"\r
+#include "DirActions.h"\r
+//#include "ExternalArchiveFormat.h"\r
+#include "version.h"\r
- #include <afxinet.h>\r
- #include <shlwapi.h>\r
- #include <paths.h>\r
++#include "paths.h"\r
+#include "Environment.h"\r
+\r
+#ifdef _DEBUG\r
+#define new DEBUG_NEW\r
+#undef THIS_FILE\r
+static char THIS_FILE[] = __FILE__;\r
+#endif\r
+\r
+/**\r
+ * @brief Proxy for Merge7z\r
+ */\r
+static __declspec(thread) Merge7z::Proxy m_Merge7z =\r
+{\r
+ { 0, 0, DllBuild_Merge7z, },\r
+ "Merge7z%u%02u"DECORATE_U".dll",\r
+ "Merge7z",\r
+ NULL\r
+};\r
+\r
+/**\r
+ * @brief assign BSTR to String, and return BSTR for optional SysFreeString()\r
+ */\r
+inline BSTR Assign(CString &dst, BSTR src)\r
+{\r
+ dst = src;\r
+ return src;\r
+}\r
+\r
+bool IsArchiveFile(const String& pszFile)\r
+{\r
+ try {\r
+ Merge7z::Format *piHandler = ArchiveGuessFormat(pszFile);\r
+ if (piHandler)\r
+ return TRUE;\r
+ else\r
+ return FALSE;\r
+ }\r
+ catch (CException *e)\r
+ {\r
+ e->Delete();\r
+ return FALSE;\r
+ }\r
+ return FALSE;\r
+}\r
+\r
+/**\r
+ * @brief Wrap Merge7z::GuessFormat() to allow for some customizing:\r
+ * - Check if 7-Zip integration is enabled.\r
+ * - Check for filename extension mappings.\r
+ */\r
+Merge7z::Format *ArchiveGuessFormat(const String& path)\r
+{\r
+ if (GetOptionsMgr()->GetInt(OPT_ARCHIVE_ENABLE) == 0)\r
+ return NULL;\r
+ if (paths_IsDirectory(path))\r
+ return NULL;\r
+ String path2(path);\r
+ // Map extensions through ExternalArchiveFormat.ini\r
+ static TCHAR null[] = _T("");\r
+ static const TCHAR section[] = _T("extensions");\r
+ String entry = paths_FindExtension(path);\r
+ TCHAR value[20];\r
+ static LPCTSTR filename = NULL;\r
+ if (filename == NULL)\r
+ {\r
+ TCHAR cPath[INTERNET_MAX_PATH_LENGTH];\r
+ DWORD cchPath = SearchPath(NULL, _T("ExternalArchiveFormat.ini"), NULL,\r
+ INTERNET_MAX_PATH_LENGTH, cPath, NULL);\r
+ filename = cchPath && cchPath < INTERNET_MAX_PATH_LENGTH ? StrDup(cPath) : null;\r
+ }\r
+ if (*filename &&\r
+ GetPrivateProfileString(section, entry.c_str(), null, value, 20, filename) &&\r
+ *value == '.')\r
+ {\r
+ // Remove end-of-line comments (in string returned from GetPrivateProfileString)\r
+ // that is, remove semicolon & whatever follows it\r
+ if (LPTSTR p = StrChr(value, ';'))\r
+ {\r
+ *p = '\0';\r
+ StrTrim(value, _T(" \t"));\r
+ }\r
+ path2 = value;\r
+ }\r
+\r
+ // PATCH [ 1229867 ] RFE [ 1205516 ], RFE [ 887948 ], and other issues\r
+ // command line integration portion is not yet applied\r
+ // so following code not yet valid, so temporarily commented out\r
+ // Look for command line tool first\r
+ /*Merge7z::Format *pFormat;\r
+ if (CExternalArchiveFormat::GuessFormat(path, pFormat))\r
+ {\r
+ return pFormat;\r
+ }*/\r
+ // Default to Merge7z*.dll\r
+\r
+ return m_Merge7z->GuessFormat(path2.c_str());\r
+}\r
+\r
+/**\r
+ * @brief Self-initializing raw C character buffer class.\r
+ */\r
+template<class TYPE, size_t SIZE> struct CRawString\r
+{\r
+ enum { Size = SIZE };\r
+ TYPE Data[SIZE];\r
+ CRawString()\r
+ {\r
+ Data[0] = 0;\r
+ }\r
+};\r
+\r
+/**\r
+ * @brief Exception class for more explicit error message.\r
+ */\r
+class C7ZipMismatchException : public CException\r
+{\r
+public:\r
+ C7ZipMismatchException(DWORD dwVer7zInstalled, DWORD dwVer7zLocal, CException *pCause)\r
+ {\r
+ m_dwVer7zInstalled = dwVer7zInstalled;\r
+ m_dwVer7zLocal = dwVer7zLocal;\r
+ m_pCause = pCause;\r
+ }\r
+ ~C7ZipMismatchException()\r
+ {\r
+ if (m_pCause)\r
+ m_pCause->Delete();\r
+ }\r
+ virtual int ReportError(UINT nType = MB_OK, UINT nMessageID = 0);\r
+protected:\r
+ DWORD m_dwVer7zInstalled;\r
+ DWORD m_dwVer7zLocal;\r
+ CException *m_pCause;\r
+ BOOL m_bShowAllways;\r
+ static const DWORD m_dwVer7zRecommended;\r
+ static const TCHAR m_strRegistryKey[];\r
+ static const TCHAR m_strDownloadURL[];\r
+ static INT_PTR CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);\r
+ static DWORD FormatVersion(LPTSTR, LPTSTR, DWORD);\r
+};\r
+\r
+/**\r
+ * @brief Recommended version of 7-Zip.\r
+ */\r
+const DWORD C7ZipMismatchException::m_dwVer7zRecommended = DWORD MAKELONG(65,4);\r
+\r
+/**\r
+ * @brief Registry key for C7ZipMismatchException's ReportError() popup.\r
+ */\r
+const TCHAR C7ZipMismatchException::m_strRegistryKey[] = _T("7ZipMismatch");\r
+\r
+/**\r
+ * @brief Download URL for C7ZipMismatchException's ReportError() popup.\r
+ */\r
+const TCHAR C7ZipMismatchException::m_strDownloadURL[] = _T("https://sourceforge.net/project/showfiles.php?group_id=13216&package_id=143957");\r
+\r
+/**\r
+ * @brief Retrieve build number of given DLL.\r
+ */\r
+static DWORD NTAPI GetDllBuild(LPCTSTR cPath)\r
+{\r
+ HMODULE hModule = LoadLibrary(cPath);\r
+ DLLVERSIONINFO dvi;\r
+ dvi.cbSize = sizeof dvi;\r
+ dvi.dwBuildNumber = ~0UL;\r
+ if (hModule)\r
+ {\r
+ dvi.dwBuildNumber = 0UL;\r
+ DLLGETVERSIONPROC DllGetVersion = (DLLGETVERSIONPROC)GetProcAddress(hModule, "DllGetVersion");\r
+ if (DllGetVersion)\r
+ {\r
+ DllGetVersion(&dvi);\r
+ }\r
+ FreeLibrary(hModule);\r
+ }\r
+ return dvi.dwBuildNumber;\r
+}\r
+\r
+/**\r
+ * @brief Format Merge7z version number and plugin name, and retrieve DllBuild.\r
+ */\r
+DWORD C7ZipMismatchException::FormatVersion(LPTSTR pcVersion, LPTSTR pcPluginName, DWORD dwVersion)\r
+{\r
+ *pcVersion = '\0';\r
+ *pcPluginName = '\0';\r
+ if (dwVersion)\r
+ {\r
+ wsprintf\r
+ (\r
+ pcVersion, _T("%u.%02u"),\r
+ UINT HIWORD(dwVersion),\r
+ UINT LOWORD(dwVersion)\r
+ );\r
+ wsprintf\r
+ (\r
+ pcPluginName,\r
+ sizeof(TCHAR) == 1 ? _T("Merge7z%u%02u.dll") : _T("Merge7z%u%02uU.dll"),\r
+ UINT HIWORD(dwVersion),\r
+ UINT LOWORD(dwVersion)\r
+ );\r
+ }\r
+ return GetDllBuild(pcPluginName);\r
+}\r
+\r
+/**\r
+ * @brief Populate ListBox with names/revisions of DLLs matching given pattern.\r
+ */\r
+static void NTAPI DlgDirListDLLs(HWND hWnd, LPTSTR cPattern, int nIDListBox)\r
+{\r
+ HDC hDC = GetDC(hWnd);\r
+ HFONT hFont = (HFONT)SendDlgItemMessage(hWnd, nIDListBox, WM_GETFONT, 0, 0);\r
+ int cxView = (int)SendDlgItemMessage(hWnd, nIDListBox, LB_GETHORIZONTALEXTENT, 0, 0) - 8;\r
+ HGDIOBJ hObject = SelectObject(hDC, hFont);\r
+ WIN32_FIND_DATA ff;\r
+ HANDLE h = FindFirstFile(cPattern, &ff);\r
+ if (h != INVALID_HANDLE_VALUE)\r
+ {\r
+ do\r
+ {\r
+ PathRemoveFileSpec(cPattern);\r
+ PathAppend(cPattern, ff.cFileName);\r
+ wsprintf(ff.cFileName, _T(" (dllbuild %04u)"), GetDllBuild(cPattern));\r
+ lstrcat(cPattern, ff.cFileName);\r
+ int cxText = (int)(WORD)GetTabbedTextExtent(hDC, cPattern, lstrlen(cPattern), 0, 0);\r
+ if (cxView < cxText)\r
+ cxView = cxText;\r
+ SendDlgItemMessage(hWnd, nIDListBox, LB_ADDSTRING, 0, (LPARAM)cPattern);\r
+ } while (FindNextFile(h, &ff));\r
+ FindClose(h);\r
+ }\r
+ SelectObject(hDC, hObject);\r
+ ReleaseDC(hWnd, hDC);\r
+ SendDlgItemMessage(hWnd, nIDListBox, LB_SETHORIZONTALEXTENT, cxView + 8, 0);\r
+}\r
+\r
+/**\r
+ * @brief OwnerDraw states from recent SDK.\r
+ */\r
+#define ODS_NOACCEL 0x0100\r
+#define ODS_NOFOCUSRECT 0x0200\r
+\r
+/**\r
+ * @brief WM_DRAWITEM notification handlers.\r
+ */\r
+struct CDrawItemStruct : DRAWITEMSTRUCT\r
+{\r
+ typedef CDrawItemStruct *From;\r
+ void DrawWebLinkButton();\r
+};\r
+\r
+void CDrawItemStruct::DrawWebLinkButton()\r
+{\r
+ CRawString<TCHAR,INTERNET_MAX_PATH_LENGTH> cText;\r
+ int cchText = ::GetWindowText(hwndItem, cText.Data, cText.Size);\r
+ COLORREF clrText = RGB(0,0,255);\r
+ if (::GetWindowLong(hwndItem, GWL_STYLE) & BS_LEFTTEXT)\r
+ {\r
+ clrText = RGB(128,0,128);\r
+ }\r
+ RECT rcText = rcItem;\r
+ ::DrawText(hDC, cText.Data, cchText, &rcText, DT_LEFT|DT_CALCRECT);\r
+ ::OffsetRect(&rcText, 1, 0);\r
+ rcItem.right = rcText.right + 1;\r
+ rcItem.bottom = rcText.bottom + 1;\r
+ switch (itemAction)\r
+ {\r
+ case ODA_DRAWENTIRE:\r
+ ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcItem, 0, 0, 0);\r
+ ::SetBkMode(hDC, TRANSPARENT);\r
+ ::SetTextColor(hDC, clrText);\r
+ ::DrawText(hDC, cText.Data, cchText, &rcText, DT_LEFT);\r
+ rcText.top = rcText.bottom - 1;\r
+ ::SetBkColor(hDC, clrText);\r
+ ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcText, 0, 0, 0);\r
+ if (itemState & ODS_FOCUS)\r
+ {\r
+ case ODA_FOCUS:\r
+ if (!(itemState & ODS_NOFOCUSRECT))\r
+ {\r
+ ::SetTextColor(hDC, 0);\r
+ ::SetBkColor(hDC, RGB(255,255,255));\r
+ ::SetBkMode(hDC, OPAQUE);\r
+ DrawFocusRect(hDC, &rcItem);\r
+ }\r
+ }\r
+ break;\r
+ }\r
+}\r
+\r
+/**\r
+ * @brief Load a cursor from COMCTL32.DLL.\r
+ */\r
+HCURSOR NTAPI CommCtrl_LoadCursor(LPCTSTR lpCursorName)\r
+{\r
+ HMODULE hModule = GetModuleHandle(_T("COMCTL32.DLL"));\r
+ return hModule ? LoadCursor(hModule, lpCursorName) : NULL;\r
+}\r
+\r
+/**\r
+ * @brief DLGPROC for C7ZipMismatchException's ReportError() popup.\r
+ */\r
+INT_PTR CALLBACK C7ZipMismatchException::DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\r
+{\r
+ switch (uMsg)\r
+ {\r
+ case WM_INITDIALOG:\r
+ {\r
+ theApp.TranslateDialog(hWnd);\r
+ if (GetDlgItem(hWnd, 9001) == NULL)\r
+ {\r
+ // Dialog template isn't up to date. Give it a second chance.\r
+ EndDialog(hWnd, -1);\r
+ return FALSE;\r
+ }\r
+ C7ZipMismatchException *pThis = (C7ZipMismatchException *)lParam;\r
+ CRawString<TCHAR,2600> cText;\r
+ CRawString<TCHAR,80> cPresent, cMissing, cOutdated, cNone, cPlugin;\r
+ if (pThis->m_pCause)\r
+ {\r
+ pThis->m_pCause->GetErrorMessage(cText.Data, cText.Size);\r
+ SetDlgItemText(hWnd, 107, cText.Data);\r
+ }\r
+ else\r
+ {\r
+ GetDlgItemText(hWnd, 107, cText.Data, cText.Size);\r
+ switch (GetOptionsMgr()->GetInt(OPT_ARCHIVE_ENABLE))\r
+ {\r
+ case 0:\r
+ lstrcat(cText.Data, _("\nNote: 7-Zip integration is disabled in WinMerge settings.").c_str());\r
+ break;\r
+ case 2:\r
+ lstrcat(cText.Data, _("\nNote: 7-Zip integration is restricted to standalone operation in WinMerge settings.").c_str());\r
+ break;\r
+ }\r
+ SetDlgItemText(hWnd, 107, cText.Data);\r
+ }\r
+ GetDlgItemText(hWnd, 112, cPresent.Data, cPresent.Size);\r
+ GetDlgItemText(hWnd, 122, cMissing.Data, cMissing.Size);\r
+ GetDlgItemText(hWnd, 132, cOutdated.Data, cOutdated.Size);\r
+ GetDlgItemText(hWnd, 120, cNone.Data, cNone.Size);\r
+ GetDlgItemText(hWnd, 102, cPlugin.Data, cPlugin.Size);\r
+ wsprintf(cText.Data, cPlugin.Data, DllBuild_Merge7z);\r
+ SetDlgItemText(hWnd, 102, cText.Data);\r
+ SetDlgItemText\r
+ (\r
+ hWnd, 109,\r
+ (\r
+ pThis->m_dwVer7zRecommended == pThis->m_dwVer7zInstalled\r
+ || pThis->m_dwVer7zRecommended == pThis->m_dwVer7zLocal\r
+ ) ? cPresent.Data : cMissing.Data\r
+ );\r
+ DWORD dwDllBuild = FormatVersion(cText.Data, cPlugin.Data, pThis->m_dwVer7zRecommended);\r
+ SetDlgItemText(hWnd, 110, *cText.Data ? cText.Data : cNone.Data);\r
+ SetDlgItemText(hWnd, 111, cPlugin.Data);\r
+ SetDlgItemText(hWnd, 112, *cPlugin.Data == '\0' ? cPlugin.Data :\r
+ dwDllBuild == ~0 ? cMissing.Data : dwDllBuild < DllBuild_Merge7z ? cOutdated.Data : cPresent.Data);\r
+ dwDllBuild = FormatVersion(cText.Data, cPlugin.Data, pThis->m_dwVer7zInstalled);\r
+ SetDlgItemText(hWnd, 120, *cText.Data ? cText.Data : cNone.Data);\r
+ SetDlgItemText(hWnd, 121, cPlugin.Data);\r
+ SetDlgItemText(hWnd, 122, *cPlugin.Data == '\0' ? cPlugin.Data :\r
+ dwDllBuild == ~0 ? cMissing.Data : dwDllBuild < DllBuild_Merge7z ? cOutdated.Data : cPresent.Data);\r
+ dwDllBuild = FormatVersion(cText.Data, cPlugin.Data, pThis->m_dwVer7zLocal);\r
+ SetDlgItemText(hWnd, 130, *cText.Data ? cText.Data : cNone.Data);\r
+ SetDlgItemText(hWnd, 131, cPlugin.Data);\r
+ SetDlgItemText(hWnd, 132, *cPlugin.Data == '\0' ? cPlugin.Data :\r
+ dwDllBuild == ~0 ? cMissing.Data : dwDllBuild < DllBuild_Merge7z ? cOutdated.Data : cPresent.Data);\r
+ GetModuleFileName(0, cText.Data, MAX_PATH);\r
+ PathRemoveFileSpec(cText.Data);\r
+ PathAppend(cText.Data, _T("Merge7z*.dll"));\r
+ DlgDirListDLLs(hWnd, cText.Data, 105);\r
+ if (DWORD cchPath = GetEnvironmentVariable(_T("path"), 0, 0))\r
+ {\r
+ static const TCHAR cSep[] = _T(";");\r
+ LPTSTR pchPath = new TCHAR[cchPath];\r
+ GetEnvironmentVariable(_T("PATH"), pchPath, cchPath);\r
+ LPTSTR pchItem = pchPath;\r
+ while (int cchItem = StrCSpn(pchItem += StrSpn(pchItem, cSep), cSep))\r
+ {\r
+ if (cchItem < MAX_PATH)\r
+ {\r
+ CopyMemory(cText.Data, pchItem, cchItem*sizeof*pchItem);\r
+ cText.Data[cchItem] = 0;\r
+ PathAppend(cText.Data, _T("Merge7z*.dll"));\r
+ DlgDirListDLLs(hWnd, cText.Data, 105);\r
+ }\r
+ pchItem += cchItem;\r
+ }\r
+ delete[] pchPath;\r
+ }\r
+ if (SendDlgItemMessage(hWnd, 105, LB_GETCOUNT, 0, 0) == 0)\r
+ {\r
+ SendDlgItemMessage(hWnd, 105, LB_ADDSTRING, 0, (LPARAM) cNone.Data);\r
+ }\r
+ HICON hIcon = LoadIcon(0, IDI_EXCLAMATION);\r
+ SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM) hIcon);\r
+ SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM) hIcon);\r
+ if (pThis->m_bShowAllways)\r
+ {\r
+ ShowWindow(GetDlgItem(hWnd, 106), SW_HIDE);\r
+ }\r
+ } return TRUE;\r
+ case WM_DRAWITEM:\r
+ {\r
+ switch (wParam)\r
+ {\r
+ case 108:\r
+ CDrawItemStruct::From(lParam)->DrawWebLinkButton();\r
+ break;\r
+ }\r
+ } return TRUE;\r
+ case WM_SETCURSOR:\r
+ {\r
+ HCURSOR hCursor = 0;\r
+ switch (GetDlgCtrlID((HWND)wParam))\r
+ {\r
+ case 108:\r
+ hCursor = CommCtrl_LoadCursor(MAKEINTRESOURCE(108));\r
+ break;\r
+ }\r
+ if (hCursor)\r
+ {\r
+ SetCursor(hCursor);\r
+ SetWindowLongPtr(hWnd, DWLP_MSGRESULT, 1);\r
+ return TRUE;\r
+ }\r
+ } return FALSE;\r
+ case WM_COMMAND:\r
+ {\r
+ switch (wParam)\r
+ {\r
+ case IDOK:\r
+ case IDCANCEL:\r
+ {\r
+ LRESULT nDontShowAgain = SendDlgItemMessage(hWnd, 106, BM_GETCHECK, 0, 0);\r
+ EndDialog(hWnd, MAKEWORD(IDOK, nDontShowAgain));\r
+ } break;\r
+ case 108:\r
+ {\r
+ HINSTANCE h = ShellExecute(hWnd, _T("open"), m_strDownloadURL, 0, 0, SW_SHOWNORMAL);\r
+ if ((UINT)h > 32)\r
+ {\r
+ LONG lStyle = ::GetWindowLong((HWND)lParam, GWL_STYLE);\r
+ ::SetWindowLong((HWND)lParam, GWL_STYLE, lStyle|BS_LEFTTEXT);\r
+ ::InvalidateRect((HWND)lParam, 0, TRUE);\r
+ }\r
+ else\r
+ {\r
+ MessageBeep(0);\r
+ }\r
+ } break;\r
+ }\r
+ } return TRUE;\r
+ }\r
+ return FALSE;\r
+}\r
+\r
+/**\r
+ * @brief Tell user what went wrong and how she can help.\r
+ */\r
+int C7ZipMismatchException::ReportError(UINT nType, UINT nMessageID)\r
+{\r
+ UINT_PTR response = -1;\r
+ m_bShowAllways = nMessageID;\r
+ if (!m_bShowAllways)\r
+ {\r
+ // Suppress error message in case 7-Zip is not installed.\r
+ response =\r
+ (\r
+ m_dwVer7zInstalled || m_dwVer7zLocal\r
+ ? theApp.GetProfileInt(REGISTRY_SECTION_MESSAGEBOX, m_strRegistryKey, -1)\r
+ : IDOK\r
+ );\r
+ }\r
+ if (response == -1)\r
+ {\r
+ HWND hwndOwner = CWnd::GetSafeOwner()->GetSafeHwnd();\r
+ response = DialogBoxParam(AfxGetResourceHandle(), MAKEINTRESOURCE(IDD_MERGE7ZMISMATCH), hwndOwner, DlgProc, (LPARAM)this);\r
+ if (response == -1)\r
+ {\r
+ response = DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_MERGE7ZMISMATCH), hwndOwner, DlgProc, (LPARAM)this);\r
+ ASSERT(response != -1);\r
+ }\r
+ if (HIBYTE(response) == 1)\r
+ {\r
+ theApp.WriteProfileInt(REGISTRY_SECTION_MESSAGEBOX, m_strRegistryKey, response = int LOBYTE(response));\r
+ }\r
+ }\r
+ return response;\r
+}\r
+\r
+/**\r
+ * @brief Check whether archive support is available.\r
+ */\r
+int NTAPI HasZipSupport()\r
+{\r
+ static int HasZipSupport = -1;\r
+ if (HasZipSupport == -1)\r
+ {\r
+ try\r
+ {\r
+ m_Merge7z.operator->();\r
+ HasZipSupport = 1;\r
+ }\r
+ catch (CException *e)\r
+ {\r
+ e->Delete();\r
+ HasZipSupport = 0;\r
+ }\r
+ }\r
+ return HasZipSupport;\r
+}\r
+\r
+/**\r
+ * @brief Tell user why archive support is not available.\r
+ */\r
+void NTAPI Recall7ZipMismatchError()\r
+{\r
+ try\r
+ {\r
+ m_Merge7z.operator->();\r
+ }\r
+ catch (CException *e)\r
+ {\r
+ e->ReportError(MB_ICONEXCLAMATION, TRUE);\r
+ e->Delete();\r
+ }\r
+}\r
+\r
+/**\r
+ * @brief Delete head of temp path context list, and return its parent context.\r
+ */\r
+CTempPathContext *CTempPathContext::DeleteHead()\r
+{\r
+ CTempPathContext *pParent = m_pParent;\r
+ delete this;\r
+ return pParent;\r
+}\r
+\r
+BOOL NTAPI IsMerge7zEnabled()\r
+{\r
+ return AfxGetApp()->GetProfileInt(_T("Merge7z"), _T("Enable"), 0);\r
+}\r
+\r
+/**\r
+ * @brief Return installed or local version of 7-Zip.\r
+ */\r
+DWORD NTAPI VersionOf7z(BOOL bLocal)\r
+{\r
+ TCHAR path[MAX_PATH];\r
+ if (bLocal)\r
+ {\r
+ GetModuleFileName(0, path, sizeof path/sizeof*path);\r
+ PathRemoveFileSpec(path);\r
+ }\r
+ else\r
+ {\r
+ static const TCHAR szSubKey[] = _T("Software\\7-Zip");\r
+ static const TCHAR szValue[] = _T("Path");\r
+ DWORD type = 0;\r
+ DWORD size = sizeof path;\r
+ SHGetValue(HKEY_LOCAL_MACHINE, szSubKey, szValue, &type, path, &size);\r
+ }\r
+ PathAppend(path, _T("7z.dll"));\r
+ unsigned versionMS = 0;\r
+ unsigned versionLS = 0;\r
+ CVersionInfo(path).GetFixedFileVersion(versionMS, versionLS);\r
+ return versionMS;\r
+}\r
+\r
+/**\r
+ * @brief Access dll functions through proxy.\r
+ */\r
+interface Merge7z *Merge7z::Proxy::operator->()\r
+{\r
+ // As long as the Merge7z*.DLL has not yet been loaded, Merge7z\r
+ // [0] points to the name of the DLL (with placeholders for 7-\r
+ // Zip major and minor version numbers). Once the DLL has been\r
+ // loaded successfully, Merge7z[0] is set to NULL, causing the\r
+ // if to fail on subsequent calls.\r
+\r
+ if (const char *format = Merge7z[0])\r
+ {\r
+ // Merge7z has not yet been loaded\r
+\r
+ char name[MAX_PATH];\r
+ DWORD flags = ~0;\r
+ CException *pCause = NULL;\r
+ switch (GetOptionsMgr()->GetInt(OPT_ARCHIVE_ENABLE))\r
+ {\r
+ case 1: //Use installed 7-Zip if present. Otherwise, use local 7-Zip.\r
+ if (DWORD ver = VersionOf7z(FALSE))\r
+ {\r
+ flags = Initialize::Default;\r
+ try\r
+ {\r
+ wsprintfA(name, format, UINT HIWORD(ver), UINT LOWORD(ver));\r
+ Merge7z[0] = name;\r
+ stub.Load();\r
+ break;\r
+ }\r
+ catch (CException *e)\r
+ {\r
+ Merge7z[0] = format;\r
+ pCause = e;\r
+ }\r
+ }\r
+ case 2: //Always use local 7-Zip.\r
+ if (DWORD ver = VersionOf7z(TRUE))\r
+ {\r
+ flags = Initialize::Default | Initialize::Local7z;\r
+ try\r
+ {\r
+ wsprintfA(name, format, UINT HIWORD(ver), UINT LOWORD(ver));\r
+ Merge7z[0] = name;\r
+ stub.Load();\r
+ break;\r
+ }\r
+ catch (CException *e)\r
+ {\r
+ Merge7z[0] = format;\r
+ if (pCause) pCause->Delete();\r
+ pCause = e;\r
+ }\r
+ }\r
+ default:\r
+ throw new C7ZipMismatchException\r
+ (\r
+ VersionOf7z(FALSE),\r
+ VersionOf7z(TRUE),\r
+ pCause\r
+ );\r
+ }\r
+ LANGID wLangID = (LANGID)GetThreadLocale();\r
+ flags |= wLangID << 16;\r
+ if (GetOptionsMgr()->GetBool(OPT_ARCHIVE_PROBETYPE))\r
+ {\r
+ flags |= Initialize::GuessFormatBySignature | Initialize::GuessFormatByExtension;\r
+ }\r
+ if (Merge7z[1])\r
+ ((interface Merge7z *)Merge7z[1])->Initialize(flags);\r
+ }\r
+ return ((interface Merge7z *)Merge7z[1]);\r
+}\r
+\r
+/**\r
+ * @brief Tell Merge7z we are going to enumerate just 1 item.\r
+ */\r
+UINT SingleItemEnumerator::Open()\r
+{\r
+ return 1;\r
+}\r
+\r
+/**\r
+ * @brief Pass information about the item to Merge7z.\r
+ */\r
+Merge7z::Envelope *SingleItemEnumerator::Enum(Item &item)\r
+{\r
+ item.Mask.Item = item.Mask.Name|item.Mask.FullPath|item.Mask.Recurse;\r
+ item.Name = Name;\r
+ item.FullPath = FullPath;\r
+ return 0;\r
+}\r
+\r
+/**\r
+ * @brief SingleFileEnumerator constructor.\r
+ */\r
+SingleItemEnumerator::SingleItemEnumerator(LPCTSTR path, LPCTSTR FullPath, LPCTSTR Name)\r
+: FullPath(FullPath)\r
+, Name(Name)\r
+{\r
+}\r
+\r
+/**\r
+ * @brief Construct a DirItemEnumerator.\r
+ *\r
+ * Argument *nFlags* controls operation as follows:\r
+ * LVNI_ALL: Enumerate all items.\r
+ * LVNI_SELECTED: Enumerate selected items only.\r
+ * Original: Set folder prefix for first iteration to "original"\r
+ * Altered: Set folder prefix for second iteration to "altered"\r
+ * BalanceFolders: Ensure that all nonempty folders on either side have a\r
+ * corresponding folder on the other side, even if it is\r
+ * empty (DirScan doesn't recurse into folders which\r
+ * appear only on one side).\r
+ * DiffsOnly: Enumerate diffs only.\r
+ */\r
+DirItemEnumerator::DirItemEnumerator(CDirView *pView, int nFlags)\r
+: m_pView(pView)\r
+, m_nFlags(nFlags)\r
+{\r
+ if (m_nFlags & Original)\r
+ {\r
+ m_rgFolderPrefix.push_back(_T("original"));\r
+ }\r
+ if (m_nFlags & Altered)\r
+ {\r
+ m_rgFolderPrefix.push_back(_T("altered"));\r
+ }\r
+ if (m_nFlags & BalanceFolders)\r
+ {\r
+ const CDiffContext& ctxt = pView->GetDiffContext();\r
+ // Collect implied folders\r
+ for (UINT i = Open() ; i-- ; )\r
+ {\r
+ const DIFFITEM &di = Next();\r
+ if ((m_nFlags & DiffsOnly) && !IsItemNavigableDiff(ctxt, di))\r
+ {\r
+ continue;\r
+ }\r
+ // Enumerating items\r
+ if (di.diffcode.isExists(m_index))\r
+ {\r
+ // Item is present on right side, i.e. folder is implied\r
+ m_rgImpliedFolders[m_index][di.diffFileInfo[m_index].path.get()] = PVOID(1);\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+/**\r
+ * @brief Initialize enumerator, return number of items to be enumerated.\r
+ */\r
+UINT DirItemEnumerator::Open()\r
+{\r
+ m_nIndex = -1;\r
+ m_curFolderPrefix = m_rgFolderPrefix.begin();\r
+ m_index = (m_nFlags & Right) != 0 ? 1 : 0;\r
+ size_t nrgFolderPrefix = m_rgFolderPrefix.size();\r
+ if (nrgFolderPrefix)\r
+ {\r
+ m_strFolderPrefix = *m_curFolderPrefix++;\r
+ }\r
+ else\r
+ {\r
+ nrgFolderPrefix = 1;\r
+ }\r
+ return\r
+ (\r
+ m_nFlags & LVNI_SELECTED\r
+ ? pView(m_pView)->GetSelectedCount()\r
+ : pView(m_pView)->GetItemCount()\r
+ ) * nrgFolderPrefix;\r
+}\r
+\r
+/**\r
+ * @brief Return next item.\r
+ */\r
+const DIFFITEM &DirItemEnumerator::Next()\r
+{\r
+ enum {nMask = LVNI_FOCUSED|LVNI_SELECTED|LVNI_CUT|LVNI_DROPHILITED};\r
+ while ((m_nIndex = pView(m_pView)->GetNextItem(m_nIndex, m_nFlags & nMask)) == -1)\r
+ {\r
+ m_strFolderPrefix = *m_curFolderPrefix++;\r
+ m_index = 1;\r
+ }\r
+ return m_pView->GetDiffItem(m_nIndex);\r
+}\r
+\r
+/**\r
+ * @brief Pass information about an item to Merge7z.\r
+ *\r
+ * Information is passed through struct Merge7z::DirItemEnumerator::Item.\r
+ * The *mask* member denotes which of the other members contain valid data.\r
+ * If *mask* is zero upon return, which will be the case if Enum() decides to\r
+ * leave the struct untouched, Merge7z will ignore the item.\r
+ * If Enum() allocates temporary storage for string members, it must also\r
+ * allocate an Envelope, providing a Free() method to free the temporary\r
+ * storage, along with the Envelope itself. The Envelope pointer is passed to\r
+ * Merge7z as the return value of the function. It is not meant to be a success\r
+ * indicator, so if no temporary storage is required, it is perfectly alright\r
+ * to return NULL.\r
+ */\r
+Merge7z::Envelope *DirItemEnumerator::Enum(Item &item)\r
+{\r
+ const CDiffContext& ctxt = m_pView->GetDiffContext();\r
+ const DIFFITEM &di = Next();\r
+\r
+ if ((m_nFlags & DiffsOnly) && !IsItemNavigableDiff(ctxt, di))\r
+ {\r
+ return 0;\r
+ }\r
+\r
+ bool isSideOnly = !di.diffcode.isExists(m_index);\r
+\r
+ Envelope *envelope = new Envelope;\r
+\r
+ const String &sFilename = di.diffFileInfo[m_index].filename;\r
+ const String &sSubdir = di.diffFileInfo[m_index].path;\r
+ if (sSubdir.length())\r
+ envelope->Name = paths_ConcatPath(sSubdir, sFilename);\r
+ else\r
+ envelope->Name = sFilename;\r
+ envelope->FullPath = paths_ConcatPath(\r
+ di.getFilepath(m_index, ctxt.GetNormalizedPath(m_index)),\r
+ sFilename);\r
+\r
+ UINT32 Recurse = item.Mask.Recurse;\r
+\r
+ if (m_nFlags & BalanceFolders)\r
+ {\r
+ // Enumerating items on right side\r
+ if (isSideOnly)\r
+ {\r
+ // Item is missing on right side\r
+ PVOID &implied = m_rgImpliedFolders[m_index][di.diffFileInfo[1-m_index].path.get()];\r
+ if (!implied)\r
+ {\r
+ // Folder is not implied by some other file, and has\r
+ // not been enumerated so far, so enumerate it now!\r
+ envelope->Name = di.diffFileInfo[1-m_index].path;\r
+ envelope->FullPath = di.getFilepath(1-m_index, ctxt.GetNormalizedPath(1-m_index));\r
+ implied = PVOID(2); // Don't enumerate same folder twice!\r
+ isSideOnly = false;\r
+ Recurse = 0;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (isSideOnly)\r
+ {\r
+ return envelope;\r
+ }\r
+\r
+ if (m_strFolderPrefix.length())\r
+ {\r
+ if (envelope->Name.length())\r
+ envelope->Name.insert(0, _T("\\"));\r
+ envelope->Name.insert(0, m_strFolderPrefix);\r
+ }\r
+\r
+ item.Mask.Item = item.Mask.Name|item.Mask.FullPath|item.Mask.CheckIfPresent|Recurse;\r
+ item.Name = envelope->Name.c_str();\r
+ item.FullPath = envelope->FullPath.c_str();\r
+ return envelope;\r
+}\r
+\r
+/**\r
+ * @brief Apply appropriate handlers from left to right.\r
+ */\r
+bool DirItemEnumerator::MultiStepCompressArchive(LPCTSTR path)\r
+{\r
+ DeleteFile(path);\r
+ Merge7z::Format *piHandler = ArchiveGuessFormat(path);\r
+ if (piHandler)\r
+ {\r
+ HWND hwndOwner = CWnd::GetSafeOwner()->GetSafeHwnd();\r
+ CString pathIntermediate;\r
+ SysFreeString(Assign(pathIntermediate, piHandler->GetDefaultName(hwndOwner, path)));\r
+ String pathPrepend = path;\r
+ pathPrepend.resize(pathPrepend.rfind('\\') + 1);\r
+ pathIntermediate.Insert(0, pathPrepend.c_str());\r
+ bool bDone = MultiStepCompressArchive(pathIntermediate);\r
+ if (bDone)\r
+ {\r
+ piHandler->CompressArchive(hwndOwner, path,\r
+ &SingleItemEnumerator(path, pathIntermediate));\r
+ DeleteFile(pathIntermediate);\r
+ }\r
+ else\r
+ {\r
+ piHandler->CompressArchive(hwndOwner, path, this);\r
+ }\r
+ return true;\r
+ }\r
+ return false;\r
+}\r
+\r
+/**\r
+ * @brief Generate archive from DirView items.\r
+ */\r
+void DirItemEnumerator::CompressArchive(LPCTSTR path)\r
+{\r
+ String strPath;\r
+ if (path == 0)\r
+ {\r
+ // No path given, so prompt for path!\r
+ static const TCHAR _T_Merge7z[] = _T("Merge7z");\r
+ static const TCHAR _T_FilterIndex[] = _T("FilterIndex");\r
+ // 7z311 can only write 7z, zip, and tar(.gz|.bz2) archives, so don't\r
+ // offer other formats here!\r
+ static const TCHAR _T_Filter[]\r
+ (\r
+ _T("7z|*.7z|")\r
+ //_T("z|*.z|")\r
+ _T("zip|*.zip|")\r
+ _T("jar (zip)|*.jar|")\r
+ _T("ear (zip)|*.ear|")\r
+ _T("war (zip)|*.war|")\r
+ _T("xpi (zip)|*.xpi|")\r
+ //_T("rar|*.rar|")\r
+ _T("tar|*.tar|")\r
+ _T("tar.z|*.tar.z|")\r
+ _T("tar.gz|*.tar.gz|")\r
+ _T("tar.bz2|*.tar.bz2|")\r
+ //_T("tz|*.tz|")\r
+ _T("tgz|*.tgz|")\r
+ _T("tbz2|*.tbz2|")\r
+ //_T("lzh|*.lzh|")\r
+ //_T("cab|*.cab|")\r
+ //_T("arj|*.arj|")\r
+ //_T("deb|*.deb|")\r
+ //_T("rpm|*.rpm|")\r
+ //_T("cpio|*.cpio|")\r
+ //_T("|")\r
+ );\r
+ String strFilter; // = CExternalArchiveFormat::GetOpenFileFilterString();\r
+ strFilter.insert(0, _T_Filter);\r
+ strFilter += _T("|");\r
+ CFileDialog dlg\r
+ (\r
+ FALSE,\r
+ 0,\r
+ 0,\r
+ OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN,\r
+ strFilter.c_str()\r
+ );\r
+ dlg.m_ofn.nFilterIndex = AfxGetApp()->GetProfileInt(_T_Merge7z, _T_FilterIndex, 1);\r
+ // Use extension from current filter as default extension:\r
+ if (int i = dlg.m_ofn.nFilterIndex)\r
+ {\r
+ dlg.m_ofn.lpstrDefExt = dlg.m_ofn.lpstrFilter;\r
+ while (*dlg.m_ofn.lpstrDefExt && --i)\r
+ {\r
+ dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 1;\r
+ dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 1;\r
+ }\r
+ if (*dlg.m_ofn.lpstrDefExt)\r
+ {\r
+ dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 3;\r
+ }\r
+ }\r
+ if (dlg.DoModal() == IDOK)\r
+ {\r
+ strPath = dlg.GetPathName();\r
+ path = strPath.c_str();\r
+ AfxGetApp()->WriteProfileInt(_T_Merge7z, _T_FilterIndex, dlg.m_ofn.nFilterIndex);\r
+ }\r
+ }\r
+ if (path && !MultiStepCompressArchive(path))\r
+ {\r
+ LangMessageBox(IDS_UNKNOWN_ARCHIVE_FORMAT, MB_ICONEXCLAMATION);\r
+ }\r
+}\r
#include "Constants.h"
#include "Merge.h"
#include "ClipBoard.h"
- #include "DirView.h"
+#include "DirActions.h"
+#include "SourceControl.h"
+#include "DirViewColItems.h"
#include "DirFrame.h" // StatePane
#include "DirDoc.h"
+ #include "MergeDoc.h"
#include "HexMergeFrm.h"
#include "HexMergeDoc.h"
#include "MainFrm.h"
}
/// User chose (context menu) Copy left to...
-void CDirView::OnCtxtDirCopyLeftTo()
+template<SIDE_TYPE stype>
+void CDirView::OnCtxtDirCopyTo()
{
- DoCopyLeftTo();
+ DoDirActionTo(stype, &DirActions::CopyTo<stype>, _("Copying files..."));
}
-/// User chose (context menu) Copy from right to...
-void CDirView::OnCtxtDirCopyRightTo()
+/// Update context menu Copy Right to Left item
+template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
+void CDirView::OnUpdateCtxtDirCopy(CCmdUI* pCmdUI)
{
- DoCopyRightTo();
+ DoUpdateDirCopy<srctype, dsttype>(pCmdUI, eContext);
}
-/// Update context menu Copy Right to Left item
-void CDirView::OnUpdateCtxtDirCopyRightToLeft(CCmdUI* pCmdUI)
+/// Update main menu Copy Right to Left item
+template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
+void CDirView::OnUpdateDirCopy(CCmdUI* pCmdUI)
{
- DoUpdateDirCopyRightToLeft(pCmdUI, eContext);
+ DoUpdateDirCopy<srctype, dsttype>(pCmdUI, eMain);
}
-/// Update context menu Copy Left to Right item
-void CDirView::OnUpdateCtxtDirCopyLeftToRight(CCmdUI* pCmdUI)
+
+void CDirView::DoDirAction(DirActions::method_type func, const String& status_message)
{
- DoUpdateDirCopyLeftToRight(pCmdUI, eContext);
+ WaitStatusCursor waitstatus(status_message);
+
+ try {
+ // First we build a list of desired actions
+ FileActionScript actionScript;
+ DirItemWithIndexIterator begin(m_pIList.get(), -1, true);
+ DirItemWithIndexIterator end;
+ std::accumulate(begin, end, &actionScript, MakeDirActions(func));
+ // Now we prompt, and execute actions
+ ConfirmAndPerformActions(actionScript);
+ } catch (ContentsChangedException& e) {
+ AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
+ }
}
-/// Update main menu Copy Right to Left item
-void CDirView::OnUpdateDirCopyRightToLeft(CCmdUI* pCmdUI)
+void CDirView::DoDirActionTo(SIDE_TYPE stype, DirActions::method_type func, const String& status_message)
{
- DoUpdateDirCopyRightToLeft(pCmdUI, eMain);
+ String destPath;
+ String startPath(m_lastCopyFolder);
+ String selectfolder_title;
+
+ if (stype == SIDE_LEFT)
+ selectfolder_title = _("Left side - select destination folder:");
+ else if (stype == SIDE_MIDDLE)
+ selectfolder_title = _("Middle side - select destination folder:");
+ else if (stype == SIDE_RIGHT)
+ selectfolder_title = _("Right side - select destination folder:");
+
+ if (!SelectFolder(destPath, startPath.c_str(), selectfolder_title))
+ return;
+
+ m_lastCopyFolder = destPath;
+ WaitStatusCursor waitstatus(status_message);
+
+ try {
+ // First we build a list of desired actions
+ FileActionScript actionScript;
+ actionScript.m_destBase = destPath;
+ DirItemWithIndexIterator begin(m_pIList.get(), -1, true);
+ DirItemWithIndexIterator end;
+ std::accumulate(begin, end, &actionScript, MakeDirActions(func));
+ // Now we prompt, and execute actions
+ ConfirmAndPerformActions(actionScript);
+ } catch (ContentsChangedException& e) {
+ AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
+ }
}
-/// Update main menu Copy Left to Right item
-void CDirView::OnUpdateDirCopyLeftToRight(CCmdUI* pCmdUI)
+
+// Confirm with user, then perform the action list
+void CDirView::ConfirmAndPerformActions(FileActionScript & actionList)
{
- DoUpdateDirCopyLeftToRight(pCmdUI, eMain);
+ if (actionList.GetActionItemCount() == 0) // Not sure it is possible to get right-click menu without
+ return; // any selected items, but may as well be safe
+
+ ASSERT(actionList.GetActionItemCount()>0); // Or else the update handler got it wrong
+
+ // Set parent window so modality is correct and correct window gets focus
+ // after dialogs.
+ actionList.SetParentWindow(this->GetSafeHwnd());
+
+ try {
+ ConfirmActionList(GetDiffContext(), actionList);
+ } catch (ConfirmationNeededException& e) {
+ ConfirmFolderCopyDlg dlg;
+ dlg.m_caption = e.m_caption;
+ dlg.m_question = e.m_question;
+ dlg.m_fromText = e.m_fromText;
+ dlg.m_toText = e.m_toText;
+ dlg.m_fromPath = e.m_fromPath;
+ dlg.m_toPath = e.m_toPath;
+ if (dlg.DoModal() != IDOK)
+ return;
+ }
+ PerformActionList(actionList);
}
-/// Should Copy to Left be enabled or disabled ? (both main menu & context menu use this)
-void CDirView::DoUpdateDirCopyRightToLeft(CCmdUI* pCmdUI, eMenuType menuType)
+/**
+ * @brief Perform an array of actions
+ * @note There can be only COPY or DELETE actions, not both!
+ * @sa SourceControl::SaveToVersionControl()
+ * @sa SourceControl::SyncFilesToVCS()
+ */
+void CDirView::PerformActionList(FileActionScript & actionScript)
{
- if (GetDocument()->GetReadOnly(0))
- pCmdUI->Enable(FALSE);
+ // Reset suppressing VSS dialog for multiple files.
+ // Set in SourceControl::SaveToVersionControl().
- GetMainFrame()->m_pSourceControl->m_CheckOutMulti = false;
- GetMainFrame()->m_pSourceControl->m_bVssSuppressPathCheck = false;
++ theApp.m_pSourceControl->m_CheckOutMulti = false;
++ theApp.m_pSourceControl->m_bVssSuppressPathCheck = false;
+
+ // Check option and enable putting deleted items to Recycle Bin
+ if (GetOptionsMgr()->GetBool(OPT_USE_RECYCLE_BIN))
+ actionScript.UseRecycleBin(true);
else
+ actionScript.UseRecycleBin(false);
+
+ actionScript.SetParentWindow(GetMainFrame()->GetSafeHwnd());
+
+ theApp.AddOperation();
+ if (actionScript.Run())
+ UpdateAfterFileScript(actionScript);
+ theApp.RemoveOperation();
+}
+
+/**
+ * @brief Update results after running FileActionScript.
+ * This functions is called after script is finished to update
+ * results (including UI).
+ * @param [in] actionlist Script that was run.
+ */
+void CDirView::UpdateAfterFileScript(FileActionScript & actionList)
+{
+ bool bItemsRemoved = false;
+ int curSel = GetFirstSelectedInd();
+ CDiffContext& ctxt = GetDiffContext();
+ while (actionList.GetActionItemCount()>0)
{
- int sel = -1;
- int legalcount = 0, selcount = 0;
- while ((sel = m_pList->GetNextItem(sel, LVNI_SELECTED)) != -1)
+ // Start handling from tail of list, so removing items
+ // doesn't invalidate our item indexes.
+ FileActionItem act = actionList.RemoveTailActionItem();
+
+ // Synchronized items may need VCS operations
+ if (act.UIResult == FileActionItem::UI_SYNC)
{
- if (GetMainFrame()->m_pSourceControl->m_bCheckinVCS)
- GetMainFrame()->m_pSourceControl->CheckinToClearCase(act.dest);
- const DIFFITEM& di = GetDiffItem(sel);
- if (di.diffcode.diffcode != 0 && IsItemCopyableToLeft(di))
- ++legalcount;
- ++selcount;
++ if (theApp.m_pSourceControl->m_bCheckinVCS)
++ theApp.m_pSourceControl->CheckinToClearCase(act.dest);
}
- pCmdUI->Enable(legalcount > 0);
- if (menuType == eContext)
+
+ // Update doc (difflist)
+ UPDATEITEM_TYPE updatetype = UpdateDiffAfterOperation(act, ctxt, GetDiffItem(act.context));
+ if (updatetype == UPDATEITEM_REMOVE)
{
- String s;
- if (legalcount == selcount)
- s = LangFormatString1(IDS_COPY_TO_LEFT, NumToStr(selcount).c_str());
- else
- s = LangFormatString2(IDS_COPY_TO_LEFT2, NumToStr(legalcount).c_str(), NumToStr(selcount).c_str());
- pCmdUI->SetText(s.c_str());
+ DeleteItem(act.context);
+ bItemsRemoved = true;
}
+ else if (updatetype == UPDATEITEM_UPDATE)
+ UpdateDiffItemStatus(act.context);
}
-}
-
-/// Should Copy to Right be enabled or disabled ? (both main menu & context menu use this)
-void CDirView::DoUpdateDirCopyLeftToRight(CCmdUI* pCmdUI, eMenuType menuType)
-{
- if (GetDocument()->GetReadOnly(GetDocument()->m_nDirs - 1))
- pCmdUI->Enable(FALSE);
- else
+
+ // Make sure selection is at sensible place if all selected items
+ // were removed.
+ if (bItemsRemoved == true)
{
- int sel = -1;
- int legalcount = 0, selcount = 0;
- while ((sel = m_pList->GetNextItem(sel, LVNI_SELECTED)) != -1)
- {
- const DIFFITEM& di = GetDiffItem(sel);
- if (di.diffcode.diffcode != 0 && IsItemCopyableToRight(di))
- ++legalcount;
- ++selcount;
- }
- pCmdUI->Enable(legalcount > 0);
- if (menuType == eContext)
+ UINT selected = GetSelectedCount();
+ if (selected == 0)
{
- String s;
- if (legalcount == selcount)
- s = LangFormatString1(IDS_COPY_TO_RIGHT, NumToStr(selcount).c_str());
- else
- s = LangFormatString2(IDS_COPY_TO_RIGHT2, NumToStr(legalcount).c_str(), NumToStr(selcount).c_str());
- pCmdUI->SetText(s.c_str());
+ if (curSel < 1)
+ ++curSel;
+ MoveFocus(0, curSel - 1, selected);
}
}
}
if (di1 == di2 && !di1->diffcode.isExists(0))
{
paths[0] = _T("");
- GetMainFrame()->m_strDescriptions[0] = _("Untitled left");
- theApp.m_strDescriptions[0] = theApp.LoadString(IDS_EMPTY_LEFT_FILE);
++ theApp.m_strDescriptions[0] = _("Untitled left");
}
if (di1 == di2 && !di1->diffcode.isExists(1))
{
paths[1] = _T("");
- GetMainFrame()->m_strDescriptions[1] = _("Untitled right");
- theApp.m_strDescriptions[1] = theApp.LoadString(IDS_EMPTY_RIGHT_FILE);
++ theApp.m_strDescriptions[1] = _("Untitled right");
}
}
else
if (di1 == di2 && di1 == di3 && !di1->diffcode.isExists(0))
{
paths[0] = _T("");
- GetMainFrame()->m_strDescriptions[0] = _("Untitled left");
- theApp.m_strDescriptions[0] = theApp.LoadString(IDS_EMPTY_LEFT_FILE);
++ theApp.m_strDescriptions[0] = _("Untitled left");
}
if (di1 == di2 && di1 == di3 && !di1->diffcode.isExists(1))
{
paths[1] = _T("");
- GetMainFrame()->m_strDescriptions[1] = _("Untitled middle");
- theApp.m_strDescriptions[1] = theApp.LoadString(IDS_EMPTY_MIDDLE_FILE);
++ theApp.m_strDescriptions[1] = _("Untitled middle");
}
if (di1 == di2 && di1 == di3 && !di1->diffcode.isExists(2))
{
paths[2] = _T("");
- GetMainFrame()->m_strDescriptions[2] = _("Untitled right");
- theApp.m_strDescriptions[2] = theApp.LoadString(IDS_EMPTY_RIGHT_FILE);
++ theApp.m_strDescriptions[2] = _("Untitled right");
}
}
return m_pList->FindItem(&findInfo);
}
-/// User chose (context menu) open left
-void CDirView::OnCtxtDirOpenLeft()
+/**
+ * @brief Get the file names on both sides for specified item.
+ * @note Return empty strings if item is special item.
+ */
+void CDirView::GetItemFileNames(int sel, String& strLeft, String& strRight) const
{
- DoOpen(SIDE_LEFT);
+ UINT_PTR diffpos = GetItemKey(sel);
+ if (diffpos == (UINT_PTR)SPECIAL_ITEM_POS)
+ {
+ strLeft.erase();
+ strRight.erase();
+ }
+ else
+ {
+ const CDiffContext& ctxt = GetDiffContext();
+ ::GetItemFileNames(ctxt, ctxt.GetDiffAt(diffpos), strLeft, strRight);
+ }
}
-/// User chose (context menu) open right
-void CDirView::OnCtxtDirOpenRight()
+
+/**
+ * @brief Get the file names on both sides for specified item.
+ * @note Return empty strings if item is special item.
+ */
+void CDirView::GetItemFileNames(int sel, PathContext * paths) const
{
- DoOpen(SIDE_RIGHT);
+ String strPath[3];
+ UINT_PTR diffpos = GetItemKey(sel);
+ if (diffpos == SPECIAL_ITEM_POS)
+ {
+ for (int nIndex = 0; nIndex < GetDocument()->m_nDirs; nIndex++)
+ paths->SetPath(nIndex, _T(""));
+ }
+ else
+ {
+ const CDiffContext& ctxt = GetDiffContext();
+ *paths = ::GetItemFileNames(ctxt, ctxt.GetDiffAt(diffpos));
+ }
}
-/// User chose (context menu) open left with
-void CDirView::OnCtxtDirOpenLeftWith()
+/**
+ * @brief Open selected file with registered application.
+ * Uses shell file associations to open file with registered
+ * application. We first try to use "Edit" action which should
+ * open file to editor, since we are more interested editing
+ * files than running them (scripts).
+ * @param [in] stype Side of file to open.
+ */
+void CDirView::DoOpen(SIDE_TYPE stype)
{
- DoOpenWith(SIDE_LEFT);
+ int sel = GetSingleSelectedItem();
+ if (sel == -1) return;
+ String file = GetSelectedFileName(SelBegin(), stype, GetDiffContext());
+ if (file.empty()) return;
+ int rtn = (int)ShellExecute(::GetDesktopWindow(), _T("edit"), file.c_str(), 0, 0, SW_SHOWNORMAL);
+ if (rtn==SE_ERR_NOASSOC)
+ rtn = (int)ShellExecute(::GetDesktopWindow(), _T("open"), file.c_str(), 0, 0, SW_SHOWNORMAL);
+ if (rtn==SE_ERR_NOASSOC)
+ DoOpenWith(stype);
}
-/// User chose (context menu) open right with
-void CDirView::OnCtxtDirOpenRightWith()
+/// Open with dialog for file on selected side
+void CDirView::DoOpenWith(SIDE_TYPE stype)
{
- DoOpenWith(SIDE_RIGHT);
+ int sel = GetSingleSelectedItem();
+ if (sel == -1) return;
+ String file = GetSelectedFileName(SelBegin(), stype, GetDiffContext());
+ if (file.empty()) return;
+ CString sysdir;
+ if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH)) return;
+ sysdir.ReleaseBuffer();
+ CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + file.c_str();
+ ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg, sysdir, SW_SHOWNORMAL);
}
-/// User chose (context menu) open right with editor
-void CDirView::OnCtxtDirOpenRightWithEditor()
+/// Open selected file on specified side to external editor
+void CDirView::DoOpenWithEditor(SIDE_TYPE stype)
{
- DoOpenWithEditor(SIDE_RIGHT);
+ int sel = GetSingleSelectedItem();
+ if (sel == -1) return;
+ String file = GetSelectedFileName(SelBegin(), stype, GetDiffContext());
+ if (file.empty()) return;
+
- GetMainFrame()->OpenFileToExternalEditor(file);
++ theApp.OpenFileToExternalEditor(file);
+}
+
+/// User chose (context menu) open left
+template<SIDE_TYPE stype>
+void CDirView::OnCtxtDirOpen()
+{
+ DoOpen(stype);
}
-/// Update context menuitem "Open right | with editor"
-void CDirView::OnUpdateCtxtDirOpenRightWithEditor(CCmdUI* pCmdUI)
+/// User chose (context menu) open left with
+template<SIDE_TYPE stype>
+void CDirView::OnCtxtDirOpenWith()
{
- DoUpdateOpenRightWith(pCmdUI);
+ DoOpenWith(stype);
}
/// User chose (context menu) open left with editor
#include "ConflictFileParser.h"
#include "codepage.h"
#include "JumpList.h"
-#include "VSSHelper.h"
+ #include "stringdiffs.h"
+ #include "TFile.h"
++#include "SourceControl.h"
+#include "paths.h"
+#include "Constants.h"
// For shutdown cleanup
#include "charsets.h"
, m_nLastCompareResult(0)
, m_bNonInteractive(false)
, m_pOptions(new CRegOptionsMgr())
+ , m_pGlobalFileFilter(new FileFilterHelper())
, m_nActiveOperations(0)
, m_pLangDlg(new CLanguageSelect(IDR_MAINFRAME, IDR_MAINFRAME))
-, m_pVssHelper(new VSSHelper())
-, m_CheckOutMulti(FALSE)
-, m_bVCProjSync(FALSE)
-, m_bVssSuppressPathCheck(FALSE)
+ , m_bEscShutdown(FALSE)
+ , m_bClearCaseTool(FALSE)
+ , m_bExitIfNoDiff(MergeCmdLineInfo::Disabled)
+ , m_pLineFilters(new LineFiltersList())
+ , m_pSyntaxColors(new SyntaxColors())
++, m_pSourceControl(new SourceControl())
{
// add construction code here,
// Place all significant initialization in InitInstance
m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter);
}
- InitializeSourceControlMembers();
+ UpdateCodepageModule();
+
++ if (m_pSourceControl)
++ m_pSourceControl->InitializeSourceControlMembers();
++
+ g_bUnpackerMode = theApp.GetProfileInt(_T("Settings"), _T("UnpackerMode"), PLUGIN_MANUAL);
+ g_bPredifferMode = theApp.GetProfileInt(_T("Settings"), _T("PredifferMode"), PLUGIN_MANUAL);
+
+ if (m_pSyntaxColors)
+ Options::SyntaxColors::Load(m_pSyntaxColors.get());
+
+ if (m_pLineFilters)
+ m_pLineFilters->Initialize(GetOptionsMgr());
+
+ // If there are no filters loaded, and there is filter string in previous
+ // option string, import old filters to new place.
+ if (m_pLineFilters->GetCount() == 0)
+ {
+ String oldFilter = theApp.GetProfileString(_T("Settings"), _T("RegExps"));
+ if (!oldFilter.empty())
+ m_pLineFilters->Import(oldFilter);
+ }
+
+ // Check if filter folder is set, and create it if not
+ String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
+ if (pathMyFolders.empty())
+ {
+ // No filter path, set it to default and make sure it exists.
+ String pathFilters = GetDefaultFilterUserPath(TRUE);
+ GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, pathFilters);
+ theApp.m_pGlobalFileFilter->SetFileFilterPath(pathFilters.c_str());
+ }
+
+ sd_Init(); // String diff init
+ sd_SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
+
CSplashWnd::EnableSplashScreen(!bDisableSplash && !bCommandLineInvoke);
// Initialize i18n (multiple language) support
return bContinue;
}
- GetMainFrame()->OpenFileToExternalEditor(paths_ConcatPath(env_GetProgPath(), ContributorsPath));
+static void OpenContributersFile(int&)
+{
++ theApp.OpenFileToExternalEditor(paths_ConcatPath(env_GetProgPath(), ContributorsPath));
+}
+
// App command to run the dialog
void CMergeApp::OnAppAbout()
{
/** @brief Open help from mainframe when user presses F1*/
void CMergeApp::OnHelp()
{
- GetMainFrame()->ShowHelp();
+ ShowHelp();
+ }
+
+ /**
+ * @brief Open given file to external editor specified in options.
+ * @param [in] file Full path to file to open.
+ *
+ * Opens file to defined (in Options/system), Notepad by default,
+ * external editor. Path is decorated with quotation marks if needed
+ * (contains spaces). Also '$file' in editor path is replaced by
+ * filename to open.
+ * @param [in] file Full path to file to open.
+ * @param [in] nLineNumber Line number to go to.
+ */
+ void CMergeApp::OpenFileToExternalEditor(const String& file, int nLineNumber/* = 1*/)
+ {
+ String sCmd = GetOptionsMgr()->GetString(OPT_EXT_EDITOR_CMD);
+ String sFile(file);
- string_replace(sCmd, _T("$linenum"), string_format(_T("%d"), nLineNumber));
++ string_replace(sCmd, _T("$linenum"), string_to_str(nLineNumber));
+
+ int nIndex = sCmd.find(_T("$file"));
+ if (nIndex > -1)
+ {
+ sFile.insert(0, _T("\""));
+ string_replace(sCmd, _T("$file"), sFile);
+ nIndex = sCmd.find(' ', nIndex + sFile.length());
+ if (nIndex > -1)
+ sCmd.insert(nIndex, _T("\""));
+ else
+ sCmd += '"';
+ }
+ else
+ {
+ sCmd += _T(" \"");
+ sCmd += sFile;
+ sCmd += _T("\"");
+ }
+
+ BOOL retVal = FALSE;
+ STARTUPINFO stInfo = {0};
+ stInfo.cb = sizeof(STARTUPINFO);
+ PROCESS_INFORMATION processInfo;
+
+ retVal = CreateProcess(NULL, (LPTSTR)sCmd.c_str(),
+ NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
+ &stInfo, &processInfo);
+
+ if (!retVal)
+ {
+ // Error invoking external editor
- ResMsgBox1(IDS_ERROR_EXECUTE_FILE, sCmd.c_str(), MB_ICONSTOP);
++ String msg = string_format_string1(_("Failed to execute external editor: %1"), sCmd);
++ AfxMessageBox(msg.c_str(), MB_ICONSTOP);
+ }
+ else
+ {
+ CloseHandle(processInfo.hThread);
+ CloseHandle(processInfo.hProcess);
+ }
+ }
+
+ /**
+ * @brief Open file, if it exists, else open url
+ */
+ void CMergeApp::OpenFileOrUrl(LPCTSTR szFile, LPCTSTR szUrl)
+ {
+ if (paths_DoesPathExist(szFile) == IS_EXISTING_FILE)
+ ShellExecute(NULL, _T("open"), _T("notepad.exe"), szFile, NULL, SW_SHOWNORMAL);
+ else
+ ShellExecute(NULL, _T("open"), szUrl, NULL, NULL, SW_SHOWNORMAL);
+ }
+
+ /**
+ * @brief Show Help - this is for opening help from outside mainframe.
+ * @param [in] helpLocation Location inside help, if NULL main help is opened.
+ */
+ void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= NULL*/)
+ {
+ String sPath = env_GetProgPath();
+ LANGID LangId = GetLangId();
+ if (PRIMARYLANGID(LangId) == LANG_JAPANESE)
- sPath += DocsPath_ja;
++ sPath = paths_ConcatPath(sPath, DocsPath_ja);
+ else
- sPath += DocsPath;
++ sPath = paths_ConcatPath(sPath, DocsPath);
+ if (helpLocation == NULL)
+ {
+ if (paths_DoesPathExist(sPath) == IS_EXISTING_FILE)
+ ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOC, NULL);
+ else
+ ShellExecute(NULL, _T("open"), DocsURL, NULL, NULL, SW_SHOWNORMAL);
+ }
+ else
+ {
+ if (paths_DoesPathExist(sPath) == IS_EXISTING_FILE)
+ {
+ sPath += helpLocation;
+ ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOPIC, NULL);
+ }
+ }
+ }
+
+ /**
+ * @brief Creates backup before file is saved or copied over.
+ * This function handles formatting correct path and filename for
+ * backup file. Formatting is done based on several options available
+ * for users in Options/Backups dialog. After path is formatted, file
+ * is simply just copied. Not much error checking, just if copying
+ * succeeded or failed.
+ * @param [in] bFolder Are we creating backup in folder compare?
+ * @param [in] pszPath Full path to file to backup.
+ * @return TRUE if backup succeeds, or isn't just done.
+ */
+ BOOL CMergeApp::CreateBackup(BOOL bFolder, const String& pszPath)
+ {
+ // If user doesn't want to backups in folder compare, return
+ // success so operations don't abort.
+ if (bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FOLDERCMP)))
+ return TRUE;
+ // Likewise if user doesn't want backups in file compare
+ else if (!bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FILECMP)))
+ return TRUE;
+
+ // create backup copy of file if destination file exists
+ if (paths_DoesPathExist(pszPath) == IS_EXISTING_FILE)
+ {
+ String bakPath;
+ String path;
+ String filename;
+ String ext;
+
+ paths_SplitFilename(pszPath, &path, &filename, &ext);
+
+ // Determine backup folder
+ if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
+ PropBackups::FOLDER_ORIGINAL)
+ {
+ // Put backups to same folder than original file
+ bakPath = path;
+ }
+ else if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
+ PropBackups::FOLDER_GLOBAL)
+ {
+ // Put backups to global folder defined in options
+ bakPath = GetOptionsMgr()->GetString(OPT_BACKUP_GLOBALFOLDER);
+ if (bakPath.empty())
+ bakPath = path;
+ else
+ bakPath = paths_GetLongPath(bakPath);
+ }
+ else
+ {
+ _RPTF0(_CRT_ERROR, "Unknown backup location!");
+ }
+
+ BOOL success = FALSE;
+ if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_BAK))
+ {
+ // Don't add dot if there is no existing extension
+ if (ext.size() > 0)
+ ext += _T(".");
+ ext += BACKUP_FILE_EXT;
+ }
+
+ // Append time to filename if wanted so
+ // NOTE just adds timestamp at the moment as I couldn't figure out
+ // nice way to add a real time (invalid chars etc).
+ if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_TIME))
+ {
+ struct tm *tm;
+ time_t curtime = 0;
+ time(&curtime);
+ tm = localtime(&curtime);
+ CString timestr;
+ timestr.Format(_T("%04d%02d%02d%02d%02d%02d"), tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
+ filename += _T("-");
+ filename += timestr;
+ }
+
+ // Append filename and extension (+ optional .bak) to path
+ if ((bakPath.length() + filename.length() + ext.length())
+ < MAX_PATH)
+ {
+ success = TRUE;
- if (!paths_EndsWithSlash(bakPath))
- bakPath += _T("\\");
+ bakPath = paths_ConcatPath(bakPath, filename);
+ bakPath += _T(".");
+ bakPath += ext;
+ }
+
+ if (success)
+ success = CopyFile(pszPath.c_str(), bakPath.c_str(), FALSE);
+
+ if (!success)
+ {
- if (ResMsgBox1(IDS_BACKUP_FAILED_PROMPT, pszPath.c_str(),
- MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN,
- IDS_BACKUP_FAILED_PROMPT) != IDYES)
++ String msg = string_format_string1(
++ _("Unable to backup original file:\n%1\n\nContinue anyway?"),
++ pszPath);
++ if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN) != IDYES)
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ // we got here because we're either not backing up of there was nothing to backup
+ return TRUE;
+ }
+
+ /**
+ * @brief Sync file to Version Control System
+ * @param pszDest [in] Where to copy (incl. filename)
+ * @param bApplyToAll [in,out] Apply user selection to all items
+ * @param psError [out] Error string that can be shown to user in caller func
+ * @return User selection or -1 if error happened
+ * @sa CMainFrame::HandleReadonlySave()
+ * @sa CDirView::PerformActionList()
+ */
+ int CMergeApp::SyncFileToVCS(const String& pszDest, BOOL &bApplyToAll,
+ String& sError)
+ {
+ String sActionError;
+ String strSavePath(pszDest);
+ int nVerSys = 0;
+
+ nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
+
+ int nRetVal = HandleReadonlySave(strSavePath, TRUE, bApplyToAll);
+ if (nRetVal == IDCANCEL || nRetVal == IDNO)
+ return nRetVal;
+
+ // If VC project opened from VSS sync and version control used
- if ((nVerSys == VCS_VSS4 || nVerSys == VCS_VSS5) && m_bVCProjSync)
++ if ((nVerSys == SourceControl::VCS_VSS4 || nVerSys == SourceControl::VCS_VSS5) && m_pSourceControl->m_bVCProjSync)
+ {
- if (!m_pVssHelper->ReLinkVCProj(strSavePath, sError))
++ if (!m_pSourceControl->m_vssHelper.ReLinkVCProj(strSavePath, sError))
+ nRetVal = -1;
+ }
+ return nRetVal;
+ }
+
+ /**
+ * @brief Checks if path (file/folder) is read-only and asks overwriting it.
+ *
+ * @param strSavePath [in,out] Path where to save (file or folder)
+ * @param bMultiFile [in] Single file or multiple files/folder
+ * @param bApplyToAll [in,out] Apply last user selection for all items?
+ * @return Users selection:
+ * - IDOK: Item was not readonly, no actions
+ * - IDYES/IDYESTOALL: Overwrite readonly item
+ * - IDNO: User selected new filename (single file) or user wants to skip
+ * - IDCANCEL: Cancel operation
+ * @sa CMainFrame::SyncFileToVCS()
+ * @sa CMergeDoc::DoSave()
+ */
+ int CMergeApp::HandleReadonlySave(String& strSavePath, BOOL bMultiFile,
+ BOOL &bApplyToAll)
+ {
+ CFileStatus status;
+ UINT userChoice = 0;
+ int nRetVal = IDOK;
+ BOOL bFileRO = FALSE;
+ BOOL bFileExists = FALSE;
+ String s;
+ String str;
+ CString title;
+ int nVerSys = 0;
+
+ try
+ {
+ TFile file(strSavePath);
+ bFileExists = file.exists();
+ if (bFileExists)
+ bFileRO = !file.canWrite();
+ }
+ catch (...)
+ {
+ }
+ nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
+
+ if (bFileExists && bFileRO)
+ {
+ // Version control system used?
+ // Checkout file from VCS and modify, don't ask about overwriting
+ // RO files etc.
- if (nVerSys != VCS_NONE)
++ if (nVerSys != SourceControl::VCS_NONE)
+ {
- BOOL bRetVal = SaveToVersionControl(strSavePath);
++ bool bRetVal = m_pSourceControl->SaveToVersionControl(strSavePath);
+ if (bRetVal)
+ return IDYES;
+ else
+ return IDCANCEL;
+ }
+
+ // Don't ask again if its already asked
+ if (bApplyToAll)
+ userChoice = IDYES;
+ else
+ {
+ // Prompt for user choice
+ if (bMultiFile)
+ {
+ // Multiple files or folder
- str = LangFormatString1(IDS_SAVEREADONLY_MULTI, strSavePath.c_str());
++ str = string_format_string1(_("%1\nis marked read-only. Would you like to override the read-only item?"), strSavePath);
+ userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
+ MB_ICONWARNING | MB_DEFBUTTON3 | MB_DONT_ASK_AGAIN |
+ MB_YES_TO_ALL, IDS_SAVEREADONLY_MULTI);
+ }
+ else
+ {
+ // Single file
- str = LangFormatString1(IDS_SAVEREADONLY_FMT, strSavePath.c_str());
++ str = string_format_string1(_("%1 is marked read-only. Would you like to override the read-only file ? (No to save as new filename.)"), strSavePath);
+ userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
+ MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN,
+ IDS_SAVEREADONLY_FMT);
+ }
+ }
+ switch (userChoice)
+ {
+ // Overwrite read-only file
+ case IDYESTOALL:
+ bApplyToAll = TRUE; // Don't ask again (no break here)
+ case IDYES:
+ CFile::GetStatus(strSavePath.c_str(), status);
+ status.m_mtime = 0; // Avoid unwanted changes
+ status.m_attribute &= ~CFile::readOnly;
+ CFile::SetStatus(strSavePath.c_str(), status);
+ nRetVal = IDYES;
+ break;
+
+ // Save to new filename (single) /skip this item (multiple)
+ case IDNO:
+ if (!bMultiFile)
+ {
- if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, strSavePath.c_str(), IDS_SAVE_AS_TITLE, NULL, FALSE))
++ if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, strSavePath.c_str(), _("Save As"), _T(""), FALSE))
+ {
+ strSavePath = s;
+ nRetVal = IDNO;
+ }
+ else
+ nRetVal = IDCANCEL;
+ }
+ else
+ nRetVal = IDNO;
+ break;
+
+ // Cancel saving
+ case IDCANCEL:
+ nRetVal = IDCANCEL;
+ break;
+ }
+ }
+ return nRetVal;
+ }
+
+ /**
+ * @brief Shows VSS error from exception and writes log.
+ */
+ void CMergeApp::ShowVSSError(CException *e, const String& strItem)
+ {
+ TCHAR errStr[1024] = {0};
+ if (e->GetErrorMessage(errStr, 1024))
+ {
+ String errMsg = theApp.LoadString(IDS_VSS_ERRORFROM);
+ String logMsg = errMsg;
+ errMsg += _T("\n");
+ errMsg += errStr;
+ logMsg += _T(" ");
+ logMsg += errStr;
+ if (!strItem.empty())
+ {
+ errMsg += _T("\n\n");
+ errMsg += strItem;
+ logMsg += _T(": ");
+ logMsg += strItem;
+ }
+ LogErrorString(logMsg);
+ AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
+ }
+ else
+ {
+ LogErrorString(_T("VSSError (unable to GetErrorMessage)"));
+ e->ReportError(MB_ICONSTOP, IDS_VSS_RUN_ERROR);
+ }
}
/**