1 // TortoiseMerge - a Diff/Patch program
\r
3 // Copyright (C) 2006-2009 - TortoiseSVN
\r
5 // This program is free software; you can redistribute it and/or
\r
6 // modify it under the terms of the GNU General Public License
\r
7 // as published by the Free Software Foundation; either version 2
\r
8 // of the License, or (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, write to the Free Software Foundation,
\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
21 #include "TortoiseMerge.h"
\r
22 #include "MainFrm.h"
\r
23 #include "AboutDlg.h"
\r
24 #include "CmdLineParser.h"
\r
25 #include "version.h"
\r
26 #include "AppUtils.h"
\r
27 #include "PathUtils.h"
\r
28 #include "BrowseFolder.h"
\r
29 #include "DirFileEnum.h"
\r
32 #define new DEBUG_NEW
\r
35 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
\r
37 BEGIN_MESSAGE_MAP(CTortoiseMergeApp, CWinAppEx)
\r
38 ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
\r
42 CTortoiseMergeApp::CTortoiseMergeApp()
\r
45 m_bLoadUserToolbars = FALSE;
\r
46 m_bSaveState = FALSE;
\r
49 // The one and only CTortoiseMergeApp object
\r
50 CTortoiseMergeApp theApp;
\r
51 CCrashReport g_crasher("tortoisesvn@gmail.com", "Crash Report for TortoiseMerge " APP_X64_STRING " : " STRPRODUCTVER, TRUE);
\r
53 // CTortoiseMergeApp initialization
\r
54 BOOL CTortoiseMergeApp::InitInstance()
\r
56 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
\r
57 CMFCButton::EnableWindowsTheming();
\r
58 //set the resource dll for the required language
\r
59 CRegDWORD loc = CRegDWORD(_T("Software\\TortoiseSVN\\LanguageID"), 1033);
\r
62 HINSTANCE hInst = NULL;
\r
65 langDll.Format(_T("..\\Languages\\TortoiseMerge%d.dll"), langId);
\r
67 hInst = LoadLibrary(langDll);
\r
68 CString sVer = _T(STRPRODUCTVER);
\r
69 CString sFileVer = CPathUtils::GetVersionFromFile(langDll);
\r
70 if (sFileVer.Compare(sVer)!=0)
\r
76 AfxSetResourceHandle(hInst);
\r
79 DWORD lid = SUBLANGID(langId);
\r
83 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
\r
88 } while ((hInst == NULL) && (langId != 0));
\r
90 _tcscpy_s(buf, _T("en"));
\r
93 sHelppath = this->m_pszHelpFilePath;
\r
94 sHelppath = sHelppath.MakeLower();
\r
95 sHelppath.Replace(_T(".chm"), _T("_en.chm"));
\r
96 free((void*)m_pszHelpFilePath);
\r
97 m_pszHelpFilePath=_tcsdup(sHelppath);
\r
98 sHelppath = CPathUtils::GetAppParentDirectory() + _T("Languages\\TortoiseMerge_en.chm");
\r
101 GetLocaleInfo(MAKELCID(langId, SORT_DEFAULT), LOCALE_SISO639LANGNAME, buf, sizeof(buf)/sizeof(TCHAR));
\r
102 CString sLang = _T("_");
\r
104 sHelppath.Replace(_T("_en"), sLang);
\r
105 if (PathFileExists(sHelppath))
\r
107 free((void*)m_pszHelpFilePath);
\r
108 m_pszHelpFilePath=_tcsdup(sHelppath);
\r
111 sHelppath.Replace(sLang, _T("_en"));
\r
112 GetLocaleInfo(MAKELCID(langId, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, sizeof(buf)/sizeof(TCHAR));
\r
115 sHelppath.Replace(_T("_en"), sLang);
\r
116 if (PathFileExists(sHelppath))
\r
118 free((void*)m_pszHelpFilePath);
\r
119 m_pszHelpFilePath=_tcsdup(sHelppath);
\r
122 sHelppath.Replace(sLang, _T("_en"));
\r
124 DWORD lid = SUBLANGID(langId);
\r
128 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
\r
133 setlocale(LC_ALL, "");
\r
134 // We need to explicitly set the thread locale to the system default one to avoid possible problems with saving files in its original codepage
\r
135 // The problems occures when the language of OS differs from the regional settings
\r
136 // See the details here: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100887
\r
137 SetThreadLocale(LOCALE_SYSTEM_DEFAULT);
\r
139 // InitCommonControls() is required on Windows XP if an application
\r
140 // manifest specifies use of ComCtl32.dll version 6 or later to enable
\r
141 // visual styles. Otherwise, any window creation will fail.
\r
142 InitCommonControls();
\r
144 // Initialize all Managers for usage. They are automatically constructed
\r
145 // if not yet present
\r
146 InitContextMenuManager();
\r
147 InitKeyboardManager();
\r
149 CCmdLineParser parser = CCmdLineParser(this->m_lpCmdLine);
\r
151 if (parser.HasKey(_T("?")) || parser.HasKey(_T("help")))
\r
154 sHelpText.LoadString(IDS_COMMANDLINEHELP);
\r
155 MessageBox(NULL, sHelpText, _T("TortoiseMerge"), MB_ICONINFORMATION);
\r
159 // Initialize OLE libraries
\r
162 AfxMessageBox(IDP_OLE_INIT_FAILED);
\r
165 AfxEnableControlContainer();
\r
166 // Standard initialization
\r
167 // If you are not using these features and wish to reduce the size
\r
168 // of your final executable, you should remove from the following
\r
169 // the specific initialization routines you do not need
\r
170 // Change the registry key under which our settings are stored
\r
171 SetRegistryKey(_T("TortoiseMerge"));
\r
173 if (CRegDWORD(_T("Software\\TortoiseMerge\\Debug"), FALSE)==TRUE)
\r
174 AfxMessageBox(AfxGetApp()->m_lpCmdLine, MB_OK | MB_ICONINFORMATION);
\r
176 // To create the main window, this code creates a new frame window
\r
177 // object and then sets it as the application's main window object
\r
178 CMainFrame* pFrame = new CMainFrame;
\r
179 if (pFrame == NULL)
\r
181 m_pMainWnd = pFrame;
\r
183 // create and load the frame with its resources
\r
184 pFrame->LoadFrame(IDR_MAINFRAME,
\r
185 WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL,
\r
188 // Fill in the command line options
\r
189 pFrame->m_Data.m_baseFile.SetFileName(parser.GetVal(_T("base")));
\r
190 pFrame->m_Data.m_baseFile.SetDescriptiveName(parser.GetVal(_T("basename")));
\r
191 pFrame->m_Data.m_theirFile.SetFileName(parser.GetVal(_T("theirs")));
\r
192 pFrame->m_Data.m_theirFile.SetDescriptiveName(parser.GetVal(_T("theirsname")));
\r
193 pFrame->m_Data.m_yourFile.SetFileName(parser.GetVal(_T("mine")));
\r
194 pFrame->m_Data.m_yourFile.SetDescriptiveName(parser.GetVal(_T("minename")));
\r
195 pFrame->m_Data.m_mergedFile.SetFileName(parser.GetVal(_T("merged")));
\r
196 pFrame->m_Data.m_mergedFile.SetDescriptiveName(parser.GetVal(_T("mergedname")));
\r
197 pFrame->m_Data.m_sPatchPath = parser.HasVal(_T("patchpath")) ? parser.GetVal(_T("patchpath")) : _T("");
\r
198 pFrame->m_Data.m_sPatchPath.Replace('/', '\\');
\r
199 if (parser.HasKey(_T("patchoriginal")))
\r
200 pFrame->m_Data.m_sPatchOriginal = parser.GetVal(_T("patchoriginal"));
\r
201 if (parser.HasKey(_T("patchpatched")))
\r
202 pFrame->m_Data.m_sPatchPatched = parser.GetVal(_T("patchpatched"));
\r
203 pFrame->m_Data.m_sDiffFile = parser.GetVal(_T("diff"));
\r
204 pFrame->m_Data.m_sDiffFile.Replace('/', '\\');
\r
205 if (parser.HasKey(_T("oneway")))
\r
206 pFrame->m_bOneWay = TRUE;
\r
207 if (parser.HasKey(_T("diff")))
\r
208 pFrame->m_bOneWay = FALSE;
\r
209 if (parser.HasKey(_T("reversedpatch")))
\r
210 pFrame->m_bReversedPatch = TRUE;
\r
211 if (pFrame->m_Data.IsBaseFileInUse() && !pFrame->m_Data.IsYourFileInUse() && pFrame->m_Data.IsTheirFileInUse())
\r
213 pFrame->m_Data.m_yourFile.TransferDetailsFrom(pFrame->m_Data.m_theirFile);
\r
216 if ((!parser.HasKey(_T("patchpath")))&&(parser.HasVal(_T("diff"))))
\r
218 // a patchfile was given, but not folder path to apply the patch to
\r
219 // If the patchfile is located inside a working copy, then use the parent directory
\r
220 // of the patchfile as the target directory, otherwise ask the user for a path.
\r
221 if (parser.HasKey(_T("wc")))
\r
222 pFrame->m_Data.m_sPatchPath = pFrame->m_Data.m_sDiffFile.Left(pFrame->m_Data.m_sDiffFile.ReverseFind('\\'));
\r
225 CBrowseFolder fbrowser;
\r
226 fbrowser.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
\r
227 if (fbrowser.Show(NULL, pFrame->m_Data.m_sPatchPath)==CBrowseFolder::CANCEL)
\r
232 if ((parser.HasKey(_T("patchpath")))&&(!parser.HasVal(_T("diff"))))
\r
234 // A path was given for applying a patchfile, but
\r
235 // the patchfile itself was not.
\r
236 // So ask the user for that patchfile
\r
238 OPENFILENAME ofn = {0}; // common dialog box structure
\r
239 TCHAR szFile[MAX_PATH] = {0}; // buffer for file name
\r
240 // Initialize OPENFILENAME
\r
241 ofn.lStructSize = sizeof(OPENFILENAME);
\r
242 ofn.hwndOwner = pFrame->m_hWnd;
\r
243 ofn.lpstrFile = szFile;
\r
244 ofn.nMaxFile = sizeof(szFile)/sizeof(TCHAR);
\r
246 temp.LoadString(IDS_OPENDIFFFILETITLE);
\r
247 if (temp.IsEmpty())
\r
248 ofn.lpstrTitle = NULL;
\r
250 ofn.lpstrTitle = temp;
\r
252 ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER;
\r
253 // check if there's a patchfile in the clipboard
\r
254 UINT cFormat = RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
\r
257 if (OpenClipboard(NULL))
\r
259 UINT enumFormat = 0;
\r
262 if (enumFormat == cFormat)
\r
264 // yes, there's a patchfile in the clipboard
\r
265 ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK | OFN_EXPLORER;
\r
267 ofn.hInstance = AfxGetResourceHandle();
\r
268 ofn.lpTemplateName = MAKEINTRESOURCE(IDD_PATCH_FILE_OPEN_CUSTOM);
\r
269 ofn.lpfnHook = CreatePatchFileOpenHook;
\r
271 } while((enumFormat = EnumClipboardFormats(enumFormat))!=0);
\r
277 sFilter.LoadString(IDS_PATCHFILEFILTER);
\r
278 TCHAR * pszFilters = new TCHAR[sFilter.GetLength()+4];
\r
279 _tcscpy_s (pszFilters, sFilter.GetLength()+4, sFilter);
\r
280 // Replace '|' delimiters with '\0's
\r
281 TCHAR *ptr = pszFilters + _tcslen(pszFilters); //set ptr at the NULL
\r
282 while (ptr != pszFilters)
\r
288 ofn.lpstrFilter = pszFilters;
\r
289 ofn.nFilterIndex = 1;
\r
291 // Display the Open dialog box.
\r
293 if (GetOpenFileName(&ofn)==FALSE)
\r
295 delete [] pszFilters;
\r
298 delete [] pszFilters;
\r
299 pFrame->m_Data.m_sDiffFile = ofn.lpstrFile;
\r
302 if ( pFrame->m_Data.m_baseFile.GetFilename().IsEmpty() && pFrame->m_Data.m_yourFile.GetFilename().IsEmpty() )
\r
307 szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
\r
308 if( NULL == szArglist )
\r
310 TRACE("CommandLineToArgvW failed\n");
\r
314 if ( nArgs==3 || nArgs==4 )
\r
316 // Four parameters:
\r
317 // [0]: Program name
\r
320 // [3]: THEIR file (optional)
\r
321 // This is the same format CAppUtils::StartExtDiff
\r
322 // uses if %base and %mine are not set and most
\r
323 // other diff tools use it too.
\r
324 if ( PathFileExists(szArglist[1]) && PathFileExists(szArglist[2]) )
\r
326 pFrame->m_Data.m_baseFile.SetFileName(szArglist[1]);
\r
327 pFrame->m_Data.m_yourFile.SetFileName(szArglist[2]);
\r
328 if ( nArgs == 4 && PathFileExists(szArglist[3]) )
\r
330 pFrame->m_Data.m_theirFile.SetFileName(szArglist[3]);
\r
336 // Free memory allocated for CommandLineToArgvW arguments.
\r
337 LocalFree(szArglist);
\r
340 pFrame->m_bReadOnly = !!parser.HasKey(_T("readonly"));
\r
341 if (GetFileAttributes(pFrame->m_Data.m_yourFile.GetFilename()) & FILE_ATTRIBUTE_READONLY)
\r
342 pFrame->m_bReadOnly = true;
\r
343 pFrame->m_bBlame = !!parser.HasKey(_T("blame"));
\r
344 // diffing a blame means no editing!
\r
345 if (pFrame->m_bBlame)
\r
346 pFrame->m_bReadOnly = true;
\r
348 // try to find a suitable window title
\r
349 CString sYour = pFrame->m_Data.m_yourFile.GetDescriptiveName();
\r
350 if (sYour.Find(_T(" - "))>=0)
\r
351 sYour = sYour.Left(sYour.Find(_T(" - ")));
\r
352 if (sYour.Find(_T(" : "))>=0)
\r
353 sYour = sYour.Left(sYour.Find(_T(" : ")));
\r
354 CString sTheir = pFrame->m_Data.m_theirFile.GetDescriptiveName();
\r
355 if (sTheir.Find(_T(" - "))>=0)
\r
356 sTheir = sTheir.Left(sTheir.Find(_T(" - ")));
\r
357 if (sTheir.Find(_T(" : "))>=0)
\r
358 sTheir = sTheir.Left(sTheir.Find(_T(" : ")));
\r
360 if (!sYour.IsEmpty() && !sTheir.IsEmpty())
\r
362 if (sYour.CompareNoCase(sTheir)==0)
\r
363 pFrame->SetWindowText(sYour + _T(" - TortoiseMerge"));
\r
364 else if ((sYour.GetLength() < 10) &&
\r
365 (sTheir.GetLength() < 10))
\r
366 pFrame->SetWindowText(sYour + _T(" - ") + sTheir + _T(" - TortoiseMerge"));
\r
369 // we have two very long descriptive texts here, which
\r
370 // means we have to find a way to use them as a window
\r
371 // title in a shorter way.
\r
372 // for simplicity, we just use the one from "yourfile"
\r
373 pFrame->SetWindowText(sYour + _T(" - TortoiseMerge"));
\r
376 else if (!sYour.IsEmpty())
\r
377 pFrame->SetWindowText(sYour + _T(" - TortoiseMerge"));
\r
378 else if (!sTheir.IsEmpty())
\r
379 pFrame->SetWindowText(sTheir + _T(" - TortoiseMerge"));
\r
381 if (parser.HasKey(_T("createunifieddiff")))
\r
383 // user requested to create a unified diff file
\r
384 CString origFile = parser.GetVal(_T("origfile"));
\r
385 CString modifiedFile = parser.GetVal(_T("modifiedfile"));
\r
386 if (!origFile.IsEmpty() && !modifiedFile.IsEmpty())
\r
388 CString outfile = parser.GetVal(_T("outfile"));
\r
389 if (outfile.IsEmpty())
\r
391 OPENFILENAME ofn = {0}; // common dialog box structure
\r
392 TCHAR szFile[MAX_PATH] = {0}; // buffer for file name
\r
393 ofn.lStructSize = sizeof(OPENFILENAME);
\r
394 ofn.lpstrFile = szFile;
\r
395 ofn.nMaxFile = sizeof(szFile)/sizeof(TCHAR);
\r
397 temp.LoadString(IDS_SAVEASTITLE);
\r
398 if (!temp.IsEmpty())
\r
399 ofn.lpstrTitle = temp;
\r
400 ofn.Flags = OFN_OVERWRITEPROMPT;
\r
402 sFilter.LoadString(IDS_COMMONFILEFILTER);
\r
403 TCHAR * pszFilters = new TCHAR[sFilter.GetLength()+4];
\r
404 _tcscpy_s (pszFilters, sFilter.GetLength()+4, sFilter);
\r
405 // Replace '|' delimiters with '\0's
\r
406 TCHAR *ptr = pszFilters + _tcslen(pszFilters); //set ptr at the NULL
\r
407 while (ptr != pszFilters)
\r
413 ofn.lpstrFilter = pszFilters;
\r
414 ofn.nFilterIndex = 1;
\r
416 // Display the Save dialog box.
\r
418 if (GetSaveFileName(&ofn)==TRUE)
\r
420 outfile = CString(ofn.lpstrFile);
\r
422 delete [] pszFilters;
\r
424 if (!outfile.IsEmpty())
\r
426 CAppUtils::CreateUnifiedDiff(origFile, modifiedFile, outfile, false);
\r
431 // The one and only window has been initialized, so show and update it
\r
432 pFrame->ActivateFrame();
\r
433 pFrame->ShowWindow(SW_SHOW);
\r
434 pFrame->UpdateWindow();
\r
435 pFrame->ShowDiffBar(!pFrame->m_bOneWay);
\r
436 if (!pFrame->m_Data.IsBaseFileInUse() && pFrame->m_Data.m_sPatchPath.IsEmpty() && pFrame->m_Data.m_sDiffFile.IsEmpty())
\r
438 pFrame->OnFileOpen();
\r
442 return pFrame->LoadViews();
\r
445 // CTortoiseMergeApp message handlers
\r
447 void CTortoiseMergeApp::OnAppAbout()
\r
449 CAboutDlg aboutDlg;
\r
450 aboutDlg.DoModal();
\r
454 CTortoiseMergeApp::CreatePatchFileOpenHook(HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM /*lParam*/)
\r
456 if(uiMsg == WM_COMMAND && LOWORD(wParam) == IDC_PATCH_TO_CLIPBOARD)
\r
458 HWND hFileDialog = GetParent(hDlg);
\r
460 // if there's a patchfile in the clipboard, we save it
\r
461 // to a temporary file and tell TortoiseMerge to use that one
\r
462 UINT cFormat = RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
\r
463 if ((cFormat)&&(OpenClipboard(NULL)))
\r
465 HGLOBAL hglb = GetClipboardData(cFormat);
\r
466 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
\r
468 DWORD len = GetTempPath(0, NULL);
\r
469 TCHAR * path = new TCHAR[len+1];
\r
470 TCHAR * tempF = new TCHAR[len+100];
\r
471 GetTempPath (len+1, path);
\r
472 GetTempFileName (path, TEXT("tsm"), 0, tempF);
\r
473 std::wstring sTempFile = std::wstring(tempF);
\r
478 size_t patchlen = strlen(lpstr);
\r
479 _tfopen_s(&outFile, sTempFile.c_str(), _T("wb"));
\r
482 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
\r
483 if (size == patchlen)
\r
485 CommDlg_OpenSave_SetControlText(hFileDialog, edt1, sTempFile.c_str());
\r
486 PostMessage(hFileDialog, WM_COMMAND, MAKEWPARAM(IDOK, BM_CLICK), (LPARAM)(GetDlgItem(hDlg, IDOK)));
\r
490 GlobalUnlock(hglb);
\r
497 int CTortoiseMergeApp::ExitInstance()
\r
499 // Look for temporary files left around by TortoiseMerge and
\r
500 // remove them. But only delete 'old' files
\r
501 DWORD len = ::GetTempPath(0, NULL);
\r
502 TCHAR * path = new TCHAR[len + 100];
\r
503 len = ::GetTempPath (len+100, path);
\r
506 CSimpleFileFind finder = CSimpleFileFind(path, _T("*tsm*.*"));
\r
508 ::GetSystemTimeAsFileTime(&systime_);
\r
509 __int64 systime = (((_int64)systime_.dwHighDateTime)<<32) | ((__int64)systime_.dwLowDateTime);
\r
510 while (finder.FindNextFileNoDirectories())
\r
512 CString filepath = finder.GetFilePath();
\r
513 HANDLE hFile = ::CreateFile(filepath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
\r
514 if (hFile != INVALID_HANDLE_VALUE)
\r
516 FILETIME createtime_;
\r
517 if (::GetFileTime(hFile, &createtime_, NULL, NULL))
\r
519 ::CloseHandle(hFile);
\r
520 __int64 createtime = (((_int64)createtime_.dwHighDateTime)<<32) | ((__int64)createtime_.dwLowDateTime);
\r
521 if ((createtime + 864000000000) < systime) //only delete files older than a day
\r
523 ::SetFileAttributes(filepath, FILE_ATTRIBUTE_NORMAL);
\r
524 ::DeleteFile(filepath);
\r
528 ::CloseHandle(hFile);
\r
534 return CWinAppEx::ExitInstance();
\r