1 // TortoiseMerge - a Diff/Patch program
\r
3 // Copyright (C) 2006-2008 - 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
31 #define new DEBUG_NEW
\r
34 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
\r
36 BEGIN_MESSAGE_MAP(CTortoiseMergeApp, CWinAppEx)
\r
37 ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
\r
41 CTortoiseMergeApp::CTortoiseMergeApp()
\r
44 m_bLoadUserToolbars = FALSE;
\r
45 m_bSaveState = FALSE;
\r
48 // The one and only CTortoiseMergeApp object
\r
49 CTortoiseMergeApp theApp;
\r
50 //CCrashReport g_crasher("crashreports@tortoisesvn.tigris.org", "Crash Report for TortoiseMerge " APP_X64_STRING " : " STRPRODUCTVER, TRUE);
\r
52 // CTortoiseMergeApp initialization
\r
53 BOOL CTortoiseMergeApp::InitInstance()
\r
55 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
\r
56 CMFCButton::EnableWindowsTheming();
\r
57 //set the resource dll for the required language
\r
58 CRegDWORD loc = CRegDWORD(_T("Software\\TortoiseGit\\LanguageID"), 1033);
\r
61 HINSTANCE hInst = NULL;
\r
64 langDll.Format(_T("..\\Languages\\TortoiseMerge%d.dll"), langId);
\r
66 hInst = LoadLibrary(langDll);
\r
67 CString sVer = _T(STRPRODUCTVER);
\r
68 CString sFileVer = CPathUtils::GetVersionFromFile(langDll);
\r
69 if (sFileVer.Compare(sVer)!=0)
\r
75 AfxSetResourceHandle(hInst);
\r
78 DWORD lid = SUBLANGID(langId);
\r
82 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
\r
87 } while ((hInst == NULL) && (langId != 0));
\r
89 _tcscpy_s(buf, _T("en"));
\r
92 sHelppath = this->m_pszHelpFilePath;
\r
93 sHelppath = sHelppath.MakeLower();
\r
94 sHelppath.Replace(_T(".chm"), _T("_en.chm"));
\r
95 free((void*)m_pszHelpFilePath);
\r
96 m_pszHelpFilePath=_tcsdup(sHelppath);
\r
97 sHelppath = CPathUtils::GetAppParentDirectory() + _T("Languages\\TortoiseMerge_en.chm");
\r
100 GetLocaleInfo(MAKELCID(langId, SORT_DEFAULT), LOCALE_SISO639LANGNAME, buf, sizeof(buf));
\r
101 CString sLang = _T("_");
\r
103 sHelppath.Replace(_T("_en"), sLang);
\r
104 if (PathFileExists(sHelppath))
\r
106 free((void*)m_pszHelpFilePath);
\r
107 m_pszHelpFilePath=_tcsdup(sHelppath);
\r
110 sHelppath.Replace(sLang, _T("_en"));
\r
111 GetLocaleInfo(MAKELCID(langId, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, sizeof(buf));
\r
114 sHelppath.Replace(_T("_en"), sLang);
\r
115 if (PathFileExists(sHelppath))
\r
117 free((void*)m_pszHelpFilePath);
\r
118 m_pszHelpFilePath=_tcsdup(sHelppath);
\r
121 sHelppath.Replace(sLang, _T("_en"));
\r
123 DWORD lid = SUBLANGID(langId);
\r
127 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
\r
132 setlocale(LC_ALL, "");
\r
134 // InitCommonControls() is required on Windows XP if an application
\r
135 // manifest specifies use of ComCtl32.dll version 6 or later to enable
\r
136 // visual styles. Otherwise, any window creation will fail.
\r
137 InitCommonControls();
\r
139 // Initialize all Managers for usage. They are automatically constructed
\r
140 // if not yet present
\r
141 InitContextMenuManager();
\r
142 InitKeyboardManager();
\r
144 CCmdLineParser parser = CCmdLineParser(this->m_lpCmdLine);
\r
146 if (parser.HasKey(_T("?")) || parser.HasKey(_T("help")))
\r
149 sHelpText.LoadString(IDS_COMMANDLINEHELP);
\r
150 MessageBox(NULL, sHelpText, _T("TortoiseMerge"), MB_ICONINFORMATION);
\r
154 // Initialize OLE libraries
\r
157 AfxMessageBox(IDP_OLE_INIT_FAILED);
\r
160 AfxEnableControlContainer();
\r
161 // Standard initialization
\r
162 // If you are not using these features and wish to reduce the size
\r
163 // of your final executable, you should remove from the following
\r
164 // the specific initialization routines you do not need
\r
165 // Change the registry key under which our settings are stored
\r
166 SetRegistryKey(_T("TortoiseMerge"));
\r
168 if (CRegDWORD(_T("Software\\TortoiseMerge\\Debug"), FALSE)==TRUE)
\r
169 AfxMessageBox(AfxGetApp()->m_lpCmdLine, MB_OK | MB_ICONINFORMATION);
\r
171 // To create the main window, this code creates a new frame window
\r
172 // object and then sets it as the application's main window object
\r
173 CMainFrame* pFrame = new CMainFrame;
\r
174 if (pFrame == NULL)
\r
176 m_pMainWnd = pFrame;
\r
178 // create and load the frame with its resources
\r
179 pFrame->LoadFrame(IDR_MAINFRAME,
\r
180 WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL,
\r
183 // Fill in the command line options
\r
184 pFrame->m_Data.m_baseFile.SetFileName(parser.GetVal(_T("base")));
\r
185 pFrame->m_Data.m_baseFile.SetDescriptiveName(parser.GetVal(_T("basename")));
\r
186 pFrame->m_Data.m_theirFile.SetFileName(parser.GetVal(_T("theirs")));
\r
187 pFrame->m_Data.m_theirFile.SetDescriptiveName(parser.GetVal(_T("theirsname")));
\r
188 pFrame->m_Data.m_yourFile.SetFileName(parser.GetVal(_T("mine")));
\r
189 pFrame->m_Data.m_yourFile.SetDescriptiveName(parser.GetVal(_T("minename")));
\r
190 pFrame->m_Data.m_mergedFile.SetFileName(parser.GetVal(_T("merged")));
\r
191 pFrame->m_Data.m_mergedFile.SetDescriptiveName(parser.GetVal(_T("mergedname")));
\r
192 pFrame->m_Data.m_sPatchPath = parser.HasVal(_T("patchpath")) ? parser.GetVal(_T("patchpath")) : _T("");
\r
193 pFrame->m_Data.m_sPatchPath.Replace('/', '\\');
\r
194 if (parser.HasKey(_T("patchoriginal")))
\r
195 pFrame->m_Data.m_sPatchOriginal = parser.GetVal(_T("patchoriginal"));
\r
196 if (parser.HasKey(_T("patchpatched")))
\r
197 pFrame->m_Data.m_sPatchPatched = parser.GetVal(_T("patchpatched"));
\r
198 pFrame->m_Data.m_sDiffFile = parser.GetVal(_T("diff"));
\r
199 pFrame->m_Data.m_sDiffFile.Replace('/', '\\');
\r
200 if (parser.HasKey(_T("oneway")))
\r
201 pFrame->m_bOneWay = TRUE;
\r
202 if (parser.HasKey(_T("diff")))
\r
203 pFrame->m_bOneWay = FALSE;
\r
204 if (parser.HasKey(_T("reversedpatch")))
\r
205 pFrame->m_bReversedPatch = TRUE;
\r
206 if (pFrame->m_Data.IsBaseFileInUse() && !pFrame->m_Data.IsYourFileInUse() && pFrame->m_Data.IsTheirFileInUse())
\r
208 pFrame->m_Data.m_yourFile.TransferDetailsFrom(pFrame->m_Data.m_theirFile);
\r
211 if ((!parser.HasKey(_T("patchpath")))&&(parser.HasVal(_T("diff"))))
\r
213 // a patchfile was given, but not folder path to apply the patch to
\r
214 // If the patchfile is located inside a working copy, then use the parent directory
\r
215 // of the patchfile as the target directory, otherwise ask the user for a path.
\r
216 if (parser.HasKey(_T("wc")))
\r
217 pFrame->m_Data.m_sPatchPath = pFrame->m_Data.m_sDiffFile.Left(pFrame->m_Data.m_sDiffFile.ReverseFind('\\'));
\r
220 CBrowseFolder fbrowser;
\r
221 fbrowser.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
\r
222 if (fbrowser.Show(NULL, pFrame->m_Data.m_sPatchPath)==CBrowseFolder::CANCEL)
\r
227 if ((parser.HasKey(_T("patchpath")))&&(!parser.HasVal(_T("diff"))))
\r
229 // A path was given for applying a patchfile, but
\r
230 // the patchfile itself was not.
\r
231 // So ask the user for that patchfile
\r
233 OPENFILENAME ofn = {0}; // common dialog box structure
\r
234 TCHAR szFile[MAX_PATH] = {0}; // buffer for file name
\r
235 // Initialize OPENFILENAME
\r
236 ofn.lStructSize = sizeof(OPENFILENAME);
\r
237 ofn.hwndOwner = pFrame->m_hWnd;
\r
238 ofn.lpstrFile = szFile;
\r
239 ofn.nMaxFile = sizeof(szFile)/sizeof(TCHAR);
\r
241 temp.LoadString(IDS_OPENDIFFFILETITLE);
\r
242 if (temp.IsEmpty())
\r
243 ofn.lpstrTitle = NULL;
\r
245 ofn.lpstrTitle = temp;
\r
247 ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER;
\r
248 // check if there's a patchfile in the clipboard
\r
249 UINT cFormat = RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
\r
252 if (OpenClipboard(NULL))
\r
254 UINT enumFormat = 0;
\r
257 if (enumFormat == cFormat)
\r
259 // yes, there's a patchfile in the clipboard
\r
260 ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK | OFN_EXPLORER;
\r
262 ofn.hInstance = AfxGetResourceHandle();
\r
263 ofn.lpTemplateName = MAKEINTRESOURCE(IDD_PATCH_FILE_OPEN_CUSTOM);
\r
264 ofn.lpfnHook = CreatePatchFileOpenHook;
\r
266 } while((enumFormat = EnumClipboardFormats(enumFormat))!=0);
\r
272 sFilter.LoadString(IDS_PATCHFILEFILTER);
\r
273 TCHAR * pszFilters = new TCHAR[sFilter.GetLength()+4];
\r
274 _tcscpy_s (pszFilters, sFilter.GetLength()+4, sFilter);
\r
275 // Replace '|' delimiters with '\0's
\r
276 TCHAR *ptr = pszFilters + _tcslen(pszFilters); //set ptr at the NULL
\r
277 while (ptr != pszFilters)
\r
283 ofn.lpstrFilter = pszFilters;
\r
284 ofn.nFilterIndex = 1;
\r
286 // Display the Open dialog box.
\r
288 if (GetOpenFileName(&ofn)==FALSE)
\r
290 delete [] pszFilters;
\r
293 delete [] pszFilters;
\r
294 pFrame->m_Data.m_sDiffFile = ofn.lpstrFile;
\r
297 if ( pFrame->m_Data.m_baseFile.GetFilename().IsEmpty() && pFrame->m_Data.m_yourFile.GetFilename().IsEmpty() )
\r
302 szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
\r
303 if( NULL == szArglist )
\r
305 TRACE("CommandLineToArgvW failed\n");
\r
309 if ( nArgs==3 || nArgs==4 )
\r
311 // Four parameters:
\r
312 // [0]: Program name
\r
315 // [3]: THEIR file (optional)
\r
316 // This is the same format CAppUtils::StartExtDiff
\r
317 // uses if %base and %mine are not set and most
\r
318 // other diff tools use it too.
\r
319 if ( PathFileExists(szArglist[1]) && PathFileExists(szArglist[2]) )
\r
321 pFrame->m_Data.m_baseFile.SetFileName(szArglist[1]);
\r
322 pFrame->m_Data.m_yourFile.SetFileName(szArglist[2]);
\r
323 if ( nArgs == 4 && PathFileExists(szArglist[3]) )
\r
325 pFrame->m_Data.m_theirFile.SetFileName(szArglist[3]);
\r
331 // Free memory allocated for CommandLineToArgvW arguments.
\r
332 LocalFree(szArglist);
\r
335 pFrame->m_bReadOnly = !!parser.HasKey(_T("readonly"));
\r
336 if (GetFileAttributes(pFrame->m_Data.m_yourFile.GetFilename()) & FILE_ATTRIBUTE_READONLY)
\r
337 pFrame->m_bReadOnly = true;
\r
338 pFrame->m_bBlame = !!parser.HasKey(_T("blame"));
\r
339 // diffing a blame means no editing!
\r
340 if (pFrame->m_bBlame)
\r
341 pFrame->m_bReadOnly = true;
\r
343 // try to find a suitable window title
\r
344 CString sYour = pFrame->m_Data.m_yourFile.GetDescriptiveName();
\r
345 if (sYour.Find(_T(" - "))>=0)
\r
346 sYour = sYour.Left(sYour.Find(_T(" - ")));
\r
347 if (sYour.Find(_T(" : "))>=0)
\r
348 sYour = sYour.Left(sYour.Find(_T(" : ")));
\r
349 CString sTheir = pFrame->m_Data.m_theirFile.GetDescriptiveName();
\r
350 if (sTheir.Find(_T(" - "))>=0)
\r
351 sTheir = sTheir.Left(sTheir.Find(_T(" - ")));
\r
352 if (sTheir.Find(_T(" : "))>=0)
\r
353 sTheir = sTheir.Left(sTheir.Find(_T(" : ")));
\r
355 if (!sYour.IsEmpty() && !sTheir.IsEmpty())
\r
357 if (sYour.CompareNoCase(sTheir)==0)
\r
358 pFrame->SetWindowText(sYour + _T(" - TortoiseMerge"));
\r
359 else if ((sYour.GetLength() < 10) &&
\r
360 (sTheir.GetLength() < 10))
\r
361 pFrame->SetWindowText(sYour + _T(" - ") + sTheir + _T(" - TortoiseMerge"));
\r
364 // we have two very long descriptive texts here, which
\r
365 // means we have to find a way to use them as a window
\r
366 // title in a shorter way.
\r
367 // for simplicity, we just use the one from "yourfile"
\r
368 pFrame->SetWindowText(sYour + _T(" - TortoiseMerge"));
\r
371 else if (!sYour.IsEmpty())
\r
372 pFrame->SetWindowText(sYour + _T(" - TortoiseMerge"));
\r
373 else if (!sTheir.IsEmpty())
\r
374 pFrame->SetWindowText(sTheir + _T(" - TortoiseMerge"));
\r
376 if (parser.HasKey(_T("createunifieddiff")))
\r
378 // user requested to create a unified diff file
\r
379 CString origFile = parser.GetVal(_T("origfile"));
\r
380 CString modifiedFile = parser.GetVal(_T("modifiedfile"));
\r
381 if (!origFile.IsEmpty() && !modifiedFile.IsEmpty())
\r
383 CString outfile = parser.GetVal(_T("outfile"));
\r
384 if (outfile.IsEmpty())
\r
386 OPENFILENAME ofn = {0}; // common dialog box structure
\r
387 TCHAR szFile[MAX_PATH] = {0}; // buffer for file name
\r
388 ofn.lStructSize = sizeof(OPENFILENAME);
\r
389 ofn.lpstrFile = szFile;
\r
390 ofn.nMaxFile = sizeof(szFile)/sizeof(TCHAR);
\r
392 temp.LoadString(IDS_SAVEASTITLE);
\r
393 if (!temp.IsEmpty())
\r
394 ofn.lpstrTitle = temp;
\r
395 ofn.Flags = OFN_OVERWRITEPROMPT;
\r
397 sFilter.LoadString(IDS_COMMONFILEFILTER);
\r
398 TCHAR * pszFilters = new TCHAR[sFilter.GetLength()+4];
\r
399 _tcscpy_s (pszFilters, sFilter.GetLength()+4, sFilter);
\r
400 // Replace '|' delimiters with '\0's
\r
401 TCHAR *ptr = pszFilters + _tcslen(pszFilters); //set ptr at the NULL
\r
402 while (ptr != pszFilters)
\r
408 ofn.lpstrFilter = pszFilters;
\r
409 ofn.nFilterIndex = 1;
\r
411 // Display the Save dialog box.
\r
413 if (GetSaveFileName(&ofn)==TRUE)
\r
415 outfile = CString(ofn.lpstrFile);
\r
417 delete [] pszFilters;
\r
419 if (!outfile.IsEmpty())
\r
421 CAppUtils::CreateUnifiedDiff(origFile, modifiedFile, outfile, false);
\r
426 // The one and only window has been initialized, so show and update it
\r
427 pFrame->ActivateFrame();
\r
428 pFrame->ShowWindow(SW_SHOW);
\r
429 pFrame->UpdateWindow();
\r
430 if (!pFrame->m_Data.IsBaseFileInUse() && pFrame->m_Data.m_sPatchPath.IsEmpty() && pFrame->m_Data.m_sDiffFile.IsEmpty())
\r
432 pFrame->OnFileOpen();
\r
435 return pFrame->LoadViews();
\r
438 // CTortoiseMergeApp message handlers
\r
440 void CTortoiseMergeApp::OnAppAbout()
\r
442 CAboutDlg aboutDlg;
\r
443 aboutDlg.DoModal();
\r
447 CTortoiseMergeApp::CreatePatchFileOpenHook(HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM /*lParam*/)
\r
449 if(uiMsg == WM_COMMAND && LOWORD(wParam) == IDC_PATCH_TO_CLIPBOARD)
\r
451 HWND hFileDialog = GetParent(hDlg);
\r
453 // if there's a patchfile in the clipboard, we save it
\r
454 // to a temporary file and tell TortoiseMerge to use that one
\r
455 UINT cFormat = RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
\r
456 if ((cFormat)&&(OpenClipboard(NULL)))
\r
458 HGLOBAL hglb = GetClipboardData(cFormat);
\r
459 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
\r
461 DWORD len = GetTempPath(0, NULL);
\r
462 TCHAR * path = new TCHAR[len+1];
\r
463 TCHAR * tempF = new TCHAR[len+100];
\r
464 GetTempPath (len+1, path);
\r
465 GetTempFileName (path, TEXT("svn"), 0, tempF);
\r
466 std::wstring sTempFile = std::wstring(tempF);
\r
471 size_t patchlen = strlen(lpstr);
\r
472 _tfopen_s(&outFile, sTempFile.c_str(), _T("wb"));
\r
475 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
\r
476 if (size == patchlen)
\r
478 CommDlg_OpenSave_SetControlText(hFileDialog, edt1, sTempFile.c_str());
\r
479 PostMessage(hFileDialog, WM_COMMAND, MAKEWPARAM(IDOK, BM_CLICK), (LPARAM)(GetDlgItem(hDlg, IDOK)));
\r
483 GlobalUnlock(hglb);
\r