From 6db9a048985ef7a58950fc4530c7b0e7ac998fd6 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Sat, 29 Dec 2012 01:26:47 +0100 Subject: [PATCH] Took all code from TortoiseMerge and made it compile again Based on TortoiseSVN revision 23767, but not dropped TGit patches which applied cleanly. Signed-off-by: Sven Strickroth --- src/Git/GitPatch.cpp | 289 ++ src/Git/GitPatch.h | 134 + src/Resources/Margin.cur | Bin 0 -> 13942 bytes src/Resources/TortoiseMerge.rc2 | 14 +- src/Resources/TortoiseMergeENG.rc | 613 +-- src/Resources/moved.ico | Bin 0 -> 894 bytes src/Resources/ribbon.mfcribbon-ms | 2 + src/Resources/ribbon/About.png | Bin 0 -> 1762 bytes src/Resources/ribbon/Check.png | Bin 0 -> 2806 bytes src/Resources/ribbon/Collapse.png | Bin 0 -> 720 bytes src/Resources/ribbon/Copy.png | Bin 0 -> 590 bytes src/Resources/ribbon/DownGreen.png | Bin 0 -> 1679 bytes src/Resources/ribbon/DownGreenDots.png | Bin 0 -> 1662 bytes src/Resources/ribbon/DownRed.png | Bin 0 -> 1335 bytes src/Resources/ribbon/Exit.png | Bin 0 -> 2016 bytes src/Resources/ribbon/FileList.png | Bin 0 -> 1742 bytes src/Resources/ribbon/Goto.png | Bin 0 -> 744 bytes src/Resources/ribbon/Help.png | Bin 0 -> 1461 bytes src/Resources/ribbon/InlineDiff.png | Bin 0 -> 144 bytes src/Resources/ribbon/InlineDiffWord.png | Bin 0 -> 481 bytes src/Resources/ribbon/LeftGreen.png | Bin 0 -> 1924 bytes src/Resources/ribbon/LeftGreenDots.png | Bin 0 -> 1624 bytes src/Resources/ribbon/LeftRed.png | Bin 0 -> 1612 bytes src/Resources/ribbon/OneWayDiff.png | Bin 0 -> 595 bytes src/Resources/ribbon/Open.png | Bin 0 -> 2645 bytes src/Resources/ribbon/OrangeDotDown.png | Bin 0 -> 1864 bytes src/Resources/ribbon/OrangeDotLeft.png | Bin 0 -> 1878 bytes src/Resources/ribbon/OrangeDotRight.png | Bin 0 -> 1900 bytes src/Resources/ribbon/OrangeDotUp.png | Bin 0 -> 1885 bytes src/Resources/ribbon/Paste.png | Bin 0 -> 1236 bytes src/Resources/ribbon/Pilcrow.png | Bin 0 -> 263 bytes src/Resources/ribbon/Refresh.png | Bin 0 -> 4684 bytes src/Resources/ribbon/RightGreen.png | Bin 0 -> 1917 bytes src/Resources/ribbon/RightGreenDots.png | Bin 0 -> 1621 bytes src/Resources/ribbon/RightRed.png | Bin 0 -> 1598 bytes src/Resources/ribbon/Save.png | Bin 0 -> 3223 bytes src/Resources/ribbon/SaveAs.png | Bin 0 -> 4121 bytes src/Resources/ribbon/Search.png | Bin 0 -> 2477 bytes src/Resources/ribbon/Settings.png | Bin 0 -> 1738 bytes src/Resources/ribbon/Switch.png | Bin 0 -> 1240 bytes src/Resources/ribbon/TortoiseMerge.png | Bin 0 -> 1868 bytes src/Resources/ribbon/Undo.png | Bin 0 -> 2737 bytes src/Resources/ribbon/UniDiff.png | Bin 0 -> 1341 bytes src/Resources/ribbon/UpGreen.png | Bin 0 -> 1659 bytes src/Resources/ribbon/UpGreenDots.png | Bin 0 -> 1640 bytes src/Resources/ribbon/UpRed.png | Bin 0 -> 1609 bytes src/Resources/ribbon/WhiteSpace1.png | Bin 0 -> 1563 bytes src/Resources/ribbon/WhiteSpace2.png | Bin 0 -> 690 bytes src/Resources/ribbon/WhiteSpace3.png | Bin 0 -> 1390 bytes src/Resources/ribbon/WrapLines.png | Bin 0 -> 305 bytes src/Resources/ribbon/createribbonimglist.bat | 8 + src/Resources/ribbon/ribbonimagelist.txt | 44 + src/Resources/ribbon/ribbonlarge.png | Bin 0 -> 55365 bytes src/Resources/ribbon/ribbonsmall.png | Bin 0 -> 23754 bytes src/TortoiseGitBlame/TortoiseGitBlameView.cpp | 5 +- src/TortoiseMerge/AboutDlg.cpp | 4 +- src/TortoiseMerge/AboutDlg.h | 2 +- src/TortoiseMerge/AppUtils.cpp | 24 +- src/TortoiseMerge/AppUtils.h | 3 +- src/TortoiseMerge/BaseView.cpp | 4687 ++++++++++++++------ src/TortoiseMerge/BaseView.h | 426 +- src/TortoiseMerge/BottomView.cpp | 371 +- src/TortoiseMerge/BottomView.h | 18 +- src/TortoiseMerge/DiffColors.cpp | 10 +- src/TortoiseMerge/DiffColors.h | 15 +- src/TortoiseMerge/DiffData.cpp | 794 ++-- src/TortoiseMerge/DiffData.h | 36 +- src/TortoiseMerge/DiffStates.h | 6 +- src/TortoiseMerge/EOL.h | 18 +- src/TortoiseMerge/EditGotoDlg.cpp | 56 - src/TortoiseMerge/FilePatchesDlg.cpp | 174 +- src/TortoiseMerge/FilePatchesDlg.h | 23 +- src/TortoiseMerge/FileTextLines.cpp | 1495 ++++--- src/TortoiseMerge/FileTextLines.h | 225 +- src/TortoiseMerge/FindDlg.cpp | 14 +- src/TortoiseMerge/FindDlg.h | 7 +- src/TortoiseMerge/GotoLineDlg.cpp | 83 + src/TortoiseMerge/{EditGotoDlg.h => GotoLineDlg.h} | 34 +- src/TortoiseMerge/LeftView.cpp | 340 +- src/TortoiseMerge/LeftView.h | 7 +- src/TortoiseMerge/LineDiffBar.cpp | 52 +- src/TortoiseMerge/LineDiffBar.h | 2 +- src/TortoiseMerge/LocatorBar.cpp | 692 ++- src/TortoiseMerge/LocatorBar.h | 19 +- src/TortoiseMerge/MainFrm.cpp | 1704 +++---- src/TortoiseMerge/MainFrm.h | 115 +- src/TortoiseMerge/MovedBlocks.cpp | 485 ++ src/TortoiseMerge/{TempFiles.h => MovedBlocks.h} | 25 +- src/TortoiseMerge/OpenDlg.cpp | 17 +- src/TortoiseMerge/OpenDlg.h | 2 +- src/TortoiseMerge/Patch.cpp | 3 +- src/TortoiseMerge/Patch.h | 8 +- src/TortoiseMerge/RightView.cpp | 610 ++- src/TortoiseMerge/RightView.h | 16 +- src/TortoiseMerge/SetColorPage.cpp | 156 +- src/TortoiseMerge/SetColorPage.h | 4 +- src/TortoiseMerge/SetMainPage.cpp | 91 +- src/TortoiseMerge/SetMainPage.h | 26 +- src/TortoiseMerge/Settings.cpp | 6 +- src/TortoiseMerge/Settings.h | 2 +- src/TortoiseMerge/TempFile.cpp | 179 + src/TortoiseMerge/TempFile.h | 80 + src/TortoiseMerge/TempFiles.cpp | 46 - src/TortoiseMerge/TortoiseMerge.cpp | 426 +- src/TortoiseMerge/TortoiseMerge.h | 11 +- src/TortoiseMerge/TortoiseMerge.vcproj | 2543 ----------- src/TortoiseMerge/TortoiseMerge.vcxproj | 92 +- src/TortoiseMerge/TortoiseMerge.vcxproj.filters | 221 +- src/TortoiseMerge/TortoiseMergeLang.vcproj | 376 -- .../TortoiseMergeLang.vcxproj.filters | 4 - src/TortoiseMerge/Undo.cpp | 148 +- src/TortoiseMerge/Undo.h | 40 +- src/TortoiseMerge/ViewData.cpp | 12 +- src/TortoiseMerge/ViewData.h | 71 +- src/TortoiseMerge/WorkingFile.cpp | 60 +- src/TortoiseMerge/WorkingFile.h | 13 +- src/TortoiseMerge/XSplitter.cpp | 77 +- src/TortoiseMerge/XSplitter.h | 64 +- src/TortoiseMerge/libsvn_diff/SVNLineDiff.cpp | 29 +- src/TortoiseMerge/resource.h | Bin 12027 -> 31292 bytes src/TortoiseMerge/stdafx.h | 15 +- src/TortoiseMerge/svninclude/SVNLineDiff.h | 22 +- src/Utils/MiscUI/FileDlgEventHandler.cpp | 92 + src/Utils/MiscUI/FileDlgEventHandler.h | 55 + src/Utils/MiscUI/LineColors.h | 72 + src/Utils/MiscUI/TripleClick.h | 82 + src/Utils/SelectFileFilter.h | 86 + .../EditGotoDlg.h => Utils/accHelper.cpp} | 31 +- 128 files changed, 10384 insertions(+), 8579 deletions(-) create mode 100644 src/Git/GitPatch.cpp create mode 100644 src/Git/GitPatch.h create mode 100644 src/Resources/Margin.cur create mode 100644 src/Resources/moved.ico create mode 100644 src/Resources/ribbon.mfcribbon-ms create mode 100644 src/Resources/ribbon/About.png create mode 100644 src/Resources/ribbon/Check.png create mode 100644 src/Resources/ribbon/Collapse.png create mode 100644 src/Resources/ribbon/Copy.png create mode 100644 src/Resources/ribbon/DownGreen.png create mode 100644 src/Resources/ribbon/DownGreenDots.png create mode 100644 src/Resources/ribbon/DownRed.png create mode 100644 src/Resources/ribbon/Exit.png create mode 100644 src/Resources/ribbon/FileList.png create mode 100644 src/Resources/ribbon/Goto.png create mode 100644 src/Resources/ribbon/Help.png create mode 100644 src/Resources/ribbon/InlineDiff.png create mode 100644 src/Resources/ribbon/InlineDiffWord.png create mode 100644 src/Resources/ribbon/LeftGreen.png create mode 100644 src/Resources/ribbon/LeftGreenDots.png create mode 100644 src/Resources/ribbon/LeftRed.png create mode 100644 src/Resources/ribbon/OneWayDiff.png create mode 100644 src/Resources/ribbon/Open.png create mode 100644 src/Resources/ribbon/OrangeDotDown.png create mode 100644 src/Resources/ribbon/OrangeDotLeft.png create mode 100644 src/Resources/ribbon/OrangeDotRight.png create mode 100644 src/Resources/ribbon/OrangeDotUp.png create mode 100644 src/Resources/ribbon/Paste.png create mode 100644 src/Resources/ribbon/Pilcrow.png create mode 100644 src/Resources/ribbon/Refresh.png create mode 100644 src/Resources/ribbon/RightGreen.png create mode 100644 src/Resources/ribbon/RightGreenDots.png create mode 100644 src/Resources/ribbon/RightRed.png create mode 100644 src/Resources/ribbon/Save.png create mode 100644 src/Resources/ribbon/SaveAs.png create mode 100644 src/Resources/ribbon/Search.png create mode 100644 src/Resources/ribbon/Settings.png create mode 100644 src/Resources/ribbon/Switch.png create mode 100644 src/Resources/ribbon/TortoiseMerge.png create mode 100644 src/Resources/ribbon/Undo.png create mode 100644 src/Resources/ribbon/UniDiff.png create mode 100644 src/Resources/ribbon/UpGreen.png create mode 100644 src/Resources/ribbon/UpGreenDots.png create mode 100644 src/Resources/ribbon/UpRed.png create mode 100644 src/Resources/ribbon/WhiteSpace1.png create mode 100644 src/Resources/ribbon/WhiteSpace2.png create mode 100644 src/Resources/ribbon/WhiteSpace3.png create mode 100644 src/Resources/ribbon/WrapLines.png create mode 100644 src/Resources/ribbon/createribbonimglist.bat create mode 100644 src/Resources/ribbon/ribbonimagelist.txt create mode 100644 src/Resources/ribbon/ribbonlarge.png create mode 100644 src/Resources/ribbon/ribbonsmall.png rewrite src/TortoiseMerge/BottomView.cpp (82%) delete mode 100644 src/TortoiseMerge/EditGotoDlg.cpp rewrite src/TortoiseMerge/FileTextLines.cpp (70%) create mode 100644 src/TortoiseMerge/GotoLineDlg.cpp copy src/TortoiseMerge/{EditGotoDlg.h => GotoLineDlg.h} (54%) rewrite src/TortoiseMerge/LeftView.cpp (89%) rewrite src/TortoiseMerge/LocatorBar.cpp (62%) create mode 100644 src/TortoiseMerge/MovedBlocks.cpp rename src/TortoiseMerge/{TempFiles.h => MovedBlocks.h} (65%) rewrite src/TortoiseMerge/RightView.cpp (88%) create mode 100644 src/TortoiseMerge/TempFile.cpp create mode 100644 src/TortoiseMerge/TempFile.h delete mode 100644 src/TortoiseMerge/TempFiles.cpp delete mode 100644 src/TortoiseMerge/TortoiseMerge.vcproj delete mode 100644 src/TortoiseMerge/TortoiseMergeLang.vcproj rewrite src/TortoiseMerge/resource.h (99%) create mode 100644 src/Utils/MiscUI/FileDlgEventHandler.cpp create mode 100644 src/Utils/MiscUI/FileDlgEventHandler.h create mode 100644 src/Utils/MiscUI/LineColors.h create mode 100644 src/Utils/MiscUI/TripleClick.h create mode 100644 src/Utils/SelectFileFilter.h rename src/{TortoiseMerge/EditGotoDlg.h => Utils/accHelper.cpp} (62%) diff --git a/src/Git/GitPatch.cpp b/src/Git/GitPatch.cpp new file mode 100644 index 000000000..9e42b8a7a --- /dev/null +++ b/src/Git/GitPatch.cpp @@ -0,0 +1,289 @@ +// TortoiseGitMerge - a Diff/Patch program + +// Copyright (C) 2012 - TortoiseGit +// Copyright (C) 2010-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#include "stdafx.h" +#include "resource.h" +#include "GitPatch.h" +#include "UnicodeUtils.h" +#include "SysProgressDlg.h" +#include "DirFileEnum.h" +#include "GitAdminDir.h" +#include "StringUtils.h" + +#define STRIP_LIMIT 10 + +GitPatch::GitPatch() + : m_nStrip(0) + , m_bSuccessfullyPatched(false) + , m_nRejected(0) + , m_pProgDlg(NULL) +{ +} + +GitPatch::~GitPatch() +{ +} + +int GitPatch::Init(const CString& patchfile, const CString& targetpath, CSysProgressDlg *pPprogDlg) +{ + CTGitPath target = CTGitPath(targetpath); + if (patchfile.IsEmpty() || targetpath.IsEmpty()) + { + m_errorStr.LoadString(IDS_ERR_PATCHPATHS); + return 0; + } + + m_errorStr.Empty(); + m_patchfile = patchfile; + m_targetpath = targetpath; + m_testPath.Empty(); + + m_patchfile.Replace('\\', '/'); + m_targetpath.Replace('\\', '/'); + + if (pPprogDlg) + { + pPprogDlg->SetTitle(IDS_APPNAME); + pPprogDlg->FormatNonPathLine(1, IDS_PATCH_PROGTITLE); + pPprogDlg->SetShowProgressBar(false); + pPprogDlg->ShowModeless(AfxGetMainWnd()); + m_pProgDlg = pPprogDlg; + } + + m_filePaths.clear(); + m_nRejected = 0; + m_nStrip = 0; + + // TODO: Read and try to apply patch + + m_pProgDlg = NULL; + + if ((m_nRejected > ((int)m_filePaths.size() / 3)) && !m_testPath.IsEmpty()) + { + m_nStrip++; + bool found = false; + for (m_nStrip = 0; m_nStrip < STRIP_LIMIT; ++m_nStrip) + { + for (std::vector::iterator it = m_filePaths.begin(); it != m_filePaths.end(); ++it) + { + if (Strip(it->path).IsEmpty()) + { + found = true; + m_nStrip--; + break; + } + } + if (found) + break; + } + } + + if (m_nStrip == STRIP_LIMIT) + m_filePaths.clear(); + else if (m_nStrip > 0) + { + m_filePaths.clear(); + m_nRejected = 0; + + // apply patch +#if 0 + if (err) + { + m_filePaths.clear(); + } +#endif + } + return (int)m_filePaths.size(); +} +bool GitPatch::PatchPath(const CString& path) +{ + m_errorStr.Empty(); + + m_patchfile.Replace('\\', '/'); + m_targetpath.Replace('\\', '/'); + + m_filetopatch = path.Mid(m_targetpath.GetLength()+1); + m_filetopatch.Replace('\\', '/'); + + m_nRejected = 0; + + m_errorStr = _T("NOT IMPLEMENTED"); + return false; +} + + +int GitPatch::GetPatchResult(const CString& sPath, CString& sSavePath, CString& sRejectPath) const +{ + for (std::vector::const_iterator it = m_filePaths.begin(); it != m_filePaths.end(); ++it) + { + if (Strip(it->path).CompareNoCase(sPath)==0) + { + sSavePath = it->resultPath; + if (it->rejects > 0) + sRejectPath = it->rejectsPath; + else + sRejectPath.Empty(); + return it->rejects; + } + } + return -1; +} + +CString GitPatch::CheckPatchPath(const CString& path) +{ + // first check if the path already matches + if (CountMatches(path) > (GetNumberOfFiles() / 3)) + return path; + + CSysProgressDlg progress; + CString tmp; + progress.SetTitle(IDS_PATCH_SEARCHPATHTITLE); + progress.SetShowProgressBar(false); + tmp.LoadString(IDS_PATCH_SEARCHPATHLINE1); + progress.SetLine(1, tmp); + progress.ShowModeless(AfxGetMainWnd()); + + // now go up the tree and try again + CString upperpath = path; + while (upperpath.ReverseFind('\\')>0) + { + upperpath = upperpath.Left(upperpath.ReverseFind('\\')); + progress.SetLine(2, upperpath, true); + if (progress.HasUserCancelled()) + return path; + if (CountMatches(upperpath) > (GetNumberOfFiles()/3)) + return upperpath; + } + // still no match found. So try sub folders + bool isDir = false; + CString subpath; + CDirFileEnum filefinder(path); + while (filefinder.NextFile(subpath, &isDir)) + { + if (progress.HasUserCancelled()) + return path; + if (!isDir) + continue; + if (g_GitAdminDir.IsAdminDirPath(subpath)) + continue; + progress.SetLine(2, subpath, true); + if (CountMatches(subpath) > (GetNumberOfFiles()/3)) + return subpath; + } + + // if a patch file only contains newly added files + // we can't really find the correct path. + // But: we can compare paths strings without the filenames + // and check if at least those match + upperpath = path; + while (upperpath.ReverseFind('\\')>0) + { + upperpath = upperpath.Left(upperpath.ReverseFind('\\')); + progress.SetLine(2, upperpath, true); + if (progress.HasUserCancelled()) + return path; + if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3)) + return upperpath; + } + + return path; +} + +int GitPatch::CountMatches(const CString& path) const +{ + int matches = 0; + for (int i=0; i 1) && (temp[0]=='\\') && (temp[1]!='\\')) ) + temp = path + _T("\\")+ temp; + if (PathFileExists(temp)) + matches++; + } + return matches; +} + +int GitPatch::CountDirMatches(const CString& path) const +{ + int matches = 0; + for (int i=0; i0 ) + { + // Remove windows drive letter "c:" + if (s.GetLength()>2 && s[1]==':') + { + s = s.Mid(2); + } + + for (int nStrip=1;nStrip<=m_nStrip;nStrip++) + { + // "/home/ts/my-working-copy/dir/file.txt" + // "home/ts/my-working-copy/dir/file.txt" + // "ts/my-working-copy/dir/file.txt" + // "my-working-copy/dir/file.txt" + // "dir/file.txt" + int p = s.FindOneOf(_T("/\\")); + if (p < 0) + { + s.Empty(); + break; + } + s = s.Mid(p+1); + } + } + return s; +} + +bool GitPatch::RemoveFile(const CString& /*path*/) +{ + // Delete file in Git + // not necessary now, because TGit doesn't support the "missing file" status + return true; +} diff --git a/src/Git/GitPatch.h b/src/Git/GitPatch.h new file mode 100644 index 000000000..c9fe5b1b9 --- /dev/null +++ b/src/Git/GitPatch.h @@ -0,0 +1,134 @@ +// TortoiseGitMerge - a Diff/Patch program + +// Copyright (C) 2012 - TortoiseGit +// Copyright (C) 2010-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#pragma once + +#include "TGitPath.h" +#include "TempFile.h" +#include "SysProgressDlg.h" + +class GitPatch +{ +public: + GitPatch(); + ~GitPatch(); + + /** + * Does a dry run of the patching, fills in all the arrays. + * Call this function first. + * The progress dialog is used to show progress info if the initialization takes a long time. + * \return the number of files affected by the patchfile, 0 in case of an error + */ + int Init(const CString& patchfile, const CString& targetpath, CSysProgressDlg *pPprogDlg); + + /** + * Sets the target path. Use this after getting a new path from CheckPatchPath() + */ + void SetTargetPath(const CString& targetpath) { m_targetpath = targetpath; m_targetpath.Replace('\\', '/'); } + CString GetTargetPath() { return m_targetpath; } + + /** + * Finds the best path to apply the patch file. Starting from the targetpath + * specified in Init() first upwards, then downwards. + * \remark this function shows a progress dialog and also shows a dialog asking + * the user to accept a possible better target path. + * \return the best path to apply the patch to. + */ + CString CheckPatchPath(const CString& path); + + /** + * Applies the patch for the given \c path, including property changes if necessary. + */ + bool PatchPath(const CString& path); + + /** + * Returns the paths of the patch result for the \c sPath. + * The patch is applied in the Init() method. + * \return the number of failed hunks, 0 if everything was applied successfully, -1 on error + */ + int GetPatchResult(const CString& sPath, CString& sSavePath, CString& sRejectPath) const; + + /** + * returns the number of files that are affected by the patchfile. + */ + int GetNumberOfFiles() const { return (int)m_filePaths.size(); } + + /** + * Returns the path of the affected file + */ + CString GetFilePath(int index) const { return m_filePaths[index].path; } + + /** + * Returns the number of failed hunks for the affected file + */ + int GetFailedHunks(int index) const { return m_filePaths[index].rejects; } + + /** + * Returns true if there are content modifications for the path + */ + bool GetContentMods(int index) const { return m_filePaths[index].content; } + + /** + * Returns true if there are property modifications for the path + */ + bool GetPropMods(int index) const { return m_filePaths[index].props; } + + /** + * Returns the path of the affected file, stripped by m_nStrip. + */ + CString GetStrippedPath(int nIndex) const; + + /** + * Returns a string containing the last error message. + */ + CString GetErrorMessage() const { return m_errorStr; } + + /** + * Removes the file from version control + */ + bool RemoveFile(const CString& path); + +private: + int CountMatches(const CString& path) const; + int CountDirMatches(const CString& path) const; + /** + * Strips the filename by removing m_nStrip prefixes. + */ + CString Strip(const CString& filename) const; + + typedef struct PathRejects + { + CString path; + int rejects; + CString resultPath; + CString rejectsPath; + bool content; + bool props; + }; + std::vector m_filePaths; + int m_nStrip; + bool m_bSuccessfullyPatched; + int m_nRejected; + CString m_patchfile; + CString m_targetpath; + CString m_testPath; + CString m_filetopatch; + CString m_errorStr; + CSysProgressDlg * m_pProgDlg; +}; diff --git a/src/Resources/Margin.cur b/src/Resources/Margin.cur new file mode 100644 index 0000000000000000000000000000000000000000..6d23e61586ba6fd56ba1c99e9b098d2f34185d9c GIT binary patch literal 13942 zcwX(APfrt35Wt@r4GAXdbq~lz<4yD+#tVJ{6XORk8t#4uzJXsrJsAIx7!nR(jDc8{ zm>_7=Y@mg*v=pqh2D&7LEMeJkrhP+}&9)Vj?Y=edM}J zgb<^bgt&~kzh8)6`un^P-|^TL{2fmb4=_#$d{Cp&Xfzs)Mx)VaG#br+hjHfAb&O$* zNsL>a&Q}F=y?X!NU3fJ75S~AM0w1QP0FS-v^gLBS*UZ@HC}gu)$mMddyu3{QSq!7o zb5#I6?_f-@yJcB0IX+H3Oa#yE>+QLT$IBSgVb_Wa^fAB8%rx8!g#xUtt??QR`py}| zs9X6wJ@&KR^|}qO0lTlSufzMdlZR`7`3AY?mzJPjuPf(I&>DQ3``U0Xm&;J8RLIi| zy6^7ps_IW`FvafGY8A#_zk;B<<2bSU)4kU&*nM+z6Kb^@NOzCj_xAQ;_YdBy{@0A( zeS3TR$p4+(_xJbX_NVue>ABh2hI^@0I+pvv!9lG41ox1hpFg&{=XtUF6WoKD$sEg_ zP{E(z9xR0k>Q8VF z<&BM&?kf8e+(V`OBa!|@ryf-IC))Lp1b?Dc4@vPS!g@%OKN0L9Y5oNF@Vojek^Tht zkW_y{_7IzY^A4Right", ID_VIEW_SWITCHLEFT - MENUITEM SEPARATOR - MENUITEM "S&ettings", ID_VIEW_OPTIONS - MENUITEM SEPARATOR - MENUITEM "Show File List", ID_VIEW_SHOWFILELIST - MENUITEM SEPARATOR - POPUP "&Application Look" - BEGIN - MENUITEM "Windows &2000", 32844 - MENUITEM "Office &XP", 32845 - MENUITEM "&Windows XP", 32846 - MENUITEM "Office 200&3", 32847 - MENUITEM "Visual Studio.NET 200&5", 32848 - POPUP "Office 200&7" - BEGIN - MENUITEM "&Blue Style", 32849 - MENUITEM "B&lack Style", 32850 - MENUITEM "&Silver Style", 32851 - MENUITEM "&Aqua Style", 32852 - END - END - END - POPUP "&Help" - BEGIN - MENUITEM "&Help Topics", ID_HELP - MENUITEM SEPARATOR - MENUITEM "&About TortoiseGitMerge...", ID_APP_ABOUT - END -END - - -///////////////////////////////////////////////////////////////////////////// -// // Accelerator // -IDR_MAINFRAME ACCELERATORS +IDR_MAINFRAME ACCELERATORS BEGIN "Q", ID_APP_EXIT, VIRTKEY, CONTROL, NOINVERT VK_ESCAPE, ID_APP_EXIT, VIRTKEY, NOINVERT + "W", ID_APP_EXIT, VIRTKEY, CONTROL, NOINVERT VK_DOWN, ID_CARET_DOWN, VIRTKEY, NOINVERT VK_DOWN, ID_CARET_DOWN, VIRTKEY, SHIFT, NOINVERT VK_LEFT, ID_CARET_LEFT, VIRTKEY, NOINVERT @@ -490,18 +364,26 @@ BEGIN VK_RIGHT, ID_CARET_WORDRIGHT, VIRTKEY, SHIFT, CONTROL, NOINVERT VK_F1, ID_CONTEXT_HELP, VIRTKEY, SHIFT, NOINVERT "C", ID_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT + VK_INSERT, ID_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT + VK_DELETE, ID_EDIT_CUT, VIRTKEY, SHIFT, NOINVERT "X", ID_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT "F", ID_EDIT_FIND, VIRTKEY, CONTROL, NOINVERT VK_F3, ID_EDIT_FINDNEXT, VIRTKEY, NOINVERT + VK_F3, ID_EDIT_FINDNEXTSTART, VIRTKEY, CONTROL, NOINVERT VK_F3, ID_EDIT_FINDPREV, VIRTKEY, SHIFT, NOINVERT - "G", ID_EDIT_GOTO, VIRTKEY, CONTROL + VK_F3, ID_EDIT_FINDPREVSTART, VIRTKEY, SHIFT, CONTROL, NOINVERT + "G", ID_EDIT_GOTOLINE, VIRTKEY, CONTROL, NOINVERT "V", ID_EDIT_PASTE, VIRTKEY, CONTROL, NOINVERT VK_INSERT, ID_EDIT_PASTE, VIRTKEY, SHIFT, NOINVERT + VK_INSERT, ID_EDIT_PASTE, VIRTKEY, SHIFT, NOINVERT + "A", ID_EDIT_SELECTALL, VIRTKEY, CONTROL, NOINVERT VK_BACK, ID_EDIT_UNDO, VIRTKEY, ALT, NOINVERT "Z", ID_EDIT_UNDO, VIRTKEY, CONTROL, NOINVERT + VK_F12, ID_EDIT_USELEFTBLOCK, VIRTKEY, NOINVERT VK_RIGHT, ID_EDIT_USELEFTBLOCK, VIRTKEY, ALT, NOINVERT VK_F10, ID_EDIT_USEMINETHENTHEIRBLOCK, VIRTKEY, SHIFT, CONTROL, NOINVERT VK_F10, ID_EDIT_USEMYBLOCK, VIRTKEY, CONTROL, NOINVERT + VK_F12, ID_EDIT_USEMYBLOCK, VIRTKEY, SHIFT, NOINVERT VK_F9, ID_EDIT_USETHEIRBLOCK, VIRTKEY, CONTROL, NOINVERT VK_F9, ID_EDIT_USETHEIRTHENMYBLOCK, VIRTKEY, SHIFT, CONTROL, NOINVERT "O", ID_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT @@ -512,21 +394,43 @@ BEGIN VK_F1, ID_HELP, VIRTKEY, NOINVERT VK_F8, ID_NAVIGATE_NEXTCONFLICT, VIRTKEY, NOINVERT VK_DOWN, ID_NAVIGATE_NEXTDIFFERENCE, VIRTKEY, CONTROL, NOINVERT + VK_DOWN, ID_NAVIGATE_NEXTDIFFERENCE, VIRTKEY, ALT, NOINVERT VK_F11, ID_NAVIGATE_NEXTDIFFERENCE, VIRTKEY, NOINVERT VK_F7, ID_NAVIGATE_NEXTDIFFERENCE, VIRTKEY, NOINVERT + VK_RIGHT, ID_NAVIGATE_NEXTINLINEDIFF, VIRTKEY, CONTROL, ALT, NOINVERT + VK_LEFT, ID_NAVIGATE_PREVINLINEDIFF, VIRTKEY, CONTROL, ALT, NOINVERT VK_F8, ID_NAVIGATE_PREVIOUSCONFLICT, VIRTKEY, SHIFT, NOINVERT VK_F11, ID_NAVIGATE_PREVIOUSDIFFERENCE, VIRTKEY, CONTROL, NOINVERT VK_F11, ID_NAVIGATE_PREVIOUSDIFFERENCE, VIRTKEY, SHIFT, NOINVERT VK_F7, ID_NAVIGATE_PREVIOUSDIFFERENCE, VIRTKEY, SHIFT, NOINVERT VK_UP, ID_NAVIGATE_PREVIOUSDIFFERENCE, VIRTKEY, CONTROL, NOINVERT + VK_UP, ID_NAVIGATE_PREVIOUSDIFFERENCE, VIRTKEY, ALT, NOINVERT VK_F6, ID_NEXT_PANE, VIRTKEY, NOINVERT VK_F6, ID_PREV_PANE, VIRTKEY, SHIFT, NOINVERT + "L", ID_VIEW_COLLAPSED, VIRTKEY, CONTROL, NOINVERT "D", ID_VIEW_ONEWAYDIFF, VIRTKEY, CONTROL, NOINVERT + "U", ID_VIEW_SWITCHLEFT, VIRTKEY, CONTROL, NOINVERT "T", ID_VIEW_WHITESPACES, VIRTKEY, CONTROL, NOINVERT - VK_INSERT, ID_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT - VK_DELETE, ID_EDIT_CUT, VIRTKEY, SHIFT, NOINVERT - VK_INSERT, ID_EDIT_PASTE, VIRTKEY, SHIFT, NOINVERT - "A", ID_EDIT_SELECTALL, VIRTKEY, CONTROL, NOINVERT + "P", ID_VIEW_WRAPLONGLINES, VIRTKEY, CONTROL, NOINVERT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Ribbon +// + +IDR_RIBBON RT_RIBBON_XML "ribbon.mfcribbon-ms" + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_MAINFRAME MENU +BEGIN + MENUITEM "&File", 0 END @@ -535,19 +439,20 @@ END // String Table // -STRINGTABLE +STRINGTABLE BEGIN IDR_MAINFRAME "TortoiseGitMerge" + IDS_TITLE_REJECTEDHUNKS "Rejected patch hunks for '%s'" END -STRINGTABLE +STRINGTABLE BEGIN AFX_IDS_APP_TITLE "TortoiseGitMerge" AFX_IDS_IDLEMESSAGE "For Help, press F1. Scroll horizontally with Ctrl-Scrollwheel" AFX_IDS_HELPMODEMESSAGE "Select an object on which to get Help" END -STRINGTABLE +STRINGTABLE BEGIN ID_INDICATOR_EXT "EXT" ID_INDICATOR_CAPS "CAP" @@ -557,7 +462,7 @@ BEGIN ID_INDICATOR_REC "REC" END -STRINGTABLE +STRINGTABLE BEGIN ID_APP_ABOUT "Display program information, version number and copyright\nAbout" ID_APP_EXIT "Quit the application; prompts to save documents\nExit" @@ -568,18 +473,18 @@ BEGIN ID_HELP "Display help for current task or command\nHelp" END -STRINGTABLE +STRINGTABLE BEGIN ID_NEXT_PANE "Switch to the next window pane\nNext Pane" ID_PREV_PANE "Switch back to the previous window pane\nPrevious Pane" END -STRINGTABLE +STRINGTABLE BEGIN ID_WINDOW_SPLIT "Split the active window into panes\nSplit" END -STRINGTABLE +STRINGTABLE BEGIN ID_EDIT_CLEAR "Erase the selection\nErase" ID_EDIT_CLEAR_ALL "Erase everything\nErase All" @@ -590,17 +495,17 @@ BEGIN ID_EDIT_REPEAT "Repeat the last action\nRepeat" ID_EDIT_REPLACE "Replace specific text with different text\nReplace" ID_EDIT_SELECT_ALL "Select the entire document\nSelect All" - ID_EDIT_UNDO "Undo the last action\nUndo" + ID_EDIT_UNDO "Undo the last modifications\nUndo" ID_EDIT_REDO "Redo the previously undone action\nRedo" END -STRINGTABLE +STRINGTABLE BEGIN ID_VIEW_TOOLBAR "Show or hide the toolbar\nToggle ToolBar" ID_VIEW_STATUS_BAR "Show or hide the status bar\nToggle StatusBar" END -STRINGTABLE +STRINGTABLE BEGIN AFX_IDS_SCSIZE "Change the window size" AFX_IDS_SCMOVE "Change the window position" @@ -611,20 +516,21 @@ BEGIN AFX_IDS_SCCLOSE "Close the active window and prompts to save the documents" END -STRINGTABLE +STRINGTABLE BEGIN AFX_IDS_SCRESTORE "Restore the window to normal size" AFX_IDS_SCTASKLIST "Activate Task List" END -STRINGTABLE +STRINGTABLE BEGIN IDS_SELECTFILE "Select File..." + IDS_SELECTDIFFFILE "Select patch file..." END -STRINGTABLE +STRINGTABLE BEGIN - ID_VIEW_WHITESPACES "Show special characters for whitespaces\nShow Whitespaces" + ID_VIEW_WHITESPACES "Show special characters for whitespaces and newlines\nShow Whitespaces" ID_VIEW_ONEWAYDIFF "Switch between single and double pane view\nSwitch between single and double pane view" ID_NAVIGATE_NEXTDIFFERENCE "Go to the next difference\nNext difference" ID_NAVIGATE_PREVIOUSDIFFERENCE @@ -632,7 +538,7 @@ BEGIN ID_VIEW_OPTIONS "Adjust the settings\nSettings" END -STRINGTABLE +STRINGTABLE BEGIN IDS_ERR_PATCH_NOINDEX "The line 'Index: ' was not found!\nEither this is not a diff file or the diff is empty." IDS_ERR_PATCH_NOEQUATIONCHARLINE @@ -650,32 +556,32 @@ BEGIN "The chunk size did not match the number of added/removed lines!" END -STRINGTABLE +STRINGTABLE BEGIN IDS_ERR_PATCH_INVALIDPATCHFILE "The file %s does not exist!" IDS_ERR_PATCH_FILENOTINPATCH "The file %s was not found in the patch file!" IDS_ERR_PATCH_DOESNOTMATCH - "The patch seems outdated! The file line\n%s\nand the patchline\n%s\ndo not match!" + "The patch seems outdated! The file line\n%1!s!\nand the patchline\n%2!s!\ndo not match!" IDS_ERR_PATCH_FILESAVE "Could not save the file %s!" END -STRINGTABLE +STRINGTABLE BEGIN IDS_ERR_MAINFRAME_FILEVERSIONNOTFOUND - "Could not retrieve revision %s of the file %s.\nPatching is not possible!" + "Could not retrieve revision %1!s! of the file %2!s!.\nPatching is not possible!" IDS_ERR_MAINFRAME_FILEHASCONFLICTS "There are still unresolved conflicts in line %d!\nYou should resolve those conflicts first before saving.\nDo you want to save the file with the conflicts still there?\nIf you click YES, then you have to manually resolve the conflicts in another editor!" END -STRINGTABLE +STRINGTABLE BEGIN ID_INDICATOR_LEFTVIEW " " ID_INDICATOR_RIGHTVIEW " " ID_INDICATOR_BOTTOMVIEW " " END -STRINGTABLE +STRINGTABLE BEGIN IDS_STATUSBAR_REMOVEDLINES "- %d" IDS_STATUSBAR_ADDEDLINES "+ %d" @@ -685,13 +591,13 @@ BEGIN IDS_STATUSBAR_CONFLICTS "Conflicts: %d" END -STRINGTABLE +STRINGTABLE BEGIN IDS_VIEWCONTEXTMENU_USETHISBLOCK "Use th&is text block" IDS_VIEWCONTEXTMENU_USETHEIRBLOCK "Use text block from '&theirs'" IDS_VIEWCONTEXTMENU_USEYOURBLOCK "Use text block from '&mine'" IDS_VIEWCONTEXTMENU_USETHEIRANDYOURBLOCK - "Use text block from 't&heirs' before 'mine" + "Use text block from 't&heirs' before 'mine'" IDS_VIEWCONTEXTMENU_USEYOURANDTHEIRBLOCK "Use text block from 'm&ine' before 'theirs'" IDS_VIEWCONTEXTMENU_USETHISFILE "Use this &whole file" @@ -702,70 +608,71 @@ BEGIN IDS_VIEWCONTEXTMENU_USEBOTHTHISLAST "Use both text blocks (this one last)" END -STRINGTABLE +STRINGTABLE BEGIN IDS_SAVEASTITLE "Save as..." IDS_COMMANDLINEHELP "Valid command line options are:\n/base:\n/theirs:\n/mine:\n/merged:\n/diff:\n/patchpath:" - IDS_OPENDIFFFILETITLE "Select diff file..." + IDS_OPENDIFFFILETITLE "Apply Patch..." IDS_GETVERSIONOFFILETITLE "Fetching file..." IDS_GETVERSIONOFFILE "Fetching revision %s of file:" - IDS_WARNMODIFIEDLOSECHANGES + IDS_WARNMODIFIEDLOOSECHANGES "There are unsaved modifications!\nDo you want to save your changes?" IDS_ASKFORSAVE "Do you want to save your changes?" - IDS_WARNMODIFIEDLOSECHANGESOPTIONS + IDS_WARNMODIFIEDLOOSECHANGESOPTIONS "Do you want to reload the documents to reflect the settings changes?\nNote: you will lose all changes you've made!" END -STRINGTABLE +STRINGTABLE BEGIN IDS_ERR_DIFF_DIFF "The diffing engine aborted because of an error:\n%s" IDS_ERR_DIFF_NEWLINES "Cannot show diff because of inconsistent newlines in the file." END -STRINGTABLE +STRINGTABLE BEGIN IDS_ERR_FILE_OPEN "Could not open the file\n%s" - IDS_ERR_FILE_BINARY "The file\n%s\nis not a valid text file!\nNote that UTF32 files are treated as binary too." + IDS_ERR_FILE_BINARY "The file\n%s\nis not a valid text file!" IDS_ERR_FILE_NOTAFILE "%s\nis a directory, not a file!\nTortoiseGitMerge can't diff directories." IDS_ERR_FILE_TOOBIG "The file is too big" END -STRINGTABLE +STRINGTABLE BEGIN - ID_FILE_OPEN "Open file" + ID_FILE_OPEN "Open files for diff or to apply a patch\nOpen files" ID_FILE_SAVE "Save the modified file\nSave file" ID_FILE_SAVE_AS "Save the modified file\nSave file" END -STRINGTABLE +STRINGTABLE BEGIN - IDS_ABOUTVERSION "TortoiseGitMerge %d.%d.%d, Build %d - %s, %s\r\nlibsvn_diff %d.%d.%d, %s\r\napr %d.%d.%d\r\napr-utils %d.%d.%d" - IDS_ABOUTVERSIONBOX "TortoiseGitMerge %d.%d.%d, Build %d - %s, %s" + IDS_ABOUTVERSION "TortoiseGitMerge %d.%d.%d.%d - %s, %s\r\nlibsvn_diff %d.%d.%d, %s\r\napr %d.%d.%d\r\napr-utils %d.%d.%d" + IDS_ABOUTVERSIONBOX "TortoiseGitMerge %d.%d.%d.%d - %s, %s" IDS_SETTINGSTITLE "Settings" END -STRINGTABLE +STRINGTABLE BEGIN IDS_COMMONFILEFILTER "All Files (*.*)|*.*||" IDS_PATCHFILEFILTER "Patchfiles (*.diff, *.patch)|*.diff;*.patch|All Files (*.*)|*.*||" END -STRINGTABLE +STRINGTABLE BEGIN - IDS_PATCH_ALL "Patch all" + IDS_PATCH_ALL "Patch all files" IDS_PATCH_TITLE "File patches" IDS_DIFF_TITLE "File diffs" - IDS_PATCH_SELECTED "Patch selected" + IDS_PATCH_SELECTED "Patch selected files" IDS_PATCH_PREVIEW "Preview patched file" - IDS_PATCH_REVIEW "Review Patch" + IDS_PATCH_ITEMTT "%s\n%ld failed hunk(s)" + IDS_PATCH_COPYFROMCLIPBOARD "Open from clipboard" END -STRINGTABLE +STRINGTABLE BEGIN ID_FILE_RELOAD "Reloads the opened files and reverts all changes.\nReload" END -STRINGTABLE +STRINGTABLE BEGIN ID_NAVIGATE_PREVIOUSCONFLICT "Go to the previous conflict\nPrevious conflict" @@ -774,7 +681,7 @@ BEGIN ID_VIEW_SWITCHLEFT "Switch the contents of the left and right view\nSwitch left and right view" END -STRINGTABLE +STRINGTABLE BEGIN ID_VIEW_SHOWFILELIST "Hide/Show the patch file list\nHides or shows the patch file list" ID_EDIT_USETHEIRBLOCK "Use text block from 'theirs'\nUse 'theirs' text block" @@ -788,45 +695,65 @@ BEGIN "Creates a patch file from the differences of the two files\nCreate patch file" END -STRINGTABLE +STRINGTABLE BEGIN IDS_WARNBETTERPATCHPATHFOUND - "The path\n%s\nseems not to match the paths in the patchfile.\nBut TortoiseGitMerge found the path\n%s\nmatches it better. Do you want to use the suggested path instead?" + "The path\n%1!s!\nseems not to match the paths in the patchfile.\nBut TortoiseGitMerge found the path\n%2!s!\nmatches it better. Do you want to use the suggested path instead?" IDS_NOTFOUNDVIEWTITLEINDICATOR "(not found)" IDS_WARNABSOLUTEPATHFOUND - "The path\n%s\nin the patchfile does not exist.\nTortoiseGitMerge found the relative path\n%s\nwhich seems to match the directory you're applying the patch.\n\nDo you want to use the suggested path? Answering 'no' will quit TortoiseGitMerge." + "The path\n%1!s!\nin the patchfile does not exist.\nTortoiseGitMerge found the relative path\n%2!s!\nwhich seems to match the directory you're applying the patch.\n\nDo you want to use the suggested path? Answering 'no' will quit TortoiseGitMerge." IDS_WARNABSOLUTEPATHNOTFOUND - "The path\n%s\nin the patchfile does not exist.\nTortoiseGitMerge tried to apply the patch by stripping prefixes but no matching path could be found." + "The path\n%s\nin the patchfile does not exist.\nTortoiseGitMerge tried to apply the patch by stripping prefixes but no matching path could be found." IDS_DELETEWHENEMPTY "The file \n%s\nis empty.\nDo you want to remove the file?" IDS_EMPTYLINETT "(no line number)" + IDS_WARNMODIFIEDOUTSIDELOOSECHANGES + "Some file(s) have been changed outside TortoiseGitMerge.\nWould you like to reload and lose your changes?" + IDS_WARNMODIFIEDOUTSIDE "Some file(s) have been changed outside TortoiseGitMerge.\nDo you want to load the changes?" END -STRINGTABLE +STRINGTABLE BEGIN IDS_VIEWTITLE_THEIRS "Theirs" IDS_VIEWTITLE_MERGED "Merged" IDS_VIEWTITLE_MINE "Mine" END -STRINGTABLE +STRINGTABLE BEGIN IDS_COLOURPICKER_CUSTOMTEXT "More colors..." IDS_COLOURPICKER_DEFAULTTEXT "Automatic" END -STRINGTABLE +STRINGTABLE BEGIN IDS_VIEWSCROLLTIPTEXT "Line: %*ld" END -STRINGTABLE +STRINGTABLE BEGIN IDS_EDIT_COPY "&Copy" IDS_EDIT_CUT "C&ut" IDS_EDIT_PASTE "&Paste" END -STRINGTABLE +STRINGTABLE +BEGIN + IDC_STYLEBUTTON "Change the style of the application\nChange Style" + ID_VIEW_APPLOOK_WIN7 "Windows 7" + ID_VIEW_APPLOOK_VS_2008 "Visual Studio 2008" + ID_VIEW_APPLOOK_WIN_2000 "Windows 2000" + ID_VIEW_APPLOOK_OFF_XP "Office XP" + ID_VIEW_APPLOOK_WIN_XP "Windows XP" + ID_VIEW_APPLOOK_OFF_2003 "Office 2003" + ID_VIEW_APPLOOK_VS_2005 "Visual Studio 2005" + ID_OFFICE2007 "Office 2007 colors" + ID_VIEW_APPLOOK_OFF_2007_BLUE "Blue Style" + ID_VIEW_APPLOOK_OFF_2007_BLACK "Black Style" + ID_VIEW_APPLOOK_OFF_2007_SILVER "Silver Style" + ID_VIEW_APPLOOK_OFF_2007_AQUA "Aqua Style" +END + +STRINGTABLE BEGIN ID_VIEW_LINEDIFFBAR "Show or hide the line diff bar\nToggle LineDiffBar" ID_VIEW_LOCATORBAR "Show or hide the locator bar\nToggle LocatorBar" @@ -838,7 +765,125 @@ BEGIN "Use block from right view before block from left view\nUse block from right before left" END -#endif // English (U.S.) resources +STRINGTABLE +BEGIN + IDS_PATCH_SEARCHPATHTITLE "Searching for better path to apply patch..." + IDS_PATCH_SEARCHPATHLINE1 "scanning path:" + IDS_PATCH_PROGTITLE "Patching" + IDS_PATCH_PATHINGFILE "Patching file '%1'" +END + +STRINGTABLE +BEGIN + ID_VIEW_COLLAPSED "Collapse unchanged sections\nCollapse" + ID_VIEW_COMPAREWHITESPACES + "Compares all whitespaces when diffing\nCompare whitespaces" + ID_VIEW_IGNOREWHITESPACECHANGES + "Ignores changes in whitespaces when diffing\nIgnore whitespace changes" + ID_VIEW_IGNOREALLWHITESPACECHANGES + "Ignores all whitespace changes when diffing\nIgnore all whitespace changes" + ID_NAVIGATE_NEXTINLINEDIFF + "Go to the previous inline difference\nPrevious inline difference" + ID_NAVIGATE_PREVINLINEDIFF + "Go to the next inline difference\nNext inline difference" +END + +STRINGTABLE +BEGIN + ID_VIEW_MOVEDBLOCKS "Detect and highlight moved blocks" + ID_VIEW_WRAPLONGLINES "Wrap long lines at the right border of the view\nWrap long lines" + ID_VIEW_INLINEDIFF "Show Inline-Diff\nInline diff" + ID_EDIT_GOTOLINE "Navigate to a specific line in the view\nGoto Line" +END + +STRINGTABLE +BEGIN + IDS_ERR_THREADSTARTFAILED "Could not start thread!" +END + +STRINGTABLE +BEGIN + IDS_APPNAME "TortoiseGitMerge" +END + +STRINGTABLE +BEGIN + IDS_ERR_DIFFVIEWSTART "Could not start diff viewer!\n%s" + IDS_ERR_PATCHPATHS "Both the path to the patch file and the target path must be absolute paths!" +END + +STRINGTABLE +BEGIN + IDS_UTILS_SELECTTEXTVIEWER "Select text editor application" +END + +STRINGTABLE +BEGIN + IDS_ERR_TEXTVIEWSTART "Could not start text viewer!\n%s" +END + +STRINGTABLE +BEGIN + IDS_PROPMODS "(properties only)" + IDS_PROPANDCONTENTMODS "(props and content)" + IDS_MOVED_FROM_TT "Line moved from line %ld" + IDS_MOVED_TO_TT "Line moved to line %ld" +END + +STRINGTABLE +BEGIN + IDS_GOTOLINE "&Line number (%d - %d)" + IDS_GOTO_OUTOFRANGE "The line number must be in between %d and %d" +END + +STRINGTABLE +BEGIN + ID_LOGOBUTTON "TortoiseGitMerge" +END + +STRINGTABLE +BEGIN + ID_APPLOOK "Changes the style of the application" +END + +STRINGTABLE +BEGIN + ID_USEBLOCKS "Click to see commands to move blocks of text\nUse text blocks" +END + +STRINGTABLE +BEGIN + IDS_MSGBOX_OK "&OK" + IDS_MSGBOX_CANCEL "&Cancel" + IDS_MSGBOX_IGNORE "Ignore" + IDS_MSGBOX_RETRY "Retry" + IDS_MSGBOX_ABORT "Abort" + IDS_MSGBOX_HELP "&Help" + IDS_MSGBOX_YES "Yes" +END + +STRINGTABLE +BEGIN + IDS_MSGBOX_NO "No" + IDS_MSGBOX_CONTINUE "Continue" + IDS_MSGBOX_DONOTASKAGAIN "Don't ask me again" + IDS_MSGBOX_DONOTTELLAGAIN "Don't tell me again" + IDS_MSGBOX_DONOTSHOWAGAIN "Don't show this message again" + IDS_MSGBOX_YESTOALL "Yes to all" + IDS_MSGBOX_NOTOALL "No to all" + IDS_MSGBOX_TRYAGAIN "Try again" + IDS_MSGBOX_REPORT "Report" + IDS_MSGBOX_IGNOREALL "Ignore all" + IDS_MSGBOX_SKIP "Skip" + IDS_MSGBOX_SKIPALL "Skip all" +END + +STRINGTABLE +BEGIN + IDS_MARKASRESOLVED "Do you want to mark the file\n%s\nas resolved?" +END + +#endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// @@ -846,9 +891,43 @@ END // German (Switzerland) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DES) -#ifdef _WIN32 LANGUAGE LANG_GERMAN, SUBLANG_GERMAN_SWISS -#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_GOTO DIALOGEX 0, 0, 175, 69 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Go To Line" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,59,48,50,14 + PUSHBUTTON "Cancel",IDCANCEL,118,48,50,14 + LTEXT "&Line number",IDC_LINELABEL,7,7,161,8 + EDITTEXT IDC_NUMBER,7,20,161,14,ES_AUTOHSCROLL | ES_NUMBER +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_GOTO, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 168 + TOPMARGIN, 7 + BOTTOMMARGIN, 62 + END +END +#endif // APSTUDIO_INVOKED + #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// diff --git a/src/Resources/moved.ico b/src/Resources/moved.ico new file mode 100644 index 0000000000000000000000000000000000000000..357d8bd3f1ccfae4c05c70dcfd60799a7a1626c1 GIT binary patch literal 894 zcwXI;Arpcy7=~ZYKS1(~MvX-3lDD|HO7YHUTrn&z5?A>NM(#{R{|3=$#CHSnghJJq z@8h=Tdl*LmNO+|wc;Dc-0@eU_Y$9yY^LGwI|6>YQEQE|OVt>l!oRFgllvWBSb)|(9 znay)2^0?qOLP=qil?~)v8&g};SlhU0=2<+N|q{`L3a0YtoT$ogaWnCD@v`r z)>R=&DZP?aEU&mF +
1
RibbonBarTRUETRUETRUETRUEFALSEIDB_RIBBONSMALL158Button_MainID_LOGOBUTTON32897TFALSEFALSE-1-1TRUEIDB_LOGO159IDB_LOGO159Category_MainTortoiseMergeIDB_RIBBONSMALL158IDB_RIBBONLARGE157ButtonID_VIEW_SHOWFILELIST32817Hide/Show the patch file listHFALSEFALSE3030TRUEFALSEButtonID_VIEW_OPTIONS32782SettingsSFALSEFALSE4242TRUEFALSEButton_Main_PanelID_APP_EXIT57665ExitXFALSEFALSE40-1TRUEFALSE300GroupButtonIDC_STYLEBUTTON10000StyleFALSEFALSE-1-1FALSEFALSEButtonID_VIEW_APPLOOK_WIN_200010003Windows 2000FALSEFALSE-1-1TRUEFALSEButtonID_VIEW_APPLOOK_OFF_XP10004Office XPFALSEFALSE-1-1TRUEFALSEButtonID_VIEW_APPLOOK_WIN_XP10005Windows XPFALSEFALSE-1-1TRUEFALSEButtonID_VIEW_APPLOOK_OFF_200310006Office 2003FALSEFALSE-1-1TRUEFALSEButtonID_VIEW_APPLOOK_VS_200510007Visual Studio 2005FALSEFALSE-1-1TRUEFALSEButtonID_VIEW_APPLOOK_VS_200810002Visual Studio 2008FALSEFALSE-1-1TRUEFALSEButtonID_VIEW_APPLOOK_WIN710001Windows 7FALSEFALSE-1-1TRUEFALSEButtonID_OFFICE200710008Office 2007FALSEFALSE-1-1TRUEFALSEButtonID_VIEW_APPLOOK_OFF_2007_BLUE10009Blue StyleFALSEFALSE-1-1TRUEFALSEButtonID_VIEW_APPLOOK_OFF_2007_BLACK10010Black StyleFALSEFALSE-1-1TRUEFALSEButtonID_VIEW_APPLOOK_OFF_2007_SILVER10011Silver StyleFALSEFALSE-1-1TRUEFALSEButtonID_VIEW_APPLOOK_OFF_2007_AQUA10012Aqua StyleFALSEFALSE-1-1TRUEFALSEButtonID_HELP57670HelpFALSEFALSE41-1TRUEFALSEButtonID_APP_ABOUT57664AboutFALSEFALSE39-1TRUEFALSECategoryMainMIDB_RIBBONSMALL158IDB_RIBBONSMALL158IDB_RIBBONLARGE157IDB_RIBBONLARGE157PanelFilesF-1FALSEFALSEButtonID_FILE_OPEN57601OpenOFALSEFALSE0-1TRUEFALSEButtonID_FILE_SAVE57603SaveSFALSEFALSE1-1TRUEFALSEButtonID_FILE_SAVE_AS57604Save asAFALSEFALSE43-1TRUEFALSEPanelEditE-1FALSEFALSEButtonID_FILE_RELOAD32794ReloadRFALSEFALSE22FALSEFALSEButtonID_EDIT_UNDO57643UndoZFALSEFALSE33TRUEFALSESeparatorFALSEButtonID_EDIT_COPY57634CopyCFALSEFALSE2727TRUEFALSEButtonID_EDIT_PASTE57637PastePFALSEFALSE2828TRUEFALSEButtonID_USEBLOCKS32914Use BlocksBFALSEFALSE1818FALSEFALSEButtonID_EDIT_USELEFTBLOCK32855Use left blockFALSEFALSE-1-1TRUEFALSEButtonID_EDIT_USELEFTFILE32856Use left fileFALSEFALSE-1-1TRUEFALSEButtonID_EDIT_USEBLOCKFROMLEFTBEFORERIGHT32857Use block from left before rightFALSEFALSE-1-1TRUEFALSEButtonID_EDIT_USEBLOCKFROMRIGHTBEFORELEFT32859Use block from right before leftFALSEFALSE-1-1TRUEFALSESeparatorTRUEButtonID_EDIT_USETHEIRBLOCK32819Use 'theirs' text blockFALSEFALSE-1-1TRUEFALSEButtonID_EDIT_USEMYBLOCK32820Use 'mine' text blockFALSEFALSE-1-1TRUEFALSEButtonID_EDIT_USETHEIRTHENMYBLOCK32821Use 'theirs' text block then 'mine'FALSEFALSE-1-1TRUEFALSEButtonID_EDIT_USEMINETHENTHEIRBLOCK32822Use 'mine' text block then 'theirs'FALSEFALSE-1-1TRUEFALSESeparatorFALSEButtonID_EDIT_FIND57636FindFFALSEFALSE2424TRUEFALSEButtonID_EDIT_GOTOLINE32893Goto LineGFALSEFALSE2525TRUEFALSEButtonID_EDIT_MARKASRESOLVED32808Mark as resolvedFALSEFALSE2020TRUEFALSESeparatorFALSEButtonID_EDIT_CREATEUNIFIEDDIFFFILE32828Create patch filePFALSEFALSE2626TRUEFALSEPanelNavigateN-1TRUEFALSEButtonID_NAVIGATE_PREVIOUSDIFFERENCE32780Previous differencePDFALSEFALSE55TRUEFALSEButtonID_NAVIGATE_NEXTDIFFERENCE32779Next differenceNDFALSEFALSE77TRUEFALSEButtonID_NAVIGATE_PREVIOUSCONFLICT32802Previous conflictPCFALSEFALSE1313TRUEFALSEButtonID_NAVIGATE_NEXTCONFLICT32804Next conflictNCFALSEFALSE1515TRUEFALSEButtonID_NAVIGATE_NEXTINLINEDIFF32875Previous inline differencePIFALSEFALSE99TRUEFALSEButtonID_NAVIGATE_PREVINLINEDIFF32876Next inline differenceNIFALSEFALSE1111TRUEFALSEPanelViewV-1FALSEFALSEButtonID_VIEW_WHITESPACES32774Show WhitespacesWFALSEFALSE2323TRUEFALSEButtonID_VIEW_WRAPLONGLINES32881Wrap long linesLFALSEFALSE3838TRUEFALSEButtonID_VIEW_INLINEDIFF32889Inline diffFALSEFALSE3131TRUEFALSEButtonID_VIEW_INLINEDIFFWORD32825Inline diff word-wiseFALSEFALSE3232TRUEFALSESeparatorFALSEButtonID_VIEW_COMPAREWHITESPACES32871Compare whitespacesFALSEFALSE35-1TRUEFALSEButtonID_VIEW_IGNOREWHITESPACECHANGES32872Ignore whitespace changesFALSEFALSE36-1TRUEFALSEButtonID_VIEW_IGNOREALLWHITESPACECHANGES32873Ignore all whitespace changesFALSEFALSE37-1TRUEFALSESeparatorFALSEButtonID_VIEW_ONEWAYDIFF32775Switch between single and double pane viewFALSEFALSE3333TRUEFALSEButtonID_VIEW_SWITCHLEFT32811Switch left and right viewFALSEFALSE3434TRUEFALSEButtonID_VIEW_COLLAPSED32870CollapseFALSEFALSE2929TRUEFALSE
diff --git a/src/Resources/ribbon/About.png b/src/Resources/ribbon/About.png new file mode 100644 index 0000000000000000000000000000000000000000..65edc32ebc09ee5cb347297ca00429ce95ac67f2 GIT binary patch literal 1762 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 IM6N<$f@cOl^Z)<= literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/Collapse.png b/src/Resources/ribbon/Collapse.png new file mode 100644 index 0000000000000000000000000000000000000000..6da570fe7fc99ece93fb23434ce0ac6248ebf22c GIT binary patch literal 720 zcwXxa@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sD*}8%Tp9csh6EDa(RdN)X7iFD zzhDMt9)3|d6=O3?8+*UNknpJ3gtVfH>c*D#uHFeV=PX*fdfl$Q2M(V+bLqzIdk>#H zfA#jm=Wjp%xUuY;$-uz4z|+Msq$2L_w3}5;1_G^%J9aEc6kMwHZnsXR;OBq&VQn?J zb{-3kot}5P{@hLlpR^{fnTj6+7TXk=TW**)aj^Fn9N3;WGGXY%a&raO-f?EPo#Y*wGs^gVJxxPkij&n=x!T3nkMrcHULF*Tr{ z)y7lPHJFn(LSe#`Wgk47zUgcb*XDk}&DgF}F#V_FXRY&54bhQP&og=pTLr|vO8>QD zN*hDFY27{97(R#PXDZ%rtkXEjZnfIsg^*gz*UP34N~hWxCMj)MCH!D=gJ-S+o6L2# z4FwM*6DIzP6FFP3=Rw8erLq@fx8Lh)d@doiYL#`t->a&XIo1qTx7~{(f4*DL{crP^ zdDh(L0(T|K@AX_;A7*9BR`py1oCm*=nK zjf&fCReZ{mhYxSt zw(ZA{AMNe!`}XZSefso^7cc((`}h6(_uad9U%h(u+O=!SdY&$hAr)~;FFf}xuC1@Jz3H9g z@X_*xvqz(?Hv8YXDoio6iVCjWoX|Ye^!xetXaSx%s~jS_8IE3`)V!R3jVk*u+3b^z zGuK@cV*b4~?lg1cG}ePVw?1QZ`f(_<^-N9>r=mul{yz1?FN01n81-L2{MWaL`Q_V$ z*?Syzy)HeZz`cbfnaP9U37g=I#Tg1a9@^_4xtkDRvcSB+V7Y0-H7WBGj9m%gIcXynJlMYKXCT6gl6=HR|;>x zU64O;)nQWEgiXrV^j9o>(-bOjV8084KFbEN^Xw7b*L`gpwq|4oHOgOnxaPRZz4oi1 O$o6#gb6Mw<&;$S$-5oms literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/DownGreen.png b/src/Resources/ribbon/DownGreen.png new file mode 100644 index 0000000000000000000000000000000000000000..c86e54333524b7eb28c33649e38571eae18121ef GIT binary patch literal 1679 zcwPaI25|X_P)zTV}=5)A|&9=}9_U7cAJG)JOzxy$F zb{vfDFHh0Tc_P{f#SrI)+E@kJC~lx=pCExU#cvenCQ4u$#XR)jYcxJY60X#Buf5ec zmxpow?d^RRN-0!TfPiukWlcFyT-UBECH<&YTC%yOH5N>rkL`!HGQflhNE|zV4Ar2D zksvU+O4)=mb?v&M;2fANbr9AfZv^0ujlmr+ybhZl9efR3^S9y%zmurtJErDdb^;8p zIGbkGFd?wg7jSXRRzTqGS*Rx!;cKyxxzX7PC?lXabIpB;Gzmm?a_MBcgdCm$r39QI z6R>q=h?4!8&Pu?AfGZwp&K$`-0(-wmAW>x}ptRiEqs+CIrDRE%utq=YP3%GrB3VXU zYYq`f+wkgA5$Nm%RD1}@X)0nOq69KAGl`~I!O2WOLzWFga|pQN(Uxl#w9G=rdq75C zM6}clEm@XVox4_c0{oz6hl)eTSyMKUT-&EOpfbOWCcuNFgdQE%$P0L~9v6{!IeVZ= z;3@@NWYSM*0awrv2~cDvU;vGPX83?I*DRBMN0QH3LiRvrGvELMXL7P%=Zuiiw@&TM z1Y{2cL45BQ5gR;6j=Z(@quBtmTtWm=O@PWFmK4(b+32tyH#o*2FveFU?G$-=777?b zmWTmQ^pnp>EMu>K%8)j#(N{2$z;ym6ul`}qvMzYjs+5CD;AJ)%{M{f9^Xf2YiYmq5 z6n}W!so+H%h6}&Z*B|x_m~8iLF*(#^7Gw{EZGXeBZ3ZHwB37V_yh|G%==Nu{UmN}K zj;b^5F2`a%{zSk$_07LRayQrqYU~tNkFS@MHOO>d!=# zF2a2gk!$JjEnW*I7uslKXVPfISj6HVJ7&+KrX;*#|1uuh9$9vB<^e~J-YZY zf5at;fl~xfXC@M$vMiFbpy9b5@@^h}eo_s**p9rDCs+qz%Q-m_2;n)F8hC}*kH3oH^HrN>HH~7I4&FEnciv3;@AJw?mUXCj>4Kn2Y9IvjGJR zYz%68PT0?Z@QsYCYoqQmyldcJ75MrG88k%&AK{CNTUqsid!V$Tv&X-E%B?t4?t}6a zf=i!c3ZY8;rokE;M;l0nmjJ**tYW_FQczY7md;)TCl?U>4uNmraatrkB#D!iRfoPk zaQ;GN5Oe*T0L=iLLBSC5$a2kSd@X8%D}5)f47{sk+4NfQjR}avA(A*j5+@Fx9~vG+ zM_WQdAWZE77HA5*Bhm>u<;3ugr?9IIe7!x8z9%-JtFW+GT7O%&-{iH>ayrYuab@Xf zwsu%w8f!Q*Yka4na1!_VduH7Y*VAU?|Fz)9Cm<4=N#d?zY2CbyezVt#GXrNs0fE;{ zE-BumTt_OY@XKa(B5;SX=j0m}{N)OW#FI6NwYO~aTXL;D6HF}Za5x~FRltpu4L^d5 z*z^6a^3ceTz+XWDk$5VUD3sRT+)WZbelGW!eW6WbTrQIEMuFie6vNN%#4y3XX~AE4 z0b8P@+i&%JT2B44_Y=O5ZS1g!A_;9I2tIw}CrH4S$dkl6BhiMwa({9OaRb*3ZL*B~ zkOiNCK)QtQO(uz7?3HieLBa6hA$46ke1-zH#QKg+ep`MD&eBY<0z-2)YrY0w!37&W zdjVVG@s1w9JwFxw;Y=t7X9>^o&^dD=f zW%yz6B;NVy?J^WB`0T@$Xd;P+^ZN?tHh;EFzjU})ul~4Oum5C=-h1?*e)h-<8v8JS z_ps0cxEdw4eEN*uad@}hfAoEV-)_NQ-4ajZp-`gttKIs!&u#esgG2#4ajya2kAEf# Z{y*GJNTZ&ss!0F<002ovPDHLkV1fap8Q%Z^ literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/DownGreenDots.png b/src/Resources/ribbon/DownGreenDots.png new file mode 100644 index 0000000000000000000000000000000000000000..07479693efccae3b9741fcb2a573b0ebbb7f4c27 GIT binary patch literal 1662 zcwPa127&pBP)Nkl{HfQ{(G{#?X%%V}pLF zY0?iZh$SJW)YMdo)4!JkDe9<(ZS6oOqKVV`l8R{m=Ze z?#hZ-J!22J!(dzNFyN!P7b4#&m`2Alh@6Bzrwc$35Mp*i4*udPa!u~(>*8VoC z3iodj8vKBxUGaRj(QJH}DBY)Oq=f_k!7@LIi?&u4@yjO^h?tk$_{EZ_D(_`^6se+H z$bBAA7NGR=fE2z4e(;skjD|yVJhvFNC1l0_z##mxdgBEVUAvt;DI0K4t$tEZ<8JdEv zoeGG6C%GAm1vNkl2}p{ofHQ$$(V8(pZa_6}9Z0H(!Bs-U)Q$m9Kmf2H22oR;c!s8+ z?llIjg@a5LD)BW!a}y;7Oom8i0@w3^L$ZhusMg3yJT!HVE0tFmi~6xT4HDscA0fph z?P5mY`|r*oe)NEB1Wp1g0jgOsAe*PzBNnE-G}01k<}8c!~$M?q=&}I@q?QgYEZqu(|z{j7I;$ zlZff$3d7T9Q!{^KJie4CEpc82vY1ut&iCET7q@?dhd%pAI`6JDKE|UD9iVwx8HHUg z`Ng@7?l~Y2w=8cya_^=N-kf@w*o@-4?bAiX;Y&bMu!?1M?O9ZmL_-oAVp8Ii$i>)3 zL?%S2XHD}xoPXmu*MoxPyjZH68{bf)tS$j}*yTi;`cWt$q{zZ}oC@XyEAsF8M`Y#Iq9*4qknFaBzUN z<&V-3G!b}Yl&bN;GD5}l5t<=HLOh8xgcKJ8&tql%dMY;)F7*HXC57+&U(W%B@1MW; zTKlr5j_s>j-_JFUkbUPRAwn~;tThD^{%Ap3{y!zf^`Oj>_^C@9(DeTlr~g_#D*y`f!9WTbT==a ze0EULkH-}TsnX%^>f)coafPv<%!5bI9e(ZMPw!som!=8LHAIBWz$}2P0SZ_qu(pKf zHD$fu7_`uke1zYedS*<~2NRe4yLOeB{fB;8J6sB7oeuQg7T{!DD&!4uC_KyX4; z-TkzJM?k&TLbH5=L)`}>tM<#LLQf$lv8iy@sM+y+_Yu<;+)rcBM#-zo7I$M%rm57D z#q>QTuY^dPJzrk`F;4XTme_{hG8K*${l(TxGx5~kGZ(w|ZTsLh#^P5IN${i&5f4?1 z)ri>6pw_3gZZl^tbkq0m8&3fHi@%LdBET=sUm957+Svcty?1_!DZT7=BM-%r*cnPT zrcpM~bNM>2_FXuvlRORxphX%GoHXHk$9qq0SXI8`^X<2Pm`JW83YG+8BT6ji(v^f` zJ;(owVNcZ@T8x1lAq0ip&!2wo*q(+TZCbys9m5z_V?^s2xaxD{^uCKE?356bCtmz@ zTHHLogv3OAeSg=!w{P5Rp|Ny(%598}tm4_OpN|mp&1?(=?r)?>15!0l9k4`V43Yly z$v^%unx0t8%)~mL{L|wRq5ec7CV?extVb>>vV{b41HO2xF-+K4pZoEPyE;V#iS<&t#JHr!U4m~rhJhQn_wMx=5IO@|3l&gD z3`_wyGfhAVko*}H*Z*;qfr8J0yb56+83%j-_bkrA0LmihKUp6*?|Q*h5&!@I07*qo IM6N<$f|~y)`v3p{ literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/DownRed.png b/src/Resources/ribbon/DownRed.png new file mode 100644 index 0000000000000000000000000000000000000000..2642800c0c52d3032843a8db9ed6849d2dec52d9 GIT binary patch literal 1335 zcwPZF1<3k|P)9o|`EUa5HrceIUG0{(yJ915dqIc`+z_W|g~W*icQ_y+A^r=_94oY^1%b2& zv=Wy@U8I(l6MNVbis&la*bZ&FPHg96IK+;}_V_bSJCYT9Jh7i=-uInJOOT-by1B_j z{qs&jl044W9(Fow0EPfYaleyK`jW;MUnl)aWE+$yp8oJX0NeraFP13)0RO1%*$HEq z&pC5~BaDM}Uv!VY`;L9O2H;f^mQ(_op}E0%5vxVr=i4ffI{i`!5WtyH<}Zu+{Sa`k zVA_^QVE&r0Sq~BOj>)1Wi+~;G5cp7VYYaag2$qf0(x(uA9+EEJ8Ry4)pOvPi5)i?J zrBl>cfJ@JWEww;c%8;1g#PgY^rAwiJ+;!!(eIB;N+U=@qKnUfX_36|(=lccEAiy>3 z1N=1)FPL>A7#k>-uq{-?{L6z%ds4F;I@`v4&z zgv@5#T5;@8=z3a=biai}@J!QScDtDLdVpd0fO%-2zyg2CrHg=7uxvcW`0Na$P6x~| z95?~I7g{KCRRYW~V7A*Bot?P=y>LMSu5(<&M)vyf&!GqNTV;H`8;d%+>fe69${CoyNX?$=mJa?U|R-N&)li9TeUbuG;+GK*` zW>ec&)o-6%UY-Cr1t8}DOb>MZ<#sN2wA$-6ZftEyc}arVZqIg2U3WgMgAfAh$$Od< zo*!=*;=?pi*xA7_pT}{l#lyb@paL*+mgMA_uAkZx)twzls#GwNWfxdr>V!lhEWN@G zN(hSg?_*dl?>h&3X zs}%?j0K9-(LRA~p-CapqTf;U9hxDG_{>fLlUQ8nxYBsbopmvr8$Bol0Q^n+86PfJ>sRD2X_dx4)30U>6neNz}M> z5`t4I>`$}WWF;_{xI^htNl_ZLy*;T^tzr@*5|lVQY}|Ez83HZ|NtS04l?tX|g-z-F zw@sWKcG&_xiFLasYza^L6#3sa8Nn|q;F2iv5~}KhmrWCY9UN#s_IjDYXCdH7+r;}9^#d;lzY2xpb6YbH(MW*ms2>>36niYxs#s*~5#Pj20?NPsWcV_O+&VI9mEiSMiSWOLD10YqfkyJG{8WY9PRMSW_G)+U%AEl9`#S}0Y z+&}dP8u?>NYXs5~02`%ZjFg})iO90BvMdYi!Y;78v-5R7`@ZMQ{g_>U`XtYsbMM@9 z-{<`}@3{g~&~FRS1jW6r=Onxv)WfC5fbi2?ZquJ%eRXTaG}Vh`Y%HF$f$$nYnV4R%00D^>^y0d7 zI*vkOL>!5zPI666cwzneKO7z$+@>Pnl}eoxLqo7;%|hV&95PKQ<1yip=ep>8{Bg{> z_g*jtjvPVh^l2EGOl*95dUPsC=mb40CMG(g6gZwI@>j3IYHMSUrWp>jVWt*BA{%GS zK!_%)n^LI+QH>rW7ExOhCNV!SfYSK5hy*Z7H*O%XZDwRJ89tAN(U>AH;rmKx2%d{_ z84l4VarN|!u8ZmQAPocF=qMO90-SsvLAlJ;uRRTo2LXyC%3Qb*GL?dR;R3QkC=nq9 zt#Jzl$Q3KlJZ~N*&zylvY^bm2UQr{$Fim)agOEu92GK8-6ybSs1ju-S1pVN-=dkFp z$JCYHD8m}R;W_8e<63t&yqh->V)jE1 z!MN`}fYNIei*U%eLGMVDFc}Nbke;WEgNz6CFf0p;)~=0>lls2Rn{}gN?}-s;tt6I@ zAIIqS?eIu^h#5~l30F$klsf;?0A#KXvOdF+sudu#07Y+X3~fu6AV=Ga!QKwO{Wgj{ zJ*p{zQ$=1N_JVhT+G6h6v&g^q9zs~Ht&o`%oNvE{(cA!;wIJ&CWDiYs$b**33L&)A=T4wnpu$UeA|jG7Qu8lo-@gGMP6 zMQAd8UwP)!$cA#Aod_|0;sgSUifC*Es;zkRop(?^c~b9x^4g#xlO#ZzCU-d{!lZ&_ zWEL$#p{-e;oD>ahJGl*+VG3w88XAzw%XJy$oM@=+3Pvoza9!Sh>b^^Rx=bfreKmyH#ft&g<#0!5KA1tEgt_Fk zq!dZImKhU%0GRi$KnQ>6Do~-djX8*vp`HV3Ye1_a1M2W00kl3#w7C^=e>b1ZG0L49StPU6h*}(Y~`=eE~6oVgdF?2O+Xqz>r!1 zM+zD;nF5bI_n9Ol|IIfT*s%j4+LkTD?bKGCIdkAdiz-7=HI%6b~MReeohZ zc7RU6>=RF*h0cvk;yb}U_HUH_(hZrb*EJ)<&p8qyr`7;b#MBBM2R&=n;Li8oM=P~d zhSlDVd7CyNAiDXy_JX#pKrJdCe}st*>jB?`FsPKm}1<&ehv9QZN~VEzXVD}2;RDgjR!PHq6vsdfR#@B78OYl8P`aj zltVjr;_N;5;L6J{Bfoz??4BO=W#bdM|Cj&y4~%d4Ek;-U1cjYDbjZ^gAc!R^_yJ_v zf-p=!N8t&^u?c=pA)4g^Lqq}xtoS06fxLfKiNS+4eiVCA>%0X6JHe1(( z9#xx9VbZX0u2k-c1RV1HqrY+NzB`sKx&6X_{|CuCqUyb06%!;Q0&p)-$F2zP@lPhL zj0AQC2z*5WUl|Vq53;TixdlJO?tgvJcam%-9nH)G*6dmQqwQT?^E*aHZm14t3ZZvM zqEKZtk14sjQ)ZP$GzZU3JsNd&D)a|F`_IseU!VE?2=I3R6@eHnIbij=_Lj8|&YypG zs?N&j!WDp+QWxQt^#ce$ocnsN=rfh?9-k{xVzN-I9J$c{_1`L$zuLgZ0B(I(sDGLSL2Z53hpgu_`ACa{9Kxl|H8V%?RFNVLts1f4>1``u~ zu=t8#o0w32lP1Jygh-H<7Fh|!cDr?VOH22AXFPM}?qznmbVeqI68me<+_`sp@0{QH zx;L3JUDx4Rh6K+v)(ET-SR)~Vc78Yb>7!(iWablozlQ~$A; zo12A+TjPLwet%Xq2|HBE$&rfHqX42~Up6<+M#2`k}gZGOLA`wc#P|2>1iChTtN z*t#nr2v~?fsZ@rQVI^QUb5+;D5LG?LH4xf2)!U&JUTiGZ=X0} zw?5(Axv!z8r-y6JDh-U?9D~D$`@ky(2hYJ?T%TweYJWVK#Pd8?(lXLVZNud38%z?=xs zM2a=dP?Sq$UMLh21c1?%T9bfotxbYpINWSgeZ`I>q?t<;zIGP>L=_cu{-c zz(FGqMC~l2=NOOskxQ32DV$}x%1%K*SXbu;e2) zsdu3kzQQuPw`U*eBg+zs46-D_?c0CA$&)9+D=u987747k6LtLUVNc~smKg+vCw z&qp300t$r@DUTpRHV_%fd{{Z8p&$wDvr@_Im&%YW=CLiI4l4Nkbu{isAeBtR#AJ+) z1b$T9XDUo2IyyT+HTpguwv}f-PD&<1+|mM_IL94`2%UtBNTP&NES8{9EJ6|GA?^qF z@BigYAU-<_&5>48*tBrP;yx6M@|*zP^N^CkBgk74d5=-0vxW>W6pjRVsE{=&&c|-t zfYYZxFx}21Y(d2I*|VQv71~R4teBXXfPsMl@Bj+qGiN@9UAuSl4NfP5jszrL4$-1S zB%8~^Bq~%3nQVp#+mX#?;rjLK;1!unhCIzr2kr+1!9_V#u>3J+3eVPOHD6dN~gfOIMas!<>mDkLsEz?h+|km&j- zbPT*kgl6M$Gl^9*4-70WF47CE1j6Ak96x@XI(P2e0WZjAGZfB#e7__!7X&0`CK7p~ zA~Gd#b90C`FHCTHUV-25hsU7_Yq&>{foL;x;{nE)P+X#_fFes&aRLnyR92<9bO|Kg zOs&_1&gToR2vDUF9zc2UU>?TDZdz-e85x1z-d?bQ;=Zr154^y>z9f@At_bivAQBYckx0aNBK80d@(S*r ze2!E#-rCvgs}cJnp>; z+1F>x`TGFlKfr{=#TZ%O>A{@`gcmp&&V_36=FOXHRs!~TVl>AC9M@M|Ot&Al{`CRv zzl368c@LW%zx@0Y+#0`WCQX$vYS~i^qf{zYIhan{2^h+Kq-omAbBWnH6j-g$bv^fR zDffl_3r*!KO>41Keh0@wt5x~r{L**H`Nbq3(ujZ+_|ZVcW+OZsNC^TpNHnhUub4~t kiD~Ko{=X7y1l9=r11VbLBX~F)<^TWy07*qoM6N<$f&nKWr2qf` literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/Goto.png b/src/Resources/ribbon/Goto.png new file mode 100644 index 0000000000000000000000000000000000000000..b4eba29cb721c78e1f41256671d64458fa6e5ffa GIT binary patch literal 744 zcwXxa@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0VAKro32_B-|NsBbfETc`vclPn zjEpQSEbQ#;0s;aME|AU1$tfr($jrwH za&~t1@bK{S^9u|N3=a>FiHYIo=hxEG($?12)zy`flG4}LmzI_`F)@*ql?A%N%F4>x z+S&d$!>-rm8%0q9PkBVAox-Q3*V-Q9(Sg}uGKeSLkEl$3y;3JMAe4Grbx z<&}_-h>eYni;GK6PEJox&&bFqEG+EL^+*M}Tec*~FPMQrK*7KvAfce4fByXS`_F^G zeGqsL1ht(_z+h(#^mK6ysff$Hc(v%Tfq>h?wR3KSg;=(VTna0jF>~XYGiToa7YNPH zwi157^KSn7x*0{y|C?;ITU_@v^Rws(PUaM5cW^z^ySU*)bIWbzqYN`W*HxGDuuLdW zTizwavf<3WQ_CK(|8n&Wja033P`c_H8pUqU(Ghfd^@#)g91@Fe{ac;I$Rs8q@$p*d z`#)>s)Hn<-Z4bI}_xW!o^@fI{Yr|Z-?p`((W?)|WuC(60E0dR@#Ov%yQ|ZMYe@B1* zKKp{GV$z{m48@Dz7dD?Pw|(?(=3EAqBfgbw6J|v}D2|@KN{7wEV%`_80-g_3)^@QA zCM??)TFE^#x_g>@NGAi+j=KR*H5C+8y0Y^6)~tMaAnEI3=i`NDX`D&2a0-nzQ3>+CfQO7d=%^u;edq5ty7ov?a?8ZBu7l?G?;5O literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/Help.png b/src/Resources/ribbon/Help.png new file mode 100644 index 0000000000000000000000000000000000000000..26223117445fc0365c96a16327875b4559a123b5 GIT binary patch literal 1461 zcwPau1xosfP)9b`domS6;~lz3s#7>V9+WxUueaEG@f60f+BC5CVb zpvGvT8ZJ>AdW{@?SwhrQodJZos^ z03&aI9sn>&j*+8e7y@L-o8&xs@z&{&b5G3xMSTFadR*oKpVv7R=yIie9w$P6H+T=V zS{-Y}G9Is5$QR1U70T(NRXJa(*vA3P(M-Ozf!5*!rlC*ALf*HAlievAfZIvYdL4DL zR)bs%YeE#PiWs{4U^Si1Tj%Ur{U}W=bYy_SI)(Q#%0pv=k!>^p0(FwYiU425gv}R< zixJxvb8_!-u0$i*D{IBd5gIv5BQ-QZ-$)$5&P2?A?4^f1;&*RP4 z29WCaL(_nIO`aE%(U3Rcc9{p)H8wIJ-Ofm-Q;EWXvKSdz{Mj|;f|*8`mT{yM(=<{D0XHH$CK&En*@bGUkANrW_p?Q*%$7Y}38U%EsXKf?CZz0GGo!kcb6RHbpg%8c0oAzs!4I|C*MFDpeb5L3wZCpB(1zWRU3b zp*IqczMu&V_5=a#9wY-b8c;?U;E+|W9suwg2_0&}ai~nOMyRW(CH)PwDmF^xs$5HL z8$QDYasT0}h!A4J;WQ5~ifdAW4WXhU!~_@9F+LPx`h8-Y zB6yTt!-eZh$mUBZRjTYEHZsjrRZ}yQqSuTS(N9tla^YmDyH zcfaGuOLtm}E6qj&k{Vs9%Ma*Yb-h)#k;xS)TBf+9k0aKPeQ%5)HP{cQ(+R6o!I_`` z#PKt;vLUq#WdIqgRE6$#h%m#K2Fe#oSY9bg9r=GKNOoQxj^LG15eI=wHx@Y?u5}`w zX*6(?(XpFNSQN{#c^I)0ZH7!WOid&Z?hb;0#gcq>>LTuNCUk(CjRxj9QW;qh4_68( zS8Ue1McND<$yfk}VSqq-F@y9{R+_j2%r_dim|eBf+^te*168|@xpW5El1xfm{^KrK zLUe#Ms7XR=*o;m9_z-PC*EM*YCNrRe4y06^hLW{5F~h^jN6nkjaWdV`A+j&x!3VpC zuzz=o9LD}#LvmDW6StKXnhng6=g=B5YxRnnWnBI91bHMj|+J%Go&?S%`2dWXl;-IsMG7G8c)I$ztaD0e0sx@+EHnTB literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/InlineDiffWord.png b/src/Resources/ribbon/InlineDiffWord.png new file mode 100644 index 0000000000000000000000000000000000000000..ff7e281d2bc072a7dae37ac13eb4973776fb08bd GIT binary patch literal 481 zcwXxa@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyivoN?T>t<7&p-q4Us*;@?0=&m zpE@fG4`-Gj?{rC_o9qk>!h9=OnAN#BVmaCTtvT4bh4~p6*d008V|lnl_&13O9GopG zI8RjYl(g7YS@GXW(h`Da7bpoT15K1E3GxeOVBr%ImsU_x(=)ZSbMy^Qsq31sXvKzo zM{d0MVYDD%8BpCNPZ!6Kinz0vZsr{_5O8~FFK}y_cZ65wY&mP4yWhY5jpu#SGv%21 z9Ywp7=8Kb0N=5!Y;5zAj#7y_B$+Y_wp~N4q(!AKuIQ3Fh@~1HrU)Sj_oKxP!-%ur7 w!!$)~%N1sejZ2G`$Zl#~c7N;49R6=dq@w<-=P`3@n}Pz?)78&qol`;+06uY(K>z>% literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/LeftGreen.png b/src/Resources/ribbon/LeftGreen.png new file mode 100644 index 0000000000000000000000000000000000000000..1f0d80134e1cd27f0f8c5751cc33f17de4e09c32 GIT binary patch literal 1924 zcwPa72YdL5P)3_D=j?X-b6OD50xhK=AwjTIKm|yu35n6f7=sD|$gdJLF)`8ji;9W28ZR`O zsPRItmK#lsQAmjL*HQop8UO`IprFP=x0IG{yEA?}GrOO2j)Wx`KVCvFfVreG*Au||pUqcbG1@Qq6lNINWmtN-3>!?Esr!i?(EIMS4Z++ORTNjo*7uKq*-BS~qMH2iI(V zJ(+JI^V8RasRWtNoWNjiuxMUtl41uEq`K<_Fhm*%uF3qWgFPy~=Qbyr4` z_dq47kyL*j05YW0LCNMfnAj|vx;jh+RrpPtcTIbW*BW3GzJmlDDLs}Zg)3j5VF7?Y zf|AYOnM^-%+v>0~$XEU(SDYXuLqOOlJ-_Hhc%FbKd_DTYqme|TuScQR{BuF_j65?J zf<}@k$hP&FP0#TjDBb*;u*xU%JtrY;c~qeTkY=>ncJ`;dNs?b2gZ$|Kdu`@+T?Fy1 zJO*|kW`(m#*~Dx!*f}UL-9wZn^5|Bj*SP-7)W6RDhzyW*?pxeKa`Q=mAhG8%Y$MYMOe+kdQ-58Wv4CF3b^VS~2kiZvs z9fMHHnJX=jWD#OqY8%J|DeJ;8F-mV(b6LYYX9VRQr9zTC1r(wr`JQj* z_5k2g_`-}zcqaEAM@R1g@F|p2@jbxiTa)RPlh=o}iP2Q~6Ha^0oN1ME-;*}$5`gh$ z4SSKfhTyrz5YWbbFArh%5d*M!8j#Dy_dpWQkoj53&8wQ93P&Zz;AHQq$X2y(O&eo9 z=^yUL!0>=U<2_(Ujud!gxxFRvjJwP0@s$(sW5+g*5Z=JtL_>1(>ZYf|(ZLuTrwt2X zE97iN1>q*S2oZ6ll7W2E5eQA-KP$jRez^#L8BXa|%K?N9Rw#{=zBr5MFM% zsU7>Qw| zg=nm4MElP>fxUbq14Ecj+&*eLx-OpO>Wf$gAR-hO1~bU!vc-U_Sauv3w&-*sTKPCc zQ*|Rc4(@URq7ZHM_okX^C!wpi03o0}<3m{q;X(|O`QB`wrDHsna5|wP2ud-ayLkc0 z5h8$z*mGz%(CGlU5WBl~E}ehN0)l9wPMqTyajqGHpAykcG2$y;c^)JCiVd5&1(Htn zq34sriRk>L-35q33_mceAr{=SFf}P`!WoKFp|T9&#wqD1ai*^;8Z9SIvBwb?gd&wVG<0uC_=>!G?SP8 zhxY&-4nWBeY|O;RnfDL%Ba^)tw_QA01XtcM%CTV>aQ-X4rY`o*0 zxU-C5RkB%pNHnh(l~V_)10v!>F$w>pp~* zzxX~;^Jz`b2YWwM_ymf-YB0he@G*J}0DswI#6b|ig&Xm8n|}aoYvOJXYv6nU0000< KMNUMnLSTY<7lPCP literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/LeftGreenDots.png b/src/Resources/ribbon/LeftGreenDots.png new file mode 100644 index 0000000000000000000000000000000000000000..d8d9db3f4ab6819f4b3b5f9ee9d3251ff99106cb GIT binary patch literal 1624 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 ICRx$=KilZPJ^%m! literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/OneWayDiff.png b/src/Resources/ribbon/OneWayDiff.png new file mode 100644 index 0000000000000000000000000000000000000000..f040d3eb2ba2b9e881171c3b4ea9636454695798 GIT binary patch literal 595 zcwXxa@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0VB`z%32_B-|NsBbfCET3Z4<9v z!;rU@As>j=F%+(2C|%D`x{eetaMf((t=+~~w~fDUt5Cx> zp@!`ujoZW;w~IAx7jN1j-n>J+`=Vsa4(Yy2GJTgU=iRhhaLZxgt-zHJLsmbWarVv3 zbMI!Ido%m|yX99uti1Md+np~v?tb0(;K#v-KQ3Rna^>>nD_5`HxOwx&t=l(l-MMw= z-tF6WZr{Ck@BY1e4<0-Of=7=ZKYjY-$>V3wUp#;I{N<}xuV1}<{pRi4H*em(d;j6x zyAK~fe){~g#+(2BvEdrG#MI^2rk}Q(q$RQ7 zZCJ0PM8e)Q0k;eHuKK;`Dm!}Vy8@5K>4H)Lwv739^GZ|>$TMve?5MYT*yHfgL_?lU zbUulzsnXxE%S`vt!yoC`bf bv-B^+-Q<*qFJHzPgVKPftDnm{r-UW|Y2Ppl literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/Open.png b/src/Resources/ribbon/Open.png new file mode 100644 index 0000000000000000000000000000000000000000..5c99c98edbd94372d09037e8241cb8c3413bab38 GIT binary patch literal 2645 zcwPZj3aa&qP)|vaY*HvHn z(d+Sg?dLboyx$y47-N{PdXGKAn9iUiRJdLKXmW106F`*fBt&&^;npoL@c#d)jCVH{ zsD|4&V`k($<|YS2RV(j^l-I68YId@fG9WoM(w6C$K6w&41ya$`PF-gizzpb&(FJfH zhwQ`urWOW}=m0|3-iWf=Cd`hW!@#Mx;P?0t^aT*|$dLRYkZ=QtF9f*cX7HYxnTqI| z0wp#LHJvtk&!H!w#i#qysdNS;qkaJ=AUPoKi>n0>p@y5P#D=Sq>v~Y?#q8Jx%#8KH z2L&NFgGT}gAt1rh(L$U!b*WUL2%6GV0qSd|#h=>&FpGvXjTF|e{s4*}jfFn<+d zlcTL&hu&)j(6A(j5LiwF^r#_8DmOsNs#joUv0kDmJK9~QpOC^%;+KsC&kRz(hq8{)&xpA0f5R3 znvX_2zjr<3JOSIV1yYcRu|Wm!W%KsyPAcaOcU*JbYo&6jv7!4Zn2YUj?~x*o696k5 zVC0EqC&0`u@^T1ZCsyj0JMX)3BZ6{JK$IAXw=Hsrg&6&kd4LH(MGBU#!v7>o84>~5 zz%PCEvyPb3kCwWKVDm7*#Uj#^q}j<3=bIHE1)`P#D+P)x0vy(c@wjpn+b28l#z+@{ z+?nx6Dk}Wt);5)m*1Cv8X#QD1c0v#<5WOsBrm=wezv~zA_0``+%Zi^G0Bj!Os**6Q z@*1hOT=k`dFqa3EYbkdIL<0dcMBx&U1R#0lU)OsPY21qC@_OvO@R*f&ez_&vB69-- zh&VWe3Wt|BTQqM5bS6@;WxdRD%wNJijoZ*9N6>y^4@P2rrhj{ge|~KJ4u!Z;>92xS zL^MYUn5pI^kr#`V00p5$>~TWiq9!;YAiN#Yhw%nC|GmcTSS8<%{hvRK;do!}9uwx9 ziR5ZqQ+p>)e)0KYuSRoLnZk6L^^!HjI&TtT8S`9XpvCVoe4>xw4cdqAH||1{cRijv zy%R&R-h!bUteSz_<^JG?>uw3HFS`eO-o3X70LpS0te}%z#53(z!E7;rnH_SDq@U{BQp&EBH9zM7WF(p>q9U03qJl_l>Zxtd0 zU?Kqn=rA|`wmgS>8y`g-8t{DALl{itnorJY7>fd^sKw@n?;w?M<9F}1;o{T<Hm%!?wN>B5`&}R5 zt*(D!JTZ-e(u~&W03a6x3W`~ea70g#eH(Hg@!s}v20-DaSa;^d|p4|uPxrC)EcR~W8 z87 z1PH)`rzBKx4#9+U#r5rQvhA3_IW@+*lAE^|J?Xp zGX=_JbfExb?BfuDqz7k5#_?s9OsnV}mKxszIke~6HsJsV(gd1l#L;-;PP=NOwzX2{kmygTC<>Dd>0uVx= zs(NQCsS0#z4>IgDtZho|hq{^ppjr3FLITDQ0a23hplf)dfPm^52+^|=@MkHIC3(RF zl$5ri=luAA@zJ@g;Wc5bH0;%b86aXg%{DG1wnY*$hxj&v2`|nMPvs?GF9LSircDSh zm(HauI0JXx_i_}#4-UNeqjoUdAFeFP8s6MX)3X=H{}C$n-BucuS9v{dV>uN^fjJi< zRW4!GPec;C#t))*WE$lqUULSr1F;~2T)L?kSCmV;>W`ROfARV@4v{f^e>hxX4Rm${ zPyh9!{QyvRZ78_q?#6Xh;lS#-cskE^8R7$d*$Pw{QJ}Xvj8$mIryBoAc>tE;v@%duc!!E zvtX8Fn*Uswx63m4yCiFm_YXxeHW}|$kAD$6etPHw zZax7Z&80biDp3gWDu+Nit+m%wm)dh60M2m5uJHa2039YwD3MenlToyI+)}IG=c#u| z5@uuRnFk&{@E3D$PjY?CHF|{;nR5swt+zMTE=O9?MgeqjMIHW~XokboPV;c9Khil6 z12AJII&6ZVcz{BPzDwQxZOOUVYvLajQTE}+<*fb-4+V6;5sim300000NkvXXu0mjf DCTZJT literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/OrangeDotDown.png b/src/Resources/ribbon/OrangeDotDown.png new file mode 100644 index 0000000000000000000000000000000000000000..8646d49ab67a956952c06df1a4d65fb237e92eba GIT binary patch literal 1864 zcwPZW2eu;veuI`fC8GRHz`}14R_1 zlw8_e$kmA*a&hC@PHd0I(aWEn2-c<#P-}G;oC#TZavDME>3bU33(_yL!fsD;UMnmDu)y{L z$%eptBVqAVz^5cR4^Tqz;A3P6Wu2-~grNF{(cp zCQ~VpIEA)|o)Bt)HMG&PJ0={}gfRdYGwRz7paAJ6Hoao%1+YG`ZxZ;JUJRFUw2F+Ym5s>Q9Zv5)Stm8<0GHbK_B zK!BYKph&n^*1vcKt2ci}#mf`9($�suuFbQvhKKP-Bm4-pTs}IUgvL0Pv0l$oLlG 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 QBme*a07*qoM6N<$g5OJp3jhEB literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/OrangeDotRight.png b/src/Resources/ribbon/OrangeDotRight.png new file mode 100644 index 0000000000000000000000000000000000000000..cf276f49b8975c22e4362cbc27a792d92d926027 GIT binary patch literal 1900 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 XUEu{|g$f=300000NkvXXu0mjf9Ak$X literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/Paste.png b/src/Resources/ribbon/Paste.png new file mode 100644 index 0000000000000000000000000000000000000000..8020f13fd8754870d98ecd6e05d6d2890f3a2c0b GIT binary patch literal 1236 zcwT+fdo+}37y$5ZOy7n!v?g2DYRIjkuvyWu8wN9SryaR9EE+6Z5sD(YRdnn~S(`eH zk;}{&M93{8POR_y#+=U7GGo{c!;DgyqudnZa-)n1Y>2!L_O`FO~ zN9!_FMcL11&RxEU$$aUJo(M3O#Tm9DkE?FgMI9 zFE6hk+N_|UpzxekVPWAN>Y<{dqT=G>yU9)^iB>e~(XwRQva+&@q+@?vv9Hc{LfbVI zx-ywev_;bmo0byK)?Aym0=xFY zV7TEd+=vQ+VHjx)K^j95CMpbJh9fLg1j0Ipuu&9*9f>qWAx%FcoM?m-gCGcx8OGzp z@Hnx&X4H9JGnLnZisQA!^IGHieE#d#uZ8_NqQToDkw`pRE*6VtW@ct*XXgcN^Yil( ziDcnT%fe*u!otGh;-YlonRKEmrcEp ziBSVGF;_N?8eE!wximAhG&8)syu7)&`SIgNxm>PLD7Ln?luG6H_V%YwpU{zzDA-!) z9~-0u_yhs~b_cj?x7MDoboC5O%`J|6f5Ne;85tiR7rZIAla!*vw9tJ94ozMa1YL2xUP-- z7)Q&1%`cdjl48wDbJ84=udiL3a+ovQAeziMO5U#At=4s}>@)=^n@KA9oE(^dvF|;~ z9iJV5cK$}T|9}w>T>V?a#WDC~;v8mHm6C@*TTVbnM@EwHpp-er8657rC|fZS89V@NY8{hdTK1=Kb6 z)9`+W^@w|O{{g>w`yjzPTgD9}@B2&R*4OmMN~aB{_~({*tPwd%;mL2`r{j#dYLtfI zb#x8hpU$^kS9IPnsnuIr^Nz)yt}-z79@#LBNY_aW7Ua+AES%}!ue5pmVgMRr6l!k$ zUk*MXD`WABsyypRu%B`6+fD!CcVl$6%NLCN+pbf&F^ftk`Ejwg;x-_rD@{1VbeHOXio)Zc)a*tyaiRM=kdAZv(Ioret zqrYg8LMrO40>iu37}|J1G&}N$emrH;p^p}E@&|nSeeAvFr`Iws*>c92?N6uJZrlTn zK&D~(gCiLgpt4*Lqtez5z71*4t1v5Pt?U}!yM9QC=+dMNt_ma}ajZ>Hvo{>0#2(6e U?2}`IC4F8qBsU)d+l7+zFT@*XCjbBd literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/Pilcrow.png b/src/Resources/ribbon/Pilcrow.png new file mode 100644 index 0000000000000000000000000000000000000000..d8f423cec67edce1b7ebc095f60e13128ecb3c43 GIT binary patch literal 263 zcwXxa@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sP60k4uK)l4XP^wUX?Sl2Qlce6 ze!&bZyrNomj^R-WDR~p-tl4?w(vufIWZqPX0Tsl1x;TbZ#JxRj$=Be(<9x9twP6GQ zkALAyJz1R&vCdN4zvXL-?y6s^k0YxXv(y{jJES)-F^csMKu5b literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/Refresh.png b/src/Resources/ribbon/Refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..7813d6ec5b788798f95bb9bbd6f0e0e36e909efc GIT binary patch literal 4684 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 O0000Nkl66QYON=Ha3hqxDW5g9u zWJ!GS$ruxqU_w~63dF=9M(jq5fbDczI?KJD-tXp~96oCsOwDx2zjv7_{r}E6clin! z<5wA}5+C3awqha1;HAeHpn`NAsRkPR8KTqyWCo^?@b;p2)04_4F+|A&u#zN9EuV&0 zZheCx#u0!b;av7SR3V9%7cfML14tQVaHR8lpqRi*3tnf4@dIE{R7HX~P7sv5L=Y1hqBH?| zbA8C>voO|hIq4z}bsmE$C~Ub(2;~o(3Ot1#hcg70OOa%U%6iQo&wywwNa3u{3kH9 zI~~K)8I5@U=FMp;r~Lz4ITOPJ7`SLBwdPu8;|wCpC^BQcd1w#M#^HKA-(ToQ-sVxJ zlK!VC2MsU;R0Pp9W}CZw=u5jv+yUMa71KABIRJC(uHk#Gh6Sp&+3< zdI_SsmLQg;zU|)k0M>&c1i&IRFI~7Ct=Tqc&5PG%K{G^5-UJXbAfm{&7{ZdKq$Z23 zM0{)>nqeG7RW0V$+>|(8EgCYe zAG2|>-mFD?PbU^#0U-cZG_FEhKLO~F0hVTvxM4TuQc%LU=v&{fE}w*?N(Pe_fDYLb z>n@zw073M2M43p}r;c1ae7^u#xp+0&2RcI8a0$iD>tLE;*lU0j5H|Ed0M#fP#!>*kZ*x_2Ds*V%pHbcn95`dDH0%0 za2-GGxrL_4--rvj^LXdtUh~xK?IBmcER->^r{7WQCK0j5vIs&janUUZ1W*PyQly{`PSu-;&#i_wgNooXkK% zBdL?H5I0GVz9A569fUCkMH>Rj_kR&>*|*-j4nPi`BR>pxUAoqp-}T$J$ovO50AP@0 zC4fpGnMjCxkFkaUf^ii}r|1w|pOeo(u5XofPOT;x5^u%w?2dnRHQ$@RgxzPhXYxNt z^CXB8TDxK`x^m~B+4w{jf;bcy|6DIMDpLd;Y=>D*WWV#SPg*-n>LdE>lg!q@y9GM_DE{>^+GVEw9f zIG^u|Enc~(R4n=eLKqzu#O>n~la(ACzV%{GdN%q;HXjGLd-YxD&UZtHCo(PXJL1@n znP#)r%CUq1{4P{Ma+wahye9K?=r8tR@37{j#3I&OqaocOA6P#tlMU-Pt!wj~*{ z#-jaoPIVmyhRmP-B$I0y$-D%}lX|cX%f{#*I4bZGz7n29rK;C&kHD6DW&tg~0cB=WSGS7>6%w-{- z-sacLgDn>D*|BYz!INbEGikmA0r=#bpPma<0Cpbn=6A=+)yxHQ7GE6yJTrKT z%eQ!l1^u@P$lq{IcP!~vG#Sy+PT z#l7RnJOec188Uy*crwqbCe@Q>jz{zS-)(^Mu`>TR++^Hd1ip9m00000NkvXXu0mjf D>s^gM literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/RightGreenDots.png b/src/Resources/ribbon/RightGreenDots.png new file mode 100644 index 0000000000000000000000000000000000000000..f280591e83898c969dfb64819c42fe0f6fd5a642 GIT binary patch literal 1621 zcwPZj2CDgqP)tE=Xsxbc~?}Gc9!89)G^R8&@s?4&@tfi5$W+hx#zdNdXgq>e_qABfbAQfXT?pce%0G2zj>@rytXqiQa#1z)_sNBZ|(U3@P{3J zvY@RD#IC}-l~dfZ`c}TQe%rTz*Ml-Qw~Ybccqny7>u2a*QfJHEk8Tj;b-&C8=9V<` zu^8|qpx_lTSSF&2P`bc(?%j2>h&k$&**sg)bD8pg^rE1E0~DN7vd<|dzs9Mi5d~h_ zsRkhVzA6pnF(I9~M5rYs>weKK>IuC7neBsN-e)pP|>pxUwuT~IpNmB&S z4E$zSpIQ$E18)z|EV*wZsg4{6$l0;=B}Qic4Qf#wLV{QniwF3ghjkSu!b=3Eh`}Ht z2@$ayrRJaD;mtb=M~4pW8G38@?MA97lv| z0u9JP2-8?FMMMmOX;eZYtWHtU6Fji~+jJL?J^AL);2O2;q=p52ppfAKAy5Y*z&0Du zcLzXeDgDP$#kK5hAKw&kDySqNfB{XA{NZIu1A-AjQcN0SCK-#4@r65j_+)X#_QR)- ztkfdgrA`ENli(Y(Fm~<=FacD6xR{_4RK`;V&feF>>}VpjUbTb39Y%v&Gla~)*FPbZ8bDfv^+ zkt}%^Ov$}^}3x0arp8j9IqN=|T%cMXVSPIyzjcPvN zW~;*7_i_XeUjm#+bK9VrRR9B7?<OND4#zlwk$gG=A|{_u_!tQiibxVw6+m0P2Xy+sFiA`4 zAOAjqV}ki)#I$_ti{xuyGJ2b_aFEcB5qNnv*%hj;0SN*g3wh_VmQ6QH2Wxinm5Z~xEV zC)8hh)=Y_EF)*Dm14KU8Nt!nT1}}}08FA@&yu?&Ej3vfMmNeQx6GVw!gsM+T)-w3k zRbD=^?>~g>k_d~iT)_iA0Ip^x!X^XPK9K-4YbgN~$5eEg(b^z2JBe>VwSh~~42O|2 z3xm5jIdqmIXO91sm`5cd0=lib87!B9F<>&QK-_FVR(~Ff9t^%ovt;Z20U*5EAI?^S zEaVDR#3r ze$B=$XX&EiO{5YKK(c5ArlS&zyVfD1jE855-3K@Yt6&rqL(yA7w|jv7hY#9XH1m{J zc$S8Qd~+P;n>j3?fL@oq(6fiWIPZ44v=n;o*vNa;_22D`SG|)zpE_{Gq1)^MQyB1|G@80D{QgTj5Jq#4m0|(7*K`4Tg0mJCPqev893j67q)1 T10n|a00000NkvXXu0mjffM@?Y literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/RightRed.png b/src/Resources/ribbon/RightRed.png new file mode 100644 index 0000000000000000000000000000000000000000..b755f31ab4ad1d3f29b811e697eec546042ea072 GIT binary patch literal 1598 zcwSAEeN@s{7{}4HIm!^wK=|<{3J7XS?-N^?hHtzmZ}Or68K^tC%-)>KnKdW&x|Z!& zomOp?m4da(TD9u*KFwD|K}1YMOEW_{T6yMv?T?+^^W5+AxzD}NIrrXk@1qy+IQ~!! z6astIR*p9hoYlKuvk}g z^d$hWi$pdmwUbEf6N`H(6ti6JjEZUlfac&}Q&yHMCZ=01w@Rf$ix!Q^WJ6U|*08Xi z(9i*ussoR=WM-N}LQG1fDLdPUM%QC7mas4ri8Rb)4#&idW@bJ^pV0E0fXtI(X13o8xGgcU^o{o>L!yr34{&;p`T7~$Kf0i z5oU#=S1NTd7%$Z7UMjUOJp4J0HmFt)L`1amc&=5e>af^B7OM^bUd!b}t5&@$s~Xnp zd)VwwK7Xoc=SY6OdHM3;ygbLcbqL^wM=C2V zGMS0bcWSkR>FHgOk*?faOIq4XiDYcU*EYG_RaMo= zu7rfnjEteUIMcFarZsCi($kx%)Tco~o$>MQ0>KM`pov0hXS3V+d=r;z7K;Z!AQBtU z=n*D!K%=$MXlAAIg-T`Lkk`Ln*CUr(Vq-^C`Sn<=GcL{rZa^lpFqyqcNfSr>*MRRV zVoL^32=Vg!A|JUN@NgEm=W;k$kK66*cDp?sk2~4}otR{B+-^mPoAY+94Kfwstx1I zA*Rk&mSfS!&+omk`ab+A>)VQB&{(yw=)7+H#*Wc5&;8V`6c?{`CaI#THii-(TCF`i z4+fj%6@=PyS7=QYPF9vjfh1uXw<_Yxjm9^}vIZA37mgpllQVvLNO+KU(g(7(1`3~S z5k|q>ZaPEA0Q?u14u9mVu;{{qs$sjKHTqMIy{shty+eM)TOEI1B<)iDh@iV@fz z`=Xq%v#z73D&1_g&tq;r=Rvn)X2$PJCiMHp7P9rcx370y85jCw5^I6F=hKy^9+KyN z7Z#W{<)f~fM!q+eA!?%HP9dQ_)6i?*oSOp)Y=CC-QrtO(u`(~!!Nq=qKZ^rz8yF4+ z+*>=OVa#iiEb}>{@nsxMA2{f(P4}jho~cemk-P{E<+!D18w|B6DZUx;+!;B;=N+9= z3&n*LRwECe*{msTdG&yY5Ry2TFWit!v!9g^A3CFMblgJi^yn_5SHG?;rUy)KmTP`V zY@T`dS`mNJcZ?Rey=e8J?Zjt>(50!%e=laEAptFq)3X^%Rz4E7A&8}d$(*Gwuiy5R z(dxlD+av^GvHX~A8#dHH=;t_$j_Tz$MoK10oiQFdzddaV+ zVTBNHt?}@4@^s)UdBI(x-IrV@vb8m4(z|PoiKUg-tHd`;u{~as1qJb5x#i3>n_@UlkB`Xr@3fu{|8tZ k3l_a8z<66V5rCb1f0ul<_L|1lAp2DGlM{IfhIm!^KQGy=Bme*a literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/Save.png b/src/Resources/ribbon/Save.png new file mode 100644 index 0000000000000000000000000000000000000000..0e777cd6eb85622131ab9281befad48f1e138b15 GIT binary patch literal 3223 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 L#8OdLSi0(8lU|h{ literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/TortoiseMerge.png b/src/Resources/ribbon/TortoiseMerge.png new file mode 100644 index 0000000000000000000000000000000000000000..ec1db1a86fcbf2ab6127a7f3e626a80324ce9f46 GIT binary patch literal 1868 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 literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/UniDiff.png b/src/Resources/ribbon/UniDiff.png new file mode 100644 index 0000000000000000000000000000000000000000..5622d02bf6b903419b2cc3630ae15949801493de GIT binary patch literal 1341 zcwPZL1;YA?P){^H>@94{R8g={8{s1sLc8U|J~5z*^ab zHM=liavO}@jEb;vZ$S!d$aI_jsIpR^eLUy8bYJ_mtYs$qk|%G^`QG=u&wJi}=Q(iz z;+`h>*`hD+Y1NhYBK>d-DD6B?Yvbeohi5BqKJW+7_Z!f^v@zs4-&}iuG*gF6A|XTj zA<9dNP+Ihb0RBz>#85wj=PLyvpQTcdY-RKH>?*P?T?<1|WJErU7jPteg z^#>97`lc5d#$0@Ss2({5R%9QvplZyg@*TOkf{MX^(J^wTxae_8IISZVa z5#z8OisSUoKDCbCa0K|fc2EFq=5A!=(_MdFhs^zEoSj$u92D%7YaM51eCQxz-xJ}^ z7%=dA;426waE1u9E+{~opL45yXJ`Frb^Fo%1a%;-z;Pw$D_{Z_C}=cjCv*v$KfrtY zY}YABK(-Es0 ze(+iI4O;uQ6zo}p!2q33hqAIVm`tVs%1TQFQPSzsg?GiKB9$0zmC~V&F@0s6wIVO)eXhNdo@Pm@~M-pY*$_XZZ~5ef%aVEV<2 z!k<0(^2cQqkuAa__zHgbM;hiiMu=%01vRVL*Y)l&b!_k?nr29kh8P2@>}RwbVD%PXmlP?kl*Dm;|gY5p|Q? zOL$cTgr2aUf~W4-4I`@-v1ja{x_@*ENXr!c;cP2Ov2}zbdD1T^qCR^sX}_p~MmljL zDsxcy)F7P4_I(|RxIaA0^Cx+MbO14 zD2$KNirVh8C^3n)IiQ_3Yri9nj0NDr+#|cyttRk{L?UGqc!lsjk+{d=#Ho!&%tHGC zMI{Utlf1kp0rUVe1Y)1G)58`SyVQ2jY-nFr~CW|3-#-$;}&S}00000NkvXXu0mjf?0kG+ literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/UpGreen.png b/src/Resources/ribbon/UpGreen.png new file mode 100644 index 0000000000000000000000000000000000000000..d1aa46f1a8ed4e9792ea0643ae934297b0ffc05c GIT binary patch literal 1659 zcwPZ}288*EP) zhz2fr5aJu(NP>tU3Tood5I7J~K*4ZrDR=F*vp(IO&M?fCTaI?)G*4z`7jmD^JkQK_ ziU<`fp5ykn zLV`CF{sn^XuEGCt0Oi1kU;+dA-{HA8U}4wO+4S7zOYmmEx24LCe*s$u zHWmEu%e~l$y#PuSL4pD{yo!)?0VqS7#G1yXu(AU8$`%L@dQ@Vr+3sO3g{z=@^whCvB1AV@UDUv z1TI%Th_FQ@V6X&C<1;XTguA?HxKt&HV`GFqrM}u$I@N(q|jyHg}Wn@l-%#bjyQLm(rP= z65c{J=Ye&_*e6gzro{2W35?H>#N5T%%q)_4?CKJ!M)0rotnq||r&W*O!_x*31_TVu zdNzitL#Pt4FUqI{m8j-r?fb$HfybpXNO1=Y9GtQ6UdD0p%Z1-Ced07ce*fa^N8-c& z(xbE|`bORpY-IR1>MO6llnDx1%LG3ixrLwyc(cg#RqaC~}rN1^oNa4)vo66{L&*L&6yyn}D*@Vpuc1X>_hF5qnOEC$Q@ z5ZnOc+<;RO+`6WqbuR;Y-0j^7r^>PG;3tLBPbATAO0dh}SKYhTW0e%ae@D<&DG-4d z{UVO%PvW1U^C(qI8rX){t8UJvh=#XC>`VZ_j1H3Uft_D%FAV=o5<9g7tG|rAec!v@ zjqXjfvgeF*K3C4+&*Ew1%NNz^e0K74qbzKAId6Q=_`maSou`1gem=+tw(ig6{>I0< zHy3aOz1S5=u<+LdpaU`6nz`$-^3Xc7Jamb`vEk)?D-&AamU(z*_gg>!9YT%mYXSwV zLpOkPDJ;Ql0B)w3Op&_UnM<j)qn^ubG=O*bC|Thwf?JM901Q$1jS!?lMINoG zl>Eo%DRPL>n0owQ846ATIw2gl=9Ua|LIDYXDkh6x3n@SmznLe60ZnrSsbL+iuQ_H( znHd0l3IA_gmK3g~2?$YzAP!Df8?H3kw9FrmYN1cw%peWNi(-0(ocn^^HHbZC)!Fd4RphEG~R z;+o*$P!*FLtI#sAvY>>3#9nBChi;z_$9ie!#@m+~b4Se`hk`lAm$?-60Fo0}y5;$p zKYyr*(UKBa4-dc{0QIeDISRjZ0{Ul+v<==?i{sTVyVvTALbaMaz002ovPDHLk FV1k-+2fF|O literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/UpGreenDots.png b/src/Resources/ribbon/UpGreenDots.png new file mode 100644 index 0000000000000000000000000000000000000000..ecbad5ec0f25c558b5cf7fe36c23957d73ef9927 GIT binary patch literal 1640 zcwPZ$2ABDXP)DfKD4uih ztB<{4L_EI!vriuiA43s@5JtWcr6iMNfMYzxJt$h zbHPO}&cDh7o5pGLI*y94FV|z#DYC~kh41egdYo&uGlT@FdepT_7}pUI(n3{Hlvt+; z%`5ER`J{9F)K5=HF&dBNIQxMo;1(G*Vt&4V=hOade1=+F0elQaVl{*s5yoLcI%<^8 zW7}3U{E8G&%P(Ik!-d{e+RenNp* z7nlz(5X3V;i~=ZW3$cje)8mg&of+WNnGoIae}6JrbL^w0g*21u@WZKEf89;^B@YCs=h;8&m3DeePoZow{L3;34CvO@V91cSgdf@tfIetjJsDn%EpBC9XoNIHXi@zH*v)MN%EWbFUv#xIeQ~s1=;}` z*?jkX+%q`F_1by+0DB3p6ztqFK8_R;d}>jJnc!WftLLZ$mss1eoj1>&=EhZ;)fM2u zA0F28xAY=QZg3Xpph&qK#~AJEBr4+1c2>*z=B8u9@r!tz=GgW zaC#|v{fOlMNr_k-z=K86DY%O$jx~Z90AdB9H4Io`vMAV&4LB*r3I|YH$pFS0ut+Le z6C@Ll8e%f>ntuWrf=PQm1sB9Y{&VLWr literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/UpRed.png b/src/Resources/ribbon/UpRed.png new file mode 100644 index 0000000000000000000000000000000000000000..79d19166ca02319b8af296d315beca9ed1890499 GIT binary patch literal 1609 zcwPZX2DbT$P)9v-M0 z8>k!{tRW!)004Cn5w9H`uOA<-A0MwEAOHXWa}yJ=9UXWP5vUv-t{EAr92@`u00000 zb`cS*ARw(FAzKd*mK7DM92~D182|tP|9FVyFfgte8Pp*m-BnfIR8-woR@Yiu+E-WD zSy|a7CE_hD*;iNIDJkJBEZkOB-zqB8A0Os0Fxw|5-zzKLDk|GoSJYiy(VrI`yPUAd0=Q1+dCMN4wSFRcw%NZHn zC@9uiTHGfm;43TSFE80yS>P-ztQ;KA930ywCe~Y9&Kn!zVPWE6VC-30*;rWDBO~K3 zF0dFF-(+ayi;LJ=TH#`5+$bp2R8-}7A$Qc}??E9Yrx=4fctPfyGzC(<7u=2KJ9BO}#3 zJlRuI_<;9p)J;v?N=nvMRp^L_-c?nt8ynk6N!C zQchC<0SFUnH#!{jM-B$^M@Jj-^7QodFh}z8^6~{o@<#LV@(LnH^zvIc@b4>r0Pj%% z00Q|*L_t(|UX76Be_UM{MQ75am`R(YO54=N*p6-6ww>CxZQHhO+kD?Yq0c#U@6G(C znKRj+*V<=4ht*=(Tn>lJX0FuhYee}AoFkmUL;(-Gxh6(hdr=i{y$k1U+U(LzKD9@xKDZ2^UaLG@YS`U z-Uct4he5%Ldg_7ls`#0&oxm{VYht~jJ`wZi0Fwv&tKxn0ru}m24hGG44aqU^q28Pq z>WvThZirfS$QcZVkKq@idbk(%P&^1Q$OrLx6Qd@r$Z#GE2G_yPcF3{+5)#tVPHfD~ zEYg|sFeuf(eCd|6=OQC_T)dRgr!SW^aL}-Rn|O3?>(msF4=@hmG5OYiILn1I;u({& zN9yQ*_;_9}L?94EKt{4m(xz?MVZbY>JwkMVKNd_NGhr*#2b=M}Bw-uWQ}+Uo2fcYq z5RSe#;%GnSckkJ&Qjvsxfw&&?KKlY^8}WK$4`=y zPM$h_Mk3K_Jt&?eT)*&6EEbDIT8Y+=;iV+us#ttY1np1{ybAMDUW8>~o(OQJzOF<* zMruWn;Pn(QNBv~smKNFp$8#vYfFLaX?tyS>4(x$cR+5B!G>+>rKYsy92&ZwXz5sZV zkQ)x$DV*WSLLJ@?I5h{do+ONxLuj1hJ$XSS0os{0D4w1Hyhh8y3n_zB^#K7d34-9& zSd{eibtqwBVNFdRQc^4moKb>5ycS|naN$IlB`By7#Z>qUY*(k*YKhI&00000NkvXX Hu0mjfTD8^v literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/WhiteSpace1.png b/src/Resources/ribbon/WhiteSpace1.png new file mode 100644 index 0000000000000000000000000000000000000000..9312945838f24192b39fb9275dab7b07363512c4 GIT binary patch literal 1563 zcwPY;2ITpPP)LMxh|8CVq6kYVQ&CHFYG&oK%4)To=2orcS8Oa@lU2E;rIv0> ztd*hFGL2k*C7FtXB9dH4Fsx`Ys8h-L}1sOW1N=q|g`ewn^IvXu_oipb;jP=6fmqL!=8 z*mi6Sj$Ye~!kTS3@<$QeJ~#THyQJZA9jz{Wn^ympL(Ps0w5zIsmVUp0em=E}JU$Qg z0Nr7lrctf^EUn$Ml0G@UlI+e#a(W%q>T02HEAuJmhlRAKd?$H*-aG#bR8_@!dlmMb z{}zK{On5Bqc@Syn^tM4#e8@`7MtX7v3hTZ^MNJt3ZdyQ56qMJOV1LDr5M^*$KBkPO(FOcc95#kFu(hH$MZCBvJ9%BFfwIIxQ=FlD;~; zfqXt6-HaYi)r}SO(XWdszkDSXHx*L3{UDueJ`kL;mV;D$aW}0$zKovOl1|Avk(9S- z6$S7B-a{{LkK3^GXW{=;uI5Vf>M< zFK4D$M9m=tejTzMtTZLYO{{;vF)G0!|>!b{eTEWfQZ?A^!cPCn^bFG?e@7ijU<=_GZ@q0*Sz33 z4pCvzCO*QK5t85yH-@t4>_v$mWF|eF5p7E36itLeA~cer!4FaCMy=x*uDH*^cDfr) zHtCYDUS6xVsXNF;P5=ZQLob$b#;?cDk62)h=b@<#_k>1c$lwu(G{-{V1u!tkJaTFT(De54S|G-zOqfq{df$#9A7X!A8d)Eqc=*n_%LVykpkUZr-bJ0QV>9{md# zh8y0GoE-Yj*enwlm&8NS0GG?)QsP{^+bi|{H=bjzKP zG!J+ZP<+@;f0SNtmnx-Pa?lO{@-1(}vhuP)IiKdtw3}+uADCn`SoIPhfN=n)4{u=l za1b~n_`m`qeKcsKK!1V=!FA3bj^d-;)O5yw{nZJt6|KlwQEM`pv_8R%y&K48r0aDr zprNq=YYNt&^x6U3KN+wl2*J%c7ASz=feaR60PC6HhJkVdm=uWfQqUjM1&a-Z3PsPU|@hD5FQ-XQy|zt;LI?{u{cvH zf!boR;@PE`GjmQb{8o7M6>OU}A`Q#uEyw1)JZ!c@h?pFPsC#8dS~oaG`rIhN5PX>U za0qe|s?YhMc0^$D>{l^s%A??U9=d@Q=wD*g$Wd4_cR9W|Ai#F!5V9skAu7fUkvajW z!2*l{p+XcQC>^#s4}4ccurPZOrcay+{hwn%=x;POHd0=GE-Ew=^kjnuz;3tG+JZba1%j@$UZKx+uAw&zX3&<(`Lz3DF1>&7IeKgJ zi*&N0gk)K!+W}p_+wj(`u~s~kF)Q3&d?B;?e9bs>gx%u0Ak@zuxu|sP=<%(BAP{cf zd$>(pOuCRT`UT69%=wm&6DJzSheR+Q|1d5*&JYob@0{-p(_ZKtnC~ICI!gBxs N002ovPDHLkV1j$V02KfL literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/WhiteSpace2.png b/src/Resources/ribbon/WhiteSpace2.png new file mode 100644 index 0000000000000000000000000000000000000000..da3d9d998474ceec7d884a1ce34a52cb4f6a4e6d GIT binary patch literal 690 zcwXxa@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy4+DHcT!A!${C^PA`p=;DpF#CM zgUUY!bs+m6gX%vh7syuo4+KE&`;S2K-{o5%^4NKhvUR&aWdD4Svf_^a*$p6#$yFe3 zXf9Y6Nc%t2V34@J?>|ish{+&9LF0cs`XC02Ce#Xtn1Am-{Qv&_e@5NE^}GIiB>jsh z_?J-rPr&$JLCe4P8UHzS{vAK}@BZWe5|;lI9R3;j{hPiNWc&7m{{k}qoxl43`knt8 z?*Hbm`M-SgKa0?R8`$2o1O2F1666=mz#$-^VPN6l5s{ElP|?uP(%#cQVfu{uOO|ig zvVF(?0|$yW#^NBD(^U~xxPQ~+S|<=zg#~wqbV_b?aaF1OZ_eWuf&Z- z55Eq)7Rxzn-*US=9m6@lb@sFuHQi*n94^MT<7-Hl;4vvy#d(F+;+lRcMK#O`k7`aj z&WhG~ajx4UdqQKgD{H#s^4Y2p5f&Ew+c-sa{0gTPgl%q7*2!TgY}>4BRB>dHj>FFq z<>0AJpKW5VuA?Jr2i#8_TxK}!&v6xnSv+r# zp5xbuteE&f_k_eg*Hire=hbKm9h#o){cK@fX=Hv$#rq4~%?$#J)r3txZ*V-9mHVZS s(Ua%3T~_ou&Y3f&y^Xt3##r`H{#!sv=0?k9)u6=Z>FVdQ&MBb@0PIXm6aWAK literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/WhiteSpace3.png b/src/Resources/ribbon/WhiteSpace3.png new file mode 100644 index 0000000000000000000000000000000000000000..36278bb615cc08d15d0d4a3ea174fa8ed43d7cf0 GIT binary patch literal 1390 zcwPZ+1(EuRP)KmOR<^V)9pQ;4q9`eVh~WU#sZwaoX?U1c{*$JtnjPkx{3_S|mI^L{?>yXV~< z1{i=4vc=nII`Gxu%Na8oAI*w2Prs)M#0&RI_mYdLp``uzZgOGXSkhcxKzz|sQ6#UW zd*r=MhxQO}sNjKnrFm#B%fooXS_B3SM%a>hXe!x_=JIdhiI$3D+}*beVTLRO1_wdE zI0-G~`Di+1f+ySt+@s7CkS&6toDhvp;sxO33-{{p&hUd7$-OqjT49Yj$;=XD!+yB&^)PV_!#L-_mz zG!_}rbhHRA=w~z*=OBD_7NAn0>-u^0b#=jM?}oGeJ|?Ec5qrY*zy6-kIlPlE;$oDzW~NpRs1?2rTzlT+ex;Tt2sFo-l{L1aMX zR*23ga)x23T!$WeAFiFOL_}_d?s!4!&&Gz??Lc{DT)B*(fbHD+{iPA{!wCD5Vc?~db=OE zBKiqbDiv7WAURG@$OE8dHK_g3j91<^9D6NmLj#n|WZ;F;upx_BwfgY%v_wUO5~aL` z%0)p)U)pfL)Z=>i;6P}I95Ev$5j~D}SA=&tN98|NQMs6&aidKkR|NQ?cxWFSvt|7(mN_;WXfT zoxz|yb->=yL7ANYAk)lMnUFXYy6q0|yaaa$0uP4iLnM7t)?aH(dq)!(Zg=E*4Updg z512QtfBn{pvNva~Tm>FLYfUA1dl$SR@OC;xM9zvrZKc&zxncR-m`}f`b8}wgUqpsb z;D-7GMX(vOF*`F8gk#ZaGo$ZeCtS#{Aj^Uxp9w-pzr(20=@1w)3^nFjM6Uk?;mcS4 zH$ZlCaKn}TMX1@CjfBiq5E&I(sw>gg)nBgFXdy|m3>rT*91cgn2!VId(Tb_jQ&E$% z9TEX1y|dEy8MtG$z`SEK5;Na}#IR_tI;)UtsGL=4V6D9<2gJ;YfyhgW2s@1sX++fY zNLY;9;6LO!jGLF@J>b&5LPRfG1dd8>pFM-#&IkSFS}JEXIA^(ll)OSnqKH!;7%(gL z4Typ=!XY}L<~k5PeHt#66#5RBv}^^+GL|52PAt&V14$zC1s~rzr^UJII;7?oA~a<_ zC{zfXTx&o~Y&1miafJMI)M}3I@q?wxhJ-J3p@6u-R*+A&j~Y&_OH;_1_(*ahI-LBH zFpZd#JD-7k^lez07*qoM6N<$f;sq*4FCWD literal 0 HcwPel00001 diff --git a/src/Resources/ribbon/WrapLines.png b/src/Resources/ribbon/WrapLines.png new file mode 100644 index 0000000000000000000000000000000000000000..f15b8cb45065589c3b12680f2da69b52b0ab010f GIT binary patch literal 305 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 FV1iT(j>!N3 literal 0 HcwPel00001 diff --git a/src/TortoiseGitBlame/TortoiseGitBlameView.cpp b/src/TortoiseGitBlame/TortoiseGitBlameView.cpp index 4fe873f5f..1310aa76f 100644 --- a/src/TortoiseGitBlame/TortoiseGitBlameView.cpp +++ b/src/TortoiseGitBlame/TortoiseGitBlameView.cpp @@ -1336,14 +1336,13 @@ int CTortoiseGitBlameView::GetEncode(unsigned char *buff, int size, int *bomoffs if(type == CFileTextLines::UTF8) return CP_UTF8; - if(type == CFileTextLines::UNICODE_LE) + if(type == CFileTextLines::UTF16_LE) { *bomoffset = 2; return 1200; } - // check for UNICODE_BE does not work, because git blame produces two NUL chars in a row - if(type == CFileTextLines::BINARY && size > 2 && buff[0] == 0xFE && buff[1] == 0xFF) + if(type == CFileTextLines::UTF16_BE) { *bomoffset = 2; return 1201; diff --git a/src/TortoiseMerge/AboutDlg.cpp b/src/TortoiseMerge/AboutDlg.cpp index 96a8bb413..60abd4f2e 100644 --- a/src/TortoiseMerge/AboutDlg.cpp +++ b/src/TortoiseMerge/AboutDlg.cpp @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2006-2007, 2009-2010 - TortoiseSVN @@ -77,7 +77,7 @@ BOOL CAboutDlg::OnInitDialog() SetTimer(ID_EFFECTTIMER, 40, NULL); SetTimer(ID_DROPTIMER, 300, NULL); - m_cWebLink.SetURL(_T("http://tortoisesvn.net")); + m_cWebLink.SetURL(_T("http://code.google.com/p/tortoisegit/")); m_cSupportLink.SetURL(_T("http://tortoisesvn.tigris.org/contributors.html")); return TRUE; // return TRUE unless you set the focus to a control diff --git a/src/TortoiseMerge/AboutDlg.h b/src/TortoiseMerge/AboutDlg.h index fc38a99e6..db6a7bd2c 100644 --- a/src/TortoiseMerge/AboutDlg.h +++ b/src/TortoiseMerge/AboutDlg.h @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2006,2009-2010 - TortoiseSVN diff --git a/src/TortoiseMerge/AppUtils.cpp b/src/TortoiseMerge/AppUtils.cpp index 50c6c69af..be8ad9500 100644 --- a/src/TortoiseMerge/AppUtils.cpp +++ b/src/TortoiseMerge/AppUtils.cpp @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2010-2011 - TortoiseGit // Copyright (C) 2006-2010 - TortoiseSVN @@ -49,7 +49,7 @@ BOOL CAppUtils::GetVersionedFile(CString sPath, CString sVersion, CString sSaveP CString sSCMPath = CRegString(_T("Software\\TortoiseGitMerge\\SCMPath"), _T("")); if (sSCMPath.IsEmpty()) { - // no path set, so use TortoiseSVN as default + // no path set, so use TortoiseGit as default sSCMPath = CPathUtils::GetAppDirectory() + _T("TortoiseGitProc.exe"); sSCMPath += _T(" /command:cat /path:\"%1\" /revision:%2 /savepath:\"%3\" /hwnd:%4"); } @@ -114,3 +114,23 @@ bool CAppUtils::HasClipboardFormat(UINT format) } return false; } + +COLORREF CAppUtils::IntenseColor(long scale, COLORREF col) +{ + // if the color is already dark (gray scale below 127), + // then lighten the color by 'scale', otherwise darken it + int Gray = (((int)GetRValue(col)) + GetGValue(col) + GetBValue(col))/3; + if (Gray > 127) + { + long red = MulDiv(GetRValue(col),(255-scale),255); + long green = MulDiv(GetGValue(col),(255-scale),255); + long blue = MulDiv(GetBValue(col),(255-scale),255); + + return RGB(red, green, blue); + } + long R = MulDiv(255-GetRValue(col),scale,255)+GetRValue(col); + long G = MulDiv(255-GetGValue(col),scale,255)+GetGValue(col); + long B = MulDiv(255-GetBValue(col),scale,255)+GetBValue(col); + + return RGB(R, G, B); +} diff --git a/src/TortoiseMerge/AppUtils.h b/src/TortoiseMerge/AppUtils.h index 173e465b2..221416d43 100644 --- a/src/TortoiseMerge/AppUtils.h +++ b/src/TortoiseMerge/AppUtils.h @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2006-2008,2010 - TortoiseSVN @@ -48,4 +48,5 @@ public: static bool CreateUnifiedDiff(const CString& orig, const CString& modified, const CString& output, bool bShowError); static bool HasClipboardFormat(UINT format); + static COLORREF IntenseColor(long scale, COLORREF col); }; diff --git a/src/TortoiseMerge/BaseView.cpp b/src/TortoiseMerge/BaseView.cpp index 63a81dee7..eb2d83a1c 100644 --- a/src/TortoiseMerge/BaseView.cpp +++ b/src/TortoiseMerge/BaseView.cpp @@ -1,7 +1,7 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2003-2009,2011 - TortoiseSVN -// Copyright (C) 2011 Sven Strickroth +// Copyright (C) 2003-2012 - TortoiseSVN +// Copyright (C) 2011-2012 Sven Strickroth // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -25,8 +25,20 @@ #include "BaseView.h" #include "DiffColors.h" #include "StringUtils.h" +#include "AppUtils.h" +#include "GotoLineDlg.h" + +// Note about lines: +// We use three different kind of lines here: +// 1. The real lines of the original files. +// These are shown in the view margin and are not used elsewhere, they're only for user information. +// 2. Screen lines. +// The lines actually shown on screen. All methods use screen lines as parameters/outputs if not explicitly specified otherwise. +// 3. View lines. +// These are the lines of the diff data. If unmodified sections are collapsed, not all of those lines are shown. +// +// Basically view lines are the line data, while screen lines are shown lines. -#include #ifdef _DEBUG #define new DEBUG_NEW @@ -35,12 +47,6 @@ #define MARGINWIDTH 20 #define HEADERHEIGHT 10 -#define MAXFONTS 8 - -#define INLINEADDED_COLOR RGB(255, 255, 150) -#define INLINEREMOVED_COLOR RGB(200, 100, 100) -#define MODIFIED_COLOR RGB(220, 220, 255) - #define IDT_SCROLLTIMER 101 CBaseView * CBaseView::m_pwndLeft = NULL; @@ -50,97 +56,95 @@ CLocatorBar * CBaseView::m_pwndLocator = NULL; CLineDiffBar * CBaseView::m_pwndLineDiffBar = NULL; CMFCStatusBar * CBaseView::m_pwndStatusBar = NULL; CMainFrame * CBaseView::m_pMainFrame = NULL; +CBaseView::Screen2View CBaseView::m_Screen2View; +const UINT CBaseView::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING); + +allviewstate CBaseView::m_AllState; IMPLEMENT_DYNCREATE(CBaseView, CView) CBaseView::CBaseView() + : m_pCacheBitmap(NULL) + , m_pViewData(NULL) + , m_pOtherViewData(NULL) + , m_pOtherView(NULL) + , m_nLineHeight(-1) + , m_nCharWidth(-1) + , m_nScreenChars(-1) + , m_nLastScreenChars(-1) + , m_nMaxLineLength(-1) + , m_nScreenLines(-1) + , m_nTopLine(0) + , m_nOffsetChar(0) + , m_nDigits(0) + , m_nMouseLine(-1) + , m_mouseInMargin(false) + , m_bIsHidden(FALSE) + , lineendings(EOL_AUTOLINE) + , m_bReadonly(true) + , m_bTarget(false) + , m_nCaretGoalPos(0) + , m_nSelViewBlockStart(-1) + , m_nSelViewBlockEnd(-1) + , m_bFocused(FALSE) + , m_bShowSelection(true) + , texttype(CFileTextLines::AUTOTYPE) + , m_bModified(FALSE) + , m_bOtherDiffChecked(false) + , m_bInlineWordDiff(true) + , m_bWhitespaceInlineDiffs(false) + , m_pState(NULL) + , m_pFindDialog(NULL) + , m_nStatusBarID(0) + , m_bMatchCase(false) + , m_bLimitToDiff(true) + , m_bWholeWord(false) + , m_pDC(NULL) { - m_pCacheBitmap = NULL; - m_pViewData = NULL; - m_pOtherViewData = NULL; - m_nLineHeight = -1; - m_nCharWidth = -1; - m_nScreenChars = -1; - m_nMaxLineLength = -1; - m_nScreenLines = -1; - m_nTopLine = 0; - m_nOffsetChar = 0; - m_nDigits = 0; - m_nMouseLine = -1; - m_bMouseWithin = FALSE; - m_bIsHidden = FALSE; - lineendings = EOL_AUTOLINE; - m_bCaretHidden = true; - m_ptCaretPos.x = 0; - m_ptCaretPos.y = 0; - m_nCaretGoalPos = 0; - m_ptSelectionStartPos = m_ptCaretPos; - m_ptSelectionEndPos = m_ptCaretPos; - m_ptSelectionOrigin = m_ptCaretPos; - m_bFocused = FALSE; - m_bShowSelection = true; - texttype = CFileTextLines::AUTOTYPE; + m_ptCaretViewPos.x = 0; + m_ptCaretViewPos.y = 0; + m_ptSelectionViewPosStart = m_ptCaretViewPos; + m_ptSelectionViewPosEnd = m_ptSelectionViewPosStart; + m_ptSelectionViewPosOrigin = m_ptSelectionViewPosEnd; m_bViewWhitespace = CRegDWORD(_T("Software\\TortoiseGitMerge\\ViewWhitespaces"), 1); m_bViewLinenumbers = CRegDWORD(_T("Software\\TortoiseGitMerge\\ViewLinenumbers"), 1); m_bShowInlineDiff = CRegDWORD(_T("Software\\TortoiseGitMerge\\DisplayBinDiff"), TRUE); + m_nInlineDiffMaxLineLength = CRegDWORD(_T("Software\\TortoiseGitMerge\\InlineDiffMaxLineLength"), 3000); m_InlineAddedBk = CRegDWORD(_T("Software\\TortoiseGitMerge\\InlineAdded"), INLINEADDED_COLOR); m_InlineRemovedBk = CRegDWORD(_T("Software\\TortoiseGitMerge\\InlineRemoved"), INLINEREMOVED_COLOR); m_ModifiedBk = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorModifiedB"), MODIFIED_COLOR); m_WhiteSpaceFg = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\Whitespace"), GetSysColor(COLOR_GRAYTEXT)); - m_sWordSeparators = CRegString(_T("Software\\TortoiseGitMerge\\WordSeparators"), _T("[]();.,{}!@#$%^&*-+=|/\\<>'`~"));; + m_sWordSeparators = CRegString(_T("Software\\TortoiseGitMerge\\WordSeparators"), _T("[]();:.,{}!@#$%^&*-+=|/\\<>'`~\"")); m_bIconLFs = CRegDWORD(_T("Software\\TortoiseGitMerge\\IconLFs"), 0); - m_nSelBlockStart = -1; - m_nSelBlockEnd = -1; - m_bModified = FALSE; - m_bOtherDiffChecked = false; - m_bInlineWordDiff = true; m_nTabSize = (int)(DWORD)CRegDWORD(_T("Software\\TortoiseGitMerge\\TabSize"), 4); - for (int i=0; iDeleteObject(); - delete m_pCacheBitmap; - } - for (int i=0; iDeleteObject(); - delete m_apFonts[i]; - } - m_apFonts[i] = NULL; - } + ReleaseBitmap(); + DeleteFonts(); DestroyIcon(m_hAddedIcon); DestroyIcon(m_hRemovedIcon); DestroyIcon(m_hConflictedIcon); @@ -151,6 +155,8 @@ CBaseView::~CBaseView() DestroyIcon(m_hLineEndingCRLF); DestroyIcon(m_hLineEndingLF); DestroyIcon(m_hEditedIcon); + DestroyIcon(m_hMovedIcon); + DestroyCursor(m_margincursor); } BEGIN_MESSAGE_MAP(CBaseView, CView) @@ -161,6 +167,7 @@ BEGIN_MESSAGE_MAP(CBaseView, CView) ON_WM_DESTROY() ON_WM_SIZE() ON_WM_MOUSEWHEEL() + ON_WM_MOUSEHWHEEL() ON_WM_SETCURSOR() ON_WM_KILLFOCUS() ON_WM_SETFOCUS() @@ -184,23 +191,29 @@ BEGIN_MESSAGE_MAP(CBaseView, CView) ON_COMMAND(ID_CARET_WORDRIGHT, &CBaseView::OnCaretWordright) ON_COMMAND(ID_EDIT_CUT, &CBaseView::OnEditCut) ON_COMMAND(ID_EDIT_PASTE, &CBaseView::OnEditPaste) - ON_WM_MOUSELEAVE() ON_WM_TIMER() + ON_WM_LBUTTONDBLCLK() + ON_COMMAND(ID_NAVIGATE_NEXTINLINEDIFF, &CBaseView::OnNavigateNextinlinediff) + ON_COMMAND(ID_NAVIGATE_PREVINLINEDIFF, &CBaseView::OnNavigatePrevinlinediff) ON_COMMAND(ID_EDIT_SELECTALL, &CBaseView::OnEditSelectall) + ON_COMMAND(ID_EDIT_FIND, OnEditFind) + ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage) + ON_COMMAND(ID_EDIT_FINDNEXT, OnEditFindnext) + ON_COMMAND(ID_EDIT_FINDPREV, OnEditFindprev) + ON_COMMAND(ID_EDIT_FINDNEXTSTART, OnEditFindnextStart) + ON_COMMAND(ID_EDIT_FINDPREVSTART, OnEditFindprevStart) + ON_COMMAND(ID_EDIT_GOTOLINE, &CBaseView::OnEditGotoline) + ON_WM_LBUTTONUP() END_MESSAGE_MAP() void CBaseView::DocumentUpdated() { - if (m_pCacheBitmap != NULL) - { - m_pCacheBitmap->DeleteObject(); - delete m_pCacheBitmap; - m_pCacheBitmap = NULL; - } + ReleaseBitmap(); m_nLineHeight = -1; m_nCharWidth = -1; m_nScreenChars = -1; + m_nLastScreenChars = -1; m_nMaxLineLength = -1; m_nScreenLines = -1; m_nTopLine = 0; @@ -208,28 +221,16 @@ void CBaseView::DocumentUpdated() m_bOtherDiffChecked = false; m_nDigits = 0; m_nMouseLine = -1; - m_nTabSize = (int)(DWORD)CRegDWORD(_T("Software\\TortoiseGitMerge\\TabSize"), 4); m_bViewLinenumbers = CRegDWORD(_T("Software\\TortoiseGitMerge\\ViewLinenumbers"), 1); - m_bShowInlineDiff = CRegDWORD(_T("Software\\TortoiseGitMerge\\DisplayBinDiff"), TRUE); m_InlineAddedBk = CRegDWORD(_T("Software\\TortoiseGitMerge\\InlineAdded"), INLINEADDED_COLOR); m_InlineRemovedBk = CRegDWORD(_T("Software\\TortoiseGitMerge\\InlineRemoved"), INLINEREMOVED_COLOR); m_ModifiedBk = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorModifiedB"), MODIFIED_COLOR); m_WhiteSpaceFg = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\Whitespace"), GetSysColor(COLOR_GRAYTEXT)); m_bIconLFs = CRegDWORD(_T("Software\\TortoiseGitMerge\\IconLFs"), 0); - for (int i=0; iDeleteObject(); - delete m_apFonts[i]; - } - m_apFonts[i] = NULL; - } - m_nSelBlockStart = -1; - m_nSelBlockEnd = -1; - RecalcVertScrollBar(); - RecalcHorzScrollBar(); + m_nInlineDiffMaxLineLength = CRegDWORD(_T("Software\\TortoiseGitMerge\\InlineDiffMaxLineLength"), 3000); + DeleteFonts(); + ClearCurrentSelection(); UpdateStatusBar(); Invalidate(); } @@ -249,6 +250,7 @@ void CBaseView::UpdateStatusBar() { case DIFFSTATE_ADDED: case DIFFSTATE_IDENTICALADDED: + case DIFFSTATE_MOVED_TO: case DIFFSTATE_THEIRSADDED: case DIFFSTATE_YOURSADDED: case DIFFSTATE_CONFLICTADDED: @@ -256,6 +258,7 @@ void CBaseView::UpdateStatusBar() break; case DIFFSTATE_IDENTICALREMOVED: case DIFFSTATE_REMOVED: + case DIFFSTATE_MOVED_FROM: case DIFFSTATE_THEIRSREMOVED: case DIFFSTATE_YOURSREMOVED: nRemovedLines++; @@ -279,12 +282,18 @@ void CBaseView::UpdateStatusBar() case CFileTextLines::BINARY: sBarText = _T("BINARY "); break; - case CFileTextLines::UNICODE_LE: + case CFileTextLines::UTF16_LE: sBarText = _T("UTF-16LE "); break; - case CFileTextLines::UNICODE_BE: + case CFileTextLines::UTF16_BE: sBarText = _T("UTF-16BE "); break; + case CFileTextLines::UTF32_LE: + sBarText = _T("UTF-32LE "); + break; + case CFileTextLines::UTF32_BE: + sBarText = _T("UTF-32BE "); + break; case CFileTextLines::UTF8: sBarText = _T("UTF8 "); break; @@ -307,6 +316,26 @@ void CBaseView::UpdateStatusBar() case EOL_CR: sBarText += _T("CR "); break; + case EOL_VT: + sBarText += _T("VT "); + break; + case EOL_FF: + sBarText += _T("FF "); + break; + case EOL_NEL: + sBarText += _T("NEL "); + break; + case EOL_LS: + sBarText += _T("LS "); + break; + case EOL_PS: + sBarText += _T("PS "); + break; +#ifdef _DEBUG + case EOL_AUTOLINE: + sBarText += _T("AEOL "); + break; +#endif } if (sBarText.IsEmpty()) @@ -379,39 +408,34 @@ BOOL CBaseView::PreCreateWindow(CREATESTRUCT& cs) CWnd *pParentWnd = CWnd::FromHandlePermanent(cs.hwndParent); if (pParentWnd == NULL || ! pParentWnd->IsKindOf(RUNTIME_CLASS(CSplitterWnd))) { - // View must always create its own scrollbars, - // if only it's not used within splitter + // View must always create its own scrollbars, + // if only it's not used within splitter cs.style |= (WS_HSCROLL | WS_VSCROLL); } cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS); return TRUE; } -CFont* CBaseView::GetFont(BOOL bItalic /*= FALSE*/, BOOL bBold /*= FALSE*/, BOOL bStrikeOut /*= FALSE*/) +CFont* CBaseView::GetFont(BOOL bItalic /*= FALSE*/, BOOL bBold /*= FALSE*/) { int nIndex = 0; if (bBold) nIndex |= 1; if (bItalic) nIndex |= 2; - if (bStrikeOut) - nIndex |= 4; if (m_apFonts[nIndex] == NULL) { m_apFonts[nIndex] = new CFont; m_lfBaseFont.lfCharSet = DEFAULT_CHARSET; m_lfBaseFont.lfWeight = bBold ? FW_BOLD : FW_NORMAL; m_lfBaseFont.lfItalic = (BYTE) bItalic; - m_lfBaseFont.lfStrikeOut = (BYTE) bStrikeOut; - if (bStrikeOut) - m_lfBaseFont.lfStrikeOut = (BYTE)(DWORD)CRegDWORD(_T("Software\\TortoiseGitMerge\\StrikeOut"), TRUE); CDC * pDC = GetDC(); if (pDC) { m_lfBaseFont.lfHeight = -MulDiv((DWORD)CRegDWORD(_T("Software\\TortoiseGitMerge\\LogFontSize"), 10), GetDeviceCaps(pDC->m_hDC, LOGPIXELSY), 72); ReleaseDC(pDC); } - _tcsncpy_s(m_lfBaseFont.lfFaceName, 32, (LPCTSTR)(CString)CRegString(_T("Software\\TortoiseGitMerge\\LogFontName"), _T("Courier New")), 32); + _tcsncpy_s(m_lfBaseFont.lfFaceName, (LPCTSTR)(CString)CRegString(_T("Software\\TortoiseGitMerge\\LogFontName"), _T("Courier New")), 32); if (!m_apFonts[nIndex]->CreateFontIndirect(&m_lfBaseFont)) { delete m_apFonts[nIndex]; @@ -425,16 +449,19 @@ CFont* CBaseView::GetFont(BOOL bItalic /*= FALSE*/, BOOL bBold /*= FALSE*/, BOOL void CBaseView::CalcLineCharDim() { CDC *pDC = GetDC(); + if (pDC == nullptr) + return; CFont *pOldFont = pDC->SelectObject(GetFont()); - CSize szCharExt = pDC->GetTextExtent(_T("X")); + const CSize szCharExt = pDC->GetTextExtent(_T("X")); + pDC->SelectObject(pOldFont); + ReleaseDC(pDC); + m_nLineHeight = szCharExt.cy; if (m_nLineHeight <= 0) m_nLineHeight = -1; m_nCharWidth = szCharExt.cx; if (m_nCharWidth <= 0) m_nCharWidth = -1; - pDC->SelectObject(pOldFont); - ReleaseDC(pDC); } int CBaseView::GetScreenChars() @@ -443,32 +470,34 @@ int CBaseView::GetScreenChars() { CRect rect; GetClientRect(&rect); - m_nScreenChars = (rect.Width() - GetMarginWidth()) / GetCharWidth(); + m_nScreenChars = (rect.Width() - GetMarginWidth() - GetSystemMetrics(SM_CXVSCROLL)) / GetCharWidth(); + if (m_nScreenChars < 0) + m_nScreenChars = 0; } return m_nScreenChars; } int CBaseView::GetAllMinScreenChars() const { - int nChars = 0; + int nChars = INT_MAX; if (IsLeftViewGood()) - nChars = m_pwndLeft->GetScreenChars(); + nChars = std::min(nChars, m_pwndLeft->GetScreenChars()); if (IsRightViewGood()) - nChars = (nChars < m_pwndRight->GetScreenChars() ? nChars : m_pwndRight->GetScreenChars()); + nChars = std::min(nChars, m_pwndRight->GetScreenChars()); if (IsBottomViewGood()) - nChars = (nChars < m_pwndBottom->GetScreenChars() ? nChars : m_pwndBottom->GetScreenChars()); - return nChars; + nChars = std::min(nChars, m_pwndBottom->GetScreenChars()); + return (nChars==INT_MAX) ? 0 : nChars; } int CBaseView::GetAllMaxLineLength() const { int nLength = 0; if (IsLeftViewGood()) - nLength = m_pwndLeft->GetMaxLineLength(); + nLength = std::max(nLength, m_pwndLeft->GetMaxLineLength()); if (IsRightViewGood()) - nLength = (nLength > m_pwndRight->GetMaxLineLength() ? nLength : m_pwndRight->GetMaxLineLength()); + nLength = std::max(nLength, m_pwndRight->GetMaxLineLength()); if (IsBottomViewGood()) - nLength = (nLength > m_pwndBottom->GetMaxLineLength() ? nLength : m_pwndBottom->GetMaxLineLength()); + nLength = std::max(nLength, m_pwndBottom->GetMaxLineLength()); return nLength; } @@ -498,7 +527,7 @@ int CBaseView::GetMaxLineLength() int nLineCount = GetLineCount(); for (int i=0; iGetCount() == 0) + return 0; + if ((int)m_Screen2View.size() <= index) + return 0; + int viewLine = GetViewLineForScreen(index); + if (m_pMainFrame->m_bWrapLines) + { + int nLineLength = GetLineChars(index).GetLength(); + ASSERT(nLineLength >= 0); + return nLineLength; + } + int nLineLength = m_pViewData->GetLine(viewLine).GetLength(); + ASSERT(nLineLength >= 0); + return nLineLength; } -int CBaseView::GetLineLength(int index) const +int CBaseView::GetViewLineLength(int nViewLine) const { if (m_pViewData == NULL) return 0; - if (m_pViewData->GetCount() == 0) + if (m_pViewData->GetCount() <= nViewLine) return 0; - int nLineLength = m_pViewData->GetLine(index).GetLength(); + int nLineLength = m_pViewData->GetLine(nViewLine).GetLength(); ASSERT(nLineLength >= 0); return nLineLength; } @@ -529,18 +570,47 @@ int CBaseView::GetLineCount() const { if (m_pViewData == NULL) return 1; - int nLineCount = m_pViewData->GetCount(); + int nLineCount = (int)m_Screen2View.size(); ASSERT(nLineCount >= 0); return nLineCount; } -LPCTSTR CBaseView::GetLineChars(int index) const +int CBaseView::GetSubLineOffset(int index) +{ + return m_Screen2View.GetSubLineOffset(index); +} + +CString CBaseView::GetViewLineChars(int nViewLine) const +{ + if (m_pViewData == NULL) + return 0; + if (m_pViewData->GetCount() <= nViewLine) + return 0; + return m_pViewData->GetLine(nViewLine); +} + +CString CBaseView::GetLineChars(int index) { if (m_pViewData == NULL) return 0; if (m_pViewData->GetCount() == 0) return 0; - return m_pViewData->GetLine(index); + if ((int)m_Screen2View.size() <= index) + return 0; + int viewLine = GetViewLineForScreen(index); + if (m_pMainFrame->m_bWrapLines) + { + int subLine = GetSubLineOffset(index); + if (subLine >= 0) + { + if (subLine < CountMultiLines(viewLine)) + { + return m_ScreenedViewLine[viewLine].SubLines[subLine]; + } + return L""; + } + } + return m_pViewData->GetLine(viewLine); } void CBaseView::CheckOtherView() @@ -549,25 +619,33 @@ void CBaseView::CheckOtherView() return; // find out what the 'other' file is m_pOtherViewData = NULL; + m_pOtherView = NULL; if (this == m_pwndLeft && IsRightViewGood()) + { m_pOtherViewData = m_pwndRight->m_pViewData; + m_pOtherView = m_pwndRight; + } if (this == m_pwndRight && IsLeftViewGood()) + { m_pOtherViewData = m_pwndLeft->m_pViewData; + m_pOtherView = m_pwndLeft; + } m_bOtherDiffChecked = true; } -CString CBaseView::GetWhitespaceBlock(CViewData *viewData, int nLineIndex) + +void CBaseView::GetWhitespaceBlock(CViewData *viewData, int nLineIndex, int & nStartBlock, int & nEndBlock) { - enum { MAX_WHITESPACEBLOCK_SIZE = 8 }; + enum { MAX_WHITESPACEBLOCK_SIZE = 8 }; ASSERT(viewData); DiffStates origstate = viewData->GetState(nLineIndex); // Go back and forward at most MAX_WHITESPACEBLOCK_SIZE lines to see where this block ends - int nStartBlock = nLineIndex; - int nEndBlock = nLineIndex; + nStartBlock = nLineIndex; + nEndBlock = nLineIndex; while ((nStartBlock > 0) && (nStartBlock > (nLineIndex - MAX_WHITESPACEBLOCK_SIZE))) { DiffStates state = viewData->GetState(nStartBlock - 1); @@ -588,8 +666,21 @@ CString CBaseView::GetWhitespaceBlock(CViewData *viewData, int nLineIndex) else break; } +} + +CString CBaseView::GetWhitespaceString(CViewData *viewData, int nStartBlock, int nEndBlock) +{ + enum { MAX_WHITESPACEBLOCK_SIZE = 8 }; + int len = 0; + for (int i = nStartBlock; i <= nEndBlock; ++i) + len += viewData->GetLine(i).GetLength(); CString block; + // do not check for whitespace blocks if the line is too long, because + // reserving a lot of memory here takes too much time (performance hog) + if (len > MAX_WHITESPACEBLOCK_SIZE*256) + return block; + block.Preallocate(len+1); for (int i = nStartBlock; i <= nEndBlock; ++i) block += viewData->GetLine(i); return block; @@ -597,39 +688,74 @@ CString CBaseView::GetWhitespaceBlock(CViewData *viewData, int nLineIndex) bool CBaseView::IsBlockWhitespaceOnly(int nLineIndex, bool& bIdentical) { - enum { MAX_WHITESPACEBLOCK_SIZE = 8 }; + if (m_pViewData == NULL) + return false; + bIdentical = false; CheckOtherView(); if (!m_pOtherViewData) return false; + int viewLine = GetViewLineForScreen(nLineIndex); if ( - (m_pViewData->GetState(nLineIndex) == DIFFSTATE_NORMAL) && - (m_pOtherViewData->GetLine(nLineIndex) == m_pViewData->GetLine(nLineIndex)) - ) + (m_pViewData->GetState(viewLine) == DIFFSTATE_NORMAL) && + (m_pOtherViewData->GetLine(viewLine) == m_pViewData->GetLine(viewLine)) + ) + { + bIdentical = true; + return false; + } + // first check whether the line itself only has whitespace changes + CString mine = m_pViewData->GetLine(viewLine); + CString other = m_pOtherViewData->GetLine(min(viewLine, m_pOtherViewData->GetCount() - 1)); + if (mine.IsEmpty() && other.IsEmpty()) + { + bIdentical = true; return false; + } + + if (mine == other) + { + bIdentical = true; + return true; + } + FilterWhitespaces(mine, other); + if (mine == other) + return true; + + int nStartBlock1, nEndBlock1; + int nStartBlock2, nEndBlock2; + GetWhitespaceBlock(m_pViewData, viewLine, nStartBlock1, nEndBlock1); + GetWhitespaceBlock(m_pOtherViewData, min(viewLine, m_pOtherViewData->GetCount() - 1), nStartBlock2, nEndBlock2); + mine = GetWhitespaceString(m_pViewData, nStartBlock1, nEndBlock1); + if (mine.IsEmpty()) + bIdentical = false; + else + { + other = GetWhitespaceString(m_pOtherViewData, nStartBlock2, nEndBlock2); + bIdentical = mine == other; + FilterWhitespaces(mine, other); + } - CString mine = GetWhitespaceBlock(m_pViewData, nLineIndex); - CString other = GetWhitespaceBlock(m_pOtherViewData, min(nLineIndex, m_pOtherViewData->GetCount() - 1)); - bIdentical = mine == other; + return (!mine.IsEmpty()) && (mine == other); +} - mine.Remove(' '); - mine.Remove('\t'); - mine.Remove('\r'); - mine.Remove('\n'); - other.Remove(' '); - other.Remove('\t'); - other.Remove('\r'); - other.Remove('\n'); +bool CBaseView::IsViewLineHidden(int nViewLine) +{ + return IsViewLineHidden(m_pViewData, nViewLine); +} - return (mine == other) && (!mine.IsEmpty()); +bool CBaseView::IsViewLineHidden(CViewData * pViewData, int nViewLine) +{ + return m_pMainFrame->m_bCollapsed && (pViewData->GetHideState(nViewLine)!=HIDESTATE_SHOWN); } int CBaseView::GetLineNumber(int index) const { if (m_pViewData == NULL) return -1; - if (m_pViewData->GetLineNumber(index)==DIFF_EMPTYLINENUMBER) + int viewLine = GetViewLineForScreen(index); + if (m_pViewData->GetLineNumber(viewLine)==DIFF_EMPTYLINENUMBER) return -1; - return m_pViewData->GetLineNumber(index); + return m_pViewData->GetLineNumber(viewLine); } int CBaseView::GetScreenLines() @@ -638,26 +764,30 @@ int CBaseView::GetScreenLines() { SCROLLBARINFO sbi; sbi.cbSize = sizeof(sbi); - GetScrollBarInfo(OBJID_HSCROLL, &sbi); - int scrollBarHeight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top; - + int scrollBarHeight = 0; + if (GetScrollBarInfo(OBJID_HSCROLL, &sbi)) + scrollBarHeight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top; + if ((sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE)||(sbi.rgstate[0] & STATE_SYSTEM_UNAVAILABLE)) + scrollBarHeight = 0; CRect rect; GetClientRect(&rect); m_nScreenLines = (rect.Height() - HEADERHEIGHT - scrollBarHeight) / GetLineHeight(); + if (m_nScreenLines < 0) + m_nScreenLines = 0; } return m_nScreenLines; } int CBaseView::GetAllMinScreenLines() const { - int nLines = 0; + int nLines = INT_MAX; if (IsLeftViewGood()) nLines = m_pwndLeft->GetScreenLines(); if (IsRightViewGood()) - nLines = (nLines < m_pwndRight->GetScreenLines() ? nLines : m_pwndRight->GetScreenLines()); + nLines = std::min(nLines, m_pwndRight->GetScreenLines()); if (IsBottomViewGood()) - nLines = (nLines < m_pwndBottom->GetScreenLines() ? nLines : m_pwndBottom->GetScreenLines()); - return nLines; + nLines = std::min(nLines, m_pwndBottom->GetScreenLines()); + return (nLines==INT_MAX) ? 0 : nLines; } int CBaseView::GetAllLineCount() const @@ -666,9 +796,9 @@ int CBaseView::GetAllLineCount() const if (IsLeftViewGood()) nLines = m_pwndLeft->GetLineCount(); if (IsRightViewGood()) - nLines = (nLines > m_pwndRight->GetLineCount() ? nLines : m_pwndRight->GetLineCount()); + nLines = std::max(nLines, m_pwndRight->GetLineCount()); if (IsBottomViewGood()) - nLines = (nLines > m_pwndBottom->GetLineCount() ? nLines : m_pwndBottom->GetLineCount()); + nLines = std::max(nLines, m_pwndBottom->GetLineCount()); return nLines; } @@ -723,7 +853,7 @@ void CBaseView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) void CBaseView::OnDoVScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/, CBaseView * master) { - // Note we cannot use nPos because of its 16-bit nature + // Note we cannot use nPos because of its 16-bit nature SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_ALL; @@ -732,8 +862,6 @@ void CBaseView::OnDoVScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBa int nPageLines = GetScreenLines(); int nLineCount = GetLineCount(); - RECT thumbrect; - POINT thumbpoint; int nNewTopLine; static LONG textwidth = 0; @@ -767,8 +895,11 @@ void CBaseView::OnDoVScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBa nNewTopLine = si.nTrackPos; if (GetFocus() == this) { + RECT thumbrect; GetClientRect(&thumbrect); ClientToScreen(&thumbrect); + + POINT thumbpoint; thumbpoint.x = thumbrect.right; thumbpoint.y = thumbrect.top + ((thumbrect.bottom-thumbrect.top)*si.nTrackPos)/(si.nMax-si.nMin); m_ScrollTool.Init(&thumbpoint); @@ -818,17 +949,23 @@ void CBaseView::RecalcHorzScrollBar(BOOL bPositionOnly /*= FALSE*/) } else { - EnableScrollBarCtrl(SB_HORZ, TRUE); - if (GetAllMinScreenChars() >= GetAllMaxLineLength() && m_nOffsetChar > 0) + EnableScrollBarCtrl(SB_HORZ, !m_pMainFrame->m_bWrapLines); + if (!m_pMainFrame->m_bWrapLines) { - m_nOffsetChar = 0; - Invalidate(); + int minScreenChars = GetAllMinScreenChars(); + int maxLineLength = GetAllMaxLineLength(); + if (minScreenChars >= maxLineLength && m_nOffsetChar > 0) + { + m_nOffsetChar = 0; + Invalidate(); + } + si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_POS | SIF_RANGE; + si.nMin = 0; + si.nMax = m_pMainFrame->m_bWrapLines ? minScreenChars : maxLineLength; + si.nMax += GetMarginWidth()/GetCharWidth(); + si.nPage = minScreenChars; + si.nPos = m_nOffsetChar; } - si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_POS | SIF_RANGE; - si.nMin = 0; - si.nMax = GetAllMaxLineLength() + GetMarginWidth()/GetCharWidth(); - si.nPage = GetAllMinScreenChars(); - si.nPos = m_nOffsetChar; } VERIFY(SetScrollInfo(SB_HORZ, &si)); } @@ -915,6 +1052,31 @@ void CBaseView::ScrollToChar(int nNewOffsetChar, BOOL bTrackScrollBar /*= TRUE*/ } } +void CBaseView::ScrollAllToChar(int nNewOffsetChar, BOOL bTrackScrollBar /* = TRUE */) +{ + if (m_pwndLeft) + m_pwndLeft->ScrollToChar(nNewOffsetChar, bTrackScrollBar); + if (m_pwndRight) + m_pwndRight->ScrollToChar(nNewOffsetChar, bTrackScrollBar); + if (m_pwndBottom) + m_pwndBottom->ScrollToChar(nNewOffsetChar, bTrackScrollBar); +} + +void CBaseView::ScrollAllSide(int delta) +{ + int nNewOffset = m_nOffsetChar; + nNewOffset += delta; + int nMaxLineLength = GetMaxLineLength(); + if (nNewOffset >= nMaxLineLength) + nNewOffset = nMaxLineLength - 1; + if (nNewOffset < 0) + nNewOffset = 0; + ScrollAllToChar(nNewOffset, TRUE); + if (m_pwndLineDiffBar) + m_pwndLineDiffBar->Invalidate(); + UpdateCaret(); +} + void CBaseView::ScrollSide(int delta) { int nNewOffset = m_nOffsetChar; @@ -930,13 +1092,27 @@ void CBaseView::ScrollSide(int delta) UpdateCaret(); } +void CBaseView::ScrollVertical(short zDelta) +{ + const int nLineCount = GetLineCount(); + int nTopLine = m_nTopLine; + nTopLine -= (zDelta/30); + if (nTopLine < 0) + nTopLine = 0; + if (nTopLine >= nLineCount) + nTopLine = nLineCount - 1; + ScrollToLine(nTopLine, TRUE); +} + void CBaseView::ScrollToLine(int nNewTopLine, BOOL bTrackScrollBar /*= TRUE*/) { if (m_nTopLine != nNewTopLine) { if (nNewTopLine < 0) nNewTopLine = 0; + int nScrollLines = m_nTopLine - nNewTopLine; + m_nTopLine = nNewTopLine; CRect rcScroll; GetClientRect(&rcScroll); @@ -956,43 +1132,85 @@ void CBaseView::DrawMargin(CDC *pdc, const CRect &rect, int nLineIndex) if ((nLineIndex >= 0)&&(m_pViewData)&&(m_pViewData->GetCount())) { - DiffStates state = m_pViewData->GetState(nLineIndex); + int nViewLine = GetViewLineForScreen(nLineIndex); HICON icon = NULL; - switch (state) + ASSERT(nViewLine<(int)m_ScreenedViewLine.size()); + TScreenedViewLine::EIcon eIcon = m_ScreenedViewLine[nViewLine].eIcon; + if (eIcon==TScreenedViewLine::ICN_UNKNOWN) { - case DIFFSTATE_ADDED: - case DIFFSTATE_THEIRSADDED: - case DIFFSTATE_YOURSADDED: - case DIFFSTATE_IDENTICALADDED: - case DIFFSTATE_CONFLICTADDED: - icon = m_hAddedIcon; + DiffStates state = m_pViewData->GetState(nViewLine); + switch (state) + { + case DIFFSTATE_ADDED: + case DIFFSTATE_THEIRSADDED: + case DIFFSTATE_YOURSADDED: + case DIFFSTATE_IDENTICALADDED: + case DIFFSTATE_CONFLICTADDED: + eIcon = TScreenedViewLine::ICN_ADD; + break; + case DIFFSTATE_REMOVED: + case DIFFSTATE_THEIRSREMOVED: + case DIFFSTATE_YOURSREMOVED: + case DIFFSTATE_IDENTICALREMOVED: + eIcon = TScreenedViewLine::ICN_REMOVED; + break; + case DIFFSTATE_CONFLICTED: + eIcon = TScreenedViewLine::ICN_CONFLICT; + break; + case DIFFSTATE_CONFLICTED_IGNORED: + eIcon = TScreenedViewLine::ICN_CONFLICTIGNORED; + break; + case DIFFSTATE_EDITED: + eIcon = TScreenedViewLine::ICN_EDIT; + break; + case DIFFSTATE_MOVED_TO: + case DIFFSTATE_MOVED_FROM: + eIcon = TScreenedViewLine::ICN_MOVED; + break; + default: + break; + } + bool bIdentical = false; + if ((state != DIFFSTATE_EDITED)&&(IsBlockWhitespaceOnly(nLineIndex, bIdentical))) + { + if (bIdentical) + eIcon = TScreenedViewLine::ICN_SAME; + else + eIcon = TScreenedViewLine::ICN_WHITESPACEDIFF; + } + m_ScreenedViewLine[nViewLine].eIcon = eIcon; + } + switch (eIcon) + { + case TScreenedViewLine::ICN_UNKNOWN: + case TScreenedViewLine::ICN_NONE: + break; + case TScreenedViewLine::ICN_SAME: + icon = m_hEqualIcon; break; - case DIFFSTATE_REMOVED: - case DIFFSTATE_THEIRSREMOVED: - case DIFFSTATE_YOURSREMOVED: - case DIFFSTATE_IDENTICALREMOVED: - icon = m_hRemovedIcon; + case TScreenedViewLine::ICN_EDIT: + icon = m_hEditedIcon; break; - case DIFFSTATE_CONFLICTED: + case TScreenedViewLine::ICN_WHITESPACEDIFF: + icon = m_hWhitespaceBlockIcon; + break; + case TScreenedViewLine::ICN_ADD: + icon = m_hAddedIcon; + break; + case TScreenedViewLine::ICN_CONFLICT: icon = m_hConflictedIcon; break; - case DIFFSTATE_CONFLICTED_IGNORED: + case TScreenedViewLine::ICN_CONFLICTIGNORED: icon = m_hConflictedIgnoredIcon; break; - case DIFFSTATE_EDITED: - icon = m_hEditedIcon; + case TScreenedViewLine::ICN_REMOVED: + icon = m_hRemovedIcon; break; - default: + case TScreenedViewLine::ICN_MOVED: + icon = m_hMovedIcon; break; } - bool bIdentical = false; - if ((state != DIFFSTATE_EDITED)&&(IsBlockWhitespaceOnly(nLineIndex, bIdentical))) - { - if (bIdentical) - icon = m_hEqualIcon; - else - icon = m_hWhitespaceBlockIcon; - } + if (icon) { @@ -1000,18 +1218,38 @@ void CBaseView::DrawMargin(CDC *pdc, const CRect &rect, int nLineIndex) } if ((m_bViewLinenumbers)&&(m_nDigits)) { - int nLineNumber = GetLineNumber(nLineIndex); - if (nLineNumber >= 0) + int nSubLine = GetSubLineOffset(nLineIndex); + bool bIsFirstSubline = (nSubLine == 0) || (nSubLine == -1); + CString sLinenumber; + if (bIsFirstSubline) { CString sLinenumberFormat; - CString sLinenumber; - sLinenumberFormat.Format(_T("%%%dd"), m_nDigits); - sLinenumber.Format(sLinenumberFormat, nLineNumber+1); - pdc->SetBkColor(::GetSysColor(COLOR_SCROLLBAR)); - pdc->SetTextColor(::GetSysColor(COLOR_WINDOWTEXT)); - - pdc->SelectObject(GetFont()); - pdc->ExtTextOut(rect.left + 18, rect.top, ETO_CLIPPED, &rect, sLinenumber, NULL); + int nLineNumber = GetLineNumber(nLineIndex); + if (IsViewLineHidden(GetViewLineForScreen(nLineIndex))) + { + // TODO: do not show if there is no number hidden + // TODO: show number if there is only one + sLinenumberFormat.Format(_T("%%%ds"), m_nDigits); + sLinenumber.Format(sLinenumberFormat, (m_nDigits>1) ? _T("↕⁞") : _T("⁞")); // alternative … + } + else if (nLineNumber >= 0) + { + sLinenumberFormat.Format(_T("%%%dd"), m_nDigits); + sLinenumber.Format(sLinenumberFormat, nLineNumber+1); + } + else if (m_pMainFrame->m_bWrapLines) + { + sLinenumberFormat.Format(_T("%%%ds"), m_nDigits); + sLinenumber.Format(sLinenumberFormat, _T("·")); + } + if (!sLinenumber.IsEmpty()) + { + pdc->SetBkColor(::GetSysColor(COLOR_SCROLLBAR)); + pdc->SetTextColor(::GetSysColor(COLOR_WINDOWTEXT)); + + pdc->SelectObject(GetFont()); + pdc->ExtTextOut(rect.left + 18, rect.top, ETO_CLIPPED, &rect, sLinenumber, NULL); + } } } } @@ -1021,19 +1259,15 @@ int CBaseView::GetMarginWidth() { if ((m_bViewLinenumbers)&&(m_pViewData)&&(m_pViewData->GetCount())) { - int nWidth = GetCharWidth(); if (m_nDigits <= 0) { int nLength = (int)m_pViewData->GetCount(); // find out how many digits are needed to show the highest line number - int nDigits = 1; - while (nLength / 10) - { - nDigits++; - nLength /= 10; - } - m_nDigits = nDigits; + CString sMax; + sMax.Format(_T("%d"), nLength); + m_nDigits = sMax.GetLength(); } + int nWidth = GetCharWidth(); return (MARGINWIDTH + (m_nDigits * nWidth) + 2); } return MARGINWIDTH; @@ -1043,50 +1277,43 @@ void CBaseView::DrawHeader(CDC *pdc, const CRect &rect) { CRect textrect(rect.left, rect.top, rect.Width(), GetLineHeight()+HEADERHEIGHT); COLORREF crBk, crFg; - CDiffColors::GetInstance().GetColors(DIFFSTATE_NORMAL, crBk, crFg); - crBk = ::GetSysColor(COLOR_SCROLLBAR); if (IsBottomViewGood()) { - pdc->SetBkColor(crBk); + CDiffColors::GetInstance().GetColors(DIFFSTATE_NORMAL, crBk, crFg); + crBk = ::GetSysColor(COLOR_SCROLLBAR); } else { - + DiffStates state = DIFFSTATE_REMOVED; if (this == m_pwndRight) { - CDiffColors::GetInstance().GetColors(DIFFSTATE_ADDED, crBk, crFg); - pdc->SetBkColor(crBk); - } - else - { - CDiffColors::GetInstance().GetColors(DIFFSTATE_REMOVED, crBk, crFg); - pdc->SetBkColor(crBk); + state = DIFFSTATE_ADDED; } + CDiffColors::GetInstance().GetColors(state, crBk, crFg); } + pdc->SetBkColor(crBk); pdc->FillSolidRect(textrect, crBk); pdc->SetTextColor(crFg); - pdc->SelectObject(GetFont(FALSE, TRUE, FALSE)); + pdc->SelectObject(GetFont(FALSE, TRUE)); + + CString sViewTitle; if (IsModified()) { - if (m_sWindowName.Left(2).Compare(_T("* "))!=0) - m_sWindowName = _T("* ") + m_sWindowName; + sViewTitle = _T("* ") + m_sWindowName; } else { - if (m_sWindowName.Left(2).Compare(_T("* "))==0) - m_sWindowName = m_sWindowName.Mid(2); + sViewTitle = m_sWindowName; } - CString sViewTitle = m_sWindowName; int nStringLength = (GetCharWidth()*m_sWindowName.GetLength()); if (nStringLength > rect.Width()) { - int offset = min(m_nOffsetChar, (nStringLength-rect.Width())/GetCharWidth()+1); - + int offset = std::min(m_nOffsetChar, (nStringLength-rect.Width())/GetCharWidth()+1); sViewTitle = m_sWindowName.Mid(offset); } - pdc->ExtTextOut(max(rect.left + (rect.Width()-nStringLength)/2, 1), + pdc->ExtTextOut(std::max(rect.left + (rect.Width()-nStringLength)/2, 1), rect.top+(HEADERHEIGHT/2), ETO_CLIPPED, textrect, sViewTitle, NULL); if (this->GetFocus() == this) pdc->DrawEdge(textrect, EDGE_BUMP, BF_RECT); @@ -1121,17 +1348,20 @@ void CBaseView::OnDraw(CDC * pDC) CRect rcCacheLine(GetMarginWidth(), 0, rcLine.Width(), nLineHeight); int nCurrentLine = m_nTopLine; + bool bBeyondFileLineCached = false; while (rcLine.top < rcClient.bottom) { if (nCurrentLine < nLineCount) { DrawMargin(&cacheDC, rcCacheMargin, nCurrentLine); DrawSingleLine(&cacheDC, rcCacheLine, nCurrentLine); + bBeyondFileLineCached = false; } - else + else if (!bBeyondFileLineCached) { DrawMargin(&cacheDC, rcCacheMargin, -1); DrawSingleLine(&cacheDC, rcCacheLine, -1); + bBeyondFileLineCached = true; } VERIFY(pDC->BitBlt(rcLine.left, rcLine.top, rcLine.Width(), rcLine.Height(), &cacheDC, 0, 0, SRCCOPY)); @@ -1144,87 +1374,122 @@ void CBaseView::OnDraw(CDC * pDC) cacheDC.DeleteDC(); } -BOOL CBaseView::IsLineRemoved(int nLineIndex) +bool CBaseView::IsStateConflicted(DiffStates state) { - DiffStates state = DIFFSTATE_UNKNOWN; - if (m_pViewData) - state = m_pViewData->GetState(nLineIndex); - BOOL ret = FALSE; switch (state) { - case DIFFSTATE_REMOVED: - case DIFFSTATE_THEIRSREMOVED: - case DIFFSTATE_YOURSREMOVED: - case DIFFSTATE_IDENTICALREMOVED: - ret = TRUE; - break; - default: - ret = FALSE; - break; + case DIFFSTATE_CONFLICTED: + case DIFFSTATE_CONFLICTED_IGNORED: + case DIFFSTATE_CONFLICTEMPTY: + case DIFFSTATE_CONFLICTADDED: + return true; } - return ret; + return false; } -bool CBaseView::IsLineConflicted(int nLineIndex) +bool CBaseView::IsStateEmpty(DiffStates state) { - DiffStates state = DIFFSTATE_UNKNOWN; - if (m_pViewData) - state = m_pViewData->GetState(nLineIndex); - bool ret = false; switch (state) { - case DIFFSTATE_CONFLICTED: - case DIFFSTATE_CONFLICTED_IGNORED: - case DIFFSTATE_CONFLICTEMPTY: - case DIFFSTATE_CONFLICTADDED: - ret = true; - break; - default: - ret = false; - break; + case DIFFSTATE_CONFLICTEMPTY: + case DIFFSTATE_UNKNOWN: + case DIFFSTATE_EMPTY: + return true; } - return ret; + return false; } -COLORREF CBaseView::IntenseColor(long scale, COLORREF col) +bool CBaseView::IsStateRemoved(DiffStates state) { - // if the color is already dark (gray scale below 127), - // then lighten the color by 'scale', otherwise darken it - int Gray = (((int)GetRValue(col)) + GetGValue(col) + GetBValue(col))/3; - if (Gray > 127) + switch (state) { - long red = MulDiv(GetRValue(col),(255-scale),255); - long green = MulDiv(GetGValue(col),(255-scale),255); - long blue = MulDiv(GetBValue(col),(255-scale),255); + case DIFFSTATE_REMOVED: + case DIFFSTATE_MOVED_FROM: + case DIFFSTATE_THEIRSREMOVED: + case DIFFSTATE_YOURSREMOVED: + case DIFFSTATE_IDENTICALREMOVED: + return true; + } + return false; +} - return RGB(red, green, blue); +DiffStates CBaseView::ResolveState(DiffStates state) +{ + if (IsStateConflicted(state)) + { + if (state == DIFFSTATE_CONFLICTEMPTY) + return DIFFSTATE_CONFLICTRESOLVEDEMPTY; + else + return DIFFSTATE_CONFLICTRESOLVED; } - long R = MulDiv(255-GetRValue(col),scale,255)+GetRValue(col); - long G = MulDiv(255-GetGValue(col),scale,255)+GetGValue(col); - long B = MulDiv(255-GetBValue(col),scale,255)+GetBValue(col); + return state; +} - return RGB(R, G, B); + +bool CBaseView::IsLineEmpty(int nLineIndex) +{ + if (m_pViewData == 0) + return FALSE; + int nViewLine = GetViewLineForScreen(nLineIndex); + return IsViewLineEmpty(nViewLine); } -COLORREF CBaseView::InlineDiffColor(int nLineIndex) +bool CBaseView::IsViewLineEmpty(int nViewLine) { - return IsLineRemoved(nLineIndex) ? m_InlineRemovedBk : m_InlineAddedBk; + if (m_pViewData == 0) + return FALSE; + const DiffStates state = m_pViewData->GetState(nViewLine); + return IsStateEmpty(state); } -void CBaseView::DrawLineEnding(CDC *pDC, const CRect &rc, int nLineIndex, const CPoint& origin) +bool CBaseView::IsLineRemoved(int nLineIndex) { - if (!(m_bViewWhitespace && m_pViewData && (nLineIndex >= 0) && (nLineIndex < m_pViewData->GetCount()))) - return; + if (m_pViewData == 0) + return FALSE; + int nViewLine = GetViewLineForScreen(nLineIndex); + return IsViewLineRemoved(nViewLine); +} - EOL ending = m_pViewData->GetLineEnding(nLineIndex); - if (m_bIconLFs) - { - HICON hEndingIcon = NULL; - switch (ending) - { - case EOL_CR: hEndingIcon = m_hLineEndingCR; break; - case EOL_CRLF: hEndingIcon = m_hLineEndingCRLF; break; - case EOL_LF: hEndingIcon = m_hLineEndingLF; break; +bool CBaseView::IsViewLineRemoved(int nViewLine) +{ + if (m_pViewData == 0) + return FALSE; + const DiffStates state = m_pViewData->GetState(nViewLine); + return IsStateRemoved(state); +} + +bool CBaseView::IsViewLineConflicted(int nLineIndex) +{ + if (m_pViewData == 0) + return false; + const DiffStates state = m_pViewData->GetState(nLineIndex); + return IsStateConflicted(state); +} + +COLORREF CBaseView::InlineDiffColor(int nLineIndex) +{ + return IsLineRemoved(nLineIndex) ? m_InlineRemovedBk : m_InlineAddedBk; +} + +COLORREF CBaseView::InlineViewLineDiffColor(int nViewLine) +{ + return IsViewLineRemoved(nViewLine) ? m_InlineRemovedBk : m_InlineAddedBk; +} + +void CBaseView::DrawLineEnding(CDC *pDC, const CRect &rc, int nLineIndex, const CPoint& origin) +{ + if (!(m_bViewWhitespace && m_pViewData && (nLineIndex >= 0) && (nLineIndex < GetLineCount()))) + return; + int viewLine = GetViewLineForScreen(nLineIndex); + EOL ending = m_pViewData->GetLineEnding(viewLine); + if (m_bIconLFs) + { + HICON hEndingIcon = NULL; + switch (ending) + { + case EOL_CR: hEndingIcon = m_hLineEndingCR; break; + case EOL_CRLF: hEndingIcon = m_hLineEndingCRLF; break; + case EOL_LF: hEndingIcon = m_hLineEndingLF; break; default: return; } if (origin.x < (rc.left-GetCharWidth())) @@ -1232,10 +1497,10 @@ void CBaseView::DrawLineEnding(CDC *pDC, const CRect &rc, int nLineIndex, const // If EOL style has changed, color end-of-line markers as inline differences. if( m_bShowInlineDiff && m_pOtherViewData && - (nLineIndex < m_pOtherViewData->GetCount()) && + (viewLine < m_pOtherViewData->GetCount()) && (ending != EOL_NOENDING) && - (ending != m_pOtherViewData->GetLineEnding(nLineIndex) && - (m_pOtherViewData->GetLineEnding(nLineIndex) != EOL_NOENDING)) + (ending != m_pOtherViewData->GetLineEnding(viewLine) && + (m_pOtherViewData->GetLineEnding(viewLine) != EOL_NOENDING)) ) { pDC->FillSolidRect(origin.x, origin.y, rc.Height(), rc.Height(), InlineDiffColor(nLineIndex)); @@ -1249,33 +1514,81 @@ void CBaseView::DrawLineEnding(CDC *pDC, const CRect &rc, int nLineIndex, const CPen * oldpen = pDC->SelectObject(&pen); int yMiddle = origin.y + rc.Height()/2; int xMiddle = origin.x+GetCharWidth()/2; - switch (ending) + bool bMultiline = false; + if (((int)m_Screen2View.size() > nLineIndex+1) && (GetViewLineForScreen(nLineIndex+1) == viewLine)) { - case EOL_CR: - // arrow from right to left - pDC->MoveTo(origin.x+GetCharWidth(), yMiddle); - pDC->LineTo(origin.x, yMiddle); - pDC->LineTo(origin.x+4, yMiddle+4); - pDC->MoveTo(origin.x, yMiddle); - pDC->LineTo(origin.x+4, yMiddle-4); - break; - case EOL_CRLF: - // arrow from top to middle+2, then left - pDC->MoveTo(origin.x+GetCharWidth(), rc.top); - pDC->LineTo(origin.x+GetCharWidth(), yMiddle); - pDC->LineTo(origin.x, yMiddle); - pDC->LineTo(origin.x+4, yMiddle+4); - pDC->MoveTo(origin.x, yMiddle); - pDC->LineTo(origin.x+4, yMiddle-4); - break; - case EOL_LF: - // arrow from top to bottom - pDC->MoveTo(xMiddle, rc.top); - pDC->LineTo(xMiddle, rc.bottom-1); - pDC->LineTo(xMiddle+4, rc.bottom-5); - pDC->MoveTo(xMiddle, rc.bottom-1); - pDC->LineTo(xMiddle-4, rc.bottom-5); - break; + if (GetLineLength(nLineIndex+1)) + { + // multiline + bMultiline = true; + pDC->MoveTo(origin.x, yMiddle-2); + pDC->LineTo(origin.x+GetCharWidth()-1, yMiddle-2); + pDC->LineTo(origin.x+GetCharWidth()-1, yMiddle+2); + pDC->LineTo(origin.x, yMiddle+2); + } + else if (GetLineLength(nLineIndex) == 0) + bMultiline = true; + } + else if ((nLineIndex > 0) && (GetViewLineForScreen(nLineIndex-1) == viewLine) && (GetLineLength(nLineIndex) == 0)) + bMultiline = true; + + if (!bMultiline) + { + switch (ending) + { + case EOL_AUTOLINE: + case EOL_CRLF: + // arrow from top to middle+2, then left + pDC->MoveTo(origin.x+GetCharWidth()-1, rc.top+1); + pDC->LineTo(origin.x+GetCharWidth()-1, yMiddle); + case EOL_CR: + // arrow from right to left + pDC->MoveTo(origin.x+GetCharWidth()-1, yMiddle); + pDC->LineTo(origin.x, yMiddle); + pDC->LineTo(origin.x+4, yMiddle+4); + pDC->MoveTo(origin.x, yMiddle); + pDC->LineTo(origin.x+4, yMiddle-4); + break; + case EOL_LFCR: + // from right-upper to left then down + pDC->MoveTo(origin.x+GetCharWidth()-1, yMiddle-2); + pDC->LineTo(xMiddle, yMiddle-2); + pDC->LineTo(xMiddle, rc.bottom-1); + pDC->LineTo(xMiddle+4, rc.bottom-5); + pDC->MoveTo(xMiddle, rc.bottom-1); + pDC->LineTo(xMiddle-4, rc.bottom-5); + break; + case EOL_LF: + // arrow from top to bottom + pDC->MoveTo(xMiddle, rc.top); + pDC->LineTo(xMiddle, rc.bottom-1); + pDC->LineTo(xMiddle+4, rc.bottom-5); + pDC->MoveTo(xMiddle, rc.bottom-1); + pDC->LineTo(xMiddle-4, rc.bottom-5); + break; + case EOL_FF: // Form Feed, U+000C + case EOL_NEL: // Next Line, U+0085 + case EOL_LS: // Line Separator, U+2028 + case EOL_PS: // Paragraph Separator, U+2029 + // draw a horizontal line at the bottom of this line + pDC->FillSolidRect(rc.left, rc.bottom-1, rc.right, rc.bottom, GetSysColor(COLOR_WINDOWTEXT)); + pDC->MoveTo(origin.x+GetCharWidth()-1, rc.bottom-GetCharWidth()-2); + pDC->LineTo(origin.x, rc.bottom-2); + pDC->LineTo(origin.x+5, rc.bottom-2); + pDC->MoveTo(origin.x, rc.bottom-2); + pDC->LineTo(origin.x+1, rc.bottom-6); + break; + default: // other EOLs + // arrow from top right to bottom left + pDC->MoveTo(origin.x+GetCharWidth()-1, rc.bottom-GetCharWidth()); + pDC->LineTo(origin.x, rc.bottom-1); + pDC->LineTo(origin.x+5, rc.bottom-2); + pDC->MoveTo(origin.x, rc.bottom-1); + pDC->LineTo(origin.x+1, rc.bottom-6); + break; + case EOL_NOENDING: + break; + } } pDC->SelectObject(oldpen); } @@ -1283,130 +1596,212 @@ void CBaseView::DrawLineEnding(CDC *pDC, const CRect &rc, int nLineIndex, const void CBaseView::DrawBlockLine(CDC *pDC, const CRect &rc, int nLineIndex) { + if (!m_bShowSelection) + return; + + int nSelBlockStart; + int nSelBlockEnd; + if (!GetViewSelection(nSelBlockStart, nSelBlockEnd)) + return; + const int THICKNESS = 2; COLORREF rectcol = GetSysColor(m_bFocused ? COLOR_WINDOWTEXT : COLOR_GRAYTEXT); - if ((nLineIndex == m_nSelBlockStart) && m_bShowSelection) + + int nViewLineIndex = GetViewLineForScreen(nLineIndex); + int nSubLine = GetSubLineOffset(nLineIndex); + bool bFirstLineOfViewLine = (nSubLine==0 || nSubLine==-1); + if ((nViewLineIndex == nSelBlockStart) && bFirstLineOfViewLine) { pDC->FillSolidRect(rc.left, rc.top, rc.Width(), THICKNESS, rectcol); } - if ((nLineIndex == m_nSelBlockEnd) && m_bShowSelection) + + bool bLastLineOfViewLine = (nLineIndex+1 == m_Screen2View.size()) || (GetViewLineForScreen(nLineIndex) != GetViewLineForScreen(nLineIndex+1)); + if ((nViewLineIndex == nSelBlockEnd) && bLastLineOfViewLine) { pDC->FillSolidRect(rc.left, rc.bottom - THICKNESS, rc.Width(), THICKNESS, rectcol); } } -void CBaseView::DrawText( - CDC * pDC, const CRect &rc, LPCTSTR text, int textlength, int nLineIndex, POINT coords, bool bModified, bool bInlineDiff) -{ - ASSERT(m_pViewData && (nLineIndex < m_pViewData->GetCount())); - DiffStates diffState = m_pViewData->GetState(nLineIndex); +void CBaseView::DrawTextLine( + CDC * pDC, const CRect &rc, int nLineIndex, POINT& coords) + { + ASSERT(nLineIndex < GetLineCount()); + int nViewLine = GetViewLineForScreen(nLineIndex); + ASSERT(m_pViewData && (nViewLine < m_pViewData->GetCount())); - // first suppose the whole line is selected - int selectedStart = 0, selectedEnd = textlength; + LineColors lineCols = GetLineColors(nViewLine); - if ((m_ptSelectionStartPos.y > nLineIndex) || (m_ptSelectionEndPos.y < nLineIndex) - || ! m_bShowSelection) + CString sViewLine = GetViewLineChars(nViewLine); + // mark selection + if (m_bShowSelection && HasTextSelection()) { - // this line has no selected text - selectedStart = textlength; + // has this line selection ? + if ((m_ptSelectionViewPosStart.y <= nViewLine) && (nViewLine <= m_ptSelectionViewPosEnd.y)) + { + int nViewLineLength = sViewLine.GetLength(); + + // first suppose the whole line is selected + int selectedStart = 0; + int selectedEnd = nViewLineLength; + + // the view line is partially selected + if (m_ptSelectionViewPosStart.y == nViewLine) + { + selectedStart = m_ptSelectionViewPosStart.x; + } + + if (m_ptSelectionViewPosEnd.y == nViewLine) + { + selectedEnd = m_ptSelectionViewPosEnd.x; + } + // apply selection coloring + // First enforce start and end point + lineCols.SplitBlock(selectedStart); + lineCols.SplitBlock(selectedEnd); + // change color of affected parts + long intenseColorScale = m_bFocused ? 70 : 30; + std::map::iterator it = lineCols.lower_bound(selectedStart); + for ( ; it != lineCols.end() && it->first < selectedEnd; ++it) + { + COLORREF crBk = CAppUtils::IntenseColor(intenseColorScale, it->second.background); + if (it->second.shot == it->second.background) + { + it->second.shot = crBk; + } + it->second.background = crBk; + it->second.text = CAppUtils::IntenseColor(intenseColorScale, it->second.text); + } + } } - else if ((m_ptSelectionStartPos.y == nLineIndex) || (m_ptSelectionEndPos.y == nLineIndex)) + + // TODO: remove duplicate from selection and mark + if (!m_sMarkedWord.IsEmpty()) { - // the line is partially selected - int xoffs = m_nOffsetChar + (coords.x - GetMarginWidth()) / GetCharWidth(); - if (m_ptSelectionStartPos.y == nLineIndex) + int nMarkLength = m_sMarkedWord.GetLength(); + //int nViewLineLength = sViewLine.GetLength(); + const TCHAR * text = sViewLine; + const TCHAR * findText = text; + while ((findText = _tcsstr(findText, (LPCTSTR)m_sMarkedWord))!=0) { - // the first line of selection - int nSelectionStartOffset = CalculateActualOffset(m_ptSelectionStartPos.y, m_ptSelectionStartPos.x); - selectedStart = max(min(nSelectionStartOffset - xoffs, textlength), 0); + int nMarkStart = static_cast(findText - text); + int nMarkEnd = nMarkStart + nMarkLength; + // First enforce start and end point + lineCols.SplitBlock(nMarkStart); + lineCols.SplitBlock(nMarkEnd); + // change color of affected parts + const long int nIntenseColorScale = 200; + std::map::iterator it = lineCols.lower_bound(nMarkStart); + for ( ; it != lineCols.end() && it->first < nMarkEnd; ++it) + { + COLORREF crBk = CAppUtils::IntenseColor(nIntenseColorScale, it->second.background); + if (it->second.shot == it->second.background) + { + it->second.shot = crBk; + } + it->second.background = crBk; + it->second.text = CAppUtils::IntenseColor(nIntenseColorScale, it->second.text); + } + findText += nMarkLength; } - - if (m_ptSelectionEndPos.y == nLineIndex) + } + if (!m_sFindText.IsEmpty()) + { + int nMarkStart = 0; + int nMarkEnd = 0; + int nStringPos = nMarkStart; + CString searchLine = sViewLine; + if (!m_bMatchCase) + searchLine.MakeLower(); + while (StringFound(searchLine, SearchNext, nMarkStart, nMarkEnd)!=0) { - // the last line of selection - int nSelectionEndOffset = CalculateActualOffset(m_ptSelectionEndPos.y, m_ptSelectionEndPos.x); - selectedEnd = max(min(nSelectionEndOffset - xoffs, textlength), 0); + // First enforce start and end point + lineCols.SplitBlock(nMarkStart+nStringPos); + lineCols.SplitBlock(nMarkEnd+nStringPos); + // change color of affected parts + const long int nIntenseColorScale = 30; + std::map::iterator it = lineCols.lower_bound(nMarkStart+nStringPos); + for ( ; it != lineCols.end() && it->first < nMarkEnd; ++it) + { + COLORREF crBk = CAppUtils::IntenseColor(nIntenseColorScale, it->second.background); + if (it->second.shot == it->second.background) + { + it->second.shot = crBk; + } + it->second.background = crBk; + it->second.text = CAppUtils::IntenseColor(nIntenseColorScale, it->second.text); + } + searchLine = searchLine.Mid(nMarkEnd); + nStringPos = nMarkEnd; } } - COLORREF crBkgnd, crText; - CDiffColors::GetInstance().GetColors(diffState, crBkgnd, crText); - if (bModified || (diffState == DIFFSTATE_EDITED)) - crBkgnd = m_ModifiedBk; - if (bInlineDiff) - crBkgnd = InlineDiffColor(nLineIndex); - - pDC->SetBkColor(crBkgnd); - pDC->SetTextColor(crText); - if (selectedStart>=0) - VERIFY(pDC->ExtTextOut(coords.x, coords.y, ETO_CLIPPED, &rc, text, selectedStart, NULL)); - - long intenseColorScale = m_bFocused ? 70 : 30; - pDC->SetBkColor(IntenseColor(intenseColorScale, crBkgnd)); - pDC->SetTextColor(IntenseColor(intenseColorScale, crText)); - VERIFY(pDC->ExtTextOut( - coords.x + selectedStart * GetCharWidth(), coords.y, ETO_CLIPPED, &rc, - text + selectedStart, selectedEnd - selectedStart, NULL)); - - pDC->SetBkColor(crBkgnd); - pDC->SetTextColor(crText); - if (textlength - selectedEnd >= 0) - VERIFY(pDC->ExtTextOut( - coords.x + selectedEnd * GetCharWidth(), coords.y, ETO_CLIPPED, &rc, - text + selectedEnd, textlength - selectedEnd, NULL)); -} - -bool CBaseView::DrawInlineDiff(CDC *pDC, const CRect &rc, int nLineIndex, const CString &line, CPoint &origin) -{ - if (!m_bShowInlineDiff || line.IsEmpty()) - return false; - if ((m_pwndBottom != NULL) && !(m_pwndBottom->IsHidden())) - return false; + // @ this point we may cache data for next line which may be same in wrapped mode - LPCTSTR pszDiffChars = NULL; - int nDiffLength = 0; - if (m_pOtherViewData) + int nTextOffset = 0; + int nSubline = GetSubLineOffset(nLineIndex); + for (int n=0; nGetCount() - 1); - pszDiffChars = m_pOtherViewData->GetLine(index); - nDiffLength = m_pOtherViewData->GetLine(index).GetLength(); + CString sLine = m_ScreenedViewLine[nViewLine].SubLines[n]; + nTextOffset += sLine.GetLength(); } - if (!pszDiffChars || !*pszDiffChars) - return false; - - CString diffline; - ExpandChars(pszDiffChars, 0, nDiffLength, diffline); - svn_diff_t * diff = NULL; - m_svnlinediff.Diff(&diff, line, line.GetLength(), diffline, diffline.GetLength(), m_bInlineWordDiff); - if (!diff || !SVNLineDiff::ShowInlineDiff(diff)) - return false; - - int lineoffset = 0; - std::deque removedPositions; - while (diff) + CString sLine = GetLineChars(nLineIndex); + int nLineLength = sLine.GetLength(); + CString sLineExp = ExpandChars(sLine); + LPCTSTR textExp = sLineExp; + //int nLineLengthExp = sLineExp.GetLength(); + int nStartExp = 0; + int nLeft = coords.x; + for (std::map::const_iterator itStart = lineCols.begin(); itStart != lineCols.end(); ++itStart) { - apr_off_t len = diff->original_length; - - CString s; - for (int i = 0; i < len; ++i) + std::map::const_iterator itEnd = itStart; + ++itEnd; + int nStart = std::max(0, itStart->first - nTextOffset); + int nEnd = nLineLength; + if (itEnd != lineCols.end()) { - s += m_svnlinediff.m_line1tokens[lineoffset].c_str(); - lineoffset++; + nEnd = std::min(nEnd, itEnd->first - nTextOffset); } - bool isModified = diff->type == svn_diff__type_diff_modified; - DrawText(pDC, rc, (LPCTSTR)s, s.GetLength(), nLineIndex, origin, true, isModified); - origin.x += pDC->GetTextExtent(s).cx; - - if (isModified && (len < diff->modified_length)) - removedPositions.push_back(origin.x - 1); + int nBlockLength = nEnd - nStart; + if (nBlockLength > 0 && nEnd>=0) + { + pDC->SetBkColor(itStart->second.background); + pDC->SetTextColor(itStart->second.text); + int nEndExp = CountExpandedChars(sLine, nEnd); + int nTextLength = nEndExp - nStartExp; + LPCTSTR p_zBlockText = textExp + nStartExp; + SIZE Size; + GetTextExtentPoint32(pDC->GetSafeHdc(), p_zBlockText, nTextLength, &Size); // falls time-2-tme + int nRight = nLeft + Size.cx; + if ((nRight > rc.left) && (nLeft < rc.right)) + { + // note: ExtTextOut has a limit for the length of the string. That limit is supposed + // to be 8192, but that's not really true: I found that the limit (at least on my machine and a few others) + // is 4094 (4095 doesn't work anymore). + // So we limit the length here to that 4094 chars. + // In case we're scrolled to the right, there's no need to draw the string + // from way outside our window, so we also offset the drawing to the start of the window. + // This reduces the string length as well. + int offset = 0; + int leftcoord = nLeft; + if (nLeft < 0) + { + offset = (-nLeft/GetCharWidth()); + nTextLength -= offset; + leftcoord = nLeft % GetCharWidth(); + } - diff = diff->next; + pDC->ExtTextOut(leftcoord, coords.y, ETO_CLIPPED, &rc, p_zBlockText+offset, min(nTextLength, 4094), NULL); + if ((itStart->second.shot != itStart->second.background) && (itStart->first == nStart + nTextOffset)) + { + pDC->FillSolidRect(nLeft-1, rc.top, 1, rc.Height(), itStart->second.shot); + } + } + nLeft = nRight; + coords.x = nRight; + nStartExp = nEndExp; + } } - // Draw vertical bars at removed chunks' positions. - for (std::deque::iterator it = removedPositions.begin(); it != removedPositions.end(); ++it) - pDC->FillSolidRect(*it, rc.top, 1, rc.Height(), m_InlineRemovedBk); - return true; } void CBaseView::DrawSingleLine(CDC *pDC, const CRect &rc, int nLineIndex) @@ -1424,7 +1819,27 @@ void CBaseView::DrawSingleLine(CDC *pDC, const CRect &rc, int nLineIndex) return; } - DiffStates diffState = m_pViewData->GetState(nLineIndex); + int viewLine = GetViewLineForScreen(nLineIndex); + if (m_pMainFrame->m_bCollapsed) + { + if (m_pViewData->GetHideState(viewLine) == HIDESTATE_MARKER) + { + COLORREF crBkgnd, crText; + CDiffColors::GetInstance().GetColors(DIFFSTATE_UNKNOWN, crBkgnd, crText); + pDC->FillSolidRect(rc, crBkgnd); + + const int THICKNESS = 2; + COLORREF rectcol = GetSysColor(COLOR_WINDOWTEXT); + pDC->FillSolidRect(rc.left, rc.top + (rc.Height()/2), rc.Width(), THICKNESS, rectcol); + pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT)); + pDC->SetBkColor(crBkgnd); + CRect rect = rc; + pDC->DrawText(_T("{...}"), &rect, DT_NOPREFIX|DT_SINGLELINE|DT_CENTER); + return; + } + } + + DiffStates diffState = m_pViewData->GetState(viewLine); COLORREF crBkgnd, crText; CDiffColors::GetInstance().GetColors(diffState, crBkgnd, crText); @@ -1434,57 +1849,39 @@ void CBaseView::DrawSingleLine(CDC *pDC, const CRect &rc, int nLineIndex) CRect rect = rc; pDC->FillSolidRect(rc, crBkgnd); // now draw some faint text patterns - pDC->SetTextColor(IntenseColor(130, crBkgnd)); + pDC->SetTextColor(CAppUtils::IntenseColor(130, crBkgnd)); pDC->DrawText(m_sConflictedText, rect, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE); DrawBlockLine(pDC, rc, nLineIndex); return; } CPoint origin(rc.left - m_nOffsetChar * GetCharWidth(), rc.top); - int nLength = GetLineLength(nLineIndex); - if (nLength == 0) + CString sLine = GetLineChars(nLineIndex); + if (sLine.IsEmpty()) { - // Draw the empty line pDC->FillSolidRect(rc, crBkgnd); DrawBlockLine(pDC, rc, nLineIndex); DrawLineEnding(pDC, rc, nLineIndex, origin); return; } - LPCTSTR pszChars = GetLineChars(nLineIndex); - if (pszChars == NULL) - return; CheckOtherView(); // Draw the line - pDC->SelectObject(GetFont(FALSE, FALSE, IsLineRemoved(nLineIndex))); - CString line; - ExpandChars(pszChars, 0, nLength, line); - - int nWidth = rc.right - origin.x; - int savedx = origin.x; - bool bInlineDiffDrawn = - nWidth > 0 && diffState != DIFFSTATE_NORMAL && - DrawInlineDiff(pDC, rc, nLineIndex, line, origin); - - if (!bInlineDiffDrawn) - { - int nCount = min(line.GetLength(), nWidth / GetCharWidth() + 1); - DrawText(pDC, rc, line, nCount, nLineIndex, origin, false, false); - } + pDC->SelectObject(GetFont(FALSE, FALSE)); - origin.x = savedx + pDC->GetTextExtent(line).cx; + DrawTextLine(pDC, rc, nLineIndex, origin); // draw white space after the end of line CRect frect = rc; if (origin.x > frect.left) frect.left = origin.x; - if (bInlineDiffDrawn) - CDiffColors::GetInstance().GetColors(DIFFSTATE_UNKNOWN, crBkgnd, crText); if (frect.right > frect.left) pDC->FillSolidRect(frect, crBkgnd); + // draw the whitespace chars + LPCTSTR pszChars = (LPCWSTR)sLine; if (m_bViewWhitespace) { int xpos = 0; @@ -1540,10 +1937,11 @@ void CBaseView::DrawSingleLine(CDC *pDC, const CRect &rc, int nLineIndex) } } DrawBlockLine(pDC, rc, nLineIndex); - DrawLineEnding(pDC, rc, nLineIndex, origin); + if (origin.x >= rc.left) + DrawLineEnding(pDC, rc, nLineIndex, origin); } -void CBaseView::ExpandChars(LPCTSTR pszChars, int nOffset, int nCount, CString &line) +void CBaseView::ExpandChars(const CString &sLine, int nOffset, int nCount, CString &line) { if (nCount <= 0) { @@ -1553,15 +1951,9 @@ void CBaseView::ExpandChars(LPCTSTR pszChars, int nOffset, int nCount, CString & int nTabSize = GetTabSize(); - int nActualOffset = 0; - for (int i=0; iScrollToLine(nNewTopLine, bTrackScrollBar); + if (m_pwndRight) m_pwndRight->ScrollToLine(nNewTopLine, bTrackScrollBar); - } - else - { - if (m_pwndLeft) - m_pwndLeft->ScrollToLine(nNewTopLine, bTrackScrollBar); - if (m_pwndRight) - m_pwndRight->ScrollToLine(nNewTopLine, bTrackScrollBar); - } if (m_pwndBottom) m_pwndBottom->ScrollToLine(nNewTopLine, bTrackScrollBar); if (m_pwndLocator) @@ -1630,15 +2037,12 @@ void CBaseView::GoToLine(int nNewLine, BOOL bAll) int nNewTopLine = nNewLine - GetScreenLines()/2; if (nNewTopLine < 0) nNewTopLine = 0; - if (m_pViewData) - { - if (nNewTopLine >= m_pViewData->GetCount()) - nNewTopLine = m_pViewData->GetCount()-1; - if (bAll) - ScrollAllToLine(nNewTopLine); - else - ScrollToLine(nNewTopLine); - } + if (nNewTopLine >= (int)m_Screen2View.size()) + nNewTopLine = (int)m_Screen2View.size()-1; + if (bAll) + ScrollAllToLine(nNewTopLine); + else + ScrollToLine(nNewTopLine); } BOOL CBaseView::OnEraseBkgnd(CDC* /*pDC*/) @@ -1668,42 +2072,56 @@ int CBaseView::OnCreate(LPCREATESTRUCT lpCreateStruct) void CBaseView::OnDestroy() { - CView::OnDestroy(); - for (int i=0; iDeleteObject(); - delete m_apFonts[i]; - m_apFonts[i] = NULL; - } - } - if (m_pCacheBitmap != NULL) + if ((m_pFindDialog)&&(!m_pFindDialog->IsTerminating())) { - delete m_pCacheBitmap; - m_pCacheBitmap = NULL; + m_pFindDialog->SendMessage(WM_CLOSE); + return; } + CView::OnDestroy(); + DeleteFonts(); + ReleaseBitmap(); } void CBaseView::OnSize(UINT nType, int cx, int cy) { - if (m_pCacheBitmap != NULL) - { - m_pCacheBitmap->DeleteObject(); - delete m_pCacheBitmap; - m_pCacheBitmap = NULL; - } - // make sure the view header is redrawn - CRect rcScroll; - GetClientRect(&rcScroll); - rcScroll.bottom = GetLineHeight()+HEADERHEIGHT; - InvalidateRect(&rcScroll, FALSE); + CView::OnSize(nType, cx, cy); + ReleaseBitmap(); m_nScreenLines = -1; m_nScreenChars = -1; - RecalcVertScrollBar(); - RecalcHorzScrollBar(); - CView::OnSize(nType, cx, cy); + if (m_nLastScreenChars != GetScreenChars()) + { + BuildAllScreen2ViewVector(); + m_nLastScreenChars = m_nScreenChars; + if (m_pMainFrame && m_pMainFrame->m_bWrapLines) + { + // if we're in wrap mode, the line wrapping most likely changed + // and that means we have to redraw the whole window, not just the + // scrolled part. + Invalidate(FALSE); + } + else + { + // make sure the view header is redrawn + CRect rcScroll; + GetClientRect(&rcScroll); + rcScroll.bottom = GetLineHeight()+HEADERHEIGHT; + InvalidateRect(&rcScroll, FALSE); + } + } + else + { + // make sure the view header is redrawn + CRect rcScroll; + GetClientRect(&rcScroll); + rcScroll.bottom = GetLineHeight()+HEADERHEIGHT; + InvalidateRect(&rcScroll, FALSE); + + UpdateLocator(); + RecalcVertScrollBar(); + RecalcHorzScrollBar(); + } + UpdateCaret(); } BOOL CBaseView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) @@ -1719,23 +2137,51 @@ BOOL CBaseView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) return CView::OnMouseWheel(nFlags, zDelta, pt); } +void CBaseView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt) +{ + if (m_pwndLeft) + m_pwndLeft->OnDoMouseHWheel(nFlags, zDelta, pt); + if (m_pwndRight) + m_pwndRight->OnDoMouseHWheel(nFlags, zDelta, pt); + if (m_pwndBottom) + m_pwndBottom->OnDoMouseHWheel(nFlags, zDelta, pt); + if (m_pwndLocator) + m_pwndLocator->Invalidate(); +} + void CBaseView::OnDoMouseWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/) { - if (GetKeyState(VK_CONTROL)&0x8000) + bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000); + bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000); + + if (bControl || bShift) { + if (m_pMainFrame->m_bWrapLines) + return; // Ctrl-Wheel scrolls sideways ScrollSide(-zDelta/30); } else { - int nLineCount = GetLineCount(); - int nTopLine = m_nTopLine; - nTopLine -= (zDelta/30); - if (nTopLine < 0) - nTopLine = 0; - if (nTopLine >= nLineCount) - nTopLine = nLineCount - 1; - ScrollToLine(nTopLine, TRUE); + ScrollVertical(zDelta); + } +} + +void CBaseView::OnDoMouseHWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/) +{ + bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000); + bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000); + + if (bControl || bShift) + { + ScrollVertical(zDelta); + } + else + { + if (m_pMainFrame->m_bWrapLines) + return; + // Ctrl-Wheel scrolls sideways + ScrollSide(-zDelta/30); } } @@ -1743,6 +2189,35 @@ BOOL CBaseView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { if (nHitTest == HTCLIENT) { + if ((m_pViewData)&&(m_pMainFrame->m_bCollapsed)) + { + if (m_nMouseLine < (int)m_Screen2View.size()) + { + if (m_nMouseLine >= 0) + { + int viewLine = GetViewLineForScreen(m_nMouseLine); + if (viewLine < m_pViewData->GetCount()) + { + if (m_pViewData->GetHideState(viewLine) == HIDESTATE_MARKER) + { + ::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_HAND))); + return TRUE; + } + } + } + } + } + if (m_mouseInMargin) + { + ::SetCursor(m_margincursor); + return TRUE; + } + if (m_nMouseLine >= 0) + { + ::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_IBEAM))); // Set To Edit Cursor + return TRUE; + } + ::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW))); // Set To Arrow Cursor return TRUE; } @@ -1771,72 +2246,131 @@ int CBaseView::GetLineFromPoint(CPoint point) return (((point.y - HEADERHEIGHT) / GetLineHeight()) + m_nTopLine); } -bool CBaseView::OnContextMenu(CPoint /*point*/, int /*nLine*/, DiffStates /*state*/) -{ - return false; -} - -void CBaseView::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +void CBaseView::OnContextMenu(CPoint point, DiffStates state) { - int nLine = GetLineFromPoint(point); + if (!this->IsWindowVisible()) + return; - if (!m_pViewData) + CIconMenu popup; + if (!popup.CreatePopupMenu()) return; - if (m_nSelBlockEnd >= GetLineCount()) - m_nSelBlockEnd = GetLineCount()-1; - if ((nLine <= m_pViewData->GetCount())&&(nLine > m_nTopLine)) - { - int nIndex = nLine - 1; - DiffStates state = m_pViewData->GetState(nIndex); - if ((state != DIFFSTATE_NORMAL) && (state != DIFFSTATE_UNKNOWN)) - { - // if there's nothing selected, or if the selection is outside the window then - // select the diff block under the cursor. - if (((m_nSelBlockStart<0)&&(m_nSelBlockEnd<0))|| - ((m_nSelBlockEnd < m_nTopLine)||(m_nSelBlockStart > m_nTopLine+m_nScreenLines))) - { - while (nIndex >= 0) - { - if (nIndex == 0) - { - nIndex--; - break; - } - if (state != m_pViewData->GetState(--nIndex)) - break; - } - m_nSelBlockStart = nIndex+1; - while (nIndex < (m_pViewData->GetCount()-1)) - { - if (state != m_pViewData->GetState(++nIndex)) - break; - } - if ((nIndex == (m_pViewData->GetCount()-1))&&(state == m_pViewData->GetState(nIndex))) - m_nSelBlockEnd = nIndex; - else - m_nSelBlockEnd = nIndex-1; - SetupSelection(m_nSelBlockStart, m_nSelBlockEnd); - m_ptCaretPos.x = 0; - m_ptCaretPos.y = nLine - 1; - UpdateCaret(); - } - } - if (((state == DIFFSTATE_NORMAL)||(state == DIFFSTATE_UNKNOWN)) && - (m_nSelBlockStart >= 0)&&(m_nSelBlockEnd >= 0)) - { - // find a more 'relevant' state in the selection - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; ++i) + + AddContextItems(popup, state); + + CompensateForKeyboard(point); + + int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this, 0); + ResetUndoStep(); + switch (cmd) + { + // 2-pane view commands; target is right view + case POPUPCOMMAND_USELEFTBLOCK: + m_pwndRight->UseLeftBlock(); + break; + case POPUPCOMMAND_USELEFTFILE: + m_pwndRight->UseLeftFile(); + break; + case POPUPCOMMAND_USEBOTHLEFTFIRST: + m_pwndRight->UseBothLeftFirst(); + break; + case POPUPCOMMAND_USEBOTHRIGHTFIRST: + m_pwndRight->UseBothRightFirst(); + break; + // 3-pane view commands; target is bottom view + case POPUPCOMMAND_USEYOURANDTHEIRBLOCK: + m_pwndBottom->UseBothRightFirst(); + break; + case POPUPCOMMAND_USETHEIRANDYOURBLOCK: + m_pwndBottom->UseBothLeftFirst(); + break; + case POPUPCOMMAND_USEYOURBLOCK: + m_pwndBottom->UseRightBlock(); + break; + case POPUPCOMMAND_USEYOURFILE: + m_pwndBottom->UseRightFile(); + break; + case POPUPCOMMAND_USETHEIRBLOCK: + m_pwndBottom->UseLeftBlock(); + break; + case POPUPCOMMAND_USETHEIRFILE: + m_pwndBottom->UseLeftFile(); + break; + // copy, cut and paste commands + case ID_EDIT_COPY: + OnEditCopy(); + break; + case ID_EDIT_CUT: + OnEditCut(); + break; + case ID_EDIT_PASTE: + OnEditPaste(); + break; + default: + return; + } // switch (cmd) + SaveUndoStep(); // all except copy, cut paste save undo step, but this should not be harmful as step is empty already and thus not saved + return; +} + +void CBaseView::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +{ + if (!m_pViewData) + return; + + int nViewBlockStart = -1; + int nViewBlockEnd = -1; + GetViewSelection(nViewBlockStart, nViewBlockEnd); + if ((point.x >= 0) && (point.y >= 0)) + { + int nLine = GetLineFromPoint(point)-1; + if ((nLine >= 0) && (nLine < m_Screen2View.size())) + { + int nViewLine = GetViewLineForScreen(nLine); + if (((nViewLine < nViewBlockStart) || (nViewBlockEnd < nViewLine))) { - state = m_pViewData->GetState(i); - if ((state != DIFFSTATE_NORMAL) && (state != DIFFSTATE_UNKNOWN)) - break; + ClearSelection(); // Clear text-copy selection + + nViewBlockStart = nViewLine; + nViewBlockEnd = nViewLine; + DiffStates state = m_pViewData->GetState(nViewLine); + while (nViewBlockStart > 0) + { + const DiffStates lineState = m_pViewData->GetState(nViewBlockStart-1); + if (!LinesInOneChange(-1, state, lineState)) + break; + nViewBlockStart--; + } + + while (nViewBlockEnd < (m_pViewData->GetCount()-1)) + { + const DiffStates lineState = m_pViewData->GetState(nViewBlockEnd+1); + if (!LinesInOneChange(1, state, lineState)) + break; + nViewBlockEnd++; + } + + SetupAllViewSelection(nViewBlockStart, nViewBlockEnd); + UpdateCaretPosition(point); } } - bool bKeepSelection = OnContextMenu(point, nLine, state); - if (! bKeepSelection) - ClearSelection(); - RefreshViews(); } + + // FixSelection(); fix selection range + /*if (m_nSelBlockEnd >= m_pViewData->GetCount()) + m_nSelBlockEnd = m_pViewData->GetCount()-1;//*/ + + DiffStates state = DIFFSTATE_UNKNOWN; + if (GetViewSelection(nViewBlockStart, nViewBlockEnd)) + { + // find a more 'relevant' state in the selection + for (int i=nViewBlockStart; i<=nViewBlockEnd; ++i) + { + state = m_pViewData->GetState(i); + if ((state != DIFFSTATE_NORMAL) && (state != DIFFSTATE_UNKNOWN)) + break; + } + } + OnContextMenu(point, state); } void CBaseView::RefreshViews() @@ -1862,45 +2396,69 @@ void CBaseView::RefreshViews() void CBaseView::GoToFirstDifference() { - m_ptCaretPos.y = 0; + SetCaretToFirstViewLine(); SelectNextBlock(1, false, false); } -void CBaseView::HiglightLines(int start, int end /* = -1 */) +void CBaseView::GoToFirstConflict() +{ + SetCaretToFirstViewLine(); + SelectNextBlock(1, true, false); +} + +void CBaseView::HighlightLines(int nStart, int nEnd /* = -1 */) { ClearSelection(); - m_nSelBlockStart = start; - if (end < 0) - end = start; - m_nSelBlockEnd = end; - m_ptCaretPos.x = 0; - m_ptCaretPos.y = start; - UpdateCaret(); + SetupAllSelection(nStart, max(nStart, nEnd)); + + UpdateCaretPosition(SetupPoint(0, nStart)); + Invalidate(); +} + +void CBaseView::HighlightViewLines(int nStart, int nEnd /* = -1 */) +{ + ClearSelection(); + SetupAllViewSelection(nStart, max(nStart, nEnd)); + + UpdateCaretViewPosition(SetupPoint(0, nStart)); Invalidate(); } +void CBaseView::SetupAllViewSelection(int start, int end) +{ + SetupViewSelection(m_pwndBottom, start, end); + SetupViewSelection(m_pwndLeft, start, end); + SetupViewSelection(m_pwndRight, start, end); +} + +void CBaseView::SetupAllSelection(int start, int end) +{ + SetupAllViewSelection(GetViewLineForScreen(start), GetViewLineForScreen(end)); +} + +//void CBaseView::SetupSelection(CBaseView* view, int start, int end) { } + void CBaseView::SetupSelection(int start, int end) { - if (IsBottomViewGood()) - { - m_pwndBottom->m_nSelBlockStart = start; - m_pwndBottom->m_nSelBlockEnd = end; - m_pwndBottom->Invalidate(); - } - if (IsLeftViewGood()) - { - m_pwndLeft->m_nSelBlockStart = start; - m_pwndLeft->m_nSelBlockEnd = end; - m_pwndLeft->Invalidate(); - } - if (IsRightViewGood()) - { - m_pwndRight->m_nSelBlockStart = start; - m_pwndRight->m_nSelBlockEnd = end; - m_pwndRight->Invalidate(); - } + SetupViewSelection(GetViewLineForScreen(start), GetViewLineForScreen(end)); +} + +void CBaseView::SetupViewSelection(CBaseView* view, int start, int end) +{ + if (!IsViewGood(view)) + return; + view->SetupViewSelection(start, end); +} + +void CBaseView::SetupViewSelection(int start, int end) +{ + // clear text selection before setting line selection ? + m_nSelViewBlockStart = start; + m_nSelViewBlockEnd = end; + Invalidate(); } + void CBaseView::OnMergePreviousconflict() { SelectNextBlock(-1, true); @@ -1921,114 +2479,213 @@ void CBaseView::OnMergePreviousdifference() SelectNextBlock(-1, false); } -void CBaseView::SelectNextBlock(int nDirection, bool bConflict, bool bSkipEndOfCurrentBlock /* = true */) +bool CBaseView::HasNextConflict() +{ + return SelectNextBlock(1, true, true, true); +} + +bool CBaseView::HasPrevConflict() +{ + return SelectNextBlock(-1, true, true, true); +} + +bool CBaseView::HasNextDiff() +{ + return SelectNextBlock(1, false, true, true); +} + +bool CBaseView::HasPrevDiff() +{ + return SelectNextBlock(-1, false, true, true); +} + +bool CBaseView::LinesInOneChange(int direction, + DiffStates initialLineState, DiffStates currentLineState) +{ + // Checks whether all the adjacent lines starting from the initial line + // and up to the current line form the single change + + // Do not distinguish between moved and added/removed lines + if (initialLineState == DIFFSTATE_MOVED_TO) + initialLineState = DIFFSTATE_ADDED; + if (initialLineState == DIFFSTATE_MOVED_FROM) + initialLineState = DIFFSTATE_REMOVED; + if (currentLineState == DIFFSTATE_MOVED_TO) + currentLineState = DIFFSTATE_ADDED; + if (currentLineState == DIFFSTATE_MOVED_FROM) + currentLineState = DIFFSTATE_REMOVED; + + // First of all, if the two lines have identical states, they surely + // belong to one change. + if (initialLineState == currentLineState) + return true; + + // Either we move down and initial line state is "added" or "removed" and + // current line state is "empty"... + if (direction > 0) + { + if (currentLineState == DIFFSTATE_EMPTY) + { + if (initialLineState == DIFFSTATE_ADDED || initialLineState == DIFFSTATE_REMOVED) + return true; + } + if (initialLineState == DIFFSTATE_CONFLICTADDED && currentLineState == DIFFSTATE_CONFLICTEMPTY) + return true; + } + // ...or we move up and initial line state is "empty" and current line + // state is "added" or "removed". + if (direction < 0) + { + if (initialLineState == DIFFSTATE_EMPTY) + { + if (currentLineState == DIFFSTATE_ADDED || currentLineState == DIFFSTATE_REMOVED) + return true; + } + if (initialLineState == DIFFSTATE_CONFLICTEMPTY && currentLineState == DIFFSTATE_CONFLICTADDED) + return true; + } + return false; +} + +bool CBaseView::SelectNextBlock(int nDirection, bool bConflict, bool bSkipEndOfCurrentBlock /* = true */, bool dryrun /* = false */) { if (! m_pViewData) - return; + return false; - if (m_pViewData->GetCount() == 0) - return; + const int linesCount = (int)m_Screen2View.size(); + if(linesCount == 0) + return false; - int nCenterPos = m_ptCaretPos.y; - int nLimit = 0; + int nCenterPos = GetCaretPosition().y; + int nLimit = -1; if (nDirection > 0) - nLimit = m_pViewData->GetCount() - 1; + nLimit = linesCount; - if (nCenterPos >= m_pViewData->GetCount()) - nCenterPos = m_pViewData->GetCount()-1; + if (nCenterPos >= linesCount) + nCenterPos = linesCount-1; if (bSkipEndOfCurrentBlock) { // Find end of current block - DiffStates state = m_pViewData->GetState(nCenterPos); - while ((nCenterPos != nLimit) && - (m_pViewData->GetState(nCenterPos)==state)) + const DiffStates state = m_pViewData->GetState(GetViewLineForScreen(nCenterPos)); + while (nCenterPos != nLimit) + { + const DiffStates lineState = m_pViewData->GetState(GetViewLineForScreen(nCenterPos)); + if (!LinesInOneChange(nDirection, state, lineState)) + break; nCenterPos += nDirection; + } } // Find next diff/conflict block while (nCenterPos != nLimit) { - DiffStates linestate = m_pViewData->GetState(nCenterPos); + DiffStates linestate = m_pViewData->GetState(GetViewLineForScreen(nCenterPos)); if (!bConflict && (linestate != DIFFSTATE_NORMAL) && (linestate != DIFFSTATE_UNKNOWN)) + { break; + } if (bConflict && ((linestate == DIFFSTATE_CONFLICTADDED) || (linestate == DIFFSTATE_CONFLICTED_IGNORED) || (linestate == DIFFSTATE_CONFLICTED) || (linestate == DIFFSTATE_CONFLICTEMPTY))) + { break; + } nCenterPos += nDirection; } + if (nCenterPos == nLimit) + return false; + if (dryrun) + return (nCenterPos != nLimit); // Find end of new block - DiffStates state = m_pViewData->GetState(nCenterPos); + DiffStates state = m_pViewData->GetState(GetViewLineForScreen(nCenterPos)); int nBlockEnd = nCenterPos; - while ((nBlockEnd != nLimit) && - (state == m_pViewData->GetState(nBlockEnd + nDirection))) + const int maxAllowedLine = nLimit-nDirection; + while (nBlockEnd != maxAllowedLine) + { + const int lineIndex = nBlockEnd + nDirection; + if (lineIndex >= linesCount) + break; + DiffStates lineState = m_pViewData->GetState(GetViewLineForScreen(lineIndex)); + if (!LinesInOneChange(nDirection, state, lineState)) + break; nBlockEnd += nDirection; + } int nTopPos = nCenterPos - (GetScreenLines()/2); if (nTopPos < 0) nTopPos = 0; - m_ptCaretPos.x = 0; - m_ptCaretPos.y = nCenterPos; + POINT ptCaretPos = {0, nCenterPos}; + SetCaretPosition(ptCaretPos); ClearSelection(); if (nDirection > 0) - SetupSelection(nCenterPos, nBlockEnd); + SetupAllSelection(nCenterPos, nBlockEnd); else - SetupSelection(nBlockEnd, nCenterPos); + SetupAllSelection(nBlockEnd, nCenterPos); ScrollAllToLine(nTopPos, FALSE); RecalcAllVertScrollBars(TRUE); - m_nCaretGoalPos = 0; + SetCaretToLineStart(); + EnsureCaretVisible(); + OnNavigateNextinlinediff(); + + UpdateViewsCaretPosition(); UpdateCaret(); ShowDiffLines(nCenterPos); + return true; } BOOL CBaseView::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult) { - // need to handle both ANSI and UNICODE versions of the message - TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR; - TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR; + if (pNMHDR->idFrom != (UINT)m_hWnd) + return FALSE; + CString strTipText; - UINT nID = (UINT)pNMHDR->idFrom; - if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) || - pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND)) - { - // idFrom is actually the HWND of the tool - nID = ::GetDlgCtrlID((HWND)nID); - } + strTipText = m_sWindowName + _T("\r\n") + m_sFullFilePath; - if (pNMHDR->idFrom == (UINT)m_hWnd) + DWORD pos = GetMessagePos(); + CPoint point(GET_X_LPARAM(pos), GET_Y_LPARAM(pos)); + ScreenToClient(&point); + const int nLine = GetButtonEventLineIndex(point); + + if (nLine >= 0) { - if (m_sWindowName.Left(2).Compare(_T("* "))==0) - { - strTipText = m_sWindowName.Mid(2) + _T("\r\n") + m_sFullFilePath; - } - else + int nViewLine = GetViewLineForScreen(nLine); + if((m_pViewData)&&(nViewLine < m_pViewData->GetCount())) { - strTipText = m_sWindowName + _T("\r\n") + m_sFullFilePath; + if (m_pViewData->GetState(nViewLine)==DIFFSTATE_MOVED_FROM) + { + strTipText.Format(IDS_MOVED_TO_TT, m_pViewData->GetMovedIndex(nViewLine)+1); + } + if (m_pViewData->GetState(nViewLine)==DIFFSTATE_MOVED_TO) + { + strTipText.Format(IDS_MOVED_FROM_TT, m_pViewData->GetMovedIndex(nViewLine)+1); + } } } - else - return FALSE; + *pResult = 0; if (strTipText.IsEmpty()) return TRUE; + // need to handle both ANSI and UNICODE versions of the message if (pNMHDR->code == TTN_NEEDTEXTA) { + TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR; pTTTA->lpszText = m_szTip; WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0); } else { + TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR; lstrcpyn(m_wszTip, strTipText, strTipText.GetLength()+1); pTTTW->lpszText = m_wszTip; } @@ -2036,13 +2693,19 @@ BOOL CBaseView::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult) return TRUE; // message was handled } - INT_PTR CBaseView::OnToolHitTest(CPoint point, TOOLINFO* pTI) const { CRect rcClient; GetClientRect(rcClient); CRect textrect(rcClient.left, rcClient.top, rcClient.Width(), m_nLineHeight+HEADERHEIGHT); - if (textrect.PtInRect(point)) + int marginwidth = MARGINWIDTH; + if ((m_bViewLinenumbers)&&(m_pViewData)&&(m_pViewData->GetCount())&&(m_nDigits > 0)) + { + marginwidth = (MARGINWIDTH + (m_nDigits * m_nCharWidth) + 2); + } + CRect borderrect(rcClient.left, rcClient.top+m_nLineHeight+HEADERHEIGHT, marginwidth, rcClient.bottom); + + if (textrect.PtInRect(point) || borderrect.PtInRect(point)) { // inside the header part of the view (showing the filename) pTI->hwnd = this->m_hWnd; @@ -2058,8 +2721,9 @@ INT_PTR CBaseView::OnToolHitTest(CPoint point, TOOLINFO* pTI) const pToolTip->SetMaxTipWidth(INT_MAX); } - return 1; + return (textrect.PtInRect(point) ? 1 : 2); } + return -1; } @@ -2067,35 +2731,68 @@ void CBaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000); bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000); + switch (nChar) { + case VK_TAB: + if ((nChar == '\t') && bControl) + { + if (this==m_pwndLeft) + { + if (IsViewGood(m_pwndRight)) + { + m_pwndRight->SetFocus(); + } + else if (IsViewGood(m_pwndBottom)) + { + m_pwndBottom->SetFocus(); + } + } + else if (this==m_pwndRight) + { + if (IsViewGood(m_pwndBottom)) + { + m_pwndBottom->SetFocus(); + } + else if (IsViewGood(m_pwndLeft)) + { + m_pwndLeft->SetFocus(); + } + } + else if (this==m_pwndBottom) + { + if (IsViewGood(m_pwndLeft)) + { + m_pwndLeft->SetFocus(); + } + else if (IsViewGood(m_pwndRight)) + { + m_pwndRight->SetFocus(); + } + } + } + break; case VK_PRIOR: { - m_ptCaretPos.y -= GetScreenLines(); - m_ptCaretPos.y = max(m_ptCaretPos.y, 0); - m_ptCaretPos.x = CalculateCharIndex(m_ptCaretPos.y, m_nCaretGoalPos); - if (bShift) - AdjustSelection(); - else - ClearSelection(); - UpdateCaret(); - EnsureCaretVisible(); - ShowDiffLines(m_ptCaretPos.y); + POINT ptCaretPos = GetCaretPosition(); + ptCaretPos.y -= GetScreenLines(); + ptCaretPos.y = max(ptCaretPos.y, 0); + ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos); + SetCaretPosition(ptCaretPos); + OnCaretMove(MOVELEFT, bShift); + ShowDiffLines(ptCaretPos.y); } break; case VK_NEXT: { - m_ptCaretPos.y += GetScreenLines(); - if (m_ptCaretPos.y >= GetLineCount()) - m_ptCaretPos.y = GetLineCount()-1; - m_ptCaretPos.x = CalculateCharIndex(m_ptCaretPos.y, m_nCaretGoalPos); - if (bShift) - AdjustSelection(); - else - ClearSelection(); - UpdateCaret(); - EnsureCaretVisible(); - ShowDiffLines(m_ptCaretPos.y); + POINT ptCaretPos = GetCaretPosition(); + ptCaretPos.y += GetScreenLines(); + if (ptCaretPos.y >= GetLineCount()) + ptCaretPos.y = GetLineCount()-1; + ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos); + SetCaretPosition(ptCaretPos); + OnCaretMove(MOVERIGHT, bShift); + ShowDiffLines(ptCaretPos.y); } break; case VK_HOME: @@ -2103,25 +2800,20 @@ void CBaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) if (bControl) { ScrollAllToLine(0); - m_ptCaretPos.x = 0; - m_ptCaretPos.y = 0; + SetCaretToViewStart(); m_nCaretGoalPos = 0; if (bShift) - AdjustSelection(); + AdjustSelection(MOVELEFT); else ClearSelection(); UpdateCaret(); } else { - m_ptCaretPos.x = 0; + SetCaretToLineStart(); m_nCaretGoalPos = 0; - if (bShift) - AdjustSelection(); - else - ClearSelection(); - EnsureCaretVisible(); - UpdateCaret(); + OnCaretMove(MOVERIGHT, bShift); + ScrollAllToChar(0); } } break; @@ -2130,54 +2822,69 @@ void CBaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) if (bControl) { ScrollAllToLine(GetLineCount()-GetAllMinScreenLines()); - m_ptCaretPos.y = GetLineCount()-1; - m_ptCaretPos.x = GetLineLength(m_ptCaretPos.y); - UpdateGoalPos(); + POINT ptCaretPos; + ptCaretPos.y = GetLineCount()-1; + ptCaretPos.x = GetLineLength(ptCaretPos.y); + SetCaretAndGoalPosition(ptCaretPos); if (bShift) - AdjustSelection(); + AdjustSelection(MOVERIGHT); else ClearSelection(); - UpdateCaret(); } else { - m_ptCaretPos.x = GetLineLength(m_ptCaretPos.y); - UpdateGoalPos(); - if (bShift) - AdjustSelection(); - else - ClearSelection(); - EnsureCaretVisible(); - UpdateCaret(); + POINT ptCaretPos = GetCaretPosition(); + ptCaretPos.x = GetLineLength(ptCaretPos.y); + if ((GetSubLineOffset(ptCaretPos.y) != -1) && (GetSubLineOffset(ptCaretPos.y) != CountMultiLines(GetViewLineForScreen(ptCaretPos.y))-1)) // not last screen line of view line + { + ptCaretPos.x--; + } + SetCaretAndGoalPosition(ptCaretPos); + OnCaretMove(bShift); } } break; case VK_BACK: + if (IsWritable()) { - if (m_bCaretHidden) - break; - - if (! HasTextSelection()) { - if (m_ptCaretPos.y == 0 && m_ptCaretPos.x == 0) + if (! HasTextSelection()) + { + POINT ptCaretPos = GetCaretPosition(); + if (ptCaretPos.y == 0 && ptCaretPos.x == 0) break; - m_ptSelectionEndPos = m_ptCaretPos; - MoveCaretLeft(); - m_ptSelectionStartPos = m_ptCaretPos; + m_ptSelectionViewPosEnd = GetCaretViewPosition(); + if (bControl) + MoveCaretWordLeft(); + else + { + while (MoveCaretLeft() && IsViewLineEmpty(GetCaretViewPosition().y)) + { + } + } + m_ptSelectionViewPosStart = GetCaretViewPosition(); } RemoveSelectedText(); } break; case VK_DELETE: + if (IsWritable()) { - if (m_bCaretHidden) - break; - - if (! HasTextSelection()) { - if (! MoveCaretRight()) - break; - m_ptSelectionEndPos = m_ptCaretPos; - MoveCaretLeft(); - m_ptSelectionStartPos = m_ptCaretPos; + if (! HasTextSelection()) + { + if (bControl) + { + m_ptSelectionViewPosStart = GetCaretViewPosition(); + MoveCaretWordRight(); + m_ptSelectionViewPosEnd = GetCaretViewPosition(); + } + else + { + if (! MoveCaretRight()) + break; + m_ptSelectionViewPosEnd = GetCaretViewPosition(); + MoveCaretLeft(); + m_ptSelectionViewPosStart = GetCaretViewPosition(); + } } RemoveSelectedText(); } @@ -2188,97 +2895,250 @@ void CBaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) void CBaseView::OnLButtonDown(UINT nFlags, CPoint point) { - int nClickedLine = (((point.y - HEADERHEIGHT) / GetLineHeight()) + m_nTopLine); - nClickedLine--; //we need the index + const int nClickedLine = GetButtonEventLineIndex(point); if ((nClickedLine >= m_nTopLine)&&(nClickedLine < GetLineCount())) { - m_ptCaretPos.y = nClickedLine; - m_ptCaretPos.x = CalculateCharIndex(m_ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()); - UpdateGoalPos(); + POINT ptCaretPos; + ptCaretPos.y = nClickedLine; + ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()); + SetCaretAndGoalPosition(ptCaretPos); if (nFlags & MK_SHIFT) - AdjustSelection(); + AdjustSelection(MOVERIGHT); else { ClearSelection(); - SetupSelection(m_ptCaretPos.y, m_ptCaretPos.y); + SetupAllSelection(ptCaretPos.y, ptCaretPos.y); + if (point.x < GetMarginWidth()) + { + // select the whole line + m_ptSelectionViewPosStart = m_ptSelectionViewPosEnd = GetCaretViewPosition(); + m_ptSelectionViewPosEnd.x = GetViewLineLength(m_ptSelectionViewPosEnd.y); + } } - UpdateCaret(); - + UpdateViewsCaretPosition(); Invalidate(); } CView::OnLButtonDown(nFlags, point); } -void CBaseView::OnEditCopy() +enum ECharGroup { // ordered by priority low-to-hi + CHG_UNKNOWN, + CHG_CONTROL, // x00-x08, x0a-x1f + CHG_WHITESPACE, // space tab + CHG_PUNCTUATION, // 0x21-2f, x3a-x40, x5b-x60, x7b-x7f .,:;!?(){}[]/\<> ... + CHG_WORDLETTER, // alpha num _ (others) +}; + +ECharGroup GetCharGroup(wchar_t zChar) { - if ((m_ptSelectionStartPos.x == m_ptSelectionEndPos.x)&&(m_ptSelectionStartPos.y == m_ptSelectionEndPos.y)) - return; - // first store the selected lines in one CString - CString sCopyData; - for (int i=m_ptSelectionStartPos.y; i<=m_ptSelectionEndPos.y; i++) + if (zChar == ' ' || zChar == '\t' ) { - switch (m_pViewData->GetState(i)) - { - case DIFFSTATE_EMPTY: - break; - case DIFFSTATE_UNKNOWN: - case DIFFSTATE_NORMAL: - case DIFFSTATE_REMOVED: - case DIFFSTATE_REMOVEDWHITESPACE: - case DIFFSTATE_ADDED: - case DIFFSTATE_ADDEDWHITESPACE: - case DIFFSTATE_WHITESPACE: - case DIFFSTATE_WHITESPACE_DIFF: - case DIFFSTATE_CONFLICTED: - case DIFFSTATE_CONFLICTED_IGNORED: - case DIFFSTATE_CONFLICTADDED: - case DIFFSTATE_CONFLICTEMPTY: - case DIFFSTATE_CONFLICTRESOLVED: - case DIFFSTATE_IDENTICALREMOVED: - case DIFFSTATE_IDENTICALADDED: - case DIFFSTATE_THEIRSREMOVED: - case DIFFSTATE_THEIRSADDED: - case DIFFSTATE_YOURSREMOVED: - case DIFFSTATE_YOURSADDED: - case DIFFSTATE_EDITED: - sCopyData += m_pViewData->GetLine(i); - sCopyData += _T("\r\n"); - break; - } + return CHG_WHITESPACE; } - // remove the last \r\n - sCopyData = sCopyData.Left(sCopyData.GetLength()-2); - // remove the non-selected chars from the first line - sCopyData = sCopyData.Mid(m_ptSelectionStartPos.x); - // remove the non-selected chars from the last line - int lastLinePos = sCopyData.ReverseFind('\n'); - lastLinePos += 1; - if (lastLinePos == 0) - lastLinePos -= m_ptSelectionStartPos.x; - sCopyData = sCopyData.Left(lastLinePos+m_ptSelectionEndPos.x); - if (!sCopyData.IsEmpty()) + if (zChar < 0x20) { - CStringUtils::WriteAsciiStringToClipboard(sCopyData, m_hWnd); + return CHG_CONTROL; } + if ((zChar >= 0x21 && zChar <= 0x2f) + || (zChar >= 0x3a && zChar <= 0x40) + || (zChar >= 0x5b && zChar <= 0x5e) + || (zChar == 0x60) + || (zChar >= 0x7b && zChar <= 0x7f)) + { + return CHG_PUNCTUATION; + } + return CHG_WORDLETTER; } -void CBaseView::OnMouseMove(UINT nFlags, CPoint point) +void CBaseView::OnLButtonDblClk(UINT nFlags, CPoint point) { - if (m_pMainFrame->m_nMoveMovesToIgnore > 0) { - --m_pMainFrame->m_nMoveMovesToIgnore; - CView::OnMouseMove(nFlags, point); + if (m_pViewData == 0) { + CView::OnLButtonDblClk(nFlags, point); return; } - int nMouseLine = (((point.y - HEADERHEIGHT) / GetLineHeight()) + m_nTopLine); - nMouseLine--; //we need the index - if (nMouseLine < -1) + const int nClickedLine = GetButtonEventLineIndex(point); + if ( nClickedLine < 0) + return; + int nViewLine = GetViewLineForScreen(nClickedLine); + if (point.x < GetMarginWidth()) // only if double clicked on the margin { - nMouseLine = -1; + if((nViewLine < m_pViewData->GetCount())) // a double click on moved line scrolls to corresponding line + { + if((m_pViewData->GetState(nViewLine)==DIFFSTATE_MOVED_FROM)|| + (m_pViewData->GetState(nViewLine)==DIFFSTATE_MOVED_TO)) + { + int movedindex = m_pViewData->GetMovedIndex(nViewLine); + int screenLine = FindViewLineNumber(movedindex); + int nTop = screenLine - GetScreenLines()/2; + if (nTop < 0) + nTop = 0; + ScrollAllToLine(nTop); + // find and select the whole moved block + int startSel = movedindex; + int endSel = movedindex; + while ((startSel > 0) && ((m_pOtherViewData->GetState(startSel) == DIFFSTATE_MOVED_FROM) || (m_pOtherViewData->GetState(startSel) == DIFFSTATE_MOVED_TO))) + startSel--; + startSel++; + while ((endSel < GetLineCount()) && ((m_pOtherViewData->GetState(endSel) == DIFFSTATE_MOVED_FROM) || (m_pOtherViewData->GetState(endSel) == DIFFSTATE_MOVED_TO))) + endSel++; + endSel--; + m_pOtherView->SetupSelection(startSel, endSel); + return CView::OnLButtonDblClk(nFlags, point); + } + } } + if ((m_pMainFrame->m_bCollapsed)&&(m_pViewData->GetHideState(nViewLine) == HIDESTATE_MARKER)) + { + // a double click on a marker expands the hidden text + int i = nViewLine; + while ((i < m_pViewData->GetCount())&&(m_pViewData->GetHideState(i) != HIDESTATE_SHOWN)) + { + if ((m_pwndLeft)&&(m_pwndLeft->m_pViewData)) + m_pwndLeft->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN); + if ((m_pwndRight)&&(m_pwndRight->m_pViewData)) + m_pwndRight->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN); + if ((m_pwndBottom)&&(m_pwndBottom->m_pViewData)) + m_pwndBottom->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN); + i++; + } + BuildAllScreen2ViewVector(); + if (m_pwndLeft) + m_pwndLeft->Invalidate(); + if (m_pwndRight) + m_pwndRight->Invalidate(); + if (m_pwndBottom) + m_pwndBottom->Invalidate(); + } + else + { + POINT ptCaretPos; + ptCaretPos.y = nClickedLine; + ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()); + SetCaretPosition(ptCaretPos); + ClearSelection(); + + POINT ptViewCarret = GetCaretViewPosition(); + nViewLine = ptViewCarret.y; + if (nViewLine >= GetViewCount()) + return; + CString sLine = GetViewLine(nViewLine); + int nLineLength = sLine.GetLength(); + int nBasePos = ptViewCarret.x; + // get target char group + ECharGroup eLeft = CHG_UNKNOWN; + if (nBasePos > 0) + { + eLeft = GetCharGroup(sLine[nBasePos-1]); + } + ECharGroup eRight = CHG_UNKNOWN; + if (nBasePos < nLineLength) + { + eRight = GetCharGroup(sLine[nBasePos]); + } + ECharGroup eTarget = max(eRight, eLeft); + // find left margin + int nLeft = nBasePos; + while (nLeft > 0 && GetCharGroup(sLine[nLeft-1]) == eTarget) + { + nLeft--; + } + // get right margin + int nRight = nBasePos; + while (nRight < nLineLength && GetCharGroup(sLine[nRight]) == eTarget) + { + nRight++; + } + // set selection + m_ptSelectionViewPosStart.x = nLeft; + m_ptSelectionViewPosStart.y = nViewLine; + m_ptSelectionViewPosEnd.x = nRight; + m_ptSelectionViewPosEnd.y = nViewLine; + m_ptSelectionViewPosOrigin = m_ptSelectionViewPosStart; + SetupAllViewSelection(nViewLine, nViewLine); + // set caret + ptCaretPos = ConvertViewPosToScreen(m_ptSelectionViewPosEnd); + UpdateViewsCaretPosition(); + UpdateGoalPos(); + + // set mark word + m_sPreviousMarkedWord = m_sMarkedWord; // store marked word to recall in case of triple click + int nMarkWidth = max(nRight - nLeft, 0); + m_sMarkedWord = sLine.Mid(m_ptSelectionViewPosStart.x, nMarkWidth).Trim(); + if (m_sMarkedWord.Compare(m_sPreviousMarkedWord) == 0) + { + m_sMarkedWord.Empty(); + } + + if (m_pwndLeft) + m_pwndLeft->SetMarkedWord(m_sMarkedWord); + if (m_pwndRight) + m_pwndRight->SetMarkedWord(m_sMarkedWord); + if (m_pwndBottom) + m_pwndBottom->SetMarkedWord(m_sMarkedWord); + + Invalidate(); + if (m_pwndLocator) + m_pwndLocator->Invalidate(); + } + + CView::OnLButtonDblClk(nFlags, point); +} + +void CBaseView::OnLButtonTrippleClick( UINT /*nFlags*/, CPoint point ) +{ + const int nClickedLine = GetButtonEventLineIndex(point); + if (nClickedLine < 0) + return; + POINT ptCaretPos; + ptCaretPos.y = nClickedLine; + ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()); + SetCaretAndGoalPosition(ptCaretPos); + m_sMarkedWord = m_sPreviousMarkedWord; // recall previous Marked word + if (m_pwndLeft) + m_pwndLeft->SetMarkedWord(m_sMarkedWord); + if (m_pwndRight) + m_pwndRight->SetMarkedWord(m_sMarkedWord); + if (m_pwndBottom) + m_pwndBottom->SetMarkedWord(m_sMarkedWord); + ClearSelection(); + m_ptSelectionViewPosStart.x = 0; + m_ptSelectionViewPosStart.y = nClickedLine; + m_ptSelectionViewPosEnd.x = GetLineLength(nClickedLine); + m_ptSelectionViewPosEnd.y = nClickedLine; + SetupSelection(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y); + UpdateViewsCaretPosition(); + Invalidate(); + if (m_pwndLocator) + m_pwndLocator->Invalidate(); +} + +void CBaseView::OnEditCopy() +{ + CString sCopyData = GetSelectedText(); + + if (!sCopyData.IsEmpty()) + { + CStringUtils::WriteAsciiStringToClipboard(sCopyData, m_hWnd); + } +} + +void CBaseView::OnMouseMove(UINT nFlags, CPoint point) +{ + if (m_pMainFrame->m_nMoveMovesToIgnore > 0) + { + --m_pMainFrame->m_nMoveMovesToIgnore; + CView::OnMouseMove(nFlags, point); + return; + } + int nMouseLine = GetButtonEventLineIndex(point); + if (nMouseLine < -1) + nMouseLine = -1; + m_mouseInMargin = point.x < GetMarginWidth(); + ShowDiffLines(nMouseLine); KillTimer(IDT_SCROLLTIMER); @@ -2286,59 +3146,52 @@ void CBaseView::OnMouseMove(UINT nFlags, CPoint point) { int saveMouseLine = nMouseLine >= 0 ? nMouseLine : 0; saveMouseLine = saveMouseLine < GetLineCount() ? saveMouseLine : GetLineCount() - 1; + if (saveMouseLine < 0) + return; int charIndex = CalculateCharIndex(saveMouseLine, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()); - if (((m_nSelBlockStart >= 0)&&(m_nSelBlockEnd >= 0))&& + if (HasSelection() && ((nMouseLine >= m_nTopLine)&&(nMouseLine < GetLineCount()))) { - m_ptCaretPos.y = nMouseLine; - m_ptCaretPos.x = charIndex; - UpdateGoalPos(); - AdjustSelection(); - UpdateCaret(); + POINT ptCaretPos = {charIndex, nMouseLine}; + SetCaretAndGoalPosition(ptCaretPos); + AdjustSelection(MOVERIGHT); Invalidate(); UpdateWindow(); } if (nMouseLine < m_nTopLine) { - ScrollToLine(m_nTopLine-1, TRUE); + ScrollAllToLine(m_nTopLine-1, TRUE); SetTimer(IDT_SCROLLTIMER, 20, NULL); } - if (nMouseLine >= m_nTopLine + GetScreenLines()) + if (nMouseLine >= m_nTopLine + GetScreenLines() - 2) { - ScrollToLine(m_nTopLine+1, TRUE); + ScrollAllToLine(m_nTopLine+1, TRUE); SetTimer(IDT_SCROLLTIMER, 20, NULL); } - if (charIndex <= m_nOffsetChar) + if (!m_pMainFrame->m_bWrapLines && ((m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()) <= m_nOffsetChar)) { - ScrollSide(-1); + ScrollAllSide(-1); SetTimer(IDT_SCROLLTIMER, 20, NULL); } - if (charIndex >= (GetScreenChars()+m_nOffsetChar)) + if (!m_pMainFrame->m_bWrapLines && (charIndex >= (GetScreenChars()+m_nOffsetChar-4))) { - ScrollSide(1); + ScrollAllSide(1); SetTimer(IDT_SCROLLTIMER, 20, NULL); } + SetCapture(); } - if (!m_bMouseWithin) - { - m_bMouseWithin = TRUE; - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = m_hWnd; - _TrackMouseEvent(&tme); - } CView::OnMouseMove(nFlags, point); } -void CBaseView::OnMouseLeave() +void CBaseView::OnLButtonUp(UINT nFlags, CPoint point) { ShowDiffLines(-1); - m_bMouseWithin = FALSE; + ReleaseCapture(); KillTimer(IDT_SCROLLTIMER); - CView::OnMouseLeave(); + + __super::OnLButtonUp(nFlags, point); } void CBaseView::OnTimer(UINT_PTR nIDEvent) @@ -2348,8 +3201,7 @@ void CBaseView::OnTimer(UINT_PTR nIDEvent) POINT point; GetCursorPos(&point); ScreenToClient(&point); - int nMouseLine = (((point.y - HEADERHEIGHT) / GetLineHeight()) + m_nTopLine); - nMouseLine--; //we need the index + int nMouseLine = GetButtonEventLineIndex(point); if (nMouseLine < -1) { nMouseLine = -1; @@ -2361,22 +3213,22 @@ void CBaseView::OnTimer(UINT_PTR nIDEvent) int charIndex = CalculateCharIndex(saveMouseLine, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()); if (nMouseLine < m_nTopLine) { - ScrollToLine(m_nTopLine-1, TRUE); + ScrollAllToLine(m_nTopLine-1, TRUE); SetTimer(IDT_SCROLLTIMER, 20, NULL); } - if (nMouseLine >= m_nTopLine + GetScreenLines()) + if (nMouseLine >= m_nTopLine + GetScreenLines() - 2) { - ScrollToLine(m_nTopLine+1, TRUE); + ScrollAllToLine(m_nTopLine+1, TRUE); SetTimer(IDT_SCROLLTIMER, 20, NULL); } - if (charIndex <= m_nOffsetChar) + if (!m_pMainFrame->m_bWrapLines && ((m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()) <= m_nOffsetChar)) { - ScrollSide(-1); + ScrollAllSide(-1); SetTimer(IDT_SCROLLTIMER, 20, NULL); } - if (charIndex >= GetScreenChars()) + if (!m_pMainFrame->m_bWrapLines && (charIndex >= (GetScreenChars()+m_nOffsetChar-4))) { - ScrollSide(1); + ScrollAllSide(1); SetTimer(IDT_SCROLLTIMER, 20, NULL); } } @@ -2386,306 +3238,188 @@ void CBaseView::OnTimer(UINT_PTR nIDEvent) CView::OnTimer(nIDEvent); } -void CBaseView::SelectLines(int nLine1, int nLine2) -{ - if (nLine2 == -1) - nLine2 = nLine1; - m_nSelBlockStart = nLine1; - m_nSelBlockEnd = nLine2; - Invalidate(); -} - void CBaseView::ShowDiffLines(int nLine) { - if ((nLine >= m_nTopLine)&&(nLine < GetLineCount())) - { - if ((m_pwndRight)&&(m_pwndRight->m_pViewData)&&(m_pwndLeft)&&(m_pwndLeft->m_pViewData)&&(!m_pMainFrame->m_bOneWay)) - { - nLine = (nLine > m_pwndRight->m_pViewData->GetCount() ? -1 : nLine); - nLine = (nLine > m_pwndLeft->m_pViewData->GetCount() ? -1 : nLine); - - if (nLine >= 0) - { - if (nLine != m_nMouseLine) - { - m_nMouseLine = nLine; - if (nLine >= GetLineCount()) - nLine = -1; - m_pwndLineDiffBar->ShowLines(nLine); - } - } - } - } - else + if ((nLine < m_nTopLine)||(nLine >= GetLineCount())) { m_pwndLineDiffBar->ShowLines(nLine); + nLine = -1; + m_nMouseLine = nLine; + return; } -} -void CBaseView::UseTheirAndYourBlock(viewstate &rightstate, viewstate &bottomstate, viewstate &leftstate) -{ - if ((m_nSelBlockStart == -1)||(m_nSelBlockEnd == -1)) + if ((!m_pwndRight)||(!m_pwndLeft)) + return; + if(m_pMainFrame->m_bOneWay) return; - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - bottomstate.difflines[i] = m_pwndBottom->m_pViewData->GetLine(i); - m_pwndBottom->m_pViewData->SetLine(i, m_pwndLeft->m_pViewData->GetLine(i)); - bottomstate.linestates[i] = m_pwndBottom->m_pViewData->GetState(i); - m_pwndBottom->m_pViewData->SetState(i, m_pwndLeft->m_pViewData->GetState(i)); - m_pwndBottom->m_pViewData->SetLineEnding(i, m_pwndBottom->lineendings); - if (m_pwndBottom->IsLineConflicted(i)) - { - if (m_pwndLeft->m_pViewData->GetState(i) == DIFFSTATE_CONFLICTEMPTY) - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVEDEMPTY); - else - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVED); - } - m_pwndLeft->m_pViewData->SetState(i, DIFFSTATE_YOURSADDED); - } - // your block is done, now insert their block - int index = m_nSelBlockEnd+1; - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - bottomstate.addedlines.push_back(m_nSelBlockEnd+1); - m_pwndBottom->m_pViewData->InsertData(index, m_pwndRight->m_pViewData->GetData(i)); - if (m_pwndBottom->IsLineConflicted(index)) - { - if (m_pwndRight->m_pViewData->GetState(i) == DIFFSTATE_CONFLICTEMPTY) - m_pwndBottom->m_pViewData->SetState(index, DIFFSTATE_CONFLICTRESOLVEDEMPTY); - else - m_pwndBottom->m_pViewData->SetState(index, DIFFSTATE_CONFLICTRESOLVED); - } - m_pwndRight->m_pViewData->SetState(i, DIFFSTATE_THEIRSADDED); - index++; - } - // adjust line numbers - for (int i=m_nSelBlockEnd+1; im_pViewData->GetLineNumber(i); - if (oldline >= 0) - m_pwndBottom->m_pViewData->SetLineNumber(i, oldline+(index-m_nSelBlockEnd)); - } + nLine = (nLine > (int)m_pwndRight->m_Screen2View.size() ? -1 : nLine); + nLine = (nLine > (int)m_pwndLeft->m_Screen2View.size() ? -1 : nLine); + + if (nLine < 0) + return; - // now insert an empty block in both yours and theirs - for (int emptyblocks=0; emptyblocks < m_nSelBlockEnd-m_nSelBlockStart+1; ++emptyblocks) + if (nLine != m_nMouseLine) { - rightstate.addedlines.push_back(m_nSelBlockStart); - m_pwndRight->m_pViewData->InsertData(m_nSelBlockStart, _T(""), DIFFSTATE_EMPTY, -1, EOL_NOENDING); - m_pwndLeft->m_pViewData->InsertData(m_nSelBlockEnd+1, _T(""), DIFFSTATE_EMPTY, -1, EOL_NOENDING); - leftstate.addedlines.push_back(m_nSelBlockEnd+1); + if (nLine >= GetLineCount()) + nLine = -1; + m_nMouseLine = nLine; + m_pwndLineDiffBar->ShowLines(nLine); } - RecalcAllVertScrollBars(); - m_pwndBottom->SetModified(); - m_pwndLeft->SetModified(); - m_pwndRight->SetModified(); + m_pMainFrame->m_nMoveMovesToIgnore = MOVESTOIGNORE; } -void CBaseView::UseYourAndTheirBlock(viewstate &rightstate, viewstate &bottomstate, viewstate &leftstate) +const viewdata& CBaseView::GetEmptyLineData() { - if ((m_nSelBlockStart == -1)||(m_nSelBlockEnd == -1)) - return; - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - bottomstate.difflines[i] = m_pwndBottom->m_pViewData->GetLine(i); - m_pwndBottom->m_pViewData->SetLine(i, m_pwndRight->m_pViewData->GetLine(i)); - bottomstate.linestates[i] = m_pwndBottom->m_pViewData->GetState(i); - m_pwndBottom->m_pViewData->SetState(i, m_pwndRight->m_pViewData->GetState(i)); - rightstate.linestates[i] = m_pwndRight->m_pViewData->GetState(i); - m_pwndBottom->m_pViewData->SetLineEnding(i, m_pwndBottom->lineendings); - if (m_pwndBottom->IsLineConflicted(i)) - { - if (m_pwndRight->m_pViewData->GetState(i) == DIFFSTATE_CONFLICTEMPTY) - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVEDEMPTY); - else - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVED); - } - m_pwndRight->m_pViewData->SetState(i, DIFFSTATE_YOURSADDED); - } - - // your block is done, now insert their block - int index = m_nSelBlockEnd+1; - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - bottomstate.addedlines.push_back(m_nSelBlockEnd+1); - m_pwndBottom->m_pViewData->InsertData(index, m_pwndLeft->m_pViewData->GetData(i)); - leftstate.linestates[i] = m_pwndLeft->m_pViewData->GetState(i); - if (m_pwndBottom->IsLineConflicted(index)) - { - if (m_pwndLeft->m_pViewData->GetState(i) == DIFFSTATE_CONFLICTEMPTY) - m_pwndBottom->m_pViewData->SetState(index, DIFFSTATE_CONFLICTRESOLVEDEMPTY); - else - m_pwndBottom->m_pViewData->SetState(index, DIFFSTATE_CONFLICTRESOLVED); - } - m_pwndLeft->m_pViewData->SetState(i, DIFFSTATE_THEIRSADDED); - index++; - } - // adjust line numbers - for (int i=m_nSelBlockEnd+1; iGetLineCount(); ++i) - { - long oldline = (long)m_pwndBottom->m_pViewData->GetLineNumber(i); - if (oldline >= 0) - m_pwndBottom->m_pViewData->SetLineNumber(i, oldline+(index-m_nSelBlockEnd)); - } + static const viewdata emptyLine(_T(""), DIFFSTATE_EMPTY, -1, EOL_NOENDING, HIDESTATE_SHOWN, -1); + return emptyLine; +} - // now insert an empty block in both yours and theirs - for (int emptyblocks=0; emptyblocks < m_nSelBlockEnd-m_nSelBlockStart+1; ++emptyblocks) +void CBaseView::InsertViewEmptyLines(int nFirstView, int nCount) +{ + for (int i = 0; i < nCount; i++) { - leftstate.addedlines.push_back(m_nSelBlockStart); - m_pwndLeft->m_pViewData->InsertData(m_nSelBlockStart, _T(""), DIFFSTATE_EMPTY, -1, EOL_NOENDING); - m_pwndRight->m_pViewData->InsertData(m_nSelBlockEnd+1, _T(""), DIFFSTATE_EMPTY, -1, EOL_NOENDING); - rightstate.addedlines.push_back(m_nSelBlockEnd+1); + InsertViewData(nFirstView, GetEmptyLineData()); } - - RecalcAllVertScrollBars(); - m_pwndBottom->SetModified(); - m_pwndLeft->SetModified(); - m_pwndRight->SetModified(); } -void CBaseView::UseBothRightFirst(viewstate &rightstate, viewstate &leftstate) + +void CBaseView::UpdateCaret() { - if ((m_nSelBlockStart == -1)||(m_nSelBlockEnd == -1)) - return; - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - rightstate.linestates[i] = m_pwndRight->m_pViewData->GetState(i); - m_pwndRight->m_pViewData->SetState(i, DIFFSTATE_YOURSADDED); - } + POINT ptCaretPos = GetCaretPosition(); + ptCaretPos.y = std::max(std::min(ptCaretPos.y, GetLineCount()-1), 0); + ptCaretPos.x = std::max(std::min(ptCaretPos.x, GetLineLength(ptCaretPos.y)), 0); + SetCaretPosition(ptCaretPos); - // your block is done, now insert their block - int index = m_nSelBlockEnd+1; - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - rightstate.addedlines.push_back(m_nSelBlockEnd+1); - m_pwndRight->m_pViewData->InsertData(index, m_pwndLeft->m_pViewData->GetData(i)); - m_pwndRight->m_pViewData->SetState(index++, DIFFSTATE_THEIRSADDED); - } - // adjust line numbers - index--; - for (int i=m_nSelBlockEnd+1; iGetLineCount(); ++i) + int nCaretOffset = CalculateActualOffset(ptCaretPos); + + if (m_bFocused && + ptCaretPos.y >= m_nTopLine && + ptCaretPos.y < (m_nTopLine+GetScreenLines()) && + nCaretOffset >= m_nOffsetChar && + nCaretOffset < (m_nOffsetChar+GetScreenChars())) { - long oldline = (long)m_pwndRight->m_pViewData->GetLineNumber(i); - if (oldline >= 0) - m_pwndRight->m_pViewData->SetLineNumber(i, oldline+(index-m_nSelBlockEnd)); + CreateSolidCaret(2, GetLineHeight()); + SetCaretPos(TextToClient(ptCaretPos)); + ShowCaret(); } - - // now insert an empty block in the left view - for (int emptyblocks=0; emptyblocks < m_nSelBlockEnd-m_nSelBlockStart+1; ++emptyblocks) + else { - leftstate.addedlines.push_back(m_nSelBlockStart); - m_pwndLeft->m_pViewData->InsertData(m_nSelBlockStart, _T(""), DIFFSTATE_EMPTY, -1, EOL_NOENDING); + HideCaret(); } - RecalcAllVertScrollBars(); - m_pwndLeft->SetModified(); - m_pwndRight->SetModified(); } -void CBaseView::UseBothLeftFirst(viewstate &rightstate, viewstate &leftstate) +POINT CBaseView::ConvertScreenPosToView(const POINT& pt) { - if ((m_nSelBlockStart == -1)||(m_nSelBlockEnd == -1)) - return; - // get line number from just before the block - long linenumber = 0; - if (m_nSelBlockStart > 0) - linenumber = m_pwndRight->m_pViewData->GetLineNumber(m_nSelBlockStart-1); - linenumber++; - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - rightstate.addedlines.push_back(m_nSelBlockStart); - m_pwndRight->m_pViewData->InsertData(i, m_pwndLeft->m_pViewData->GetLine(i), DIFFSTATE_THEIRSADDED, linenumber++, m_pwndLeft->m_pViewData->GetLineEnding(i)); - } - // adjust line numbers - for (int i=m_nSelBlockEnd+1; iGetLineCount(); ++i) - { - long oldline = (long)m_pwndRight->m_pViewData->GetLineNumber(i); - if (oldline >= 0) - m_pwndRight->m_pViewData->SetLineNumber(i, oldline+(m_nSelBlockEnd-m_nSelBlockStart)+1); - } + POINT ptViewPos; + ptViewPos.x = pt.x; - // now insert an empty block in left view - for (int emptyblocks=0; emptyblocks < m_nSelBlockEnd-m_nSelBlockStart+1; ++emptyblocks) + int nSubLine = GetSubLineOffset(pt.y); + if (nSubLine > 0) { - leftstate.addedlines.push_back(m_nSelBlockEnd + 1); - m_pwndLeft->m_pViewData->InsertData(m_nSelBlockEnd + 1, _T(""), DIFFSTATE_EMPTY, -1, EOL_NOENDING); + for (int nScreenLine = pt.y-1; nScreenLine >= pt.y-nSubLine; nScreenLine--) + { + ptViewPos.x += GetLineChars(nScreenLine).GetLength(); + } } - RecalcAllVertScrollBars(); - m_pwndLeft->SetModified(); - m_pwndRight->SetModified(); + + ptViewPos.y = GetViewLineForScreen(pt.y); + return ptViewPos; } -void CBaseView::UpdateCaret() +POINT CBaseView::ConvertViewPosToScreen(const POINT& pt) { - if (m_ptCaretPos.y >= GetLineCount()) - m_ptCaretPos.y = GetLineCount()-1; - if (m_ptCaretPos.y < 0) - m_ptCaretPos.y = 0; - if (m_ptCaretPos.x > GetLineLength(m_ptCaretPos.y)) - m_ptCaretPos.x = GetLineLength(m_ptCaretPos.y); - if (m_ptCaretPos.x < 0) - m_ptCaretPos.x = 0; - - int nCaretOffset = CalculateActualOffset(m_ptCaretPos.y, m_ptCaretPos.x); - - if (m_bFocused && !m_bCaretHidden && - m_ptCaretPos.y >= m_nTopLine && - m_ptCaretPos.y < (m_nTopLine+GetScreenLines()) && - nCaretOffset >= m_nOffsetChar && - nCaretOffset < (m_nOffsetChar+GetScreenChars())) + POINT ptPos; + int nViewLineLenLeft = GetViewLineLength(pt.y); + ptPos.x = min(nViewLineLenLeft, pt.x); + ptPos.y = FindScreenLineForViewLine(pt.y); + if (GetViewLineForScreen(ptPos.y) != pt.y ) { - CreateSolidCaret(2, GetLineHeight()); - SetCaretPos(TextToClient(m_ptCaretPos)); - ShowCaret(); + ptPos.x = 0; } - else + else if (GetSubLineOffset(ptPos.y) >= 0) // sublined { - HideCaret(); + int nSubLineLength = GetLineChars(ptPos.y).GetLength(); + while (nSubLineLength < ptPos.x) + { + ptPos.x -= nSubLineLength; + nViewLineLenLeft -= nSubLineLength; + ptPos.y++; + nSubLineLength = GetLineChars(ptPos.y).GetLength(); + } + // last pos of non last sub-line go to start of next screen line + // Note: while this works correctly, it's not what a user might expect: + // cursor-right when the caret is before the last char of a wrapped line + // now moves the caret to the next line. But users expect the caret to + // move to the right of the last char instead, and with another cursor-right + // keystroke to move the caret to the next line. + // Basically, this would require to handle two caret positions for the same + // logical position in the line string (one on the last position of the first line, + // one on the first position of the new line. For non-wrapped lines this works + // because there's an 'invisible' newline char at the end of the first line. + if (nSubLineLength == ptPos.x && nViewLineLenLeft > nSubLineLength) + { + ptPos.x = 0; + ptPos.y++; + } } + + return ptPos; } + void CBaseView::EnsureCaretVisible() { - int nCaretOffset = CalculateActualOffset(m_ptCaretPos.y, m_ptCaretPos.x); + POINT ptCaretPos = GetCaretPosition(); + int nCaretOffset = CalculateActualOffset(ptCaretPos); - if (m_ptCaretPos.y < m_nTopLine) - ScrollAllToLine(m_ptCaretPos.y); - if (m_ptCaretPos.y >= (m_nTopLine+GetScreenLines())) - ScrollAllToLine(m_ptCaretPos.y-GetScreenLines()+1); - if (nCaretOffset < m_nOffsetChar) - ScrollToChar(nCaretOffset); - if (nCaretOffset > (m_nOffsetChar+GetScreenChars()-1)) - ScrollToChar(nCaretOffset-GetScreenChars()+1); + if (ptCaretPos.y < m_nTopLine) + ScrollAllToLine(ptCaretPos.y); + int screnLines = GetScreenLines(); + if (screnLines) + { + if (ptCaretPos.y >= (m_nTopLine+screnLines)-1) + ScrollAllToLine(ptCaretPos.y-screnLines+2); + if (nCaretOffset < m_nOffsetChar) + ScrollAllToChar(nCaretOffset); + if (nCaretOffset > (m_nOffsetChar+GetScreenChars()-1)) + ScrollAllToChar(nCaretOffset-GetScreenChars()+1); + } } -int CBaseView::CalculateActualOffset(int nLineIndex, int nCharIndex) const +int CBaseView::CalculateActualOffset(const POINT& point) { - int nLength = GetLineLength(nLineIndex); + int nLineIndex = point.y; + int nCharIndex = point.x; ASSERT(nCharIndex >= 0); - if (nCharIndex > nLength) - nCharIndex = nLength; - LPCTSTR pszChars = GetLineChars(nLineIndex); - int nOffset = 0; - int nTabSize = GetTabSize(); - for (int I = 0; I < nCharIndex; I ++) - { - if (pszChars[I] == _T('\t')) - nOffset += (nTabSize - nOffset % nTabSize); - else - nOffset++; - } - return nOffset; + CString sLine = GetLineChars(nLineIndex); + int nLineLength = sLine.GetLength(); + return CountExpandedChars(sLine, min(nCharIndex, nLineLength)); } -int CBaseView::CalculateCharIndex(int nLineIndex, int nActualOffset) const +int CBaseView::CalculateCharIndex(int nLineIndex, int nActualOffset) { int nLength = GetLineLength(nLineIndex); - LPCTSTR pszLine = GetLineChars(nLineIndex); + int nSubLine = GetSubLineOffset(nLineIndex); + if (nSubLine>=0) + { + int nViewLine = GetViewLineForScreen(nLineIndex); + if ((nViewLine>=0)&&(nViewLine < (int)m_ScreenedViewLine.size())) + { + int nMultilineCount = CountMultiLines(nViewLine); + if ((nMultilineCount>0) && (nSubLineSelectObject(GetFont()); // is this right font ? + int nScreenLine = nOffsetScreenLine + m_nTopLine; + CString sLine = GetLineChars(nScreenLine); + ExpandChars(sLine, 0, std::min(pt.x, sLine.GetLength()), sLine); + nLeft += pDC->GetTextExtent(sLine, pt.x).cx; + ReleaseDC(pDC); + } else { + nLeft += pt.x * GetCharWidth(); + } - pt.x = (pt.x - m_nOffsetChar) * GetCharWidth() + GetMarginWidth(); + pt.x = nLeft; pt.y = (pt.y + GetLineHeight() + HEADERHEIGHT); return pt; } @@ -2709,32 +3458,80 @@ void CBaseView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { CView::OnChar(nChar, nRepCnt, nFlags); - if (m_bCaretHidden) + if (IsReadonly()) return; if ((::GetKeyState(VK_LBUTTON) & 0x8000) != 0 || (::GetKeyState(VK_RBUTTON) & 0x8000) != 0) + { + return; + } + + if (!m_pViewData) // no data - nothing to do return; - if ((nChar > 31)||(nChar == VK_TAB)) + if (nChar == VK_F16) + { + // generated by a ctrl+backspace - ignore. + } + else if ((nChar > 31)||(nChar == VK_TAB)) { + ResetUndoStep(); RemoveSelectedText(); - AddUndoLine(m_ptCaretPos.y); - CString sLine = GetLineChars(m_ptCaretPos.y); - sLine.Insert(m_ptCaretPos.x, (wchar_t)nChar); - m_pViewData->SetLine(m_ptCaretPos.y, sLine); - m_pViewData->SetState(m_ptCaretPos.y, DIFFSTATE_EDITED); - if (m_pViewData->GetLineEnding(m_ptCaretPos.y) == EOL_NOENDING && m_pViewData->GetCount() - 1 != m_ptCaretPos.y) - m_pViewData->SetLineEnding(m_ptCaretPos.y, lineendings); - m_ptCaretPos.x++; + POINT ptCaretViewPos = GetCaretViewPosition(); + int nViewLine = ptCaretViewPos.y; + if ((nViewLine==0)&&(GetViewCount()==0)) + OnChar(VK_RETURN, 0, 0); + viewdata lineData = GetViewData(nViewLine); + lineData.sLine.Insert(ptCaretViewPos.x, (wchar_t)nChar); + if (IsStateEmpty(lineData.state)) + { + // if not last line set EOL + for (int nCheckViewLine = nViewLine+1; nCheckViewLine < GetViewCount(); nCheckViewLine++) + { + if (!IsViewLineEmpty(nCheckViewLine)) + { + lineData.ending = lineendings; + break; + } + } + // make sure previous (non empty) line have EOL set + for (int nCheckViewLine = nViewLine-1; nCheckViewLine > 0; nCheckViewLine--) + { + if (!IsViewLineEmpty(nCheckViewLine)) + { + if (GetViewLineEnding(nCheckViewLine) == EOL_NOENDING) + { + SetViewLineEnding(nCheckViewLine, lineendings); + } + break; + } + } + } + lineData.state = DIFFSTATE_EDITED; + bool bNeedRenumber = false; + if (lineData.linenumber == -1) + { + lineData.linenumber = 0; + bNeedRenumber = true; + } + SetViewData(nViewLine, lineData); + SaveUndoStep(); + BuildAllScreen2ViewVector(nViewLine); + if (bNeedRenumber) + { + UpdateViewLineNumbers(); + } + MoveCaretRight(); UpdateGoalPos(); } else if (nChar == 10) { - AddUndoLine(m_ptCaretPos.y); - EOL eol = m_pViewData->GetLineEnding(m_ptCaretPos.y); + int nViewLine = GetViewLineForScreen(GetCaretPosition().y); + EOL eol = m_pViewData->GetLineEnding(nViewLine); EOL newEOL = EOL_CRLF; - switch (eol) { + switch (eol) + { case EOL_CRLF: newEOL = EOL_CR; break; @@ -2742,27 +3539,71 @@ void CBaseView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) newEOL = EOL_LF; break; case EOL_LF: - newEOL = (m_pViewData->GetCount() - 1 == m_ptCaretPos.y && !m_pViewData->GetLine(m_ptCaretPos.y).IsEmpty()) ? EOL_NOENDING : EOL_CRLF; + newEOL = EOL_CRLF; break; } - m_pViewData->SetLineEnding(m_ptCaretPos.y, newEOL); - m_pViewData->SetState(m_ptCaretPos.y, DIFFSTATE_EDITED); + if (eol==EOL_NOENDING || eol==newEOL) + // don't allow to change enter on empty line, or last text line (lines with EOL_NOENDING) + // to add EOL on newly edited empty line hit enter + // don't store into UNDO if no change happened + // and don't mark file as modified + return; + AddUndoViewLine(nViewLine); + m_pViewData->SetLineEnding(nViewLine, newEOL); + m_pViewData->SetState(nViewLine, DIFFSTATE_EDITED); UpdateGoalPos(); } else if (nChar == VK_RETURN) { // insert a new, fresh and empty line below the cursor RemoveSelectedText(); - AddUndoLine(m_ptCaretPos.y, true); - EOL eOriginalEnding = m_pViewData->GetLineEnding(m_ptCaretPos.y); - if (m_pViewData->GetCount() - 2 == m_ptCaretPos.y && eOriginalEnding == EOL_NOENDING) - m_pViewData->SetLineEnding(m_ptCaretPos.y, lineendings); + + CUndo::GetInstance().BeginGrouping(); + + POINT ptCaretViewPos = GetCaretViewPosition(); + int nViewLine = ptCaretViewPos.y; + int nLeft = ptCaretViewPos.x; + CString sLine = GetViewLineChars(nViewLine); + CString sLineLeft = sLine.Left(nLeft); + CString sLineRight = sLine.Right(sLine.GetLength() - nLeft); + EOL eOriginalEnding = EOL_AUTOLINE; + if (m_pViewData->GetCount() > nViewLine) + eOriginalEnding = GetViewLineEnding(nViewLine); + + if (!sLineRight.IsEmpty() || (eOriginalEnding!=lineendings)) + { + viewdata newFirstLine(sLineLeft, DIFFSTATE_EDITED, 1, lineendings, HIDESTATE_SHOWN, -1); + SetViewData(nViewLine, newFirstLine); + } + + int nInsertLine = (m_pViewData->GetCount()==0) ? 0 : nViewLine + 1; + viewdata newLastLine(sLineRight, DIFFSTATE_EDITED, 1, eOriginalEnding, HIDESTATE_SHOWN, -1); + InsertViewData(nInsertLine, newLastLine); + SaveUndoStep(); + + // adds new line everywhere except me + if (IsViewGood(m_pwndLeft) && m_pwndLeft!=this) + { + m_pwndLeft->InsertViewEmptyLines(nInsertLine, 1); + } + if (IsViewGood(m_pwndRight) && m_pwndRight!=this) + { + m_pwndRight->InsertViewEmptyLines(nInsertLine, 1); + } + if (IsViewGood(m_pwndBottom) && m_pwndBottom!=this) + { + m_pwndBottom->InsertViewEmptyLines(nInsertLine, 1); + } + SaveUndoStep(); + + UpdateViewLineNumbers(); + SaveUndoStep(); + CUndo::GetInstance().EndGrouping(); + + BuildAllScreen2ViewVector(); // move the cursor to the new line - m_ptCaretPos.y++; - m_ptCaretPos.x = 0; - if (m_pViewData->GetCount() - 1 == m_ptCaretPos.y) - m_pViewData->SetLineEnding(m_ptCaretPos.y, eOriginalEnding); - UpdateGoalPos(); + ptCaretViewPos = SetupPoint(0, nViewLine+1); + SetCaretAndGoalViewPosition(ptCaretViewPos); } else return; // Unknown control character -- ignore it. @@ -2773,41 +3614,38 @@ void CBaseView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) Invalidate(FALSE); } -void CBaseView::AddUndoLine(int nLine, bool bAddEmptyLine) +void CBaseView::AddUndoViewLine(int nViewLine, bool bAddEmptyLine) { - viewstate leftstate; - viewstate rightstate; - viewstate bottomstate; - leftstate.AddLineFormView(m_pwndLeft, nLine, bAddEmptyLine); - rightstate.AddLineFormView(m_pwndRight, nLine, bAddEmptyLine); - bottomstate.AddLineFormView(m_pwndBottom, nLine, bAddEmptyLine); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); + ResetUndoStep(); + m_AllState.left.AddViewLineFromView(m_pwndLeft, nViewLine, bAddEmptyLine); + m_AllState.right.AddViewLineFromView(m_pwndRight, nViewLine, bAddEmptyLine); + m_AllState.bottom.AddViewLineFromView(m_pwndBottom, nViewLine, bAddEmptyLine); + SaveUndoStep(); + RecalcAllVertScrollBars(); + Invalidate(FALSE); } -void CBaseView::AddEmptyLine(int nLineIndex) +void CBaseView::AddEmptyViewLine(int nViewLineIndex) { if (m_pViewData == NULL) return; - if (!m_bCaretHidden) + int viewLine = nViewLineIndex; + EOL ending = m_pViewData->GetLineEnding(viewLine); + if (ending == EOL_NOENDING) { - CString sPartLine = GetLineChars(nLineIndex); - m_pViewData->SetLine(nLineIndex, sPartLine.Left(m_ptCaretPos.x)); - sPartLine = sPartLine.Mid(m_ptCaretPos.x); - m_pViewData->InsertData(nLineIndex+1, sPartLine, DIFFSTATE_EDITED, -1, lineendings); + ending = lineendings; } - else - m_pViewData->InsertData(nLineIndex+1, _T(""), DIFFSTATE_EDITED, -1, lineendings); - Invalidate(FALSE); -} - -void CBaseView::RemoveLine(int nLineIndex) -{ - if (m_pViewData == NULL) - return; - m_pViewData->RemoveData(nLineIndex); - if (m_ptCaretPos.y >= GetLineCount()) - m_ptCaretPos.y = GetLineCount()-1; - Invalidate(FALSE); + viewdata newLine(_T(""), DIFFSTATE_EDITED, -1, ending, HIDESTATE_SHOWN, -1); + if (IsTarget()) // TODO: once more wievs will writable this is not correct anymore + { + CString sPartLine = GetViewLineChars(nViewLineIndex); + int nPosx = GetCaretPosition().x; // should be view pos ? + m_pViewData->SetLine(viewLine, sPartLine.Left(nPosx)); + sPartLine = sPartLine.Mid(nPosx); + newLine.sLine = sPartLine; + } + m_pViewData->InsertData(viewLine+1, newLine); + BuildAllScreen2ViewVector(); } void CBaseView::RemoveSelectedText() @@ -2817,77 +3655,60 @@ void CBaseView::RemoveSelectedText() if (!HasTextSelection()) return; - viewstate rightstate; - viewstate bottomstate; - viewstate leftstate; - std::vector linestoremove; - for (LONG i = m_ptSelectionStartPos.y; i <= m_ptSelectionEndPos.y; ++i) + // fix selection if starts or ends on empty line + SetCaretViewPosition(m_ptSelectionViewPosEnd); + while (IsViewLineEmpty(GetCaretViewPosition().y) && MoveCaretRight()) { - if (i == m_ptSelectionStartPos.y) - { - CString sLine = GetLineChars(m_ptSelectionStartPos.y); - CString newLine; - if (i == m_ptSelectionStartPos.y) - { - if ((m_pwndLeft)&&(m_pwndLeft->m_pViewData)) - { - leftstate.difflines[i] = m_pwndLeft->m_pViewData->GetLine(i); - leftstate.linestates[i] = m_pwndLeft->m_pViewData->GetState(i); - } - if ((m_pwndRight)&&(m_pwndRight->m_pViewData)) - { - rightstate.difflines[i] = m_pwndRight->m_pViewData->GetLine(i); - rightstate.linestates[i] = m_pwndRight->m_pViewData->GetState(i); - } - if ((m_pwndBottom)&&(m_pwndBottom->m_pViewData)) - { - bottomstate.difflines[i] = m_pwndBottom->m_pViewData->GetLine(i); - bottomstate.linestates[i] = m_pwndBottom->m_pViewData->GetState(i); - } - newLine = sLine.Left(m_ptSelectionStartPos.x); - sLine = GetLineChars(m_ptSelectionEndPos.y); - newLine = newLine + sLine.Mid(m_ptSelectionEndPos.x); - } - m_pViewData->SetLine(i, newLine); - m_pViewData->SetState(i, DIFFSTATE_EDITED); - SetModified(); - } - else - { - if ((m_pwndLeft)&&(m_pwndLeft->m_pViewData)) - { - leftstate.removedlines[i] = m_pwndLeft->m_pViewData->GetData(i); - } - if ((m_pwndRight)&&(m_pwndRight->m_pViewData)) - { - rightstate.removedlines[i] = m_pwndRight->m_pViewData->GetData(i); - } - if ((m_pwndBottom)&&(m_pwndBottom->m_pViewData)) - { - bottomstate.removedlines[i] = m_pwndBottom->m_pViewData->GetData(i); - } - linestoremove.push_back(i); - } } - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - // remove the lines at the end, to avoid problems with line indexes - if (!linestoremove.empty()) + m_ptSelectionViewPosEnd = GetCaretViewPosition(); + SetCaretViewPosition(m_ptSelectionViewPosStart); + while (IsViewLineEmpty(GetCaretViewPosition().y) && MoveCaretRight()) { - std::vector::const_iterator it = linestoremove.begin(); - int nLineToRemove = *it; - for ( ; it != linestoremove.end(); ++it) - { - if (m_pwndLeft) - m_pwndLeft->RemoveLine(nLineToRemove); - if (m_pwndRight) - m_pwndRight->RemoveLine(nLineToRemove); - if (m_pwndBottom) - m_pwndBottom->RemoveLine(nLineToRemove); - SetModified(); - } } - m_ptCaretPos = m_ptSelectionStartPos; - UpdateGoalPos(); + m_ptSelectionViewPosStart = GetCaretViewPosition(); + if (!HasTextSelection()) + { + ClearSelection(); + return; + } + + // We want to undo the insertion in a single step. + ResetUndoStep(); + CUndo::GetInstance().BeginGrouping(); + + // combine first and last line + viewdata oFirstLine = GetViewData(m_ptSelectionViewPosStart.y); + viewdata oLastLine = GetViewData(m_ptSelectionViewPosEnd.y); + oFirstLine.sLine = oFirstLine.sLine.Left(m_ptSelectionViewPosStart.x) + oLastLine.sLine.Mid(m_ptSelectionViewPosEnd.x); + oFirstLine.ending = oLastLine.ending; + oFirstLine.state = DIFFSTATE_EDITED; + SetViewData(m_ptSelectionViewPosStart.y, oFirstLine); + + // clean up middle lines if any + if (m_ptSelectionViewPosStart.y != m_ptSelectionViewPosEnd.y) + { + viewdata oEmptyLine = GetEmptyLineData(); + for (int nViewLine = m_ptSelectionViewPosStart.y+1; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++) + { + SetViewData(nViewLine, oEmptyLine); + } + SaveUndoStep(); + + if (CleanEmptyLines()) + { + BuildAllScreen2ViewVector(); // schedule full rebuild + } + SaveUndoStep(); + UpdateViewLineNumbers(); + } + + SaveUndoStep(); + CUndo::GetInstance().EndGrouping(); + + SetModified(); + BuildAllScreen2ViewVector(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y); + SetCaretViewPosition(m_ptSelectionViewPosStart); + UpdateGoalPos(); ClearSelection(); UpdateCaret(); EnsureCaretVisible(); @@ -2921,162 +3742,300 @@ void CBaseView::PasteText() sClipboardText.Replace(_T("\r\n"), _T("\r")); sClipboardText.Replace('\n', '\r'); - // We want to undo the insertion in a single step. - CUndo::GetInstance().BeginGrouping(); - // use the easy way to insert text: - // insert char by char, using the OnChar() method - for (int i=0; i lines; + int nStart = 0; + int nEolPos = 0; + while ((nEolPos = sClipboardText.Find('\r', nEolPos))>=0) { - OnChar(sClipboardText[i], 0, 0); + CString sLine = sClipboardText.Mid(nStart, nEolPos-nStart); + lines.push_back(sLine); + nEolPos++; + nStart = nEolPos; } - CUndo::GetInstance().EndGrouping(); + CString sLine = sClipboardText.Mid(nStart); + lines.push_back(sLine); + + int nLinesToPaste = (int)lines.size(); + if (nLinesToPaste > 1) + { + // multiline text + + // We want to undo the multiline insertion in a single step. + CUndo::GetInstance().BeginGrouping(); + + sLine = GetViewLineChars(nViewLine); + CString sLineLeft = sLine.Left(nLeft); + CString sLineRight = sLine.Right(sLine.GetLength() - nLeft); + EOL eOriginalEnding = GetViewLineEnding(nViewLine); + viewdata newLine(_T(""), DIFFSTATE_EDITED, 1, lineendings, HIDESTATE_SHOWN, -1); + if (!lines[0].IsEmpty() || !sLineRight.IsEmpty() || (eOriginalEnding!=lineendings)) + { + newLine.sLine = sLineLeft + lines[0]; + SetViewData(nViewLine, newLine); + } + + int nInsertLine = nViewLine; + for (int i = 1; i < nLinesToPaste-1; i++) + { + newLine.sLine = lines[i]; + InsertViewData(++nInsertLine, newLine); + } + newLine.sLine = lines[nLinesToPaste-1] + sLineRight; + newLine.ending = eOriginalEnding; + InsertViewData(++nInsertLine, newLine); + + SaveUndoStep(); + + // adds new lines everywhere except me + if (IsViewGood(m_pwndLeft) && m_pwndLeft!=this) + { + m_pwndLeft->InsertViewEmptyLines(nViewLine+1, nLinesToPaste-1); + } + if (IsViewGood(m_pwndRight) && m_pwndRight!=this) + { + m_pwndRight->InsertViewEmptyLines(nViewLine+1, nLinesToPaste-1); + } + if (IsViewGood(m_pwndBottom) && m_pwndBottom!=this) + { + m_pwndBottom->InsertViewEmptyLines(nViewLine+1, nLinesToPaste-1); + } + SaveUndoStep(); + + UpdateViewLineNumbers(); + CUndo::GetInstance().EndGrouping(); + + ptCaretViewPos = SetupPoint(lines[nLinesToPaste-1].GetLength(), nInsertLine); + } + else + { + // single line text - just insert it + sLine = GetViewLineChars(nViewLine); + sLine.Insert(nLeft, sClipboardText); + ptCaretViewPos = SetupPoint(nLeft + sClipboardText.GetLength(), nViewLine); + SetViewLine(nViewLine, sLine); + SetViewState(nViewLine, DIFFSTATE_EDITED); + SaveUndoStep(); + } + + SetModified(); + RefreshViews(); + BuildAllScreen2ViewVector(); + UpdateCaretViewPosition(ptCaretViewPos); } void CBaseView::OnCaretDown() { - m_ptCaretPos.y++; - m_ptCaretPos.y = min(m_ptCaretPos.y, GetLineCount()-1); - m_ptCaretPos.x = CalculateCharIndex(m_ptCaretPos.y, m_nCaretGoalPos); - if (GetKeyState(VK_SHIFT)&0x8000) - AdjustSelection(); - else - ClearSelection(); - UpdateCaret(); - EnsureCaretVisible(); - ShowDiffLines(m_ptCaretPos.y); + POINT ptCaretPos = GetCaretPosition(); + int nLine = ptCaretPos.y; + int nNextLine = nLine + 1; + if (nNextLine >= GetLineCount()) // already at last line + { + return; + } + + POINT ptCaretViewPos = GetCaretViewPosition(); + int nViewLine = ptCaretViewPos.y; + int nNextViewLine = GetViewLineForScreen(nNextLine); + if (!((nNextViewLine == nViewLine) && (GetSubLineOffset(nNextLine)= GetLineCount()) + { + return; + } + nNextViewLine = GetViewLineForScreen(nNextLine); + } + } + ptCaretPos.y = nNextLine; + ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos); + SetCaretPosition(ptCaretPos); + OnCaretMove(MOVELEFT); + ShowDiffLines(ptCaretPos.y); } bool CBaseView::MoveCaretLeft() { - if (m_ptCaretPos.x == 0) + POINT ptCaretViewPos = GetCaretViewPosition(); + + //int nViewLine = ptCaretViewPos.y; + if (ptCaretViewPos.x == 0) { - if (m_ptCaretPos.y > 0) - { - --m_ptCaretPos.y; - m_ptCaretPos.x = GetLineLength(m_ptCaretPos.y); - } - else - return false; + int nPrevLine = GetCaretPosition().y; + int nPrevViewLine; + do { + nPrevLine--; + if (nPrevLine < 0) + { + return false; + } + nPrevViewLine = GetViewLineForScreen(nPrevLine); + } while ((GetSubLineOffset(nPrevLine) >= CountMultiLines(nPrevViewLine)) || IsViewLineHidden(nPrevViewLine)); + ptCaretViewPos = ConvertScreenPosToView(SetupPoint(GetLineLength(nPrevLine), nPrevLine)); + ShowDiffLines(nPrevLine); } else - --m_ptCaretPos.x; + --ptCaretViewPos.x; - UpdateGoalPos(); + SetCaretAndGoalViewPosition(ptCaretViewPos); return true; } bool CBaseView::MoveCaretRight() { - if (m_ptCaretPos.x >= GetLineLength(m_ptCaretPos.y)) + POINT ptCaretViewPos = GetCaretViewPosition(); + + int nViewLine = ptCaretViewPos.y; + int nViewLineLen = GetViewLineLength(nViewLine); + if (ptCaretViewPos.x >= nViewLineLen) { - if (m_ptCaretPos.y < (GetLineCount() - 1)) - { - ++m_ptCaretPos.y; - m_ptCaretPos.x = 0; - } - else - return false; + int nNextLine = GetCaretPosition().y; + int nNextViewLine; + do { + nNextLine++; + if (nNextLine >= GetLineCount()) + { + return false; + } + nNextViewLine = GetViewLineForScreen(nNextLine); + } while (nNextViewLine == nViewLine || IsViewLineHidden(nNextViewLine)); + ptCaretViewPos.y = nNextViewLine; + ptCaretViewPos.x = 0; + ShowDiffLines(nNextLine); } else - ++m_ptCaretPos.x; + ++ptCaretViewPos.x; - UpdateGoalPos(); + SetCaretAndGoalViewPosition(ptCaretViewPos); return true; } void CBaseView::UpdateGoalPos() { - m_nCaretGoalPos = CalculateActualOffset(m_ptCaretPos.y, m_ptCaretPos.x); + m_nCaretGoalPos = CalculateActualOffset(GetCaretPosition()); } void CBaseView::OnCaretLeft() { MoveCaretLeft(); - if (GetKeyState(VK_SHIFT)&0x8000) - AdjustSelection(); - else - ClearSelection(); - EnsureCaretVisible(); - UpdateCaret(); + OnCaretMove(MOVELEFT); } void CBaseView::OnCaretRight() { MoveCaretRight(); - if (GetKeyState(VK_SHIFT)&0x8000) - AdjustSelection(); - else - ClearSelection(); - EnsureCaretVisible(); - UpdateCaret(); + OnCaretMove(MOVERIGHT); } void CBaseView::OnCaretUp() { - m_ptCaretPos.y--; - m_ptCaretPos.y = max(0, m_ptCaretPos.y); - m_ptCaretPos.x = CalculateCharIndex(m_ptCaretPos.y, m_nCaretGoalPos); - if (GetKeyState(VK_SHIFT)&0x8000) - AdjustSelection(); - else - ClearSelection(); - UpdateCaret(); - EnsureCaretVisible(); - ShowDiffLines(m_ptCaretPos.y); + POINT ptCaretPos = GetCaretPosition(); + int nLine = ptCaretPos.y; + if (nLine <= 0) // already at first line + { + return; + } + int nPrevLine = nLine - 1; + + POINT ptCaretViewPos = GetCaretViewPosition(); + int nViewLine = ptCaretViewPos.y; + int nPrevViewLine = GetViewLineForScreen(nPrevLine); + if (nPrevViewLine != nViewLine) // not on same view line + { + // find previous suitable screen line + while ((GetSubLineOffset(nPrevLine) >= CountMultiLines(nPrevViewLine)) || IsViewLineHidden(nPrevViewLine)) + { + if (nPrevLine <= 0) + { + return; + } + nPrevLine--; + nPrevViewLine = GetViewLineForScreen(nPrevLine); + } + } + ptCaretPos.y = nPrevLine; + ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos); + SetCaretPosition(ptCaretPos); + OnCaretMove(MOVELEFT); + ShowDiffLines(ptCaretPos.y); } -bool CBaseView::IsWordSeparator(wchar_t ch) const +bool CBaseView::IsWordSeparator(const wchar_t ch) const { return ch == ' ' || ch == '\t' || (m_sWordSeparators.Find(ch) >= 0); } -bool CBaseView::IsCaretAtWordBoundary() const +bool CBaseView::IsCaretAtWordBoundary() { - LPCTSTR line = GetLineChars(m_ptCaretPos.y); - if (!*line) + POINT ptViewCaret = GetCaretViewPosition(); + CString line = GetViewLineChars(ptViewCaret.y); + if (line.IsEmpty()) return false; // no boundary at the empty lines - if (m_ptCaretPos.x == 0) - return !IsWordSeparator(line[m_ptCaretPos.x]); - if (m_ptCaretPos.x >= GetLineLength(m_ptCaretPos.y)) - return !IsWordSeparator(line[m_ptCaretPos.x - 1]); + if (ptViewCaret.x == 0) + return !IsWordSeparator(line.GetAt(ptViewCaret.x)); + if (ptViewCaret.x >= GetViewLineLength(ptViewCaret.y)) + return !IsWordSeparator(line.GetAt(ptViewCaret.x - 1)); return - IsWordSeparator(line[m_ptCaretPos.x]) != - IsWordSeparator(line[m_ptCaretPos.x - 1]); + IsWordSeparator(line.GetAt(ptViewCaret.x)) != + IsWordSeparator(line.GetAt(ptViewCaret.x - 1)); +} + +void CBaseView::UpdateViewsCaretPosition() +{ + POINT ptCaretPos = GetCaretPosition(); + if (m_pwndBottom && m_pwndBottom!=this) + m_pwndBottom->UpdateCaretPosition(ptCaretPos); + if (m_pwndLeft && m_pwndLeft!=this) + m_pwndLeft->UpdateCaretPosition(ptCaretPos); + if (m_pwndRight && m_pwndRight!=this) + m_pwndRight->UpdateCaretPosition(ptCaretPos); } void CBaseView::OnCaretWordleft() { + MoveCaretWordLeft(); + OnCaretMove(MOVELEFT); +} + +void CBaseView::OnCaretWordright() +{ + MoveCaretWordRight(); + OnCaretMove(MOVERIGHT); +} + +void CBaseView::MoveCaretWordLeft() +{ while (MoveCaretLeft() && !IsCaretAtWordBoundary()) { } - if (GetKeyState(VK_SHIFT)&0x8000) - AdjustSelection(); - else - ClearSelection(); - EnsureCaretVisible(); - UpdateCaret(); } -void CBaseView::OnCaretWordright() +void CBaseView::MoveCaretWordRight() { while (MoveCaretRight() && !IsCaretAtWordBoundary()) { } - if (GetKeyState(VK_SHIFT)&0x8000) - AdjustSelection(); - else - ClearSelection(); - EnsureCaretVisible(); - UpdateCaret(); } void CBaseView::ClearCurrentSelection() { - m_ptSelectionStartPos = m_ptCaretPos; - m_ptSelectionEndPos = m_ptCaretPos; - m_ptSelectionOrigin = m_ptCaretPos; - m_nSelBlockStart = -1; - m_nSelBlockEnd = -1; + m_ptSelectionViewPosStart = GetCaretViewPosition(); + m_ptSelectionViewPosEnd = m_ptSelectionViewPosStart; + m_ptSelectionViewPosOrigin = m_ptSelectionViewPosStart; + m_nSelViewBlockStart = -1; + m_nSelViewBlockEnd = -1; Invalidate(FALSE); } @@ -3090,30 +4049,34 @@ void CBaseView::ClearSelection() m_pwndBottom->ClearCurrentSelection(); } -void CBaseView::AdjustSelection() +void CBaseView::AdjustSelection(bool bMoveLeft) { - if ((m_ptCaretPos.y < m_ptSelectionOrigin.y) || - (m_ptCaretPos.y == m_ptSelectionOrigin.y && m_ptCaretPos.x <= m_ptSelectionOrigin.x)) + POINT ptCaretViewPos = GetCaretViewPosition(); + if (ArePointsSame(m_ptSelectionViewPosOrigin, SetupPoint(-1, -1))) { - m_ptSelectionStartPos = m_ptCaretPos; - m_ptSelectionEndPos = m_ptSelectionOrigin; + // select all have been used recently update origin + m_ptSelectionViewPosOrigin = bMoveLeft ? m_ptSelectionViewPosEnd : m_ptSelectionViewPosStart; } - - if ((m_ptCaretPos.y > m_ptSelectionOrigin.y) || - (m_ptCaretPos.y == m_ptSelectionOrigin.y && m_ptCaretPos.x >= m_ptSelectionOrigin.x)) + if ((ptCaretViewPos.y < m_ptSelectionViewPosOrigin.y) || + (ptCaretViewPos.y == m_ptSelectionViewPosOrigin.y && ptCaretViewPos.x <= m_ptSelectionViewPosOrigin.x)) + { + m_ptSelectionViewPosStart = ptCaretViewPos; + m_ptSelectionViewPosEnd = m_ptSelectionViewPosOrigin; + } + else { - m_ptSelectionStartPos = m_ptSelectionOrigin; - m_ptSelectionEndPos = m_ptCaretPos; + m_ptSelectionViewPosStart = m_ptSelectionViewPosOrigin; + m_ptSelectionViewPosEnd = ptCaretViewPos; } - SetupSelection(min(m_ptSelectionStartPos.y, m_ptSelectionEndPos.y), max(m_ptSelectionStartPos.y, m_ptSelectionEndPos.y)); + SetupAllViewSelection(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y); Invalidate(FALSE); } void CBaseView::OnEditCut() { - if (!m_bCaretHidden) + if (IsWritable()) { OnEditCopy(); RemoveSelectedText(); @@ -3122,22 +4085,1194 @@ void CBaseView::OnEditCut() void CBaseView::OnEditPaste() { - if (!m_bCaretHidden) + if (IsWritable()) { + CUndo::GetInstance().BeginGrouping(); + RemoveSelectedText(); PasteText(); + CUndo::GetInstance().EndGrouping(); } } -void CBaseView::OnEditSelectall() +void CBaseView::DeleteFonts() +{ + for (int i=0; iDeleteObject(); + delete m_apFonts[i]; + m_apFonts[i] = NULL; + } + } +} + +void CBaseView::OnCaretMove(bool bMoveLeft) +{ + bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000); + OnCaretMove(bMoveLeft, bShift); +} + +void CBaseView::OnCaretMove(bool bMoveLeft, bool isShiftPressed) +{ + if(isShiftPressed) + AdjustSelection(bMoveLeft); + else + ClearSelection(); + EnsureCaretVisible(); + UpdateCaret(); +} + +void CBaseView::AddContextItems(CIconMenu& popup, DiffStates /*state*/) { - int nCount = GetLineCount(); - SetupSelection(0, nCount); - m_ptSelectionStartPos.x = 0; - m_ptSelectionStartPos.y = 0; + AddCutCopyAndPaste(popup); +} - m_ptSelectionEndPos.y = nCount-1; - CString sLine = GetLineChars(nCount-1); - m_ptSelectionEndPos.x = sLine.GetLength(); +void CBaseView::AddCutCopyAndPaste(CIconMenu& popup) +{ + popup.AppendMenu(MF_SEPARATOR, NULL); + CString temp; + temp.LoadString(IDS_EDIT_COPY); + popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_COPY, temp); + if (IsWritable()) + { + temp.LoadString(IDS_EDIT_CUT); + popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_CUT, temp); + temp.LoadString(IDS_EDIT_PASTE); + popup.AppendMenu(MF_STRING | (CAppUtils::HasClipboardFormat(CF_UNICODETEXT)||CAppUtils::HasClipboardFormat(CF_TEXT) ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_PASTE, temp); + } +} - UpdateWindow(); +void CBaseView::CompensateForKeyboard(CPoint& point) +{ + // if the context menu is invoked through the keyboard, we have to use + // a calculated position on where to anchor the menu on + if (ArePointsSame(point, SetupPoint(-1, -1))) + { + CRect rect; + GetWindowRect(&rect); + point = rect.CenterPoint(); + } +} + +HICON CBaseView::LoadIcon(WORD iconId) +{ + HANDLE icon = ::LoadImage( AfxGetResourceHandle(), MAKEINTRESOURCE(iconId), + IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); + return (HICON)icon; +} + +void CBaseView::ReleaseBitmap() +{ + if (m_pCacheBitmap != NULL) + { + m_pCacheBitmap->DeleteObject(); + delete m_pCacheBitmap; + m_pCacheBitmap = NULL; + } +} + +void CBaseView::BuildMarkedWordArray() +{ + int lineCount = GetLineCount(); + m_arMarkedWordLines.clear(); + m_arMarkedWordLines.reserve(lineCount); + bool bDoit = !m_sMarkedWord.IsEmpty(); + for (int i = 0; i < lineCount; ++i) + { + if (bDoit) + { + CString line = GetLineChars(i); + + if (!line.IsEmpty()) + { + m_arMarkedWordLines.push_back(line.Find(m_sMarkedWord) != -1); + } + else + m_arMarkedWordLines.push_back(0); + } + else + m_arMarkedWordLines.push_back(0); + } +} + +void CBaseView::BuildFindStringArray() +{ + int lineCount = GetLineCount(); + m_arFindStringLines.clear(); + m_arFindStringLines.reserve(lineCount); + bool bDoit = !m_sFindText.IsEmpty(); + int s = 0; + int e = 0; + for (int i = 0; i < lineCount; ++i) + { + if (bDoit) + { + CString line = GetLineChars(i); + + if (!line.IsEmpty()) + { + line = line.MakeLower(); + m_arFindStringLines.push_back(StringFound(line, SearchNext, s, e)); + } + else + m_arFindStringLines.push_back(0); + } + else + m_arFindStringLines.push_back(0); + } + UpdateLocator(); +} + +bool CBaseView::GetInlineDiffPositions(int nViewLine, std::vector& positions) +{ + if (!m_bShowInlineDiff) + return false; + if ((m_pwndBottom != NULL) && !(m_pwndBottom->IsHidden())) + return false; + + CString sLine = GetViewLineChars(nViewLine); + if (sLine.IsEmpty()) + return false; + + CheckOtherView(); + if (!m_pOtherViewData) + return false; + + CString sDiffLine = m_pOtherViewData->GetLine(nViewLine); + if (sDiffLine.IsEmpty()) + return false; + + CString sLineExp = ExpandChars(sLine); + CString sDiffLineExp = ExpandChars(sDiffLine); + svn_diff_t * diff = NULL; + m_svnlinediff.Diff(&diff, sLineExp, sLineExp.GetLength(), sDiffLineExp, sDiffLineExp.GetLength(), m_bInlineWordDiff); + if (!diff || !SVNLineDiff::ShowInlineDiff(diff)) + return false; + + size_t lineoffset = 0; + size_t position = 0; + while (diff) + { + apr_off_t len = diff->original_length; + size_t oldpos = position; + + for (apr_off_t i = 0; i < len; ++i) + { + position += m_svnlinediff.m_line1tokens[lineoffset].size(); + lineoffset++; + } + + if (diff->type == svn_diff__type_diff_modified) + { + inlineDiffPos p; + p.start = oldpos; + p.end = position; + positions.push_back(p); + } + + diff = diff->next; + } + + return !positions.empty(); +} + +void CBaseView::OnNavigateNextinlinediff() +{ + int nX; + if (GetNextInlineDiff(nX)) + { + POINT ptCaretViewPos = GetCaretViewPosition(); + ptCaretViewPos.x = nX; + SetCaretAndGoalViewPosition(ptCaretViewPos); + m_ptSelectionViewPosOrigin = ptCaretViewPos; + EnsureCaretVisible(); + } +} + +void CBaseView::OnNavigatePrevinlinediff() +{ + int nX; + if (GetPrevInlineDiff(nX)) + { + POINT ptCaretViewPos = GetCaretViewPosition(); + ptCaretViewPos.x = nX; + SetCaretAndGoalViewPosition(ptCaretViewPos); + m_ptSelectionViewPosOrigin = ptCaretViewPos; + EnsureCaretVisible(); + } +} + +bool CBaseView::HasNextInlineDiff() +{ + int nPos; + return GetNextInlineDiff(nPos); +} + +bool CBaseView::GetNextInlineDiff(int & nPos) +{ + POINT ptCaretViewPos = GetCaretViewPosition(); + std::vector positions; + if (GetInlineDiffPositions(ptCaretViewPos.y, positions)) + { + for (auto it = positions.cbegin(); it != positions.cend(); ++it) + { + if (it->start > ptCaretViewPos.x) + { + nPos = (LONG)it->start; + return true; + } + if (it->end > ptCaretViewPos.x) + { + nPos = (LONG)it->end; + return true; + } + } + } + return false; +} + +bool CBaseView::HasPrevInlineDiff() +{ + int nPos; + return GetPrevInlineDiff(nPos); } + +bool CBaseView::GetPrevInlineDiff(int & nPos) +{ + POINT ptCaretViewPos = GetCaretViewPosition(); + std::vector positions; + if (GetInlineDiffPositions(ptCaretViewPos.y, positions)) + { + for (auto it = positions.crbegin(); it != positions.crend(); ++it) + { + if ( it->end < ptCaretViewPos.x) + { + nPos = (LONG)it->end; + return true; + } + if ( it->start < ptCaretViewPos.x) + { + nPos = (LONG)it->start; + return true; + } + } + } + return false; +} + +CBaseView * CBaseView::GetFirstGoodView() +{ + if (IsViewGood(m_pwndLeft)) + return m_pwndLeft; + if (IsViewGood(m_pwndRight)) + return m_pwndRight; + if (IsViewGood(m_pwndBottom)) + return m_pwndBottom; + return NULL; +} + +void CBaseView::BuildAllScreen2ViewVector() +{ + CBaseView * p_pwndView = GetFirstGoodView(); + if (p_pwndView) + { + m_Screen2View.ScheduleFullRebuild(p_pwndView->m_pViewData); + } +} + +void CBaseView::BuildAllScreen2ViewVector(int nViewLine) +{ + BuildAllScreen2ViewVector(nViewLine, nViewLine); +} + +void CBaseView::BuildAllScreen2ViewVector(int nFirstViewLine, int nLastViewLine) +{ + CBaseView * p_pwndView = GetFirstGoodView(); + if (p_pwndView) + { + m_Screen2View.ScheduleRangeRebuild(p_pwndView->m_pViewData, nFirstViewLine, nLastViewLine); + } +} + +void CBaseView::UpdateViewLineNumbers() +{ + int nLineNumber = 0; + int nViewLineCount = GetViewCount(); + for (int nViewLine = 0; nViewLine < nViewLineCount; nViewLine++) + { + int oldLine = (int)GetViewLineNumber(nViewLine); + if (oldLine >= 0) + SetViewLineNumber(nViewLine, nLineNumber++); + } + m_nDigits = 0; +} + +int CBaseView::CleanEmptyLines() +{ + int nRemovedCount = 0; + int nViewLineCount = GetViewCount(); + bool bCheckLeft = IsViewGood(m_pwndLeft); + bool bCheckRight = IsViewGood(m_pwndRight); + bool bCheckBottom = IsViewGood(m_pwndBottom); + for (int nViewLine = 0; nViewLine < nViewLineCount; ) + { + bool bAllEmpty = true; + bAllEmpty &= !bCheckLeft || IsStateEmpty(m_pwndLeft->GetViewState(nViewLine)); + bAllEmpty &= !bCheckRight || IsStateEmpty(m_pwndRight->GetViewState(nViewLine)); + bAllEmpty &= !bCheckBottom || IsStateEmpty(m_pwndBottom->GetViewState(nViewLine)); + if (bAllEmpty) + { + if (bCheckLeft) + { + m_pwndLeft->RemoveViewData(nViewLine); + } + if (bCheckRight) + { + m_pwndRight->RemoveViewData(nViewLine); + } + if (bCheckBottom) + { + m_pwndBottom->RemoveViewData(nViewLine); + } + if (CUndo::GetInstance().IsGrouping()) // if use group undo -> ensure back adding goes in right (reversed) order + { + SaveUndoStep(); + } + nViewLineCount--; + nRemovedCount++; + continue; + } + nViewLine++; + } + if (nRemovedCount != 0) + { + if (bCheckLeft) + { + m_pwndLeft->SetModified(); + } + if (bCheckRight) + { + m_pwndRight->SetModified(); + } + if (bCheckBottom) + { + m_pwndBottom->SetModified(); + } + } + return nRemovedCount; +} + +int CBaseView::FindScreenLineForViewLine( int viewLine ) +{ + return m_Screen2View.FindScreenLineForViewLine(viewLine); +} + +int CBaseView::CountMultiLines( int nViewLine ) +{ + if (m_ScreenedViewLine.empty()) + return 0; // in case the view is completely empty + + ASSERT(nViewLine < (int)m_ScreenedViewLine.size()); + + if (m_ScreenedViewLine[nViewLine].bSublinesSet) + { + return (int)m_ScreenedViewLine[nViewLine].SubLines.size(); + } + + CString multiline = CStringUtils::WordWrap(m_pViewData->GetLine(nViewLine), GetScreenChars()-1, false, true, GetTabSize()); // GetMultiLine(nLine); + + TScreenedViewLine oScreenedLine; + // tokenize string + int prevpos = 0; + int pos = 0; + while ((pos = multiline.Find('\n', pos)) >= 0) + { + oScreenedLine.SubLines.push_back(multiline.Mid(prevpos, pos-prevpos)); // WordWrap could return vector/list of lines instead of string + pos++; + prevpos = pos; + } + oScreenedLine.SubLines.push_back(multiline.Mid(prevpos)); + oScreenedLine.bSublinesSet = true; + m_ScreenedViewLine[nViewLine] = oScreenedLine; + + return CountMultiLines(nViewLine); +} + +/// prepare inline diff cache +LineColors & CBaseView::GetLineColors(int nViewLine) +{ + ASSERT(nViewLine < (int)m_ScreenedViewLine.size()); + + if (m_bWhitespaceInlineDiffs) + { + if (m_ScreenedViewLine[nViewLine].bLineColorsSetWhiteSpace) + return m_ScreenedViewLine[nViewLine].lineColorsWhiteSpace; + } + else + { + if (m_ScreenedViewLine[nViewLine].bLineColorsSet) + return m_ScreenedViewLine[nViewLine].lineColors; + } + + LineColors oLineColors; + // set main line color + COLORREF crBkgnd, crText; + DiffStates diffState = m_pViewData->GetState(nViewLine); + CDiffColors::GetInstance().GetColors(diffState, crBkgnd, crText); + oLineColors.SetColor(0, crText, crBkgnd); + + do { + if (!m_bShowInlineDiff) + break; + + if ((diffState == DIFFSTATE_NORMAL)&&(!m_bWhitespaceInlineDiffs)) + break; + + CString sLine = GetViewLineChars(nViewLine); + if (sLine.IsEmpty()) + break; + if (!m_pOtherView) + break; + + CString sDiffLine = m_pOtherView->GetViewLineChars(nViewLine); + if (sDiffLine.IsEmpty()) + break; + + svn_diff_t * diff = NULL; + if (sLine.GetLength() > (int)m_nInlineDiffMaxLineLength) + break; + m_svnlinediff.Diff(&diff, sLine, sLine.GetLength(), sDiffLine, sDiffLine.GetLength(), m_bInlineWordDiff); + if (!diff || !SVNLineDiff::ShowInlineDiff(diff) || !diff->next) + break; + + int lineoffset = 0; + int nTextStartOffset = 0; + std::map removedPositions; + while (diff) + { + apr_off_t len = diff->original_length; + + CString s; + for (int i = 0; i < len; ++i) + { + s += m_svnlinediff.m_line1tokens[lineoffset].c_str(); + lineoffset++; + } + bool bInlineDiff = (diff->type == svn_diff__type_diff_modified); + int nTextLength = s.GetLength(); + + CDiffColors::GetInstance().GetColors(diffState, crBkgnd, crText); + if ((m_bShowInlineDiff)&&(bInlineDiff)) + { + crBkgnd = InlineViewLineDiffColor(nViewLine); + } + else + { + crBkgnd = m_ModifiedBk; + } + + if (len < diff->modified_length) + { + removedPositions[nTextStartOffset] = m_InlineRemovedBk; + } + oLineColors.SetColor(nTextStartOffset, crText, crBkgnd); + + nTextStartOffset += nTextLength; + diff = diff->next; + } + for (std::map::const_iterator it = removedPositions.begin(); it != removedPositions.end(); ++it) + { + oLineColors.AddShotColor(it->first, it->second); + } + } while (false); // error catch + + if (!m_bWhitespaceInlineDiffs) + { + m_ScreenedViewLine[nViewLine].lineColors = oLineColors; + m_ScreenedViewLine[nViewLine].bLineColorsSet = true; + } + else + { + m_ScreenedViewLine[nViewLine].lineColorsWhiteSpace = oLineColors; + m_ScreenedViewLine[nViewLine].bLineColorsSetWhiteSpace = true; + } + + return GetLineColors(nViewLine); +} + +void CBaseView::OnEditSelectall() +{ + if (m_pViewData == nullptr) + return; + int nLastViewLine = m_pViewData->GetCount()-1; + if (nLastViewLine < 0) + return; + SetupAllViewSelection(0, nLastViewLine); + + CString sLine = GetViewLineChars(nLastViewLine); + m_ptSelectionViewPosStart = SetupPoint(0, 0); + m_ptSelectionViewPosEnd = SetupPoint(sLine.GetLength(), nLastViewLine); + m_ptSelectionViewPosOrigin = SetupPoint(-1, -1); + + UpdateWindow(); +} + +void CBaseView::FilterWhitespaces(CString& first, CString& second) +{ + FilterWhitespaces(first); + FilterWhitespaces(second); +} + +void CBaseView::FilterWhitespaces(CString& line) +{ + line.Remove(' '); + line.Remove('\t'); + line.Remove('\r'); + line.Remove('\n'); +} + +int CBaseView::GetButtonEventLineIndex(const POINT& point) +{ + const int nLineFromTop = (point.y - HEADERHEIGHT) / GetLineHeight(); + int nEventLine = nLineFromTop + m_nTopLine; + nEventLine--; //we need the index + return nEventLine; +} + + +BOOL CBaseView::PreTranslateMessage(MSG* pMsg) +{ + if (RelayTrippleClick(pMsg)) + return TRUE; + return CView::PreTranslateMessage(pMsg); +} + + +void CBaseView::ResetUndoStep() +{ + m_AllState.Clear(); +} + +void CBaseView::SaveUndoStep() +{ + if (!m_AllState.IsEmpty()) + { + CUndo::GetInstance().AddState(m_AllState, GetCaretViewPosition()); + } + ResetUndoStep(); +} + +void CBaseView::InsertViewData( int index, const CString& sLine, DiffStates state, int linenumber, EOL ending, HIDESTATE hide, int movedline ) +{ + m_pState->addedlines.push_back(index); + m_pViewData->InsertData(index, sLine, state, linenumber, ending, hide, movedline); +} + +void CBaseView::InsertViewData( int index, const viewdata& data ) +{ + m_pState->addedlines.push_back(index); + m_pViewData->InsertData(index, data); +} + +void CBaseView::RemoveViewData( int index ) +{ + m_pState->removedlines[index] = m_pViewData->GetData(index); + m_pViewData->RemoveData(index); +} + +void CBaseView::SetViewData( int index, const viewdata& data ) +{ + m_pState->replacedlines[index] = m_pViewData->GetData(index); + m_pViewData->SetData(index, data); +} + +void CBaseView::SetViewState( int index, DiffStates state ) +{ + m_pState->linestates[index] = m_pViewData->GetState(index); + m_pViewData->SetState(index, state); +} + +void CBaseView::SetViewLine( int index, const CString& sLine ) +{ + m_pState->difflines[index] = m_pViewData->GetLine(index); + m_pViewData->SetLine(index, sLine); +} + +void CBaseView::SetViewLineNumber( int index, int linenumber ) +{ + int oldLineNumber = m_pViewData->GetLineNumber(index); + if (oldLineNumber != linenumber) { + m_pState->linelines[index] = oldLineNumber; + m_pViewData->SetLineNumber(index, linenumber); + } +} + +void CBaseView::SetViewLineEnding( int index, EOL ending ) +{ + m_pState->linesEOL[index] = m_pViewData->GetLineEnding(index); + m_pViewData->SetLineEnding(index, ending); +} + + +BOOL CBaseView::GetViewSelection( int& start, int& end ) const +{ + if (HasSelection()) + { + start = m_nSelViewBlockStart; + end = m_nSelViewBlockEnd; + return true; + } + return false; +} + +int CBaseView::Screen2View::GetViewLineForScreen( int screenLine ) +{ + RebuildIfNecessary(); + if (size() <= screenLine) + return 0; + return m_Screen2View[screenLine].nViewLine; +} + +int CBaseView::Screen2View::size() +{ + RebuildIfNecessary(); + return (int)m_Screen2View.size(); +} + +int CBaseView::Screen2View::GetSubLineOffset( int screenLine ) +{ + RebuildIfNecessary(); + if (size() <= screenLine) + return 0; + return m_Screen2View[screenLine].nViewSubLine; +} + +CBaseView::TScreenLineInfo CBaseView::Screen2View::GetScreenLineInfo( int screenLine ) +{ + RebuildIfNecessary(); + return m_Screen2View[screenLine]; +} + +/** + doing partial rebuild, whole screen2view vector is built, but uses ScreenedViewLine cache to do it faster +*/ +void CBaseView::Screen2View::RebuildIfNecessary() +{ + if (!m_pViewData) + return; // rebuild not necessary + + FixScreenedCacheSize(m_pwndLeft); + FixScreenedCacheSize(m_pwndRight); + FixScreenedCacheSize(m_pwndBottom); + if (!m_bFull) + { + for (auto it = m_RebuildRanges.cbegin(); it != m_RebuildRanges.cend(); ++it) + { + ResetScreenedViewLineCache(m_pwndLeft, *it); + ResetScreenedViewLineCache(m_pwndRight, *it); + ResetScreenedViewLineCache(m_pwndBottom, *it); + } + } + else + { + ResetScreenedViewLineCache(m_pwndLeft); + ResetScreenedViewLineCache(m_pwndRight); + ResetScreenedViewLineCache(m_pwndBottom); + } + m_RebuildRanges.clear(); + m_bFull = false; + + size_t OldSize = m_Screen2View.size(); + m_Screen2View.clear(); + m_Screen2View.reserve(OldSize); // guess same size + for (int i = 0; i < m_pViewData->GetCount(); ++i) + { + if (m_pMainFrame->m_bCollapsed) + { + while ((i < m_pViewData->GetCount())&&(m_pViewData->GetHideState(i) == HIDESTATE_HIDDEN)) + ++i; + if (!(i < m_pViewData->GetCount())) + break; + } + TScreenLineInfo oLineInfo; + oLineInfo.nViewLine = i; + oLineInfo.nViewSubLine = -1; // no wrap + if (m_pMainFrame->m_bWrapLines && !IsViewLineHidden(m_pViewData, i)) + { + int nMaxLines = 0; + if (IsLeftViewGood()) + nMaxLines = std::max(nMaxLines, m_pwndLeft->CountMultiLines(i)); + if (IsRightViewGood()) + nMaxLines = std::max(nMaxLines, m_pwndRight->CountMultiLines(i)); + if (IsBottomViewGood()) + nMaxLines = std::max(nMaxLines, m_pwndBottom->CountMultiLines(i)); + for (int l = 0; l < (nMaxLines-1); ++l) + { + oLineInfo.nViewSubLine++; + m_Screen2View.push_back(oLineInfo); + } + oLineInfo.nViewSubLine++; + } + m_Screen2View.push_back(oLineInfo); + } + m_pViewData = NULL; + + if (IsLeftViewGood()) + m_pwndLeft->BuildMarkedWordArray(); + if (IsRightViewGood()) + m_pwndRight->BuildMarkedWordArray(); + if (IsBottomViewGood()) + m_pwndBottom->BuildMarkedWordArray(); + UpdateLocator(); + RecalcAllVertScrollBars(); + RecalcAllHorzScrollBars(); +} + +int CBaseView::Screen2View::FindScreenLineForViewLine( int viewLine ) +{ + RebuildIfNecessary(); + + int nScreenLineCount = (int)m_Screen2View.size(); + + int nPos = 0; + if (nScreenLineCount>16) + { + // for enough long data search for last screen + // with viewline less than one we are looking for + // use approximate method (based on) binary search using asymmetric start point + // in form 2**n (determined as MSB of length) to go around division and rounding; + // this effectively looks for bit values from MSB to LSB + + int nTestBit; + //GetMostSignificantBitValue + // note _BitScanReverse(&nTestBit, nScreenLineCount); can be used instead + nTestBit = nScreenLineCount; + nTestBit |= nTestBit>>1; + nTestBit |= nTestBit>>2; + nTestBit |= nTestBit>>4; + nTestBit |= nTestBit>>8; + nTestBit |= nTestBit>>16; + nTestBit ^= (nTestBit>>1); + + while (nTestBit) + { + int nTestPos = nPos | nTestBit; + if (nTestPos < nScreenLineCount && m_Screen2View[nTestPos].nViewLine < viewLine) + { + nPos = nTestPos; + } + nTestBit >>= 1; + } + } + while (nPos < nScreenLineCount && m_Screen2View[nPos].nViewLine < viewLine) + { + nPos++; + } + + return nPos; +} + +void CBaseView::Screen2View::ScheduleFullRebuild(CViewData * pViewData) { + m_bFull = true; + + m_pViewData = pViewData; +} + +void CBaseView::Screen2View::ScheduleRangeRebuild(CViewData * pViewData, int nFirstViewLine, int nLastViewLine) +{ + if (m_bFull) + return; + + m_pViewData = pViewData; + + TRebuildRange Range; + Range.FirstViewLine=nFirstViewLine; + Range.LastViewLine=nLastViewLine; + m_RebuildRanges.push_back(Range); +} + +bool CBaseView::Screen2View::FixScreenedCacheSize(CBaseView* pwndView) +{ + if (!IsViewGood(pwndView)) + { + return false; + } + const int nOldSize = (int)pwndView->m_ScreenedViewLine.size(); + const int nViewCount = std::max(pwndView->GetViewCount(), 0); + if (nOldSize == nViewCount) + { + return false; + } + pwndView->m_ScreenedViewLine.resize(nViewCount); + return true; +} + +bool CBaseView::Screen2View::ResetScreenedViewLineCache(CBaseView* pwndView) +{ + if (!IsViewGood(pwndView)) + { + return false; + } + TRebuildRange Range={0, pwndView->GetViewCount()-1}; + ResetScreenedViewLineCache(pwndView, Range); + return true; +} + +bool CBaseView::Screen2View::ResetScreenedViewLineCache(CBaseView* pwndView, const TRebuildRange& Range) +{ + if (!IsViewGood(pwndView)) + { + return false; + } + if (Range.LastViewLine == -1) + { + return false; + } + ASSERT(Range.FirstViewLine >= 0); + ASSERT(Range.LastViewLine < pwndView->GetViewCount()); + for (int i = Range.FirstViewLine; i <= Range.LastViewLine; i++) + { + pwndView->m_ScreenedViewLine[i].Clear(); + } + return false; +} + +void CBaseView::WrapChanged() +{ + m_nMaxLineLength = -1; + m_nOffsetChar = 0; +} + +void CBaseView::OnEditFind() +{ + if (m_pFindDialog) + return; + + m_pFindDialog = new CFindDlg(this); + m_pFindDialog->Create(this); + + m_pFindDialog->SetFindString(HasTextSelection() ? GetSelectedText() : L""); +} + +LRESULT CBaseView::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + ASSERT(m_pFindDialog != NULL); + + if (m_pFindDialog->IsTerminating()) + { + // invalidate the handle identifying the dialog box. + m_pFindDialog = NULL; + return 0; + } + + if(m_pFindDialog->FindNext()) + { + //read data from dialog + m_sFindText = m_pFindDialog->GetFindString(); + m_bMatchCase = (m_pFindDialog->MatchCase() == TRUE); + m_bLimitToDiff = m_pFindDialog->LimitToDiffs(); + m_bWholeWord = m_pFindDialog->WholeWord(); + + if (!m_bMatchCase) + m_sFindText = m_sFindText.MakeLower(); + + BuildFindStringArray(); + OnEditFindnext(); + } + + return 0; +} + +void CBaseView::OnEditFindnextStart() +{ + if (m_pViewData == nullptr) + return; + if (HasTextSelection()) + { + m_sFindText = GetSelectedText(); + m_bMatchCase = false; + m_bLimitToDiff = false; + m_bWholeWord = false; + m_sFindText = m_sFindText.MakeLower(); + + BuildFindStringArray(); + OnEditFindnext(); + } + else + { + m_sFindText.Empty(); + BuildFindStringArray(); + } +} + +void CBaseView::OnEditFindprevStart() +{ + if (m_pViewData == nullptr) + return; + if (HasTextSelection()) + { + m_sFindText = GetSelectedText(); + m_bMatchCase = false; + m_bLimitToDiff = false; + m_bWholeWord = false; + m_sFindText = m_sFindText.MakeLower(); + + BuildFindStringArray(); + OnEditFindprev(); + } + else + { + m_sFindText.Empty(); + BuildFindStringArray(); + } +} + +bool CBaseView::StringFound(const CString& str, SearchDirection srchDir, int& start, int& end) const +{ + start = str.Find(m_sFindText); + if ((srchDir==SearchPrevious)&&(start>=0)) + { + int laststart = start; + do + { + start = laststart; + laststart = str.Find(m_sFindText, laststart+1); + } while (laststart >= 0); + } + end = start + m_sFindText.GetLength(); + bool bStringFound = (start >= 0); + if (bStringFound && m_bWholeWord) + { + if (start) + bStringFound = IsWordSeparator(str.Mid(start-1,1).GetAt(0)); + + if (bStringFound) + { + if (str.GetLength() > end) + bStringFound = IsWordSeparator(str.Mid(end, 1).GetAt(0)); + } + } + return bStringFound; +} + +void CBaseView::OnEditFindprev() +{ + Search(SearchPrevious); +} + +void CBaseView::OnEditFindnext() +{ + Search(SearchNext); +} + +void CBaseView::Search(SearchDirection srchDir) +{ + if (m_sFindText.IsEmpty()) + return; + if(!m_pViewData) + return; + + POINT start = m_ptSelectionViewPosEnd; + POINT end; + end.y = m_pViewData->GetCount()-1; + if (end.y < 0) + return; + + if (srchDir==SearchNext) + end.x = GetViewLineLength(end.y); + else + { + end.x = m_ptSelectionViewPosStart.x; + start.x = 0; + } + + if (!HasTextSelection()) + { + start.y = m_ptCaretViewPos.y; + if (srchDir==SearchNext) + start.x = m_ptCaretViewPos.x; + else + { + start.x = 0; + end.x = m_ptCaretViewPos.x; + } + } + CString sSelectedText; + int startline = -1; + for (int nViewLine=start.y; ;srchDir==SearchNext ? nViewLine++ : nViewLine--) + { + if (nViewLine < 0) + { + nViewLine = m_pViewData->GetCount()-1; + startline = start.y; + } + if (nViewLine > end.y) + { + nViewLine = 0; + startline = start.y; + } + if (startline >= 0) + { + if (nViewLine == startline) + break; + } + switch (m_pViewData->GetState(nViewLine)) + { + case DIFFSTATE_EMPTY: + break; + case DIFFSTATE_UNKNOWN: + case DIFFSTATE_NORMAL: + if (m_bLimitToDiff) + break; + case DIFFSTATE_REMOVED: + case DIFFSTATE_REMOVEDWHITESPACE: + case DIFFSTATE_ADDED: + case DIFFSTATE_ADDEDWHITESPACE: + case DIFFSTATE_WHITESPACE: + case DIFFSTATE_WHITESPACE_DIFF: + case DIFFSTATE_CONFLICTED: + case DIFFSTATE_CONFLICTED_IGNORED: + case DIFFSTATE_CONFLICTADDED: + case DIFFSTATE_CONFLICTEMPTY: + case DIFFSTATE_CONFLICTRESOLVED: + case DIFFSTATE_IDENTICALREMOVED: + case DIFFSTATE_IDENTICALADDED: + case DIFFSTATE_THEIRSREMOVED: + case DIFFSTATE_THEIRSADDED: + case DIFFSTATE_MOVED_FROM: + case DIFFSTATE_MOVED_TO: + case DIFFSTATE_YOURSREMOVED: + case DIFFSTATE_YOURSADDED: + case DIFFSTATE_EDITED: + { + sSelectedText = GetViewLineChars(nViewLine); + if (nViewLine==start.y) + sSelectedText = srchDir==SearchNext ? sSelectedText.Mid(start.x) : sSelectedText.Left(start.x); + if (!m_bMatchCase) + sSelectedText = sSelectedText.MakeLower(); + int startfound = -1; + int endfound = -1; + if (StringFound(sSelectedText, srchDir, startfound, endfound)) + { + HighlightViewLines(nViewLine, nViewLine); + m_ptSelectionViewPosStart.x = startfound; + m_ptSelectionViewPosEnd.x = endfound; + if (nViewLine==start.y) + { + m_ptSelectionViewPosStart.x += start.x; + m_ptSelectionViewPosEnd.x += start.x; + } + m_ptSelectionViewPosEnd.x = m_ptSelectionViewPosStart.x + m_sFindText.GetLength(); + m_ptSelectionViewPosStart.y = nViewLine; + m_ptSelectionViewPosEnd.y = nViewLine; + m_ptCaretViewPos = m_ptSelectionViewPosStart; + UpdateViewsCaretPosition(); + EnsureCaretVisible(); + Invalidate(); + return; + } + } + break; + } + } + m_pMainFrame->m_nMoveMovesToIgnore = MOVESTOIGNORE; +} + +CString CBaseView::GetSelectedText() const +{ + CString sSelectedText; + POINT start = m_ptSelectionViewPosStart; + POINT end = m_ptSelectionViewPosEnd; + if (!HasTextSelection()) + { + if (!HasSelection()) + return sSelectedText; + start.y = m_nSelViewBlockStart; + start.x = 0; + end.y = m_nSelViewBlockEnd; + end.x = GetViewLineLength(m_nSelViewBlockEnd); + } + // first store the selected lines in one CString + for (int nViewLine=start.y; nViewLine<=end.y; nViewLine++) + { + switch (m_pViewData->GetState(nViewLine)) + { + case DIFFSTATE_EMPTY: + break; + case DIFFSTATE_UNKNOWN: + case DIFFSTATE_NORMAL: + case DIFFSTATE_REMOVED: + case DIFFSTATE_REMOVEDWHITESPACE: + case DIFFSTATE_ADDED: + case DIFFSTATE_ADDEDWHITESPACE: + case DIFFSTATE_WHITESPACE: + case DIFFSTATE_WHITESPACE_DIFF: + case DIFFSTATE_CONFLICTED: + case DIFFSTATE_CONFLICTED_IGNORED: + case DIFFSTATE_CONFLICTADDED: + case DIFFSTATE_CONFLICTEMPTY: + case DIFFSTATE_CONFLICTRESOLVED: + case DIFFSTATE_IDENTICALREMOVED: + case DIFFSTATE_IDENTICALADDED: + case DIFFSTATE_THEIRSREMOVED: + case DIFFSTATE_THEIRSADDED: + case DIFFSTATE_MOVED_FROM: + case DIFFSTATE_MOVED_TO: + case DIFFSTATE_YOURSREMOVED: + case DIFFSTATE_YOURSADDED: + case DIFFSTATE_EDITED: + sSelectedText += GetViewLineChars(nViewLine); + sSelectedText += _T("\r\n"); + break; + } + } + // remove the non-selected chars from the first line, last line and last \r\n + int nLeftCut = start.x; + int nRightCut = GetViewLineChars(end.y).GetLength() - end.x + 2; + sSelectedText = sSelectedText.Mid(nLeftCut, sSelectedText.GetLength()-nLeftCut-nRightCut); + return sSelectedText; +} + +void CBaseView::OnEditGotoline() +{ + if (m_pViewData == NULL) + return; + // find the last and first line number + int nViewLineCount = m_pViewData->GetCount(); + + int nLastLineNumber = DIFF_EMPTYLINENUMBER; + for (int nViewLine=nViewLineCount-1; nViewLine>=0; --nViewLine) + { + nLastLineNumber = m_pViewData->GetLineNumber(nViewLine); + if (nLastLineNumber!=DIFF_EMPTYLINENUMBER) + { + break; + } + } + if (nLastLineNumber==DIFF_EMPTYLINENUMBER || nLastLineNumber==0) // not numbered line foud or last one is first + { + return; + } + nLastLineNumber++; + int nFirstLineNumber=1; // first is always 1 + + CString sText; + sText.Format(IDS_GOTOLINE, nFirstLineNumber, nLastLineNumber); + + CGotoLineDlg dlg(this); + dlg.SetLabel(sText); + dlg.SetLimits(nFirstLineNumber, nLastLineNumber); + if (dlg.DoModal() == IDOK) + { + for (int nViewLine = 0; nViewLine < nViewLineCount; ++nViewLine) + { + if ((m_pViewData->GetLineNumber(nViewLine)+1) == dlg.GetLineNumber()) + { + HighlightViewLines(nViewLine, nViewLine); + return; + } + } + } +} + diff --git a/src/TortoiseMerge/BaseView.h b/src/TortoiseMerge/BaseView.h index 8dd238d2f..d1ce6b34a 100644 --- a/src/TortoiseMerge/BaseView.h +++ b/src/TortoiseMerge/BaseView.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2003-2008 - TortoiseSVN +// Copyright (C) 2003-2012 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -22,6 +22,17 @@ #include "ScrollTool.h" #include "Undo.h" #include "LocatorBar.h" +#include "LineColors.h" +#include "TripleClick.h" +#include "IconMenu.h" +#include "FindDlg.h" + +typedef struct inlineDiffPos +{ + apr_off_t start; + apr_off_t end; +} inlineDiffPos; + /** * \ingroup TortoiseMerge @@ -30,7 +41,7 @@ * showing diffs. Has three parent classes which inherit * from this base class: CLeftView, CRightView and CBottomView. */ -class CBaseView : public CView +class CBaseView : public CView, public CTripleClick { DECLARE_DYNCREATE(CBaseView) friend class CLineDiffBar; @@ -38,12 +49,12 @@ public: CBaseView(); virtual ~CBaseView(); -public: +public: // methods /** * Indicates that the underlying document has been updated. Reloads all * data and redraws the view. */ - virtual void DocumentUpdated(); + virtual void DocumentUpdated(); /** * Returns the number of lines visible on the view. */ @@ -56,33 +67,142 @@ public: void ScrollToLine(int nNewTopLine, BOOL bTrackScrollBar = TRUE); void ScrollAllToLine(int nNewTopLine, BOOL bTrackScrollBar = TRUE); void ScrollSide(int delta); + void ScrollAllSide(int delta); + void ScrollVertical(short delta); + static void RecalcAllVertScrollBars(BOOL bPositionOnly = FALSE); + static void RecalcAllHorzScrollBars(BOOL bPositionOnly = FALSE); void GoToLine(int nNewLine, BOOL bAll = TRUE); void ScrollToChar(int nNewOffsetChar, BOOL bTrackScrollBar = TRUE); - void UseCaret(bool bUse = true) {m_bCaretHidden = !bUse;} - bool HasCaret() {return !m_bCaretHidden;} - void SetCaretPosition(POINT pt) {m_ptCaretPos = pt ; m_nCaretGoalPos = pt.x; UpdateCaret();} + void ScrollAllToChar(int nNewOffsetChar, BOOL bTrackScrollBar = TRUE); + void SetReadonly(bool bReadonly = true) {m_bReadonly = bReadonly;} + void SetWritable(bool bWritable = true) {m_bReadonly = !bWritable;} + void SetTarget(bool bTarget = true) {m_bTarget = bTarget;} + bool IsReadonly() const {return m_bReadonly;} + bool IsWritable() const {return !m_bReadonly && m_pViewData;} + bool IsTarget() const {return m_bTarget;} + void SetCaretAndGoalPosition(const POINT& pt) {UpdateCaretPosition(pt); UpdateGoalPos(); } + void SetCaretAndGoalViewPosition(const POINT& pt) {UpdateCaretViewPosition(pt); UpdateGoalPos(); } + void SetCaretPosition(const POINT& pt) { SetCaretViewPosition(ConvertScreenPosToView(pt)); } + POINT GetCaretPosition() { return ConvertViewPosToScreen(GetCaretViewPosition()); } + void SetCaretViewPosition(const POINT & pt) { m_ptCaretViewPos = pt; } + POINT GetCaretViewPosition() { return m_ptCaretViewPos; } + void UpdateCaretPosition(const POINT& pt) { SetCaretPosition(pt); UpdateCaret(); } + void UpdateCaretViewPosition(const POINT& pt) { SetCaretViewPosition(pt); UpdateCaret(); EnsureCaretVisible(); } + void SetCaretToViewStart() { SetCaretToFirstViewLine(); SetCaretToViewLineStart(); } + void SetCaretToFirstViewLine() { m_ptCaretViewPos.y=0; } + void SetCaretToViewLineStart() { m_ptCaretViewPos.x=0; } + void SetCaretToLineStart() { SetCaretAndGoalPosition(SetupPoint(0, GetCaretPosition().y)); } void EnsureCaretVisible(); void UpdateCaret(); - void ClearSelection(); - void RefreshViews(); - void SelectLines(int nLine1, int nLine2 = -1); - void HiglightLines(int start, int end = -1); + bool ArePointsSame(const POINT &pt1, const POINT &pt2) {return (pt1.x == pt2.x) && (pt1.y == pt2.y); }; + POINT SetupPoint(int x, int y) {POINT ptRet={x, y}; return ptRet; }; + POINT ConvertScreenPosToView(const POINT& pt); + POINT ConvertViewPosToScreen(const POINT& pt); + + void RefreshViews(); + static void BuildAllScreen2ViewVector(); ///< schedule full screen2view rebuild + static void BuildAllScreen2ViewVector(int ViewLine); ///< schedule rebuild screen2view for single line + static void BuildAllScreen2ViewVector(int FirstViewLine, int LastViewLine); ///< schedule rebuild screen2view for line range (first and last inclusive) + void UpdateViewLineNumbers(); + int CleanEmptyLines(); ///< remove line empty in all views + int GetLineCount() const; + static int GetViewLineForScreen(int screenLine) { return m_Screen2View.GetViewLineForScreen(screenLine); } + int FindScreenLineForViewLine(int viewLine); + // TODO: find better consistent names for Multiline(line with sublines) and Subline, Count.. or Get..Count ? + int CountMultiLines(int nViewLine); + int GetSubLineOffset(int index); + LineColors & GetLineColors(int nViewLine); + static void UpdateLocator() { if (m_pwndLocator) m_pwndLocator->DocumentUpdated(); } + void WrapChanged(); + + void HighlightLines(int start, int end = -1); + void HighlightViewLines(int start, int end = -1); inline BOOL IsHidden() const {return m_bIsHidden;} inline void SetHidden(BOOL bHidden) {m_bIsHidden = bHidden;} - inline BOOL IsModified() const {return m_bModified;} - void SetModified(BOOL bModified = TRUE) {m_bModified = bModified;} - BOOL HasSelection() {return (!((m_nSelBlockEnd < 0)||(m_nSelBlockStart < 0)||(m_nSelBlockStart > m_nSelBlockEnd)));} - BOOL HasTextSelection() {return ((m_ptSelectionStartPos.x != m_ptSelectionEndPos.x)||(m_ptSelectionStartPos.y != m_ptSelectionEndPos.y));} - BOOL GetSelection(int& start, int& end) {start=m_nSelBlockStart; end=m_nSelBlockEnd; return HasSelection();} + inline bool IsModified() const {return m_bModified;} + void SetModified(bool bModified = true) {m_bModified = bModified; Invalidate();} void SetInlineWordDiff(bool bWord) {m_bInlineWordDiff = bWord;} - - BOOL IsLineRemoved(int nLineIndex); + void SetInlineDiff(bool bDiff) {m_bShowInlineDiff = bDiff;} + void SetMarkedWord(const CString& word) {m_sMarkedWord = word; BuildMarkedWordArray();} + LPCTSTR GetMarkedWord() {return (LPCTSTR)m_sMarkedWord;} + LPCTSTR GetFindString() {return (LPCTSTR)m_sFindText;} + + // Selection methods; all public methods dealing with selection go here + static void ClearSelection(); + BOOL GetViewSelection(int& start, int& end) const; + BOOL HasSelection() const { return (!((m_nSelViewBlockEnd < 0)||(m_nSelViewBlockStart < 0)||(m_nSelViewBlockStart > m_nSelViewBlockEnd))); } + BOOL HasTextSelection() const { return ((m_ptSelectionViewPosStart.x != m_ptSelectionViewPosEnd.x) || (m_ptSelectionViewPosStart.y != m_ptSelectionViewPosEnd.y)); } + static void SetupAllViewSelection(int start, int end); + static void SetupAllSelection(int start, int end); + void SetupSelection(int start, int end); + static void SetupViewSelection(CBaseView* view, int start, int end); + void SetupViewSelection(int start, int end); + CString GetSelectedText() const; + + // state classifying methods; note: state may belong to more classes + static bool IsStateConflicted(DiffStates state); + static bool IsStateEmpty(DiffStates state); + static bool IsStateRemoved(DiffStates state); + static DiffStates ResolveState(DiffStates state); + + bool IsLineEmpty(int nLineIndex); + bool IsViewLineEmpty(int nViewLine); + bool IsLineRemoved(int nLineIndex); + bool IsViewLineRemoved(int nViewLine); bool IsBlockWhitespaceOnly(int nLineIndex, bool& bIdentical); - bool IsLineConflicted(int nLineIndex); - + bool IsViewLineConflicted(int nLineIndex); + bool HasNextConflict(); + bool HasPrevConflict(); + bool HasNextDiff(); + bool HasPrevDiff(); + bool GetNextInlineDiff(int & nPos); + bool GetPrevInlineDiff(int & nPos); + bool HasNextInlineDiff(); + bool HasPrevInlineDiff(); + + static const viewdata& GetEmptyLineData(); + void InsertViewEmptyLines(int nFirstView, int nCount); + + virtual void UseBothLeftFirst() {return UseBothBlocks(m_pwndLeft, m_pwndRight); } + virtual void UseBothRightFirst() {return UseBothBlocks(m_pwndRight, m_pwndLeft); } + void UseTheirAndYourBlock() {return UseBothLeftFirst(); } ///< ! for backward compatibility + void UseYourAndTheirBlock() {return UseBothRightFirst(); } ///< ! for backward compatibility + + virtual void UseLeftBlock() {return UseViewBlock(m_pwndLeft); } + virtual void UseLeftFile() {return UseViewFile(m_pwndLeft); } + virtual void UseRightBlock() {return UseViewBlock(m_pwndRight); } + virtual void UseRightFile() {return UseViewFile(m_pwndRight); } + + // ViewData methods + void InsertViewData(int index, const CString& sLine, DiffStates state, int linenumber, EOL ending, HIDESTATE hide, int movedline); + void InsertViewData(int index, const viewdata& data); + void RemoveViewData(int index); + + const viewdata& GetViewData(int index) const {return m_pViewData->GetData(index); } + const CString& GetViewLine(int index) const {return m_pViewData->GetLine(index); } + DiffStates GetViewState(int index) const {return m_pViewData->GetState(index); } + HIDESTATE GetViewHideState(int index) {return m_pViewData->GetHideState(index); } + int GetViewLineNumber(int index) {return m_pViewData->GetLineNumber(index); } + int GetViewMovedIndex(int index) {return m_pViewData->GetMovedIndex(index); } + int FindViewLineNumber(int number) {return m_pViewData->FindLineNumber(number); } + EOL GetViewLineEnding(int index) const {return m_pViewData->GetLineEnding(index); } + + int GetViewCount() const {return m_pViewData ? m_pViewData->GetCount() : -1; } + + void SetViewData(int index, const viewdata& data); + void SetViewState(int index, DiffStates state); + void SetViewLine(int index, const CString& sLine); + void SetViewLineNumber(int index, int linenumber); + void SetViewLineEnding(int index, EOL ending); + + static bool IsViewGood(const CBaseView* view ) { return (view != 0) && view->IsWindowVisible(); } + static CBaseView * GetFirstGoodView(); + +public: // variables CViewData * m_pViewData; CViewData * m_pOtherViewData; + CBaseView * m_pOtherView; CString m_sWindowName; ///< The name of the view which is shown as a window title to the user CString m_sFullFilePath; ///< The full path of the file shown @@ -92,7 +212,10 @@ public: BOOL m_bViewWhitespace; ///< If TRUE, then SPACE and TAB are shown as special characters BOOL m_bShowInlineDiff; ///< If TRUE, diffs in lines are marked colored bool m_bShowSelection; ///< If true, selection bars are shown and selected text darkened + bool m_bWhitespaceInlineDiffs; ///< if true, inline diffs are shown for identical lines only differing in whitespace int m_nTopLine; ///< The topmost text line in the view + std::vector m_arMarkedWordLines; ///< which lines contain a marked word + std::vector m_arFindStringLines; ///< which lines contain a found string static CLocatorBar * m_pwndLocator; ///< Pointer to the locator bar on the left static CLineDiffBar * m_pwndLineDiffBar; ///< Pointer to the line diff bar at the bottom @@ -100,19 +223,28 @@ public: static CMainFrame * m_pMainFrame; ///< Pointer to the mainframe void GoToFirstDifference(); - void AddEmptyLine(int nLineIndex); -protected: + void GoToFirstConflict(); + void AddEmptyViewLine(int nLineIndex); + +protected: // methods + enum { + MOVERIGHT =0, + MOVELEFT = 1, + }; + virtual BOOL PreCreateWindow(CREATESTRUCT& cs); virtual void OnDraw(CDC * pDC); - virtual INT_PTR OnToolHitTest(CPoint point, TOOLINFO* pTI) const; + virtual INT_PTR OnToolHitTest(CPoint point, TOOLINFO* pTI) const; + virtual BOOL PreTranslateMessage(MSG* pMsg); BOOL OnToolTipNotify(UINT id, NMHDR *pNMHDR, LRESULT *pResult); afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); afx_msg BOOL OnEraseBkgnd(CDC* pDC); - afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); + afx_msg void OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt); afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); afx_msg void OnKillFocus(CWnd* pNewWnd); afx_msg void OnSetFocus(CWnd* pOldWnd); @@ -121,12 +253,16 @@ protected: afx_msg void OnMergePreviousdifference(); afx_msg void OnMergePreviousconflict(); afx_msg void OnMergeNextconflict(); + afx_msg void OnNavigateNextinlinediff(); + afx_msg void OnNavigatePrevinlinediff(); afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnLButtonUp(UINT nFlags, CPoint point); + afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point); + virtual void OnLButtonTrippleClick(UINT nFlags, CPoint point) override; afx_msg void OnEditCopy(); afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg void OnTimer(UINT_PTR nIDEvent); - afx_msg void OnMouseLeave(); afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg void OnCaretDown(); afx_msg void OnCaretLeft(); @@ -137,14 +273,19 @@ protected: afx_msg void OnEditCut(); afx_msg void OnEditPaste(); afx_msg void OnEditSelectall(); + afx_msg LRESULT OnFindDialogMessage(WPARAM wParam, LPARAM lParam); + afx_msg void OnEditFind(); + afx_msg void OnEditFindnext(); + afx_msg void OnEditFindprev(); + afx_msg void OnEditFindnextStart(); + afx_msg void OnEditFindprevStart(); + afx_msg void OnEditGotoline(); DECLARE_MESSAGE_MAP() -protected: void DrawHeader(CDC *pdc, const CRect &rect); void DrawMargin(CDC *pdc, const CRect &rect, int nLineIndex); void DrawSingleLine(CDC *pDC, const CRect &rc, int nLineIndex); - bool DrawInlineDiff(CDC *pDC, const CRect &rc, int nLineIndex, const CString &line, CPoint &origin); /** * Draws the horizontal lines around current diff block or selection block. */ @@ -153,81 +294,107 @@ protected: * Draws the line ending 'char'. */ void DrawLineEnding(CDC *pDC, const CRect &rc, int nLineIndex, const CPoint& origin); - void ExpandChars(LPCTSTR pszChars, int nOffset, int nCount, CString &line); + void ExpandChars(const CString &sLine, int nOffset, int nCount, CString &line); + CString ExpandChars(const CString &sLine, int nOffset = 0); + int CountExpandedChars(const CString &sLine, int nLength); void RecalcVertScrollBar(BOOL bPositionOnly = FALSE); - void RecalcAllVertScrollBars(BOOL bPositionOnly = FALSE); void RecalcHorzScrollBar(BOOL bPositionOnly = FALSE); - void RecalcAllHorzScrollBars(BOOL bPositionOnly = FALSE); void OnDoMouseWheel(UINT nFlags, short zDelta, CPoint pt); + void OnDoMouseHWheel(UINT nFlags, short zDelta, CPoint pt); void OnDoHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar, CBaseView * master); void OnDoVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar, CBaseView * master); - void SetupSelection(int start, int end); void ShowDiffLines(int nLine); int GetTabSize() const {return m_nTabSize;} + void DeleteFonts(); - int GetLineActualLength(int index) const; - int GetLineCount() const; void CalcLineCharDim(); int GetLineHeight(); int GetCharWidth(); int GetMaxLineLength(); - int GetLineLength(int index) const; + int GetLineLength(int index); + int GetViewLineLength(int index) const; int GetScreenChars(); int GetAllMinScreenChars() const; int GetAllMaxLineLength() const; int GetAllLineCount() const; int GetAllMinScreenLines() const; - LPCTSTR GetLineChars(int index) const; + CString GetViewLineChars(int index) const; + CString GetLineChars(int index); int GetLineNumber(int index) const; - CFont * GetFont(BOOL bItalic = FALSE, BOOL bBold = FALSE, BOOL bStrikeOut = FALSE); + CFont * GetFont(BOOL bItalic = FALSE, BOOL bBold = FALSE); int GetLineFromPoint(CPoint point); int GetMarginWidth(); - COLORREF IntenseColor(long scale, COLORREF col); COLORREF InlineDiffColor(int nLineIndex); + COLORREF InlineViewLineDiffColor(int nLineIndex); + bool GetInlineDiffPositions(int lineIndex, std::vector& positions); void CheckOtherView(); - static CString GetWhitespaceBlock(CViewData *viewData, int nLineIndex); + static void GetWhitespaceBlock(CViewData *viewData, int nLineIndex, int & nStartBlock, int & nEndBlock); + static CString GetWhitespaceString(CViewData *viewData, int nStartBlock, int nEndBlock); + bool IsViewLineHidden(int nViewLine); + static bool IsViewLineHidden(CViewData * pViewData, int nViewLine); - /// Returns true if selection should be kept - virtual bool OnContextMenu(CPoint point, int nLine, DiffStates state); + void OnContextMenu(CPoint point, DiffStates state); /** * Updates the status bar pane. Call this if the document changed. */ void UpdateStatusBar(); - void UseTheirAndYourBlock(viewstate &rightstate, viewstate &bottomstate, viewstate &leftstate); - void UseYourAndTheirBlock(viewstate &rightstate, viewstate &bottomstate, viewstate &leftstate); - void UseBothLeftFirst(viewstate &rightstate, viewstate &leftstate); - void UseBothRightFirst(viewstate &rightstate, viewstate &leftstate); + static bool IsLeftViewGood() {return IsViewGood(m_pwndLeft);} + static bool IsRightViewGood() {return IsViewGood(m_pwndRight);} + static bool IsBottomViewGood() {return IsViewGood(m_pwndBottom);} - bool IsLeftViewGood() const {return ((m_pwndLeft)&&(m_pwndLeft->IsWindowVisible()));} - bool IsRightViewGood() const {return ((m_pwndRight)&&(m_pwndRight->IsWindowVisible()));} - bool IsBottomViewGood() const {return ((m_pwndBottom)&&(m_pwndBottom->IsWindowVisible()));} - - int CalculateActualOffset(int nLineIndex, int nCharIndex) const; - int CalculateCharIndex(int nLineIndex, int nActualOffset) const; + int CalculateActualOffset(const POINT& point); + int CalculateCharIndex(int nLineIndex, int nActualOffset); POINT TextToClient(const POINT& point); - void DrawText(CDC * pDC, const CRect &rc, LPCTSTR text, int textlength, int nLineIndex, POINT coords, bool bModified, bool bInlineDiff); + void DrawTextLine(CDC * pDC, const CRect &rc, int nLineIndex, POINT& coords); void ClearCurrentSelection(); - void AdjustSelection(); - void SelectNextBlock(int nDirection, bool bConflict, bool bSkipEndOfCurrentBlock = true); + void AdjustSelection(bool bMoveLeft); + bool SelectNextBlock(int nDirection, bool bConflict, bool bSkipEndOfCurrentBlock = true, bool dryrun = false); + + enum SearchDirection{SearchNext=0, SearchPrevious=1}; + bool StringFound(const CString& str, SearchDirection srchDir, int& start, int& end) const; + void Search(SearchDirection srchDir); + void BuildFindStringArray(); void RemoveLine(int nLineIndex); void RemoveSelectedText(); void PasteText(); - void AddUndoLine(int nLine, bool bAddEmptyLine = false); + void AddUndoViewLine(int nViewLine, bool bAddEmptyLine = false); bool MoveCaretLeft(); bool MoveCaretRight(); + void MoveCaretWordLeft(); + void MoveCaretWordRight(); + void OnCaretMove(bool bMoveLeft); + void OnCaretMove(bool bMoveLeft, bool isShiftPressed); void UpdateGoalPos(); - bool IsWordSeparator(wchar_t ch) const; - bool IsCaretAtWordBoundary() const; - -protected: + bool IsWordSeparator(const wchar_t ch) const; + bool IsCaretAtWordBoundary(); + void UpdateViewsCaretPosition(); + void BuildMarkedWordArray(); + + virtual void UseBothBlocks(CBaseView * /*pwndFirst*/, CBaseView * /*pwndLast*/) {}; + virtual void UseViewBlock(CBaseView * /*pwndView*/) {} + virtual void UseViewFile(CBaseView * /*pwndView*/) {} + + virtual void AddContextItems(CIconMenu& popup, DiffStates state); + void AddCutCopyAndPaste(CIconMenu& popup); + void CompensateForKeyboard(CPoint& point); + static HICON LoadIcon(WORD iconId); + void ReleaseBitmap(); + static bool LinesInOneChange( int direction, DiffStates firstLineState, DiffStates currentLineState ); + static void FilterWhitespaces(CString& first, CString& second); + static void FilterWhitespaces(CString& line); + int GetButtonEventLineIndex(const POINT& point); + + static void ResetUndoStep(); + void SaveUndoStep(); +protected: // variables COLORREF m_InlineRemovedBk; COLORREF m_InlineAddedBk; COLORREF m_ModifiedBk; @@ -235,34 +402,49 @@ protected: UINT m_nStatusBarID; ///< The ID of the status bar pane used by this view. Must be set by the parent class. SVNLineDiff m_svnlinediff; + DWORD m_nInlineDiffMaxLineLength; BOOL m_bOtherDiffChecked; - BOOL m_bModified; + bool m_bModified; BOOL m_bFocused; BOOL m_bViewLinenumbers; BOOL m_bIsHidden; - BOOL m_bMouseWithin; BOOL m_bIconLFs; int m_nLineHeight; int m_nCharWidth; int m_nMaxLineLength; int m_nScreenLines; int m_nScreenChars; + int m_nLastScreenChars; int m_nOffsetChar; int m_nTabSize; int m_nDigits; bool m_bInlineWordDiff; - int m_nSelBlockStart; - int m_nSelBlockEnd; + // Block selection attributes + int m_nSelViewBlockStart; + int m_nSelViewBlockEnd; int m_nMouseLine; + bool m_mouseInMargin; + HCURSOR m_margincursor; - bool m_bCaretHidden; - POINT m_ptCaretPos; + // caret + bool m_bReadonly; + bool m_bTarget; ///< view intended as result + POINT m_ptCaretViewPos; int m_nCaretGoalPos; - POINT m_ptSelectionStartPos; - POINT m_ptSelectionEndPos; - POINT m_ptSelectionOrigin; + + // Text selection attributes + POINT m_ptSelectionViewPosStart; + POINT m_ptSelectionViewPosEnd; + POINT m_ptSelectionViewPosOrigin; + + static const UINT m_FindDialogMessage; + CFindDlg * m_pFindDialog; + CString m_sFindText; + BOOL m_bMatchCase; + bool m_bLimitToDiff; + bool m_bWholeWord; HICON m_hAddedIcon; @@ -277,10 +459,15 @@ protected: HICON m_hLineEndingCRLF; HICON m_hLineEndingLF; + HICON m_hMovedIcon; + LOGFONT m_lfBaseFont; - CFont * m_apFonts[8]; + static const int fontsCount = 4; + CFont * m_apFonts[fontsCount]; CString m_sConflictedText; CString m_sNoLineNr; + CString m_sMarkedWord; + CString m_sPreviousMarkedWord; CBitmap * m_pCacheBitmap; CDC * m_pDC; @@ -298,6 +485,103 @@ protected: static CBaseView * m_pwndLeft; ///< Pointer to the left view. Must be set by the CLeftView parent class. static CBaseView * m_pwndRight; ///< Pointer to the right view. Must be set by the CRightView parent class. static CBaseView * m_pwndBottom; ///< Pointer to the bottom view. Must be set by the CBottomView parent class. -}; - + struct TScreenLineInfo + { + int nViewLine; + int nViewSubLine; + }; + class TScreenedViewLine + { + public: + TScreenedViewLine() + { + Clear(); + } + + void Clear() + { + bSublinesSet = false; + eIcon = ICN_UNKNOWN; + bLineColorsSet = false; + bLineColorsSetWhiteSpace = false; + } + + bool bSublinesSet; + std::vector SubLines; + + enum EIcon + { + ICN_UNKNOWN, + ICN_NONE, + ICN_EDIT, + ICN_SAME, + ICN_WHITESPACEDIFF, + ICN_ADD, + ICN_REMOVED, + ICN_MOVED, + ICN_CONFLICT, + ICN_CONFLICTIGNORED, + } eIcon; + + bool bLineColorsSetWhiteSpace; + LineColors lineColorsWhiteSpace; + bool bLineColorsSet; + LineColors lineColors; + }; + std::vector m_ScreenedViewLine; ///< cached data for screening + + static allviewstate m_AllState; + viewstate * m_pState; + + enum PopupCommands + { + // 2-pane view commands + POPUPCOMMAND_USELEFTBLOCK = 1, // 0 means the context menu was dismissed + POPUPCOMMAND_USELEFTFILE, + POPUPCOMMAND_USEBOTHLEFTFIRST, + POPUPCOMMAND_USEBOTHRIGHTFIRST, + // 3-pane view commands + POPUPCOMMAND_USEYOURANDTHEIRBLOCK, + POPUPCOMMAND_USETHEIRANDYOURBLOCK, + POPUPCOMMAND_USEYOURBLOCK, + POPUPCOMMAND_USEYOURFILE, + POPUPCOMMAND_USETHEIRBLOCK, + POPUPCOMMAND_USETHEIRFILE, + }; + + class Screen2View + { + public: + Screen2View() + : m_pViewData(NULL) + {m_bFull=false; } + + int GetViewLineForScreen(int screenLine); + int GetSubLineOffset(int screenLine); + TScreenLineInfo GetScreenLineInfo(int screenLine); + int FindScreenLineForViewLine(int viewLine); + void ScheduleFullRebuild(CViewData * ViewData); + void ScheduleRangeRebuild(CViewData * ViewData, int FirstViewLine, int LastViewLine); + int size(); + + private: + struct TRebuildRange + { + int FirstViewLine; + int LastViewLine; + }; + + bool FixScreenedCacheSize(CBaseView* View); + void RebuildIfNecessary(); + bool ResetScreenedViewLineCache(CBaseView* View); + bool ResetScreenedViewLineCache(CBaseView* View, const TRebuildRange& Range); + + CViewData * m_pViewData; + bool m_bFull; + std::vector m_Screen2View; + std::vector m_RebuildRanges; + }; + + static Screen2View m_Screen2View; +}; diff --git a/src/TortoiseMerge/BottomView.cpp b/src/TortoiseMerge/BottomView.cpp dissimilarity index 82% index 20136592e..5d015c28b 100644 --- a/src/TortoiseMerge/BottomView.cpp +++ b/src/TortoiseMerge/BottomView.cpp @@ -1,199 +1,172 @@ -// TortoiseMerge - a Diff/Patch program - -// Copyright (C) 2006-2009,2011 - TortoiseSVN - -// 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, -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// -#include "stdafx.h" -#include "resource.h" -#include "AppUtils.h" -#include "bottomview.h" - -IMPLEMENT_DYNCREATE(CBottomView, CBaseView) - -CBottomView::CBottomView(void) -{ - m_pwndBottom = this; - m_nStatusBarID = ID_INDICATOR_BOTTOMVIEW; -} - -CBottomView::~CBottomView(void) -{ -} - -bool CBottomView::OnContextMenu(CPoint point, int /*nLine*/, DiffStates state) -{ - if (!this->IsWindowVisible()) - return false; - - CMenu popup; - if (popup.CreatePopupMenu()) - { -#define ID_USETHEIRBLOCK 1 -#define ID_USEYOURBLOCK 2 -#define ID_USETHEIRANDYOURBLOCK 3 -#define ID_USEYOURANDTHEIRBLOCK 4 - UINT uEnabled = MF_ENABLED; - if ((m_nSelBlockStart == -1)||(m_nSelBlockEnd == -1)) - uEnabled |= MF_DISABLED | MF_GRAYED; - CString temp; - - bool bImportantBlock = true; - switch (state) - { - case DIFFSTATE_UNKNOWN: - bImportantBlock = false; - break; - } - - temp.LoadString(IDS_VIEWCONTEXTMENU_USETHEIRBLOCK); - popup.AppendMenu(MF_STRING | uEnabled | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USETHEIRBLOCK, temp); - temp.LoadString(IDS_VIEWCONTEXTMENU_USEYOURBLOCK); - popup.AppendMenu(MF_STRING | uEnabled | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USEYOURBLOCK, temp); - temp.LoadString(IDS_VIEWCONTEXTMENU_USEYOURANDTHEIRBLOCK); - popup.AppendMenu(MF_STRING | uEnabled | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USEYOURANDTHEIRBLOCK, temp); - temp.LoadString(IDS_VIEWCONTEXTMENU_USETHEIRANDYOURBLOCK); - popup.AppendMenu(MF_STRING | uEnabled | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USETHEIRANDYOURBLOCK, temp); - - popup.AppendMenu(MF_SEPARATOR, NULL); - - temp.LoadString(IDS_EDIT_COPY); - popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_COPY, temp); - if (!m_bCaretHidden) - { - temp.LoadString(IDS_EDIT_CUT); - popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_CUT, temp); - temp.LoadString(IDS_EDIT_PASTE); - popup.AppendMenu(MF_STRING | (CAppUtils::HasClipboardFormat(CF_UNICODETEXT)||CAppUtils::HasClipboardFormat(CF_TEXT) ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_PASTE, temp); - } - - // if the context menu is invoked through the keyboard, we have to use - // a calculated position on where to anchor the menu on - if ((point.x == -1) && (point.y == -1)) - { - CRect rect; - GetWindowRect(&rect); - point = rect.CenterPoint(); - } - - int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0); - viewstate rightstate; - viewstate bottomstate; - viewstate leftstate; - switch (cmd) - { - case ID_USETHEIRBLOCK: - UseTheirTextBlock(); - break; - case ID_USEYOURBLOCK: - UseMyTextBlock(); - break; - case ID_USEYOURANDTHEIRBLOCK: - UseYourAndTheirBlock(rightstate, bottomstate, leftstate); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - break; - case ID_USETHEIRANDYOURBLOCK: - UseTheirAndYourBlock(rightstate, bottomstate, leftstate); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - break; - case ID_EDIT_COPY: - OnEditCopy(); - return true; - case ID_EDIT_CUT: - OnEditCopy(); - RemoveSelectedText(); - break; - case ID_EDIT_PASTE: - PasteText(); - break; - } - } - return false; -} - -void CBottomView::UseTheirTextBlock() -{ - viewstate leftstate; - viewstate rightstate; - viewstate bottomstate; - if ((m_nSelBlockStart < 0)||(m_nSelBlockEnd < 0)) - return; - - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - bottomstate.difflines[i] = m_pViewData->GetLine(i); - m_pViewData->SetLine(i, m_pwndLeft->m_pViewData->GetLine(i)); - bottomstate.linestates[i] = m_pViewData->GetState(i); - m_pViewData->SetState(i, m_pwndLeft->m_pViewData->GetState(i)); - m_pViewData->SetLineEnding(i, lineendings); - if (IsLineConflicted(i)) - { - if (m_pwndLeft->m_pViewData->GetState(i) == DIFFSTATE_CONFLICTEMPTY) - m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVEDEMPTY); - else - m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVED); - } - } - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - SetModified(); - RefreshViews(); -} - -void CBottomView::UseMyTextBlock() -{ - viewstate leftstate; - viewstate rightstate; - viewstate bottomstate; - if ((m_nSelBlockStart < 0)||(m_nSelBlockEnd < 0)) - return; - - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - bottomstate.difflines[i] = m_pViewData->GetLine(i); - m_pViewData->SetLine(i, m_pwndRight->m_pViewData->GetLine(i)); - bottomstate.linestates[i] = m_pViewData->GetState(i); - m_pViewData->SetState(i, m_pwndRight->m_pViewData->GetState(i)); - m_pViewData->SetLineEnding(i, lineendings); - { - if (m_pwndRight->m_pViewData->GetState(i) == DIFFSTATE_CONFLICTEMPTY) - m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVEDEMPTY); - else - m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVED); - } - } - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - SetModified(); - RefreshViews(); -} - -void CBottomView::UseTheirThenMyTextBlock() -{ - viewstate leftstate; - viewstate rightstate; - viewstate bottomstate; - UseTheirAndYourBlock(rightstate, bottomstate, leftstate); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - RefreshViews(); -} - -void CBottomView::UseMyThenTheirTextBlock() -{ - viewstate leftstate; - viewstate rightstate; - viewstate bottomstate; - UseYourAndTheirBlock(rightstate, bottomstate, leftstate); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - RefreshViews(); -} +// TortoiseGitMerge - a Diff/Patch program + +// Copyright (C) 2006-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#include "stdafx.h" +#include "resource.h" +#include "AppUtils.h" + +#include "bottomview.h" + +IMPLEMENT_DYNCREATE(CBottomView, CBaseView) + +CBottomView::CBottomView(void) +{ + m_pwndBottom = this; + m_pState = &m_AllState.bottom; + m_nStatusBarID = ID_INDICATOR_BOTTOMVIEW; +} + +CBottomView::~CBottomView(void) +{ +} + + +void CBottomView::AddContextItems(CIconMenu& popup, DiffStates state) +{ + const bool bShow = HasSelection() && (state != DIFFSTATE_UNKNOWN); + if (!bShow) + return; + + popup.AppendMenuIcon(POPUPCOMMAND_USETHEIRBLOCK, IDS_VIEWCONTEXTMENU_USETHEIRBLOCK); + popup.AppendMenuIcon(POPUPCOMMAND_USEYOURBLOCK, IDS_VIEWCONTEXTMENU_USEYOURBLOCK); + popup.AppendMenuIcon(POPUPCOMMAND_USEYOURANDTHEIRBLOCK, IDS_VIEWCONTEXTMENU_USEYOURANDTHEIRBLOCK); + popup.AppendMenuIcon(POPUPCOMMAND_USETHEIRANDYOURBLOCK, IDS_VIEWCONTEXTMENU_USETHEIRANDYOURBLOCK); + + CBaseView::AddContextItems(popup, state); +} + + +void CBottomView::UseBlock(CBaseView * pwndView, int nFirstViewLine, int nLastViewLine) +{ + if (!IsViewGood(pwndView)) + return; + CUndo::GetInstance().BeginGrouping(); // start group undo + + for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++) + { + viewdata lineData = pwndView->GetViewData(viewLine); + lineData.ending = lineendings; + lineData.state = ResolveState(lineData.state); + SetViewData(viewLine, lineData); + } + + int nRemovedLines = CleanEmptyLines(); + SaveUndoStep(); + UpdateViewLineNumbers(); + SaveUndoStep(); + + CUndo::GetInstance().EndGrouping(); + + // final clean up + ClearSelection(); + SetupAllViewSelection(nFirstViewLine, nLastViewLine - nRemovedLines); + BuildAllScreen2ViewVector(); + SetModified(); + RefreshViews(); +} + +void CBottomView::UseBothBlocks(CBaseView * pwndFirst, CBaseView * pwndLast) +{ + if (!IsViewGood(pwndFirst) || !IsViewGood(pwndLast)) + return; + int nFirstViewLine = 0; // first view line in selection + int nLastViewLine = 0; // last view line in selection + + if (!GetViewSelection(nFirstViewLine, nLastViewLine)) + return; + + int nNextViewLine = nLastViewLine + 1; // first view line after selected block + + CUndo::GetInstance().BeginGrouping(); // start group undo + + // use (copy) first block + for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++) + { + viewdata lineData = pwndFirst->GetViewData(viewLine); + lineData.ending = lineendings; + lineData.state = ResolveState(lineData.state); + SetViewData(viewLine, lineData); + if (!IsStateEmpty(pwndFirst->GetViewState(viewLine))) + { + pwndFirst->SetViewState(viewLine, DIFFSTATE_YOURSADDED); // this is improper (may be DIFFSTATE_THEIRSADDED) but seems not to produce any visible bug + } + } + SaveUndoStep(); + + // use (insert) last block + int nViewIndex = nNextViewLine; + for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++, nViewIndex++) + { + viewdata lineData = pwndLast->GetViewData(viewLine); + lineData.state = ResolveState(lineData.state); + InsertViewData(nViewIndex, lineData); + if (!IsStateEmpty(pwndLast->GetViewState(viewLine))) + { + pwndLast->SetViewState(viewLine, DIFFSTATE_THEIRSADDED); // this is improper but seems not to produce any visible bug + } + } + SaveUndoStep(); + + // adjust line numbers in target + // we fix all line numbers to handle exotic cases + UpdateViewLineNumbers(); + SaveUndoStep(); + + // now insert an empty block in both first and last + int nCount = nLastViewLine - nFirstViewLine + 1; + pwndLast->InsertViewEmptyLines(nFirstViewLine, nCount); + pwndFirst->InsertViewEmptyLines(nNextViewLine, nCount); + SaveUndoStep(); + + int nRemovedLines = CleanEmptyLines(); + SaveUndoStep(); + + CUndo::GetInstance().EndGrouping(); + + // final clean up + ClearSelection(); + SetupAllViewSelection(nFirstViewLine, 2*nLastViewLine - nFirstViewLine - nRemovedLines + 1); + BuildAllScreen2ViewVector(); + SetModified(); + pwndLast->SetModified(); + pwndFirst->SetModified(); + RefreshViews(); +} + +void CBottomView::UseViewBlock(CBaseView * pwndView) +{ + if (!IsViewGood(pwndView)) + return; + int nFirstViewLine = 0; // first view line in selection + int nLastViewLine = 0; // last view line in selection + + if (!GetViewSelection(nFirstViewLine, nLastViewLine)) + return; + + return UseBlock(pwndView, nFirstViewLine, nLastViewLine); +} + +void CBottomView::UseViewFile(CBaseView * pwndView) +{ + if (!IsViewGood(pwndView)) + return; + int nFirstViewLine = 0; + int nLastViewLine = GetViewCount()-1; + + return UseBlock(pwndView, nFirstViewLine, nLastViewLine); +} diff --git a/src/TortoiseMerge/BottomView.h b/src/TortoiseMerge/BottomView.h index 2344eaa42..c678efa3e 100644 --- a/src/TortoiseMerge/BottomView.h +++ b/src/TortoiseMerge/BottomView.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2007 - TortoiseSVN +// Copyright (C) 2006-2007, 2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -30,12 +30,14 @@ public: CBottomView(void); ~CBottomView(void); - void UseMyTextBlock(); - void UseMyThenTheirTextBlock(); - void UseTheirTextBlock(); - void UseTheirThenMyTextBlock(); + void UseMyTextBlock() {UseRightBlock(); }; + void UseTheirTextBlock() {UseLeftBlock(); }; -protected: - bool OnContextMenu(CPoint point, int nLine, DiffStates state); + protected: + void AddContextItems(CIconMenu& popup, DiffStates state); + void UseBlock(CBaseView * pwndView, int nFirstViewLine, int nLastViewLine); + void UseBothBlocks(CBaseView * pwndFirst, CBaseView * pwndLast); + void UseViewBlock(CBaseView * pwndView); + void UseViewFile(CBaseView * pwndView); }; diff --git a/src/TortoiseMerge/DiffColors.cpp b/src/TortoiseMerge/DiffColors.cpp index 1d1c890d3..d488f6481 100644 --- a/src/TortoiseMerge/DiffColors.cpp +++ b/src/TortoiseMerge/DiffColors.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2007-2008 - TortoiseSVN +// Copyright (C) 2007-2008, 2010 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -34,6 +34,8 @@ CDiffColors::CDiffColors(void) m_regForegroundColors[DIFFSTATE_REMOVED] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorRemovedF"), DIFFSTATE_REMOVED_DEFAULT_FG); m_regForegroundColors[DIFFSTATE_REMOVEDWHITESPACE] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorRemovedWhitespaceF"), DIFFSTATE_REMOVEDWHITESPACE_DEFAULT_FG); m_regForegroundColors[DIFFSTATE_ADDED] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorAddedF"), DIFFSTATE_ADDED_DEFAULT_FG); + m_regForegroundColors[DIFFSTATE_MOVED_TO] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorMovedToF"), DIFFSTATE_MOVEDTO_DEFAULT_FG); + m_regForegroundColors[DIFFSTATE_MOVED_FROM] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorMovedFromF"), DIFFSTATE_MOVEDFROM_DEFAULT_FG); m_regForegroundColors[DIFFSTATE_ADDEDWHITESPACE] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorAddedWhitespaceF"), DIFFSTATE_ADDEDWHITESPACE_DEFAULT_FG); m_regForegroundColors[DIFFSTATE_WHITESPACE] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorWhitespaceF"), DIFFSTATE_WHITESPACE_DEFAULT_FG); m_regForegroundColors[DIFFSTATE_WHITESPACE_DIFF] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorWhitespaceDiffF"), DIFFSTATE_WHITESPACE_DIFF_DEFAULT_FG); @@ -55,8 +57,10 @@ CDiffColors::CDiffColors(void) m_regBackgroundColors[DIFFSTATE_UNKNOWN] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorUnknownB"), DIFFSTATE_UNKNOWN_DEFAULT_BG); m_regBackgroundColors[DIFFSTATE_NORMAL] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorNormalB"), DIFFSTATE_NORMAL_DEFAULT_BG); m_regBackgroundColors[DIFFSTATE_REMOVED] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorRemovedB"), DIFFSTATE_REMOVED_DEFAULT_BG); + m_regBackgroundColors[DIFFSTATE_MOVED_FROM] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorMovedFromB"), DIFFSTATE_MOVEDFROM_DEFAULT_BG); m_regBackgroundColors[DIFFSTATE_REMOVEDWHITESPACE] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorRemovedWhitespaceB"), DIFFSTATE_REMOVEDWHITESPACE_DEFAULT_BG); m_regBackgroundColors[DIFFSTATE_ADDED] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorAddedB"), DIFFSTATE_ADDED_DEFAULT_BG); + m_regBackgroundColors[DIFFSTATE_MOVED_TO] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorMovedToB"), DIFFSTATE_MOVEDTO_DEFAULT_BG); m_regBackgroundColors[DIFFSTATE_ADDEDWHITESPACE] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorAddedWhitespaceB"), DIFFSTATE_ADDEDWHITESPACE_DEFAULT_BG); m_regBackgroundColors[DIFFSTATE_WHITESPACE] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorWhitespaceB"), DIFFSTATE_WHITESPACE_DEFAULT_BG); m_regBackgroundColors[DIFFSTATE_WHITESPACE_DIFF] = CRegDWORD(_T("Software\\TortoiseGitMerge\\Colors\\ColorWhitespaceDiffB"), DIFFSTATE_WHITESPACE_DIFF_DEFAULT_BG); @@ -94,7 +98,7 @@ void CDiffColors::GetColors(DiffStates state, COLORREF &crBkgnd, COLORREF &crTex } } -void CDiffColors::SetColors(DiffStates state, COLORREF &crBkgnd, COLORREF &crText) +void CDiffColors::SetColors(DiffStates state, const COLORREF &crBkgnd, const COLORREF &crText) { if ((state < DIFFSTATE_END)&&(state >= 0)) { diff --git a/src/TortoiseMerge/DiffColors.h b/src/TortoiseMerge/DiffColors.h index 2c71506d0..ab9f0a059 100644 --- a/src/TortoiseMerge/DiffColors.h +++ b/src/TortoiseMerge/DiffColors.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2007-2008 - TortoiseSVN +// Copyright (C) 2007-2008, 2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -20,6 +20,9 @@ #include "DiffStates.h" #include "registry.h" +#define INLINEADDED_COLOR RGB(255, 255, 150) +#define INLINEREMOVED_COLOR RGB(200, 100, 100) +#define MODIFIED_COLOR RGB(220, 220, 255) #define DIFFSTATE_UNKNOWN_DEFAULT_FG ::GetSysColor(COLOR_WINDOWTEXT) #define DIFFSTATE_NORMAL_DEFAULT_FG ::GetSysColor(COLOR_WINDOWTEXT) @@ -34,6 +37,8 @@ #define DIFFSTATE_CONFLICTED_IGNORED_DEFAULT_FG ::GetSysColor(COLOR_WINDOWTEXT) #define DIFFSTATE_CONFLICTADDED_DEFAULT_FG ::GetSysColor(COLOR_WINDOWTEXT) #define DIFFSTATE_CONFLICTEMPTY_DEFAULT_FG ::GetSysColor(COLOR_WINDOWTEXT) +#define DIFFSTATE_MOVEDFROM_DEFAULT_FG ::GetSysColor(COLOR_WINDOWTEXT) +#define DIFFSTATE_MOVEDTO_DEFAULT_FG ::GetSysColor(COLOR_WINDOWTEXT) #define DIFFSTATE_IDENTICALREMOVED_DEFAULT_FG ::GetSysColor(COLOR_WINDOWTEXT) #define DIFFSTATE_IDENTICALADDED_DEFAULT_FG ::GetSysColor(COLOR_WINDOWTEXT) #define DIFFSTATE_THEIRSREMOVED_DEFAULT_FG ::GetSysColor(COLOR_WINDOWTEXT) @@ -56,6 +61,8 @@ #define DIFFSTATE_CONFLICTED_IGNORED_DEFAULT_BG DIFFSTATE_CONFLICTED_DEFAULT_BG #define DIFFSTATE_CONFLICTADDED_DEFAULT_BG DIFFSTATE_CONFLICTED_DEFAULT_BG #define DIFFSTATE_CONFLICTEMPTY_DEFAULT_BG DIFFSTATE_CONFLICTED_DEFAULT_BG +#define DIFFSTATE_MOVEDFROM_DEFAULT_BG DIFFSTATE_REMOVED_DEFAULT_BG +#define DIFFSTATE_MOVEDTO_DEFAULT_BG DIFFSTATE_ADDED_DEFAULT_BG #define DIFFSTATE_IDENTICALREMOVED_DEFAULT_BG DIFFSTATE_REMOVED_DEFAULT_BG #define DIFFSTATE_IDENTICALADDED_DEFAULT_BG DIFFSTATE_ADDED_DEFAULT_BG #define DIFFSTATE_THEIRSREMOVED_DEFAULT_BG DIFFSTATE_REMOVED_DEFAULT_BG @@ -63,7 +70,7 @@ #define DIFFSTATE_YOURSREMOVED_DEFAULT_BG DIFFSTATE_REMOVED_DEFAULT_BG #define DIFFSTATE_YOURSADDED_DEFAULT_BG DIFFSTATE_ADDED_DEFAULT_BG #define DIFFSTATE_CONFLICTRESOLVED_DEFAULT_BG RGB(200,255,200) -#define DIFFSTATE_EDITED_DEFAULT_BG ::GetSysColor(COLOR_WINDOW) +#define DIFFSTATE_EDITED_DEFAULT_BG MODIFIED_COLOR /** * \ingroup TortoiseMerge @@ -76,7 +83,7 @@ public: static CDiffColors& GetInstance(); void GetColors(DiffStates state, COLORREF &crBkgnd, COLORREF &crText); - void SetColors(DiffStates state, COLORREF &crBkgnd, COLORREF &crText); + void SetColors(DiffStates state, const COLORREF &crBkgnd, const COLORREF &crText); void LoadRegistry(); protected: diff --git a/src/TortoiseMerge/DiffData.cpp b/src/TortoiseMerge/DiffData.cpp index a4cafac59..c196d69fa 100644 --- a/src/TortoiseMerge/DiffData.cpp +++ b/src/TortoiseMerge/DiffData.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2009 - TortoiseSVN +// Copyright (C) 2006-2012 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -17,16 +17,16 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "stdafx.h" -#include "svn_version.h" -#include "svn_io.h" +#pragma warning(push) #include "diff.h" -#include "TempFiles.h" +#pragma warning(pop) +#include "TempFile.h" #include "registry.h" #include "resource.h" #include "Diffdata.h" #include "UnicodeUtils.h" -#include "GitAdminDir.h" #include "svn_dso.h" +#include "MovedBlocks.h" #pragma warning(push) #pragma warning(disable: 4702) // unreachable code @@ -38,11 +38,14 @@ int CDiffData::abort_on_pool_failure (int /*retcode*/) #pragma warning(pop) CDiffData::CDiffData(void) - : m_bBlame(false) + : m_bViewMovedBlocks(false) + , m_bPatchRequired(false) { apr_initialize(); svn_dso_initialize2(); + m_bBlame = false; + m_sPatchOriginal = _T(": original"); m_sPatchPatched = _T(": patched"); } @@ -52,45 +55,132 @@ CDiffData::~CDiffData(void) apr_terminate(); } -int CDiffData::GetLineCount() +void CDiffData::SetMovedBlocks(bool bViewMovedBlocks/* = true*/) { - int count = 0; - count = (int)m_arBaseFile.GetCount(); - count = (int)(count > m_arTheirFile.GetCount() ? count : m_arTheirFile.GetCount()); - count = (int)(count > m_arYourFile.GetCount() ? count : m_arYourFile.GetCount()); - return count; + m_bViewMovedBlocks = bViewMovedBlocks; } -int CDiffData::GetLineActualLength(int index) +int CDiffData::GetLineCount() const { - int count = 0; - if (index < m_arBaseFile.GetCount()) - count = (count > m_arBaseFile.GetAt(index).GetLength() ? count : m_arBaseFile.GetAt(index).GetLength()); - if (index < m_arTheirFile.GetCount()) - count = (count > m_arTheirFile.GetAt(index).GetLength() ? count : m_arTheirFile.GetAt(index).GetLength()); - if (index < m_arYourFile.GetCount()) - count = (count > m_arYourFile.GetAt(index).GetLength() ? count : m_arYourFile.GetAt(index).GetLength()); + int count = (int)m_arBaseFile.GetCount(); + if (count < m_arTheirFile.GetCount()) + count = m_arTheirFile.GetCount(); + if (count < m_arYourFile.GetCount()) + count = m_arYourFile.GetCount(); return count; } -LPCTSTR CDiffData::GetLineChars(int index) +svn_diff_file_ignore_space_t CDiffData::GetIgnoreSpaceMode(DWORD dwIgnoreWS) { - if (index < m_arBaseFile.GetCount()) - return m_arBaseFile.GetAt(index); - if (index < m_arTheirFile.GetCount()) - return m_arTheirFile.GetAt(index); - if (index < m_arYourFile.GetCount()) - return m_arYourFile.GetAt(index); - return NULL; + switch (dwIgnoreWS) + { + case 0: + return svn_diff_file_ignore_space_none; + case 1: + return svn_diff_file_ignore_space_all; + case 2: + return svn_diff_file_ignore_space_change; + default: + return svn_diff_file_ignore_space_none; + } } -BOOL CDiffData::Load() +svn_diff_file_options_t * CDiffData::CreateDiffFileOptions(DWORD dwIgnoreWS, bool bIgnoreEOL, apr_pool_t * pool) +{ + svn_diff_file_options_t * options = svn_diff_file_options_create(pool); + options->ignore_eol_style = bIgnoreEOL; + options->ignore_space = GetIgnoreSpaceMode(dwIgnoreWS); + return options; +} + +bool CDiffData::HandleSvnError(svn_error_t * svnerr) { - CString sConvertedBaseFilename, sConvertedTheirFilename, sConvertedYourFilename; - apr_pool_t * pool; + TRACE(_T("diff-error in CDiffData::Load()\n")); + TRACE(_T("diff-error in CDiffData::Load()\n")); + CStringA sMsg = CStringA(svnerr->message); + while (svnerr->child) + { + svnerr = svnerr->child; + sMsg += _T("\n"); + sMsg += CStringA(svnerr->message); + } + CString readableMsg = CUnicodeUtils::GetUnicode(sMsg); + m_sError.Format(IDS_ERR_DIFF_DIFF, (LPCTSTR)readableMsg); + svn_error_clear(svnerr); + return false; +} - apr_pool_create_ex (&pool, NULL, abort_on_pool_failure, NULL); +void CDiffData::TieMovedBlocks(int from, int to, apr_off_t length) +{ + for(int i=0; itype == svn_diff__type_diff_modified)) + { + svn_diff_t * nextdiff = tempdiff->next; + while((nextdiff)&&(nextdiff->type == svn_diff__type_diff_modified)) + { + original_length_sticked += nextdiff->original_length; + modified_length_sticked += nextdiff->modified_length; + tempdiff = nextdiff; + nextdiff = tempdiff->next; + } + } +} + +BOOL CDiffData::Load() +{ m_arBaseFile.RemoveAll(); m_arYourFile.RemoveAll(); m_arTheirFile.RemoveAll(); @@ -105,11 +195,6 @@ BOOL CDiffData::Load() m_Diff3.Clear(); - m_arDiff3LinesBase.RemoveAll(); - m_arDiff3LinesYour.RemoveAll(); - m_arDiff3LinesTheir.RemoveAll(); - - CTempFiles tempfiles; CRegDWORD regIgnoreWS = CRegDWORD(_T("Software\\TortoiseGitMerge\\IgnoreWS")); CRegDWORD regIgnoreEOL = CRegDWORD(_T("Software\\TortoiseGitMerge\\IgnoreEOL"), TRUE); CRegDWORD regIgnoreCase = CRegDWORD(_T("Software\\TortoiseGitMerge\\CaseInsensitive"), FALSE); @@ -122,9 +207,29 @@ BOOL CDiffData::Load() // To ignore case changes or to handle UTF-16 files, we have to // save the original file in UTF-8 and/or the letters changed to lowercase // so the Subversion diff can handle those. - sConvertedBaseFilename = m_baseFile.GetFilename(); - sConvertedYourFilename = m_yourFile.GetFilename(); - sConvertedTheirFilename = m_theirFile.GetFilename(); + CString sConvertedBaseFilename = m_baseFile.GetFilename(); + CString sConvertedYourFilename = m_yourFile.GetFilename(); + CString sConvertedTheirFilename = m_theirFile.GetFilename(); + + m_baseFile.StoreFileAttributes(); + m_theirFile.StoreFileAttributes(); + m_yourFile.StoreFileAttributes(); + //m_mergedFile.StoreFileAttributes(); + + bool bBaseNeedConvert = false; + bool bTheirNeedConvert = false; + bool bYourNeedConvert = false; + bool bBaseIsUtf8 = false; + bool bTheirIsUtf8 = false; + bool bYourIsUtf8 = false; + + // in case at least one of the files got converted or is UTF8 + // we have to convert all non UTF8 (ASCII) files + // otherwise one file might be in ANSI and the other in UTF8 and we'll end up + // with lines marked as different throughout the files even though the lines + // would show no change at all in the viewer. + bool bIsNotUtf8 = false; // Any non UTF8 file ? + if (IsBaseFileInUse()) { if (!m_arBaseFile.Load(m_baseFile.GetFilename())) @@ -132,51 +237,62 @@ BOOL CDiffData::Load() m_sError = m_arBaseFile.GetErrorString(); return FALSE; } - if ((bIgnoreCase)||(m_arBaseFile.GetUnicodeType() == CFileTextLines::UNICODE_LE)||(m_arBaseFile.GetUnicodeType() == CFileTextLines::UNICODE_BE)) - { - CFileTextLines converted(m_arBaseFile); - sConvertedBaseFilename = tempfiles.GetTempFilePath(); - converted.Save(sConvertedBaseFilename, m_arBaseFile.GetUnicodeType() == CFileTextLines::UNICODE_LE || m_arBaseFile.GetUnicodeType() == CFileTextLines::UNICODE_BE, dwIgnoreWS, bIgnoreCase, m_bBlame); - } + bBaseNeedConvert = bIgnoreCase || (m_arBaseFile.NeedsConversion()); + bBaseIsUtf8 = (m_arBaseFile.GetUnicodeType()!=CFileTextLines::ASCII) || bBaseNeedConvert; + bIsNotUtf8 |= !bBaseIsUtf8; } if (IsTheirFileInUse()) { // m_arBaseFile.GetCount() is passed as a hint for the number of lines in this file // It's a fair guess that the files will be roughly the same size - if (!m_arTheirFile.Load(m_theirFile.GetFilename(),m_arBaseFile.GetCount())) + if (!m_arTheirFile.Load(m_theirFile.GetFilename(), m_arBaseFile.GetCount())) { m_sError = m_arTheirFile.GetErrorString(); return FALSE; } - if ((bIgnoreCase)||(m_arTheirFile.GetUnicodeType() == CFileTextLines::UNICODE_LE)||(m_arTheirFile.GetUnicodeType() == CFileTextLines::UNICODE_BE)) - { - CFileTextLines converted(m_arTheirFile); - sConvertedTheirFilename = tempfiles.GetTempFilePath(); - converted.Save(sConvertedTheirFilename, m_arTheirFile.GetUnicodeType() == CFileTextLines::UNICODE_LE || m_arTheirFile.GetUnicodeType() == CFileTextLines::UNICODE_BE, dwIgnoreWS, bIgnoreCase, m_bBlame); - } + bTheirNeedConvert = bIgnoreCase || (m_arTheirFile.NeedsConversion()); + bTheirIsUtf8 = (m_arTheirFile.GetUnicodeType()!=CFileTextLines::ASCII) || bTheirNeedConvert; + bIsNotUtf8 |= !bTheirIsUtf8; } if (IsYourFileInUse()) { // m_arBaseFile.GetCount() is passed as a hint for the number of lines in this file // It's a fair guess that the files will be roughly the same size - if (!m_arYourFile.Load(m_yourFile.GetFilename(),m_arBaseFile.GetCount())) + if (!m_arYourFile.Load(m_yourFile.GetFilename(), m_arBaseFile.GetCount())) { m_sError = m_arYourFile.GetErrorString(); return FALSE; } - if ((bIgnoreCase)||(m_arYourFile.GetUnicodeType() == CFileTextLines::UNICODE_LE)||(m_arYourFile.GetUnicodeType() == CFileTextLines::UNICODE_BE)) - { - CFileTextLines converted(m_arYourFile); - sConvertedYourFilename = tempfiles.GetTempFilePath(); - converted.Save(sConvertedYourFilename, m_arYourFile.GetUnicodeType() == CFileTextLines::UNICODE_LE || m_arYourFile.GetUnicodeType() == CFileTextLines::UNICODE_BE, dwIgnoreWS, bIgnoreCase, m_bBlame); - } + bYourNeedConvert = bIgnoreCase || (m_arYourFile.NeedsConversion()); + bYourIsUtf8 = (m_arYourFile.GetUnicodeType()!=CFileTextLines::ASCII) || bYourNeedConvert; + bIsNotUtf8 |= !bYourIsUtf8; + } + + // convert all files we need to + bool bIsUtf8 = bBaseIsUtf8 || bTheirIsUtf8 || bYourIsUtf8; // any file end as UTF8 + bBaseNeedConvert |= (IsBaseFileInUse() && !bBaseIsUtf8 && bIsUtf8); + if (bBaseNeedConvert) + { + sConvertedBaseFilename = CTempFiles::Instance().GetTempFilePathString(); + m_arBaseFile.Save(sConvertedBaseFilename, true, true, 0, bIgnoreCase, m_bBlame); + } + bYourNeedConvert |= (IsYourFileInUse() && !bYourIsUtf8 && bIsUtf8); + if (bYourNeedConvert) + { + sConvertedYourFilename = CTempFiles::Instance().GetTempFilePathString(); + m_arYourFile.Save(sConvertedYourFilename, true, true, 0, bIgnoreCase, m_bBlame); + } + bTheirNeedConvert |= (IsTheirFileInUse() && !bTheirIsUtf8 && bIsUtf8); + if (bTheirNeedConvert) + { + sConvertedTheirFilename = CTempFiles::Instance().GetTempFilePathString(); + m_arTheirFile.Save(sConvertedTheirFilename, true, true, 0, bIgnoreCase, m_bBlame); } // Calculate the number of lines in the largest of the three files - int lengthHint = max(m_arBaseFile.GetCount(), m_arTheirFile.GetCount()); - lengthHint = max(lengthHint, m_arYourFile.GetCount()); + int lengthHint = GetLineCount(); try { @@ -187,24 +303,24 @@ BOOL CDiffData::Load() m_TheirBaseBoth.Reserve(lengthHint); m_TheirBaseLeft.Reserve(lengthHint); m_TheirBaseRight.Reserve(lengthHint); - - m_arDiff3LinesBase.Reserve(lengthHint); - m_arDiff3LinesYour.Reserve(lengthHint); - m_arDiff3LinesTheir.Reserve(lengthHint); } catch (CMemoryException* e) { e->GetErrorMessage(m_sError.GetBuffer(255), 255); m_sError.ReleaseBuffer(); + e->Delete(); return FALSE; } + apr_pool_t * pool = NULL; + apr_pool_create_ex (&pool, NULL, abort_on_pool_failure, NULL); + // Is this a two-way diff? if (IsBaseFileInUse() && IsYourFileInUse() && !IsTheirFileInUse()) { if (!DoTwoWayDiff(sConvertedBaseFilename, sConvertedYourFilename, dwIgnoreWS, bIgnoreEOL, pool)) { - apr_pool_destroy (pool); // free the allocated memory + apr_pool_destroy (pool); // free the allocated memory return FALSE; } } @@ -214,70 +330,58 @@ BOOL CDiffData::Load() ASSERT(FALSE); } + // Is this a three-way diff? if (IsBaseFileInUse() && IsTheirFileInUse() && IsYourFileInUse()) { m_Diff3.Reserve(lengthHint); if (!DoThreeWayDiff(sConvertedBaseFilename, sConvertedYourFilename, sConvertedTheirFilename, dwIgnoreWS, bIgnoreEOL, !!bIgnoreCase, pool)) { - apr_pool_destroy (pool); // free the allocated memory + apr_pool_destroy (pool); // free the allocated memory return FALSE; } } - apr_pool_destroy (pool); // free the allocated memory + // free the allocated memory + apr_pool_destroy (pool); + + m_arBaseFile.RemoveAll(); + m_arYourFile.RemoveAll(); + m_arTheirFile.RemoveAll(); + return TRUE; } - bool CDiffData::DoTwoWayDiff(const CString& sBaseFilename, const CString& sYourFilename, DWORD dwIgnoreWS, bool bIgnoreEOL, apr_pool_t * pool) { + svn_diff_file_options_t * options = CreateDiffFileOptions(dwIgnoreWS, bIgnoreEOL, pool); // convert CString filenames (UTF-16 or ANSI) to UTF-8 CStringA sBaseFilenameUtf8 = CUnicodeUtils::GetUTF8(sBaseFilename); CStringA sYourFilenameUtf8 = CUnicodeUtils::GetUTF8(sYourFilename); svn_diff_t * diffYourBase = NULL; - svn_error_t * svnerr = NULL; - svn_diff_file_options_t * options = svn_diff_file_options_create(pool); - options->ignore_eol_style = bIgnoreEOL; - options->ignore_space = svn_diff_file_ignore_space_none; - switch (dwIgnoreWS) - { - case 0: - options->ignore_space = svn_diff_file_ignore_space_none; - break; - case 1: - options->ignore_space = svn_diff_file_ignore_space_all; - break; - case 2: - options->ignore_space = svn_diff_file_ignore_space_change; - break; - } + svn_error_t * svnerr = svn_diff_file_diff_2(&diffYourBase, sBaseFilenameUtf8, sYourFilenameUtf8, options, pool); - svnerr = svn_diff_file_diff_2(&diffYourBase, sBaseFilenameUtf8, sYourFilenameUtf8, options, pool); if (svnerr) - { - TRACE(_T("diff-error in CDiffData::Load()\n")); - CStringA sMsg = CStringA(svnerr->message); - while (svnerr->child) - { - svnerr = svnerr->child; - sMsg += _T("\n"); - sMsg += CStringA(svnerr->message); - } - CString readableMsg = CUnicodeUtils::GetUnicode(sMsg); - m_sError.Format(IDS_ERR_DIFF_DIFF, (LPCTSTR)readableMsg); - svn_error_clear(svnerr); - return false; - } + return HandleSvnError(svnerr); + + tsvn_svn_diff_t_extension * movedBlocks = NULL; + if(m_bViewMovedBlocks) + movedBlocks = MovedBlocksDetect(diffYourBase, dwIgnoreWS, pool); // Side effect is that diffs are now splitted + svn_diff_t * tempdiff = diffYourBase; LONG baseline = 0; LONG yourline = 0; - while (tempdiff) { - for (int i=0; ioriginal_length; i++) + svn_diff__type_e diffType = tempdiff->type; + // Side effect described above overcoming - sticking together + apr_off_t original_length_sticked = tempdiff->original_length; + apr_off_t modified_length_sticked = tempdiff->modified_length; + StickAndSkip(tempdiff, original_length_sticked, modified_length_sticked); + + for (int i=0; i= m_arBaseFile.GetCount()) { @@ -286,7 +390,7 @@ CDiffData::DoTwoWayDiff(const CString& sBaseFilename, const CString& sYourFilena } const CString& sCurrentBaseLine = m_arBaseFile.GetAt(baseline); EOL endingBase = m_arBaseFile.GetLineEnding(baseline); - if (tempdiff->type == svn_diff__type_common) + if (diffType == svn_diff__type_common) { if (yourline >= m_arYourFile.GetCount()) { @@ -297,62 +401,39 @@ CDiffData::DoTwoWayDiff(const CString& sBaseFilename, const CString& sYourFilena EOL endingYours = m_arYourFile.GetLineEnding(yourline); if (sCurrentBaseLine != sCurrentYourLine) { + bool changedWS = false; if (dwIgnoreWS == 2 || dwIgnoreWS == 3) + changedWS = CompareWithIgnoreWS(sCurrentBaseLine, sCurrentYourLine, dwIgnoreWS); + if (changedWS || dwIgnoreWS == 0) { - CString s1 = m_arBaseFile.GetAt(baseline); - CString s2 = sCurrentYourLine; - - if ( dwIgnoreWS == 2 ) - { - s1.TrimLeft(_T(" \t")); - s2.TrimLeft(_T(" \t")); - } - else - { - s1.TrimRight(_T(" \t")); - s2.TrimRight(_T(" \t")); - } - - if (s1 != s2) - { - // one-pane view: two lines, one 'removed' and one 'added' - m_YourBaseBoth.AddData(sCurrentBaseLine, DIFFSTATE_REMOVEDWHITESPACE, yourline, endingBase); - m_YourBaseBoth.AddData(sCurrentYourLine, DIFFSTATE_ADDEDWHITESPACE, yourline, endingYours); - } - else - { - m_YourBaseBoth.AddData(sCurrentBaseLine, DIFFSTATE_NORMAL, yourline, endingBase); - } - } - else if (dwIgnoreWS == 0) - { - m_YourBaseBoth.AddData(sCurrentBaseLine, DIFFSTATE_REMOVEDWHITESPACE, yourline, endingBase); - m_YourBaseBoth.AddData(sCurrentYourLine, DIFFSTATE_ADDEDWHITESPACE, yourline, endingYours); + // one-pane view: two lines, one 'removed' and one 'added' + m_YourBaseBoth.AddData(sCurrentBaseLine, DIFFSTATE_REMOVEDWHITESPACE, yourline, endingBase, HIDESTATE_SHOWN, -1); + m_YourBaseBoth.AddData(sCurrentYourLine, DIFFSTATE_ADDEDWHITESPACE, yourline, endingYours, HIDESTATE_SHOWN, -1); } else { - m_YourBaseBoth.AddData(sCurrentBaseLine, DIFFSTATE_NORMAL, yourline, endingBase); + m_YourBaseBoth.AddData(sCurrentYourLine, DIFFSTATE_NORMAL, yourline, endingBase, HIDESTATE_HIDDEN, -1); } } else { - m_YourBaseBoth.AddData(sCurrentBaseLine, DIFFSTATE_NORMAL, yourline, endingBase); + m_YourBaseBoth.AddData(sCurrentYourLine, DIFFSTATE_NORMAL, yourline, endingBase, HIDESTATE_HIDDEN, -1); } - yourline++; //in both files + yourline++; //in both files } else - { - m_YourBaseBoth.AddData(sCurrentBaseLine, DIFFSTATE_REMOVED, yourline, endingBase); + { // small trick - we need here a baseline, but we fix it back to yourline at the end of routine + m_YourBaseBoth.AddData(sCurrentBaseLine, DIFFSTATE_REMOVED, baseline, endingBase, HIDESTATE_SHOWN, -1); } baseline++; } - if (tempdiff->type == svn_diff__type_diff_modified) + if (diffType == svn_diff__type_diff_modified) { - for (int i=0; imodified_length; i++) + for (int i=0; i yourline) { - m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_ADDED, yourline, m_arYourFile.GetLineEnding(yourline)); + m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_ADDED, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1); } yourline++; } @@ -360,6 +441,8 @@ CDiffData::DoTwoWayDiff(const CString& sBaseFilename, const CString& sYourFilena tempdiff = tempdiff->next; } + HideUnchangedSections(&m_YourBaseBoth, NULL, NULL); + tempdiff = diffYourBase; baseline = 0; yourline = 0; @@ -375,47 +458,24 @@ CDiffData::DoTwoWayDiff(const CString& sBaseFilename, const CString& sYourFilena EOL endingBase = m_arBaseFile.GetLineEnding(baseline); if (sCurrentBaseLine != sCurrentYourLine) { + bool changedWS = false; if (dwIgnoreWS == 2 || dwIgnoreWS == 3) + changedWS = CompareWithIgnoreWS(sCurrentBaseLine, sCurrentYourLine, dwIgnoreWS); + if (changedWS || dwIgnoreWS == 0) { - CString s1 = sCurrentBaseLine; - CString s2 = sCurrentYourLine; - if ( dwIgnoreWS == 2 ) - { - s1 = s1.TrimLeft(_T(" \t")); - s2 = s2.TrimLeft(_T(" \t")); - } - else - { - s1 = s1.TrimRight(_T(" \t")); - s2 = s2.TrimRight(_T(" \t")); - } - - if (s1 != s2) - { - m_YourBaseLeft.AddData(sCurrentBaseLine, DIFFSTATE_WHITESPACE, baseline, endingBase); - m_YourBaseRight.AddData(sCurrentYourLine, DIFFSTATE_WHITESPACE, yourline, endingYours); - } - else - { - m_YourBaseLeft.AddData(sCurrentBaseLine, DIFFSTATE_NORMAL, baseline, endingBase); - m_YourBaseRight.AddData(sCurrentYourLine, DIFFSTATE_NORMAL, yourline, endingYours); - } - } - else if (dwIgnoreWS == 0) - { - m_YourBaseLeft.AddData(sCurrentBaseLine, DIFFSTATE_WHITESPACE, baseline, endingBase); - m_YourBaseRight.AddData(sCurrentYourLine, DIFFSTATE_WHITESPACE, yourline, endingYours); + m_YourBaseLeft.AddData(sCurrentBaseLine, DIFFSTATE_WHITESPACE, baseline, endingBase, HIDESTATE_SHOWN, -1); + m_YourBaseRight.AddData(sCurrentYourLine, DIFFSTATE_WHITESPACE, yourline, endingYours, HIDESTATE_SHOWN, -1); } else { - m_YourBaseLeft.AddData(sCurrentBaseLine, DIFFSTATE_NORMAL, baseline, endingBase); - m_YourBaseRight.AddData(sCurrentYourLine, DIFFSTATE_NORMAL, yourline, endingYours); + m_YourBaseLeft.AddData(sCurrentBaseLine, DIFFSTATE_NORMAL, baseline, endingBase, HIDESTATE_HIDDEN, -1); + m_YourBaseRight.AddData(sCurrentYourLine, DIFFSTATE_NORMAL, yourline, endingYours, HIDESTATE_HIDDEN, -1); } } else { - m_YourBaseLeft.AddData(sCurrentBaseLine, DIFFSTATE_NORMAL, baseline, endingBase); - m_YourBaseRight.AddData(sCurrentYourLine, DIFFSTATE_NORMAL, yourline, endingYours); + m_YourBaseLeft.AddData(sCurrentBaseLine, DIFFSTATE_NORMAL, baseline, endingBase, HIDESTATE_HIDDEN, -1); + m_YourBaseRight.AddData(sCurrentYourLine, DIFFSTATE_NORMAL, yourline, endingYours, HIDESTATE_HIDDEN, -1); } baseline++; yourline++; @@ -423,94 +483,171 @@ CDiffData::DoTwoWayDiff(const CString& sBaseFilename, const CString& sYourFilena } if (tempdiff->type == svn_diff__type_diff_modified) { - apr_off_t original_length = tempdiff->original_length; - for (int i=0; imodified_length; i++) + // now we trying to stick together parts, that were splitted by MovedBlocks + apr_off_t original_length_sticked = tempdiff->original_length; + apr_off_t modified_length_sticked = tempdiff->modified_length; + StickAndSkip(tempdiff, original_length_sticked, modified_length_sticked); + + apr_off_t original_length = original_length_sticked; + for (int i=0; i yourline) { EOL endingYours = m_arYourFile.GetLineEnding(yourline); + m_YourBaseRight.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_ADDED, yourline, endingYours, HIDESTATE_SHOWN, -1); if (original_length-- <= 0) { - m_YourBaseLeft.AddData(_T(""), DIFFSTATE_EMPTY, DIFF_EMPTYLINENUMBER, EOL_NOENDING); - m_YourBaseRight.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_ADDED, yourline, endingYours); + m_YourBaseLeft.AddEmpty(); } else { EOL endingBase = m_arBaseFile.GetLineEnding(baseline); - m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_REMOVED, baseline, endingBase); - m_YourBaseRight.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_ADDED, yourline, endingYours); + m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_REMOVED, baseline, endingBase, HIDESTATE_SHOWN, -1); baseline++; } yourline++; } } - apr_off_t modified_length = tempdiff->modified_length; - for (int i=0; ioriginal_length; i++) + apr_off_t modified_length = modified_length_sticked; + for (int i=0; i baseline)) { - if (m_arBaseFile.GetCount() > baseline) - { - EOL endingBase = m_arBaseFile.GetLineEnding(baseline); - m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_REMOVED, baseline, endingBase); - m_YourBaseRight.AddData(_T(""), DIFFSTATE_EMPTY, DIFF_EMPTYLINENUMBER, EOL_NOENDING); - baseline++; - } + EOL endingBase = m_arBaseFile.GetLineEnding(baseline); + m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_REMOVED, baseline, endingBase, HIDESTATE_SHOWN, -1); + m_YourBaseRight.AddEmpty(); + baseline++; } } } tempdiff = tempdiff->next; } + // add last (empty) lines if needed - diff don't report those + if (m_arBaseFile.GetCount() > baseline) + { + if (m_arYourFile.GetCount() > yourline) + { + // last line is missing in both files add them to end and mark as no diff + m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_NORMAL, baseline, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1); + m_YourBaseRight.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1); + yourline++; + baseline++; + } + else + { + viewdata oViewData(m_arBaseFile.GetAt(baseline), DIFFSTATE_REMOVED, baseline, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1); + baseline++; + + // find first EMPTY line in last blok + int nPos = m_YourBaseLeft.GetCount(); + while (--nPos>=0 && m_YourBaseLeft.GetState(nPos)==DIFFSTATE_EMPTY) ; + if (++nPos yourline) + { + viewdata oViewData(m_arYourFile.GetAt(yourline), DIFFSTATE_ADDED, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1); + yourline++; + + // try to move last line higher + int nPos = m_YourBaseRight.GetCount(); + while (--nPos>=0 && m_YourBaseRight.GetState(nPos)==DIFFSTATE_EMPTY) ; + if (++nPosbase; + if(movedBlocks->moved_to != -1) + { + // set states in a block original:length -> moved_to:length + TieMovedBlocks((int)tempdiff->original_start, movedBlocks->moved_to, tempdiff->original_length); + } + if(movedBlocks->moved_from != -1) + { + // set states in a block modified:length -> moved_from:length + TieMovedBlocks(movedBlocks->moved_from, (int)tempdiff->modified_start, tempdiff->modified_length); + } + movedBlocks = movedBlocks->next; + } + + // replace baseline with the yourline in m_YourBaseBoth + yourline = 0; + for(int i=0; iignore_eol_style = bIgnoreEOL; - options->ignore_space = svn_diff_file_ignore_space_none; - switch (dwIgnoreWS) - { - case 0: - options->ignore_space = svn_diff_file_ignore_space_none; - break; - case 1: - options->ignore_space = svn_diff_file_ignore_space_all; - break; - case 2: - options->ignore_space = svn_diff_file_ignore_space_change; - break; - } svn_error_t * svnerr = svn_diff_file_diff3_2(&diffTheirYourBase, sBaseFilenameUtf8, sTheirFilenameUtf8, sYourFilenameUtf8, options, pool); if (svnerr) - { - TRACE(_T("diff-error in CDiffData::Load()\n")); - CStringA sMsg = CStringA(svnerr->message); - while (svnerr->child) - { - svnerr = svnerr->child; - sMsg += _T("\n"); - sMsg += CStringA(svnerr->message); - } - CString readableMsg = CUnicodeUtils::GetUnicode(sMsg); - m_sError.Format(IDS_ERR_DIFF_DIFF, (LPCTSTR)readableMsg); - svn_error_clear(svnerr); - return false; - } + return HandleSvnError(svnerr); svn_diff_t * tempdiff = diffTheirYourBase; LONG baseline = 0; LONG yourline = 0; LONG theirline = 0; LONG resline = 0; + // common viewdata + const viewdata emptyConflictEmpty(_T(""), DIFFSTATE_CONFLICTEMPTY, DIFF_EMPTYLINENUMBER, EOL_NOENDING, HIDESTATE_SHOWN, -1); + const viewdata emptyIdenticalRemoved(_T(""), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING, HIDESTATE_SHOWN, -1); while (tempdiff) { if (tempdiff->type == svn_diff__type_common) @@ -520,14 +657,11 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile { if ((m_arYourFile.GetCount() > yourline)&&(m_arTheirFile.GetCount() > theirline)) { - m_Diff3.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, resline, m_arYourFile.GetLineEnding(yourline)); - - m_arDiff3LinesBase.Add(baseline); - m_arDiff3LinesYour.Add(yourline); - m_arDiff3LinesTheir.Add(theirline); + AddLines(baseline, yourline, theirline); - m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, yourline, m_arYourFile.GetLineEnding(yourline)); - m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_NORMAL, theirline, m_arTheirFile.GetLineEnding(theirline)); + m_Diff3.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, resline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_HIDDEN, -1); + m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_HIDDEN, -1); + m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_NORMAL, theirline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_HIDDEN, -1); baseline++; yourline++; @@ -544,14 +678,11 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile { if (m_arBaseFile.GetCount() > baseline) { - m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline)); + AddLines(baseline, yourline, theirline); - m_arDiff3LinesBase.Add(baseline); - m_arDiff3LinesYour.Add(yourline); - m_arDiff3LinesTheir.Add(theirline); - - m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING); - m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING); + m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1); + m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING, HIDESTATE_SHOWN, -1); + m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING, HIDESTATE_SHOWN, -1); baseline++; } @@ -560,14 +691,11 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile { if ((m_arYourFile.GetCount() > yourline)&&(m_arTheirFile.GetCount() > theirline)) { - m_Diff3.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_IDENTICALADDED, resline, m_arYourFile.GetLineEnding(yourline)); - - m_arDiff3LinesBase.Add(baseline); - m_arDiff3LinesYour.Add(yourline); - m_arDiff3LinesTheir.Add(theirline); + AddLines(baseline, yourline, theirline); - m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_IDENTICALADDED, yourline, m_arYourFile.GetLineEnding(yourline)); - m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_IDENTICALADDED, resline, m_arTheirFile.GetLineEnding(theirline)); + m_Diff3.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_IDENTICALADDED, resline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1); + m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_IDENTICALADDED, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1); + m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_IDENTICALADDED, resline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_SHOWN, -1); yourline++; theirline++; @@ -598,66 +726,27 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile } for (int i=0; i baseline ? m_arBaseFile.GetLineEnding(baseline) : EOL_AUTOLINE; if (original) { if (m_arBaseFile.GetCount() > baseline) { - m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline)); + AddLines(baseline, yourline, theirline); - m_arDiff3LinesBase.Add(baseline); - m_arDiff3LinesYour.Add(yourline); - m_arDiff3LinesTheir.Add(theirline); + m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, endingBase , HIDESTATE_SHOWN, -1); + m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, endingBase , HIDESTATE_SHOWN, -1); + m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, endingBase , HIDESTATE_SHOWN, -1); } } else if ((originalresolved)||((modifiedresolved)&&(latestresolved))) { - m_Diff3.AddData(_T(""), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING); + AddLines(baseline, yourline, theirline); - m_arDiff3LinesBase.Add(baseline); - m_arDiff3LinesYour.Add(yourline); - m_arDiff3LinesTheir.Add(theirline); - - } - if ((latest)&&(original)) - { - if (m_arBaseFile.GetCount() > baseline) - { - m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline)); - } - } - else - { - if (original) - { - if (m_arBaseFile.GetCount() > baseline) - { - m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline)); - } - } - else if ((latestresolved)&&(modifiedresolved)) - { - m_YourBaseBoth.AddData(_T(""), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING); - } - } - if ((modified)&&(original)) - { - if (m_arBaseFile.GetCount() > baseline) - { - m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline)); - } - } - else - { - if (original) - { - if (m_arBaseFile.GetCount() > baseline) - { - m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline)); - } - } - else if ((modifiedresolved)&&(latestresolved)) + m_Diff3.AddData(emptyIdenticalRemoved); + if ((latestresolved)&&(modifiedresolved)) { - m_TheirBaseBoth.AddData(_T(""), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING); + m_YourBaseBoth.AddData(emptyIdenticalRemoved); + m_TheirBaseBoth.AddData(emptyIdenticalRemoved); } } if (original) @@ -699,11 +788,9 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile { if ((latest)||(modified)) { - m_Diff3.AddData(_T(""), DIFFSTATE_CONFLICTED, resline, EOL_NOENDING); + AddLines(baseline, yourline, theirline); - m_arDiff3LinesBase.Add(baseline); - m_arDiff3LinesYour.Add(yourline); - m_arDiff3LinesTheir.Add(theirline); + m_Diff3.AddData(_T(""), DIFFSTATE_CONFLICTED, resline, EOL_NOENDING, HIDESTATE_SHOWN, -1); resline++; } @@ -712,23 +799,23 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile { if (m_arYourFile.GetCount() > yourline) { - m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_CONFLICTADDED, yourline, m_arYourFile.GetLineEnding(yourline)); + m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_CONFLICTADDED, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1); } } else if ((latestresolved)||(modified)||(modifiedresolved)) { - m_YourBaseBoth.AddData(_T(""), DIFFSTATE_CONFLICTEMPTY, DIFF_EMPTYLINENUMBER, EOL_NOENDING); + m_YourBaseBoth.AddData(emptyConflictEmpty); } if (modified) { if (m_arTheirFile.GetCount() > theirline) { - m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_CONFLICTADDED, theirline, m_arTheirFile.GetLineEnding(theirline)); + m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_CONFLICTADDED, theirline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_SHOWN, -1); } } else if ((modifiedresolved)||(latest)||(latestresolved)) { - m_TheirBaseBoth.AddData(_T(""), DIFFSTATE_CONFLICTEMPTY, DIFF_EMPTYLINENUMBER, EOL_NOENDING); + m_TheirBaseBoth.AddData(emptyConflictEmpty); } if (original) { @@ -760,14 +847,12 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile { if ((m_arBaseFile.GetCount() > baseline)&&(m_arYourFile.GetCount() > yourline)) { - m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_THEIRSREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline)); + AddLines(baseline, yourline, theirline); - m_arDiff3LinesBase.Add(baseline); - m_arDiff3LinesYour.Add(yourline); - m_arDiff3LinesTheir.Add(theirline); + m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_THEIRSREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1); + m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1); + m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_THEIRSREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING, HIDESTATE_SHOWN, -1); - m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, yourline, m_arYourFile.GetLineEnding(yourline)); - m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_THEIRSREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING); baseline++; yourline++; } @@ -777,14 +862,11 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile { if (m_arTheirFile.GetCount() > theirline) { - m_Diff3.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_THEIRSADDED, resline, m_arTheirFile.GetLineEnding(theirline)); + AddLines(baseline, yourline, theirline); - m_arDiff3LinesBase.Add(baseline); - m_arDiff3LinesYour.Add(yourline); - m_arDiff3LinesTheir.Add(theirline); - - m_YourBaseBoth.AddData(_T(""), DIFFSTATE_EMPTY, DIFF_EMPTYLINENUMBER, EOL_NOENDING); - m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_THEIRSADDED, theirline, m_arTheirFile.GetLineEnding(theirline)); + m_Diff3.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_THEIRSADDED, resline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_SHOWN, -1); + m_YourBaseBoth.AddEmpty(); + m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_THEIRSADDED, theirline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_SHOWN, -1); theirline++; resline++; @@ -799,14 +881,11 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile { if ((m_arBaseFile.GetCount() > baseline)&&(m_arTheirFile.GetCount() > theirline)) { - m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_YOURSREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline)); - - m_arDiff3LinesBase.Add(baseline); - m_arDiff3LinesYour.Add(yourline); - m_arDiff3LinesTheir.Add(theirline); + AddLines(baseline, yourline, theirline); - m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_YOURSREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline)); - m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_NORMAL, theirline, m_arTheirFile.GetLineEnding(theirline)); + m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_YOURSREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1); + m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_YOURSREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1); + m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_NORMAL, theirline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_HIDDEN, -1); baseline++; theirline++; @@ -816,14 +895,11 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile { if (m_arYourFile.GetCount() > yourline) { - m_Diff3.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_YOURSADDED, resline, m_arYourFile.GetLineEnding(yourline)); + AddLines(baseline, yourline, theirline); - m_arDiff3LinesBase.Add(baseline); - m_arDiff3LinesYour.Add(yourline); - m_arDiff3LinesTheir.Add(theirline); - - m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_IDENTICALADDED, yourline, m_arYourFile.GetLineEnding(yourline)); - m_TheirBaseBoth.AddData(_T(""), DIFFSTATE_EMPTY, DIFF_EMPTYLINENUMBER, EOL_NOENDING); + m_Diff3.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_YOURSADDED, resline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1); + m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_IDENTICALADDED, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1); + m_TheirBaseBoth.AddEmpty(); yourline++; resline++; @@ -878,5 +954,75 @@ CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFile ASSERT(m_TheirBaseBoth.GetCount() == m_YourBaseBoth.GetCount()); TRACE(_T("done with 3-way diff\n")); + + HideUnchangedSections(&m_Diff3, &m_YourBaseBoth, &m_TheirBaseBoth); + return true; } + +void CDiffData::HideUnchangedSections(CViewData * data1, CViewData * data2, CViewData * data3) +{ + if (data1 == NULL) + return; + + CRegDWORD contextLines = CRegDWORD(_T("Software\\TortoiseGitMerge\\ContextLines"), 1); + + if (data1->GetCount() > 1) + { + HIDESTATE lastHideState = data1->GetHideState(0); + if (lastHideState == HIDESTATE_HIDDEN) + { + data1->SetLineHideState(0, HIDESTATE_MARKER); + if (data2) + data2->SetLineHideState(0, HIDESTATE_MARKER); + if (data3) + data3->SetLineHideState(0, HIDESTATE_MARKER); + } + for (int i = 1; i < data1->GetCount(); ++i) + { + HIDESTATE hideState = data1->GetHideState(i); + if (hideState != lastHideState) + { + if (hideState == HIDESTATE_SHOWN) + { + // go back and show the last 'contextLines' lines to "SHOWN" + int lineback = i - 1; + int stopline = lineback - (int)(DWORD)contextLines; + while ((lineback >= 0)&&(lineback > stopline)) + { + data1->SetLineHideState(lineback, HIDESTATE_SHOWN); + if (data2) + data2->SetLineHideState(lineback, HIDESTATE_SHOWN); + if (data3) + data3->SetLineHideState(lineback, HIDESTATE_SHOWN); + lineback--; + } + } + else if ((hideState == HIDESTATE_HIDDEN)&&(lastHideState != HIDESTATE_MARKER)) + { + // go forward and show the next 'contextLines' lines to "SHOWN" + int lineforward = i + 1; + int stopline = lineforward + (int)(DWORD)contextLines; + while ((lineforward < data1->GetCount())&&(lineforward < stopline)) + { + data1->SetLineHideState(lineforward, HIDESTATE_SHOWN); + if (data2) + data2->SetLineHideState(lineforward, HIDESTATE_SHOWN); + if (data3) + data3->SetLineHideState(lineforward, HIDESTATE_SHOWN); + lineforward++; + } + if ((lineforward < data1->GetCount())&&(data1->GetHideState(lineforward) == HIDESTATE_HIDDEN)) + { + data1->SetLineHideState(lineforward, HIDESTATE_MARKER); + if (data2) + data2->SetLineHideState(lineforward, HIDESTATE_MARKER); + if (data3) + data3->SetLineHideState(lineforward, HIDESTATE_MARKER); + } + } + } + lastHideState = hideState; + } + } +} \ No newline at end of file diff --git a/src/TortoiseMerge/DiffData.h b/src/TortoiseMerge/DiffData.h index 5e3fef17c..5ef7a4a41 100644 --- a/src/TortoiseMerge/DiffData.h +++ b/src/TortoiseMerge/DiffData.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2008 - TortoiseSVN +// Copyright (C) 2006-2008, 2010-2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -18,12 +18,15 @@ // #pragma once +#pragma warning(push) #include "svn_diff.h" #include "apr_pools.h" +#pragma warning(pop) #include "FileTextLines.h" #include "registry.h" #include "WorkingFile.h" #include "ViewData.h" +#include "MovedBlocks.h" @@ -41,9 +44,8 @@ public: BOOL Load(); void SetBlame(bool bBlame = true) {m_bBlame = bBlame;} - int GetLineCount(); - int GetLineActualLength(int index); - LPCTSTR GetLineChars(int index); + void SetMovedBlocks(bool bViewMovedBlocks = true); + int GetLineCount() const; CString GetError() const {return m_sError;} bool IsBaseFileInUse() const { return m_baseFile.InUse(); } @@ -52,9 +54,23 @@ public: private: bool DoTwoWayDiff(const CString& sBaseFilename, const CString& sYourFilename, DWORD dwIgnoreWS, bool bIgnoreEOL, apr_pool_t * pool); + + void StickAndSkip(svn_diff_t * &tempdiff, apr_off_t &original_length_sticked, apr_off_t &modified_length_sticked); bool DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFilename, const CString& sTheirFilename, DWORD dwIgnoreWS, bool bIgnoreEOL, bool bIgnoreCase, apr_pool_t * pool); +/** +* Moved blocks detection for further highlighting, +* implemented exclusively for TwoWayDiff +**/ + tsvn_svn_diff_t_extension * MovedBlocksDetect(svn_diff_t * diffYourBase, DWORD dwIgnoreWS, apr_pool_t * pool); + void TieMovedBlocks(int from, int to, apr_off_t length); + void HideUnchangedSections(CViewData * data1, CViewData * data2, CViewData * data3); + + svn_diff_file_ignore_space_t GetIgnoreSpaceMode(DWORD dwIgnoreWS); + svn_diff_file_options_t * CreateDiffFileOptions(DWORD dwIgnoreWS, bool bIgnoreEOL, apr_pool_t * pool); + bool HandleSvnError(svn_error_t * svnerr); + bool CompareWithIgnoreWS(CString s1, CString s2, DWORD dwIgnoreWS); public: CWorkingFile m_baseFile; CWorkingFile m_theirFile; @@ -65,6 +81,7 @@ public: CString m_sPatchPath; CString m_sPatchOriginal; CString m_sPatchPatched; + bool m_bPatchRequired; public: CFileTextLines m_arBaseFile; @@ -79,17 +96,12 @@ public: CViewData m_TheirBaseLeft; ///< two-pane view, diff between 'theirs' and 'base', left view CViewData m_TheirBaseRight; ///< two-pane view, diff between 'theirs' and 'base', right view - CViewData m_Diff3; ///< thee-pane view, bottom pane - - // the following three arrays are used to check for conflicts even in case the - // user has ignored spaces/eols. - CStdDWORDArray m_arDiff3LinesBase; - CStdDWORDArray m_arDiff3LinesYour; - CStdDWORDArray m_arDiff3LinesTheir; + CViewData m_Diff3; ///< three-pane view, bottom pane CString m_sError; static int abort_on_pool_failure (int retcode); protected: bool m_bBlame; + bool m_bViewMovedBlocks; }; diff --git a/src/TortoiseMerge/DiffStates.h b/src/TortoiseMerge/DiffStates.h index fa1a6cce7..8694f7032 100644 --- a/src/TortoiseMerge/DiffStates.h +++ b/src/TortoiseMerge/DiffStates.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2007-2008 - TortoiseSVN +// Copyright (C) 2007-2008, 2010 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -32,6 +32,8 @@ enum DiffStates DIFFSTATE_ADDEDWHITESPACE, ///< line was added (whitespace diff) DIFFSTATE_WHITESPACE, ///< line differs in whitespaces only DIFFSTATE_WHITESPACE_DIFF, ///< the in-line diffs of whitespaces + DIFFSTATE_MOVED_TO, ///< line was moved here + DIFFSTATE_MOVED_FROM, ///< line was moved from here DIFFSTATE_EMPTY, ///< empty line DIFFSTATE_CONFLICTED, ///< conflicted line DIFFSTATE_CONFLICTED_IGNORED, ///< a conflict which isn't conflicted due to ignore settings diff --git a/src/TortoiseMerge/EOL.h b/src/TortoiseMerge/EOL.h index 6dd3c5a26..f625ee2be 100644 --- a/src/TortoiseMerge/EOL.h +++ b/src/TortoiseMerge/EOL.h @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2007 - TortoiseSVN @@ -25,9 +25,19 @@ enum EOL { EOL_AUTOLINE, - EOL_LF, - EOL_CRLF, + // MS native + EOL_CRLF, ///< CR (U+000D) followed by LF (U+000A) + // foregin + EOL_LF, ///< Line Feed, U+000A + EOL_CR, ///< Carriage Return, U+000D + // exotic - diff needs conversion EOL_LFCR, - EOL_CR, + EOL_VT, ///< Vertical Tab, U+000B + EOL_FF, ///< Form Feed, U+000C + EOL_NEL, ///< Next Line, U+0085 + EOL_LS, ///< Line Separator, U+2028 + EOL_PS, ///< Paragraph Separator, U+2029 EOL_NOENDING, + + EOL__COUNT }; diff --git a/src/TortoiseMerge/EditGotoDlg.cpp b/src/TortoiseMerge/EditGotoDlg.cpp deleted file mode 100644 index 7bb697db9..000000000 --- a/src/TortoiseMerge/EditGotoDlg.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// TortoiseGit - a Windows shell extension for easy version control - -// Copyright (C) 2008-2012 - TortoiseGit - -// 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, -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -// EditGoto.cpp : implementation file -// - -#include "stdafx.h" -#include "TortoiseMerge.h" -#include "EditGotoDlg.h" - - -// CEditGotoDlg dialog - -IMPLEMENT_DYNAMIC(CEditGotoDlg, CDialog) - -CEditGotoDlg::CEditGotoDlg(CWnd* pParent /*=NULL*/) - : CDialog(CEditGotoDlg::IDD, pParent) - , m_LineNumber(0) -{ - -} - -CEditGotoDlg::~CEditGotoDlg() -{ -} - -void CEditGotoDlg::DoDataExchange(CDataExchange* pDX) -{ - CDialog::DoDataExchange(pDX); - DDX_Text(pDX, IDC_LINENUMBER, m_LineNumber); - DDV_MinMaxUInt(pDX, m_LineNumber, 0, 40000000); -} - - -BEGIN_MESSAGE_MAP(CEditGotoDlg, CDialog) - -END_MESSAGE_MAP() - - -// CEditGotoDlg message handlers diff --git a/src/TortoiseMerge/FilePatchesDlg.cpp b/src/TortoiseMerge/FilePatchesDlg.cpp index 3d682f675..cefb199ca 100644 --- a/src/TortoiseMerge/FilePatchesDlg.cpp +++ b/src/TortoiseMerge/FilePatchesDlg.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006, 2008, 2010-2011 - TortoiseSVN +// Copyright (C) 2006, 2008, 2010-2012 - TortoiseSVN // Copyright (C) 2012 - Sven Strickroth // This program is free software; you can redistribute it and/or @@ -20,7 +20,7 @@ #include "stdafx.h" #include "TortoiseMerge.h" #include "FilePatchesDlg.h" -#include "Patch.h" +#include "GitPatch.h" #include "AppUtils.h" #include "PathUtils.h" #include "SysProgressDlg.h" @@ -29,17 +29,21 @@ IMPLEMENT_DYNAMIC(CFilePatchesDlg, CResizableStandAloneDialog) CFilePatchesDlg::CFilePatchesDlg(CWnd* pParent /*=NULL*/) : CResizableStandAloneDialog(CFilePatchesDlg::IDD, pParent) + , m_ShownIndex(-1) , m_bMinimized(FALSE) , m_pPatch(NULL) , m_pCallBack(NULL) , m_nWindowHeight(-1) , m_pMainFrame(NULL) + , m_boldFont(NULL) { m_ImgList.Create(16, 16, ILC_COLOR16 | ILC_MASK, 4, 1); } CFilePatchesDlg::~CFilePatchesDlg() { + if (m_boldFont) + DeleteObject(m_boldFont); } void CFilePatchesDlg::DoDataExchange(CDataExchange* pDX) @@ -52,9 +56,9 @@ BOOL CFilePatchesDlg::SetFileStatusAsPatched(CString sPath) { for (int i=0; iGetFullPath(m_sPath, i)) == 0 || sPath.CompareNoCase(m_pPatch->GetFullPath(m_sPath, i, 1)) == 0) + if (sPath.CompareNoCase(GetFullPath(i))==0) { - m_arFileStates.SetAt(i, FPDLG_FILESTATE_PATCHED); + m_arFileStates.SetAt(i, (DWORD)FPDLG_FILESTATE_PATCHED); Invalidate(); return TRUE; } @@ -62,12 +66,30 @@ BOOL CFilePatchesDlg::SetFileStatusAsPatched(CString sPath) return FALSE; } +CString CFilePatchesDlg::GetFullPath(int nIndex) +{ + CString temp = m_pPatch->GetStrippedPath(nIndex); + temp.Replace('/', '\\'); + //temp = temp.Mid(temp.Find('\\')+1); + if (PathIsRelative(temp)) + temp = m_sPath + temp; + return temp; +} + BOOL CFilePatchesDlg::OnInitDialog() { CResizableStandAloneDialog::OnInitDialog(); // hide the grip since it would overlap with the "path all" button -// m_wndGrip.ShowWindow(SW_HIDE); m_nShowCount = -100; +#if 0 + HideGrip(); +#endif + + HFONT hFont = (HFONT)m_cFileList.SendMessage(WM_GETFONT); + LOGFONT lf = {0}; + GetObject(hFont, sizeof(LOGFONT), &lf); + lf.lfWeight = FW_BOLD; + m_boldFont = CreateFontIndirect(&lf); AddAnchor(IDC_FILELIST, TOP_LEFT, BOTTOM_RIGHT); AddAnchor(IDC_PATCHSELECTEDBUTTON, BOTTOM_LEFT, BOTTOM_RIGHT); @@ -76,7 +98,7 @@ BOOL CFilePatchesDlg::OnInitDialog() return TRUE; } -BOOL CFilePatchesDlg::Init(CPatch * pPatch, CPatchFilesDlgCallBack * pCallBack, CString sPath, CWnd * pParent) +BOOL CFilePatchesDlg::Init(GitPatch * pPatch, CPatchFilesDlgCallBack * pCallBack, CString sPath, CWnd * pParent) { if ((pCallBack==NULL)||(pPatch==NULL)) { @@ -118,43 +140,19 @@ BOOL CFilePatchesDlg::Init(CPatch * pPatch, CPatchFilesDlgCallBack * pCallBack, for(int i=0; iGetNumberOfFiles(); i++) { - CString sFile = CPathUtils::GetFileNameFromPath(m_pPatch->GetFilename2(i)); - if(sFile == _T("NUL")) - sFile = CPathUtils::GetFileNameFromPath(m_pPatch->GetFilename(i)); + CString sFile = CPathUtils::GetFileNameFromPath(m_pPatch->GetStrippedPath(i)); - DWORD state; + int state; if (m_sPath.IsEmpty()) - state = FPDLG_FILESTATE_GOOD; + state = 0; else { - state = 0; - if(m_pPatch->GetFilename(i) != m_pPatch->GetFilename2(i)) - { - if( m_pPatch->GetFilename(i) == _T("NUL")) - state = FPDLG_FILESTATE_NEW; - else if (m_pPatch->GetFilename2(i) == _T("NUL")) - state = FPDLG_FILESTATE_DELETE; - else - state = FPDLG_FILESTATE_RENAME; - } - - int doesApply = m_pPatch->PatchFile(i, m_sPath); - if (doesApply == 2) - state = FPDLG_FILESTATE_PATCHED; - else if (doesApply) - { - if(state != FPDLG_FILESTATE_NEW && - state != FPDLG_FILESTATE_RENAME && - state != FPDLG_FILESTATE_DELETE) - state = FPDLG_FILESTATE_GOOD; - } - else - state = FPDLG_FILESTATE_CONFLICTED; + state = m_pPatch->GetFailedHunks(i); } m_arFileStates.Add(state); SHFILEINFO sfi; SHGetFileInfo( - m_pPatch->GetFullPath(m_sPath, i), + GetFullPath(i), FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO), @@ -224,18 +222,18 @@ void CFilePatchesDlg::OnLvnGetInfoTipFilelist(NMHDR *pNMHDR, LRESULT *pResult) { LPNMLVGETINFOTIP pGetInfoTip = reinterpret_cast(pNMHDR); - CString temp = m_pPatch->GetFullPath(m_sPath, pGetInfoTip->iItem); - CString temp2 = m_pPatch->GetFullPath(m_sPath, pGetInfoTip->iItem, 1); - - if(temp != temp2) + if (m_arFileStates.GetCount() > pGetInfoTip->iItem) { - if(temp == _T("NUL")) - _tcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, _T("New file: ") + temp2, pGetInfoTip->cchTextMax); - else if(temp2 == _T("NUL")) - _tcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, _T("Delete file: ") + temp, pGetInfoTip->cchTextMax); + CString temp; + if (m_arFileStates.GetAt(pGetInfoTip->iItem) == 0) + temp = GetFullPath(pGetInfoTip->iItem); + else + temp.Format(IDS_PATCH_ITEMTT, (LPCTSTR)GetFullPath(pGetInfoTip->iItem), m_arFileStates.GetAt(pGetInfoTip->iItem)); + _tcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, temp, pGetInfoTip->cchTextMax); } else - _tcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, temp, pGetInfoTip->cchTextMax); + pGetInfoTip->pszText[0] = 0; + *pResult = 0; } @@ -249,14 +247,18 @@ void CFilePatchesDlg::OnNMDblclkFilelist(NMHDR *pNMHDR, LRESULT *pResult) return; if (m_sPath.IsEmpty()) { - m_pCallBack->DiffFiles(m_pPatch->GetFullPath(m_sPath, pNMLV->iItem), m_pPatch->GetRevision(pNMLV->iItem), - m_pPatch->GetFilename2(pNMLV->iItem), m_pPatch->GetRevision2(pNMLV->iItem)); + m_pCallBack->DiffFiles(GetFullPath(pNMLV->iItem), _T(""), + _T(""), _T("")); + m_ShownIndex = pNMLV->iItem; + m_cFileList.Invalidate(); } else { if (m_arFileStates.GetAt(pNMLV->iItem)!=FPDLG_FILESTATE_PATCHED) { - m_pCallBack->PatchFile(pNMLV->iItem, false, true); + m_pCallBack->PatchFile(m_pPatch->GetStrippedPath(pNMLV->iItem), m_pPatch->GetContentMods(pNMLV->iItem), m_pPatch->GetPropMods(pNMLV->iItem), _T("")); + m_ShownIndex = pNMLV->iItem; + m_cFileList.Invalidate(); } } } @@ -285,36 +287,28 @@ void CFilePatchesDlg::OnNMCustomdrawFilelist(NMHDR *pNMHDR, LRESULT *pResult) if (m_arFileStates.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec) { - if (m_arFileStates.GetAt(pLVCD->nmcd.dwItemSpec)==FPDLG_FILESTATE_CONFLICTED) - { - crText = RGB(200, 0, 0); - } - - if (m_arFileStates.GetAt(pLVCD->nmcd.dwItemSpec)==FPDLG_FILESTATE_DELETE) + if (m_arFileStates.GetAt(pLVCD->nmcd.dwItemSpec)==FPDLG_FILESTATE_ERROR) { crText = RGB(200, 0, 0); } - - if (m_arFileStates.GetAt(pLVCD->nmcd.dwItemSpec)==FPDLG_FILESTATE_RENAME) + if (m_arFileStates.GetAt(pLVCD->nmcd.dwItemSpec)>0) { - crText = RGB(0, 200, 200); + crText = RGB(100, 0, 0); } - - if (m_arFileStates.GetAt(pLVCD->nmcd.dwItemSpec)==FPDLG_FILESTATE_NEW) - { - crText = RGB(0, 0, 200); - } - if (m_arFileStates.GetAt(pLVCD->nmcd.dwItemSpec)==FPDLG_FILESTATE_PATCHED) { crText = ::GetSysColor(COLOR_GRAYTEXT); } // Store the color back in the NMLVCUSTOMDRAW struct. pLVCD->clrText = crText; + if (m_ShownIndex == (int)pLVCD->nmcd.dwItemSpec) + { + SelectObject(pLVCD->nmcd.hdc, m_boldFont); + // We changed the font, so we're returning CDRF_NEWFONT. This + // tells the control to recalculate the extent of the text. + *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT; + } } - - // Tell Windows to paint the control itself. - *pResult = CDRF_DODEFAULT; } } @@ -332,14 +326,10 @@ void CFilePatchesDlg::OnNMRclickFilelist(NMHDR * /*pNMHDR*/, LRESULT *pResult) if (!popup.CreatePopupMenu()) return; - UINT nFlags = MF_STRING | (m_cFileList.GetSelectedCount() == 1 ? MF_ENABLED : MF_DISABLED | MF_GRAYED); - - temp.LoadString(IDS_PATCH_REVIEW); - popup.AppendMenu(nFlags, ID_PATCH_REVIEW, temp); - popup.SetDefaultItem(ID_PATCH_REVIEW, FALSE); - + UINT nFlags = MF_STRING | (m_cFileList.GetSelectedCount()==1 ? MF_ENABLED : MF_DISABLED | MF_GRAYED); temp.LoadString(IDS_PATCH_PREVIEW); popup.AppendMenu(nFlags, ID_PATCHPREVIEW, temp); + popup.SetDefaultItem(ID_PATCHPREVIEW, FALSE); temp.LoadString(IDS_PATCH_ALL); popup.AppendMenu(MF_STRING | MF_ENABLED, ID_PATCHALL, temp); @@ -357,23 +347,18 @@ void CFilePatchesDlg::OnNMRclickFilelist(NMHDR * /*pNMHDR*/, LRESULT *pResult) point = rect.CenterPoint(); } - bool bReview=false; - - int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0); + int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this, 0); switch (cmd) { - case ID_PATCH_REVIEW: - bReview = true; - //go through case case ID_PATCHPREVIEW: + if (m_pCallBack) { - if (m_pCallBack) + int nIndex = m_cFileList.GetSelectionMark(); + if ( m_arFileStates.GetAt(nIndex)!=FPDLG_FILESTATE_PATCHED) { - int nIndex = m_cFileList.GetSelectionMark(); - if ( m_arFileStates.GetAt(nIndex)!=FPDLG_FILESTATE_PATCHED) - { - m_pCallBack->PatchFile(nIndex, false, bReview); - } + m_pCallBack->PatchFile(m_pPatch->GetStrippedPath(nIndex), m_pPatch->GetContentMods(nIndex), m_pPatch->GetPropMods(nIndex), _T("")); + m_ShownIndex = nIndex; + m_cFileList.Invalidate(); } } break; @@ -465,14 +450,14 @@ void CFilePatchesDlg::PatchAll() progDlg.SetShowProgressBar(true); progDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PATCH_ALL))); progDlg.ShowModeless(m_hWnd); - - BOOL ret = TRUE; - for (int i=0; iGetFullPath(m_sPath, i), true); - ret = m_pCallBack->PatchFile(i, true, false); + progDlg.SetLine(2, GetFullPath(i), true); + m_pCallBack->PatchFile(m_pPatch->GetStrippedPath(i), m_pPatch->GetContentMods(i), m_pPatch->GetPropMods(i), _T(""), TRUE); + m_ShownIndex = i; + m_cFileList.Invalidate(); } progDlg.SetProgress64(i, m_arFileStates.GetCount()); } @@ -489,7 +474,6 @@ void CFilePatchesDlg::PatchSelected() progDlg.SetShowProgressBar(true); progDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PATCH_SELECTED))); progDlg.ShowModeless(m_hWnd); - // The list cannot be sorted by user, so the order of the // items in the list is identical to the order in the array // m_arFileStates. @@ -497,13 +481,14 @@ void CFilePatchesDlg::PatchSelected() int count = 1; POSITION pos = m_cFileList.GetFirstSelectedItemPosition(); int index; - BOOL ret = TRUE; - while (((index = m_cFileList.GetNextSelectedItem(pos)) >= 0) && (!progDlg.HasUserCancelled()) && ret == TRUE) + while (((index = m_cFileList.GetNextSelectedItem(pos)) >= 0) && (!progDlg.HasUserCancelled())) { if (m_arFileStates.GetAt(index)!= FPDLG_FILESTATE_PATCHED) { - progDlg.SetLine(2, m_pPatch->GetFullPath(m_sPath, index), true); - ret = m_pCallBack->PatchFile(index, true, false); + progDlg.SetLine(2, GetFullPath(index), true); + m_pCallBack->PatchFile(m_pPatch->GetStrippedPath(index), m_pPatch->GetContentMods(index), m_pPatch->GetPropMods(index), _T(""), TRUE); + m_ShownIndex = index; + m_cFileList.Invalidate(); } progDlg.SetProgress64(count++, selCount); } @@ -517,4 +502,3 @@ void CFilePatchesDlg::OnLvnItemchangedFilelist(NMHDR * /*pNMHDR*/, LRESULT *pRes *pResult = 0; } - diff --git a/src/TortoiseMerge/FilePatchesDlg.h b/src/TortoiseMerge/FilePatchesDlg.h index bf5013a27..d91067dc8 100644 --- a/src/TortoiseMerge/FilePatchesDlg.h +++ b/src/TortoiseMerge/FilePatchesDlg.h @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2006, 2008, 2010-2011 - TortoiseSVN @@ -19,7 +19,7 @@ #pragma once #include "StandAloneDlg.h" -class CPatch; +class GitPatch; /** * \ingroup TortoiseMerge @@ -37,7 +37,7 @@ public: * \param sVersion the revision number of the file to patch * \return TRUE if patching was successful */ - virtual BOOL PatchFile(const int nIndex, bool bAutoPatch = false, bool bIsReview = true) = 0; + virtual BOOL PatchFile(CString sFilePath, bool bContentMods, bool bPropMods, CString sVersion, BOOL bAutoPatch = FALSE) = 0; /** * Callback function. Called when the user double clicks on a @@ -52,17 +52,13 @@ public: virtual BOOL DiffFiles(CString sURL1, CString sRev1, CString sURL2, CString sRev2) = 0; }; -#define FPDLG_FILESTATE_GOOD 0x0000 -#define FPDLG_FILESTATE_CONFLICTED 0x0001 -#define FPDLG_FILESTATE_PATCHED 0x0002 -#define FPDLG_FILESTATE_NEW 0x0003 -#define FPDLG_FILESTATE_DELETE 0x0004 -#define FPDLG_FILESTATE_RENAME 0x0005 +#define FPDLG_FILESTATE_GOOD 0 +#define FPDLG_FILESTATE_ERROR (-1) +#define FPDLG_FILESTATE_PATCHED (-2) #define ID_PATCHALL 1 #define ID_PATCHSELECTED 2 #define ID_PATCHPREVIEW 3 -#define ID_PATCH_REVIEW 4 /** * \ingroup TortoiseMerge * @@ -83,13 +79,13 @@ public: * \param sPath The path to the "parent" folder where the patch gets applied to * \return TRUE if successful */ - BOOL Init(CPatch * pPatch, CPatchFilesDlgCallBack * pCallBack, CString sPath, CWnd * pParent); + BOOL Init(GitPatch * pPatch, CPatchFilesDlgCallBack * pCallBack, CString sPath, CWnd * pParent); BOOL SetFileStatusAsPatched(CString sPath); bool HasFiles() {return m_cFileList.GetItemCount()>0;} enum { IDD = IDD_FILEPATCHES }; protected: - CPatch * m_pPatch; + GitPatch * m_pPatch; CPatchFilesDlgCallBack * m_pCallBack; CString m_sPath; CListCtrl m_cFileList; @@ -98,6 +94,8 @@ protected: BOOL m_bMinimized; int m_nWindowHeight; CWnd * m_pMainFrame; + int m_ShownIndex; + HFONT m_boldFont; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support virtual void OnOK(); @@ -115,6 +113,7 @@ protected: DECLARE_MESSAGE_MAP() + CString GetFullPath(int nIndex); void SetTitleWithPath(int width); void PatchAll(); void PatchSelected(); diff --git a/src/TortoiseMerge/FileTextLines.cpp b/src/TortoiseMerge/FileTextLines.cpp dissimilarity index 70% index 6eccd82dd..e7adc1b0e 100644 --- a/src/TortoiseMerge/FileTextLines.cpp +++ b/src/TortoiseMerge/FileTextLines.cpp @@ -1,714 +1,781 @@ -// TortoiseMerge - a Diff/Patch program - -// Copyright (C) 2007-2011 - TortoiseSVN - -// 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, -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// -#include "stdafx.h" -#include "resource.h" -#include "UnicodeUtils.h" -#include "registry.h" -#include "filetextlines.h" -#include "FormatMessageWrapper.h" -#include "SmartHandle.h" -#include "auto_buffer.h" - -wchar_t WideCharSwap(wchar_t nValue) -{ - return (((nValue>> 8)) | (nValue << 8)); -} - -CFileTextLines::CFileTextLines(void) - : m_UnicodeType(CFileTextLines::AUTOTYPE) - , m_LineEndings(EOL_AUTOLINE) - , m_bReturnAtEnd(false) -{ -} - -CFileTextLines::~CFileTextLines(void) -{ -} - -CFileTextLines::UnicodeType CFileTextLines::CheckUnicodeType(LPVOID pBuffer, int cb) -{ - if (cb < 2) - return CFileTextLines::ASCII; - UINT16 * pVal16 = (UINT16 *)pBuffer; - UINT8 * pVal8 = (UINT8 *)(pVal16+1); - // scan the whole buffer for a 0x0000 sequence - // if found, we assume a binary file - for (int i=0; i<(cb-2); i=i+2) - { - if (0x0000 == *pVal16++) - return CFileTextLines::BINARY; - } - pVal16 = (UINT16 *)pBuffer; - if (*pVal16 == 0xFEFF) - return CFileTextLines::UNICODE_LE; - if (*pVal16 == 0xFFFE) - return CFileTextLines::UNICODE_BE; - if (cb < 3) - return ASCII; - if (*pVal16 == 0xBBEF) - { - if (*pVal8 == 0xBF) - return CFileTextLines::UTF8BOM; - } - // check for illegal UTF8 chars - pVal8 = (UINT8 *)pBuffer; - for (int i=0; i= 0xF5)) - return CFileTextLines::ASCII; - pVal8++; - } - pVal8 = (UINT8 *)pBuffer; - bool bUTF8 = false; - bool bNonANSI = false; - for (int i=0; i<(cb-3); ++i) - { - if (*pVal8 > 127) - bNonANSI = true; - if ((*pVal8 & 0xE0)==0xC0) - { - pVal8++;i++; - if ((*pVal8 & 0xC0)!=0x80) - return CFileTextLines::ASCII; - bUTF8 = true; - } - if ((*pVal8 & 0xF0)==0xE0) - { - pVal8++;i++; - if ((*pVal8 & 0xC0)!=0x80) - return CFileTextLines::ASCII; - pVal8++;i++; - if ((*pVal8 & 0xC0)!=0x80) - return CFileTextLines::ASCII; - bUTF8 = true; - } - if ((*pVal8 & 0xF8)==0xF0) - { - pVal8++;i++; - if ((*pVal8 & 0xC0)!=0x80) - return CFileTextLines::ASCII; - pVal8++;i++; - if ((*pVal8 & 0xC0)!=0x80) - return CFileTextLines::ASCII; - pVal8++;i++; - if ((*pVal8 & 0xC0)!=0x80) - return CFileTextLines::ASCII; - bUTF8 = true; - } - pVal8++; - } - if (bUTF8) - return CFileTextLines::UTF8; - if ((!bNonANSI)&&(DWORD(CRegDWORD(_T("Software\\TortoiseGitMerge\\UseUTF8"), FALSE)))) - return CFileTextLines::UTF8; - return CFileTextLines::ASCII; -} - - -EOL CFileTextLines::CheckLineEndings(LPVOID pBuffer, int cb) -{ - EOL retval = EOL_AUTOLINE; - char * buf = (char *)pBuffer; - for (int i=0; iGetErrorMessage(exceptionError, _countof(exceptionError)); - m_sErrorString = exceptionError; - return FALSE; - } - DWORD dwReadBytes = 0; - if (!ReadFile(hFile, pFileBuf, fsize.LowPart, &dwReadBytes, NULL)) - { - delete [] pFileBuf; - SetErrorString(); - return FALSE; - } - if (m_UnicodeType == CFileTextLines::AUTOTYPE) - { - m_UnicodeType = this->CheckUnicodeType(pFileBuf, dwReadBytes); - } - if (m_LineEndings == EOL_AUTOLINE) - { - m_LineEndings = CheckLineEndings(pFileBuf, min(10000, dwReadBytes)); - } - hFile.CloseHandle(); - - if (m_UnicodeType == CFileTextLines::BINARY) - { - m_sErrorString.Format(IDS_ERR_FILE_BINARY, (LPCTSTR)sFilePath); - delete [] pFileBuf; - return FALSE; - } - - // we may have to convert the file content - if ((m_UnicodeType == UTF8)||(m_UnicodeType == UTF8BOM)) - { - int ret = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pFileBuf, dwReadBytes, NULL, 0); - wchar_t * pWideBuf = NULL; - try - { - pWideBuf = new wchar_t[ret]; - } - catch (CMemoryException* e) - { - e->GetErrorMessage(exceptionError, _countof(exceptionError)); - m_sErrorString = exceptionError; - delete [] pFileBuf; - return FALSE; - } - int ret2 = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pFileBuf, dwReadBytes, pWideBuf, ret); - if (ret2 == ret) - { - delete [] pFileBuf; - pFileBuf = pWideBuf; - dwReadBytes = ret2; - } else - delete [] pWideBuf; - } - else if (m_UnicodeType == ASCII) - { - int ret = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)pFileBuf, dwReadBytes, NULL, 0); - wchar_t * pWideBuf = NULL; - try - { - pWideBuf = new wchar_t[ret]; - } - catch (CMemoryException* e) - { - e->GetErrorMessage(exceptionError, _countof(exceptionError)); - m_sErrorString = exceptionError; - delete [] pFileBuf; - return FALSE; - } - int ret2 = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)pFileBuf, dwReadBytes, pWideBuf, ret); - if (ret2 == ret) - { - delete [] pFileBuf; - pFileBuf = pWideBuf; - dwReadBytes = ret2; - } - else - delete [] pWideBuf; - } - // fill in the lines into the array - wchar_t * pTextBuf = pFileBuf; - wchar_t * pLineStart = pFileBuf; - if ((m_UnicodeType == UNICODE_LE)||(m_UnicodeType == UNICODE_BE)) - { - // UTF16 have two bytes per char - dwReadBytes/=2; - } - if ((m_UnicodeType == UTF8BOM)||(m_UnicodeType == UNICODE_LE)||(m_UnicodeType == UNICODE_BE)) - { - // ignore the BOM - ++pTextBuf; - ++pLineStart; - --dwReadBytes; - } - - if (m_UnicodeType == UNICODE_BE) - { - // swap the bytes to little-endian order to get proper strings in wchar_t format - wchar_t * pSwapBuf = pTextBuf; - for (DWORD i = 0; i 66) - sLine = sLine.Mid(66); - } - switch (dwIgnoreWhitespaces) - { - case 0: - // Compare whitespaces - // do nothing - break; - case 1: - // Ignore all whitespaces - sLine.TrimLeft(_T(" \t")); - sLine.TrimRight(_T(" \t")); - break; - case 2: - // Ignore leading whitespace - sLine.TrimLeft(_T(" \t")); - break; - case 3: - // Ignore ending whitespace - sLine.TrimRight(_T(" \t")); - break; - } -} - -void CFileTextLines::StripAsciiWhiteSpace(CStringA& sLine,DWORD dwIgnoreWhitespaces, bool blame) -{ - if (blame) - { - if (sLine.GetLength() > 66) - sLine = sLine.Mid(66); - } - switch (dwIgnoreWhitespaces) - { - case 0: // Compare whitespaces - // do nothing - break; - case 1: - // Ignore all whitespaces - StripAsciiWhiteSpace(sLine); - break; - case 2: - // Ignore leading whitespace - sLine.TrimLeft(" \t"); - break; - case 3: - // Ignore leading whitespace - sLine.TrimRight(" \t"); - break; - } -} - -// -// Fast in-place removal of spaces and tabs from CStringA line -// -void CFileTextLines::StripAsciiWhiteSpace(CStringA& sLine) -{ - int outputLen = 0; - char* pWriteChr = sLine.GetBuffer(sLine.GetLength()); - const char* pReadChr = pWriteChr; - while(*pReadChr) - { - if(*pReadChr != ' ' && *pReadChr != '\t') - { - *pWriteChr++ = *pReadChr; - outputLen++; - } - ++pReadChr; - } - *pWriteChr = '\0'; - sLine.ReleaseBuffer(outputLen); -} - -BOOL CFileTextLines::Save(const CString& sFilePath, bool bSaveAsUTF8, DWORD dwIgnoreWhitespaces /*=0*/, BOOL bIgnoreCase /*= FALSE*/, bool bBlame /*= false*/) -{ - try - { - CString destPath = sFilePath; - // now make sure that the destination directory exists - int ind = 0; - while (destPath.Find('\\', ind)>=2) - { - if (!PathIsDirectory(destPath.Left(destPath.Find('\\', ind)))) - { - if (!CreateDirectory(destPath.Left(destPath.Find('\\', ind)), NULL)) - return FALSE; - } - ind = destPath.Find('\\', ind)+1; - } - - CStdioFile file; // Hugely faster than CFile for big file writes - because it uses buffering - if (!file.Open(sFilePath, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary)) - { - m_sErrorString.Format(IDS_ERR_FILE_OPEN, (LPCTSTR)sFilePath); - return FALSE; - } - if ((!bSaveAsUTF8)&&(m_UnicodeType == CFileTextLines::UNICODE_LE)) - { - //first write the BOM - UINT16 wBOM = 0xFEFF; - file.Write(&wBOM, 2); - for (int i=0; i beBuf(linebuflen); - //first write the BOM - UINT16 wBOM = 0xFFFE; - file.Write(&wBOM, 2); - for (int i=0; i linebuflen) - { - // increase buffer size if necessary - linebuflen = bytelen + 1024; - beBuf.reset(linebuflen); - } - for (int spos = 0; spos < bytelen; ) - { - // swap the bytes to big-endian order - wchar_t c = sLine[spos/2]; - beBuf[spos++] = c>>8; - beBuf[spos++] = c & 0xFF; - } - file.Write(beBuf.get(), sLine.GetLength()*sizeof(WCHAR)); - if (ending == EOL_AUTOLINE) - ending = m_LineEndings; - switch (ending) - { - case EOL_CR: - sLine = _T("\x0d"); - break; - case EOL_CRLF: - case EOL_AUTOLINE: - sLine = _T("\x0d\x0a"); - break; - case EOL_LF: - sLine = _T("\x0a"); - break; - case EOL_LFCR: - sLine = _T("\x0a\x0d"); - break; - default: - sLine.Empty(); - break; - } - if ((m_bReturnAtEnd)||(i != GetCount()-1)) - { - // swap the bytes to big-endian order - BYTE buf[5]; - int p = 0; - if (sLine.GetLength() > 0) - { - wchar_t c = sLine[0]; - buf[p++] = c>>8; - buf[p++] = c & 0xFF; - } - if (sLine.GetLength() > 1) - { - wchar_t c = sLine[1]; - buf[p++] = c>>8; - buf[p++] = c & 0xFF; - } - - file.Write(buf, sLine.GetLength()*sizeof(TCHAR)); - } - } - } - else if ((!bSaveAsUTF8)&&((m_UnicodeType == CFileTextLines::ASCII)||(m_UnicodeType == CFileTextLines::AUTOTYPE))) - { - for (int i=0; i< GetCount(); i++) - { - // Copy CString to 8 bit without conversion - CString sLineT = GetAt(i); - CStringA sLine = CStringA(sLineT); - EOL ending = GetLineEnding(i); - - StripAsciiWhiteSpace(sLine,dwIgnoreWhitespaces, bBlame); - if (bIgnoreCase) - sLine = sLine.MakeLower(); - if ((m_bReturnAtEnd)||(i != GetCount()-1)) - { - if (ending == EOL_AUTOLINE) - ending = m_LineEndings; - switch (ending) - { - case EOL_CR: - sLine += '\x0d'; - break; - case EOL_CRLF: - case EOL_AUTOLINE: - sLine.Append("\x0d\x0a", 2); - break; - case EOL_LF: - sLine += '\x0a'; - break; - case EOL_LFCR: - sLine.Append("\x0a\x0d", 2); - break; - } - } - file.Write((LPCSTR)sLine, sLine.GetLength()); - } - } - else if ((bSaveAsUTF8)||((m_UnicodeType == CFileTextLines::UTF8BOM)||(m_UnicodeType == CFileTextLines::UTF8))) - { - if (m_UnicodeType == CFileTextLines::UTF8BOM) - { - //first write the BOM - UINT16 wBOM = 0xBBEF; - file.Write(&wBOM, 2); - UINT8 uBOM = 0xBF; - file.Write(&uBOM, 1); - } - for (int i=0; iGetErrorMessage(m_sErrorString.GetBuffer(4096), 4096); - m_sErrorString.ReleaseBuffer(); - e->Delete(); - return FALSE; - } - return TRUE; -} - -void CFileTextLines::SetErrorString() -{ - m_sErrorString = CFormatMessageWrapper(); -} - -void CFileTextLines::CopySettings(CFileTextLines * pFileToCopySettingsTo) -{ - if (pFileToCopySettingsTo) - { - pFileToCopySettingsTo->m_UnicodeType = m_UnicodeType; - pFileToCopySettingsTo->m_LineEndings = m_LineEndings; - pFileToCopySettingsTo->m_bReturnAtEnd = m_bReturnAtEnd; - } -} +// TortoiseGitMerge - a Diff/Patch program + +// Copyright (C) 2007-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#include "stdafx.h" +#include "resource.h" +#include "UnicodeUtils.h" +#include "registry.h" +#include "filetextlines.h" +#include "FormatMessageWrapper.h" +#include "SmartHandle.h" + +wchar_t inline WideCharSwap(wchar_t nValue) +{ + return (((nValue>> 8)) | (nValue << 8)); + //return _byteswap_ushort(nValue); +} + +UINT64 inline WordSwapBytes(UINT64 nValue) +{ + return ((nValue&0xff00ff00ff00ff)<<8) | ((nValue>>8)&0xff00ff00ff00ff); // swap BYTESs in WORDs +} + +UINT32 inline DwordSwapBytes(UINT32 nValue) +{ + UINT32 nRet = (nValue<<16) | (nValue>>16); // swap WORDs + nRet = ((nRet&0xff00ff)<<8) | ((nRet>>8)&0xff00ff); // swap BYTESs in WORDs + return nRet; + //return _byteswap_ulong(nValue); +} + +UINT64 inline DwordSwapBytes(UINT64 nValue) +{ + UINT64 nRet = ((nValue&0xffff0000ffffL)<<16) | ((nValue>>16)&0xffff0000ffffL); // swap WORDs in DWORDs + nRet = ((nRet&0xff00ff00ff00ff)<<8) | ((nRet>>8)&0xff00ff00ff00ff); // swap BYTESs in WORDs + return nRet; +} + +CFileTextLines::CFileTextLines(void) + : m_UnicodeType(CFileTextLines::AUTOTYPE) + , m_LineEndings(EOL_AUTOLINE) + , m_bNeedsConversion(false) +{ +} + +CFileTextLines::~CFileTextLines(void) +{ +} + +CFileTextLines::UnicodeType CFileTextLines::CheckUnicodeType(LPVOID pBuffer, int cb) +{ + if (cb < 2) + return CFileTextLines::ASCII; + const UINT32 * const pVal32 = (UINT32 *)pBuffer; + const UINT16 * const pVal16 = (UINT16 *)pBuffer; + const UINT8 * const pVal8 = (UINT8 *)pBuffer; + // scan the whole buffer for a 0x00000000 sequence + // if found, we assume a binary file + int nDwords = cb/4; + for (int i=0; i=4 ) + { + if (*pVal32 == 0x0000FEFF) + { + return CFileTextLines::UTF32_LE; + } + if (*pVal32 == 0xFFFE0000) + { + return CFileTextLines::UTF32_BE; + } + } + if (*pVal16 == 0xFEFF) + { + return CFileTextLines::UTF16_LE; + } + if (*pVal16 == 0xFFFE) + { + return CFileTextLines::UTF16_BE; + } + if (cb < 3) + return CFileTextLines::ASCII; + if (*pVal16 == 0xBBEF) + { + if (pVal8[2] == 0xBF) + return CFileTextLines::UTF8BOM; + } + // check for illegal UTF8 sequences + bool bNonANSI = false; + int nNeedData = 0; + int i=0; + // run fast for ascii + for (; i=0xf5) + return CFileTextLines::ASCII; + nNeedData = 3; + } + else + return CFileTextLines::ASCII; + } + if (bNonANSI && nNeedData==0) + // if get here thru nonAscii and no missing data left then its valid UTF8 + return CFileTextLines::UTF8; + if ((!bNonANSI)&&(DWORD(CRegDWORD(_T("Software\\TortoiseGitMerge\\UseUTF8"), FALSE)))) + return CFileTextLines::UTF8; + return CFileTextLines::ASCII; +} + + +BOOL CFileTextLines::Load(const CString& sFilePath, int lengthHint /* = 0*/) +{ + WCHAR exceptionError[1000] = {0}; + m_LineEndings = EOL_AUTOLINE; + m_UnicodeType = CFileTextLines::AUTOTYPE; + RemoveAll(); + if(lengthHint != 0) + { + Reserve(lengthHint); + } + + if (PathIsDirectory(sFilePath)) + { + m_sErrorString.Format(IDS_ERR_FILE_NOTAFILE, (LPCTSTR)sFilePath); + return FALSE; + } + + if (!PathFileExists(sFilePath)) + { + //file does not exist, so just return SUCCESS + return TRUE; + } + + CAutoFile hFile = CreateFile(sFilePath, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); + if (!hFile) + { + SetErrorString(); + return FALSE; + } + + LARGE_INTEGER fsize; + if (!GetFileSizeEx(hFile, &fsize)) + { + SetErrorString(); + return FALSE; + } + if (fsize.HighPart) + { + // file is way too big for us + m_sErrorString.LoadString(IDS_ERR_FILE_TOOBIG); + return FALSE; + } + + // create buffer + // If new[] was done for type T delete[] must be called on a pointer of type T*, + // otherwise the behavior is undefined. + // +1 is to address possible truncation when integer division is done + CBuffer oFile; + try + { + oFile.SetLength(fsize.LowPart); + } + catch (CMemoryException* e) + { + e->GetErrorMessage(exceptionError, _countof(exceptionError)); + m_sErrorString = exceptionError; + return FALSE; + } + + // load file + DWORD dwReadBytes = 0; + if (!ReadFile(hFile, (void *)oFile, fsize.LowPart, &dwReadBytes, NULL)) + { + SetErrorString(); + return FALSE; + } + hFile.CloseHandle(); + + // detect type + if (m_UnicodeType == CFileTextLines::AUTOTYPE) + { + m_UnicodeType = this->CheckUnicodeType((LPVOID)oFile, dwReadBytes); + // enforce conversion for all but ASCII and UTF8 type + m_bNeedsConversion = (m_UnicodeType!=CFileTextLines::UTF8)&&(m_UnicodeType!=CFileTextLines::ASCII); + } + + // we may have to convert the file content - CString is UTF16LE + try + { + CBaseFilter * pFilter = NULL; + switch (m_UnicodeType) + { + case BINARY: + m_sErrorString.Format(IDS_ERR_FILE_BINARY, (LPCTSTR)sFilePath); + return FALSE; + case UTF8: + case UTF8BOM: + pFilter = new CUtf8Filter(NULL); + break; + default: + case ASCII: + pFilter = new CAsciiFilter(NULL); + break; + case UTF16_BE: + pFilter = new CUtf16beFilter(NULL); + break; + case UTF16_LE: + pFilter = new CUtf16leFilter(NULL); + break; + case UTF32_BE: + pFilter = new CUtf32beFilter(NULL); + break; + case UTF32_LE: + pFilter = new CUtf32leFilter(NULL); + break; + } + pFilter->Decode(oFile); + delete pFilter; + } + catch (CMemoryException* e) + { + e->GetErrorMessage(exceptionError, _countof(exceptionError)); + m_sErrorString = exceptionError; + return FALSE; + } + + int nReadChars=oFile.GetLength()/sizeof(wchar_t); + wchar_t * pTextBuf = (wchar_t *)oFile; + wchar_t * pLineStart = pTextBuf; + if ((m_UnicodeType == UTF8BOM) + || (m_UnicodeType == UTF16_LE) + || (m_UnicodeType == UTF16_BE) + || (m_UnicodeType == UTF32_LE) + || (m_UnicodeType == UTF32_BE)) + { + // ignore the BOM + ++pTextBuf; + ++pLineStart; + --nReadChars; + } + + // fill in the lines into the array + size_t countEOLs[EOL__COUNT]; + memset(countEOLs, 0, sizeof(countEOLs)); + CFileTextLine oTextLine; + for (int i = nReadChars; i; --i) + { + EOL eEol; + switch (*pTextBuf++) + { + case '\r': + // crlf line ending or cr line ending + eEol = ((i > 1) && *(pTextBuf) == '\n') ? EOL_CRLF : EOL_CR; + break; + case '\n': + // lfcr line ending or lf line ending + eEol = ((i > 1) && *(pTextBuf) == '\r') ? EOL_LFCR : EOL_LF; + break; + case 0x000b: + eEol = EOL_VT; + break; + case 0x000c: + eEol = EOL_FF; + break; + case 0x0085: + eEol = EOL_NEL; + break; + case 0x2028: + eEol = EOL_LS; + break; + case 0x2029: + eEol = EOL_PS; + break; + default: + continue; + } + oTextLine.sLine = CString(pLineStart, (int)(pTextBuf-pLineStart)-1); + oTextLine.eEnding = eEol; + Add(oTextLine); + ++countEOLs[eEol]; + if (eEol==EOL_CRLF || eEol==EOL_LFCR) + { + ++pTextBuf; + --i; + } + pLineStart = pTextBuf; + } + CString line(pLineStart, (int)(pTextBuf-pLineStart)); + Add(line, EOL_NOENDING); + + // some EOLs are not supported by the svn diff lib. + m_bNeedsConversion |= (countEOLs[EOL_CRLF]!=0); + m_bNeedsConversion |= (countEOLs[EOL_FF]!=0); + m_bNeedsConversion |= (countEOLs[EOL_VT]!=0); + m_bNeedsConversion |= (countEOLs[EOL_NEL]!=0); + m_bNeedsConversion |= (countEOLs[EOL_LS]!=0); + m_bNeedsConversion |= (countEOLs[EOL_PS]!=0); + + size_t eolmax = 0; + for (int nEol = 0; nEol 66) + sLine = sLine.Mid(66); + } + switch (dwIgnoreWhitespaces) + { + case 0: + // Compare whitespaces + // do nothing + break; + case 1: + // Ignore all whitespaces + sLine.TrimLeft(_T(" \t")); + sLine.TrimRight(_T(" \t")); + break; + case 2: + // Ignore leading whitespace + sLine.TrimLeft(_T(" \t")); + break; + case 3: + // Ignore ending whitespace + sLine.TrimRight(_T(" \t")); + break; + } +} + +/** + Encoding pattern: + - encode & save BOM + - Get Line + - modify line - whitespaces, lowercase + - encode & save line + - get cached encoded eol + - save eol +*/ +BOOL CFileTextLines::Save(const CString& sFilePath + , bool bSaveAsUTF8 /*= false*/ + , bool bUseSVNCompatibleEOLs /*= false*/ + , DWORD dwIgnoreWhitespaces /*=0*/ + , BOOL bIgnoreCase /*= FALSE*/ + , bool bBlame /*= false*/) const +{ + try + { + CString destPath = sFilePath; + // now make sure that the destination directory exists + int ind = 0; + while (destPath.Find('\\', ind)>=2) + { + if (!PathIsDirectory(destPath.Left(destPath.Find('\\', ind)))) + { + if (!CreateDirectory(destPath.Left(destPath.Find('\\', ind)), NULL)) + return FALSE; + } + ind = destPath.Find('\\', ind)+1; + } + + CStdioFile file; // Hugely faster than CFile for big file writes - because it uses buffering + if (!file.Open(sFilePath, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary)) + { + const_cast(&m_sErrorString)->Format(IDS_ERR_FILE_OPEN, (LPCTSTR)sFilePath); + return FALSE; + } + + CBaseFilter * pFilter = NULL; + bool bSaveBom = true; + CFileTextLines::UnicodeType eUnicodeType = bSaveAsUTF8 ? CFileTextLines::UTF8 : m_UnicodeType; + switch (eUnicodeType) + { + default: + case CFileTextLines::ASCII: + bSaveBom = false; + pFilter = new CAsciiFilter(&file); + break; + case CFileTextLines::UTF8: + bSaveBom = false; + case CFileTextLines::UTF8BOM: + pFilter = new CUtf8Filter(&file); + break; + case CFileTextLines::UTF16_BE: + pFilter = new CUtf16beFilter(&file); + break; + case CFileTextLines::UTF16_LE: + pFilter = new CUtf16leFilter(&file); + break; + case CFileTextLines::UTF32_BE: + pFilter = new CUtf32beFilter(&file); + break; + case CFileTextLines::UTF32_LE: + pFilter = new CUtf32leFilter(&file); + break; + } + + if (bSaveBom) + { + //first write the BOM + pFilter->Write(L"\xfeff"); + } + // cache EOLs + CBuffer oEncodedEol[EOL__COUNT]; + oEncodedEol[EOL_LF] = pFilter->Encode(_T("\n")); // x0a + oEncodedEol[EOL_CR] = pFilter->Encode(_T("\r")); // x0d + oEncodedEol[EOL_CRLF] = pFilter->Encode(_T("\r\n")); // x0d x0a + if (bUseSVNCompatibleEOLs) + { + // when using EOLs that are supported by the svn lib, + // we have to use the same EOLs as the file has in case + // they're already supported, but a different supported one + // in case the original one isn't supported. + // Only this way the option "ignore EOLs (recommended)" unchecked + // actually shows the lines as different. + // However, the diff won't find and differences in EOLs + // for these special EOLs if they differ between those special ones + // listed below. + // But it will work properly for the most common EOLs LF/CR/CRLF. + oEncodedEol[EOL_LFCR] = oEncodedEol[EOL_CR]; + for (int nEol = 0; nEolEncode(_T("\n\r")); + oEncodedEol[EOL_VT] = pFilter->Encode(_T("\v")); // x0b + oEncodedEol[EOL_FF] = pFilter->Encode(_T("\f")); // x0c + oEncodedEol[EOL_NEL] = pFilter->Encode(_T("\x85")); + oEncodedEol[EOL_LS] = pFilter->Encode(_T("\x2028")); + oEncodedEol[EOL_PS] = pFilter->Encode(_T("\x2029")); + } + oEncodedEol[EOL_AUTOLINE] = oEncodedEol[m_LineEndings==EOL_AUTOLINE ? EOL_CRLF : m_LineEndings]; + + for (int i=0; iWrite(sLineT); + EOL eEol = GetLineEnding(i); + pFilter->Write(oEncodedEol[eEol]); + } + delete pFilter; + file.Close(); + } + catch (CException * e) + { + CString * psErrorString = const_cast(&m_sErrorString); + e->GetErrorMessage(psErrorString->GetBuffer(4096), 4096); + psErrorString->ReleaseBuffer(); + e->Delete(); + return FALSE; + } + return TRUE; +} + +void CFileTextLines::SetErrorString() +{ + m_sErrorString = CFormatMessageWrapper(); +} + +void CFileTextLines::CopySettings(CFileTextLines * pFileToCopySettingsTo) +{ + if (pFileToCopySettingsTo) + { + pFileToCopySettingsTo->m_UnicodeType = m_UnicodeType; + pFileToCopySettingsTo->m_LineEndings = m_LineEndings; + } +} + + + +void CBuffer::ExpandToAtLeast(int nNewSize) +{ + if (nNewSize>m_nAllocated) + { + delete [] m_pBuffer; // we don't preserve buffer content intentionally + nNewSize+=2048-1; + nNewSize&=~(1024-1); + m_pBuffer=new BYTE[nNewSize]; + m_nAllocated=nNewSize; + } +} + +void CBuffer::SetLength(int nUsed) +{ + ExpandToAtLeast(nUsed); + m_nUsed = nUsed; +} + +void CBuffer::Swap(CBuffer & Src) +{ + std::swap(Src.m_nAllocated, m_nAllocated); + std::swap(Src.m_pBuffer, m_pBuffer); + std::swap(Src.m_nUsed, m_nUsed); +} + +void CBuffer::Copy(const CBuffer & Src) +{ + if (&Src != this) + { + SetLength(Src.m_nUsed); + memcpy(m_pBuffer, Src.m_pBuffer, m_nUsed); + } +} + + + +bool CBaseFilter::Decode(/*in out*/ CBuffer & data) +{ + int nFlags = (m_nCodePage==CP_ACP) ? MB_PRECOMPOSED : 0; + // dry decode is around 8 times faster then real one, alternatively we can set buffer to max length + int nReadChars = MultiByteToWideChar(m_nCodePage, nFlags, (LPCSTR)data, data.GetLength(), NULL, 0); + m_oBuffer.SetLength(nReadChars*sizeof(wchar_t)); + int ret2 = MultiByteToWideChar(m_nCodePage, nFlags, (LPCSTR)data, data.GetLength(), (LPWSTR)(void *)m_oBuffer, nReadChars); + if (ret2 != nReadChars) + { + return FALSE; + } + data.Swap(m_oBuffer); + return TRUE; +} + +const CBuffer & CBaseFilter::Encode(const CString s) +{ + m_oBuffer.SetLength(s.GetLength()*3+1); // set buffer to guessed max size + int nConvertedLen = WideCharToMultiByte(m_nCodePage, 0, (LPCTSTR)s, s.GetLength(), (LPSTR)m_oBuffer, m_oBuffer.GetLength(), NULL, NULL); + m_oBuffer.SetLength(nConvertedLen); // set buffer to used size + return m_oBuffer; +} + + + +bool CUtf16leFilter::Decode(/*in out*/ CBuffer & /*data*/) +{ + // we believe data is ok for use + return TRUE; +} + +const CBuffer & CUtf16leFilter::Encode(const CString s) +{ + int nNeedBytes = s.GetLength()*sizeof(TCHAR); + m_oBuffer.SetLength(nNeedBytes); + memcpy((void *)m_oBuffer, (LPCTSTR)s, nNeedBytes); + return m_oBuffer; +} + + + +bool CUtf16beFilter::Decode(/*in out*/ CBuffer & data) +{ + int nNeedBytes = data.GetLength(); + // make in place WORD BYTEs swap + UINT64 * p_qw = (UINT64 *)(void *)data; + int nQwords = nNeedBytes/8; + for (int nQword = 0; nQword=0x10000) + { + ++nSurrogatePairCount; + } + } + + // fill buffer + m_oBuffer.SetLength((nReadChars+nSurrogatePairCount)*sizeof(wchar_t)); + wchar_t * pOut = (wchar_t *)m_oBuffer; + for (int i = 0; i=0x110000) + { + *pOut=0xfffd; // ? mark + } + else if (zChar>=0x10000) + { + zChar-=0x10000; + pOut[0] = ((zChar>>10)&0x3ff) | 0xd800; // lead surrogate + pOut[1] = (zChar&0x7ff) | 0xdc00; // trail surrogate + pOut++; + } + else + { + *pOut = (wchar_t)zChar; + } + } + data.Swap(m_oBuffer); + return TRUE; +} + +const CBuffer & CUtf32leFilter::Encode(const CString s) +{ + int nInWords = s.GetLength(); + m_oBuffer.SetLength(nInWords*2); + + LPCTSTR p_In = (LPCTSTR)s; + UINT32 * p_Out = (UINT32 *)(void *)m_oBuffer; + int nOutDword = 0; + for (int nInWord = 0; nInWord // A template class to make an array which looks like a CStringArray or CDWORDArray but -// is in fact based on a STL array, which is much faster at large sizes -template class CStdArray +// is in fact based on a STL vector, which is much faster at large sizes +template class CStdArrayV { public: int GetCount() const { return (int)m_vec.size(); } @@ -30,25 +31,54 @@ public: void InsertAt(int index, const T& strVal) { m_vec.insert(m_vec.begin()+index, strVal); } void InsertAt(int index, const T& strVal, int nCopies) { m_vec.insert(m_vec.begin()+index, nCopies, strVal); } void SetAt(int index, const T& strVal) { m_vec[index] = strVal; } - void Add(const T& strVal) { m_vec.push_back(strVal); } + void Add(const T& strVal) + { + if (m_vec.size()==m_vec.capacity()) { + m_vec.reserve(m_vec.capacity() ? m_vec.capacity()*2 : 256); + } + m_vec.push_back(strVal); + } void RemoveAll() { m_vec.clear(); } - void Reserve(int lengthHint) { m_vec.reserve(lengthHint); } + void Reserve(int nHintSize) { m_vec.reserve(nHintSize); } private: std::vector m_vec; }; -typedef CStdArray CStdCStringArray; -typedef CStdArray CStdDWORDArray; +// A template class to make an array which looks like a CStringArray or CDWORDArray but +// is in fact based on a STL deque, which is much faster at large sizes +template class CStdArrayD +{ +public: + int GetCount() const { return (int)m_vec.size(); } + const T& GetAt(int index) const { return m_vec[index]; } + void RemoveAt(int index) { m_vec.erase(m_vec.begin()+index); } + void InsertAt(int index, const T& strVal) { m_vec.insert(m_vec.begin()+index, strVal); } + void InsertAt(int index, const T& strVal, int nCopies) { m_vec.insert(m_vec.begin()+index, nCopies, strVal); } + void SetAt(int index, const T& strVal) { m_vec[index] = strVal; } + void Add(const T& strVal) { m_vec.push_back(strVal); } + void RemoveAll() { m_vec.clear(); } + void Reserve(int ) { } + +private: + std::deque m_vec; +}; + +typedef CStdArrayV CStdDWORDArray; +struct CFileTextLine { + CString sLine; + EOL eEnding; +}; +typedef CStdArrayD CStdFileLineArray; /** * \ingroup TortoiseMerge * * Represents an array of text lines which are read from a file. * This class is also responsible for determining the encoding of - * the file (e.g. UNICODE, UTF8, ASCII, ...). + * the file (e.g. UNICODE(UTF16), UTF8, ASCII, ...). */ -class CFileTextLines : public CStdCStringArray +class CFileTextLines : public CStdFileLineArray { public: CFileTextLines(void); @@ -59,10 +89,12 @@ public: AUTOTYPE, BINARY, ASCII, - UNICODE_LE, - UNICODE_BE, - UTF8, - UTF8BOM, + UTF16_LE, //=1200, + UTF16_BE, //=1201, + UTF32_LE, //=12000, + UTF32_BE, //=12001, + UTF8, //=65001, + UTF8BOM, //=UTF8+65536, }; /** @@ -74,8 +106,18 @@ public: * Saves the whole array of text lines to a file, preserving * the line endings detected at Load() * \param sFilePath the path to save the file to + * \param bSaveAsUTF8 enforce encoding for save + * \param bUseSVNCompatibleEOLs limit EOLs to CRLF, CR and LF, last one is used instead of all others + * \param dwIgnoreWhitespaces "enum" mode of removing whitespaces + * \param bIgnoreCase converts whole file to lower case + * \param bBlame limit line len */ - BOOL Save(const CString& sFilePath, bool bSaveAsUTF8, DWORD dwIgnoreWhitespaces=0, BOOL bIgnoreCase = FALSE, bool bBlame = false); + BOOL Save(const CString& sFilePath + , bool bSaveAsUTF8 = false + , bool bUseSVNCompatibleEOLs = false + , DWORD dwIgnoreWhitespaces = 0 + , BOOL bIgnoreCase = FALSE + , bool bBlame = false) const; /** * Returns an error string of the last failed operation */ @@ -86,45 +128,156 @@ public: */ void CopySettings(CFileTextLines * pFileToCopySettingsTo); + bool NeedsConversion() const { return m_bNeedsConversion; } CFileTextLines::UnicodeType GetUnicodeType() const {return m_UnicodeType;} EOL GetLineEndings() const {return m_LineEndings;} - void Add(const CString& sLine, EOL ending) {CStdCStringArray::Add(sLine); m_endings.push_back(ending);} - void RemoveAt(int index) {CStdCStringArray::RemoveAt(index); m_endings.erase(m_endings.begin()+index);} - void InsertAt(int index, const CString& strVal, EOL ending) {CStdCStringArray::InsertAt(index, strVal); m_endings.insert(m_endings.begin()+index, ending);} + using CStdFileLineArray::Add; + void Add(const CString& sLine, EOL ending) { CFileTextLine temp={sLine, ending}; CStdFileLineArray::Add(temp); } + using CStdFileLineArray::RemoveAt; + using CStdFileLineArray::InsertAt; + void InsertAt(int index, const CString& strVal, EOL ending) { CFileTextLine temp={strVal, ending}; CStdFileLineArray::InsertAt(index, temp); } - EOL GetLineEnding(int index) {return m_endings[index];} - void SetLineEnding(int index, EOL ending) {m_endings[index] = ending;} + const CString& GetAt(int index) const { return CStdFileLineArray::GetAt(index).sLine; } + EOL GetLineEnding(int index) const { return CStdFileLineArray::GetAt(index).eEnding; } + //void SetLineEnding(int index, EOL ending) { CStdFileLineArray::GetAt(index).eEnding = ending; } - void RemoveAll() {CStdCStringArray::RemoveAll(); m_endings.clear();} + using CStdFileLineArray::RemoveAll; /** * Checks the Unicode type in a text buffer + * Must be public for TortoiseGitBlame * \param pBuffer pointer to the buffer containing text * \param cd size of the text buffer in bytes */ CFileTextLines::UnicodeType CheckUnicodeType(LPVOID pBuffer, int cb); private: - /** - * Checks the line endings in a text buffer - * \param pBuffer pointer to the buffer containing text - * \param cd size of the text buffer in bytes - */ - EOL CheckLineEndings(LPVOID pBuffer, int cb); - void SetErrorString(); - void StripAsciiWhiteSpace(CStringA& sLine); + static void StripWhiteSpace(CString& sLine, DWORD dwIgnoreWhitespaces, bool blame); + + +private: + CString m_sErrorString; + UnicodeType m_UnicodeType; + EOL m_LineEndings; + bool m_bNeedsConversion; +}; + + + +class CBuffer +{ +public: + CBuffer() {Init(); } + CBuffer(const CBuffer & Src) {Init(); Copy(Src); } + CBuffer(const CBuffer * const Src) {Init(); Copy(*Src); } + ~CBuffer() {Free(); } + + CBuffer & operator =(const CBuffer & Src) { Copy(Src); return *this; } + operator bool () { return !IsEmpty(); } + template + operator T () const { return (T)m_pBuffer; } + + void Clear() { m_nUsed=0; } + void ExpandToAtLeast(int nNewSize); + int GetLength() const { return m_nUsed; } + bool IsEmpty() const { return GetLength()==0; } + void SetLength(int nUsed); + void Swap(CBuffer & Src); + +private: + void Copy(const CBuffer & Src); + void Free() { delete [] m_pBuffer; } + void Init() { m_pBuffer=NULL; m_nUsed=0; m_nAllocated=0; } + + BYTE * m_pBuffer; + int m_nUsed; + int m_nAllocated; +}; + + +class CBaseFilter +{ +public: + CBaseFilter(CStdioFile * p_File) { m_pFile=p_File; m_nCodePage=0; } + virtual ~CBaseFilter() {} - void StripWhiteSpace(CString& sLine,DWORD dwIgnoreWhitespaces, bool blame); - void StripAsciiWhiteSpace(CStringA& sLine,DWORD dwIgnoreWhitespaces, bool blame); + virtual bool Decode(/*in out*/ CBuffer & s); + virtual const CBuffer & Encode(const CString data); + const CBuffer & GetBuffer() {return m_oBuffer; } + void Write(const CString s) { Write(Encode(s)); } ///< encode into buffer and write + void Write() { Write(m_oBuffer); } ///< write preencoded internal buffer + void Write(const CBuffer & buffer) { if (buffer.GetLength()) m_pFile->Write((void*)buffer, buffer.GetLength()); } ///< write preencoded buffer +protected: + CBuffer m_oBuffer; + /** + Code page for WideCharToMultiByte. + */ + UINT m_nCodePage; private: - std::vector m_endings; - CString m_sErrorString; - CFileTextLines::UnicodeType m_UnicodeType; - EOL m_LineEndings; - bool m_bReturnAtEnd; + CStdioFile * m_pFile; +}; + + +class CAsciiFilter : public CBaseFilter +{ +public: + CAsciiFilter(CStdioFile *pFile) : CBaseFilter(pFile){ m_nCodePage=CP_ACP; } + virtual ~CAsciiFilter() {} +}; + + +class CUtf8Filter : public CBaseFilter +{ +public: + CUtf8Filter(CStdioFile *pFile) : CBaseFilter(pFile){ m_nCodePage=CP_UTF8;} + virtual ~CUtf8Filter() {} +}; + + +class CUtf16leFilter : public CBaseFilter +{ +public: + CUtf16leFilter(CStdioFile *pFile) : CBaseFilter(pFile){} + virtual ~CUtf16leFilter() {} + + virtual bool Decode(/*in out*/ CBuffer & data); + virtual const CBuffer & Encode(const CString s); +}; + + +class CUtf16beFilter : public CUtf16leFilter +{ +public: + CUtf16beFilter(CStdioFile *pFile) : CUtf16leFilter(pFile){} + virtual ~CUtf16beFilter() {} + + virtual bool Decode(/*in out*/ CBuffer & data); + virtual const CBuffer & Encode(const CString s); +}; + + +class CUtf32leFilter : public CBaseFilter +{ +public: + CUtf32leFilter(CStdioFile *pFile) : CBaseFilter(pFile){} + virtual ~CUtf32leFilter() {} + + virtual bool Decode(/*in out*/ CBuffer & data); + virtual const CBuffer & Encode(const CString s); +}; + + +class CUtf32beFilter : public CUtf32leFilter +{ +public: + CUtf32beFilter(CStdioFile *pFile) : CUtf32leFilter(pFile){} + virtual ~CUtf32beFilter() {} + + virtual bool Decode(/*in out*/ CBuffer & data); + virtual const CBuffer & Encode(const CString s); }; diff --git a/src/TortoiseMerge/FindDlg.cpp b/src/TortoiseMerge/FindDlg.cpp index 1409112c5..47fcd570f 100644 --- a/src/TortoiseMerge/FindDlg.cpp +++ b/src/TortoiseMerge/FindDlg.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006 - Stefan Kueng +// Copyright (C) 2006, 2011-2012 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -27,6 +27,7 @@ IMPLEMENT_DYNAMIC(CFindDlg, CDialog) CFindDlg::CFindDlg(CWnd* pParent /*=NULL*/) : CDialog(CFindDlg::IDD, pParent) + , m_pParent(pParent) , m_bTerminating(false) , m_bFindNext(false) , m_bMatchCase(FALSE) @@ -60,7 +61,9 @@ END_MESSAGE_MAP() void CFindDlg::OnCancel() { m_bTerminating = true; - if (GetParent()) + if (m_pParent) + m_pParent->SendMessage(m_FindMsg); + else if (GetParent()) GetParent()->SendMessage(m_FindMsg); DestroyWindow(); } @@ -78,7 +81,9 @@ void CFindDlg::OnOK() if (m_FindCombo.GetString().IsEmpty()) return; m_bFindNext = true; - if (GetParent()) + if (m_pParent) + m_pParent->SendMessage(m_FindMsg); + else if (GetParent()) GetParent()->SendMessage(m_FindMsg); m_bFindNext = false; } @@ -88,6 +93,7 @@ BOOL CFindDlg::OnInitDialog() CDialog::OnInitDialog(); m_FindMsg = RegisterWindowMessage(FINDMSGSTRING); + m_FindCombo.DisableTrimming(); m_FindCombo.LoadHistory(_T("Software\\TortoiseGitMerge\\History\\Find"), _T("Search")); m_FindCombo.SetFocus(); diff --git a/src/TortoiseMerge/FindDlg.h b/src/TortoiseMerge/FindDlg.h index be9825b57..490d2a68c 100644 --- a/src/TortoiseMerge/FindDlg.h +++ b/src/TortoiseMerge/FindDlg.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2007 - TortoiseSVN +// Copyright (C) 2006-2007, 2010 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -17,6 +17,7 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #pragma once +#include "resource.h" #include "afxcmn.h" #include "HistoryCombo.h" @@ -38,6 +39,7 @@ public: bool LimitToDiffs() {return !!m_bLimitToDiffs;} bool WholeWord() {return !!m_bWholeWord;} CString GetFindString() {return m_FindCombo.GetString();} + void SetFindString(const CString& str) { m_FindCombo.SetWindowText(str); } // Dialog Data enum { IDD = IDD_FIND }; @@ -58,4 +60,5 @@ private: BOOL m_bLimitToDiffs; BOOL m_bWholeWord; CHistoryCombo m_FindCombo; + CWnd * m_pParent; }; diff --git a/src/TortoiseMerge/GotoLineDlg.cpp b/src/TortoiseMerge/GotoLineDlg.cpp new file mode 100644 index 000000000..f1d267559 --- /dev/null +++ b/src/TortoiseMerge/GotoLineDlg.cpp @@ -0,0 +1,83 @@ +// TortoiseGitMerge - a Diff/Patch program + +// Copyright (C) 2011 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#include "stdafx.h" +#include "TortoiseMerge.h" +#include "GotoLineDlg.h" +#include "afxdialogex.h" + + +// CGotoLineDlg dialog + +IMPLEMENT_DYNAMIC(CGotoLineDlg, CDialogEx) + +CGotoLineDlg::CGotoLineDlg(CWnd* pParent /*=NULL*/) + : CDialogEx(CGotoLineDlg::IDD, pParent) + , m_nLine(0) + , m_nLow(-1) + , m_nHigh(-1) +{ + +} + +CGotoLineDlg::~CGotoLineDlg() +{ +} + +void CGotoLineDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialogEx::DoDataExchange(pDX); + DDX_Text(pDX, IDC_NUMBER, m_nLine); + DDX_Control(pDX, IDC_NUMBER, m_cNumber); +} + + +BEGIN_MESSAGE_MAP(CGotoLineDlg, CDialogEx) +END_MESSAGE_MAP() + + + + +BOOL CGotoLineDlg::OnInitDialog() +{ + CDialogEx::OnInitDialog(); + + if (!m_sLabel.IsEmpty()) + { + SetDlgItemText(IDC_LINELABEL, m_sLabel); + } + SetDlgItemText(IDC_NUMBER, L""); + GetDlgItem(IDC_NUMBER)->SetFocus(); + + return FALSE; +} + + +void CGotoLineDlg::OnOK() +{ + UpdateData(); + if ((m_nLine < m_nLow)||(m_nLine > m_nHigh)) + { + CString sError; + sError.Format(IDS_GOTO_OUTOFRANGE, m_nLow, m_nHigh); + m_cNumber.ShowBalloonTip(L"", sError); + m_cNumber.SetSel(0, -1); + return; + } + CDialogEx::OnOK(); +} diff --git a/src/TortoiseMerge/EditGotoDlg.h b/src/TortoiseMerge/GotoLineDlg.h similarity index 54% copy from src/TortoiseMerge/EditGotoDlg.h copy to src/TortoiseMerge/GotoLineDlg.h index 8b5cadf61..4b266545f 100644 --- a/src/TortoiseMerge/EditGotoDlg.h +++ b/src/TortoiseMerge/GotoLineDlg.h @@ -1,6 +1,6 @@ -// TortoiseGit - a Windows shell extension for easy version control +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2008-2012 - TortoiseGit +// Copyright (C) 2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -16,27 +16,37 @@ // along with this program; if not, write to the Free Software Foundation, // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // - #pragma once +#include "afxwin.h" -// CEditGotoDlg dialog +// CGotoLineDlg dialog -class CEditGotoDlg : public CDialog +class CGotoLineDlg : public CDialogEx { - DECLARE_DYNAMIC(CEditGotoDlg) + DECLARE_DYNAMIC(CGotoLineDlg) public: - CEditGotoDlg(CWnd* pParent = NULL); // standard constructor - virtual ~CEditGotoDlg(); + CGotoLineDlg(CWnd* pParent = NULL); // standard constructor + virtual ~CGotoLineDlg(); + + int GetLineNumber() const {return m_nLine;} + void SetLabel(const CString& label) { m_sLabel = label; } + void SetLimits(int low, int high) { m_nLow = low; m_nHigh = high; } -// Dialog Data - enum { IDD = IDD_GOTODLG }; + // Dialog Data + enum { IDD = IDD_GOTO }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnInitDialog(); + virtual void OnOK(); DECLARE_MESSAGE_MAP() -public: - DWORD m_LineNumber; +private: + int m_nLine; + int m_nLow; + int m_nHigh; + CString m_sLabel; + CEdit m_cNumber; }; diff --git a/src/TortoiseMerge/LeftView.cpp b/src/TortoiseMerge/LeftView.cpp dissimilarity index 89% index 14433517b..02e725a42 100644 --- a/src/TortoiseMerge/LeftView.cpp +++ b/src/TortoiseMerge/LeftView.cpp @@ -1,272 +1,68 @@ -// TortoiseMerge - a Diff/Patch program - -// Copyright (C) 2006-2009,2011 - TortoiseSVN - -// 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, -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// -#include "stdafx.h" -#include "resource.h" -#include "AppUtils.h" -#include "leftview.h" - -IMPLEMENT_DYNCREATE(CLeftView, CBaseView) - -CLeftView::CLeftView(void) -{ - m_pwndLeft = this; - m_nStatusBarID = ID_INDICATOR_LEFTVIEW; -} - -CLeftView::~CLeftView(void) -{ -} - -bool CLeftView::OnContextMenu(CPoint point, int /*nLine*/, DiffStates state) -{ - if (!m_pwndRight->IsWindowVisible()) - return false; - - CMenu popup; - if (popup.CreatePopupMenu()) - { -#define ID_USEBLOCK 1 -#define ID_USEFILE 2 -#define ID_USETHEIRANDYOURBLOCK 3 -#define ID_USEYOURANDTHEIRBLOCK 4 -#define ID_USEBOTHTHISFIRST 5 -#define ID_USEBOTHTHISLAST 6 - - UINT uFlags = MF_ENABLED; - if ((m_nSelBlockStart == -1)||(m_nSelBlockEnd == -1)) - uFlags |= MF_DISABLED | MF_GRAYED; - CString temp; - - bool bImportantBlock = true; - switch (state) - { - case DIFFSTATE_UNKNOWN: - bImportantBlock = false; - break; - } - temp.LoadString(IDS_VIEWCONTEXTMENU_USETHISBLOCK); - popup.AppendMenu(MF_STRING | uFlags | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USEBLOCK, temp); - - temp.LoadString(IDS_VIEWCONTEXTMENU_USETHISFILE); - popup.AppendMenu(MF_STRING | MF_ENABLED, ID_USEFILE, temp); - - if (m_pwndBottom->IsWindowVisible()) - { - temp.LoadString(IDS_VIEWCONTEXTMENU_USEYOURANDTHEIRBLOCK); - popup.AppendMenu(MF_STRING | uFlags | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USEYOURANDTHEIRBLOCK, temp); - temp.LoadString(IDS_VIEWCONTEXTMENU_USETHEIRANDYOURBLOCK); - popup.AppendMenu(MF_STRING | uFlags | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USETHEIRANDYOURBLOCK, temp); - } - else - { - temp.LoadString(IDS_VIEWCONTEXTMENU_USEBOTHTHISFIRST); - popup.AppendMenu(MF_STRING | uFlags | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USEBOTHTHISFIRST, temp); - temp.LoadString(IDS_VIEWCONTEXTMENU_USEBOTHTHISLAST); - popup.AppendMenu(MF_STRING | uFlags | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USEBOTHTHISLAST, temp); - } - - popup.AppendMenu(MF_SEPARATOR, NULL); - - temp.LoadString(IDS_EDIT_COPY); - popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_COPY, temp); - if (!m_bCaretHidden) - { - temp.LoadString(IDS_EDIT_CUT); - popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_CUT, temp); - temp.LoadString(IDS_EDIT_PASTE); - popup.AppendMenu(MF_STRING | (CAppUtils::HasClipboardFormat(CF_UNICODETEXT)||CAppUtils::HasClipboardFormat(CF_TEXT) ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_PASTE, temp); - } - - // if the context menu is invoked through the keyboard, we have to use - // a calculated position on where to anchor the menu on - if ((point.x == -1) && (point.y == -1)) - { - CRect rect; - GetWindowRect(&rect); - point = rect.CenterPoint(); - } - int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0); - viewstate leftstate; - viewstate bottomstate; - viewstate rightstate; - switch (cmd) - { - case ID_EDIT_COPY: - OnEditCopy(); - return true; - case ID_EDIT_CUT: - OnEditCopy(); - RemoveSelectedText(); - return false; - case ID_EDIT_PASTE: - PasteText(); - return false; - case ID_USEFILE: - { - if (m_pwndBottom->IsWindowVisible()) - { - for (int i=0; im_pViewData->GetLine(i); - m_pwndBottom->m_pViewData->SetLine(i, m_pViewData->GetLine(i)); - bottomstate.linestates[i] = m_pwndBottom->m_pViewData->GetState(i); - m_pwndBottom->m_pViewData->SetState(i, m_pViewData->GetState(i)); - m_pwndBottom->m_pViewData->SetLineEnding(i, m_pViewData->GetLineEnding(i)); - if (m_pwndBottom->IsLineConflicted(i)) - { - if (m_pViewData->GetState(i) == DIFFSTATE_CONFLICTEMPTY) - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVEDEMPTY); - else - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVED); - } - } - m_pwndBottom->SetModified(); - } - else - { - for (int i=0; im_pViewData->GetLine(i); - m_pwndRight->m_pViewData->SetLine(i, m_pViewData->GetLine(i)); - m_pwndRight->m_pViewData->SetLineEnding(i, m_pViewData->GetLineEnding(i)); - DiffStates state2 = m_pViewData->GetState(i); - switch (state2) - { - case DIFFSTATE_CONFLICTEMPTY: - case DIFFSTATE_UNKNOWN: - case DIFFSTATE_EMPTY: - rightstate.linestates[i] = m_pwndRight->m_pViewData->GetState(i); - m_pwndRight->m_pViewData->SetState(i, state2); - break; - case DIFFSTATE_YOURSADDED: - case DIFFSTATE_IDENTICALADDED: - case DIFFSTATE_NORMAL: - case DIFFSTATE_THEIRSADDED: - case DIFFSTATE_ADDED: - case DIFFSTATE_CONFLICTADDED: - case DIFFSTATE_CONFLICTED: - case DIFFSTATE_CONFLICTED_IGNORED: - case DIFFSTATE_IDENTICALREMOVED: - case DIFFSTATE_REMOVED: - case DIFFSTATE_THEIRSREMOVED: - case DIFFSTATE_YOURSREMOVED: - rightstate.linestates[i] = m_pwndRight->m_pViewData->GetState(i); - m_pwndRight->m_pViewData->SetState(i, DIFFSTATE_NORMAL); - leftstate.linestates[i] = m_pViewData->GetState(i); - m_pViewData->SetState(i, DIFFSTATE_NORMAL); - break; - default: - break; - } - } - m_pwndRight->SetModified(); - if (m_pwndLocator) - m_pwndLocator->DocumentUpdated(); - } - } - break; - case ID_USEBLOCK: - { - if ((m_nSelBlockStart == -1)||(m_nSelBlockEnd == -1)) - break; - if (m_pwndBottom->IsWindowVisible()) - { - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - bottomstate.difflines[i] = m_pwndBottom->m_pViewData->GetLine(i); - m_pwndBottom->m_pViewData->SetLine(i, m_pViewData->GetLine(i)); - bottomstate.linestates[i] = m_pwndBottom->m_pViewData->GetState(i); - m_pwndBottom->m_pViewData->SetState(i, m_pViewData->GetState(i)); - m_pwndBottom->m_pViewData->SetLineEnding(i, lineendings); - if (m_pwndBottom->IsLineConflicted(i)) - { - if (m_pViewData->GetState(i) == DIFFSTATE_CONFLICTEMPTY) - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVEDEMPTY); - else - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVED); - } - } - m_pwndBottom->SetModified(); - } - else - { - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - rightstate.difflines[i] = m_pwndRight->m_pViewData->GetLine(i); - m_pwndRight->m_pViewData->SetLine(i, m_pViewData->GetLine(i)); - m_pwndRight->m_pViewData->SetLineEnding(i, lineendings); - DiffStates state2 = m_pViewData->GetState(i); - switch (state2) - { - case DIFFSTATE_ADDED: - case DIFFSTATE_CONFLICTADDED: - case DIFFSTATE_CONFLICTED: - case DIFFSTATE_CONFLICTED_IGNORED: - case DIFFSTATE_CONFLICTEMPTY: - case DIFFSTATE_IDENTICALADDED: - case DIFFSTATE_NORMAL: - case DIFFSTATE_THEIRSADDED: - case DIFFSTATE_UNKNOWN: - case DIFFSTATE_YOURSADDED: - case DIFFSTATE_EMPTY: - rightstate.linestates[i] = m_pwndRight->m_pViewData->GetState(i); - m_pwndRight->m_pViewData->SetState(i, state2); - break; - case DIFFSTATE_IDENTICALREMOVED: - case DIFFSTATE_REMOVED: - case DIFFSTATE_THEIRSREMOVED: - case DIFFSTATE_YOURSREMOVED: - rightstate.linestates[i] = m_pwndRight->m_pViewData->GetState(i); - m_pwndRight->m_pViewData->SetState(i, DIFFSTATE_ADDED); - break; - default: - break; - } - } - m_pwndRight->SetModified(); - } - } - break; - case ID_USEYOURANDTHEIRBLOCK: - { - UseYourAndTheirBlock(rightstate, bottomstate, leftstate); - } - break; - case ID_USETHEIRANDYOURBLOCK: - { - UseTheirAndYourBlock(rightstate, bottomstate, leftstate); - } - break; - case ID_USEBOTHTHISLAST: - { - UseBothRightFirst(rightstate, leftstate); - } - break; - case ID_USEBOTHTHISFIRST: - { - UseBothLeftFirst(rightstate, leftstate); - } - break; - default: - return false; - } // switch (cmd) - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - } // if (popup.CreatePopupMenu()) - return false; -} +// TortoiseGitMerge - a Diff/Patch program + +// Copyright (C) 2006-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#include "stdafx.h" +#include "resource.h" +#include "AppUtils.h" + +#include "leftview.h" +#include "BottomView.h" + +IMPLEMENT_DYNCREATE(CLeftView, CBaseView) + +CLeftView::CLeftView(void) +{ + m_pwndLeft = this; + m_pState = &m_AllState.left; + m_nStatusBarID = ID_INDICATOR_LEFTVIEW; +} + +CLeftView::~CLeftView(void) +{ +} + + +void CLeftView::AddContextItems(CIconMenu& popup, DiffStates state) +{ + const bool bShow = HasSelection() && (state != DIFFSTATE_UNKNOWN); + + if (IsBottomViewGood()) + { + if (bShow) + popup.AppendMenuIcon(POPUPCOMMAND_USETHEIRBLOCK, IDS_VIEWCONTEXTMENU_USETHISBLOCK); + popup.AppendMenuIcon(POPUPCOMMAND_USETHEIRFILE, IDS_VIEWCONTEXTMENU_USETHISFILE); + if (bShow) + { + popup.AppendMenuIcon(POPUPCOMMAND_USEYOURANDTHEIRBLOCK, IDS_VIEWCONTEXTMENU_USEYOURANDTHEIRBLOCK); + popup.AppendMenuIcon(POPUPCOMMAND_USETHEIRANDYOURBLOCK, IDS_VIEWCONTEXTMENU_USETHEIRANDYOURBLOCK); + } + } + else + { + if (bShow) + popup.AppendMenuIcon(POPUPCOMMAND_USELEFTBLOCK, IDS_VIEWCONTEXTMENU_USETHISBLOCK); + popup.AppendMenuIcon(POPUPCOMMAND_USELEFTFILE, IDS_VIEWCONTEXTMENU_USETHISFILE); + if (bShow) + { + popup.AppendMenuIcon(POPUPCOMMAND_USEBOTHLEFTFIRST, IDS_VIEWCONTEXTMENU_USEBOTHTHISFIRST); + popup.AppendMenuIcon(POPUPCOMMAND_USEBOTHRIGHTFIRST, IDS_VIEWCONTEXTMENU_USEBOTHTHISLAST); + } + } + + CBaseView::AddContextItems(popup, state); +} diff --git a/src/TortoiseMerge/LeftView.h b/src/TortoiseMerge/LeftView.h index ef49770b6..cdf9c44c6 100644 --- a/src/TortoiseMerge/LeftView.h +++ b/src/TortoiseMerge/LeftView.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2007 - TortoiseSVN +// Copyright (C) 2006-2007, 2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -29,7 +29,8 @@ class CLeftView : public CBaseView public: CLeftView(void); ~CLeftView(void); + protected: - bool OnContextMenu(CPoint point, int nLine, DiffStates state); + void AddContextItems(CIconMenu& popup, DiffStates state); }; diff --git a/src/TortoiseMerge/LineDiffBar.cpp b/src/TortoiseMerge/LineDiffBar.cpp index cb6ca53ff..4880305ec 100644 --- a/src/TortoiseMerge/LineDiffBar.cpp +++ b/src/TortoiseMerge/LineDiffBar.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2008 - TortoiseSVN +// Copyright (C) 2006-2008, 2010 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -95,30 +95,36 @@ void CLineDiffBar::OnPaint() CRect upperrect = CRect(rect.left, rect.top, rect.right, rect.bottom/2); CRect lowerrect = CRect(rect.left, rect.bottom/2, rect.right, rect.bottom); - if ((m_pMainFrm)&&(m_pMainFrm->m_pwndLeftView)&&(m_pMainFrm->m_pwndRightView)) + if (m_pMainFrm!=0) { - if ((m_pMainFrm->m_pwndLeftView->IsWindowVisible())&&(m_pMainFrm->m_pwndRightView->IsWindowVisible())) + CLeftView* leftView = m_pMainFrm->m_pwndLeftView; + CRightView* rightView = m_pMainFrm->m_pwndRightView; + if (CBaseView::IsViewGood(leftView)&&CBaseView::IsViewGood(rightView)) { - BOOL bViewWhiteSpace = m_pMainFrm->m_pwndLeftView->m_bViewWhitespace; - BOOL bInlineDiffs = m_pMainFrm->m_pwndLeftView->m_bShowInlineDiff; - - m_pMainFrm->m_pwndLeftView->m_bViewWhitespace = TRUE; - m_pMainFrm->m_pwndLeftView->m_bShowInlineDiff = TRUE; - m_pMainFrm->m_pwndLeftView->m_bShowSelection = false; - m_pMainFrm->m_pwndRightView->m_bViewWhitespace = TRUE; - m_pMainFrm->m_pwndRightView->m_bShowInlineDiff = TRUE; - m_pMainFrm->m_pwndRightView->m_bShowSelection = false; + BOOL bViewWhiteSpace = leftView->m_bViewWhitespace; + BOOL bInlineDiffs = leftView->m_bShowInlineDiff; + + leftView->m_bViewWhitespace = TRUE; + leftView->m_bShowInlineDiff = TRUE; + leftView->m_bWhitespaceInlineDiffs = true; + leftView->m_bShowSelection = false; + rightView->m_bViewWhitespace = TRUE; + rightView->m_bShowInlineDiff = TRUE; + rightView->m_bWhitespaceInlineDiffs = true; + rightView->m_bShowSelection = false; // Use left and right view to display lines next to each other - m_pMainFrm->m_pwndLeftView->DrawSingleLine(&cacheDC, &upperrect, m_nLineIndex); - m_pMainFrm->m_pwndRightView->DrawSingleLine(&cacheDC, &lowerrect, m_nLineIndex); - - m_pMainFrm->m_pwndLeftView->m_bViewWhitespace = bViewWhiteSpace; - m_pMainFrm->m_pwndLeftView->m_bShowInlineDiff = bInlineDiffs; - m_pMainFrm->m_pwndLeftView->m_bShowSelection = true; - m_pMainFrm->m_pwndRightView->m_bViewWhitespace = bViewWhiteSpace; - m_pMainFrm->m_pwndRightView->m_bShowInlineDiff = bInlineDiffs; - m_pMainFrm->m_pwndRightView->m_bShowSelection = true; + leftView->DrawSingleLine(&cacheDC, &upperrect, m_nLineIndex); + rightView->DrawSingleLine(&cacheDC, &lowerrect, m_nLineIndex); + + leftView->m_bViewWhitespace = bViewWhiteSpace; + leftView->m_bShowInlineDiff = bInlineDiffs; + leftView->m_bWhitespaceInlineDiffs = false; + leftView->m_bShowSelection = true; + rightView->m_bViewWhitespace = bViewWhiteSpace; + rightView->m_bShowInlineDiff = bInlineDiffs; + rightView->m_bWhitespaceInlineDiffs = false; + rightView->m_bShowSelection = true; } } @@ -146,5 +152,3 @@ BOOL CLineDiffBar::OnEraseBkgnd(CDC* /*pDC*/) { return TRUE; } - - diff --git a/src/TortoiseMerge/LineDiffBar.h b/src/TortoiseMerge/LineDiffBar.h index ca9608519..df974db7e 100644 --- a/src/TortoiseMerge/LineDiffBar.h +++ b/src/TortoiseMerge/LineDiffBar.h @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2006, 2008 - TortoiseSVN diff --git a/src/TortoiseMerge/LocatorBar.cpp b/src/TortoiseMerge/LocatorBar.cpp dissimilarity index 62% index c4a16e56f..2d64bd48b 100644 --- a/src/TortoiseMerge/LocatorBar.cpp +++ b/src/TortoiseMerge/LocatorBar.cpp @@ -1,347 +1,345 @@ -// TortoiseMerge - a Diff/Patch program - -// Copyright (C) 2006-2008 - TortoiseSVN - -// 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, -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// -#include "stdafx.h" -#include "TortoiseMerge.h" -#include "MainFrm.h" -#include "LocatorBar.h" -#include "LeftView.h" -#include "RightView.h" -#include "BottomView.h" -#include "DiffColors.h" - - -IMPLEMENT_DYNAMIC(CLocatorBar, CPaneDialog) -CLocatorBar::CLocatorBar() : CPaneDialog() - , m_pMainFrm(NULL) - , m_pCacheBitmap(NULL) - , m_bMouseWithin(FALSE) - , m_nLines(-1) -{ -} - -CLocatorBar::~CLocatorBar() -{ - if (m_pCacheBitmap) - { - m_pCacheBitmap->DeleteObject(); - delete m_pCacheBitmap; - m_pCacheBitmap = NULL; - } -} - -BEGIN_MESSAGE_MAP(CLocatorBar, CPaneDialog) - ON_WM_PAINT() - ON_WM_SIZE() - ON_WM_ERASEBKGND() - ON_WM_LBUTTONDOWN() - ON_WM_MOUSEMOVE() - ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave) -END_MESSAGE_MAP() - -void CLocatorBar::DocumentUpdated() -{ - m_pMainFrm = (CMainFrame *)this->GetParentFrame(); - m_arLeftIdent.RemoveAll(); - m_arLeftState.RemoveAll(); - m_arRightIdent.RemoveAll(); - m_arRightState.RemoveAll(); - m_arBottomIdent.RemoveAll(); - m_arBottomState.RemoveAll(); - DiffStates state = DIFFSTATE_UNKNOWN; - long identcount = 1; - m_nLines = 0; - if (m_pMainFrm->m_pwndLeftView->m_pViewData) - { - if (m_pMainFrm->m_pwndLeftView->m_pViewData->GetCount()) - state = m_pMainFrm->m_pwndLeftView->m_pViewData->GetState(0); - for (int i=0; im_pwndLeftView->m_pViewData->GetCount(); i++) - { - if (state == m_pMainFrm->m_pwndLeftView->m_pViewData->GetState(i)) - { - identcount++; - } - else - { - m_arLeftIdent.Add(identcount); - m_arLeftState.Add(state); - state = m_pMainFrm->m_pwndLeftView->m_pViewData->GetState(i); - identcount = 1; - } - } - m_arLeftIdent.Add(identcount); - m_arLeftState.Add(state); - } - - if (m_pMainFrm->m_pwndRightView->m_pViewData) - { - if (m_pMainFrm->m_pwndRightView->m_pViewData->GetCount()) - state = m_pMainFrm->m_pwndRightView->m_pViewData->GetState(0); - identcount = 1; - for (int i=0; im_pwndRightView->m_pViewData->GetCount(); i++) - { - if (state == m_pMainFrm->m_pwndRightView->m_pViewData->GetState(i)) - { - identcount++; - } - else - { - m_arRightIdent.Add(identcount); - m_arRightState.Add(state); - state = m_pMainFrm->m_pwndRightView->m_pViewData->GetState(i); - identcount = 1; - } - } - m_arRightIdent.Add(identcount); - m_arRightState.Add(state); - } - - if (m_pMainFrm->m_pwndBottomView->m_pViewData) - { - if (m_pMainFrm->m_pwndBottomView->m_pViewData->GetCount()) - state = m_pMainFrm->m_pwndBottomView->m_pViewData->GetState(0); - identcount = 1; - for (int i=0; im_pwndBottomView->m_pViewData->GetCount(); i++) - { - if (state == m_pMainFrm->m_pwndBottomView->m_pViewData->GetState(i)) - { - identcount++; - } - else - { - m_arBottomIdent.Add(identcount); - m_arBottomState.Add(state); - state = m_pMainFrm->m_pwndBottomView->m_pViewData->GetState(i); - identcount = 1; - } - } - m_arBottomIdent.Add(identcount); - m_arBottomState.Add(state); - m_nLines = (int)max(m_pMainFrm->m_pwndBottomView->m_pViewData->GetCount(), m_pMainFrm->m_pwndRightView->m_pViewData->GetCount()); - } - else if (m_pMainFrm->m_pwndRightView->m_pViewData) - m_nLines = (int)max(0, m_pMainFrm->m_pwndRightView->m_pViewData->GetCount()); - - if (m_pMainFrm->m_pwndLeftView->m_pViewData) - m_nLines = (int)max(m_nLines, m_pMainFrm->m_pwndLeftView->m_pViewData->GetCount()); - else - m_nLines = 0; - m_nLines++; - Invalidate(); -} - -void CLocatorBar::OnPaint() -{ - CPaintDC dc(this); // device context for painting - CRect rect; - GetClientRect(rect); - long height = rect.Height(); - long width = rect.Width(); - long nTopLine = 0; - long nBottomLine = 0; - if ((m_pMainFrm)&&(m_pMainFrm->m_pwndLeftView)) - { - nTopLine = m_pMainFrm->m_pwndLeftView->m_nTopLine; - nBottomLine = nTopLine + m_pMainFrm->m_pwndLeftView->GetScreenLines(); - } - CDC cacheDC; - VERIFY(cacheDC.CreateCompatibleDC(&dc)); - - if (m_pCacheBitmap == NULL) - { - m_pCacheBitmap = new CBitmap; - VERIFY(m_pCacheBitmap->CreateCompatibleBitmap(&dc, width, height)); - } - CBitmap *pOldBitmap = cacheDC.SelectObject(m_pCacheBitmap); - - - COLORREF color, color2; - - CDiffColors::GetInstance().GetColors(DIFFSTATE_UNKNOWN, color, color2); - cacheDC.FillSolidRect(rect, color); - - long barwidth = (width/3); - DWORD state = 0; - long identcount = 0; - long linecount = 0; - - if (m_nLines) - cacheDC.FillSolidRect(rect.left, height*nTopLine/m_nLines, - rect.Width(), (height*nBottomLine/m_nLines)-(height*nTopLine/m_nLines), RGB(180,180,255)); - if (m_pMainFrm->m_pwndLeftView->IsWindowVisible()) - { - for (long i=0; im_pwndRightView->IsWindowVisible()) - { - for (long i=0; im_pwndBottomView->IsWindowVisible()) - { - for (long i=0; iDeleteObject(); - delete m_pCacheBitmap; - m_pCacheBitmap = NULL; - } - Invalidate(); -} - -BOOL CLocatorBar::OnEraseBkgnd(CDC* /*pDC*/) -{ - return TRUE; -} - -void CLocatorBar::OnLButtonDown(UINT nFlags, CPoint point) -{ - CRect rect; - GetClientRect(rect); - int nLine = point.y*m_nLines/rect.Height(); - - if (nLine < 0) - nLine = 0; - if ((m_pMainFrm)&&(m_pMainFrm->m_pwndBottomView)) - m_pMainFrm->m_pwndBottomView->GoToLine(nLine, FALSE); - if ((m_pMainFrm)&&(m_pMainFrm->m_pwndLeftView)) - m_pMainFrm->m_pwndLeftView->GoToLine(nLine, FALSE); - if ((m_pMainFrm)&&(m_pMainFrm->m_pwndRightView)) - m_pMainFrm->m_pwndRightView->GoToLine(nLine, FALSE); - Invalidate(); - CPaneDialog::OnLButtonDown(nFlags, point); -} - -void CLocatorBar::OnMouseMove(UINT nFlags, CPoint point) -{ - m_MousePos = point; - if (!m_bMouseWithin) - { - m_bMouseWithin = TRUE; - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = m_hWnd; - _TrackMouseEvent(&tme); - } - - if (nFlags & MK_LBUTTON) - { - CRect rect; - GetClientRect(rect); - int nLine = point.y*m_nLines/rect.Height(); - - if (nLine < 0) - nLine = 0; - if ((m_pMainFrm)&&(m_pMainFrm->m_pwndBottomView)) - m_pMainFrm->m_pwndBottomView->GoToLine(nLine, FALSE); - if ((m_pMainFrm)&&(m_pMainFrm->m_pwndLeftView)) - m_pMainFrm->m_pwndLeftView->GoToLine(nLine, FALSE); - if ((m_pMainFrm)&&(m_pMainFrm->m_pwndRightView)) - m_pMainFrm->m_pwndRightView->GoToLine(nLine, FALSE); - } - Invalidate(); -} - -LRESULT CLocatorBar::OnMouseLeave(WPARAM, LPARAM) -{ - m_bMouseWithin = FALSE; - Invalidate(); - return 0; -} - - +// TortoiseGitMerge - a Diff/Patch program + +// Copyright (C) 2006-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#include "stdafx.h" +#include "TortoiseMerge.h" +#include "MainFrm.h" +#include "LocatorBar.h" +#include "LeftView.h" +#include "RightView.h" +#include "BottomView.h" +#include "DiffColors.h" +#include "AppUtils.h" + + +IMPLEMENT_DYNAMIC(CLocatorBar, CPaneDialog) +CLocatorBar::CLocatorBar() : CPaneDialog() + , m_pMainFrm(NULL) + , m_pCacheBitmap(NULL) + , m_regUseFishEye(_T("Software\\TortoiseGitMerge\\UseFishEye"), TRUE) + , m_nLines(-1) +{ +} + +CLocatorBar::~CLocatorBar() +{ + if (m_pCacheBitmap) + { + m_pCacheBitmap->DeleteObject(); + delete m_pCacheBitmap; + m_pCacheBitmap = NULL; + } +} + +BEGIN_MESSAGE_MAP(CLocatorBar, CPaneDialog) + ON_WM_PAINT() + ON_WM_SIZE() + ON_WM_ERASEBKGND() + ON_WM_LBUTTONDOWN() + ON_WM_MOUSEMOVE() + ON_WM_LBUTTONUP() + ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave) +END_MESSAGE_MAP() + +void CLocatorBar::DocumentUpdated() +{ + m_pMainFrm = (CMainFrame *)this->GetParentFrame(); + if (m_pMainFrm == NULL) + return; + + m_nLines = 0; + DocumentUpdated(m_pMainFrm->m_pwndLeftView, m_arLeftIdent, m_arLeftState); + DocumentUpdated(m_pMainFrm->m_pwndRightView, m_arRightIdent, m_arRightState); + DocumentUpdated(m_pMainFrm->m_pwndBottomView, m_arBottomIdent, m_arBottomState); + + if (m_pMainFrm->m_pwndLeftView) + { + m_nLines = m_pMainFrm->m_pwndLeftView->GetLineCount(); + if (m_pMainFrm->m_pwndRightView) + { + m_nLines = std::max(m_nLines, m_pMainFrm->m_pwndRightView->GetLineCount()); + if (m_pMainFrm->m_pwndBottomView) + { + m_nLines = std::max(m_nLines, m_pMainFrm->m_pwndBottomView->GetLineCount()); + } + } + } + Invalidate(); +} + +void CLocatorBar::DocumentUpdated(CBaseView* view, CDWordArray& indents, CDWordArray& states) +{ + indents.RemoveAll(); + states.RemoveAll(); + CViewData* viewData = view->m_pViewData; + if(viewData == 0) + return; + + long identcount = 1; + const int linesInView = view->GetLineCount(); + DiffStates state = DIFFSTATE_UNKNOWN; + if (linesInView) + state = viewData->GetState(0); + for (int i=1; iGetState(view->GetViewLineForScreen(i)); + if (state == lineState) + { + identcount++; + } + else + { + indents.Add(identcount); + states.Add(state); + state = lineState; + identcount = 1; + } + } + indents.Add(identcount); + states.Add(state); +} + +void CLocatorBar::OnPaint() +{ + CPaintDC dc(this); // device context for painting + CRect rect; + GetClientRect(rect); + const long height = rect.Height(); + const long width = rect.Width(); + long nTopLine = 0; + long nBottomLine = 0; + if ((m_pMainFrm)&&(m_pMainFrm->m_pwndLeftView)) + { + nTopLine = m_pMainFrm->m_pwndLeftView->m_nTopLine; + nBottomLine = nTopLine + m_pMainFrm->m_pwndLeftView->GetScreenLines(); + } + CDC cacheDC; + VERIFY(cacheDC.CreateCompatibleDC(&dc)); + + if (m_pCacheBitmap == NULL) + { + m_pCacheBitmap = new CBitmap; + VERIFY(m_pCacheBitmap->CreateCompatibleBitmap(&dc, width, height)); + } + CBitmap *pOldBitmap = cacheDC.SelectObject(m_pCacheBitmap); + + COLORREF color, color2; + CDiffColors::GetInstance().GetColors(DIFFSTATE_UNKNOWN, color, color2); + cacheDC.FillSolidRect(rect, color); + + if (m_nLines) + { + cacheDC.FillSolidRect(rect.left, height*nTopLine/m_nLines, + width, (height*nBottomLine/m_nLines)-(height*nTopLine/m_nLines), RGB(180,180,255)); + + if (m_pMainFrm) + { + PaintView (cacheDC, m_pMainFrm->m_pwndLeftView, m_arLeftIdent, m_arLeftState, rect, 0); + PaintView (cacheDC, m_pMainFrm->m_pwndRightView, m_arRightIdent, m_arRightState, rect, 2); + PaintView (cacheDC, m_pMainFrm->m_pwndBottomView, m_arBottomIdent, m_arBottomState, rect, 1); + } + } + + if (m_nLines == 0) + m_nLines = 1; + cacheDC.FillSolidRect(rect.left, height*nTopLine/m_nLines, + width, 2, RGB(0,0,0)); + cacheDC.FillSolidRect(rect.left, height*nBottomLine/m_nLines, + width, 2, RGB(0,0,0)); + //draw two vertical lines, so there are three rows visible indicating the three panes + cacheDC.FillSolidRect(rect.left + (width/3), rect.top, 1, height, RGB(0,0,0)); + cacheDC.FillSolidRect(rect.left + (width*2/3), rect.top, 1, height, RGB(0,0,0)); + + // draw the fish eye + DWORD pos = GetMessagePos(); + CRect screenRect = rect; + ClientToScreen(screenRect); + POINT pt; + pt.x = GET_X_LPARAM(pos); + pt.y = GET_Y_LPARAM(pos); + + if ((screenRect.PtInRect(pt))&&(DWORD(m_regUseFishEye))) + DrawFishEye (cacheDC, rect); + + VERIFY(dc.BitBlt(rect.left, rect.top, width, height, &cacheDC, 0, 0, SRCCOPY)); + + cacheDC.SelectObject(pOldBitmap); + cacheDC.DeleteDC(); +} + +void CLocatorBar::OnSize(UINT nType, int cx, int cy) +{ + CPaneDialog::OnSize(nType, cx, cy); + + if (m_pCacheBitmap != NULL) + { + m_pCacheBitmap->DeleteObject(); + delete m_pCacheBitmap; + m_pCacheBitmap = NULL; + } + Invalidate(); +} + +BOOL CLocatorBar::OnEraseBkgnd(CDC* /*pDC*/) +{ + return TRUE; +} + +void CLocatorBar::OnLButtonDown(UINT nFlags, CPoint point) +{ + ScrollOnMouseMove(point); + Invalidate(); + CPaneDialog::OnLButtonDown(nFlags, point); +} + +void CLocatorBar::OnMouseMove(UINT nFlags, CPoint point) +{ + m_MousePos = point; + + if (nFlags & MK_LBUTTON) + { + SetCapture(); + ScrollOnMouseMove(point); + } + + TRACKMOUSEEVENT Tme; + Tme.cbSize = sizeof(TRACKMOUSEEVENT); + Tme.dwFlags = TME_LEAVE; + Tme.hwndTrack = m_hWnd; + TrackMouseEvent(&Tme); + + + Invalidate(); +} + +LRESULT CLocatorBar::OnMouseLeave(WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + Invalidate(); + return 0; +} + +void CLocatorBar::OnLButtonUp(UINT nFlags, CPoint point) +{ + ReleaseCapture(); + Invalidate(); + + CPaneDialog::OnLButtonUp(nFlags, point); +} + +void CLocatorBar::ScrollOnMouseMove(const CPoint& point ) +{ + if (m_pMainFrm == 0) + return; + + CRect rect; + GetClientRect(rect); + + int nLine = point.y*m_nLines/rect.Height(); + if (nLine < 0) + nLine = 0; + + ScrollViewToLine(m_pMainFrm->m_pwndBottomView, nLine); + ScrollViewToLine(m_pMainFrm->m_pwndLeftView, nLine); + ScrollViewToLine(m_pMainFrm->m_pwndRightView, nLine); +} + +void CLocatorBar::ScrollViewToLine(CBaseView* view, int nLine) const +{ + if (view != 0) + view->GoToLine(nLine, FALSE); +} + +void CLocatorBar::PaintView(CDC& cacheDC, CBaseView* view, CDWordArray& indents, + CDWordArray& states, const CRect& rect, int stripeIndex) +{ + if (!view->IsWindowVisible()) + return; + + const long height = rect.Height(); + const long width = rect.Width(); + const long barwidth = (width/3); + long linecount = 0; + for (long i=0; iGetMarkedWord()[0]) + { + COLORREF color, color2; + CDiffColors::GetInstance().GetColors(DIFFSTATE_NORMAL, color, color2); + color = CAppUtils::IntenseColor(200, color); + for (size_t i=0; im_arMarkedWordLines.size(); ++i) + { + if (view->m_arMarkedWordLines[i]) + { + cacheDC.FillSolidRect(rect.left + (width*stripeIndex/3), (int)(height*i/m_nLines), + barwidth, max(height/m_nLines,2), color); + } + } + } + if (view->GetFindString()[0]) + { + COLORREF color, color2; + CDiffColors::GetInstance().GetColors(DIFFSTATE_NORMAL, color, color2); + color = CAppUtils::IntenseColor(30, color); + for (size_t i=0; im_arFindStringLines.size(); ++i) + { + if (view->m_arFindStringLines[i]) + { + cacheDC.FillSolidRect(rect.left + (width*stripeIndex/3), (int)(height*i/m_nLines), + barwidth, max(height/m_nLines,2), color); + } + } + } + +} + +void CLocatorBar::DrawFishEye(CDC& cacheDC, const CRect& rect ) +{ + const long height = rect.Height(); + const long width = rect.Width(); + const int fishstart = m_MousePos.y - height/20; + const int fishheight = height/10; + cacheDC.FillSolidRect(rect.left, fishstart-1, width, 1, RGB(0,0,100)); + cacheDC.FillSolidRect(rect.left, fishstart+fishheight+1, width, 1, RGB(0,0,100)); + VERIFY(cacheDC.StretchBlt(rect.left, fishstart, width, fishheight, + &cacheDC, 0, fishstart + (3*fishheight/8), width, fishheight/4, SRCCOPY)); + // draw the magnified area a little darker, so the + // user has a clear indication of the magnifier + for (int i=rect.left; i<(width - rect.left); i++) + { + for (int j=fishstart; j -// Copyright (C) 2004-2009 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -32,23 +32,23 @@ #include "BottomView.h" #include "DiffColors.h" #include "mainfrm.h" +#include "SelectFileFilter.h" #include "FormatMessageWrapper.h" -#include "EditGotoDlg.h" +#include "TaskbarUUID.h" #ifdef _DEBUG #define new DEBUG_NEW #endif - // CMainFrame -const UINT CMainFrame::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING); +const UINT TaskBarButtonCreated = RegisterWindowMessage(L"TaskbarButtonCreated"); IMPLEMENT_DYNCREATE(CMainFrame, CFrameWndEx) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx) ON_WM_CREATE() - ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_OFF_2007_AQUA, &CMainFrame::OnApplicationLook) - ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_OFF_2007_AQUA, &CMainFrame::OnUpdateApplicationLook) + ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN7, ID_VIEW_APPLOOK_OFF_2007_AQUA, &CMainFrame::OnApplicationLook) + ON_UPDATE_COMMAND_UI_RANGE(IDC_STYLEBUTTON, ID_VIEW_APPLOOK_OFF_2007_AQUA, &CMainFrame::OnUpdateApplicationLook) // Global help commands ON_COMMAND(ID_HELP_FINDER, CFrameWndEx::OnHelpFinder) ON_COMMAND(ID_HELP, CFrameWndEx::OnHelp) @@ -66,14 +66,12 @@ BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx) ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACES, OnUpdateViewWhitespaces) ON_COMMAND(ID_VIEW_OPTIONS, OnViewOptions) ON_WM_CLOSE() - ON_COMMAND(ID_EDIT_FIND, OnEditFind) - ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage) - ON_COMMAND(ID_EDIT_FINDNEXT, OnEditFindnext) - ON_COMMAND(ID_EDIT_FINDPREV, OnEditFindprev) - ON_COMMAND(ID_EDIT_GOTO, OnEditGoto) + ON_WM_ACTIVATE() ON_COMMAND(ID_FILE_RELOAD, OnFileReload) ON_COMMAND(ID_VIEW_LINEDOWN, OnViewLinedown) ON_COMMAND(ID_VIEW_LINEUP, OnViewLineup) + ON_COMMAND(ID_VIEW_MOVEDBLOCKS, OnViewMovedBlocks) + ON_UPDATE_COMMAND_UI(ID_VIEW_MOVEDBLOCKS, OnUpdateViewMovedBlocks) ON_UPDATE_COMMAND_UI(ID_EDIT_MARKASRESOLVED, OnUpdateMergeMarkasresolved) ON_COMMAND(ID_EDIT_MARKASRESOLVED, OnMergeMarkasresolved) ON_UPDATE_COMMAND_UI(ID_NAVIGATE_NEXTCONFLICT, OnUpdateMergeNextconflict) @@ -99,6 +97,8 @@ BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx) ON_UPDATE_COMMAND_UI(ID_EDIT_USETHEIRTHENMYBLOCK, &CMainFrame::OnUpdateEditUsetheirthenmyblock) ON_COMMAND(ID_VIEW_INLINEDIFFWORD, &CMainFrame::OnViewInlinediffword) ON_UPDATE_COMMAND_UI(ID_VIEW_INLINEDIFFWORD, &CMainFrame::OnUpdateViewInlinediffword) + ON_COMMAND(ID_VIEW_INLINEDIFF, &CMainFrame::OnViewInlinediff) + ON_UPDATE_COMMAND_UI(ID_VIEW_INLINEDIFF, &CMainFrame::OnUpdateViewInlinediff) ON_UPDATE_COMMAND_UI(ID_EDIT_CREATEUNIFIEDDIFFFILE, &CMainFrame::OnUpdateEditCreateunifieddifffile) ON_COMMAND(ID_EDIT_CREATEUNIFIEDDIFFFILE, &CMainFrame::OnEditCreateunifieddifffile) ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFBAR, &CMainFrame::OnUpdateViewLinediffbar) @@ -106,6 +106,7 @@ BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx) ON_UPDATE_COMMAND_UI(ID_VIEW_LOCATORBAR, &CMainFrame::OnUpdateViewLocatorbar) ON_COMMAND(ID_VIEW_LOCATORBAR, &CMainFrame::OnViewLocatorbar) ON_COMMAND(ID_EDIT_USELEFTBLOCK, &CMainFrame::OnEditUseleftblock) + ON_UPDATE_COMMAND_UI(ID_USEBLOCKS, &CMainFrame::OnUpdateUseBlock) ON_UPDATE_COMMAND_UI(ID_EDIT_USELEFTBLOCK, &CMainFrame::OnUpdateEditUseleftblock) ON_COMMAND(ID_EDIT_USELEFTFILE, &CMainFrame::OnEditUseleftfile) ON_UPDATE_COMMAND_UI(ID_EDIT_USELEFTFILE, &CMainFrame::OnUpdateEditUseleftfile) @@ -113,6 +114,22 @@ BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx) ON_UPDATE_COMMAND_UI(ID_EDIT_USEBLOCKFROMLEFTBEFORERIGHT, &CMainFrame::OnUpdateEditUseblockfromleftbeforeright) ON_COMMAND(ID_EDIT_USEBLOCKFROMRIGHTBEFORELEFT, &CMainFrame::OnEditUseblockfromrightbeforeleft) ON_UPDATE_COMMAND_UI(ID_EDIT_USEBLOCKFROMRIGHTBEFORELEFT, &CMainFrame::OnUpdateEditUseblockfromrightbeforeleft) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_NEXTDIFFERENCE, &CMainFrame::OnUpdateNavigateNextdifference) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_PREVIOUSDIFFERENCE, &CMainFrame::OnUpdateNavigatePreviousdifference) + ON_COMMAND(ID_VIEW_COLLAPSED, &CMainFrame::OnViewCollapsed) + ON_UPDATE_COMMAND_UI(ID_VIEW_COLLAPSED, &CMainFrame::OnUpdateViewCollapsed) + ON_COMMAND(ID_VIEW_COMPAREWHITESPACES, &CMainFrame::OnViewComparewhitespaces) + ON_UPDATE_COMMAND_UI(ID_VIEW_COMPAREWHITESPACES, &CMainFrame::OnUpdateViewComparewhitespaces) + ON_COMMAND(ID_VIEW_IGNOREWHITESPACECHANGES, &CMainFrame::OnViewIgnorewhitespacechanges) + ON_UPDATE_COMMAND_UI(ID_VIEW_IGNOREWHITESPACECHANGES, &CMainFrame::OnUpdateViewIgnorewhitespacechanges) + ON_COMMAND(ID_VIEW_IGNOREALLWHITESPACECHANGES, &CMainFrame::OnViewIgnoreallwhitespacechanges) + ON_UPDATE_COMMAND_UI(ID_VIEW_IGNOREALLWHITESPACECHANGES, &CMainFrame::OnUpdateViewIgnoreallwhitespacechanges) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_NEXTINLINEDIFF, &CMainFrame::OnUpdateNavigateNextinlinediff) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_PREVINLINEDIFF, &CMainFrame::OnUpdateNavigatePrevinlinediff) + ON_COMMAND(ID_VIEW_WRAPLONGLINES, &CMainFrame::OnViewWraplonglines) + ON_UPDATE_COMMAND_UI(ID_VIEW_WRAPLONGLINES, &CMainFrame::OnUpdateViewWraplonglines) + ON_REGISTERED_MESSAGE( TaskBarButtonCreated, CMainFrame::OnTaskbarButtonCreated ) + ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, &CMainFrame::OnUpdateEditPaste) END_MESSAGE_MAP() static UINT indicators[] = @@ -130,31 +147,49 @@ static UINT indicators[] = // CMainFrame construction/destruction CMainFrame::CMainFrame() - : m_pFindDialog(NULL) - , m_nSearchIndex(0) - , m_bInitSplitter(FALSE) + : m_bInitSplitter(FALSE) , m_bReversedPatch(FALSE) , m_bHasConflicts(false) , m_bInlineWordDiff(true) , m_bLineDiff(true) , m_bLocatorBar(true) , m_nMoveMovesToIgnore(0) - , m_bLimitToDiff(false) - , m_bWholeWord(false) , m_pwndLeftView(NULL) , m_pwndRightView(NULL) , m_pwndBottomView(NULL) , m_bReadOnly(false) , m_bBlame(false) -{ - m_bOneWay = (0 != ((DWORD)CRegDWORD(_T("Software\\TortoiseGitMerge\\OnePane")))); + , m_bCheckReload(false) + , m_bSaveRequired(false) + , resolveMsgWnd(0) + , resolveMsgWParam(0) + , resolveMsgLParam(0) + , m_regWrapLines(L"Software\\TortoiseGitMerge\\WrapLines", 0) + , m_regViewModedBlocks(L"Software\\TortoiseGitMerge\\ViewMovedBlocks", 0) + , m_regOneWay(L"Software\\TortoiseGitMerge\\OnePane") + , m_regCollapsed(L"Software\\TortoiseGitMerge\\Collapsed", 0) + , m_regInlineDiff(L"Software\\TortoiseGitMerge\\DisplayBinDiff", TRUE) +{ + m_bOneWay = (0 != ((DWORD)m_regOneWay)); theApp.m_nAppLook = theApp.GetInt(_T("ApplicationLook"), ID_VIEW_APPLOOK_VS_2005); + m_bCollapsed = !!(DWORD)m_regCollapsed; + m_bViewMovedBlocks = !!(DWORD)m_regViewModedBlocks; + m_bWrapLines = !!(DWORD)m_regWrapLines; + m_bInlineDiff = !!m_regInlineDiff; + CMFCVisualManagerWindows::m_b3DTabsXPTheme = TRUE; } CMainFrame::~CMainFrame() { } +LRESULT CMainFrame::OnTaskbarButtonCreated(WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + SetUUIDOverlayIcon(m_hWnd); + return 0; +} + + int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWndEx::OnCreate(lpCreateStruct) == -1) @@ -162,24 +197,17 @@ int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) OnApplicationLook(theApp.m_nAppLook); - if (!m_wndMenuBar.Create(this)) - { - TRACE0("Failed to create menubar\n"); - return -1; // fail to create - } - - m_wndMenuBar.SetPaneStyle(m_wndMenuBar.GetPaneStyle() | CBRS_SIZE_DYNAMIC | CBRS_TOOLTIPS | CBRS_FLYBY); - - // prevent the menu bar from taking the focus on activation - CMFCPopupMenu::SetForceMenuFocus(FALSE); + m_wndRibbonBar.Create (this); + m_wndRibbonBar.LoadFromResource(IDR_RIBBON); - if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY) || - !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) + // enable the dialog launch button on the view panel + CMFCRibbonCategory * pMainCat = m_wndRibbonBar.GetCategory(1); + if (pMainCat) { - TRACE0("Failed to create toolbar\n"); - return -1; // fail to create + CMFCRibbonPanel * pPanel = pMainCat->GetPanel(3); + if (pPanel) + pPanel->EnableLaunchButton(ID_VIEW_OPTIONS); } - m_wndToolBar.SetWindowText(_T("Main")); if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, @@ -205,10 +233,6 @@ int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) m_wndLineDiffBar.m_pMainFrm = this; EnableDocking(CBRS_ALIGN_ANY); - m_wndMenuBar.EnableDocking(CBRS_ALIGN_TOP); - m_wndToolBar.EnableDocking(CBRS_ALIGN_TOP); - DockPane(&m_wndMenuBar); - DockPane(&m_wndToolBar); DockPane(&m_wndLocatorBar); DockPane(&m_wndLineDiffBar); ShowPane(&m_wndLocatorBar, true, false, true); @@ -237,25 +261,42 @@ void CMainFrame::OnApplicationLook(UINT id) { case ID_VIEW_APPLOOK_WIN_2000: CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManager)); + m_wndRibbonBar.SetWindows7Look(FALSE); break; case ID_VIEW_APPLOOK_OFF_XP: CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerOfficeXP)); + m_wndRibbonBar.SetWindows7Look(FALSE); break; case ID_VIEW_APPLOOK_WIN_XP: CMFCVisualManagerWindows::m_b3DTabsXPTheme = TRUE; CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows)); + m_wndRibbonBar.SetWindows7Look(FALSE); break; case ID_VIEW_APPLOOK_OFF_2003: CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerOffice2003)); CDockingManager::SetDockingMode(DT_SMART); + m_wndRibbonBar.SetWindows7Look(FALSE); break; case ID_VIEW_APPLOOK_VS_2005: CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerVS2005)); CDockingManager::SetDockingMode(DT_SMART); + m_wndRibbonBar.SetWindows7Look(FALSE); + break; + + case ID_VIEW_APPLOOK_VS_2008: + CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerVS2008)); + CDockingManager::SetDockingMode(DT_SMART); + m_wndRibbonBar.SetWindows7Look(FALSE); + break; + + case ID_VIEW_APPLOOK_WIN7: + CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows7)); + CDockingManager::SetDockingMode(DT_SMART); + m_wndRibbonBar.SetWindows7Look(TRUE); break; default: @@ -280,6 +321,7 @@ void CMainFrame::OnApplicationLook(UINT id) CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerOffice2007)); CDockingManager::SetDockingMode(DT_SMART); + m_wndRibbonBar.SetWindows7Look(FALSE); } RedrawWindow(NULL, NULL, RDW_ALLCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW | RDW_FRAME | RDW_ERASE); @@ -289,6 +331,7 @@ void CMainFrame::OnApplicationLook(UINT id) void CMainFrame::OnUpdateApplicationLook(CCmdUI* pCmdUI) { + pCmdUI->Enable(); pCmdUI->SetRadio(theApp.m_nAppLook == pCmdUI->m_nID); } @@ -392,156 +435,68 @@ BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/, CCreateContext* pContex } // Callback function -BOOL CMainFrame::PatchFile(int nIndex, bool bAutoPatch, bool bIsReview) +BOOL CMainFrame::PatchFile(CString sFilePath, bool /*bContentMods*/, bool bPropMods, CString sVersion, BOOL bAutoPatch) { - CString Path2 = m_Patch.GetFullPath(m_Data.m_sPatchPath, nIndex, 1); - CString sFilePath = m_Patch.GetFullPath(m_Data.m_sPatchPath, nIndex); - //first, do a "dry run" of patching... - if (!m_Patch.PatchFile(nIndex, m_Data.m_sPatchPath)) + CString sDummy; + //"dry run" was successful, so save the patched file somewhere... + CString sTempFile = CTempFiles::Instance().GetTempFilePathString(); + CString sRejectedFile; + if (m_Patch.GetPatchResult(sFilePath, sTempFile, sRejectedFile) < 0) { - //patching not successful, so retrieve the - //base file from version control and try - //again... - CString sVersion = m_Patch.GetRevision(nIndex); - - CString sBaseFile; - if (sVersion == _T("0000000") || sFilePath == _T("NUL")) - sBaseFile = m_TempFiles.GetTempFilePath(); - else - { - CSysProgressDlg progDlg; - CString sTemp; - sTemp.Format(IDS_GETVERSIONOFFILE, (LPCTSTR)sVersion); - progDlg.SetLine(1, sTemp, true); - progDlg.SetLine(2, sFilePath, true); - sTemp.LoadString(IDS_GETVERSIONOFFILETITLE); - progDlg.SetTitle(sTemp); - progDlg.SetShowProgressBar(false); - progDlg.SetAnimation(IDR_DOWNLOAD); - progDlg.SetTime(FALSE); - - if(!m_Patch.m_IsGitPatch) - progDlg.ShowModeless(this); - - sBaseFile = m_TempFiles.GetTempFilePath(); - if (!CAppUtils::GetVersionedFile(sFilePath, sVersion, sBaseFile, &progDlg, m_hWnd)) - { - progDlg.Stop(); - CString sErrMsg; - sErrMsg.Format(IDS_ERR_MAINFRAME_FILEVERSIONNOTFOUND, (LPCTSTR)sVersion, (LPCTSTR)sFilePath); - MessageBox(sErrMsg, NULL, MB_ICONERROR); - return FALSE; - } - - progDlg.Stop(); - } - - CString sTempFile = m_TempFiles.GetTempFilePath(); - if (!m_Patch.PatchFile(nIndex, m_Data.m_sPatchPath, sTempFile, sBaseFile, true)) - { - MessageBox(m_Patch.GetErrorMessage(), NULL, MB_ICONERROR); - return FALSE; - } - + MessageBox(m_Patch.GetErrorMessage(), NULL, MB_ICONERROR); + return FALSE; + } + sFilePath = m_Patch.GetTargetPath() + _T("\\") + sFilePath; + sFilePath.Replace('/', '\\'); + if (m_bReversedPatch) + { + m_Data.m_baseFile.SetFileName(sTempFile); CString temp; - temp.Format(_T("%s Revision %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath), (LPCTSTR)sVersion); - m_Data.m_baseFile.SetFileName(sBaseFile); + temp.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath), (LPCTSTR)m_Data.m_sPatchPatched); m_Data.m_baseFile.SetDescriptiveName(temp); - if(!Path2.IsEmpty() && Path2 != _T("NUL")) - temp.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(Path2), (LPCTSTR)m_Data.m_sPatchPatched); - else - temp.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath), (LPCTSTR)m_Data.m_sPatchPatched); - - if (sVersion == _T("0000000") || sFilePath == _T("NUL")) - { - m_Data.m_baseFile.SetFileName(Path2); - m_Data.m_yourFile.SetFileName(Path2); - m_Data.m_theirFile.SetFileName(sTempFile); - m_Data.m_theirFile.SetDescriptiveName(CPathUtils::GetFileNameFromPath(Path2)); - m_Data.m_mergedFile.SetFileName(Path2); - if (!bIsReview) - { - LoadViews(); - return FALSE; - } - } - else if (bIsReview) - { - m_Data.m_yourFile.SetFileName(sTempFile); - m_Data.m_yourFile.SetDescriptiveName(temp); - m_Data.m_theirFile.SetOutOfUse(); - m_Data.m_mergedFile.SetOutOfUse(); - } - else - { - m_Data.m_theirFile.SetFileName(sTempFile); - m_Data.m_theirFile.SetDescriptiveName(temp); - m_Data.m_yourFile.SetFileName(sFilePath); - m_Data.m_yourFile.SetDescriptiveName(CPathUtils::GetFileNameFromPath(sFilePath)); - m_Data.m_mergedFile.SetFileName(sFilePath); - m_Data.m_mergedFile.SetDescriptiveName(CPathUtils::GetFileNameFromPath(sFilePath)); - } - TRACE(_T("comparing %s and %s\nagainst the base file %s\n"), (LPCTSTR)sTempFile, (LPCTSTR)sFilePath, (LPCTSTR)sBaseFile); - - + m_Data.m_yourFile.SetFileName(sFilePath); + temp.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath), (LPCTSTR)m_Data.m_sPatchOriginal); + m_Data.m_yourFile.SetDescriptiveName(temp); + m_Data.m_theirFile.SetOutOfUse(); + m_Data.m_mergedFile.SetOutOfUse(); } else { - //"dry run" was successful, so save the patched file somewhere... - CString sTempFile = m_TempFiles.GetTempFilePath(); - if (!m_Patch.PatchFile(nIndex, m_Data.m_sPatchPath, sTempFile)) - { - MessageBox(m_Patch.GetErrorMessage(), NULL, MB_ICONERROR); - return FALSE; - } - if (m_bReversedPatch) + if ((!PathFileExists(sFilePath))||(PathIsDirectory(sFilePath))) { - m_Data.m_baseFile.SetFileName(sTempFile); - CString temp; - temp.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath), (LPCTSTR)m_Data.m_sPatchPatched); - m_Data.m_baseFile.SetDescriptiveName(temp); - m_Data.m_yourFile.SetFileName(sFilePath); - temp.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath), (LPCTSTR)m_Data.m_sPatchOriginal); - m_Data.m_yourFile.SetDescriptiveName(temp); - m_Data.m_theirFile.SetOutOfUse(); - m_Data.m_mergedFile.SetOutOfUse(); + m_Data.m_baseFile.SetFileName(CTempFiles::Instance().GetTempFilePathString()); + m_Data.m_baseFile.CreateEmptyFile(); } else { - if (!PathFileExists(sFilePath)) - { - m_Data.m_baseFile.SetFileName(m_TempFiles.GetTempFilePath()); - m_Data.m_baseFile.CreateEmptyFile(); - } - else - { - m_Data.m_baseFile.SetFileName(sFilePath); - } - CString sDescription; - sDescription.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath), (LPCTSTR)m_Data.m_sPatchOriginal); - m_Data.m_baseFile.SetDescriptiveName(sDescription); - m_Data.m_yourFile.SetFileName(sTempFile); - CString temp; - if (!Path2.IsEmpty() && Path2 != _T("NUL")) - { - temp.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(Path2), (LPCTSTR)m_Data.m_sPatchPatched); - m_Data.m_mergedFile.SetFileName(Path2); - } - else - { - temp.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath), (LPCTSTR)m_Data.m_sPatchPatched); - m_Data.m_mergedFile.SetFileName(sFilePath); - } - - m_Data.m_yourFile.SetDescriptiveName(temp); - m_Data.m_theirFile.SetOutOfUse(); + m_Data.m_baseFile.SetFileName(sFilePath); } - TRACE(_T("comparing %s\nwith the patched result %s\n"), (LPCTSTR)sFilePath, (LPCTSTR)sTempFile); + CString sDescription; + sDescription.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath), (LPCTSTR)m_Data.m_sPatchOriginal); + m_Data.m_baseFile.SetDescriptiveName(sDescription); + m_Data.m_yourFile.SetFileName(sTempFile); + CString temp; + temp.Format(_T("%s %s"), (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath), (LPCTSTR)m_Data.m_sPatchPatched); + m_Data.m_yourFile.SetDescriptiveName(temp); + m_Data.m_theirFile.SetOutOfUse(); + m_Data.m_mergedFile.SetFileName(sFilePath); + m_Data.m_bPatchRequired = bPropMods; } + TRACE(_T("comparing %s\nwith the patched result %s\n"), (LPCTSTR)sFilePath, (LPCTSTR)sTempFile); + LoadViews(); + if (!sRejectedFile.IsEmpty()) + { +#if 0 // TGIT TODO + // start TortoiseUDiff with the rejected hunks + CString sTitle; + sTitle.Format(IDS_TITLE_REJECTEDHUNKS, (LPCTSTR)CPathUtils::GetFileNameFromPath(sFilePath)); + CAppUtils::StartUnifiedDiffViewer(sRejectedFile, sTitle); +#endif + } if (bAutoPatch) { - OnFileSave(); + PatchSave(); } return TRUE; } @@ -549,8 +504,8 @@ BOOL CMainFrame::PatchFile(int nIndex, bool bAutoPatch, bool bIsReview) // Callback function BOOL CMainFrame::DiffFiles(CString sURL1, CString sRev1, CString sURL2, CString sRev2) { - CString tempfile1 = m_TempFiles.GetTempFilePath(); - CString tempfile2 = m_TempFiles.GetTempFilePath(); + CString tempfile1 = CTempFiles::Instance().GetTempFilePathString(); + CString tempfile2 = CTempFiles::Instance().GetTempFilePathString(); ASSERT(tempfile1.Compare(tempfile2)); @@ -635,11 +590,13 @@ void CMainFrame::OnFileOpen() // a diff between two files means "Yours" against "Base", not "Theirs" against "Base" m_Data.m_yourFile.TransferDetailsFrom(m_Data.m_theirFile); } + m_bSaveRequired = false; LoadViews(); } -void CMainFrame::ClearViewNamesAndPaths() { +void CMainFrame::ClearViewNamesAndPaths() +{ m_pwndLeftView->m_sWindowName.Empty(); m_pwndLeftView->m_sFullFilePath.Empty(); m_pwndRightView->m_sWindowName.Empty(); @@ -648,70 +605,75 @@ void CMainFrame::ClearViewNamesAndPaths() { m_pwndBottomView->m_sFullFilePath.Empty(); } -bool CMainFrame::LoadViews(bool bRetainPosition) +bool CMainFrame::LoadViews(int line) { m_Data.SetBlame(m_bBlame); + m_Data.SetMovedBlocks(m_bViewMovedBlocks); m_bHasConflicts = false; CBaseView* pwndActiveView = m_pwndLeftView; - int nOldLine = m_pwndLeftView ? m_pwndLeftView->m_nTopLine : -1; + int nOldLine = m_pwndRightView ? m_pwndRightView->m_nTopLine : -1; int nOldLineNumber = - m_pwndLeftView && m_pwndLeftView->m_pViewData ? - m_pwndLeftView->m_pViewData->GetLineNumber(m_pwndLeftView->m_nTopLine) : -1; + m_pwndRightView && m_pwndRightView->m_pViewData ? + m_pwndRightView->m_pViewData->GetLineNumber(m_pwndRightView->m_nTopLine) : -1; + POINT ptOldCaretPos = {-1, -1}; + if (m_pwndRightView && m_pwndRightView->IsTarget()) + ptOldCaretPos = m_pwndRightView->GetCaretPosition(); + if (m_pwndBottomView && m_pwndBottomView->IsTarget()) + ptOldCaretPos = m_pwndBottomView->GetCaretPosition(); if (!m_Data.Load()) { - ::MessageBox(NULL, m_Data.GetError(), _T("TortoiseGitMerge"), MB_ICONERROR); + m_pwndLeftView->BuildAllScreen2ViewVector(); + m_pwndLeftView->DocumentUpdated(); + m_pwndRightView->DocumentUpdated(); + m_pwndBottomView->DocumentUpdated(); + m_wndLocatorBar.DocumentUpdated(); + m_wndLineDiffBar.DocumentUpdated(); + ::MessageBox(m_hWnd, m_Data.GetError(), _T("TortoiseGitMerge"), MB_ICONERROR); m_Data.m_mergedFile.SetOutOfUse(); + m_bSaveRequired = false; return false; } + SetWindowTitle(); + m_pwndLeftView->BuildAllScreen2ViewVector(); + m_pwndLeftView->DocumentUpdated(); + m_pwndRightView->DocumentUpdated(); + m_pwndBottomView->DocumentUpdated(); + m_wndLocatorBar.DocumentUpdated(); + m_wndLineDiffBar.DocumentUpdated(); - m_pwndRightView->UseCaret(false); - m_pwndBottomView->UseCaret(false); + m_pwndRightView->SetWritable(false); + m_pwndRightView->SetTarget(false); + m_pwndBottomView->SetWritable(false); + m_pwndBottomView->SetTarget(false); if (!m_Data.IsBaseFileInUse()) { + CSysProgressDlg progDlg; if (m_Data.IsYourFileInUse() && m_Data.IsTheirFileInUse()) { m_Data.m_baseFile.TransferDetailsFrom(m_Data.m_theirFile); } - else if ((!m_Data.m_sDiffFile.IsEmpty())&&(!m_Patch.OpenUnifiedDiffFile(m_Data.m_sDiffFile))) + else if ((!m_Data.m_sDiffFile.IsEmpty())&&(!m_Patch.Init(m_Data.m_sDiffFile, m_Data.m_sPatchPath, &progDlg))) { + progDlg.Stop(); ClearViewNamesAndPaths(); MessageBox(m_Patch.GetErrorMessage(), NULL, MB_ICONERROR); + m_bSaveRequired = false; return false; } if (m_Patch.GetNumberOfFiles() > 0) { - CString firstpath = m_Patch.GetFilename(0); - CString path=firstpath; - path.Replace('/','\\'); - if ( !PathIsRelative(path) && !PathFileExists(path) ) - { - // The absolute path mentioned in the patch does not exist. Lets - // try to find the correct relative path by stripping prefixes. - BOOL bFound = m_Patch.StripPrefixes(m_Data.m_sPatchPath); - CString strippedpath = m_Patch.GetFilename(0); - if (bFound) - { - CString msg; - msg.Format(IDS_WARNABSOLUTEPATHFOUND, (LPCTSTR)firstpath, (LPCTSTR)strippedpath); - if (CMessageBox::Show(m_hWnd, msg, _T("TortoiseGitMerge"), MB_ICONQUESTION | MB_YESNO)==IDNO) - return false; - } - else - { - CString msg; - msg.Format(IDS_WARNABSOLUTEPATHNOTFOUND, (LPCTSTR)firstpath); - CMessageBox::Show(m_hWnd, msg, _T("TortoiseGitMerge"), MB_ICONEXCLAMATION); - return false; - } - } CString betterpatchpath = m_Patch.CheckPatchPath(m_Data.m_sPatchPath); if (betterpatchpath.CompareNoCase(m_Data.m_sPatchPath)!=0) { + progDlg.Stop(); CString msg; msg.Format(IDS_WARNBETTERPATCHPATHFOUND, (LPCTSTR)m_Data.m_sPatchPath, (LPCTSTR)betterpatchpath); if (CMessageBox::Show(m_hWnd, msg, _T("TortoiseGitMerge"), MB_ICONQUESTION | MB_YESNO)==IDYES) + { m_Data.m_sPatchPath = betterpatchpath; + m_Patch.Init(m_Data.m_sDiffFile, m_Data.m_sPatchPath, &progDlg); + } } m_dlgFilePatches.Init(&m_Patch, this, m_Data.m_sPatchPath, this); m_dlgFilePatches.ShowWindow(SW_SHOW); @@ -730,7 +692,8 @@ bool CMainFrame::LoadViews(bool bRetainPosition) if (m_Data.IsBaseFileInUse() && m_Data.IsYourFileInUse() && !m_Data.IsTheirFileInUse()) { //diff between YOUR and BASE - m_pwndRightView->UseCaret(); + m_pwndRightView->SetWritable(); + m_pwndRightView->SetTarget(); if (m_bOneWay) { if (!m_wndSplitter2.IsColumnHidden(1)) @@ -772,7 +735,7 @@ bool CMainFrame::LoadViews(bool bRetainPosition) m_pwndBottomView->m_pViewData = NULL; - if (!m_wndSplitter.IsRowHidden(1)) + if (!m_wndSplitter.IsRowHidden(1)) m_wndSplitter.HideRow(1); m_pwndLeftView->SetHidden(FALSE); m_pwndRightView->SetHidden(FALSE); @@ -782,7 +745,8 @@ bool CMainFrame::LoadViews(bool bRetainPosition) else if (m_Data.IsBaseFileInUse() && m_Data.IsYourFileInUse() && m_Data.IsTheirFileInUse()) { //diff between THEIR, YOUR and BASE - m_pwndBottomView->UseCaret(); + m_pwndBottomView->SetWritable(); + m_pwndBottomView->SetTarget(); pwndActiveView = m_pwndBottomView; m_pwndLeftView->m_pViewData = &m_Data.m_TheirBaseBoth; @@ -821,6 +785,7 @@ bool CMainFrame::LoadViews(bool bRetainPosition) { m_Data.m_mergedFile.SetFileName(m_Data.m_yourFile.GetFilename()); } + m_pwndLeftView->BuildAllScreen2ViewVector(); m_pwndLeftView->DocumentUpdated(); m_pwndRightView->DocumentUpdated(); m_pwndBottomView->DocumentUpdated(); @@ -829,36 +794,58 @@ bool CMainFrame::LoadViews(bool bRetainPosition) UpdateLayout(); SetActiveView(pwndActiveView); - if (bRetainPosition && m_pwndLeftView->m_pViewData) + if ((line >= -1) && m_pwndRightView->m_pViewData) { - int n = nOldLineNumber; + int n = line == -1 ? min( nOldLineNumber, nOldLine ) : line; if (n >= 0) - n = m_pwndLeftView->m_pViewData->FindLineNumber(n); + n = m_pwndRightView->m_pViewData->FindLineNumber(n); if (n < 0) n = nOldLine; - m_pwndLeftView->ScrollAllToLine(n); + m_pwndRightView->ScrollAllToLine(n); POINT p; p.x = 0; p.y = n; + if ((ptOldCaretPos.x >= 0) || (ptOldCaretPos.y >= 0)) + p = ptOldCaretPos; m_pwndLeftView->SetCaretPosition(p); + m_pwndRightView->SetCaretPosition(p); + m_pwndBottomView->SetCaretPosition(p); + m_pwndBottomView->ScrollToChar(0); + m_pwndLeftView->ScrollToChar(0); + m_pwndRightView->ScrollToChar(0); } else { - bool bGoFirstDiff = (0 != (DWORD)CRegDWORD(_T("Software\\TortoiseGitMerge\\FirstDiffOnLoad"), TRUE)); - if (bGoFirstDiff) { + CRegDWORD regFirstDiff = CRegDWORD(_T("Software\\TortoiseGitMerge\\FirstDiffOnLoad"), TRUE); + CRegDWORD regFirstConflict = CRegDWORD(_T("Software\\TortoiseGitMerge\\FirstConflictOnLoad"), TRUE); + bool bGoFirstDiff = (0 != (DWORD)regFirstDiff); + bool bGoFirstConflict = (0 != (DWORD)regFirstConflict); + if (bGoFirstConflict && (CheckResolved()>=0)) + { + pwndActiveView->GoToFirstConflict(); + // Ignore the first few Mouse Move messages, so that the line diff stays on + // the first diff line until the user actually moves the mouse + m_nMoveMovesToIgnore = MOVESTOIGNORE; + } + else if (bGoFirstDiff) + { pwndActiveView->GoToFirstDifference(); // Ignore the first few Mouse Move messages, so that the line diff stays on // the first diff line until the user actually moves the mouse - m_nMoveMovesToIgnore = 3; + m_nMoveMovesToIgnore = MOVESTOIGNORE; + } + else + { + // Avoid incorrect rendering of active pane. + m_pwndBottomView->ScrollToChar(0); + m_pwndLeftView->ScrollToChar(0); + m_pwndRightView->ScrollToChar(0); } - } - // Avoid incorrect rendering of active pane. - m_pwndBottomView->ScrollToChar(0); - m_pwndLeftView->ScrollToChar(0); - m_pwndRightView->ScrollToChar(0); CheckResolved(); + if (m_bHasConflicts) + m_bSaveRequired = false; CUndo::GetInstance().Clear(); return true; } @@ -867,32 +854,56 @@ void CMainFrame::UpdateLayout() { if (m_bInitSplitter) { - CRect cr, rclocbar; - GetWindowRect(&cr); - int width = cr.Width(); - if (::IsWindow(m_wndLocatorBar) && m_wndLocatorBar.IsWindowVisible()) - { - m_wndLocatorBar.GetWindowRect(&rclocbar); - width -= rclocbar.Width(); - } - m_wndSplitter.SetRowInfo(0, cr.Height()/2, 0); - m_wndSplitter.SetRowInfo(1, cr.Height()/2, 0); - m_wndSplitter.SetColumnInfo(0, width / 2, 50); - m_wndSplitter2.SetRowInfo(0, cr.Height()/2, 0); - m_wndSplitter2.SetColumnInfo(0, width / 2, 50); - m_wndSplitter2.SetColumnInfo(1, width / 2, 50); - - m_wndSplitter.RecalcLayout(); + m_wndSplitter.CenterSplitter(); + m_wndSplitter2.CenterSplitter(); } } void CMainFrame::OnSize(UINT nType, int cx, int cy) { + CFrameWndEx::OnSize(nType, cx, cy); if (m_bInitSplitter && nType != SIZE_MINIMIZED) { - UpdateLayout(); + if (m_wndSplitter.GetSafeHwnd()) + { + if (m_wndSplitter.HasOldRowSize() && (m_wndSplitter.GetOldRowCount() == 2)) + { + int oldTotal = m_wndSplitter.GetOldRowSize(0) + m_wndSplitter.GetOldRowSize(1); + if (oldTotal) + { + int cxCur0, cxCur1, cxMin0, cxMin1; + m_wndSplitter.GetRowInfo(0, cxCur0, cxMin0); + m_wndSplitter.GetRowInfo(1, cxCur1, cxMin1); + cxCur0 = m_wndSplitter.GetOldRowSize(0) * (cxCur0 + cxCur1) / oldTotal; + cxCur1 = m_wndSplitter.GetOldRowSize(1) * (cxCur0 + cxCur1) / oldTotal; + m_wndSplitter.SetRowInfo(0, cxCur0, 0); + m_wndSplitter.SetRowInfo(1, cxCur1, 0); + m_wndSplitter.RecalcLayout(); + } + } + + if (m_wndSplitter2.HasOldColSize() && (m_wndSplitter2.GetOldColCount() == 2)) + { + int oldTotal = m_wndSplitter2.GetOldColSize(0) + m_wndSplitter2.GetOldColSize(1); + if (oldTotal) + { + int cyCur0, cyCur1, cyMin0, cyMin1; + m_wndSplitter2.GetColumnInfo(0, cyCur0, cyMin0); + m_wndSplitter2.GetColumnInfo(1, cyCur1, cyMin1); + cyCur0 = m_wndSplitter2.GetOldColSize(0) * (cyCur0 + cyCur1) / oldTotal; + cyCur1 = m_wndSplitter2.GetOldColSize(1) * (cyCur0 + cyCur1) / oldTotal; + m_wndSplitter2.SetColumnInfo(0, cyCur0, 0); + m_wndSplitter2.SetColumnInfo(1, cyCur1, 0); + m_wndSplitter2.RecalcLayout(); + } + } + } + } + if ((nType == SIZE_RESTORED)&&m_bCheckReload) + { + m_bCheckReload = false; + CheckForReload(); } - CFrameWndEx::OnSize(nType, cx, cy); } void CMainFrame::OnViewWhitespaces() @@ -927,26 +938,61 @@ void CMainFrame::OnUpdateViewWhitespaces(CCmdUI *pCmdUI) pCmdUI->SetCheck(m_pwndLeftView->m_bViewWhitespace); } +void CMainFrame::OnViewCollapsed() +{ + m_regCollapsed = !(DWORD)m_regCollapsed; + m_bCollapsed = !!(DWORD)m_regCollapsed; + + OnViewTextFoldUnfold(); + m_wndLocatorBar.Invalidate(); +} + +void CMainFrame::OnUpdateViewCollapsed(CCmdUI *pCmdUI) +{ + pCmdUI->SetCheck(m_bCollapsed); +} + +void CMainFrame::OnViewWraplonglines() +{ + m_bWrapLines = !(DWORD)m_regWrapLines; + m_regWrapLines = m_bWrapLines; + + if (m_pwndLeftView) m_pwndLeftView ->WrapChanged(); + if (m_pwndRightView) m_pwndRightView ->WrapChanged(); + if (m_pwndBottomView) m_pwndBottomView->WrapChanged(); + OnViewTextFoldUnfold(); + m_wndLocatorBar.DocumentUpdated(); +} + +void CMainFrame::OnViewTextFoldUnfold() +{ + OnViewTextFoldUnfold(m_pwndLeftView); + OnViewTextFoldUnfold(m_pwndRightView); + OnViewTextFoldUnfold(m_pwndBottomView); +} + +void CMainFrame::OnViewTextFoldUnfold(CBaseView* view) +{ + if (view == 0) + return; + view->BuildAllScreen2ViewVector(); + view->UpdateCaret(); + view->Invalidate(); + view->EnsureCaretVisible(); +} + +void CMainFrame::OnUpdateViewWraplonglines(CCmdUI *pCmdUI) +{ + pCmdUI->SetCheck(m_bWrapLines); +} + void CMainFrame::OnViewOnewaydiff() { if (CheckForSave()==IDCANCEL) return; m_bOneWay = !m_bOneWay; - if (m_bOneWay) - { - // in one way view, hide the line diff bar - m_wndLineDiffBar.ShowPane(false, false, true); - m_wndLineDiffBar.DocumentUpdated(); - } - else - { - // restore the line diff bar - m_wndLineDiffBar.ShowPane(m_bLineDiff, false, true); - m_wndLineDiffBar.DocumentUpdated(); - m_wndLocatorBar.ShowPane(m_bLocatorBar, false, true); - m_wndLocatorBar.DocumentUpdated(); - } - LoadViews(true); + ShowDiffBar(!m_bOneWay); + LoadViews(-1); } void CMainFrame::ShowDiffBar(bool bShow) @@ -973,12 +1019,13 @@ int CMainFrame::CheckResolved() m_bHasConflicts = true; if (m_pwndBottomView->IsWindowVisible()) { - if (m_pwndBottomView->m_pViewData) + CViewData* viewdata = m_pwndBottomView->m_pViewData; + if (viewdata) { - for (int i=0; im_pViewData->GetCount(); i++) + for (int i=0; iGetCount(); i++) { - if ((DIFFSTATE_CONFLICTED == m_pwndBottomView->m_pViewData->GetState(i))|| - (DIFFSTATE_CONFLICTED_IGNORED == m_pwndBottomView->m_pViewData->GetState(i))) + const DiffStates state = viewdata->GetState(i); + if ((DIFFSTATE_CONFLICTED == state)||(DIFFSTATE_CONFLICTED_IGNORED == state)) return i; } } @@ -991,12 +1038,12 @@ int CMainFrame::SaveFile(const CString& sFilePath) { CViewData * pViewData = NULL; CFileTextLines * pOriginFile = &m_Data.m_arBaseFile; - if ((m_pwndBottomView)&&(m_pwndBottomView->IsWindowVisible())) + if (IsViewGood(m_pwndBottomView)) { pViewData = m_pwndBottomView->m_pViewData; Invalidate(); } - else if ((m_pwndRightView)&&(m_pwndRightView->IsWindowVisible())) + else if (IsViewGood(m_pwndRightView)) { pViewData = m_pwndRightView->m_pViewData; if (m_Data.IsYourFileInUse()) @@ -1008,7 +1055,7 @@ int CMainFrame::SaveFile(const CString& sFilePath) else { // nothing to save! - return -1; + return 1; } if ((pViewData)&&(pOriginFile)) { @@ -1029,6 +1076,7 @@ int CMainFrame::SaveFile(const CString& sFilePath) { last++; } while((lastGetCount()) && ((pViewData->GetState(last)==DIFFSTATE_CONFLICTED)||(pViewData->GetState(last)==DIFFSTATE_CONFLICTED_IGNORED))); + // TortoiseGitMerge changes here file.Add(_T("<<<<<<< .mine"), m_pwndRightView->lineendings); for (int j=first; jSetModified(FALSE); if (m_pwndRightView) m_pwndRightView->SetModified(FALSE); CUndo::GetInstance().MarkAsOriginalState(); + if (file.GetCount() == 1 && file.GetLineEnding(0) == EOL_NOENDING) + return 0; return file.GetCount(); } - return -1; + return 1; } void CMainFrame::OnFileSave() @@ -1084,6 +1150,36 @@ void CMainFrame::OnFileSave() FileSave(); } +void CMainFrame::PatchSave() +{ + bool bDoesNotExist = !PathFileExists(m_Data.m_mergedFile.GetFilename()); + if (m_Data.m_bPatchRequired) + { + m_Patch.PatchPath(m_Data.m_mergedFile.GetFilename()); + } + if (!PathIsDirectory(m_Data.m_mergedFile.GetFilename())) + { + int saveret = SaveFile(m_Data.m_mergedFile.GetFilename()); + if (saveret==0) + { + // file was saved with 0 lines, remove it. + m_Patch.RemoveFile(m_Data.m_mergedFile.GetFilename()); + // just in case + DeleteFile(m_Data.m_mergedFile.GetFilename()); + } + m_Data.m_mergedFile.StoreFileAttributes(); + if (m_Data.m_mergedFile.GetFilename() == m_Data.m_yourFile.GetFilename()) + m_Data.m_yourFile.StoreFileAttributes(); + if ((bDoesNotExist)&&(DWORD(CRegDWORD(_T("Software\\TortoiseGitMerge\\AutoAdd"), TRUE)))) + { + // call TortoiseProc to add the new file to version control + CString cmd = _T("/command:add /noui /path:\""); + cmd += m_Data.m_mergedFile.GetFilename() + _T("\""); + CAppUtils::RunTortoiseGitProc(cmd); + } + } +} + bool CMainFrame::FileSave(bool bCheckResolved /*=true*/) { if (!m_Data.m_mergedFile.InUse()) @@ -1097,26 +1193,19 @@ bool CMainFrame::FileSave(bool bCheckResolved /*=true*/) { bDoesNotExist = (GetLastError() == ERROR_FILE_NOT_FOUND); } - if (bCheckResolved) - { - int nConflictLine = CheckResolved(); - if (nConflictLine >= 0) - { - CString sTemp; - sTemp.Format(IDS_ERR_MAINFRAME_FILEHASCONFLICTS, m_pwndBottomView->m_pViewData->GetLineNumber(nConflictLine)+1); - if (MessageBox(sTemp, 0, MB_ICONERROR | MB_YESNO)!=IDYES) - { - if (m_pwndBottomView) - m_pwndBottomView->GoToLine(nConflictLine); - return false; - } - } - } + if (bCheckResolved && HasConflictsWontKeep()) + return false; + if (((DWORD)CRegDWORD(_T("Software\\TortoiseGitMerge\\Backup"))) != 0) { MoveFileEx(m_Data.m_mergedFile.GetFilename(), m_Data.m_mergedFile.GetFilename() + _T(".bak"), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH); } - if (SaveFile(m_Data.m_mergedFile.GetFilename())==0) + if (m_Data.m_bPatchRequired) + { + m_Patch.PatchPath(m_Data.m_mergedFile.GetFilename()); + } + int saveret = SaveFile(m_Data.m_mergedFile.GetFilename()); + if (saveret==0) { // file was saved with 0 lines! // ask the user if the file should be deleted @@ -1124,16 +1213,63 @@ bool CMainFrame::FileSave(bool bCheckResolved /*=true*/) sTemp.Format(IDS_DELETEWHENEMPTY, (LPCTSTR)m_Data.m_mergedFile.GetFilename()); if (CMessageBox::ShowCheck(m_hWnd, sTemp, _T("TortoiseGitMerge"), MB_YESNO, _T("DeleteFileWhenEmpty")) == IDYES) { + m_Patch.RemoveFile(m_Data.m_mergedFile.GetFilename()); DeleteFile(m_Data.m_mergedFile.GetFilename()); } } + else if (saveret < 0) + { + // error while saving the file + return false; + } + + // if we're in conflict resolve mode (three pane view), check if there are no more conflicts + // and if there aren't, ask to mark the file as resolved + if (IsViewGood(m_pwndBottomView) && !m_bHasConflicts) + { +#if 0 // TGIT TODO + CTGitPath svnpath = CTGitPath(m_Data.m_mergedFile.GetFilename()); + if (SVNHelper::IsVersioned(svnpath, true)) + { + SVNPool pool; + svn_opt_revision_t rev; + rev.kind = svn_opt_revision_unspecified; + svn_wc_status_kind statuskind = svn_wc_status_none; + svn_client_ctx_t * ctx = NULL; + svn_error_clear(svn_client_create_context2(&ctx, SVNConfig::Instance().GetConfig(pool), pool)); + svn_error_t * err = svn_client_status5(NULL, ctx, svnpath.GetSVNApiPath(pool), &rev, + svn_depth_empty, + true, + false, + true, + true, + false, + NULL, + getallstatus, + &statuskind, + pool); + if ((err == NULL) && (statuskind == svn_wc_status_conflicted)) + { + CString sTemp; + sTemp.Format(IDS_MARKASRESOLVED, (LPCTSTR)CPathUtils::GetFileNameFromPath(m_Data.m_mergedFile.GetFilename())); + if (CMessageBox::Show(m_hWnd, sTemp, _T("TortoiseGitMerge"), MB_YESNO) == IDYES)) + MarkAsResolved(); + } + svn_error_clear(err); + } +#endif + } - if (bDoesNotExist) + m_bSaveRequired = false; + m_Data.m_mergedFile.StoreFileAttributes(); + + if ((bDoesNotExist)&&(DWORD(CRegDWORD(_T("Software\\TortoiseGitMerge\\AutoAdd"), TRUE)))) { - // call TortoiseGitProc to add the new file to version control + // call TortoiseProc to add the new file to version control CString cmd = _T("/command:add /noui /path:\""); cmd += m_Data.m_mergedFile.GetFilename() + _T("\""); - CAppUtils::RunTortoiseGitProc(cmd); + if(!CAppUtils::RunTortoiseGitProc(cmd)) + return false; } return true; } @@ -1145,58 +1281,15 @@ void CMainFrame::OnFileSaveAs() bool CMainFrame::FileSaveAs(bool bCheckResolved /*=true*/) { - if (bCheckResolved) - { - int nConflictLine = CheckResolved(); - if (nConflictLine >= 0) - { - CString sTemp; - sTemp.Format(IDS_ERR_MAINFRAME_FILEHASCONFLICTS, m_pwndBottomView->m_pViewData->GetLineNumber(nConflictLine)+1); - if (MessageBox(sTemp, 0, MB_ICONERROR | MB_YESNO)!=IDYES) - { - if (m_pwndBottomView) - m_pwndBottomView->GoToLine(nConflictLine); - return false; - } - } - } - OPENFILENAME ofn = {0}; // common dialog box structure - TCHAR szFile[MAX_PATH] = {0}; // buffer for file name - ofn.lStructSize = sizeof(OPENFILENAME); - ofn.hwndOwner = m_hWnd; - ofn.lpstrFile = szFile; - ofn.nMaxFile = _countof(szFile); - CString temp; - temp.LoadString(IDS_SAVEASTITLE); - if (!temp.IsEmpty()) - ofn.lpstrTitle = temp; - ofn.Flags = OFN_OVERWRITEPROMPT; - CString sFilter; - sFilter.LoadString(IDS_COMMONFILEFILTER); - TCHAR * pszFilters = new TCHAR[sFilter.GetLength()+4]; - _tcscpy_s (pszFilters, sFilter.GetLength()+4, sFilter); - // Replace '|' delimiters with '\0's - TCHAR *ptr = pszFilters + _tcslen(pszFilters); //set ptr at the NULL - while (ptr != pszFilters) - { - if (*ptr == '|') - *ptr = '\0'; - ptr--; - } - ofn.lpstrFilter = pszFilters; - ofn.nFilterIndex = 1; - - // Display the Open dialog box. - CString sFile; - if (GetSaveFileName(&ofn)==TRUE) - { - sFile = CString(ofn.lpstrFile); - SaveFile(sFile); - delete [] pszFilters; - return true; - } - delete [] pszFilters; - return false; + if (bCheckResolved && HasConflictsWontKeep()) + return false; + + CString fileName; + if(!TryGetFileName(fileName)) + return false; + + SaveFile(fileName); + return true; } void CMainFrame::OnUpdateFileSave(CCmdUI *pCmdUI) @@ -1204,21 +1297,11 @@ void CMainFrame::OnUpdateFileSave(CCmdUI *pCmdUI) BOOL bEnable = FALSE; if (m_Data.m_mergedFile.InUse()) { - if (m_pwndBottomView) - { - if ((m_pwndBottomView->IsWindowVisible())&&(m_pwndBottomView->m_pViewData)) - { - bEnable = TRUE; - } - } - if (m_pwndRightView) - { - if ((m_pwndRightView->IsWindowVisible())&&(m_pwndRightView->m_pViewData)) - { - if (m_pwndRightView->IsModified() || (m_Data.m_yourFile.GetWindowName().Right(9).Compare(_T(": patched"))==0)) - bEnable = TRUE; - } - } + if (IsViewGood(m_pwndBottomView)&&(m_pwndBottomView->m_pViewData)) + bEnable = TRUE; + else if ( (IsViewGood(m_pwndRightView)&&(m_pwndRightView->m_pViewData)) && + (m_pwndRightView->IsModified() || (m_Data.m_yourFile.GetWindowName().Right(9).Compare(_T(": patched"))==0)) ) + bEnable = TRUE; } pCmdUI->Enable(bEnable); } @@ -1226,32 +1309,20 @@ void CMainFrame::OnUpdateFileSave(CCmdUI *pCmdUI) void CMainFrame::OnUpdateFileSaveAs(CCmdUI *pCmdUI) { BOOL bEnable = FALSE; - if (m_pwndBottomView) - { - if ((m_pwndBottomView->IsWindowVisible())&&(m_pwndBottomView->m_pViewData)) - { - bEnable = TRUE; - } - } - if (m_pwndRightView) - { - if ((m_pwndRightView->IsWindowVisible())&&(m_pwndRightView->m_pViewData)) - { - bEnable = TRUE; - } - } + if (IsViewGood(m_pwndBottomView)&&(m_pwndBottomView->m_pViewData)) + bEnable = TRUE; + else if (IsViewGood(m_pwndRightView)&&(m_pwndRightView->m_pViewData)) + bEnable = TRUE; pCmdUI->Enable(bEnable); } - void CMainFrame::OnUpdateViewOnewaydiff(CCmdUI *pCmdUI) { pCmdUI->SetCheck(!m_bOneWay); BOOL bEnable = TRUE; - if (m_pwndBottomView) + if (IsViewGood(m_pwndBottomView)) { - if (m_pwndBottomView->IsWindowVisible()) - bEnable = FALSE; + bEnable = FALSE; } pCmdUI->Enable(bEnable); } @@ -1281,18 +1352,13 @@ void CMainFrame::OnViewOptions() void CMainFrame::OnClose() { - if ((m_pFindDialog)&&(!m_pFindDialog->IsTerminating())) - { - m_pFindDialog->SendMessage(WM_CLOSE); - return; - } - int ret = IDNO; - if (((m_pwndBottomView)&&(m_pwndBottomView->IsModified())) || - ((m_pwndRightView)&&(m_pwndRightView->IsModified()))) + UINT ret = IDNO; + if (HasUnsavedEdits()) { CString sTemp; sTemp.LoadString(IDS_ASKFORSAVE); ret = MessageBox(sTemp, 0, MB_YESNOCANCEL | MB_ICONQUESTION); + if (ret == IDYES) { if (!FileSave()) @@ -1324,333 +1390,58 @@ void CMainFrame::OnClose() } } -void CMainFrame::OnEditFind() -{ - if (m_pFindDialog) - { - return; - } - else +void CMainFrame::OnActivate(UINT nValue, CWnd* /*pwnd*/, BOOL /*bActivated?*/) { + if (nValue != 0) // activated { - // start searching from the start again - // if no line is selected, otherwise start from - // the selected line - m_nSearchIndex = FindSearchStart(0); - m_pFindDialog = new CFindDlg(); - m_pFindDialog->Create(this); + if (IsIconic()) + { + m_bCheckReload = TRUE; + } + else + CheckForReload(); } } -LRESULT CMainFrame::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/) +void CMainFrame::OnViewLinedown() { - ASSERT(m_pFindDialog != NULL); - - if (m_pFindDialog->IsTerminating()) - { - // invalidate the handle identifying the dialog box. - m_pFindDialog = NULL; - return 0; - } - - if(m_pFindDialog->FindNext()) - { - //read data from dialog - m_sFindText = m_pFindDialog->GetFindString(); - m_bMatchCase = (m_pFindDialog->MatchCase() == TRUE); - m_bLimitToDiff = m_pFindDialog->LimitToDiffs(); - m_bWholeWord = m_pFindDialog->WholeWord(); - - OnEditFindnext(); - } - - return 0; + OnViewLineUpDown(1); } -bool CharIsDelimiter(const CString& ch) +void CMainFrame::OnViewLineup() { - CString delimiters(_T(" .,:;=+-*/\\\n\t()[]<>@")); - return delimiters.Find(ch) >= 0; + OnViewLineUpDown(-1); } -bool CMainFrame::StringFound(const CString& str)const -{ - int nSubStringStartIdx = str.Find(m_sFindText); - bool bStringFound = (nSubStringStartIdx >= 0); - if (bStringFound && m_bWholeWord) - { - if (nSubStringStartIdx) - bStringFound = CharIsDelimiter(str.Mid(nSubStringStartIdx-1,1)); - - if (bStringFound) - { - int nEndIndex = nSubStringStartIdx + m_sFindText.GetLength(); - if (str.GetLength() > nEndIndex) - bStringFound = CharIsDelimiter(str.Mid(nEndIndex, 1)); - } - } - return bStringFound; -} - -void CMainFrame::OnEditFindprev() -{ - Search(SearchPrevious); -} - -void CMainFrame::OnEditFindnext() -{ - Search(SearchNext); -} - -void CMainFrame::Search(SearchDirection srchDir) -{ - if (m_sFindText.IsEmpty()) - return; - - if ((m_pwndLeftView)&&(m_pwndLeftView->m_pViewData)) - { - bool bFound = FALSE; - - CString left; - CString right; - CString bottom; - DiffStates leftstate = DIFFSTATE_NORMAL; - DiffStates rightstate = DIFFSTATE_NORMAL; - DiffStates bottomstate = DIFFSTATE_NORMAL; - int i = 0; - - m_nSearchIndex = FindSearchStart(m_nSearchIndex); - m_nSearchIndex++; - if (m_nSearchIndex >= m_pwndLeftView->m_pViewData->GetCount()) - m_nSearchIndex = 0; - if (srchDir == SearchPrevious) - { - // SearchIndex points 1 past where we found the last match, - // so if we are searching backwards we need to adjust accordingly - m_nSearchIndex -= 2; - // if at the top, start again from the end - if (m_nSearchIndex < 0) - m_nSearchIndex += m_pwndLeftView->m_pViewData->GetCount(); - } - const int idxLimits[2][2][2]={{{m_nSearchIndex, m_pwndLeftView->m_pViewData->GetCount()}, - {0, m_nSearchIndex}}, - {{m_nSearchIndex, -1}, - {m_pwndLeftView->m_pViewData->GetCount()-1, m_nSearchIndex}}}; - const int offsets[2]={+1, -1}; - - for (int j=0; j != 2 && !bFound; ++j) - { - for (i=idxLimits[srchDir][j][0]; i != idxLimits[srchDir][j][1]; i += offsets[srchDir]) - { - left = m_pwndLeftView->m_pViewData->GetLine(i); - leftstate = m_pwndLeftView->m_pViewData->GetState(i); - if ((!m_bOneWay)&&(m_pwndRightView->m_pViewData)) - { - right = m_pwndRightView->m_pViewData->GetLine(i); - rightstate = m_pwndRightView->m_pViewData->GetState(i); - } - if ((m_pwndBottomView)&&(m_pwndBottomView->m_pViewData)) - { - bottom = m_pwndBottomView->m_pViewData->GetLine(i); - bottomstate = m_pwndBottomView->m_pViewData->GetState(i); - } - - if (!m_bMatchCase) - { - left = left.MakeLower(); - right = right.MakeLower(); - bottom = bottom.MakeLower(); - m_sFindText = m_sFindText.MakeLower(); - } - if (StringFound(left)) - { - if ((!m_bLimitToDiff)||(leftstate != DIFFSTATE_NORMAL)) - { - bFound = TRUE; - break; - } - } - else if (StringFound(right)) - { - if ((!m_bLimitToDiff)||(rightstate != DIFFSTATE_NORMAL)) - { - bFound = TRUE; - break; - } - } - else if (StringFound(bottom)) - { - if ((!m_bLimitToDiff)||(bottomstate != DIFFSTATE_NORMAL)) - { - bFound = TRUE; - break; - } - } - } - } - if (bFound) - { - m_nSearchIndex = i; - m_pwndLeftView->GoToLine(m_nSearchIndex); - if (StringFound(left)) - { - m_pwndLeftView->SetFocus(); - m_pwndLeftView->HiglightLines(m_nSearchIndex); - } - else if (StringFound(right)) - { - m_pwndRightView->SetFocus(); - m_pwndRightView->HiglightLines(m_nSearchIndex); - } - else if (StringFound(bottom)) - { - m_pwndBottomView->SetFocus(); - m_pwndBottomView->HiglightLines(m_nSearchIndex); - } - } - else - { - m_nSearchIndex = 0; - } - } -} - -int CMainFrame::FindSearchStart(int nDefault) -{ - // TortoiseMerge doesn't have a cursor which we could use to - // anchor the search on. - // Instead we use a line that is selected. - // If however no line is selected, use the default line (which could - // be the top of the document for a new search, or the line where the - // search was successful on) - int nLine = nDefault; - int nSelStart = 0; - int nSelEnd = 0; - if (m_pwndLeftView) - { - if (m_pwndLeftView->GetSelection(nSelStart, nSelEnd)) - { - if (nSelStart == nSelEnd) - nLine = nSelStart; - } - } - else if ((nLine == nDefault)&&(m_pwndRightView)) - { - if (m_pwndRightView->GetSelection(nSelStart, nSelEnd)) - { - if (nSelStart == nSelEnd) - nLine = nSelStart; - } - } - else if ((nLine == nDefault)&&(m_pwndBottomView)) - { - if (m_pwndBottomView->GetSelection(nSelStart, nSelEnd)) - { - if (nSelStart == nSelEnd) - nLine = nSelStart; - } - } - return nLine; -} - -bool CMainFrame::GetCurrentLineNum(CBaseView *&view, CBaseView *&view2, CBaseView *&view3, int &lineNum) -{ - view = view2 = view3 = NULL; - lineNum = 1; - CWnd *wnd = this->GetFocus(); - if (wnd != m_pwndLeftView && wnd != m_pwndRightView && wnd != m_pwndBottomView) - return false; - - view = (CBaseView *)wnd; - view2 = view == m_pwndBottomView || view == m_pwndRightView ? (CBaseView *)m_pwndLeftView : m_pwndRightView; - view3 = view == m_pwndBottomView || view == m_pwndLeftView ? (CBaseView *)m_pwndRightView : m_Data.IsTheirFileInUse() ? m_pwndBottomView : NULL; - - int start, end; - int count = view->m_pViewData->GetCount(); - if (view->GetSelection(start, end)) - { - int line = DIFF_EMPTYLINENUMBER; - for (int i = start; i < count; i++) - { - line = view->m_pViewData->GetLineNumber(i); - if (line != DIFF_EMPTYLINENUMBER) - break; - } - - lineNum = line == DIFF_EMPTYLINENUMBER ? 1 : (line + 1); - } - - return true; -} - -void CMainFrame::SetCurrentLineNum(CBaseView *view, CBaseView *view2, CBaseView *view3, int lineNum) -{ - if (view == NULL) - return; - int index = view->m_pViewData->FindLineNumber(lineNum - 1); - if (index < 0) - index = 0; - view->HiglightLines(index); - view->GoToLine(index, FALSE); - if (view2) - view2->GoToLine(index, FALSE); - if (view3) - view3->GoToLine(index, FALSE); -} - -void CMainFrame::OnEditGoto() -{ - CBaseView *view, *view2, *view3; - int lineNum; - if (!GetCurrentLineNum(view, view2, view3, lineNum)) - return; - - CEditGotoDlg dlg; - dlg.m_LineNumber = lineNum; - - if (dlg.DoModal() == IDOK) - SetCurrentLineNum(view, view2, view3, dlg.m_LineNumber); -} - -void CMainFrame::OnViewLinedown() +void CMainFrame::OnViewLineUpDown(int direction) { if (m_pwndLeftView) - m_pwndLeftView->ScrollToLine(m_pwndLeftView->m_nTopLine+1); + m_pwndLeftView->ScrollToLine(m_pwndLeftView->m_nTopLine+direction); if (m_pwndRightView) - m_pwndRightView->ScrollToLine(m_pwndRightView->m_nTopLine+1); + m_pwndRightView->ScrollToLine(m_pwndRightView->m_nTopLine+direction); if (m_pwndBottomView) - m_pwndBottomView->ScrollToLine(m_pwndBottomView->m_nTopLine+1); + m_pwndBottomView->ScrollToLine(m_pwndBottomView->m_nTopLine+direction); m_wndLocatorBar.Invalidate(); + m_nMoveMovesToIgnore = MOVESTOIGNORE; } -void CMainFrame::OnViewLineup() +void CMainFrame::OnViewLineleft() { - if (m_pwndLeftView) - m_pwndLeftView->ScrollToLine(m_pwndLeftView->m_nTopLine-1); - if (m_pwndRightView) - m_pwndRightView->ScrollToLine(m_pwndRightView->m_nTopLine-1); - if (m_pwndBottomView) - m_pwndBottomView->ScrollToLine(m_pwndBottomView->m_nTopLine-1); - m_wndLocatorBar.Invalidate(); + OnViewLineLeftRight(-1); } -void CMainFrame::OnViewLineleft() +void CMainFrame::OnViewLineright() { - if (m_pwndLeftView) - m_pwndLeftView->ScrollSide(-1); - if (m_pwndRightView) - m_pwndRightView->ScrollSide(-1); - if (m_pwndBottomView) - m_pwndBottomView->ScrollSide(-1); + OnViewLineLeftRight(1); } -void CMainFrame::OnViewLineright() +void CMainFrame::OnViewLineLeftRight(int direction) { if (m_pwndLeftView) - m_pwndLeftView->ScrollSide(1); + m_pwndLeftView->ScrollSide(direction); if (m_pwndRightView) - m_pwndRightView->ScrollSide(1); + m_pwndRightView->ScrollSide(direction); if (m_pwndBottomView) - m_pwndBottomView->ScrollSide(1); + m_pwndBottomView->ScrollSide(direction); } void CMainFrame::OnEditUseTheirs() @@ -1660,14 +1451,9 @@ void CMainFrame::OnEditUseTheirs() } void CMainFrame::OnUpdateEditUsetheirblock(CCmdUI *pCmdUI) { - int nSelBlockStart = -1; - int nSelBlockEnd = -1; - if (m_pwndBottomView) - m_pwndBottomView->GetSelection(nSelBlockStart, nSelBlockEnd); - pCmdUI->Enable((nSelBlockStart >= 0)&&(nSelBlockEnd >= 0)); + pCmdUI->Enable(m_pwndBottomView && m_pwndBottomView->HasSelection()); } - void CMainFrame::OnEditUseMine() { if (m_pwndBottomView) @@ -1675,94 +1461,90 @@ void CMainFrame::OnEditUseMine() } void CMainFrame::OnUpdateEditUsemyblock(CCmdUI *pCmdUI) { - int nSelBlockStart = -1; - int nSelBlockEnd = -1; - if (m_pwndBottomView) - m_pwndBottomView->GetSelection(nSelBlockStart, nSelBlockEnd); - pCmdUI->Enable((nSelBlockStart >= 0)&&(nSelBlockEnd >= 0)); + OnUpdateEditUsetheirblock(pCmdUI); } - void CMainFrame::OnEditUseTheirsThenMine() { if (m_pwndBottomView) - m_pwndBottomView->UseTheirThenMyTextBlock(); + m_pwndBottomView->UseTheirAndYourBlock(); } + void CMainFrame::OnUpdateEditUsetheirthenmyblock(CCmdUI *pCmdUI) { - int nSelBlockStart = -1; - int nSelBlockEnd = -1; - if (m_pwndBottomView) - m_pwndBottomView->GetSelection(nSelBlockStart, nSelBlockEnd); - pCmdUI->Enable((nSelBlockStart >= 0)&&(nSelBlockEnd >= 0)); + OnUpdateEditUsetheirblock(pCmdUI); } - void CMainFrame::OnEditUseMineThenTheirs() { if (m_pwndBottomView) - m_pwndBottomView->UseMyThenTheirTextBlock(); + m_pwndBottomView->UseYourAndTheirBlock(); } + void CMainFrame::OnUpdateEditUseminethentheirblock(CCmdUI *pCmdUI) { - int nSelBlockStart = -1; - int nSelBlockEnd = -1; - if (m_pwndBottomView) - m_pwndBottomView->GetSelection(nSelBlockStart, nSelBlockEnd); - pCmdUI->Enable((nSelBlockStart >= 0)&&(nSelBlockEnd >= 0)); + OnUpdateEditUsetheirblock(pCmdUI); } void CMainFrame::OnEditUseleftblock() { - if (m_pwndRightView) - m_pwndRightView->UseBlock(); + if (m_pwndBottomView->IsWindowVisible()) + m_pwndBottomView->UseRightBlock(); + else + m_pwndRightView->UseLeftBlock(); } void CMainFrame::OnUpdateEditUseleftblock(CCmdUI *pCmdUI) { - pCmdUI->Enable(m_pwndRightView && m_pwndRightView->IsWindowVisible() && m_pwndRightView->HasCaret() && m_pwndRightView->HasSelection()); + pCmdUI->Enable(IsViewGood(m_pwndRightView) && m_pwndRightView->IsTarget() && m_pwndRightView->HasSelection()); +} + +void CMainFrame::OnUpdateUseBlock(CCmdUI *pCmdUI) +{ + pCmdUI->Enable(TRUE); } void CMainFrame::OnEditUseleftfile() { - if (m_pwndRightView) - m_pwndRightView->UseFile(); + if (m_pwndBottomView->IsWindowVisible()) + m_pwndBottomView->UseRightFile(); + else + m_pwndRightView->UseLeftFile(); } void CMainFrame::OnUpdateEditUseleftfile(CCmdUI *pCmdUI) { - pCmdUI->Enable(m_pwndRightView && m_pwndRightView->IsWindowVisible() && m_pwndRightView->HasCaret()); + pCmdUI->Enable(IsViewGood(m_pwndRightView) && m_pwndRightView->IsTarget()); } void CMainFrame::OnEditUseblockfromleftbeforeright() { if (m_pwndRightView) - m_pwndRightView->UseLeftBeforeRight(); + m_pwndRightView->UseBothLeftFirst(); } void CMainFrame::OnUpdateEditUseblockfromleftbeforeright(CCmdUI *pCmdUI) { - pCmdUI->Enable(m_pwndRightView && m_pwndRightView->IsWindowVisible() && m_pwndRightView->HasCaret() && m_pwndRightView->HasSelection()); + OnUpdateEditUseleftblock(pCmdUI); } void CMainFrame::OnEditUseblockfromrightbeforeleft() { if (m_pwndRightView) - m_pwndRightView->UseRightBeforeLeft(); + m_pwndRightView->UseBothRightFirst(); } void CMainFrame::OnUpdateEditUseblockfromrightbeforeleft(CCmdUI *pCmdUI) { - pCmdUI->Enable(m_pwndRightView && m_pwndRightView->IsWindowVisible() && m_pwndRightView->HasCaret() && m_pwndRightView->HasSelection()); + OnUpdateEditUseleftblock(pCmdUI); } - void CMainFrame::OnFileReload() { if (CheckForSave()==IDCANCEL) return; CDiffColors::GetInstance().LoadRegistry(); - LoadViews(true); + LoadViews(-1); } void CMainFrame::ActivateFrame(int nCmdShow) @@ -1800,7 +1582,6 @@ void CMainFrame::ActivateFrame(int nCmdShow) // and finally, bring to top after showing BringToTop(nCmdShow); } - return; } BOOL CMainFrame::ReadWindowPlacement(WINDOWPLACEMENT * pwp) @@ -1826,9 +1607,8 @@ void CMainFrame::WriteWindowPlacement(WINDOWPLACEMENT * pwp) { CRegString placement = CRegString(_T("Software\\TortoiseGitMerge\\WindowPos")); TCHAR szBuffer[_countof("-32767")*8 + sizeof("65535")*2]; - CString s; - _stprintf_s(szBuffer, _countof("-32767")*8 + sizeof("65535")*2, _T("%u,%u,%d,%d,%d,%d,%d,%d,%d,%d"), + _stprintf_s(szBuffer, _T("%u,%u,%d,%d,%d,%d,%d,%d,%d,%d"), pwp->flags, pwp->showCmd, pwp->ptMinPosition.x, pwp->ptMinPosition.y, pwp->ptMaxPosition.x, pwp->ptMaxPosition.y, @@ -1844,12 +1624,9 @@ void CMainFrame::OnUpdateMergeMarkasresolved(CCmdUI *pCmdUI) BOOL bEnable = FALSE; if ((!m_bReadOnly)&&(m_Data.m_mergedFile.InUse())) { - if (m_pwndBottomView) + if (IsViewGood(m_pwndBottomView)&&(m_pwndBottomView->m_pViewData)) { - if ((m_pwndBottomView->IsWindowVisible())&&(m_pwndBottomView->m_pViewData)) - { - bEnable = TRUE; - } + bEnable = TRUE; } } pCmdUI->Enable(bEnable); @@ -1857,27 +1634,16 @@ void CMainFrame::OnUpdateMergeMarkasresolved(CCmdUI *pCmdUI) void CMainFrame::OnMergeMarkasresolved() { - int nConflictLine = CheckResolved(); - if (nConflictLine >= 0) - { - CString sTemp; - sTemp.Format(IDS_ERR_MAINFRAME_FILEHASCONFLICTS, m_pwndBottomView->m_pViewData->GetLineNumber(nConflictLine)+1); - if (MessageBox(sTemp, 0, MB_ICONERROR | MB_YESNO)!=IDYES) - { - if (m_pwndBottomView) - m_pwndBottomView->GoToLine(nConflictLine); - return; - } - } + if(HasConflictsWontKeep()) + return; + // now check if the file has already been saved and if not, save it. if (m_Data.m_mergedFile.InUse()) { - if (m_pwndBottomView) + if (IsViewGood(m_pwndBottomView)&&(m_pwndBottomView->m_pViewData)) { - if ((m_pwndBottomView->IsWindowVisible())&&(m_pwndBottomView->m_pViewData)) - { - FileSave(false); - } + FileSave(false); + m_bSaveRequired = false; } } MarkAsResolved(); @@ -1887,26 +1653,124 @@ BOOL CMainFrame::MarkAsResolved() { if (m_bReadOnly) return FALSE; - if (!((m_pwndBottomView) && (m_pwndBottomView->IsWindowVisible()))) + if (!IsViewGood(m_pwndBottomView)) return FALSE; CString cmd = _T("/command:resolve /path:\""); cmd += m_Data.m_mergedFile.GetFilename(); cmd += _T("\" /closeonend:1 /noquestion /skipcheck /silent"); - if (!CAppUtils::RunTortoiseGitProc(cmd)) + if (resolveMsgWnd) + { + CString s; + s.Format(L" /resolvemsghwnd:%I64d /resolvemsgwparam:%I64d /resolvemsglparam:%I64d", (__int64)resolveMsgWnd, (__int64)resolveMsgWParam, (__int64)resolveMsgLParam); + cmd += s; + } + if(!CAppUtils::RunTortoiseGitProc(cmd)) return FALSE; - + m_bSaveRequired = false; return TRUE; } void CMainFrame::OnUpdateMergeNextconflict(CCmdUI *pCmdUI) { - pCmdUI->Enable(m_bHasConflicts); + BOOL bShow = FALSE; + if (HasNextConflict(m_pwndBottomView)) + bShow = TRUE; + else if (HasNextConflict(m_pwndRightView)) + bShow = TRUE; + else if (HasNextConflict(m_pwndLeftView)) + bShow = TRUE; + pCmdUI->Enable(bShow); +} + +bool CMainFrame::HasNextConflict(CBaseView* view) +{ + if (view == 0) + return false; + if (!view->IsTarget()) + return false; + return view->HasNextConflict(); } void CMainFrame::OnUpdateMergePreviousconflict(CCmdUI *pCmdUI) { - pCmdUI->Enable(m_bHasConflicts); + BOOL bShow = FALSE; + if (HasPrevConflict(m_pwndBottomView)) + bShow = TRUE; + else if (HasPrevConflict(m_pwndRightView)) + bShow = TRUE; + else if (HasPrevConflict(m_pwndLeftView)) + bShow = TRUE; + pCmdUI->Enable(bShow); +} + +bool CMainFrame::HasPrevConflict(CBaseView* view) +{ + if (view == 0) + return false; + if (!view->IsTarget()) + return false; + return view->HasPrevConflict(); +} + +void CMainFrame::OnUpdateNavigateNextdifference(CCmdUI *pCmdUI) +{ + CBaseView* baseView = GetActiveBaseView(); + BOOL bShow = FALSE; + if (baseView != 0) + bShow = baseView->HasNextDiff(); + pCmdUI->Enable(bShow); +} + +void CMainFrame::OnUpdateNavigatePreviousdifference(CCmdUI *pCmdUI) +{ + CBaseView* baseView = GetActiveBaseView(); + BOOL bShow = FALSE; + if (baseView != 0) + bShow = baseView->HasPrevDiff(); + pCmdUI->Enable(bShow); +} + +void CMainFrame::OnUpdateNavigateNextinlinediff(CCmdUI *pCmdUI) +{ + BOOL bShow = FALSE; + if (HasNextInlineDiff(m_pwndBottomView)) + bShow = TRUE; + else if (HasNextInlineDiff(m_pwndRightView)) + bShow = TRUE; + else if (HasNextInlineDiff(m_pwndLeftView)) + bShow = TRUE; + pCmdUI->Enable(bShow); +} + +bool CMainFrame::HasNextInlineDiff(CBaseView* view) +{ + if (view == 0) + return false; + if (!view->IsTarget()) + return false; + return view->HasNextInlineDiff(); +} + +void CMainFrame::OnUpdateNavigatePrevinlinediff(CCmdUI *pCmdUI) +{ + BOOL bShow = FALSE; + if (HasPrevInlineDiff(m_pwndBottomView)) + bShow = TRUE; + else if (HasPrevInlineDiff(m_pwndRightView)) + bShow = TRUE; + else if (HasPrevInlineDiff(m_pwndLeftView)) + bShow = TRUE; + pCmdUI->Enable(bShow); +} + +bool CMainFrame::HasPrevInlineDiff(CBaseView* view) +{ + if (view == 0) + return false; + if (!view->IsTarget()) + return false; + return view->HasPrevInlineDiff(); } void CMainFrame::OnMoving(UINT fwSide, LPRECT pRect) @@ -1936,18 +1800,29 @@ void CMainFrame::OnUpdateEditCopy(CCmdUI *pCmdUI) BOOL bShow = FALSE; if ((m_pwndBottomView)&&(m_pwndBottomView->HasSelection())) bShow = TRUE; - if ((m_pwndRightView)&&(m_pwndRightView->HasSelection())) + else if ((m_pwndRightView)&&(m_pwndRightView->HasSelection())) bShow = TRUE; - if ((m_pwndLeftView)&&(m_pwndLeftView->HasSelection())) + else if ((m_pwndLeftView)&&(m_pwndLeftView->HasSelection())) bShow = TRUE; pCmdUI->Enable(bShow); } +void CMainFrame::OnUpdateEditPaste(CCmdUI *pCmdUI) +{ + BOOL bWritable = FALSE; + if ((m_pwndBottomView)&&(m_pwndBottomView->IsWritable())) + bWritable = TRUE; + else if ((m_pwndRightView)&&(m_pwndRightView->IsWritable())) + bWritable = TRUE; + else if ((m_pwndLeftView)&&(m_pwndLeftView->IsWritable())) + bWritable = TRUE; + pCmdUI->Enable(bWritable && ::IsClipboardFormatAvailable(CF_TEXT)); +} + void CMainFrame::OnViewSwitchleft() { int ret = IDNO; - if (((m_pwndBottomView)&&(m_pwndBottomView->IsModified())) || - ((m_pwndRightView)&&(m_pwndRightView->IsModified()))) + if (HasUnsavedEdits()) { CString sTemp; sTemp.LoadString(IDS_ASKFORSAVE); @@ -1977,24 +1852,14 @@ void CMainFrame::OnViewSwitchleft() void CMainFrame::OnUpdateViewSwitchleft(CCmdUI *pCmdUI) { - BOOL bEnable = TRUE; - if (m_pwndBottomView) - { - if (m_pwndBottomView->IsWindowVisible()) - bEnable = FALSE; - } + BOOL bEnable = !IsViewGood(m_pwndBottomView); pCmdUI->Enable(bEnable); } - void CMainFrame::OnUpdateViewShowfilelist(CCmdUI *pCmdUI) { - if (m_dlgFilePatches.HasFiles()) - { - pCmdUI->Enable(true); - } - else - pCmdUI->Enable(false); + BOOL bEnable = m_dlgFilePatches.HasFiles(); + pCmdUI->Enable(bEnable); pCmdUI->SetCheck(m_dlgFilePatches.IsWindowVisible()); } @@ -2008,7 +1873,6 @@ void CMainFrame::OnEditUndo() if (CUndo::GetInstance().CanUndo()) { CUndo::GetInstance().Undo(m_pwndLeftView, m_pwndRightView, m_pwndBottomView); - } } @@ -2017,111 +1881,194 @@ void CMainFrame::OnUpdateEditUndo(CCmdUI *pCmdUI) pCmdUI->Enable(CUndo::GetInstance().CanUndo()); } +int CMainFrame::CheckForReload() +{ + static bool bLock = false; //we don't want to check when activated after MessageBox we just created ... this is simple, but we don't need multithread lock + if (bLock) + { + return IDNO; + } + bLock = true; + bool bSourceChanged = + m_Data.m_baseFile.HasSourceFileChanged() + || m_Data.m_yourFile.HasSourceFileChanged() + || m_Data.m_theirFile.HasSourceFileChanged() + /*|| m_Data.m_mergedFile.HasSourceFileChanged()*/; + if (!bSourceChanged) + { + bLock = false; + return IDNO; + } + + int idsMessage = HasUnsavedEdits() ? IDS_WARNMODIFIEDOUTSIDELOOSECHANGES : IDS_WARNMODIFIEDOUTSIDE; + UINT ret = CMessageBox::Show(m_hWnd, idsMessage, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION); + + if (ret == IDYES) + { + CDiffColors::GetInstance().LoadRegistry(); + LoadViews(-1); + } + else + { + if (IsViewGood(m_pwndBottomView)) // three pane view + { + /*if (m_Data.m_sourceFile.HasSourceFileChanged()) + m_pwndBottomView->SetModified(); + if (m_Data.m_mergedFile.HasSourceFileChanged()) + m_pwndBottomView->SetModified();//*/ + if (m_Data.m_yourFile.HasSourceFileChanged()) + m_pwndRightView->SetModified(); + if (m_Data.m_theirFile.HasSourceFileChanged()) + m_pwndLeftView->SetModified(); + } + else if (IsViewGood(m_pwndRightView)) // two pane view + { + if (m_Data.m_baseFile.HasSourceFileChanged()) + m_pwndLeftView->SetModified(); + if (m_Data.m_yourFile.HasSourceFileChanged()) + m_pwndRightView->SetModified(); + } + else + { + if (m_Data.m_yourFile.HasSourceFileChanged()) + m_pwndLeftView->SetModified(); + } + + // no reload just store updated file time + m_Data.m_baseFile.StoreFileAttributes(); + m_Data.m_theirFile.StoreFileAttributes(); + m_Data.m_yourFile.StoreFileAttributes(); + //m_Data.m_mergedFile.StoreFileAttributes(); + } + bLock = false; + return ret; +} + int CMainFrame::CheckForSave() { - int ret = IDNO; - if (((m_pwndBottomView)&&(m_pwndBottomView->IsModified())) || - ((m_pwndRightView)&&(m_pwndRightView->IsModified()))) + UINT ret = IDNO; + if (HasUnsavedEdits()) { CString sTemp; - sTemp.LoadString(IDS_WARNMODIFIEDLOSECHANGES); + sTemp.LoadString(IDS_WARNMODIFIEDLOOSECHANGES); ret = MessageBox(sTemp, 0, MB_YESNOCANCEL | MB_ICONQUESTION); if (ret == IDYES) { - FileSave(); + if (!FileSave()) + ret = IDCANCEL; } } return ret; } +bool CMainFrame::HasUnsavedEdits() const +{ + return HasUnsavedEdits(m_pwndBottomView) || HasUnsavedEdits(m_pwndRightView) || m_bSaveRequired; +} + +bool CMainFrame::HasUnsavedEdits(const CBaseView* view) +{ + if(view == 0) + return false; + return view->IsModified(); +} + +bool CMainFrame::IsViewGood(const CBaseView* view) +{ + return CBaseView::IsViewGood(view); +} + void CMainFrame::OnViewInlinediffword() { m_bInlineWordDiff = !m_bInlineWordDiff; if (m_pwndLeftView) { m_pwndLeftView->SetInlineWordDiff(m_bInlineWordDiff); - m_pwndLeftView->Invalidate(); + m_pwndLeftView->BuildAllScreen2ViewVector(); + m_pwndLeftView->DocumentUpdated(); } if (m_pwndRightView) { m_pwndRightView->SetInlineWordDiff(m_bInlineWordDiff); - m_pwndRightView->Invalidate(); + m_pwndRightView->BuildAllScreen2ViewVector(); + m_pwndRightView->DocumentUpdated(); } if (m_pwndBottomView) { m_pwndBottomView->SetInlineWordDiff(m_bInlineWordDiff); - m_pwndBottomView->Invalidate(); + m_pwndBottomView->BuildAllScreen2ViewVector(); + m_pwndBottomView->DocumentUpdated(); } - m_wndLineDiffBar.Invalidate(); + m_wndLineDiffBar.DocumentUpdated(); } void CMainFrame::OnUpdateViewInlinediffword(CCmdUI *pCmdUI) { - pCmdUI->Enable(m_pwndLeftView && m_pwndLeftView->IsWindowVisible() && - m_pwndRightView && m_pwndRightView->IsWindowVisible()); + pCmdUI->Enable(m_bInlineDiff && IsViewGood(m_pwndLeftView) && IsViewGood(m_pwndRightView)); pCmdUI->SetCheck(m_bInlineWordDiff); } +void CMainFrame::OnViewInlinediff() +{ + m_bInlineDiff = !m_bInlineDiff; + if (m_pwndLeftView) + { + m_pwndLeftView->SetInlineDiff(m_bInlineDiff); + m_pwndLeftView->BuildAllScreen2ViewVector(); + m_pwndLeftView->DocumentUpdated(); + } + if (m_pwndRightView) + { + m_pwndRightView->SetInlineDiff(m_bInlineDiff); + m_pwndRightView->BuildAllScreen2ViewVector(); + m_pwndRightView->DocumentUpdated(); + } + if (m_pwndBottomView) + { + m_pwndBottomView->SetInlineDiff(m_bInlineDiff); + m_pwndBottomView->BuildAllScreen2ViewVector(); + m_pwndBottomView->DocumentUpdated(); + } + m_wndLineDiffBar.DocumentUpdated(); +} + +void CMainFrame::OnUpdateViewInlinediff(CCmdUI *pCmdUI) +{ + pCmdUI->Enable(IsViewGood(m_pwndLeftView) && IsViewGood(m_pwndRightView)); + pCmdUI->SetCheck(m_bInlineDiff); +} + void CMainFrame::OnUpdateEditCreateunifieddifffile(CCmdUI *pCmdUI) { // "create unified diff file" is only available if two files // are diffed, not three. bool bEnabled = true; - if ((m_pwndLeftView == NULL)||(!m_pwndLeftView->IsWindowVisible())) + if (!IsViewGood(m_pwndLeftView)) bEnabled = false; - if ((m_pwndRightView == NULL)||(!m_pwndRightView->IsWindowVisible())) + else if (!IsViewGood(m_pwndRightView)) bEnabled = false; - if ((m_pwndBottomView)&&(m_pwndBottomView->IsWindowVisible())) + else if (IsViewGood(m_pwndBottomView)) //no negation here bEnabled = false; pCmdUI->Enable(bEnabled); } void CMainFrame::OnEditCreateunifieddifffile() { - CString origFile, modifiedFile, outputFile; + CString origFile, modifiedFile; // the original file is the one on the left if (m_pwndLeftView) origFile = m_pwndLeftView->m_sFullFilePath; if (m_pwndRightView) modifiedFile = m_pwndRightView->m_sFullFilePath; - if (!origFile.IsEmpty() && !modifiedFile.IsEmpty()) - { - // ask for the path to save the unified diff file to - OPENFILENAME ofn = {0}; // common dialog box structure - TCHAR szFile[MAX_PATH] = {0}; // buffer for file name - ofn.lStructSize = sizeof(OPENFILENAME); - ofn.lpstrFile = szFile; - ofn.nMaxFile = _countof(szFile); - CString temp; - temp.LoadString(IDS_SAVEASTITLE); - if (!temp.IsEmpty()) - ofn.lpstrTitle = temp; - ofn.Flags = OFN_OVERWRITEPROMPT; - CString sFilter; - sFilter.LoadString(IDS_COMMONFILEFILTER); - TCHAR * pszFilters = new TCHAR[sFilter.GetLength()+4]; - _tcscpy_s (pszFilters, sFilter.GetLength()+4, sFilter); - // Replace '|' delimiters with '\0's - TCHAR *ptr = pszFilters + _tcslen(pszFilters); //set ptr at the NULL - while (ptr != pszFilters) - { - if (*ptr == '|') - *ptr = '\0'; - ptr--; - } - ofn.lpstrFilter = pszFilters; - ofn.nFilterIndex = 1; + if (origFile.IsEmpty() || modifiedFile.IsEmpty()) + return; - // Display the Save dialog box. - CString sFile; - if (GetSaveFileName(&ofn)==TRUE) - { - outputFile = CString(ofn.lpstrFile); - CAppUtils::CreateUnifiedDiff(origFile, modifiedFile, outputFile, true); - } - delete [] pszFilters; - } + CString outputFile; + if(!TryGetFileName(outputFile)) + return; + + CAppUtils::CreateUnifiedDiff(origFile, modifiedFile, outputFile, true); } void CMainFrame::OnUpdateViewLinediffbar(CCmdUI *pCmdUI) @@ -2154,3 +2101,136 @@ void CMainFrame::OnViewLocatorbar() m_wndLineDiffBar.DocumentUpdated(); } +void CMainFrame::OnViewComparewhitespaces() +{ + if (CheckForSave()==IDCANCEL) + return; + CRegDWORD regIgnoreWS = CRegDWORD(_T("Software\\TortoiseGitMerge\\IgnoreWS")); + regIgnoreWS = 0; + LoadViews(-1); +} + +void CMainFrame::OnUpdateViewComparewhitespaces(CCmdUI *pCmdUI) +{ + CRegDWORD regIgnoreWS = CRegDWORD(_T("Software\\TortoiseGitMerge\\IgnoreWS")); + DWORD dwIgnoreWS = regIgnoreWS; + pCmdUI->SetCheck(dwIgnoreWS == 0); +} + +void CMainFrame::OnViewIgnorewhitespacechanges() +{ + if (CheckForSave()==IDCANCEL) + return; + CRegDWORD regIgnoreWS = CRegDWORD(_T("Software\\TortoiseGitMerge\\IgnoreWS")); + regIgnoreWS = 2; + LoadViews(-1); +} + +void CMainFrame::OnUpdateViewIgnorewhitespacechanges(CCmdUI *pCmdUI) +{ + CRegDWORD regIgnoreWS = CRegDWORD(_T("Software\\TortoiseGitMerge\\IgnoreWS")); + DWORD dwIgnoreWS = regIgnoreWS; + pCmdUI->SetCheck(dwIgnoreWS == 2); +} + +void CMainFrame::OnViewIgnoreallwhitespacechanges() +{ + if (CheckForSave()==IDCANCEL) + return; + CRegDWORD regIgnoreWS = CRegDWORD(_T("Software\\TortoiseGitMerge\\IgnoreWS")); + regIgnoreWS = 1; + LoadViews(-1); +} + +void CMainFrame::OnUpdateViewIgnoreallwhitespacechanges(CCmdUI *pCmdUI) +{ + CRegDWORD regIgnoreWS = CRegDWORD(_T("Software\\TortoiseGitMerge\\IgnoreWS")); + DWORD dwIgnoreWS = regIgnoreWS; + pCmdUI->SetCheck(dwIgnoreWS == 1); +} + +void CMainFrame::OnViewMovedBlocks() +{ + m_bViewMovedBlocks = !(DWORD)m_regViewModedBlocks; + m_regViewModedBlocks = m_bViewMovedBlocks; + LoadViews(-1); +} + +void CMainFrame::OnUpdateViewMovedBlocks(CCmdUI *pCmdUI) +{ + pCmdUI->SetCheck(m_bViewMovedBlocks); + BOOL bEnable = TRUE; + if (IsViewGood(m_pwndBottomView)) + { + bEnable = FALSE; + } + pCmdUI->Enable(bEnable); +} + +bool CMainFrame::HasConflictsWontKeep() +{ + const int nConflictLine = CheckResolved(); + if (nConflictLine < 0) + return false; + + CString sTemp; + sTemp.Format(IDS_ERR_MAINFRAME_FILEHASCONFLICTS, m_pwndBottomView->m_pViewData->GetLineNumber(nConflictLine)+1); + if (MessageBox(sTemp, 0, MB_ICONERROR | MB_YESNO)==IDYES) + return false; + + if (m_pwndBottomView) + m_pwndBottomView->GoToLine(nConflictLine); + return true; +} + +bool CMainFrame::TryGetFileName(CString& result) +{ + return CCommonAppUtils::FileOpenSave(result, NULL, IDS_SAVEASTITLE, IDS_COMMONFILEFILTER, false, m_hWnd); +} + +CBaseView* CMainFrame::GetActiveBaseView() const +{ + CView* activeView = GetActiveView(); + CBaseView* activeBase = dynamic_cast( activeView ); + return activeBase; +} + +void CMainFrame::SetWindowTitle() +{ + // try to find a suitable window title + CString sYour = m_Data.m_yourFile.GetDescriptiveName(); + if (sYour.Find(_T(" - "))>=0) + sYour = sYour.Left(sYour.Find(_T(" - "))); + if (sYour.Find(_T(" : "))>=0) + sYour = sYour.Left(sYour.Find(_T(" : "))); + CString sTheir = m_Data.m_theirFile.GetDescriptiveName(); + if (sTheir.IsEmpty()) + sTheir = m_Data.m_baseFile.GetDescriptiveName(); + if (sTheir.Find(_T(" - "))>=0) + sTheir = sTheir.Left(sTheir.Find(_T(" - "))); + if (sTheir.Find(_T(" : "))>=0) + sTheir = sTheir.Left(sTheir.Find(_T(" : "))); + + if (!sYour.IsEmpty() && !sTheir.IsEmpty()) + { + if (sYour.CompareNoCase(sTheir)==0) + SetWindowText(sYour + _T(" - TortoiseGitMerge")); + else if ((sYour.GetLength() < 10) && + (sTheir.GetLength() < 10)) + SetWindowText(sYour + _T(" - ") + sTheir + _T(" - TortoiseGitMerge")); + else + { + // we have two very long descriptive texts here, which + // means we have to find a way to use them as a window + // title in a shorter way. + // for simplicity, we just use the one from "yourfile" + SetWindowText(sYour + _T(" - TortoiseGitMerge")); + } + } + else if (!sYour.IsEmpty()) + SetWindowText(sYour + _T(" - TortoiseGitMerge")); + else if (!sTheir.IsEmpty()) + SetWindowText(sTheir + _T(" - TortoiseGitMerge")); + else + SetWindowText(L"TortoiseGitMerge"); +} diff --git a/src/TortoiseMerge/MainFrm.h b/src/TortoiseMerge/MainFrm.h index acbcaf744..12cc79938 100644 --- a/src/TortoiseMerge/MainFrm.h +++ b/src/TortoiseMerge/MainFrm.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2009 - TortoiseSVN +// Copyright (C) 2006-2012 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -22,15 +22,14 @@ #include "LocatorBar.h" #include "LineDiffBar.h" #include "FilePatchesDlg.h" -#include "TempFiles.h" +#include "TempFile.h" #include "XSplitter.h" -#include "Patch.h" -#include "FindDlg.h" -#include "BaseView.h" +#include "GitPatch.h" class CLeftView; class CRightView; class CBottomView; +#define MOVESTOIGNORE 3 /** * \ingroup TortoiseMerge @@ -43,6 +42,7 @@ public: CMainFrame(); virtual ~CMainFrame(); + void ShowDiffBar(bool bShow); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; @@ -53,11 +53,14 @@ protected: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext); virtual void ActivateFrame(int nCmdShow = -1); - bool LoadViews(bool bRetainPosition = false); + /// line = -1 means keep the current position, + /// line >= 0 means scroll to that line, + /// and line == -2 means do nothing or scroll to first diff depending on registry setting + bool LoadViews(int line = -2); void ClearViewNamesAndPaths(); - bool GetCurrentLineNum(CBaseView *&view, CBaseView *&view2, CBaseView *&view3, int &lineNum); - void SetCurrentLineNum(CBaseView *view, CBaseView *view2, CBaseView *view3, int lineNum); - afx_msg LRESULT OnFindDialogMessage(WPARAM wParam, LPARAM lParam); + void SetWindowTitle(); + + afx_msg LRESULT OnTaskbarButtonCreated(WPARAM wParam, LPARAM lParam); afx_msg void OnApplicationLook(UINT id); afx_msg void OnUpdateApplicationLook(CCmdUI* pCmdUI); @@ -66,10 +69,7 @@ protected: afx_msg void OnFileOpen(); afx_msg void OnFileReload(); afx_msg void OnClose(); - afx_msg void OnEditFind(); - afx_msg void OnEditFindnext(); - afx_msg void OnEditFindprev(); - afx_msg void OnEditGoto(); + afx_msg void OnActivate(UINT, CWnd*, BOOL); afx_msg void OnViewWhitespaces(); afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnSize(UINT nType, int cx, int cy); @@ -96,6 +96,7 @@ protected: afx_msg void OnUpdateMergeNextconflict(CCmdUI *pCmdUI); afx_msg void OnUpdateMergePreviousconflict(CCmdUI *pCmdUI); afx_msg void OnUpdateEditCopy(CCmdUI *pCmdUI); + afx_msg void OnUpdateEditPaste(CCmdUI *pCmdUI); afx_msg void OnMoving(UINT fwSide, LPRECT pRect); afx_msg void OnViewSwitchleft(); afx_msg void OnUpdateViewSwitchleft(CCmdUI *pCmdUI); @@ -105,6 +106,8 @@ protected: afx_msg void OnUpdateEditUndo(CCmdUI *pCmdUI); afx_msg void OnViewInlinediffword(); afx_msg void OnUpdateViewInlinediffword(CCmdUI *pCmdUI); + afx_msg void OnViewInlinediff(); + afx_msg void OnUpdateViewInlinediff(CCmdUI *pCmdUI); afx_msg void OnUpdateEditCreateunifieddifffile(CCmdUI *pCmdUI); afx_msg void OnEditCreateunifieddifffile(); afx_msg void OnUpdateViewLinediffbar(CCmdUI *pCmdUI); @@ -112,6 +115,7 @@ protected: afx_msg void OnUpdateViewLocatorbar(CCmdUI *pCmdUI); afx_msg void OnViewLocatorbar(); afx_msg void OnEditUseleftblock(); + afx_msg void OnUpdateUseBlock(CCmdUI *pCmdUI); afx_msg void OnUpdateEditUseleftblock(CCmdUI *pCmdUI); afx_msg void OnEditUseleftfile(); afx_msg void OnUpdateEditUseleftfile(CCmdUI *pCmdUI); @@ -119,56 +123,84 @@ protected: afx_msg void OnUpdateEditUseblockfromleftbeforeright(CCmdUI *pCmdUI); afx_msg void OnEditUseblockfromrightbeforeleft(); afx_msg void OnUpdateEditUseblockfromrightbeforeleft(CCmdUI *pCmdUI); + afx_msg void OnUpdateNavigateNextdifference(CCmdUI *pCmdUI); + afx_msg void OnUpdateNavigatePreviousdifference(CCmdUI *pCmdUI); + afx_msg void OnViewCollapsed(); + afx_msg void OnUpdateViewCollapsed(CCmdUI *pCmdUI); + afx_msg void OnViewComparewhitespaces(); + afx_msg void OnUpdateViewComparewhitespaces(CCmdUI *pCmdUI); + afx_msg void OnViewIgnorewhitespacechanges(); + afx_msg void OnUpdateViewIgnorewhitespacechanges(CCmdUI *pCmdUI); + afx_msg void OnViewIgnoreallwhitespacechanges(); + afx_msg void OnUpdateViewIgnoreallwhitespacechanges(CCmdUI *pCmdUI); + afx_msg void OnUpdateNavigateNextinlinediff(CCmdUI *pCmdUI); + afx_msg void OnUpdateNavigatePrevinlinediff(CCmdUI *pCmdUI); + afx_msg void OnViewMovedBlocks(); + afx_msg void OnUpdateViewMovedBlocks(CCmdUI *pCmdUI); + afx_msg void OnViewWraplonglines(); + afx_msg void OnUpdateViewWraplonglines(CCmdUI *pCmdUI); DECLARE_MESSAGE_MAP() protected: void UpdateLayout(); - virtual BOOL PatchFile(const int nIndex, bool bAutoPatch, bool bIsReview); - virtual BOOL DiffFiles(CString sURL1, CString sRev1, CString sURL2, CString sRev2); + virtual BOOL PatchFile(CString sFilePath, bool bContentMods, bool bPropMods, CString sVersion, BOOL bAutoPatch) override; + virtual BOOL DiffFiles(CString sURL1, CString sRev1, CString sURL2, CString sRev2) override; int CheckResolved(); BOOL MarkAsResolved(); int SaveFile(const CString& sFilePath); void WriteWindowPlacement(WINDOWPLACEMENT * pwp); BOOL ReadWindowPlacement(WINDOWPLACEMENT * pwp); bool FileSave(bool bCheckResolved=true); + void PatchSave(); bool FileSaveAs(bool bCheckResolved=true); - bool StringFound(const CString&)const; - enum SearchDirection{SearchNext=0, SearchPrevious=1}; - void Search(SearchDirection); - int FindSearchStart(int nDefault); /// checks if there are modifications and asks the user to save them first /// IDCANCEL is returned if the user wants to cancel. /// If the user wanted to save the modifications, this method does the saving /// itself. + int CheckForReload(); int CheckForSave(); + void OnViewLineUpDown(int direction); + void OnViewLineLeftRight(int direction); + bool HasConflictsWontKeep(); + bool TryGetFileName(CString& result); + CBaseView* GetActiveBaseView() const; + void OnViewTextFoldUnfold(); + void OnViewTextFoldUnfold(CBaseView* view); + bool HasUnsavedEdits() const; + static bool HasUnsavedEdits(const CBaseView* view); + static bool IsViewGood(const CBaseView* view); + static bool HasPrevConflict(CBaseView* view); + static bool HasNextConflict(CBaseView* view); + static bool HasPrevInlineDiff(CBaseView* view); + static bool HasNextInlineDiff(CBaseView* view); protected: - CMFCMenuBar m_wndMenuBar; CMFCStatusBar m_wndStatusBar; - CMFCToolBar m_wndToolBar; CLocatorBar m_wndLocatorBar; CLineDiffBar m_wndLineDiffBar; CXSplitter m_wndSplitter; CXSplitter m_wndSplitter2; - CFilePatchesDlg m_dlgFilePatches; + CFilePatchesDlg m_dlgFilePatches; - CPatch m_Patch; + GitPatch m_Patch; BOOL m_bInitSplitter; - CTempFiles m_TempFiles; - - int m_nSearchIndex; - CString m_sFindText; - BOOL m_bMatchCase; - bool m_bLimitToDiff; - bool m_bWholeWord; - static const UINT m_FindDialogMessage; - CFindDlg * m_pFindDialog; + bool m_bCheckReload; + bool m_bHasConflicts; bool m_bInlineWordDiff; + bool m_bInlineDiff; bool m_bLineDiff; bool m_bLocatorBar; + CMFCRibbonBar m_wndRibbonBar; + CMFCRibbonApplicationButton m_MainButton; + + CRegDWORD m_regWrapLines; + CRegDWORD m_regViewModedBlocks; + CRegDWORD m_regOneWay; + CRegDWORD m_regCollapsed; + CRegDWORD m_regInlineDiff; public: CLeftView * m_pwndLeftView; CRightView * m_pwndRightView; @@ -179,12 +211,11 @@ public: bool m_bReadOnly; bool m_bBlame; int m_nMoveMovesToIgnore; - - void ShowDiffBar(bool bShow); -}; - - - - - - + bool m_bCollapsed; + bool m_bViewMovedBlocks; + bool m_bWrapLines; + bool m_bSaveRequired; + HWND resolveMsgWnd; + WPARAM resolveMsgWParam; + LPARAM resolveMsgLParam; +}; \ No newline at end of file diff --git a/src/TortoiseMerge/MovedBlocks.cpp b/src/TortoiseMerge/MovedBlocks.cpp new file mode 100644 index 000000000..1de7a453d --- /dev/null +++ b/src/TortoiseMerge/MovedBlocks.cpp @@ -0,0 +1,485 @@ +// TortoiseGitMerge - a Diff/Patch program + +// Copyright (C) 2010-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#include "stdafx.h" +#include "diff.h" +#include "MovedBlocks.h" +#include "DiffData.h" +#include +#include + +// This file implements moved blocks detection algorithm, based +// on WinMerges(http:\\winmerge.org) one + +class IntSet +{ +public: + void Add(int val); + void Remove(int val); + int Count() const; + bool IsPresent(int val) const; + int GetSingle() const; +private: + std::set m_set; +}; + +struct EquivalencyGroup +{ + IntSet m_LinesLeft; // equivalent lines on left pane + IntSet m_LinesRight; // equivalent lines on right pane + + bool IsPerfectMatch() const; +}; + +class LineToGroupMap : public std::map +{ +public: + void Add(int lineno, const CString &line, int nside); + EquivalencyGroup *find(const CString &line) const; + ~LineToGroupMap(); +}; + +void IntSet::Add(int val) +{ + m_set.insert(val); +} + +void IntSet::Remove(int val) +{ + m_set.erase(val); +} + +int IntSet::Count() const +{ + return (int)m_set.size(); +} + +bool IntSet::IsPresent(int val) const +{ + return m_set.find(val) != m_set.end(); +} + +int IntSet::GetSingle() const +{ + if (!m_set.empty()) + { + return *m_set.cbegin(); + } + return 0; +} + +bool EquivalencyGroup::IsPerfectMatch() const +{ + return (m_LinesLeft.Count() == 1)&&(m_LinesRight.Count() == 1); +} + +void LineToGroupMap::Add(int lineno, const CString &line, int nside) +{ + EquivalencyGroup *pGroup = NULL; + auto it = __super::find(line); + if ( it == cend() ) + { + pGroup = new EquivalencyGroup; + insert(std::pair(line, pGroup)); + } + else + pGroup = it->second; + if(nside) + { + pGroup->m_LinesRight.Add(lineno); + } + else + { + pGroup->m_LinesLeft.Add(lineno); + } +} + +EquivalencyGroup *LineToGroupMap::find(const CString &line) const +{ + EquivalencyGroup *pGroup = NULL; + auto it = __super::find(line); + if ( it != cend() ) + pGroup = it->second; + return pGroup; +} + +LineToGroupMap::~LineToGroupMap() +{ + for (auto it = cbegin(); it != cend(); ++it) + { + delete it->second; + } +} + +tsvn_svn_diff_t_extension * CreateDiffExtension(svn_diff_t * base, apr_pool_t * pool) +{ + tsvn_svn_diff_t_extension * ext = (tsvn_svn_diff_t_extension *)apr_palloc(pool, sizeof(tsvn_svn_diff_t_extension)); + ext->next = NULL; + ext->moved_to = -1; + ext->moved_from = -1; + ext->base = base; + return ext; +} + +void AdjustExistingAndTail(svn_diff_t * tempdiff, tsvn_svn_diff_t_extension *& existing, tsvn_svn_diff_t_extension *& tail) +{ + if(existing && existing->base == tempdiff) + { + if(tail && tail != existing) + { + tail->next = existing; + } + tail = existing; + existing = existing->next; + } +} + +CString GetTrimmedString(const CString& s1, DWORD dwIgnoreWS) +{ + if(dwIgnoreWS == 1) + { + CString s2 = s1; + s2.Remove(' '); + s2.Remove('\t'); + return s2; + } + else if(dwIgnoreWS == 2) + return CString(s1).TrimLeft(_T(" \t")); + return CString(s1).TrimRight(_T(" \t")); +} + +EquivalencyGroup * ExtractGroup(const LineToGroupMap & map, const CString & line, DWORD dwIgnoreWS) +{ + if (dwIgnoreWS) + return map.find(GetTrimmedString(line, dwIgnoreWS)); + return map.find(line); +} + +tsvn_svn_diff_t_extension * CDiffData::MovedBlocksDetect(svn_diff_t * diffYourBase, DWORD dwIgnoreWS, apr_pool_t * pool) +{ + LineToGroupMap map; + tsvn_svn_diff_t_extension * head = NULL; + tsvn_svn_diff_t_extension * tail = NULL; + svn_diff_t * tempdiff = diffYourBase; + LONG baseLine = 0; + LONG yourLine = 0; + for(;tempdiff; tempdiff = tempdiff->next) // fill map + { + if(tempdiff->type != svn_diff__type_diff_modified) + continue; + + baseLine = (LONG)tempdiff->original_start; + for(int i = 0; i < tempdiff->original_length; ++i, ++baseLine) + { + const CString &sCurrentBaseLine = m_arBaseFile.GetAt(baseLine); + if (dwIgnoreWS) + map.Add(baseLine, GetTrimmedString(sCurrentBaseLine, dwIgnoreWS), 0); + else + map.Add(baseLine, sCurrentBaseLine, 0); + } + yourLine = (LONG)tempdiff->modified_start; + for(int i = 0; i < tempdiff->modified_length; ++i, ++yourLine) + { + const CString &sCurrentYourLine = m_arYourFile.GetAt(yourLine); + if(dwIgnoreWS) + map.Add(yourLine, GetTrimmedString(sCurrentYourLine, dwIgnoreWS), 1); + else + map.Add(yourLine, sCurrentYourLine, 1); + } + } + for(tempdiff = diffYourBase; tempdiff; tempdiff = tempdiff->next) + { + // Scan through diff blocks, finding moved sections from left side + // and splitting them out + // That is, we actually fragment diff blocks as we find moved sections + if(tempdiff->type != svn_diff__type_diff_modified) + continue; + + EquivalencyGroup * pGroup = NULL; + + int i; + for(i = (int)tempdiff->original_start; (i - tempdiff->original_start)< tempdiff->original_length; ++i) + { + EquivalencyGroup * group = ExtractGroup(map, m_arBaseFile.GetAt(i), dwIgnoreWS); + if(group->IsPerfectMatch()) + { + pGroup = group; + break; + } + } + if(!pGroup) // if no match + continue; + // found a match + int j = pGroup->m_LinesRight.GetSingle(); + // Ok, now our moved block is the single line (i, j) + + // extend moved block upward as far as possible + int i1 = i - 1; + int j1 = j - 1; + for(; (i1 >= tempdiff->original_start) && (j1>=0) && (i1>=0); --i1, --j1) + { + EquivalencyGroup * pGroup0 = ExtractGroup(map, m_arBaseFile.GetAt(i1), dwIgnoreWS); + EquivalencyGroup * pGroup1 = ExtractGroup(map, m_arYourFile.GetAt(j1), dwIgnoreWS); + if(pGroup1 != pGroup0) + break; + pGroup0->m_LinesLeft.Remove(i1); + pGroup1->m_LinesRight.Remove(j1); + } + ++i1; + ++j1; + // Ok, now our moved block is (i1..i, j1..j) + + // extend moved block downward as far as possible + + int i2 = i + 1; + int j2 = j + 1; + for(; ((i2-tempdiff->original_start) < tempdiff->original_length)&&(j2>=0); ++i2, ++j2) + { + if(i2 >= m_arBaseFile.GetCount() || j2 >= m_arYourFile.GetCount()) + break; + EquivalencyGroup * pGroup0 = ExtractGroup(map, m_arBaseFile.GetAt(i2), dwIgnoreWS); + EquivalencyGroup * pGroup1 = ExtractGroup(map, m_arYourFile.GetAt(j2), dwIgnoreWS); + if(pGroup1 != pGroup0) + break; + pGroup0->m_LinesLeft.Remove(i2); + pGroup1->m_LinesRight.Remove(j2); + } + --i2; + --j2; + // Ok, now our moved block is (i1..i2,j1..j2) + tsvn_svn_diff_t_extension * newTail = CreateDiffExtension(tempdiff, pool); + if(head == NULL) + { + head = newTail; + tail = head; + } + else + { + tail->next = newTail; + tail = newTail; + } + + int prefix = i1 - (int)tempdiff->original_start; + if(prefix) + { + // break tempdiff (current change) into two pieces + // first part is the prefix, before the moved part + // that stays in tempdiff + // second part is the moved part & anything after it + // that goes in newob + // leave the left side (tempdiff->original_length) on tempdiff + // so no right side on newob + // newob will be the moved part only, later after we split off any suffix from it + svn_diff_t * newob = (svn_diff_t *)apr_palloc(pool, sizeof(svn_diff_t)); + memset(newob, 0, sizeof(*newob)); + + tail->base = newob; + newob->type = svn_diff__type_diff_modified; + newob->original_start = i1; + newob->modified_start = tempdiff->modified_start + tempdiff->modified_length; + newob->modified_length = 0; + newob->original_length = tempdiff->original_length - prefix; + newob->next = tempdiff->next; + + tempdiff->original_length = prefix; + tempdiff->next = newob; + + // now make tempdiff point to the moved part (& any suffix) + tempdiff = newob; + } + + tail->moved_to = j1; + + apr_off_t suffix = (tempdiff->original_length) - (i2- (tempdiff->original_start)) - 1; + if (suffix) + { + // break off any suffix from tempdiff + // newob will be the suffix, and will get all the right side + svn_diff_t * newob = (svn_diff_t *) apr_palloc(pool, sizeof (*newob)); + memset(newob, 0, sizeof(*newob)); + newob->type = svn_diff__type_diff_modified; + + newob->original_start = i2 + 1; + newob->modified_start = tempdiff->modified_start; + newob->modified_length = tempdiff->modified_length; + newob->original_length = suffix; + newob->next = tempdiff->next; + + tempdiff->modified_length = 0; + tempdiff->original_length -= suffix; + tempdiff->next = newob; + } + } + // Scan through diff blocks, finding moved sections from right side + // and splitting them out + // That is, we actually fragment diff blocks as we find moved sections + tsvn_svn_diff_t_extension * existing = head; + tail = NULL; + for(tempdiff = diffYourBase; tempdiff; tempdiff = tempdiff->next) + { + // scan down block for a match + if(tempdiff->type != svn_diff__type_diff_modified) + continue; + + EquivalencyGroup * pGroup = NULL; + int j = 0; + for(j = (int)tempdiff->modified_start; (j - tempdiff->modified_start) < tempdiff->modified_length; ++j) + { + EquivalencyGroup * group = ExtractGroup(map, m_arYourFile.GetAt(j), dwIgnoreWS); + if(group->IsPerfectMatch()) + { + pGroup = group; + break; + } + } + + // if no match, go to next diff block + if (!pGroup) + { + AdjustExistingAndTail(tempdiff, existing, tail); + continue; + } + + // found a match + int i = pGroup->m_LinesLeft.GetSingle(); + if (i == 0) + continue; + // Ok, now our moved block is the single line (i,j) + + // extend moved block upward as far as possible + int i1 = i-1; + int j1 = j-1; + for ( ; (j1>=tempdiff->modified_start) && (j1>=0) && (i1>=0); --i1, --j1) + { + EquivalencyGroup * pGroup0 = ExtractGroup(map, m_arBaseFile.GetAt(i1), dwIgnoreWS); + EquivalencyGroup * pGroup1 = ExtractGroup(map, m_arYourFile.GetAt(j1), dwIgnoreWS); + if (pGroup0 != pGroup1) + break; + pGroup0->m_LinesLeft.Remove(i1); + pGroup1->m_LinesRight.Remove(j1); + } + ++i1; + ++j1; + // Ok, now our moved block is (i1..i,j1..j) + + // extend moved block downward as far as possible + int i2 = i+1; + int j2 = j+1; + for ( ; (j2-(tempdiff->modified_start) < tempdiff->modified_length) && (i2>=0); ++i2,++j2) + { + if(i2 >= m_arBaseFile.GetCount() || j2 >= m_arYourFile.GetCount()) + break; + EquivalencyGroup * pGroup0 = ExtractGroup(map, m_arBaseFile.GetAt(i2), dwIgnoreWS); + EquivalencyGroup * pGroup1 = ExtractGroup(map, m_arYourFile.GetAt(j2), dwIgnoreWS); + if (pGroup0 != pGroup1) + break; + pGroup0->m_LinesLeft.Remove(i2); + pGroup1->m_LinesRight.Remove(j2); + } + --i2; + --j2; + // Ok, now our moved block is (i1..i2,j1..j2) + tsvn_svn_diff_t_extension * newTail = NULL; + if(existing && existing->base == tempdiff) + { + newTail = existing; + } + else + { + newTail = CreateDiffExtension(tempdiff, pool); + if(head == NULL) + { + head = newTail; + } + else if(tail) + { + newTail->next = tail->next; + tail->next = newTail; + } + } + tail = newTail; + + apr_off_t prefix = j1 - (tempdiff->modified_start); + if (prefix) + { + // break tempdiff (current change) into two pieces + // first part is the prefix, before the moved part + // that stays in tempdiff + // second part is the moved part & anything after it + // that goes in newob + // leave the left side (tempdiff->original_length) on tempdiff + // so no right side on newob + // newob will be the moved part only, later after we split off any suffix from it + svn_diff_t * newob = (svn_diff_t *) apr_palloc(pool, sizeof (*newob)); + memset(newob, 0, sizeof(*newob)); + newob->type = svn_diff__type_diff_modified; + + if(existing == newTail) + { + newTail = CreateDiffExtension(newob, pool); + newTail->next = tail->next; + tail->next = newTail; + tail = newTail; + } + tail->base = newob; + newob->original_start = tempdiff->original_start + tempdiff->original_length; + newob->modified_start = j1; + newob->modified_length = tempdiff->modified_length - prefix; + newob->original_length = 0; + newob->next = tempdiff->next; + + tempdiff->modified_length = prefix; + tempdiff->next = newob; + + // now make tempdiff point to the moved part (& any suffix) + tempdiff = newob; + } + // now tempdiff points to a moved diff chunk with no prefix, but maybe a suffix + + tail->moved_from = i1; + + apr_off_t suffix = (tempdiff->modified_length) - (j2-(tempdiff->modified_start)) - 1; + if (suffix) + { + // break off any suffix from tempdiff + // newob will be the suffix, and will get all the left side + svn_diff_t * newob = (svn_diff_t *) apr_palloc(pool, sizeof (*newob)); + memset(newob, 0, sizeof(*newob)); + tsvn_svn_diff_t_extension * eNewOb = CreateDiffExtension(newob, pool); + + newob->type = svn_diff__type_diff_modified; + newob->original_start = tempdiff->original_start; + newob->modified_start = j2+1; + newob->modified_length = suffix; + newob->original_length = tempdiff->original_length; + newob->next = tempdiff->next; + eNewOb->moved_from = -1; + eNewOb->moved_to = tail->moved_to; + + tempdiff->modified_length -= suffix; + tempdiff->original_length = 0; + tail->moved_to = -1; + tempdiff->next = newob; + eNewOb->next = tail->next; + tail->next = eNewOb; + existing = tail = eNewOb; + } + AdjustExistingAndTail(tempdiff, existing, tail); + } + return head; +} diff --git a/src/TortoiseMerge/TempFiles.h b/src/TortoiseMerge/MovedBlocks.h similarity index 65% rename from src/TortoiseMerge/TempFiles.h rename to src/TortoiseMerge/MovedBlocks.h index 48b06072b..74c3e4db7 100644 --- a/src/TortoiseMerge/TempFiles.h +++ b/src/TortoiseMerge/MovedBlocks.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006 - Stefan Kueng +// Copyright (C) 2010 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -18,18 +18,13 @@ // #pragma once -/** - * \ingroup TortoiseMerge - * - * Keeps track of all used temporary files and destroys (deletes) - * them when this object is destroyed. - */ -class CTempFiles +#include "diff.h" + +// Inheritance emulation (adding extra fields) +struct tsvn_svn_diff_t_extension { -public: - CTempFiles(void); - ~CTempFiles(void); - CString GetTempFilePath(); -protected: - CStringArray m_arTempFileList; + svn_diff_t * base; + tsvn_svn_diff_t_extension * next; + int moved_from; + int moved_to; }; diff --git a/src/TortoiseMerge/OpenDlg.cpp b/src/TortoiseMerge/OpenDlg.cpp index b6facedd3..8dbe5897e 100644 --- a/src/TortoiseMerge/OpenDlg.cpp +++ b/src/TortoiseMerge/OpenDlg.cpp @@ -1,7 +1,7 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2012 - TortoiseGit -// Copyright (C) 2006-2010 - TortoiseSVN +// Copyright (C) 2006-2010, 2012 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -21,7 +21,6 @@ #include "TortoiseMerge.h" #include "BrowseFolder.h" #include "opendlg.h" -#include "auto_buffer.h" #include "CommonAppUtils.h" #include "registry.h" @@ -125,7 +124,7 @@ void COpenDlg::OnBnClickedHelp() void COpenDlg::OnBrowseForFile(CString& filepath, UINT nFileFilter) { UpdateData(); - CCommonAppUtils::FileOpenSave(filepath, NULL, IDS_SELECTFILE, nFileFilter, true, this->m_hWnd); + CCommonAppUtils::FileOpenSave(filepath, NULL, IDS_SELECTFILE, nFileFilter, true, m_hWnd); UpdateData(FALSE); } @@ -232,11 +231,11 @@ void COpenDlg::OnOK() LPCSTR lpstr = (LPCSTR)GlobalLock(hglb); DWORD len = GetTempPath(0, NULL); - auto_buffer path(len+1); - auto_buffer tempF(len+100); - GetTempPath (len+1, path); - GetTempFileName (path, _T("tsm"), 0, tempF); - CString sTempFile = CString(tempF); + std::unique_ptr path(new TCHAR[len+1]); + std::unique_ptr tempF(new TCHAR[len+100]); + GetTempPath (len+1, path.get()); + GetTempFileName (path.get(), _T("tsm"), 0, tempF.get()); + CString sTempFile = CString(tempF.get()); FILE * outFile; size_t patchlen = strlen(lpstr); diff --git a/src/TortoiseMerge/OpenDlg.h b/src/TortoiseMerge/OpenDlg.h index c5cd4b4c4..8193b022c 100644 --- a/src/TortoiseMerge/OpenDlg.h +++ b/src/TortoiseMerge/OpenDlg.h @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2006, 2009-2010 - TortoiseSVN diff --git a/src/TortoiseMerge/Patch.cpp b/src/TortoiseMerge/Patch.cpp index 5592cb090..6fcf8e46a 100644 --- a/src/TortoiseMerge/Patch.cpp +++ b/src/TortoiseMerge/Patch.cpp @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2009-2012 - TortoiseGit // Copyright (C) 2012 - Sven Strickroth @@ -23,7 +23,6 @@ #include "UnicodeUtils.h" #include "DirFileEnum.h" #include "TortoiseMerge.h" -#include "svn_wc.h" #include "GitAdminDir.h" #include "Patch.h" diff --git a/src/TortoiseMerge/Patch.h b/src/TortoiseMerge/Patch.h index 34ad30620..96e005c83 100644 --- a/src/TortoiseMerge/Patch.h +++ b/src/TortoiseMerge/Patch.h @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2006-2008 - TortoiseSVN // Copyright (C) 2012 - Sven Strickroth @@ -73,7 +73,7 @@ protected: LONG lRemoveLength; LONG lAddStart; LONG lAddLength; - CStdCStringArray arLines; + CStringArray arLines; CStdDWORDArray arLinesStates; std::vector arEOLs; }; @@ -84,10 +84,10 @@ protected: CString sRevision; CString sFilePath2; CString sRevision2; - CStdArray chunks; + CStdArrayV chunks; }; - CStdArray m_arFileDiffs; + CStdArrayV m_arFileDiffs; CString m_sErrorMessage; CFileTextLines::UnicodeType m_UnicodeType; diff --git a/src/TortoiseMerge/RightView.cpp b/src/TortoiseMerge/RightView.cpp dissimilarity index 88% index 97ee281ee..d17ef9694 100644 --- a/src/TortoiseMerge/RightView.cpp +++ b/src/TortoiseMerge/RightView.cpp @@ -1,326 +1,284 @@ -// TortoiseMerge - a Diff/Patch program - -// Copyright (C) 2006-2009,2011 - TortoiseSVN - -// 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, -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// -#include "stdafx.h" -#include "resource.h" -#include "AppUtils.h" -#include "rightview.h" - -IMPLEMENT_DYNCREATE(CRightView, CBaseView) - -CRightView::CRightView(void) -{ - m_pwndRight = this; - m_nStatusBarID = ID_INDICATOR_RIGHTVIEW; -} - -CRightView::~CRightView(void) -{ -} - -bool CRightView::OnContextMenu(CPoint point, int /*nLine*/, DiffStates state) -{ - if (!this->IsWindowVisible()) - return false; - - CMenu popup; - if (popup.CreatePopupMenu()) - { -#define ID_USEBLOCK 1 -#define ID_USEFILE 2 -#define ID_USETHEIRANDYOURBLOCK 3 -#define ID_USEYOURANDTHEIRBLOCK 4 -#define ID_USEBOTHTHISFIRST 5 -#define ID_USEBOTHTHISLAST 6 - - UINT uEnabled = MF_ENABLED; - if ((m_nSelBlockStart == -1)||(m_nSelBlockEnd == -1)) - uEnabled |= MF_DISABLED | MF_GRAYED; - CString temp; - - bool bImportantBlock = true; - switch (state) - { - case DIFFSTATE_UNKNOWN: - bImportantBlock = false; - break; - } - - if (!m_pwndBottom->IsWindowVisible()) - { - temp.LoadString(IDS_VIEWCONTEXTMENU_USEOTHERBLOCK); - } - else - temp.LoadString(IDS_VIEWCONTEXTMENU_USETHISBLOCK); - popup.AppendMenu(MF_STRING | uEnabled | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USEBLOCK, temp); - - if (!m_pwndBottom->IsWindowVisible()) - { - temp.LoadString(IDS_VIEWCONTEXTMENU_USEOTHERFILE); - } - else - temp.LoadString(IDS_VIEWCONTEXTMENU_USETHISFILE); - popup.AppendMenu(MF_STRING | MF_ENABLED, ID_USEFILE, temp); - - if (m_pwndBottom->IsWindowVisible()) - { - temp.LoadString(IDS_VIEWCONTEXTMENU_USEYOURANDTHEIRBLOCK); - popup.AppendMenu(MF_STRING | uEnabled | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USEYOURANDTHEIRBLOCK, temp); - temp.LoadString(IDS_VIEWCONTEXTMENU_USETHEIRANDYOURBLOCK); - popup.AppendMenu(MF_STRING | uEnabled | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USETHEIRANDYOURBLOCK, temp); - } - else - { - temp.LoadString(IDS_VIEWCONTEXTMENU_USEBOTHTHISFIRST); - popup.AppendMenu(MF_STRING | uEnabled | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USEBOTHTHISFIRST, temp); - temp.LoadString(IDS_VIEWCONTEXTMENU_USEBOTHTHISLAST); - popup.AppendMenu(MF_STRING | uEnabled | (bImportantBlock ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_USEBOTHTHISLAST, temp); - } - - popup.AppendMenu(MF_SEPARATOR, NULL); - - temp.LoadString(IDS_EDIT_COPY); - popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_COPY, temp); - if (!m_bCaretHidden) - { - temp.LoadString(IDS_EDIT_CUT); - popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_CUT, temp); - temp.LoadString(IDS_EDIT_PASTE); - popup.AppendMenu(MF_STRING | (CAppUtils::HasClipboardFormat(CF_UNICODETEXT)||CAppUtils::HasClipboardFormat(CF_TEXT) ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_PASTE, temp); - } - - // if the context menu is invoked through the keyboard, we have to use - // a calculated position on where to anchor the menu on - if ((point.x == -1) && (point.y == -1)) - { - CRect rect; - GetWindowRect(&rect); - point = rect.CenterPoint(); - } - int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0); - viewstate rightstate; - viewstate bottomstate; - viewstate leftstate; - switch (cmd) - { - case ID_EDIT_COPY: - OnEditCopy(); - return true; - case ID_EDIT_CUT: - OnEditCopy(); - RemoveSelectedText(); - return false; - case ID_EDIT_PASTE: - PasteText(); - return false; - case ID_USEFILE: - { - UseFile(false); - } - break; - case ID_USEBLOCK: - { - UseBlock(false); - } - break; - case ID_USEYOURANDTHEIRBLOCK: - { - UseYourAndTheirBlock(rightstate, bottomstate, leftstate); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - } - break; - case ID_USETHEIRANDYOURBLOCK: - { - UseTheirAndYourBlock(rightstate, bottomstate, leftstate); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - } - break; - case ID_USEBOTHTHISFIRST: - { - UseBothRightFirst(rightstate, leftstate); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - } - break; - case ID_USEBOTHTHISLAST: - { - UseBothLeftFirst(rightstate, leftstate); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - } - break; - default: - return false; - } // switch (cmd) - } // if (popup.CreatePopupMenu()) - return false; -} - -void CRightView::UseFile(bool refreshViews /* = true */) -{ - viewstate rightstate; - viewstate bottomstate; - viewstate leftstate; - if (m_pwndBottom->IsWindowVisible()) - { - for (int i=0; im_pViewData->GetLine(i); - m_pwndBottom->m_pViewData->SetLine(i, m_pViewData->GetLine(i)); - bottomstate.linestates[i] = m_pwndBottom->m_pViewData->GetState(i); - m_pwndBottom->m_pViewData->SetState(i, m_pViewData->GetState(i)); - m_pwndBottom->m_pViewData->SetLineEnding(i, m_pViewData->GetLineEnding(i)); - if (m_pwndBottom->IsLineConflicted(i)) - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVED); - } - m_pwndBottom->SetModified(); - } - else - { - for (int i=0; iGetLine(i); - m_pViewData->SetLine(i, m_pwndLeft->m_pViewData->GetLine(i)); - m_pViewData->SetLineEnding(i, m_pwndLeft->m_pViewData->GetLineEnding(i)); - DiffStates state = m_pwndLeft->m_pViewData->GetState(i); - switch (state) - { - case DIFFSTATE_CONFLICTEMPTY: - case DIFFSTATE_UNKNOWN: - case DIFFSTATE_EMPTY: - rightstate.linestates[i] = m_pViewData->GetState(i); - m_pViewData->SetState(i, state); - break; - case DIFFSTATE_YOURSADDED: - case DIFFSTATE_IDENTICALADDED: - case DIFFSTATE_NORMAL: - case DIFFSTATE_THEIRSADDED: - case DIFFSTATE_ADDED: - case DIFFSTATE_CONFLICTADDED: - case DIFFSTATE_CONFLICTED: - case DIFFSTATE_CONFLICTED_IGNORED: - case DIFFSTATE_IDENTICALREMOVED: - case DIFFSTATE_REMOVED: - case DIFFSTATE_THEIRSREMOVED: - case DIFFSTATE_YOURSREMOVED: - rightstate.linestates[i] = m_pViewData->GetState(i); - m_pViewData->SetState(i, DIFFSTATE_NORMAL); - leftstate.linestates[i] = m_pwndLeft->m_pViewData->GetState(i); - m_pwndLeft->m_pViewData->SetState(i, DIFFSTATE_NORMAL); - break; - default: - break; - } - SetModified(); - if (m_pwndLocator) - m_pwndLocator->DocumentUpdated(); - } - } - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - if (refreshViews) - RefreshViews(); -} - -void CRightView::UseBlock(bool refreshViews /* = true */) -{ - viewstate rightstate; - viewstate bottomstate; - viewstate leftstate; - - if ((m_nSelBlockStart == -1)||(m_nSelBlockEnd == -1)) - return; - if (m_pwndBottom->IsWindowVisible()) - { - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - bottomstate.difflines[i] = m_pwndBottom->m_pViewData->GetLine(i); - m_pwndBottom->m_pViewData->SetLine(i, m_pViewData->GetLine(i)); - bottomstate.linestates[i] = m_pwndBottom->m_pViewData->GetState(i); - m_pwndBottom->m_pViewData->SetState(i, m_pViewData->GetState(i)); - m_pwndBottom->m_pViewData->SetLineEnding(i, lineendings); - if (m_pwndBottom->IsLineConflicted(i)) - { - if (m_pViewData->GetState(i) == DIFFSTATE_CONFLICTEMPTY) - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVEDEMPTY); - else - m_pwndBottom->m_pViewData->SetState(i, DIFFSTATE_CONFLICTRESOLVED); - } - } - m_pwndBottom->SetModified(); - } - else - { - for (int i=m_nSelBlockStart; i<=m_nSelBlockEnd; i++) - { - rightstate.difflines[i] = m_pViewData->GetLine(i); - m_pViewData->SetLine(i, m_pwndLeft->m_pViewData->GetLine(i)); - m_pViewData->SetLineEnding(i, lineendings); - DiffStates state = m_pwndLeft->m_pViewData->GetState(i); - switch (state) - { - case DIFFSTATE_ADDED: - case DIFFSTATE_CONFLICTADDED: - case DIFFSTATE_CONFLICTED: - case DIFFSTATE_CONFLICTED_IGNORED: - case DIFFSTATE_CONFLICTEMPTY: - case DIFFSTATE_IDENTICALADDED: - case DIFFSTATE_NORMAL: - case DIFFSTATE_THEIRSADDED: - case DIFFSTATE_UNKNOWN: - case DIFFSTATE_YOURSADDED: - case DIFFSTATE_EMPTY: - rightstate.linestates[i] = m_pViewData->GetState(i); - m_pViewData->SetState(i, state); - break; - case DIFFSTATE_IDENTICALREMOVED: - case DIFFSTATE_REMOVED: - case DIFFSTATE_THEIRSREMOVED: - case DIFFSTATE_YOURSREMOVED: - rightstate.linestates[i] = m_pViewData->GetState(i); - m_pViewData->SetState(i, DIFFSTATE_ADDED); - break; - default: - break; - } - } - SetModified(); - } - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - if (refreshViews) - RefreshViews(); -} - -void CRightView::UseLeftBeforeRight(bool refreshViews /* = true */) -{ - viewstate rightstate; - viewstate bottomstate; - viewstate leftstate; - UseBothLeftFirst(rightstate, leftstate); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - if (refreshViews) - RefreshViews(); -} - -void CRightView::UseRightBeforeLeft(bool refreshViews /* = true */) -{ - viewstate rightstate; - viewstate bottomstate; - viewstate leftstate; - UseBothRightFirst(rightstate, leftstate); - CUndo::GetInstance().AddState(leftstate, rightstate, bottomstate, m_ptCaretPos); - if (refreshViews) - RefreshViews(); -} +// TortoiseGitMerge - a Diff/Patch program + +// Copyright (C) 2006-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#include "stdafx.h" +#include "resource.h" +#include "AppUtils.h" +#if 0 +#include "IconBitmapUtils.h" +#endif + +#include "RightView.h" +#include "BottomView.h" + +IMPLEMENT_DYNCREATE(CRightView, CBaseView) + +CRightView::CRightView(void) +{ + m_pwndRight = this; + m_pState = &m_AllState.right; + m_nStatusBarID = ID_INDICATOR_RIGHTVIEW; +} + +CRightView::~CRightView(void) +{ +} + +void CRightView::UseBothLeftFirst() +{ + if (!IsLeftViewGood()) + return; + int nFirstViewLine = 0; // first view line in selection + int nLastViewLine = 0; // last view line in selection + + if (!IsWritable()) + return; + if (!GetViewSelection(nFirstViewLine, nLastViewLine)) + return; + + int nNextViewLine = nLastViewLine + 1; // first view line after selected block + + CUndo::GetInstance().BeginGrouping(); + + // right original become added + for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++) + { + if (!IsStateEmpty(GetViewState(viewLine))) + { + SetViewState(viewLine, DIFFSTATE_YOURSADDED); + } + } + SaveUndoStep(); + + for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++) + { + viewdata line = m_pwndLeft->GetViewData(viewLine); + if (IsStateEmpty(line.state)) + { + line.state = DIFFSTATE_EMPTY; + } + else + { + line.state = DIFFSTATE_THEIRSADDED; + } + InsertViewData(viewLine, line); + } + + // now insert an empty block in left view + int nCount = nLastViewLine - nFirstViewLine + 1; + m_pwndLeft->InsertViewEmptyLines(nNextViewLine, nCount); + SaveUndoStep(); + + // clean up + int nRemovedLines = CleanEmptyLines(); + SaveUndoStep(); + UpdateViewLineNumbers(); + SaveUndoStep(); + + CUndo::GetInstance().EndGrouping(); + + // final clean up + ClearSelection(); + SetupAllViewSelection(nFirstViewLine, 2*nLastViewLine - nFirstViewLine - nRemovedLines + 1); + BuildAllScreen2ViewVector(); + m_pwndLeft->SetModified(); + SetModified(); + RefreshViews(); +} + +void CRightView::UseBothRightFirst() +{ + if (!IsLeftViewGood()) + return; + int nFirstViewLine = 0; // first view line in selection + int nLastViewLine = 0; // last view line in selection + + if (!IsWritable()) + return; + if (!GetViewSelection(nFirstViewLine, nLastViewLine)) + return; + + int nNextViewLine = nLastViewLine + 1; // first view line after selected block + + CUndo::GetInstance().BeginGrouping(); + + // right original become added + for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++) + { + if (!IsStateEmpty(GetViewState(viewLine))) + { + SetViewState(viewLine, DIFFSTATE_ADDED); + } + } + SaveUndoStep(); + + // your block is done, now insert their block + int viewindex = nNextViewLine; + for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++) + { + viewdata line = m_pwndLeft->GetViewData(viewLine); + if (IsStateEmpty(line.state)) + { + line.state = DIFFSTATE_EMPTY; + } + else + { + line.state = DIFFSTATE_THEIRSADDED; + } + InsertViewData(viewindex++, line); + } + SaveUndoStep(); + + // now insert an empty block in left view + int nCount = nLastViewLine - nFirstViewLine + 1; + m_pwndLeft->InsertViewEmptyLines(nFirstViewLine, nCount); + SaveUndoStep(); + + // clean up + int nRemovedLines = CleanEmptyLines(); + SaveUndoStep(); + UpdateViewLineNumbers(); + SaveUndoStep(); + + CUndo::GetInstance().EndGrouping(); + + // final clean up + ClearSelection(); + SetupAllViewSelection(nFirstViewLine, 2*nLastViewLine - nFirstViewLine - nRemovedLines + 1); + BuildAllScreen2ViewVector(); + m_pwndLeft->SetModified(); + SetModified(); + RefreshViews(); +} + +void CRightView::UseLeftBlock() +{ + int nFirstViewLine = 0; + int nLastViewLine = 0; + + if (!IsWritable()) + return; + if (!GetViewSelection(nFirstViewLine, nLastViewLine)) + return; + + return UseBlock(nFirstViewLine, nLastViewLine); +} + +void CRightView::UseLeftFile() +{ + int nFirstViewLine = 0; + int nLastViewLine = GetViewCount()-1; + + if (!IsWritable()) + return; + return UseBlock(nFirstViewLine, nLastViewLine); +} + + +void CRightView::AddContextItems(CIconMenu& popup, DiffStates state) +{ + const bool bShow = HasSelection() && (state != DIFFSTATE_UNKNOWN); + + if (IsBottomViewGood()) + { + if (bShow) + popup.AppendMenuIcon(POPUPCOMMAND_USEYOURBLOCK, IDS_VIEWCONTEXTMENU_USETHISBLOCK); + popup.AppendMenuIcon(POPUPCOMMAND_USEYOURFILE, IDS_VIEWCONTEXTMENU_USETHISFILE); + if (bShow) + { + popup.AppendMenuIcon(POPUPCOMMAND_USEYOURANDTHEIRBLOCK, IDS_VIEWCONTEXTMENU_USEYOURANDTHEIRBLOCK); + popup.AppendMenuIcon(POPUPCOMMAND_USETHEIRANDYOURBLOCK, IDS_VIEWCONTEXTMENU_USETHEIRANDYOURBLOCK); + } + } + else + { + if (bShow) + popup.AppendMenuIcon(POPUPCOMMAND_USELEFTBLOCK, IDS_VIEWCONTEXTMENU_USEOTHERBLOCK); + popup.AppendMenuIcon(POPUPCOMMAND_USELEFTFILE, IDS_VIEWCONTEXTMENU_USEOTHERFILE); + if (bShow) + { + popup.AppendMenuIcon(POPUPCOMMAND_USEBOTHRIGHTFIRST, IDS_VIEWCONTEXTMENU_USEBOTHTHISFIRST); + popup.AppendMenuIcon(POPUPCOMMAND_USEBOTHLEFTFIRST, IDS_VIEWCONTEXTMENU_USEBOTHTHISLAST); + } + } + + CBaseView::AddContextItems(popup, state); +} + + +void CRightView::UseBlock(int nFirstViewLine, int nLastViewLine) +{ + if (!IsLeftViewGood()) + return; + if (!IsWritable()) + return; + CUndo::GetInstance().BeginGrouping(); + + for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++) + { + viewdata line = m_pwndLeft->GetViewData(viewLine); + line.ending = lineendings; + switch (line.state) + { + case DIFFSTATE_CONFLICTEMPTY: + case DIFFSTATE_UNKNOWN: + case DIFFSTATE_EMPTY: + line.state = DIFFSTATE_EMPTY; + break; + case DIFFSTATE_ADDED: + case DIFFSTATE_MOVED_TO: + case DIFFSTATE_MOVED_FROM: + case DIFFSTATE_CONFLICTADDED: + case DIFFSTATE_CONFLICTED: + case DIFFSTATE_CONFLICTED_IGNORED: + case DIFFSTATE_IDENTICALADDED: + case DIFFSTATE_NORMAL: + case DIFFSTATE_THEIRSADDED: + case DIFFSTATE_YOURSADDED: + break; + case DIFFSTATE_IDENTICALREMOVED: + case DIFFSTATE_REMOVED: + case DIFFSTATE_THEIRSREMOVED: + case DIFFSTATE_YOURSREMOVED: + line.state = DIFFSTATE_ADDED; + break; + default: + break; + } + SetViewData(viewLine, line); + } + SaveUndoStep(); + + int nRemovedLines = CleanEmptyLines(); + SaveUndoStep(); + UpdateViewLineNumbers(); + SaveUndoStep(); + + CUndo::GetInstance().EndGrouping(); + + if (nRemovedLines) + { + // some lines are gone update selection + ClearSelection(); + SetupAllViewSelection(nFirstViewLine, nLastViewLine - nRemovedLines); + } + BuildAllScreen2ViewVector(); + m_pwndLeft->SetModified(); + SetModified(); + RefreshViews(); +} diff --git a/src/TortoiseMerge/RightView.h b/src/TortoiseMerge/RightView.h index 4330e5546..efaf0f25e 100644 --- a/src/TortoiseMerge/RightView.h +++ b/src/TortoiseMerge/RightView.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2008 - TortoiseSVN +// Copyright (C) 2006-2008, 2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -31,11 +31,13 @@ public: CRightView(void); ~CRightView(void); - void UseFile(bool refreshViews = true); - void UseBlock(bool refreshViews = true); - void UseLeftBeforeRight(bool refreshViews = true); - void UseRightBeforeLeft(bool refreshViews = true); + void UseBothLeftFirst(); + void UseBothRightFirst(); + void UseLeftBlock(); ///< Use Block from Left + void UseLeftFile(); ///< Use File from Left + protected: - bool OnContextMenu(CPoint point, int nLine, DiffStates state); + void AddContextItems(CIconMenu& popup, DiffStates state); + void UseBlock(int nStart, int nEnd); }; diff --git a/src/TortoiseMerge/SetColorPage.cpp b/src/TortoiseMerge/SetColorPage.cpp index c73f6cd84..b745be056 100644 --- a/src/TortoiseMerge/SetColorPage.cpp +++ b/src/TortoiseMerge/SetColorPage.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2008 - TortoiseSVN +// Copyright (C) 2006-2008, 2012 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -31,10 +31,10 @@ IMPLEMENT_DYNAMIC(CSetColorPage, CPropertyPage) CSetColorPage::CSetColorPage() : CPropertyPage(CSetColorPage::IDD) , m_bReloadNeeded(FALSE) - , m_bInit(FALSE) , m_regInlineAdded(_T("Software\\TortoiseGitMerge\\InlineAdded"), INLINEADDED_COLOR) , m_regInlineRemoved(_T("Software\\TortoiseGitMerge\\InlineRemoved"), INLINEREMOVED_COLOR) , m_regModifiedBackground(_T("Software\\TortoiseGitMerge\\Colors\\ColorModifiedB"), MODIFIED_COLOR) + , m_bInit(false) { } @@ -45,70 +45,78 @@ CSetColorPage::~CSetColorPage() void CSetColorPage::SaveData() { - if (m_bInit) - { - COLORREF cBk; - COLORREF cFg; - - cFg = ::GetSysColor(COLOR_WINDOWTEXT); - - cBk = m_cBkNormal.GetColor(); - if (cBk == -1) - cBk = m_cBkNormal.GetAutomaticColor(); - CDiffColors::GetInstance().SetColors(DIFFSTATE_NORMAL, cBk, cFg); - CDiffColors::GetInstance().SetColors(DIFFSTATE_UNKNOWN, cBk, cFg); - - cBk = m_cBkRemoved.GetColor(); - if (cBk == -1) - cBk = m_cBkRemoved.GetAutomaticColor(); - CDiffColors::GetInstance().SetColors(DIFFSTATE_REMOVED, cBk, cFg); - CDiffColors::GetInstance().SetColors(DIFFSTATE_IDENTICALREMOVED, cBk, cFg); - CDiffColors::GetInstance().SetColors(DIFFSTATE_THEIRSREMOVED, cBk, cFg); - CDiffColors::GetInstance().SetColors(DIFFSTATE_YOURSREMOVED, cBk, cFg); - - cBk = m_cBkAdded.GetColor(); - if (cBk == -1) - cBk = m_cBkAdded.GetAutomaticColor(); - CDiffColors::GetInstance().SetColors(DIFFSTATE_ADDED, cBk, cFg); - CDiffColors::GetInstance().SetColors(DIFFSTATE_IDENTICALADDED, cBk, cFg); - CDiffColors::GetInstance().SetColors(DIFFSTATE_THEIRSADDED, cBk, cFg); - CDiffColors::GetInstance().SetColors(DIFFSTATE_YOURSADDED, cBk, cFg); - - if ((DWORD)m_regInlineAdded != (DWORD)m_cBkInlineAdded.GetColor()) - m_bReloadNeeded = true; - m_regInlineAdded = (m_cBkInlineAdded.GetColor() == -1 ? m_cBkInlineAdded.GetAutomaticColor() : m_cBkInlineAdded.GetColor()); - if ((DWORD)m_regInlineRemoved != (DWORD)m_cBkInlineRemoved.GetColor()) - m_bReloadNeeded = true; - m_regInlineRemoved = (m_cBkInlineRemoved.GetColor() == -1 ? m_cBkInlineRemoved.GetAutomaticColor() : m_cBkInlineRemoved.GetColor()); - if ((DWORD)m_regModifiedBackground != (DWORD)m_cBkModified.GetColor()) - m_bReloadNeeded = true; - m_regModifiedBackground = (m_cBkModified.GetColor() == -1 ? m_cBkModified.GetAutomaticColor() : m_cBkModified.GetColor()); - - cBk = m_cBkEmpty.GetColor(); - if (cBk == -1) - cBk = m_cBkEmpty.GetAutomaticColor(); - CDiffColors::GetInstance().SetColors(DIFFSTATE_EMPTY, cBk, cFg); - - COLORREF adjustedcolor; - cBk = m_cBkConflict.GetColor(); - if (cBk == -1) - cBk = m_cBkConflict.GetAutomaticColor(); - CDiffColors::GetInstance().SetColors(DIFFSTATE_CONFLICTED, cBk, cFg); - CDiffColors::GetInstance().SetColors(DIFFSTATE_CONFLICTED_IGNORED, cBk, cFg); - CDiffColors::GetInstance().SetColors(DIFFSTATE_CONFLICTADDED, cBk, cFg); - CDiffColors::GetInstance().SetColors(DIFFSTATE_CONFLICTEMPTY, adjustedcolor, cFg); - - cBk = m_cBkConflictResolved.GetColor(); - if (cBk == -1) - cBk = m_cBkConflictResolved.GetAutomaticColor(); - CDiffColors::GetInstance().SetColors(DIFFSTATE_CONFLICTRESOLVED, cBk, cFg); - - cFg = m_cFgWhitespaces.GetColor(); - if (cFg == -1) - cFg = m_cFgWhitespaces.GetAutomaticColor(); - CRegDWORD regWhitespaceColor(_T("Software\\TortoiseGitMerge\\Colors\\Whitespace"), GetSysColor(COLOR_GRAYTEXT)); - regWhitespaceColor = cFg; - } + if (!m_bInit) + return; + + COLORREF cFg = ::GetSysColor(COLOR_WINDOWTEXT); + + COLORREF cBk = m_cBkNormal.GetColor(); + if (cBk == -1) + cBk = m_cBkNormal.GetAutomaticColor(); + CDiffColors::GetInstance().SetColors(DIFFSTATE_NORMAL, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_UNKNOWN, cBk, cFg); + + cBk = m_cBkRemoved.GetColor(); + if (cBk == -1) + cBk = m_cBkRemoved.GetAutomaticColor(); + CDiffColors::GetInstance().SetColors(DIFFSTATE_REMOVED, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_IDENTICALREMOVED, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_THEIRSREMOVED, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_YOURSREMOVED, cBk, cFg); + + cBk = m_cBkAdded.GetColor(); + if (cBk == -1) + cBk = m_cBkAdded.GetAutomaticColor(); + CDiffColors::GetInstance().SetColors(DIFFSTATE_ADDED, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_IDENTICALADDED, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_THEIRSADDED, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_YOURSADDED, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_MOVED_TO, cBk, cFg); + + if ((DWORD)m_regInlineAdded != (DWORD)m_cBkInlineAdded.GetColor()) + m_bReloadNeeded = true; + m_regInlineAdded = (m_cBkInlineAdded.GetColor() == -1 ? m_cBkInlineAdded.GetAutomaticColor() : m_cBkInlineAdded.GetColor()); + if ((DWORD)m_regInlineRemoved != (DWORD)m_cBkInlineRemoved.GetColor()) + m_bReloadNeeded = true; + m_regInlineRemoved = (m_cBkInlineRemoved.GetColor() == -1 ? m_cBkInlineRemoved.GetAutomaticColor() : m_cBkInlineRemoved.GetColor()); + if ((DWORD)m_regModifiedBackground != (DWORD)m_cBkModified.GetColor()) + m_bReloadNeeded = true; + m_regModifiedBackground = (m_cBkModified.GetColor() == -1 ? m_cBkModified.GetAutomaticColor() : m_cBkModified.GetColor()); + + cBk = m_cBkEmpty.GetColor(); + if (cBk == -1) + cBk = m_cBkEmpty.GetAutomaticColor(); + CDiffColors::GetInstance().SetColors(DIFFSTATE_EMPTY, cBk, cFg); + + COLORREF adjustedcolor = cBk; + cBk = m_cBkConflict.GetColor(); + if (cBk == -1) + cBk = m_cBkConflict.GetAutomaticColor(); + CDiffColors::GetInstance().SetColors(DIFFSTATE_CONFLICTED, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_CONFLICTED_IGNORED, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_CONFLICTADDED, cBk, cFg); + CDiffColors::GetInstance().SetColors(DIFFSTATE_CONFLICTEMPTY, adjustedcolor, cFg); + + cBk = m_cBkConflictResolved.GetColor(); + if (cBk == -1) + cBk = m_cBkConflictResolved.GetAutomaticColor(); + CDiffColors::GetInstance().SetColors(DIFFSTATE_CONFLICTRESOLVED, cBk, cFg); + + cBk = m_cBkMovedFrom.GetColor(); + if (cBk == -1) + cBk = m_cBkMovedFrom.GetAutomaticColor(); + CDiffColors::GetInstance().SetColors(DIFFSTATE_MOVED_FROM, cBk, cFg); + + cBk = m_cBkMovedTo.GetColor(); + if (cBk == -1) + cBk = m_cBkMovedTo.GetAutomaticColor(); + CDiffColors::GetInstance().SetColors(DIFFSTATE_MOVED_TO, cBk, cFg); + + cFg = m_cFgWhitespaces.GetColor(); + if (cFg == -1) + cFg = m_cFgWhitespaces.GetAutomaticColor(); + CRegDWORD regWhitespaceColor(_T("Software\\TortoiseGitMerge\\Colors\\Whitespace"), GetSysColor(COLOR_GRAYTEXT)); + regWhitespaceColor = cFg; } void CSetColorPage::DoDataExchange(CDataExchange* pDX) @@ -123,6 +131,8 @@ void CSetColorPage::DoDataExchange(CDataExchange* pDX) DDX_Control(pDX, IDC_BKEMPTY, m_cBkEmpty); DDX_Control(pDX, IDC_BKCONFLICTED, m_cBkConflict); DDX_Control(pDX, IDC_BKCONFLICTRESOLVED, m_cBkConflictResolved); + DDX_Control(pDX, IDC_BKMOVEDFROM, m_cBkMovedFrom); + DDX_Control(pDX, IDC_BKMOVEDTO, m_cBkMovedTo); DDX_Control(pDX, IDC_FGWHITESPACES, m_cFgWhitespaces); } @@ -137,6 +147,8 @@ BEGIN_MESSAGE_MAP(CSetColorPage, CPropertyPage) ON_BN_CLICKED(IDC_BKEMPTY, &CSetColorPage::OnBnClickedColor) ON_BN_CLICKED(IDC_BKCONFLICTED, &CSetColorPage::OnBnClickedColor) ON_BN_CLICKED(IDC_BKCONFLICTRESOLVED, &CSetColorPage::OnBnClickedColor) + ON_BN_CLICKED(IDC_BKMOVEDFROM, &CSetColorPage::OnBnClickedColor) + ON_BN_CLICKED(IDC_BKMOVEDTO, &CSetColorPage::OnBnClickedColor) ON_BN_CLICKED(IDC_FGWHITESPACES, &CSetColorPage::OnBnClickedColor) END_MESSAGE_MAP() @@ -191,6 +203,16 @@ BOOL CSetColorPage::OnInitDialog() m_cBkConflict.EnableAutomaticButton(sDefaultText, DIFFSTATE_CONFLICTED_DEFAULT_BG); m_cBkConflict.EnableOtherButton(sCustomText); + CDiffColors::GetInstance().GetColors(DIFFSTATE_MOVED_FROM, cBk, cFg); + m_cBkMovedFrom.SetColor(cBk); + m_cBkMovedFrom.EnableAutomaticButton(sDefaultText, DIFFSTATE_MOVEDFROM_DEFAULT_BG); + m_cBkMovedFrom.EnableOtherButton(sCustomText); + + CDiffColors::GetInstance().GetColors(DIFFSTATE_MOVED_TO, cBk, cFg); + m_cBkMovedTo.SetColor(cBk); + m_cBkMovedTo.EnableAutomaticButton(sDefaultText, DIFFSTATE_MOVEDTO_DEFAULT_BG); + m_cBkMovedTo.EnableOtherButton(sCustomText); + CDiffColors::GetInstance().GetColors(DIFFSTATE_CONFLICTRESOLVED, cBk, cFg); m_cBkConflictResolved.SetColor(cBk); m_cBkConflictResolved.EnableAutomaticButton(sDefaultText, DIFFSTATE_CONFLICTRESOLVED_DEFAULT_BG); diff --git a/src/TortoiseMerge/SetColorPage.h b/src/TortoiseMerge/SetColorPage.h index c9f4e27b4..71ec52bc6 100644 --- a/src/TortoiseMerge/SetColorPage.h +++ b/src/TortoiseMerge/SetColorPage.h @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2006-2008 - TortoiseSVN @@ -66,6 +66,8 @@ protected: CMFCColorButton m_cBkEmpty; CMFCColorButton m_cBkConflict; CMFCColorButton m_cBkConflictResolved; + CMFCColorButton m_cBkMovedFrom; + CMFCColorButton m_cBkMovedTo; CMFCColorButton m_cBkModified; CMFCColorButton m_cFgWhitespaces; }; diff --git a/src/TortoiseMerge/SetMainPage.cpp b/src/TortoiseMerge/SetMainPage.cpp index de65ebabc..142b91bf5 100644 --- a/src/TortoiseMerge/SetMainPage.cpp +++ b/src/TortoiseMerge/SetMainPage.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2008 - TortoiseSVN +// Copyright (C) 2006-2010, 2012 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -33,42 +33,43 @@ CSetMainPage::CSetMainPage() : CPropertyPage(CSetMainPage::IDD) , m_bBackup(FALSE) , m_bFirstDiffOnLoad(FALSE) + , m_bFirstConflictOnLoad(FALSE) , m_nTabSize(0) , m_bIgnoreEOL(FALSE) , m_bOnePane(FALSE) , m_bViewLinenumbers(FALSE) - , m_bStrikeout(FALSE) , m_bReloadNeeded(FALSE) - , m_bDisplayBinDiff(TRUE) , m_bCaseInsensitive(FALSE) , m_bUTF8Default(FALSE) + , m_bAutoAdd(TRUE) + , m_nMaxInline(3000) , m_dwFontSize(0) { m_regBackup = CRegDWORD(_T("Software\\TortoiseGitMerge\\Backup")); m_regFirstDiffOnLoad = CRegDWORD(_T("Software\\TortoiseGitMerge\\FirstDiffOnLoad"), TRUE); + m_regFirstConflictOnLoad = CRegDWORD(_T("Software\\TortoiseGitMerge\\FirstConflictOnLoad"), TRUE); m_regTabSize = CRegDWORD(_T("Software\\TortoiseGitMerge\\TabSize"), 4); m_regIgnoreEOL = CRegDWORD(_T("Software\\TortoiseGitMerge\\IgnoreEOL"), TRUE); m_regOnePane = CRegDWORD(_T("Software\\TortoiseGitMerge\\OnePane")); - m_regIgnoreWS = CRegDWORD(_T("Software\\TortoiseGitMerge\\IgnoreWS")); m_regViewLinenumbers = CRegDWORD(_T("Software\\TortoiseGitMerge\\ViewLinenumbers"), 1); - m_regStrikeout = CRegDWORD(_T("Software\\TortoiseGitMerge\\StrikeOut"), TRUE); m_regFontName = CRegString(_T("Software\\TortoiseGitMerge\\LogFontName"), _T("Courier New")); m_regFontSize = CRegDWORD(_T("Software\\TortoiseGitMerge\\LogFontSize"), 10); - m_regDisplayBinDiff = CRegDWORD(_T("Software\\TortoiseGitMerge\\DisplayBinDiff"), TRUE); m_regCaseInsensitive = CRegDWORD(_T("Software\\TortoiseGitMerge\\CaseInsensitive"), FALSE); m_regUTF8Default = CRegDWORD(_T("Software\\TortoiseGitMerge\\UseUTF8"), FALSE); + m_regAutoAdd = CRegDWORD(_T("Software\\TortoiseGitMerge\\AutoAdd"), TRUE); + m_regMaxInline = CRegDWORD(_T("Software\\TortoiseGitMerge\\InlineDiffMaxLineLength"), 3000); m_bBackup = m_regBackup; m_bFirstDiffOnLoad = m_regFirstDiffOnLoad; + m_bFirstConflictOnLoad = m_regFirstConflictOnLoad; m_nTabSize = m_regTabSize; m_bIgnoreEOL = m_regIgnoreEOL; m_bOnePane = m_regOnePane; - m_nIgnoreWS = m_regIgnoreWS; m_bViewLinenumbers = m_regViewLinenumbers; - m_bStrikeout = m_regStrikeout; - m_bDisplayBinDiff = m_regDisplayBinDiff; m_bCaseInsensitive = m_regCaseInsensitive; m_bUTF8Default = m_regUTF8Default; + m_bAutoAdd = m_regAutoAdd; + m_nMaxInline = m_regMaxInline; } CSetMainPage::~CSetMainPage() @@ -80,6 +81,7 @@ void CSetMainPage::DoDataExchange(CDataExchange* pDX) CPropertyPage::DoDataExchange(pDX); DDX_Check(pDX, IDC_BACKUP, m_bBackup); DDX_Check(pDX, IDC_FIRSTDIFFONLOAD, m_bFirstDiffOnLoad); + DDX_Check(pDX, IDC_FIRSTCONFLICTONLOAD, m_bFirstConflictOnLoad); DDX_Text(pDX, IDC_TABSIZE, m_nTabSize); DDV_MinMaxInt(pDX, m_nTabSize, 1, 1000); DDX_Check(pDX, IDC_IGNORELF, m_bIgnoreEOL); @@ -94,27 +96,27 @@ void CSetMainPage::DoDataExchange(CDataExchange* pDX) } DDX_Control(pDX, IDC_FONTNAMES, m_cFontNames); DDX_Check(pDX, IDC_LINENUMBERS, m_bViewLinenumbers); - DDX_Check(pDX, IDC_STRIKEOUT, m_bStrikeout); - DDX_Check(pDX, IDC_USEBDIFF, m_bDisplayBinDiff); DDX_Check(pDX, IDC_CASEINSENSITIVE, m_bCaseInsensitive); DDX_Check(pDX, IDC_UTF8DEFAULT, m_bUTF8Default); + DDX_Check(pDX, IDC_AUTOADD, m_bAutoAdd); + DDX_Text(pDX, IDC_MAXINLINE, m_nMaxInline); } void CSetMainPage::SaveData() { m_regBackup = m_bBackup; m_regFirstDiffOnLoad = m_bFirstDiffOnLoad; + m_regFirstConflictOnLoad = m_bFirstConflictOnLoad; m_regTabSize = m_nTabSize; m_regIgnoreEOL = m_bIgnoreEOL; m_regOnePane = m_bOnePane; - m_regIgnoreWS = m_nIgnoreWS; m_regFontName = m_sFontName; m_regFontSize = m_dwFontSize; m_regViewLinenumbers = m_bViewLinenumbers; - m_regStrikeout = m_bStrikeout; - m_regDisplayBinDiff = m_bDisplayBinDiff; m_regCaseInsensitive = m_bCaseInsensitive; m_regUTF8Default = m_bUTF8Default; + m_regAutoAdd = m_bAutoAdd; + m_regMaxInline = m_nMaxInline; } BOOL CSetMainPage::OnApply() @@ -137,35 +139,19 @@ BOOL CSetMainPage::OnInitDialog() m_bBackup = m_regBackup; m_bFirstDiffOnLoad = m_regFirstDiffOnLoad; + m_bFirstConflictOnLoad = m_regFirstConflictOnLoad; m_nTabSize = m_regTabSize; m_bIgnoreEOL = m_regIgnoreEOL; m_bOnePane = m_regOnePane; - m_nIgnoreWS = m_regIgnoreWS; - m_bDisplayBinDiff = m_regDisplayBinDiff; m_sFontName = m_regFontName; m_dwFontSize = m_regFontSize; m_bViewLinenumbers = m_regViewLinenumbers; - m_bStrikeout = m_regStrikeout; m_bCaseInsensitive = m_regCaseInsensitive; m_bUTF8Default = m_regUTF8Default; + m_bAutoAdd = m_regAutoAdd; + m_nMaxInline = m_regMaxInline; - UINT uRadio = IDC_WSCOMPARE; - switch (m_nIgnoreWS) - { - case 0: - uRadio = IDC_WSCOMPARE; - break; - case 1: - uRadio = IDC_WSIGNOREALL; - break; - case 2: - uRadio = IDC_WSIGNORECHANGED; - break; - default: - break; - } - - CheckRadioButton(IDC_WSCOMPARE, IDC_WSIGNORECHANGED, uRadio); + DialogEnableWindow(IDC_FIRSTCONFLICTONLOAD, m_bFirstDiffOnLoad); CString temp; int count = 0; @@ -202,17 +188,15 @@ BEGIN_MESSAGE_MAP(CSetMainPage, CPropertyPage) ON_BN_CLICKED(IDC_IGNORELF, &CSetMainPage::OnModifiedWithReload) ON_BN_CLICKED(IDC_ONEPANE, &CSetMainPage::OnModified) ON_BN_CLICKED(IDC_FIRSTDIFFONLOAD, &CSetMainPage::OnModified) - ON_BN_CLICKED(IDC_WSCOMPARE, &CSetMainPage::OnBnClickedWhitespace) - ON_BN_CLICKED(IDC_WSIGNORECHANGED, &CSetMainPage::OnBnClickedWhitespace) - ON_BN_CLICKED(IDC_WSIGNOREALL, &CSetMainPage::OnBnClickedWhitespace) + ON_BN_CLICKED(IDC_FIRSTCONFLICTONLOAD, &CSetMainPage::OnModified) ON_BN_CLICKED(IDC_LINENUMBERS, &CSetMainPage::OnModified) - ON_BN_CLICKED(IDC_STRIKEOUT, &CSetMainPage::OnModified) ON_EN_CHANGE(IDC_TABSIZE, &CSetMainPage::OnModified) ON_CBN_SELCHANGE(IDC_FONTSIZES, &CSetMainPage::OnModified) ON_CBN_SELCHANGE(IDC_FONTNAMES, &CSetMainPage::OnModified) - ON_BN_CLICKED(IDC_USEBDIFF, &CSetMainPage::OnModifiedWithReload) ON_BN_CLICKED(IDC_CASEINSENSITIVE, &CSetMainPage::OnModified) ON_BN_CLICKED(IDC_UTF8DEFAULT, &CSetMainPage::OnModified) + ON_BN_CLICKED(IDC_AUTOADD, &CSetMainPage::OnModified) + ON_EN_CHANGE(IDC_MAXINLINE, &CSetMainPage::OnModifiedWithReload) END_MESSAGE_MAP() @@ -220,7 +204,9 @@ END_MESSAGE_MAP() void CSetMainPage::OnModified() { + UpdateData(); SetModified(); + DialogEnableWindow(IDC_FIRSTCONFLICTONLOAD, m_bFirstDiffOnLoad); } void CSetMainPage::OnModifiedWithReload() @@ -233,20 +219,19 @@ void CSetMainPage::OnBnClickedWhitespace() { m_bReloadNeeded = TRUE; SetModified(); - UINT uRadio = GetCheckedRadioButton(IDC_WSCOMPARE, IDC_WSIGNORECHANGED); - switch (uRadio) +} + +BOOL CSetMainPage::DialogEnableWindow(UINT nID, BOOL bEnable) +{ + CWnd * pwndDlgItem = GetDlgItem(nID); + if (pwndDlgItem == NULL) + return FALSE; + if (bEnable) + return pwndDlgItem->EnableWindow(bEnable); + if (GetFocus() == pwndDlgItem) { - case IDC_WSCOMPARE: - m_nIgnoreWS = 0; - break; - case IDC_WSIGNOREALL: - m_nIgnoreWS = 1; - break; - case IDC_WSIGNORECHANGED: - m_nIgnoreWS = 2; - break; - default: - break; + SendMessage(WM_NEXTDLGCTL, 0, FALSE); } + return pwndDlgItem->EnableWindow(bEnable); } diff --git a/src/TortoiseMerge/SetMainPage.h b/src/TortoiseMerge/SetMainPage.h index 6b6689425..4be5a83a0 100644 --- a/src/TortoiseMerge/SetMainPage.h +++ b/src/TortoiseMerge/SetMainPage.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2008 - TortoiseSVN +// Copyright (C) 2006-2010 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -49,30 +49,36 @@ protected: virtual BOOL OnApply(); virtual BOOL OnInitDialog(); + afx_msg void OnModified(); + afx_msg void OnModifiedWithReload(); + afx_msg void OnBnClickedWhitespace(); + DECLARE_MESSAGE_MAP() + BOOL DialogEnableWindow(UINT nID, BOOL bEnable); + BOOL m_bBackup; CRegDWORD m_regBackup; BOOL m_bFirstDiffOnLoad; CRegDWORD m_regFirstDiffOnLoad; + BOOL m_bFirstConflictOnLoad; + CRegDWORD m_regFirstConflictOnLoad; int m_nTabSize; CRegDWORD m_regTabSize; BOOL m_bIgnoreEOL; CRegDWORD m_regIgnoreEOL; BOOL m_bOnePane; CRegDWORD m_regOnePane; - DWORD m_nIgnoreWS; - CRegDWORD m_regIgnoreWS; BOOL m_bViewLinenumbers; CRegDWORD m_regViewLinenumbers; - BOOL m_bStrikeout; - CRegDWORD m_regStrikeout; - BOOL m_bDisplayBinDiff; - CRegDWORD m_regDisplayBinDiff; BOOL m_bCaseInsensitive; CRegDWORD m_regCaseInsensitive; BOOL m_bUTF8Default; CRegDWORD m_regUTF8Default; + BOOL m_bAutoAdd; + CRegDWORD m_regAutoAdd; + int m_nMaxInline; + CRegDWORD m_regMaxInline; CRegDWORD m_regFontSize; DWORD m_dwFontSize; @@ -81,8 +87,4 @@ protected: CMFCFontComboBox m_cFontNames; CComboBox m_cFontSizes; -protected: - afx_msg void OnModified(); - afx_msg void OnModifiedWithReload(); - afx_msg void OnBnClickedWhitespace(); }; diff --git a/src/TortoiseMerge/Settings.cpp b/src/TortoiseMerge/Settings.cpp index e02adedde..536bc32e0 100644 --- a/src/TortoiseMerge/Settings.cpp +++ b/src/TortoiseMerge/Settings.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006 - Stefan Kueng +// Copyright (C) 2006, 2009-2010 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -54,7 +54,9 @@ void CSettings::AddPropPages() void CSettings::RemovePropPages() { delete m_pMainPage; + m_pMainPage = NULL; delete m_pColorPage; + m_pColorPage = NULL; } void CSettings::SaveData() diff --git a/src/TortoiseMerge/Settings.h b/src/TortoiseMerge/Settings.h index 7d0920d35..aab47580a 100644 --- a/src/TortoiseMerge/Settings.h +++ b/src/TortoiseMerge/Settings.h @@ -1,4 +1,4 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program // Copyright (C) 2006 - Stefan Kueng diff --git a/src/TortoiseMerge/TempFile.cpp b/src/TortoiseMerge/TempFile.cpp new file mode 100644 index 000000000..82ba60f40 --- /dev/null +++ b/src/TortoiseMerge/TempFile.cpp @@ -0,0 +1,179 @@ +// TortoiseGitMerge - a Windows shell extension for easy version control + +// Copyright (C) 2003-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#include "stdafx.h" +#include "TempFile.h" +#include "PathUtils.h" +#include "DirFileEnum.h" +#include "SmartHandle.h" + +CTempFiles::CTempFiles(void) +{ +} + +CTempFiles::~CTempFiles(void) +{ + m_TempFileList.DeleteAllFiles(false, false); +} + +CTempFiles& CTempFiles::Instance() +{ + static CTempFiles instance; + return instance; +} + +CTGitPath CTempFiles::ConstructTempPath(const CTGitPath& path) +{ + DWORD len = ::GetTempPath(0, NULL); + std::unique_ptr temppath (new TCHAR[len+1]); + std::unique_ptr tempF (new TCHAR[len+50]); + ::GetTempPath (len+1, temppath.get()); + CTGitPath tempfile; + CString possibletempfile; + if (path.IsEmpty()) + { + ::GetTempFileName (temppath.get(), _T("tsm"), 0, tempF.get()); + tempfile = CTGitPath (tempF.get()); + } + else + { + int i=0; + do + { + // use the UI path, which does unescaping for urls + CString filename = path.GetUIFileOrDirectoryName(); + // remove illegal chars which could be present in urls + filename.Remove('?'); + filename.Remove('*'); + filename.Remove('<'); + filename.Remove('>'); + filename.Remove('|'); + filename.Remove('"'); + // the inner loop assures that the resulting path is < MAX_PATH + // if that's not possible without reducing the 'filename' to less than 5 chars, use a path + // that's longer than MAX_PATH (in that case, we can't really do much to avoid longer paths) + do + { + possibletempfile.Format(_T("%s%s.tsm%3.3x.tmp%s"), temppath, (LPCTSTR)filename, i, (LPCTSTR)path.GetFileExtension()); + tempfile.SetFromWin(possibletempfile); + filename = filename.Left(filename.GetLength()-1); + } while ( (filename.GetLength() > 4) + && (tempfile.GetWinPathString().GetLength() >= MAX_PATH)); + i++; + } while (PathFileExists(tempfile.GetWinPath())); + } + + // caller has to actually grab the file path + + return tempfile; +} + +CTGitPath CTempFiles::CreateTempPath (bool bRemoveAtEnd, const CTGitPath& path, bool directory) +{ + bool succeeded = false; + for (int retryCount = 0; retryCount < MAX_RETRIES; ++retryCount) + { + CTGitPath tempfile = ConstructTempPath (path); + + // now create the temp file / directory, so that subsequent calls to GetTempFile() return + // different filenames. + // Handle races, i.e. name collisions. + + if (directory) + { + DeleteFile(tempfile.GetWinPath()); + if (CreateDirectory (tempfile.GetWinPath(), NULL) == FALSE) + { + if (GetLastError() != ERROR_ALREADY_EXISTS) + return CTGitPath(); + } + else + succeeded = true; + } + else + { + CAutoFile hFile = CreateFile(tempfile.GetWinPath(), GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL); + if (!hFile) + { + if (GetLastError() != ERROR_ALREADY_EXISTS) + return CTGitPath(); + } + else + { + succeeded = true; + } + } + + // done? + + if (succeeded) + { + if (bRemoveAtEnd) + m_TempFileList.AddPath(tempfile); + + return tempfile; + } + } + + // give up + + return CTGitPath(); +} + +CTGitPath CTempFiles::GetTempFilePath(bool bRemoveAtEnd, const CTGitPath& path /* = CTGitPath() */) +{ + return CreateTempPath (bRemoveAtEnd, path, false); +} + +CString CTempFiles::GetTempFilePathString() +{ + return CreateTempPath (true, CTGitPath(), false).GetWinPathString(); +} + +CTGitPath CTempFiles::GetTempDirPath(bool bRemoveAtEnd, const CTGitPath& path /* = CTGitPath() */) +{ + return CreateTempPath (bRemoveAtEnd, path, true); +} + +void CTempFiles::DeleteOldTempFiles(LPCTSTR wildCard) +{ + DWORD len = ::GetTempPath(0, NULL); + std::unique_ptr path(new TCHAR[len + 100]); + len = ::GetTempPath (len+100, path.get()); + if (len == 0) + return; + + CSimpleFileFind finder = CSimpleFileFind(path.get(), wildCard); + FILETIME systime_; + ::GetSystemTimeAsFileTime(&systime_); + __int64 systime = (__int64)systime_.dwLowDateTime | (__int64)systime_.dwHighDateTime << 32LL; + while (finder.FindNextFileNoDirectories()) + { + CString filepath = finder.GetFilePath(); + + FILETIME createtime_ = finder.GetCreateTime(); + __int64 createtime = (__int64)createtime_.dwLowDateTime | (__int64)createtime_.dwHighDateTime << 32LL; + createtime += 864000000000LL; //only delete files older than a day + if (createtime < systime) + { + ::SetFileAttributes(filepath, FILE_ATTRIBUTE_NORMAL); + ::DeleteFile(filepath); + } + } +} + diff --git a/src/TortoiseMerge/TempFile.h b/src/TortoiseMerge/TempFile.h new file mode 100644 index 000000000..71a47e187 --- /dev/null +++ b/src/TortoiseMerge/TempFile.h @@ -0,0 +1,80 @@ +// TortoiseGitMerge - a Windows shell extension for easy version control + +// Copyright (C) 2003-2006,2008,2010 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#pragma once + +#include +#include "TGitPath.h" + +/** +* \ingroup Utils +* This singleton class handles temporary files. +* All temp files are deleted at the end of a run of SVN operations +*/ +class CTempFiles +{ +public: + static CTempFiles& Instance(); + + /** + * Returns a path to a temporary file. + * \param bRemoveAtEnd if true, the temp file is removed when this object + * goes out of scope. + * \param path if set, the temp file will have the same file extension + * as this path. + * \param revision if set, the temp file name will include the revision number + */ + CTGitPath GetTempFilePath(bool bRemoveAtEnd, const CTGitPath& path = CTGitPath()); + CString GetTempFilePathString(); + + /** + * Returns a path to a temporary directory. + * \param bRemoveAtEnd if true, the temp directory is removed when this object + * goes out of scope. + * \param path if set, the temp directory will have the same file extension + * as this path. + * \param revision if set, the temp directory name will include the revision number + */ + CTGitPath GetTempDirPath(bool bRemoveAtEnd, const CTGitPath& path = CTGitPath()); + + // Look for temporary files left around by TortoiseMerge and + // remove them. But only delete 'old' files + static void DeleteOldTempFiles(LPCTSTR wildCard); + + void AddFileToRemove(const CString& file) { m_TempFileList.AddPath(CTGitPath(file)); } + +private: + + // try to allocate an unused temp file / dir at most MAX_RETRIES times + + enum {MAX_RETRIES = 100}; + + // list of paths to delete when terminating the app + + CTGitPathList m_TempFileList; + + // actual implementation + + CTGitPath ConstructTempPath(const CTGitPath& path); + CTGitPath CreateTempPath (bool bRemoveAtEnd, const CTGitPath& path, bool directory); + + // construction / destruction + + CTempFiles(void); + ~CTempFiles(void); +}; diff --git a/src/TortoiseMerge/TempFiles.cpp b/src/TortoiseMerge/TempFiles.cpp deleted file mode 100644 index 515d30876..000000000 --- a/src/TortoiseMerge/TempFiles.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// TortoiseMerge - a Diff/Patch program - -// Copyright (C) 2006,2008 - Stefan Kueng - -// 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, -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// -#include "stdafx.h" -#include "tempfiles.h" - -CTempFiles::CTempFiles(void) -{ -} - -CTempFiles::~CTempFiles(void) -{ - for (int i=0; i pDlg = pfdc; + if (pDlg) + { + pDlg->Close(S_OK); + } + } + return S_OK; + } +}; + CTortoiseMergeApp::CTortoiseMergeApp() + : m_nAppLook(0) { EnableHtmlHelp(); - m_bLoadUserToolbars = FALSE; - m_bSaveState = FALSE; + m_bHiColorIcons = TRUE; } // The one and only CTortoiseMergeApp object @@ -51,23 +75,23 @@ CTortoiseMergeApp theApp; CString sOrigCWD; CCrashReportTGit g_crasher(L"TortoiseGitMerge " _T(APP_X64_STRING)); +CString g_sGroupingUUID; + // CTortoiseMergeApp initialization BOOL CTortoiseMergeApp::InitInstance() { SetDllDirectory(L""); + SetTaskIDPerUUID(); CCrashReport::Instance().AddUserInfoToReport(L"CommandLine", GetCommandLine()); - CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows)); - CMFCButton::EnableWindowsTheming(); - { DWORD len = GetCurrentDirectory(0, NULL); if (len) { - auto_buffer originalCurrentDirectory(len); - if (GetCurrentDirectory(len, originalCurrentDirectory)) + std::unique_ptr originalCurrentDirectory(new TCHAR[len]); + if (GetCurrentDirectory(len, originalCurrentDirectory.get())) { - sOrigCWD = originalCurrentDirectory; + sOrigCWD = originalCurrentDirectory.get(); sOrigCWD = CPathUtils::GetLongPathname(sOrigCWD); } } @@ -156,13 +180,27 @@ BOOL CTortoiseMergeApp::InitInstance() // visual styles. Otherwise, any window creation will fail. InitCommonControls(); + CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows)); + CMFCButton::EnableWindowsTheming(); + EnableTaskbarInteraction(FALSE); + // Initialize all Managers for usage. They are automatically constructed // if not yet present InitContextMenuManager(); InitKeyboardManager(); + InitTooltipManager (); + CMFCToolTipInfo params; + params.m_bVislManagerTheme = TRUE; + + GetTooltipManager ()->SetTooltipParams ( + AFX_TOOLTIP_TYPE_ALL, + RUNTIME_CLASS (CMFCToolTipCtrl), + ¶ms); CCmdLineParser parser = CCmdLineParser(this->m_lpCmdLine); + g_sGroupingUUID = parser.GetVal(L"groupuuid"); + if (parser.HasKey(_T("?")) || parser.HasKey(_T("help"))) { CString sHelpText; @@ -222,6 +260,8 @@ BOOL CTortoiseMergeApp::InitInstance() pFrame->m_bOneWay = FALSE; if (parser.HasKey(_T("reversedpatch"))) pFrame->m_bReversedPatch = TRUE; + if (parser.HasKey(_T("saverequired"))) + pFrame->m_bSaveRequired = true; if (pFrame->m_Data.IsBaseFileInUse() && !pFrame->m_Data.IsYourFileInUse() && pFrame->m_Data.IsTheirFileInUse()) { pFrame->m_Data.m_yourFile.TransferDetailsFrom(pFrame->m_Data.m_theirFile); @@ -249,76 +289,129 @@ BOOL CTortoiseMergeApp::InitInstance() // the patchfile itself was not. // So ask the user for that patchfile - OPENFILENAME ofn = {0}; // common dialog box structure - TCHAR szFile[MAX_PATH] = {0}; // buffer for file name - // Initialize OPENFILENAME - ofn.lStructSize = sizeof(OPENFILENAME); - ofn.hwndOwner = pFrame->m_hWnd; - ofn.lpstrFile = szFile; - ofn.nMaxFile = _countof(szFile); - CString temp; - temp.LoadString(IDS_OPENDIFFFILETITLE); - if (temp.IsEmpty()) - ofn.lpstrTitle = NULL; - else - ofn.lpstrTitle = temp; - - ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER; - // check if there's a patchfile in the clipboard - UINT cFormat = RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF")); - if (cFormat) + HRESULT hr; + // Create a new common save file dialog + CComPtr pfd = NULL; + hr = pfd.CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER); + if (SUCCEEDED(hr)) { - if (OpenClipboard(NULL)) + // Set the dialog options + DWORD dwOptions; + if (SUCCEEDED(hr = pfd->GetOptions(&dwOptions))) + { + hr = pfd->SetOptions(dwOptions | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST); + } + + // Set a title + if (SUCCEEDED(hr)) + { + CString temp; + temp.LoadString(IDS_OPENDIFFFILETITLE); + pfd->SetTitle(temp); + } + CSelectFileFilter fileFilter(IDS_PATCHFILEFILTER); + hr = pfd->SetFileTypes(fileFilter.GetCount(), fileFilter); + bool bAdvised = false; + DWORD dwCookie = 0; + CComObjectStackEx cbk; + CComQIPtr pEvents = cbk.GetUnknown(); + { - UINT enumFormat = 0; - do + CComPtr pfdCustomize; + hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdCustomize)); + if (SUCCEEDED(hr)) { - if (enumFormat == cFormat) + // check if there's a unified diff on the clipboard and + // add a button to the fileopen dialog if there is. + UINT cFormat = RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF")); + if ((cFormat)&&(OpenClipboard(NULL))) { - // yes, there's a patchfile in the clipboard - ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK | OFN_EXPLORER; + HGLOBAL hglb = GetClipboardData(cFormat); + if (hglb) + { + pfdCustomize->AddPushButton(101, CString(MAKEINTRESOURCE(IDS_PATCH_COPYFROMCLIPBOARD))); + hr = pfd->Advise(pEvents, &dwCookie); + bAdvised = SUCCEEDED(hr); + } + CloseClipboard(); + } + } + } - ofn.hInstance = AfxGetResourceHandle(); - ofn.lpTemplateName = MAKEINTRESOURCE(IDD_PATCH_FILE_OPEN_CUSTOM); - ofn.lpfnHook = CreatePatchFileOpenHook; + // Show the save file dialog + if (SUCCEEDED(hr) && SUCCEEDED(hr = pfd->Show(pFrame->m_hWnd))) + { + // Get the selection from the user + CComPtr psiResult = NULL; + hr = pfd->GetResult(&psiResult); + if (bAdvised) + pfd->Unadvise(dwCookie); + if (SUCCEEDED(hr)) + { + PWSTR pszPath = NULL; + hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszPath); + if (SUCCEEDED(hr)) + { + pFrame->m_Data.m_sDiffFile = pszPath; + CoTaskMemFree(pszPath); } - } while((enumFormat = EnumClipboardFormats(enumFormat))!=0); - CloseClipboard(); + } + else + { + // no result, which means we closed the dialog in our button handler + std::wstring sTempFile; + if (TrySavePatchFromClipboard(sTempFile)) + pFrame->m_Data.m_sDiffFile = sTempFile.c_str(); + } + } + else + { + if (bAdvised) + pfd->Unadvise(dwCookie); + return FALSE; } } - - CString sFilter; - sFilter.LoadString(IDS_PATCHFILEFILTER); - TCHAR * pszFilters = new TCHAR[sFilter.GetLength()+4]; - _tcscpy_s (pszFilters, sFilter.GetLength()+4, sFilter); - // Replace '|' delimiters with '\0's - TCHAR *ptr = pszFilters + _tcslen(pszFilters); //set ptr at the NULL - while (ptr != pszFilters) + else { - if (*ptr == '|') - *ptr = '\0'; - ptr--; - } - ofn.lpstrFilter = pszFilters; - ofn.nFilterIndex = 1; + OPENFILENAME ofn = {0}; // common dialog box structure + TCHAR szFile[MAX_PATH] = {0}; // buffer for file name + // Initialize OPENFILENAME + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = pFrame->m_hWnd; + ofn.lpstrFile = szFile; + ofn.nMaxFile = _countof(szFile); + CString temp; + temp.LoadString(IDS_OPENDIFFFILETITLE); + if (temp.IsEmpty()) + ofn.lpstrTitle = NULL; + else + ofn.lpstrTitle = temp; + + ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER; + if( HasClipboardPatch() ) { + ofn.Flags |= ( OFN_ENABLETEMPLATE | OFN_ENABLEHOOK ); + ofn.hInstance = AfxGetResourceHandle(); + ofn.lpTemplateName = MAKEINTRESOURCE(IDD_PATCH_FILE_OPEN_CUSTOM); + ofn.lpfnHook = CreatePatchFileOpenHook; + } - // Display the Open dialog box. - CString tempfile; - if (GetOpenFileName(&ofn)==FALSE) - { - delete [] pszFilters; - return FALSE; + CSelectFileFilter fileFilter(IDS_PATCHFILEFILTER); + ofn.lpstrFilter = fileFilter; + ofn.nFilterIndex = 1; + + // Display the Open dialog box. + if (GetOpenFileName(&ofn)==FALSE) + { + return FALSE; + } + pFrame->m_Data.m_sDiffFile = ofn.lpstrFile; } - delete [] pszFilters; - pFrame->m_Data.m_sDiffFile = ofn.lpstrFile; } if ( pFrame->m_Data.m_baseFile.GetFilename().IsEmpty() && pFrame->m_Data.m_yourFile.GetFilename().IsEmpty() ) { - LPWSTR *szArglist; int nArgs; - - szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs); + LPWSTR *szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs); if( NULL == szArglist ) { TRACE("CommandLineToArgvW failed\n"); @@ -359,38 +452,7 @@ BOOL CTortoiseMergeApp::InitInstance() if (pFrame->m_bBlame) pFrame->m_bReadOnly = true; - // try to find a suitable window title - CString sYour = pFrame->m_Data.m_yourFile.GetDescriptiveName(); - if (sYour.Find(_T(" - "))>=0) - sYour = sYour.Left(sYour.Find(_T(" - "))); - if (sYour.Find(_T(" : "))>=0) - sYour = sYour.Left(sYour.Find(_T(" : "))); - CString sTheir = pFrame->m_Data.m_theirFile.GetDescriptiveName(); - if (sTheir.Find(_T(" - "))>=0) - sTheir = sTheir.Left(sTheir.Find(_T(" - "))); - if (sTheir.Find(_T(" : "))>=0) - sTheir = sTheir.Left(sTheir.Find(_T(" : "))); - - if (!sYour.IsEmpty() && !sTheir.IsEmpty()) - { - if (sYour.CompareNoCase(sTheir)==0) - pFrame->SetWindowText(sYour + _T(" - TortoiseGitMerge")); - else if ((sYour.GetLength() < 10) && - (sTheir.GetLength() < 10)) - pFrame->SetWindowText(sYour + _T(" - ") + sTheir + _T(" - TortoiseGitMerge")); - else - { - // we have two very long descriptive texts here, which - // means we have to find a way to use them as a window - // title in a shorter way. - // for simplicity, we just use the one from "yourfile" - pFrame->SetWindowText(sYour + _T(" - TortoiseGitMerge")); - } - } - else if (!sYour.IsEmpty()) - pFrame->SetWindowText(sYour + _T(" - TortoiseGitMerge")); - else if (!sTheir.IsEmpty()) - pFrame->SetWindowText(sTheir + _T(" - TortoiseGitMerge")); + pFrame->SetWindowTitle(); if (parser.HasKey(_T("createunifieddiff"))) { @@ -402,38 +464,7 @@ BOOL CTortoiseMergeApp::InitInstance() CString outfile = parser.GetVal(_T("outfile")); if (outfile.IsEmpty()) { - OPENFILENAME ofn = {0}; // common dialog box structure - TCHAR szFile[MAX_PATH] = {0}; // buffer for file name - ofn.lStructSize = sizeof(OPENFILENAME); - ofn.lpstrFile = szFile; - ofn.nMaxFile = _countof(szFile); - CString temp; - temp.LoadString(IDS_SAVEASTITLE); - if (!temp.IsEmpty()) - ofn.lpstrTitle = temp; - ofn.Flags = OFN_OVERWRITEPROMPT; - CString sFilter; - sFilter.LoadString(IDS_COMMONFILEFILTER); - TCHAR * pszFilters = new TCHAR[sFilter.GetLength()+4]; - _tcscpy_s (pszFilters, sFilter.GetLength()+4, sFilter); - // Replace '|' delimiters with '\0's - TCHAR *ptr = pszFilters + _tcslen(pszFilters); //set ptr at the NULL - while (ptr != pszFilters) - { - if (*ptr == '|') - *ptr = '\0'; - ptr--; - } - ofn.lpstrFilter = pszFilters; - ofn.nFilterIndex = 1; - - // Display the Save dialog box. - CString sFile; - if (GetSaveFileName(&ofn)==TRUE) - { - outfile = CString(ofn.lpstrFile); - } - delete [] pszFilters; + CCommonAppUtils::FileOpenSave(outfile, NULL, IDS_SAVEASTITLE, IDS_COMMONFILEFILTER, false, NULL); } if (!outfile.IsEmpty()) { @@ -442,6 +473,13 @@ BOOL CTortoiseMergeApp::InitInstance() } } } + +#if 0 + pFrame->resolveMsgWnd = parser.HasVal(L"resolvemsghwnd") ? (HWND)parser.GetLongLongVal(L"resolvemsghwnd") : 0; + pFrame->resolveMsgWParam = parser.HasVal(L"resolvemsgwparam") ? (WPARAM)parser.GetLongLongVal(L"resolvemsgwparam") : 0; + pFrame->resolveMsgLParam = parser.HasVal(L"resolvemsglparam") ? (LPARAM)parser.GetLongLongVal(L"resolvemsglparam") : 0; +#endif + // The one and only window has been initialized, so show and update it pFrame->ActivateFrame(); pFrame->ShowWindow(SW_SHOW); @@ -453,7 +491,14 @@ BOOL CTortoiseMergeApp::InitInstance() return TRUE; } - return pFrame->LoadViews(); + int line = -2; + if (parser.HasVal(_T("line"))) + { + line = parser.GetLongVal(_T("line")); + line--; // we need the index + } + + return pFrame->LoadViews(line); } // CTortoiseMergeApp message handlers @@ -467,42 +512,17 @@ void CTortoiseMergeApp::OnAppAbout() UINT_PTR CALLBACK CTortoiseMergeApp::CreatePatchFileOpenHook(HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM /*lParam*/) { - if(uiMsg == WM_COMMAND && LOWORD(wParam) == IDC_PATCH_TO_CLIPBOARD) + if(uiMsg == WM_COMMAND && LOWORD(wParam) == IDC_PATCH_TO_CLIPBOARD) { HWND hFileDialog = GetParent(hDlg); // if there's a patchfile in the clipboard, we save it // to a temporary file and tell TortoiseMerge to use that one - UINT cFormat = RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF")); - if ((cFormat)&&(OpenClipboard(NULL))) + std::wstring sTempFile; + if (TrySavePatchFromClipboard(sTempFile)) { - HGLOBAL hglb = GetClipboardData(cFormat); - LPCSTR lpstr = (LPCSTR)GlobalLock(hglb); - - DWORD len = GetTempPath(0, NULL); - TCHAR * path = new TCHAR[len+1]; - TCHAR * tempF = new TCHAR[len+100]; - GetTempPath (len+1, path); - GetTempFileName (path, _T("tsm"), 0, tempF); - std::wstring sTempFile = std::wstring(tempF); - delete [] path; - delete [] tempF; - - FILE * outFile; - size_t patchlen = strlen(lpstr); - _tfopen_s(&outFile, sTempFile.c_str(), _T("wb")); - if(outFile) - { - size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile); - if (size == patchlen) - { - CommDlg_OpenSave_SetControlText(hFileDialog, edt1, sTempFile.c_str()); - PostMessage(hFileDialog, WM_COMMAND, MAKEWPARAM(IDOK, BM_CLICK), (LPARAM)(GetDlgItem(hDlg, IDOK))); - } - fclose(outFile); - } - GlobalUnlock(hglb); - CloseClipboard(); + CommDlg_OpenSave_SetControlText(hFileDialog, edt1, sTempFile.c_str()); + PostMessage(hFileDialog, WM_COMMAND, MAKEWPARAM(IDOK, BM_CLICK), (LPARAM)(GetDlgItem(hDlg, IDOK))); } } return 0; @@ -512,38 +532,68 @@ int CTortoiseMergeApp::ExitInstance() { // Look for temporary files left around by TortoiseMerge and // remove them. But only delete 'old' files - DWORD len = ::GetTempPath(0, NULL); - TCHAR * path = new TCHAR[len + 100]; - len = ::GetTempPath (len+100, path); - if (len != 0) + CTempFiles::DeleteOldTempFiles(_T("*tsm*.*")); + + return CWinAppEx::ExitInstance(); +} + +bool CTortoiseMergeApp::HasClipboardPatch() +{ + // check if there's a patchfile in the clipboard + const UINT cFormat = RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF")); + if (cFormat == 0) + return false; + + if (OpenClipboard(NULL) == 0) + return false; + + bool containsPatch = false; + UINT enumFormat = 0; + do { - CSimpleFileFind finder = CSimpleFileFind(path, _T("*tsm*.*")); - FILETIME systime_; - ::GetSystemTimeAsFileTime(&systime_); - __int64 systime = (((_int64)systime_.dwHighDateTime)<<32) | ((__int64)systime_.dwLowDateTime); - while (finder.FindNextFileNoDirectories()) + if (enumFormat == cFormat) { - CString filepath = finder.GetFilePath(); - HANDLE hFile = ::CreateFile(filepath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); - if (hFile != INVALID_HANDLE_VALUE) - { - FILETIME createtime_; - if (::GetFileTime(hFile, &createtime_, NULL, NULL)) - { - ::CloseHandle(hFile); - __int64 createtime = (((_int64)createtime_.dwHighDateTime)<<32) | ((__int64)createtime_.dwLowDateTime); - if ((createtime + 864000000000) < systime) //only delete files older than a day - { - ::SetFileAttributes(filepath, FILE_ATTRIBUTE_NORMAL); - ::DeleteFile(filepath); - } - } - else - ::CloseHandle(hFile); - } + containsPatch = true; // yes, there's a patchfile in the clipboard } + } while((enumFormat = EnumClipboardFormats(enumFormat))!=0); + CloseClipboard(); + + return containsPatch; +} + +bool CTortoiseMergeApp::TrySavePatchFromClipboard(std::wstring& resultFile) +{ + resultFile.clear(); + + UINT cFormat = RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF")); + if (cFormat == 0) + return false; + if (OpenClipboard(NULL) == 0) + return false; + + HGLOBAL hglb = GetClipboardData(cFormat); + LPCSTR lpstr = (LPCSTR)GlobalLock(hglb); + + DWORD len = GetTempPath(0, NULL); + std::unique_ptr path(new TCHAR[len+1]); + std::unique_ptr tempF(new TCHAR[len+100]); + GetTempPath (len+1, path.get()); + GetTempFileName (path.get(), _T("tsm"), 0, tempF.get()); + std::wstring sTempFile = std::wstring(tempF.get()); + + FILE* outFile = 0; + _tfopen_s(&outFile, sTempFile.c_str(), _T("wb")); + if (outFile != 0) + { + size_t patchlen = strlen(lpstr); + size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile); + if (size == patchlen) + resultFile = sTempFile; + + fclose(outFile); } - delete[] path; + GlobalUnlock(hglb); + CloseClipboard(); - return CWinAppEx::ExitInstance(); + return !resultFile.empty(); } diff --git a/src/TortoiseMerge/TortoiseMerge.h b/src/TortoiseMerge/TortoiseMerge.h index 10f7bc773..0d026bad5 100644 --- a/src/TortoiseMerge/TortoiseMerge.h +++ b/src/TortoiseMerge/TortoiseMerge.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2008 - TortoiseSVN +// Copyright (C) 2006-2008, 2010-2012 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -23,6 +23,7 @@ #endif #include "resource.h" // main symbols +#include "CrashReport.h" /** * \ingroup TortoiseMerge @@ -41,12 +42,16 @@ public: // Implementation UINT m_nAppLook; + BOOL m_bHiColorIcons; + protected: afx_msg void OnAppAbout(); DECLARE_MESSAGE_MAP() private: static UINT_PTR CALLBACK CreatePatchFileOpenHook(HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM lParam); - + bool HasClipboardPatch(); + static bool TrySavePatchFromClipboard(std::wstring& resultFile); }; extern CTortoiseMergeApp theApp; +extern CString g_sGroupingUUID; diff --git a/src/TortoiseMerge/TortoiseMerge.vcproj b/src/TortoiseMerge/TortoiseMerge.vcproj deleted file mode 100644 index 704524534..000000000 --- a/src/TortoiseMerge/TortoiseMerge.vcproj +++ /dev/null @@ -1,2543 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TortoiseMerge/TortoiseMerge.vcxproj b/src/TortoiseMerge/TortoiseMerge.vcxproj index 068e54675..03de83fba 100644 --- a/src/TortoiseMerge/TortoiseMerge.vcxproj +++ b/src/TortoiseMerge/TortoiseMerge.vcxproj @@ -123,23 +123,36 @@ + + NotUsing + + + + + + + + + - - + + + + - + - + @@ -147,7 +160,7 @@ Create - + @@ -155,7 +168,6 @@ - @@ -163,14 +175,12 @@ - - - + @@ -343,30 +353,43 @@ + + + + + + + + + + - + + + + - - + + @@ -374,46 +397,45 @@ - - - - - + - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + diff --git a/src/TortoiseMerge/TortoiseMerge.vcxproj.filters b/src/TortoiseMerge/TortoiseMerge.vcxproj.filters index dd0075984..51710dac7 100644 --- a/src/TortoiseMerge/TortoiseMerge.vcxproj.filters +++ b/src/TortoiseMerge/TortoiseMerge.vcxproj.filters @@ -25,6 +25,15 @@ {6963f9fb-04ac-4303-8b76-5b03773aa40e} + + {fb22fec1-7b09-4ab2-8fe2-93e8343bad3d} + + + {ea35101d-5b37-4be4-8328-1d869be484bc} + + + {5a81c7c4-96ff-4357-b8b4-683681fa377d} + @@ -42,9 +51,6 @@ Source Files - - Source Files - Source Files @@ -54,9 +60,6 @@ Source Files - - Source Files - Source Files @@ -69,15 +72,9 @@ Source Files - - Source Files - Source Files - - Source Files - Source Files @@ -93,9 +90,6 @@ Source Files - - Source Files - Source Files @@ -108,9 +102,6 @@ Source Files - - Utils\Source Files - Utils\Source Files @@ -135,9 +126,6 @@ Utils\Source Files - - Utils\Source Files - Utils\Source Files @@ -156,7 +144,7 @@ Utils\Source Files - + Utils\Source Files @@ -168,6 +156,27 @@ Utils\Source Files + + Source Files + + + Utils\Source Files + + + Utils\Source Files + + + Utils\Source Files + + + Utils\Source Files + + + Utils\Source Files + + + Source Files + Libdiff @@ -294,6 +303,30 @@ Libdiff + + Source Files + + + Utils\Source Files + + + Git\Source Files + + + Git\Source Files + + + Utils\Source Files + + + Utils\Source Files + + + Source Files + + + Source Files + @@ -314,9 +347,6 @@ Header Files - - Header Files - Header Files @@ -344,12 +374,6 @@ Header Files - - Header Files - - - Header Files - Header Files @@ -380,9 +404,6 @@ Header Files - - Utils\Header Files - Utils\Header Files @@ -425,82 +446,134 @@ Utils\Header Files - + + Utils\Header Files + + + Utils\Header Files + + + Utils\Header Files + + Utils\Header Files + + Header Files + Utils\Header Files - + Utils\Header Files - + Utils\Header Files - + Utils\Header Files - + Utils\Header Files - - Libdiff + + Utils\Header Files + + + Utils\Header Files + + + Header Files + + + Header Files + + + Git\Header Files + + + Git\Header Files + + + Utils\Header Files + + + Header Files + + + Header Files Resource Files - + Resource Files - - - + Resource Files - - + + Resource Files - - + + Resource Files - - + + Resource Files - - + + Resource Files - - + + Resource Files - - + + Resource Files - - + + Resource Files - - + + Resource Files - - + + Resource Files - - + + Resource Files - - + + Resource Files - - + + Resource Files - - + + + Resource Files + + + Resource Files + + + Resource Files + + Resource Files - - + + Resource Files - + + + Resource Files + + + Resource Files + + + Resource Files + diff --git a/src/TortoiseMerge/TortoiseMergeLang.vcproj b/src/TortoiseMerge/TortoiseMergeLang.vcproj deleted file mode 100644 index 28728432c..000000000 --- a/src/TortoiseMerge/TortoiseMergeLang.vcproj +++ /dev/null @@ -1,376 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TortoiseMerge/TortoiseMergeLang.vcxproj.filters b/src/TortoiseMerge/TortoiseMergeLang.vcxproj.filters index 6bbc257e1..df99b3022 100644 --- a/src/TortoiseMerge/TortoiseMergeLang.vcxproj.filters +++ b/src/TortoiseMerge/TortoiseMergeLang.vcxproj.filters @@ -1,10 +1,6 @@  - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx - {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd diff --git a/src/TortoiseMerge/Undo.cpp b/src/TortoiseMerge/Undo.cpp index 8f2233184..8fa551610 100644 --- a/src/TortoiseMerge/Undo.cpp +++ b/src/TortoiseMerge/Undo.cpp @@ -1,7 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2007 - TortoiseSVN -// Copyright (C) 2011 Sven Strickroth +// Copyright (C) 2006-2007, 2010-2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -23,20 +22,31 @@ #include "BaseView.h" -void viewstate::AddLineFormView(CBaseView *pView, int nLine, bool bAddEmptyLine) +void viewstate::AddViewLineFromView(CBaseView *pView, int nViewLine, bool bAddEmptyLine) { + // is undo good place for this ? if (!pView || !pView->m_pViewData) return; - difflines[nLine] = pView->m_pViewData->GetLine(nLine); - linestates[nLine] = pView->m_pViewData->GetState(nLine); - linesEOL[nLine] = pView->m_pViewData->GetLineEnding(nLine); + replacedlines[nViewLine] = pView->m_pViewData->GetData(nViewLine); if (bAddEmptyLine) { - addedlines.push_back(nLine + 1); - pView->AddEmptyLine(nLine); + addedlines.push_back(nViewLine + 1); + pView->AddEmptyViewLine(nViewLine); } } +void viewstate::Clear() +{ + difflines.clear(); + linestates.clear(); + linelines.clear(); + linesEOL.clear(); + addedlines.clear(); + + removedlines.clear(); + replacedlines.clear(); +} + CUndo& CUndo::GetInstance() { static CUndo instance; @@ -46,17 +56,16 @@ CUndo& CUndo::GetInstance() CUndo::CUndo() { m_originalstate = 0; + m_groupCount = 0; } CUndo::~CUndo() { } -void CUndo::AddState(const viewstate& leftstate, const viewstate& rightstate, const viewstate& bottomstate, POINT pt) +void CUndo::AddState(const allviewstate& allstate, POINT pt) { - m_viewstates.push_back(bottomstate); - m_viewstates.push_back(rightstate); - m_viewstates.push_back(leftstate); + m_viewstates.push_back(allstate); m_caretpoints.push_back(pt); } @@ -65,7 +74,7 @@ bool CUndo::Undo(CBaseView * pLeft, CBaseView * pRight, CBaseView * pBottom) if (!CanUndo()) return false; - if (!m_groups.empty() && m_groups.back() == m_caretpoints.size()) + if (m_groups.size() && m_groups.back() == m_caretpoints.size()) { m_groups.pop_back(); std::list::size_type b = m_groups.back(); @@ -76,110 +85,113 @@ bool CUndo::Undo(CBaseView * pLeft, CBaseView * pRight, CBaseView * pBottom) else UndoOne(pLeft, pRight, pBottom); - CBaseView * pActiveView = pLeft; + CBaseView * pActiveView = NULL; - if (pRight && pRight->HasCaret()) + if (pBottom && pBottom->IsTarget()) + { + pActiveView = pBottom; + } + else + if (pRight && pRight->IsTarget()) + { pActiveView = pRight; + } + else + //if (pLeft && pLeft->IsTarget()) + { + pActiveView = pLeft; + } - if (pBottom && pBottom->HasCaret()) - pActiveView = pBottom; if (pActiveView) { pActiveView->ClearSelection(); + pActiveView->BuildAllScreen2ViewVector(); + pActiveView->RecalcAllVertScrollBars(); + pActiveView->RecalcAllHorzScrollBars(); pActiveView->EnsureCaretVisible(); pActiveView->UpdateCaret(); - pActiveView->SetModified(m_viewstates.size() != m_originalstate); + bool bModified = m_viewstates.size() != m_originalstate; + if (pLeft) + pLeft->SetModified(bModified); + if (pRight) + pRight->SetModified(bModified); + if (pBottom) + pBottom->SetModified(bModified); pActiveView->RefreshViews(); } if (m_viewstates.size() < m_originalstate) // Can never get back to original state now - m_originalstate = 1; // size() is always a multiple of 3 + m_originalstate = (size_t)-1; return true; } void CUndo::UndoOne(CBaseView * pLeft, CBaseView * pRight, CBaseView * pBottom) { - viewstate state = m_viewstates.back(); - Undo(state, pLeft); - m_viewstates.pop_back(); - state = m_viewstates.back(); - Undo(state, pRight); - m_viewstates.pop_back(); - state = m_viewstates.back(); - Undo(state, pBottom); + allviewstate allstate = m_viewstates.back(); + POINT pt = m_caretpoints.back(); + + Undo(allstate.left, pLeft, pt); + Undo(allstate.right, pRight, pt); + Undo(allstate.bottom, pBottom, pt); + m_viewstates.pop_back(); - if ((pLeft)&&(pLeft->HasCaret())) - { - pLeft->SetCaretPosition(m_caretpoints.back()); - pLeft->EnsureCaretVisible(); - } - if ((pRight)&&(pRight->HasCaret())) - { - pRight->SetCaretPosition(m_caretpoints.back()); - pRight->EnsureCaretVisible(); - } - if ((pBottom)&&(pBottom->HasCaret())) - { - pBottom->SetCaretPosition(m_caretpoints.back()); - pBottom->EnsureCaretVisible(); - } m_caretpoints.pop_back(); } -void CUndo::Undo(const viewstate& state, CBaseView * pView) +void CUndo::Undo(const viewstate& state, CBaseView * pView, const POINT& pt) { if (!pView) return; - for (std::list::const_iterator it = state.addedlines.begin(); it != state.addedlines.end(); ++it) + CViewData* viewData = pView->m_pViewData; + if (!viewData) + return; + + for (std::list::const_reverse_iterator it = state.addedlines.rbegin(); it != state.addedlines.rend(); ++it) { - if (pView->m_pViewData) - pView->m_pViewData->RemoveData(*it); + viewData->RemoveData(*it); } for (std::map::const_iterator it = state.linelines.begin(); it != state.linelines.end(); ++it) { - if (pView->m_pViewData) - { - pView->m_pViewData->SetLineNumber(it->first, it->second); - } + viewData->SetLineNumber(it->first, it->second); } for (std::map::const_iterator it = state.linestates.begin(); it != state.linestates.end(); ++it) { - if (pView->m_pViewData) - { - pView->m_pViewData->SetState(it->first, (DiffStates)it->second); - } + viewData->SetState(it->first, (DiffStates)it->second); } for (std::map::const_iterator it = state.linesEOL.begin(); it != state.linesEOL.end(); ++it) { - if (pView->m_pViewData) - { - pView->m_pViewData->SetLineEnding(it->first, (EOL)it->second); - } + viewData->SetLineEnding(it->first, (EOL)it->second); } for (std::map::const_iterator it = state.difflines.begin(); it != state.difflines.end(); ++it) { - if (pView->m_pViewData) - { - pView->m_pViewData->SetLine(it->first, it->second); - } + viewData->SetLine(it->first, it->second); } for (std::map::const_iterator it = state.removedlines.begin(); it != state.removedlines.end(); ++it) { - if (pView->m_pViewData) - { - pView->m_pViewData->InsertData(it->first, it->second.sLine, it->second.state, it->second.linenumber, it->second.ending); - } + viewData->InsertData(it->first, it->second); + } + for (std::map::const_iterator it = state.replacedlines.begin(); it != state.replacedlines.end(); ++it) + { + viewData->SetData(it->first, it->second); + } + + if (pView->IsTarget()) + { + pView->SetCaretViewPosition(pt); + pView->EnsureCaretVisible(); } -} +} + void CUndo::Clear() { m_viewstates.clear(); m_caretpoints.clear(); m_groups.clear(); m_originalstate = 0; + m_groupCount = 0; } diff --git a/src/TortoiseMerge/Undo.h b/src/TortoiseMerge/Undo.h index 2c4b6473e..7fc7b86f8 100644 --- a/src/TortoiseMerge/Undo.h +++ b/src/TortoiseMerge/Undo.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2007 - TortoiseSVN +// Copyright (C) 2006-2007,2009-2012 - TortoiseSVN // Copyright (C) 2011 Sven Strickroth // This program is free software; you can redistribute it and/or @@ -37,12 +37,29 @@ typedef struct viewstate std::list addedlines; std::map removedlines; + std::map replacedlines; - void AddLineFormView(CBaseView *pView, int nLine, bool bAddEmptyLine); + void AddViewLineFromView(CBaseView *pView, int nViewLine, bool bAddEmptyLine); + void Clear(); + bool IsEmpty() { return difflines.empty() && linestates.empty() && linelines.empty() && linesEOL.empty() && addedlines.empty() && removedlines.empty() && replacedlines.empty(); } } viewstate; /** * \ingroup TortoiseMerge + * this struct holds all the information of a single change in TortoiseMerge for all(3) views. + */ +struct allviewstate +{ + viewstate right; + viewstate bottom; + viewstate left; + + void Clear() { right.Clear(); bottom.Clear(); left.Clear(); } + bool IsEmpty() { return right.IsEmpty() && bottom.IsEmpty() && left.IsEmpty(); } +}; + +/** + * \ingroup TortoiseMerge * Holds all the information of previous changes made to a view content. * Of course, can undo those changes. */ @@ -52,21 +69,22 @@ public: static CUndo& GetInstance(); bool Undo(CBaseView * pLeft, CBaseView * pRight, CBaseView * pBottom); - void AddState(const viewstate& leftstate, const viewstate& rightstate, const viewstate& bottomstate, POINT pt); - bool CanUndo() { return (!m_viewstates.empty()); } + void AddState(const allviewstate& allstate, POINT pt); + bool CanUndo() {return !m_viewstates.empty();} bool IsGrouping() { return m_groups.size() % 2 == 1; } - void BeginGrouping() { ASSERT(!IsGrouping()); m_groups.push_back(m_caretpoints.size()); } - void EndGrouping(){ ASSERT(IsGrouping()); m_groups.push_back(m_caretpoints.size()); } + void BeginGrouping() { if (m_groupCount==0) m_groups.push_back(m_caretpoints.size()); m_groupCount++; } + void EndGrouping(){ m_groupCount--; if (m_groupCount==0) m_groups.push_back(m_caretpoints.size()); } void Clear(); - void MarkAsOriginalState() { m_originalstate = (unsigned int)m_viewstates.size(); } + void MarkAsOriginalState() { m_originalstate = m_viewstates.size(); } protected: - void Undo(const viewstate& state, CBaseView * pView); + void Undo(const viewstate& state, CBaseView * pView, const POINT& pt); void UndoOne(CBaseView * pLeft, CBaseView * pRight, CBaseView * pBottom); - std::list m_viewstates; + std::list m_viewstates; std::list m_caretpoints; std::list< std::list::size_type > m_groups; - unsigned int m_originalstate; + size_t m_originalstate; + int m_groupCount; private: CUndo(); ~CUndo(); diff --git a/src/TortoiseMerge/ViewData.cpp b/src/TortoiseMerge/ViewData.cpp index 97303a2e9..d33e3bed2 100644 --- a/src/TortoiseMerge/ViewData.cpp +++ b/src/TortoiseMerge/ViewData.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2007 - TortoiseSVN +// Copyright (C) 2007,2009-2010 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -27,13 +27,15 @@ CViewData::~CViewData(void) { } -void CViewData::AddData(const CString& sLine, DiffStates state, int linenumber, EOL ending) +void CViewData::AddData(const CString& sLine, DiffStates state, int linenumber, EOL ending, HIDESTATE hide, int movedIndex) { viewdata data; data.sLine = sLine; data.state = state; data.linenumber = linenumber; data.ending = ending; + data.hidestate = hide; + data.movedIndex = movedIndex; return AddData(data); } @@ -42,13 +44,15 @@ void CViewData::AddData(const viewdata& data) return m_data.push_back(data); } -void CViewData::InsertData(int index, const CString& sLine, DiffStates state, int linenumber, EOL ending) +void CViewData::InsertData(int index, const CString& sLine, DiffStates state, int linenumber, EOL ending, HIDESTATE hide, int movedIndex) { viewdata data; data.sLine = sLine; data.state = state; data.linenumber = linenumber; data.ending = ending; + data.hidestate = hide; + data.movedIndex = movedIndex; return InsertData(index, data); } diff --git a/src/TortoiseMerge/ViewData.h b/src/TortoiseMerge/ViewData.h index 7873b4924..e8d62b501 100644 --- a/src/TortoiseMerge/ViewData.h +++ b/src/TortoiseMerge/ViewData.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2007-2008 - TortoiseSVN +// Copyright (C) 2007-2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -22,17 +22,52 @@ #include +enum HIDESTATE +{ + HIDESTATE_SHOWN, + HIDESTATE_HIDDEN, + HIDESTATE_MARKER, +}; + /** * \ingroup TortoiseMerge * Holds the information which is required to define a single line of text. */ -typedef struct +class viewdata { - CString sLine; - DiffStates state; - int linenumber; - EOL ending; -} viewdata; +public: + viewdata() + : state(DIFFSTATE_UNKNOWN) + , linenumber(-1) + , movedIndex(-1) + , ending(EOL_AUTOLINE) + , hidestate(HIDESTATE_HIDDEN) + { + } + + viewdata( + const CString& sLineInit, + DiffStates stateInit, + int linenumberInit, + EOL endingInit, + HIDESTATE hideInit, + int movedIndexInit) + : state(stateInit) + , linenumber(linenumberInit) + , movedIndex(movedIndexInit) + , ending(endingInit) + , hidestate(hideInit) + { + sLine=sLineInit; + } + + CString sLine; + DiffStates state; + int linenumber; + int movedIndex; + EOL ending; + HIDESTATE hidestate; +}; /** * \ingroup TortoiseMerge @@ -44,29 +79,35 @@ public: CViewData(void); ~CViewData(void); - void AddData(const CString& sLine, DiffStates state, int linenumber, EOL ending); + void AddData(const CString& sLine, DiffStates state, int linenumber, EOL ending, HIDESTATE hide, int movedline); void AddData(const viewdata& data); - void InsertData(int index, const CString& sLine, DiffStates state, int linenumber, EOL ending); + void AddEmpty() {AddData(CString(), DIFFSTATE_EMPTY, -1, EOL_NOENDING, HIDESTATE_SHOWN, -1);} + void InsertData(int index, const CString& sLine, DiffStates state, int linenumber, EOL ending, HIDESTATE hide, int movedline); void InsertData(int index, const viewdata& data); void RemoveData(int index) {m_data.erase(m_data.begin() + index);} const viewdata& GetData(int index) {return m_data[index];} - const CString& GetLine(int index) {return m_data[index].sLine;} - DiffStates GetState(int index) {return m_data[index].state;} - int GetLineNumber(int index) { return (!m_data.empty()) ? m_data[index].linenumber : 0; } + const CString& GetLine(int index) const {return m_data[index].sLine;} + DiffStates GetState(int index) const {return m_data[index].state;} + HIDESTATE GetHideState(int index) {return m_data[index].hidestate;} + int GetLineNumber(int index) {return m_data.size() ? m_data[index].linenumber : 0;} + int GetMovedIndex(int index) {return m_data.size() ? m_data[index].movedIndex: 0;} int FindLineNumber(int number); - EOL GetLineEnding(int index) {return m_data[index].ending;} + EOL GetLineEnding(int index) const {return m_data[index].ending;} int GetCount() { return (int)m_data.size(); } + void SetData(int index, const viewdata& data) {m_data[index] = data;}; void SetState(int index, DiffStates state) {m_data[index].state = state;} void SetLine(int index, const CString& sLine) {m_data[index].sLine = sLine;} void SetLineNumber(int index, int linenumber) {m_data[index].linenumber = linenumber;} void SetLineEnding(int index, EOL ending) {m_data[index].ending = ending;} + void SetMovedIndex(int index, int movedIndex) {m_data[index].movedIndex = movedIndex;} + void SetLineHideState(int index, HIDESTATE state) {m_data[index].hidestate = state;} void Clear() {m_data.clear();} void Reserve(int length) {m_data.reserve(length);} protected: std::vector m_data; -}; +}; \ No newline at end of file diff --git a/src/TortoiseMerge/WorkingFile.cpp b/src/TortoiseMerge/WorkingFile.cpp index cfa0dc070..dbabc50e4 100644 --- a/src/TortoiseMerge/WorkingFile.cpp +++ b/src/TortoiseMerge/WorkingFile.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2007,2011 - TortoiseSVN +// Copyright (C) 2006-2007, 2011-2012 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -25,6 +25,7 @@ CWorkingFile::CWorkingFile(void) { + ClearStoredAttributes(); } CWorkingFile::~CWorkingFile(void) @@ -36,6 +37,7 @@ void CWorkingFile::SetFileName(const CString& newFilename) m_sFilename = newFilename; m_sFilename.Replace('/', '\\'); m_sDescriptiveName.Empty(); + ClearStoredAttributes(); } void CWorkingFile::SetDescriptiveName(const CString& newDescName) @@ -48,8 +50,10 @@ CString CWorkingFile::GetDescriptiveName() if (m_sDescriptiveName.IsEmpty()) { CString sDescriptiveName = CPathUtils::GetFileNameFromPath(m_sFilename); - if (sDescriptiveName.GetLength() < 20) - return sDescriptiveName; + WCHAR pathbuf[MAX_PATH] = {0}; + PathCompactPathEx(pathbuf, sDescriptiveName, 50, 0); + sDescriptiveName = pathbuf; + return sDescriptiveName; } return m_sDescriptiveName; } @@ -70,6 +74,7 @@ void CWorkingFile::TransferDetailsFrom(CWorkingFile& rightHandFile) m_sFilename = rightHandFile.m_sFilename; m_sDescriptiveName = rightHandFile.m_sDescriptiveName; rightHandFile.SetOutOfUse(); + m_attribs = rightHandFile.m_attribs; } CString CWorkingFile::GetWindowName() const @@ -99,3 +104,50 @@ bool CWorkingFile::Exists() const { return (!!PathFileExists(m_sFilename)); } + +void CWorkingFile::SetOutOfUse() +{ + m_sFilename.Empty(); + m_sDescriptiveName.Empty(); + ClearStoredAttributes(); +} + + +bool CWorkingFile::HasSourceFileChanged() const +{ + if (!InUse()) + { + return false; + } + WIN32_FILE_ATTRIBUTE_DATA attribs = {0}; + if (PathFileExists(m_sFilename)) + { + if (GetFileAttributesEx(m_sFilename, GetFileExInfoStandard, &attribs)) + { + if ( (m_attribs.nFileSizeHigh != attribs.nFileSizeHigh) || + (m_attribs.nFileSizeLow != attribs.nFileSizeLow) ) + return true; + return ( (CompareFileTime(&m_attribs.ftCreationTime, &attribs.ftCreationTime)!=0) || + (CompareFileTime(&m_attribs.ftLastWriteTime, &attribs.ftLastWriteTime)!=0) ); + } + } + + return false; +} + +void CWorkingFile::StoreFileAttributes() +{ + ClearStoredAttributes(); + + WIN32_FILE_ATTRIBUTE_DATA attribs = {0}; + if (GetFileAttributesEx(m_sFilename, GetFileExInfoStandard, &attribs)) + { + m_attribs = attribs; + } +} + +void CWorkingFile::ClearStoredAttributes() +{ + static const WIN32_FILE_ATTRIBUTE_DATA attribsEmpty = {0}; + m_attribs = attribsEmpty; +} diff --git a/src/TortoiseMerge/WorkingFile.h b/src/TortoiseMerge/WorkingFile.h index 0ab2cefa8..6a17ca13d 100644 --- a/src/TortoiseMerge/WorkingFile.h +++ b/src/TortoiseMerge/WorkingFile.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2007 - TortoiseSVN +// Copyright (C) 2006-2007, 2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -39,14 +39,19 @@ public: void CreateEmptyFile(); CString GetWindowName() const; CString GetFilename() const { return m_sFilename; } - void SetOutOfUse() { m_sFilename.Empty(); m_sDescriptiveName.Empty(); } + void SetOutOfUse(); + + bool HasSourceFileChanged() const; + void StoreFileAttributes(); // Move the details of the specified file to the current one, and then mark the specified file // as out of use void TransferDetailsFrom(CWorkingFile& rightHandFile); private: + void ClearStoredAttributes(); + CString m_sFilename; CString m_sDescriptiveName; - + WIN32_FILE_ATTRIBUTE_DATA m_attribs; }; diff --git a/src/TortoiseMerge/XSplitter.cpp b/src/TortoiseMerge/XSplitter.cpp index cdc929366..8cb4e23e5 100644 --- a/src/TortoiseMerge/XSplitter.cpp +++ b/src/TortoiseMerge/XSplitter.cpp @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006 - Stefan Kueng +// Copyright (C) 2006, 2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -26,18 +26,27 @@ static char THIS_FILE[] = __FILE__; #endif CXSplitter::CXSplitter() + : m_bBarLocked(FALSE) + , m_nHiddenCol(-1) + , m_nHiddenRow(-1) + , m_pColOldSize(nullptr) + , m_pRowOldSize(nullptr) + , m_nOldCols(0) + , m_nOldRows(0) { - m_bBarLocked=FALSE; - m_nHiddenCol = -1; - m_nHiddenRow = -1; } CXSplitter::~CXSplitter() { + delete [] m_pRowOldSize; + delete [] m_pColOldSize; } + BEGIN_MESSAGE_MAP(CXSplitter, CSplitterWnd) + ON_WM_LBUTTONDBLCLK() ON_WM_LBUTTONDOWN() + ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() ON_WM_SETCURSOR() END_MESSAGE_MAP() @@ -277,3 +286,61 @@ void CXSplitter::ShowColumn() RecalcLayout(); } +void CXSplitter::CenterSplitter() +{ + // get the size of all views + int width = 0; + int height = 0; + for( int nRow = 0; nRow < m_nRows; ++nRow ) + { + height += m_pRowInfo[nRow].nCurSize; + } + for( int nCol = 0; nCol < m_nCols; ++nCol ) + { + width += m_pColInfo[nCol].nCurSize; + } + + // now set the sizes of the views + for( int nRow = 0; nRow < m_nRows; ++nRow ) + { + m_pRowInfo[nRow].nIdealSize = height / m_nRows; + } + for( int nCol = 0; nCol < m_nCols; ++nCol ) + { + m_pColInfo[nCol].nIdealSize = width / m_nCols; + } + RecalcLayout(); + CopyRowAndColInfo(); +} + +void CXSplitter::OnLButtonDblClk( UINT /*nFlags*/, CPoint /*point*/ ) +{ + CenterSplitter(); +} + +void CXSplitter::OnLButtonUp(UINT nFlags, CPoint point) +{ + CSplitterWnd::OnLButtonUp(nFlags, point); + CopyRowAndColInfo(); +} + +void CXSplitter::CopyRowAndColInfo() +{ + delete [] m_pColOldSize; m_pColOldSize = nullptr; + delete [] m_pRowOldSize; m_pRowOldSize = nullptr; + + m_nOldCols = m_nCols; + m_nOldRows = m_nRows; + if (m_nCols) + { + m_pColOldSize = new int[m_nCols]; + for (int i = 0; i < m_nCols; ++i) + m_pColOldSize[i] = m_pColInfo[i].nCurSize; + } + if (m_nRows) + { + m_pRowOldSize = new int[m_nRows]; + for (int i = 0; i < m_nRows; ++i) + m_pRowOldSize[i] = m_pRowInfo[i].nCurSize; + } +} diff --git a/src/TortoiseMerge/XSplitter.h b/src/TortoiseMerge/XSplitter.h index 4fe247e7c..a2628be9b 100644 --- a/src/TortoiseMerge/XSplitter.h +++ b/src/TortoiseMerge/XSplitter.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006 - Stefan Kueng +// Copyright (C) 2006, 2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -53,48 +53,66 @@ public: * Replaces a view in the Splitter with another view. */ BOOL ReplaceView(int row, int col,CRuntimeClass * pViewClass, SIZE size); - /** - * Shows a splitter column which was previously hidden. Don't call + /** + * Shows a splitter column which was previously hidden. Don't call * this method if the column is already visible! Check it first * with IsColumnHidden() - */ - void ShowColumn(); - /** - * Hides the given splitter column. Don't call this method on already hidden columns! + */ + void ShowColumn(); + /** + * Hides the given splitter column. Don't call this method on already hidden columns! * Check it first with IsColumnHidden() - * \param nColHide The column to hide - */ - void HideColumn(int nColHide); + * \param nColHide The column to hide + */ + void HideColumn(int nColHide); /** * Checks if a given column is hidden. */ BOOL IsColumnHidden(int nCol) const {return (m_nHiddenCol == nCol);} - /** - * Shows a splitter row which was previously hidden. Don't call + /** + * Shows a splitter row which was previously hidden. Don't call * this method if the row is already visible! Check it first * with IsRowHidden() - */ - void ShowRow(); - /** - * Hides the given splitter row. Don't call this method on already hidden rows! + */ + void ShowRow(); + /** + * Hides the given splitter row. Don't call this method on already hidden rows! * Check it first with IsRowHidden() - * \param nRowHide The row to hide - */ - void HideRow(int nRowHide); + * \param nRowHide The row to hide + */ + void HideRow(int nRowHide); /** * Checks if a given row is hidden. */ BOOL IsRowHidden(int nRow) const {return (m_nHiddenRow == nRow);} + /** + * Centers the splitter in the middle of the views + */ + void CenterSplitter(); + + int GetOldRowCount() { return m_nOldRows; } + int GetOldColCount() { return m_nOldCols; } + bool HasOldRowSize() { return m_pRowOldSize != nullptr; } + bool HasOldColSize() { return m_pColOldSize != nullptr; } + int GetOldRowSize(int index) { return m_pRowOldSize[index]; } + int GetOldColSize(int index) { return m_pColOldSize[index]; } + protected: + afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnLButtonUp(UINT nFlags, CPoint point); afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); DECLARE_MESSAGE_MAP() + void CopyRowAndColInfo(); private: BOOL m_bBarLocked; ///< is the splitter bar locked? - int m_nHiddenCol; ///< Index of the hidden column. - int m_nHiddenRow; ///< Index of the hidden row. - + int m_nHiddenCol; ///< Index of the hidden column. + int m_nHiddenRow; ///< Index of the hidden row + int * m_pColOldSize; ///< the current size of the last splitter positioning + int * m_pRowOldSize; ///< the current size of the last splitter positioning + int m_nOldCols; + int m_nOldRows; }; diff --git a/src/TortoiseMerge/libsvn_diff/SVNLineDiff.cpp b/src/TortoiseMerge/libsvn_diff/SVNLineDiff.cpp index a8bcb67fc..4d23f277f 100644 --- a/src/TortoiseMerge/libsvn_diff/SVNLineDiff.cpp +++ b/src/TortoiseMerge/libsvn_diff/SVNLineDiff.cpp @@ -1,6 +1,6 @@ // TortoiseMerge - a Diff/Patch program -// Copyright (C) 2006-2008 - TortoiseSVN +// Copyright (C) 2006-2009, 2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -17,8 +17,6 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "stdafx.h" -#include -#include #include "SVNLineDiff.h" const svn_diff_fns_t SVNLineDiff::SVNLineDiff_vtable = @@ -36,16 +34,17 @@ const svn_diff_fns_t SVNLineDiff::SVNLineDiff_vtable = #define SVNLINEDIFF_CHARTYPE_SPACE 2 #define SVNLINEDIFF_CHARTYPE_OTHER 3 -typedef void (*LineParser)(LPCTSTR line, unsigned long lineLength, std::vector &tokens); +typedef void (*LineParser)(LPCTSTR line, apr_size_t lineLength, std::vector &tokens); void SVNLineDiff::ParseLineWords( - LPCTSTR line, unsigned long lineLength, std::vector &tokens) + LPCTSTR line, apr_size_t lineLength, std::vector &tokens) { std::wstring token; int prevCharType = SVNLINEDIFF_CHARTYPE_NONE; - for (unsigned long i = 0; i < lineLength; ++i) + tokens.reserve(lineLength/2); + for (apr_size_t i = 0; i < lineLength; ++i) { - int charType = + int charType = IsCharAlphaNumeric(line[i]) ? SVNLINEDIFF_CHARTYPE_ALPHANUMERIC : IsCharWhiteSpace(line[i]) ? SVNLINEDIFF_CHARTYPE_SPACE : SVNLINEDIFF_CHARTYPE_OTHER; @@ -67,10 +66,10 @@ void SVNLineDiff::ParseLineWords( } void SVNLineDiff::ParseLineChars( - LPCTSTR line, unsigned long lineLength, std::vector &tokens) + LPCTSTR line, apr_size_t lineLength, std::vector &tokens) { std::wstring token; - for (unsigned long i = 0; i < lineLength; ++i) + for (apr_size_t i = 0; i < lineLength; ++i) { token = line[i]; tokens.push_back(token); @@ -99,7 +98,7 @@ svn_error_t * SVNLineDiff::datasource_close(void * /*baton*/, svn_diff_datasourc } void SVNLineDiff::NextTokenWords( - apr_uint32_t* hash, void** token, unsigned long& linePos, const std::vector& tokens) + apr_uint32_t* hash, void** token, apr_size_t& linePos, const std::vector& tokens) { if (linePos < tokens.size()) { @@ -110,7 +109,7 @@ void SVNLineDiff::NextTokenWords( } void SVNLineDiff::NextTokenChars( - apr_uint32_t* hash, void** token, unsigned long& linePos, LPCTSTR line, unsigned long lineLength) + apr_uint32_t* hash, void** token, apr_size_t& linePos, LPCTSTR line, apr_size_t lineLength) { if (linePos < lineLength) { @@ -199,7 +198,7 @@ SVNLineDiff::~SVNLineDiff() svn_pool_destroy(m_pool); }; -bool SVNLineDiff::Diff(svn_diff_t **diff, LPCTSTR line1, int len1, LPCTSTR line2, int len2, bool bWordDiff) +bool SVNLineDiff::Diff(svn_diff_t **diff, LPCTSTR line1, apr_size_t len1, LPCTSTR line2, apr_size_t len2, bool bWordDiff) { if (m_subpool) svn_pool_clear(m_subpool); @@ -275,15 +274,13 @@ bool SVNLineDiff::IsCharWhiteSpace(TCHAR c) bool SVNLineDiff::ShowInlineDiff(svn_diff_t* diff) { svn_diff_t* tempdiff = diff; - int diffcounts = 0; - int origcounts = 0; + apr_size_t diffcounts = 0; apr_off_t origsize = 0; apr_off_t diffsize = 0; while (tempdiff) { if (tempdiff->type == svn_diff__type_common) { - origcounts++; origsize += tempdiff->original_length; } else @@ -294,5 +291,5 @@ bool SVNLineDiff::ShowInlineDiff(svn_diff_t* diff) } tempdiff = tempdiff->next; } - return (origcounts >= diffcounts) && (origsize > diffsize); + return (origsize > 0.5 * diffsize + 1.0 * diffcounts); // Multiplier values may be subject to individual preferences } diff --git a/src/TortoiseMerge/resource.h b/src/TortoiseMerge/resource.h dissimilarity index 99% index e0bcea9e955301d878a12359b933bfd8d4e6fec8..85fb14b28e9d63c271ab15f781b98aa4f45ec8a3 100644 GIT binary patch literal 31292 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 diff --git a/src/TortoiseMerge/stdafx.h b/src/TortoiseMerge/stdafx.h index 2ee6000bd..d6af83682 100644 --- a/src/TortoiseMerge/stdafx.h +++ b/src/TortoiseMerge/stdafx.h @@ -23,8 +23,6 @@ #include // MFC Collection templates and classes #include // Shell API -#include - #include // MFC support for Internet Explorer 4 Common Controls #ifndef _AFX_NO_AFXCMN_SUPPORT #include // MFC support for Windows Common Controls @@ -55,11 +53,22 @@ #include #pragma warning(pop) +#pragma warning(push) +#include "apr_general.h" +#include "svn_pools.h" +#include "svn_path.h" +#include "svn_wc.h" +#include "svn_utf.h" +#include "svn_config.h" +#include "svn_error_codes.h" +#pragma warning(pop) + #define USE_GDI_GRADIENT #define XMESSAGEBOX_APPREGPATH "Software\\TortoiseGitMerge\\" -#include "..\Utils\CrashReport.h" +#include "ProfilingInfo.h" +#include "CrashReport.h" #ifdef _WIN64 # define APP_X64_STRING "x64" diff --git a/src/TortoiseMerge/svninclude/SVNLineDiff.h b/src/TortoiseMerge/svninclude/SVNLineDiff.h index 56a7036f2..d5cbe9b3d 100644 --- a/src/TortoiseMerge/svninclude/SVNLineDiff.h +++ b/src/TortoiseMerge/svninclude/SVNLineDiff.h @@ -1,6 +1,6 @@ -// TortoiseMerge - a Diff/Patch program +// TortoiseGitMerge - a Diff/Patch program -// Copyright (C) 2006-2007 - TortoiseSVN +// Copyright (C) 2006-2007, 2011 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -31,7 +31,7 @@ public: SVNLineDiff(); ~SVNLineDiff(); - bool Diff(svn_diff_t** diff, LPCTSTR line1, int len1, LPCTSTR line2, int len2, bool bWordDiff); + bool Diff(svn_diff_t** diff, LPCTSTR line1, apr_size_t len1, LPCTSTR line2, apr_size_t len2, bool bWordDiff); /** Checks if we really should show inline diffs. * Inline diffs are only useful if the two lines are not * completely different but at least a little bit similar. @@ -45,11 +45,11 @@ private: apr_pool_t * m_pool; apr_pool_t * m_subpool; LPCTSTR m_line1; - unsigned long m_line1length; + apr_size_t m_line1length; LPCTSTR m_line2; - unsigned long m_line2length; - unsigned long m_line1pos; - unsigned long m_line2pos; + apr_size_t m_line2length; + apr_size_t m_line1pos; + apr_size_t m_line2pos; bool m_bWordDiff; @@ -63,12 +63,12 @@ private: static apr_uint32_t Adler32(apr_uint32_t checksum, const WCHAR *data, apr_size_t len); static void ParseLineWords( - LPCTSTR line, unsigned long lineLength, std::vector& tokens); + LPCTSTR line, apr_size_t lineLength, std::vector& tokens); static void ParseLineChars( - LPCTSTR line, unsigned long lineLength, std::vector& tokens); + LPCTSTR line, apr_size_t lineLength, std::vector& tokens); static void NextTokenWords( - apr_uint32_t* hash, void** token, unsigned long& linePos, const std::vector& tokens); + apr_uint32_t* hash, void** token, apr_size_t& linePos, const std::vector& tokens); static void NextTokenChars( - apr_uint32_t* hash, void** token, unsigned long& linePos, LPCTSTR line, unsigned long lineLength); + apr_uint32_t* hash, void** token, apr_size_t& linePos, LPCTSTR line, apr_size_t lineLength); static const svn_diff_fns_t SVNLineDiff_vtable; }; \ No newline at end of file diff --git a/src/Utils/MiscUI/FileDlgEventHandler.cpp b/src/Utils/MiscUI/FileDlgEventHandler.cpp new file mode 100644 index 000000000..aec607614 --- /dev/null +++ b/src/Utils/MiscUI/FileDlgEventHandler.cpp @@ -0,0 +1,92 @@ +// TortoiseGit - a Windows shell extension for easy version control + +// Copyright (C) 2010 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#include "stdafx.h" +#include "resource.h" +#include "FileDlgEventHandler.h" + +CFileDlgEventHandler::CFileDlgEventHandler() +{ +} + +CFileDlgEventHandler::~CFileDlgEventHandler() +{ +} + + +///////////////////////////////////////////////////////////////////////////// +// IFileDialogEvents methods + +STDMETHODIMP CFileDlgEventHandler::OnFileOk ( IFileDialog* /*pfd*/ ) +{ + return S_OK; // allow the dialog to close +} + +STDMETHODIMP CFileDlgEventHandler::OnFolderChanging ( IFileDialog* /*pfd*/, IShellItem* /*psiFolder*/ ) +{ + return S_OK; // allow the change +} + +STDMETHODIMP CFileDlgEventHandler::OnFolderChange ( IFileDialog* /*pfd*/ ) +{ + return S_OK; +} + +STDMETHODIMP CFileDlgEventHandler::OnSelectionChange ( IFileDialog* /*pfd*/ ) +{ + return S_OK; +} + +STDMETHODIMP CFileDlgEventHandler::OnShareViolation ( IFileDialog* /*pfd*/, IShellItem* /*psi*/, FDE_SHAREVIOLATION_RESPONSE* /*pResponse*/ ) +{ + return S_OK; +} + +STDMETHODIMP CFileDlgEventHandler::OnTypeChange ( IFileDialog* /*pfd*/ ) +{ + return S_OK; +} + +STDMETHODIMP CFileDlgEventHandler::OnOverwrite ( IFileDialog* /*pfd*/, IShellItem* /*psi*/, FDE_OVERWRITE_RESPONSE* /*pResponse*/ ) +{ + return S_OK; +} + + +///////////////////////////////////////////////////////////////////////////// +// IFileDialogControlEvents methods + +STDMETHODIMP CFileDlgEventHandler::OnItemSelected ( IFileDialogCustomize* /*pfdc*/, DWORD /*dwIDCtl*/, DWORD /*dwIDItem*/ ) +{ + return S_OK; +} + +STDMETHODIMP CFileDlgEventHandler::OnButtonClicked ( IFileDialogCustomize* /*pfdc*/, DWORD /*dwIDCtl*/ ) +{ + return S_OK; +} + +STDMETHODIMP CFileDlgEventHandler::OnCheckButtonToggled ( IFileDialogCustomize* /*pfdc*/, DWORD /*dwIDCtl*/, BOOL /*bChecked*/ ) +{ + return S_OK; +} + +STDMETHODIMP CFileDlgEventHandler::OnControlActivating ( IFileDialogCustomize* /*pfdc*/, DWORD /*dwIDCtl*/ ) +{ + return S_OK; +} diff --git a/src/Utils/MiscUI/FileDlgEventHandler.h b/src/Utils/MiscUI/FileDlgEventHandler.h new file mode 100644 index 000000000..9b0dd1dba --- /dev/null +++ b/src/Utils/MiscUI/FileDlgEventHandler.h @@ -0,0 +1,55 @@ +// TortoiseGit - a Windows shell extension for easy version control + +// Copyright (C) 2010 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#pragma once +#include + +/** + * helper class for IFileDialogEvents and IFileDialogControlEvents. + * use this class as a base class so you only need to implement + * the methods you actually need and not all of them. + */ +class CFileDlgEventHandler : public CComObjectRootEx, + public CComCoClass, + public IFileDialogEvents, + public IFileDialogControlEvents +{ +public: + CFileDlgEventHandler(); + ~CFileDlgEventHandler(); + + BEGIN_COM_MAP(CFileDlgEventHandler) + COM_INTERFACE_ENTRY(IFileDialogEvents) + COM_INTERFACE_ENTRY(IFileDialogControlEvents) + END_COM_MAP() + + // IFileDialogEvents + virtual STDMETHODIMP OnFileOk(IFileDialog* pfd); + virtual STDMETHODIMP OnFolderChanging(IFileDialog* pfd, IShellItem* psiFolder); + virtual STDMETHODIMP OnFolderChange(IFileDialog* pfd); + virtual STDMETHODIMP OnSelectionChange(IFileDialog* pfd); + virtual STDMETHODIMP OnShareViolation(IFileDialog* pfd, IShellItem* psi, FDE_SHAREVIOLATION_RESPONSE* pResponse); + virtual STDMETHODIMP OnTypeChange(IFileDialog* pfd); + virtual STDMETHODIMP OnOverwrite(IFileDialog* pfd, IShellItem* psi, FDE_OVERWRITE_RESPONSE* pResponse); + + // IFileDialogControlEvents + virtual STDMETHODIMP OnItemSelected(IFileDialogCustomize* pfdc, DWORD dwIDCtl, DWORD dwIDItem); + virtual STDMETHODIMP OnButtonClicked(IFileDialogCustomize* pfdc, DWORD dwIDCtl); + virtual STDMETHODIMP OnCheckButtonToggled(IFileDialogCustomize* pfdc, DWORD dwIDCtl, BOOL bChecked); + virtual STDMETHODIMP OnControlActivating(IFileDialogCustomize* pfdc, DWORD dwIDCtl); +}; diff --git a/src/Utils/MiscUI/LineColors.h b/src/Utils/MiscUI/LineColors.h new file mode 100644 index 000000000..ad6a2072c --- /dev/null +++ b/src/Utils/MiscUI/LineColors.h @@ -0,0 +1,72 @@ +// TortoiseGit - a Windows shell extension for easy version control + +// Copyright (C) 2010 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#pragma once +#include + + +struct linecolors_t +{ + COLORREF text; + COLORREF background; + COLORREF shot; +}; + +class LineColors : public std::map +{ +public: + void AddShotColor(int pos, COLORREF b) + { + // make sure position exists + SplitBlock(pos); + // set value + (*this)[pos].shot = b; + } + + void SetColor(int pos, COLORREF f, COLORREF b) + { + linecolors_t c; + c.text = f; + c.background = b; + c.shot = b; + (*this)[pos] = c; + } + + void SetColor(int pos, const linecolors_t &c) + { + linecolors_t cNew = c; + cNew.shot = c.background; + (*this)[pos] = cNew; + } + + void SplitBlock(int pos) /// insert colormark with same value as previous position defines + { + std::map::const_iterator it = this->upper_bound(pos); + if (it != this->begin()) + { + if ((it == this->end()) || (it->first != pos)) + { + SetColor(pos, (--it)->second); + } + } + else if (it != this->end()) + { + SetColor(pos, it->second); + } + } +}; diff --git a/src/Utils/MiscUI/TripleClick.h b/src/Utils/MiscUI/TripleClick.h new file mode 100644 index 000000000..f6bc000db --- /dev/null +++ b/src/Utils/MiscUI/TripleClick.h @@ -0,0 +1,82 @@ +// TortoiseGit - a Windows shell extension for easy version control + +// Copyright (C) 2011-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#pragma once + + +/** + * \ingroup Utils + * Helper to catch tripple mouse clicks. + */ +class CTripleClick +{ +public: + CTripleClick() + : m_LastDblClickMsg(0) + , m_LastDblClickTime(0) + { + } + virtual ~CTripleClick() {} + + virtual void OnLButtonTrippleClick(UINT /*nFlags*/, CPoint /*point*/) {} + virtual void OnMButtonTrippleClick(UINT /*nFlags*/, CPoint /*point*/) {} + virtual void OnRButtonTrippleClick(UINT /*nFlags*/, CPoint /*point*/) {} + + BOOL RelayTrippleClick(MSG* pMsg) + { + if ( (pMsg->message == WM_LBUTTONDBLCLK) || + (pMsg->message == WM_MBUTTONDBLCLK) || + (pMsg->message == WM_RBUTTONDBLCLK)) + { + m_LastDblClickMsg = pMsg->message; + m_LastDblClickTime = GetTickCount(); + } + else if ( + ((pMsg->message == WM_LBUTTONDOWN)&&(m_LastDblClickMsg == WM_LBUTTONDBLCLK)) || + ((pMsg->message == WM_MBUTTONDOWN)&&(m_LastDblClickMsg == WM_MBUTTONDBLCLK)) || + ((pMsg->message == WM_RBUTTONDOWN)&&(m_LastDblClickMsg == WM_RBUTTONDBLCLK)) + ) + { + if ((GetTickCount() - GetDoubleClickTime()) < m_LastDblClickTime) + { + m_LastDblClickTime = 0; + m_LastDblClickMsg = 0; + CPoint pt; + pt.x = GET_X_LPARAM(pMsg->lParam); + pt.y = GET_Y_LPARAM(pMsg->lParam); + switch (pMsg->message) + { + case WM_LBUTTONDOWN: + OnLButtonTrippleClick((UINT)pMsg->wParam, pt); + break; + case WM_MBUTTONDOWN: + OnMButtonTrippleClick((UINT)pMsg->wParam, pt); + break; + case WM_RBUTTONDOWN: + OnRButtonTrippleClick((UINT)pMsg->wParam, pt); + break; + } + return TRUE; + } + } + return FALSE; + } +private: + UINT m_LastDblClickMsg; + DWORD m_LastDblClickTime; +}; diff --git a/src/Utils/SelectFileFilter.h b/src/Utils/SelectFileFilter.h new file mode 100644 index 000000000..0e8a570ee --- /dev/null +++ b/src/Utils/SelectFileFilter.h @@ -0,0 +1,86 @@ +// TortoiseGit - a Windows shell extension for easy version control + +// Copyright (C) 2010-2012 - TortoiseSVN + +// 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, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#pragma once + +#include "StringUtils.h" + +class CSelectFileFilter { +public: + CSelectFileFilter(UINT stringId); + CSelectFileFilter() {} + ~CSelectFileFilter() {} + + operator const TCHAR*() { return buffer.get(); } + void Load(UINT stringId); + UINT GetCount() { return (UINT)filternames.size(); } + operator const COMDLG_FILTERSPEC*() { return filterspec.get(); } + +private: + std::unique_ptr buffer; + std::unique_ptr filterspec; + std::vector filternames; + std::vector filtermasks; + + void ResetAll(); +}; + +inline CSelectFileFilter::CSelectFileFilter(UINT stringId) +{ + Load(stringId); +} + +inline void CSelectFileFilter::Load(UINT stringId) +{ + ResetAll(); + CString sFilter; + sFilter.LoadString(stringId); + const int bufferLength = sFilter.GetLength()+4; + buffer.reset(new TCHAR[bufferLength]); + _tcscpy_s (buffer.get(), bufferLength, sFilter); + CStringUtils::PipesToNulls(buffer.get()); + + int pos = 0; + CString temp; + for (;;) + { + temp = sFilter.Tokenize(_T("|"), pos); + if (temp.IsEmpty()) + { + break; + } + filternames.push_back(temp); + temp = sFilter.Tokenize(_T("|"), pos); + filtermasks.push_back(temp); + } + filterspec.reset(new COMDLG_FILTERSPEC[filternames.size()]); + for (size_t i = 0; i < filternames.size(); ++i) + { + filterspec[i].pszName = filternames[i]; + filterspec[i].pszSpec = filtermasks[i]; + } +} + +inline void CSelectFileFilter::ResetAll() +{ + buffer.reset(); + // First release the struct that references the vectors, then clear the vectors + filterspec.reset(); + filternames.clear(); + filtermasks.clear(); +} diff --git a/src/TortoiseMerge/EditGotoDlg.h b/src/Utils/accHelper.cpp similarity index 62% rename from src/TortoiseMerge/EditGotoDlg.h rename to src/Utils/accHelper.cpp index 8b5cadf61..f70c53255 100644 --- a/src/TortoiseMerge/EditGotoDlg.h +++ b/src/Utils/accHelper.cpp @@ -1,6 +1,6 @@ // TortoiseGit - a Windows shell extension for easy version control -// Copyright (C) 2008-2012 - TortoiseGit +// Copyright (C) 2009 - TortoiseSVN // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -17,26 +17,13 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#pragma once +/* +To avoid linker errors with CLSIDs from oleacc.h, this file is needed +and has to be added to the project with the option "use precompiled headers" to "no". +Without this, oleacc.h is included already in stdafx.h by MFC and ATL headers without +the header included first. +*/ -// CEditGotoDlg dialog - -class CEditGotoDlg : public CDialog -{ - DECLARE_DYNAMIC(CEditGotoDlg) - -public: - CEditGotoDlg(CWnd* pParent = NULL); // standard constructor - virtual ~CEditGotoDlg(); - -// Dialog Data - enum { IDD = IDD_GOTODLG }; - -protected: - virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support - - DECLARE_MESSAGE_MAP() -public: - DWORD m_LineNumber; -}; +#include +#include -- 2.11.4.GIT