Remember "subfolder" as correctly spelled
[TortoiseGit.git] / src / TortoiseMerge / BaseView.cpp
blobd27c2412e4a844b18972ff2a40e866ca1e35a935
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2003-2021 - TortoiseSVN
4 // Copyright (C) 2011-2012, 2017-2024 TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "stdafx.h"
22 #include "registry.h"
23 #include "TortoiseMerge.h"
24 #include "MainFrm.h"
25 #include "BaseView.h"
26 #include "DiffColors.h"
27 #include "StringUtils.h"
28 #include "AppUtils.h"
29 #include "GotoLineDlg.h"
30 #include "EncodingDlg.h"
31 #include "EditorConfigWrapper.h"
32 #include "DPIAware.h"
33 #include "Theme.h"
34 #include "DarkModeHelper.h"
35 #include "ClipboardHelper.h"
37 // Note about lines:
38 // We use three different kind of lines here:
39 // 1. The real lines of the original files.
40 // These are shown in the view margin and are not used elsewhere, they're only for user information.
41 // 2. Screen lines.
42 // The lines actually shown on screen. All methods use screen lines as parameters/outputs if not explicitly specified otherwise.
43 // 3. View lines.
44 // These are the lines of the diff data. If unmodified sections are collapsed, not all of those lines are shown.
46 // Basically view lines are the line data, while screen lines are shown lines.
49 #ifdef _DEBUG
50 #define new DEBUG_NEW
51 #endif
53 #define HEADERHEIGHT (CDPIAware::Instance().ScaleY(GetSafeHwnd(), 10))
55 #define IDT_SCROLLTIMER 101
57 CBaseView* CBaseView::m_pwndLeft = nullptr;
58 CBaseView* CBaseView::m_pwndRight = nullptr;
59 CBaseView* CBaseView::m_pwndBottom = nullptr;
60 CLocatorBar* CBaseView::m_pwndLocator = nullptr;
61 CLineDiffBar* CBaseView::m_pwndLineDiffBar = nullptr;
62 CMFCStatusBar* CBaseView::m_pwndStatusBar = nullptr;
63 CMFCRibbonStatusBar* CBaseView::m_pwndRibbonStatusBar = nullptr;
64 CMainFrame* CBaseView::m_pMainFrame = nullptr;
65 CBaseView::Screen2View CBaseView::m_Screen2View;
66 const UINT CBaseView::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING);
68 allviewstate CBaseView::m_AllState;
70 IMPLEMENT_DYNCREATE(CBaseView, CView)
72 CBaseView::CBaseView()
74 m_bViewWhitespace = CRegDWORD(L"Software\\TortoiseGitMerge\\ViewWhitespaces", 1);
75 m_bViewLinenumbers = CRegDWORD(L"Software\\TortoiseGitMerge\\ViewLinenumbers", 1);
76 m_bShowInlineDiff = CRegDWORD(L"Software\\TortoiseGitMerge\\DisplayBinDiff", TRUE);
77 m_nInlineDiffMaxLineLength = CRegDWORD(L"Software\\TortoiseGitMerge\\InlineDiffMaxLineLength", 3000);
78 m_InlineAddedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\InlineAdded", INLINEADDED_COLOR);
79 m_InlineRemovedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\InlineRemoved", INLINEREMOVED_COLOR);
80 m_ModifiedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\ColorModifiedB", MODIFIED_COLOR);
81 m_InlineAddedDarkBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\DarkInlineAdded", INLINEADDED_DARK_COLOR);
82 m_InlineRemovedDarkBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\DarkInlineRemoved", INLINEREMOVED_DARK_COLOR);
83 m_ModifiedDarkBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\DarkColorModifiedB", MODIFIED_DARK_COLOR);
84 m_WhiteSpaceFg = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\Whitespace", CTheme::Instance().GetThemeColor(GetSysColor(COLOR_3DSHADOW)));
85 m_sWordSeparators = CRegString(L"Software\\TortoiseGitMerge\\WordSeparators", L"[]();:.,{}!@#$%^&*-+=|/\\<>'`~\"?");
86 m_bIconLFs = CRegDWORD(L"Software\\TortoiseGitMerge\\IconLFs", 0);
87 m_nTabSize = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabSize", 4));
88 m_nTabMode = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabMode", TABMODE_NONE));
89 m_bEditorConfigEnabled = !!static_cast<DWORD>(CRegDWORD(L"Software\\TortoiseGitMerge\\EnableEditorConfig", FALSE));
90 std::fill_n(m_apFonts, fontsCount, static_cast<CFont*>(nullptr));
92 int cxIcon = GetSystemMetrics(SM_CXSMICON);
93 int cyIcon = GetSystemMetrics(SM_CYSMICON);
94 m_hConflictedIcon = CCommonAppUtils::LoadIconEx(IDI_CONFLICTEDLINE, cxIcon, cyIcon);
95 m_hConflictedIgnoredIcon = CCommonAppUtils::LoadIconEx(IDI_CONFLICTEDIGNOREDLINE, cxIcon, cyIcon);
96 m_hRemovedIcon = CCommonAppUtils::LoadIconEx(IDI_REMOVEDLINE, cxIcon, cyIcon);
97 m_hAddedIcon = CCommonAppUtils::LoadIconEx(IDI_ADDEDLINE, cxIcon, cyIcon);
98 m_hWhitespaceBlockIcon = CCommonAppUtils::LoadIconEx(IDI_WHITESPACELINE, cxIcon, cyIcon);
99 m_hEqualIcon = CCommonAppUtils::LoadIconEx(IDI_EQUALLINE, cxIcon, cyIcon);
100 m_hLineEndingCR = CCommonAppUtils::LoadIconEx(IDI_LINEENDINGCR, cxIcon, cyIcon);
101 m_hLineEndingCRLF = CCommonAppUtils::LoadIconEx(IDI_LINEENDINGCRLF, cxIcon, cyIcon);
102 m_hLineEndingLF = CCommonAppUtils::LoadIconEx(IDI_LINEENDINGLF, cxIcon, cyIcon);
103 m_hEditedIcon = CCommonAppUtils::LoadIconEx(IDI_LINEEDITED, cxIcon, cyIcon);
104 m_hMovedIcon = CCommonAppUtils::LoadIconEx(IDI_MOVEDLINE, cxIcon, cyIcon);
105 m_hMarkedIcon = CCommonAppUtils::LoadIconEx(IDI_LINEMARKED, cxIcon, cyIcon);
106 m_margincursor = static_cast<HCURSOR>(LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_MARGINCURSOR), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE));
108 for (int i=0; i<1024; ++i)
109 m_sConflictedText += L"??";
110 m_sNoLineNr.LoadString(IDS_EMPTYLINETT);
112 m_szTip[0] = 0;
113 m_wszTip[0] = 0;
114 EnableToolTips();
116 m_Eols[static_cast<int>(EOL::LF)] = L"\n"; // x0a
117 m_Eols[static_cast<int>(EOL::CR)] = L"\r"; // x0d
118 m_Eols[static_cast<int>(EOL::CRLF)] = L"\r\n"; // x0d x0a
119 m_Eols[static_cast<int>(EOL::LFCR)] = L"\n\r";
120 m_Eols[static_cast<int>(EOL::VT)] = L"\v"; // x0b
121 m_Eols[static_cast<int>(EOL::FF)] = L"\f"; // x0c
122 m_Eols[static_cast<int>(EOL::NEL)] = L"\x85";
123 m_Eols[static_cast<int>(EOL::LS)] = L"\x2028";
124 m_Eols[static_cast<int>(EOL::PS)] = L"\x2029";
125 m_Eols[static_cast<int>(EOL::AutoLine)] = m_Eols[static_cast<int>(m_lineendings == EOL::AutoLine ? EOL::CRLF : m_lineendings)];
126 m_SaveParams.m_LineEndings = EOL::AutoLine;
127 m_SaveParams.m_UnicodeType = CFileTextLines::UnicodeType::AUTOTYPE;
129 m_themeCallbackId = CTheme::Instance().RegisterThemeChangeCallback([this]() { SetTheme(CTheme::Instance().IsDarkTheme()); });
132 CBaseView::~CBaseView()
134 CTheme::Instance().RemoveRegisteredCallback(m_themeCallbackId);
135 ReleaseBitmap();
136 DeleteFonts();
137 DestroyCursor(m_margincursor);
140 BEGIN_MESSAGE_MAP(CBaseView, CView)
141 ON_WM_VSCROLL()
142 ON_WM_HSCROLL()
143 ON_WM_ERASEBKGND()
144 ON_WM_CREATE()
145 ON_WM_DESTROY()
146 ON_WM_SIZE()
147 ON_WM_MOUSEWHEEL()
148 ON_WM_MOUSEHWHEEL()
149 ON_WM_SETCURSOR()
150 ON_WM_KILLFOCUS()
151 ON_WM_SETFOCUS()
152 ON_WM_CONTEXTMENU()
153 ON_COMMAND(ID_NAVIGATE_NEXTDIFFERENCE, OnMergeNextdifference)
154 ON_COMMAND(ID_NAVIGATE_PREVIOUSDIFFERENCE, OnMergePreviousdifference)
155 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
156 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
157 ON_WM_KEYDOWN()
158 ON_WM_LBUTTONDOWN()
159 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
160 ON_WM_MOUSEMOVE()
161 ON_COMMAND(ID_NAVIGATE_PREVIOUSCONFLICT, OnMergePreviousconflict)
162 ON_COMMAND(ID_NAVIGATE_NEXTCONFLICT, OnMergeNextconflict)
163 ON_WM_CHAR()
164 ON_COMMAND(ID_CARET_DOWN, &CBaseView::OnCaretDown)
165 ON_COMMAND(ID_CARET_LEFT, &CBaseView::OnCaretLeft)
166 ON_COMMAND(ID_CARET_RIGHT, &CBaseView::OnCaretRight)
167 ON_COMMAND(ID_CARET_UP, &CBaseView::OnCaretUp)
168 ON_COMMAND(ID_CARET_WORDLEFT, &CBaseView::OnCaretWordleft)
169 ON_COMMAND(ID_CARET_WORDRIGHT, &CBaseView::OnCaretWordright)
170 ON_COMMAND(ID_EDIT_CUT, &CBaseView::OnEditCut)
171 ON_COMMAND(ID_EDIT_PASTE, &CBaseView::OnEditPaste)
172 ON_WM_TIMER()
173 ON_WM_LBUTTONDBLCLK()
174 ON_COMMAND(ID_NAVIGATE_NEXTINLINEDIFF, &CBaseView::OnNavigateNextinlinediff)
175 ON_COMMAND(ID_NAVIGATE_PREVINLINEDIFF, &CBaseView::OnNavigatePrevinlinediff)
176 ON_COMMAND(ID_EDIT_SELECTALL, &CBaseView::OnEditSelectall)
177 ON_COMMAND(ID_EDIT_FIND, OnEditFind)
178 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
179 ON_COMMAND(ID_EDIT_FINDNEXT, OnEditFindnext)
180 ON_COMMAND(ID_EDIT_FINDPREV, OnEditFindprev)
181 ON_COMMAND(ID_EDIT_FINDNEXTSTART, OnEditFindnextStart)
182 ON_COMMAND(ID_EDIT_FINDPREVSTART, OnEditFindprevStart)
183 ON_COMMAND(ID_EDIT_GOTOLINE, &CBaseView::OnEditGotoline)
184 ON_WM_LBUTTONUP()
185 END_MESSAGE_MAP()
188 void CBaseView::DocumentUpdated()
190 ReleaseBitmap();
191 m_nLineHeight = -1;
192 m_nCharWidth = -1;
193 m_nScreenChars = -1;
194 m_nLastScreenChars = -1;
195 m_nMaxLineLength = -1;
196 m_nScreenLines = -1;
197 m_nTopLine = 0;
198 m_bModified = FALSE;
199 m_bOtherDiffChecked = false;
200 m_nDigits = 0;
201 m_nMouseLine = -1;
202 m_nLDownLine = -1;
203 m_nTabSize = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabSize", 4));
204 m_nTabMode = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabMode", TABMODE_NONE));
205 m_bViewLinenumbers = CRegDWORD(L"Software\\TortoiseGitMerge\\ViewLinenumbers", 1);
206 m_bIconLFs = CRegDWORD(L"Software\\TortoiseGitMerge\\IconLFs", 0);
207 m_nInlineDiffMaxLineLength = CRegDWORD(L"Software\\TortoiseGitMerge\\InlineDiffMaxLineLength", 3000);
208 m_Eols[static_cast<int>(EOL::AutoLine)] = m_Eols[static_cast<int>(m_lineendings == EOL::AutoLine ? EOL::CRLF : m_lineendings)];
209 SetEditorConfigEnabled(m_bEditorConfigEnabled);
210 DeleteFonts();
211 ClearCurrentSelection();
212 UpdateStatusBar();
213 SetTheme(CTheme::Instance().IsDarkTheme());
214 Invalidate();
217 void CBaseView::SetEditorConfigEnabled(bool bEditorConfigEnabled)
219 m_bEditorConfigEnabled = bEditorConfigEnabled;
220 m_nTabSize = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabSize", 4));
221 m_nTabMode = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabMode", TABMODE_NONE));
222 if (m_bEditorConfigEnabled)
224 m_bEditorConfigLoaded = FALSE; // no editorconfig entries loaded
225 CEditorConfigWrapper ec;
226 if (ec.Load(m_sReflectedName.IsEmpty() ? m_sFullFilePath : m_sReflectedName))
228 m_bEditorConfigLoaded = TRUE;
229 if (ec.m_nTabWidth != nullptr)
230 m_nTabSize = ec.m_nTabWidth;
231 if (ec.m_bIndentStyle != nullptr)
232 m_nTabMode = (m_nTabMode & ~TABMODE_USESPACES) | (ec.m_bIndentStyle ? TABMODE_USESPACES : TABMODE_NONE);
237 void CBaseView::DPIChanged()
239 ReleaseBitmap();
240 m_nLineHeight = -1;
241 m_nCharWidth = -1;
242 m_nScreenChars = -1;
243 m_nLastScreenChars = -1;
244 m_nMaxLineLength = -1;
245 m_nScreenLines = -1;
246 m_nDigits = 0;
247 DeleteFonts();
248 UpdateStatusBar();
249 SetTheme(CTheme::Instance().IsDarkTheme());
250 EnsureCaretVisible();
251 Invalidate();
254 static CString GetTabModeString(int nTabMode, int nTabSize, bool bEditorConfig)
256 CString text;
257 if (nTabMode & TABMODE_USESPACES)
258 text = L"Space";
259 else
260 text = L"Tab";
261 text.AppendFormat(L" %d", nTabSize);
262 if (nTabMode & TABMODE_SMARTINDENT)
263 text += L" Smart";
264 if (bEditorConfig)
265 text += L" EC";
266 return text;
269 void CBaseView::UpdateStatusBar()
271 int nRemovedLines = 0;
272 int nAddedLines = 0;
273 int nConflictedLines = 0;
275 if (m_pViewData)
277 for (int i=0; i<m_pViewData->GetCount(); i++)
279 DiffState state = m_pViewData->GetState(i);
280 switch (state)
282 case DiffState::Added:
283 case DiffState::IdenticalAdded:
284 case DiffState::TheirsAdded:
285 case DiffState::YoursAdded:
286 case DiffState::ConflictAdded:
287 nAddedLines++;
288 break;
289 case DiffState::IdenticalRemoved:
290 case DiffState::Removed:
291 case DiffState::TheirsRemoved:
292 case DiffState::YoursRemoved:
293 nRemovedLines++;
294 break;
295 case DiffState::Conflicted:
296 case DiffState::Conflicted_Ignored:
297 nConflictedLines++;
298 break;
303 CString sBarText;
304 CString sTemp;
306 if (m_pwndStatusBar)
308 sBarText += CFileTextLines::GetEncodingName(m_texttype);
309 sBarText += sBarText.IsEmpty() ? L"" : L" ";
310 sBarText += GetEolName(m_lineendings);
311 sBarText += sBarText.IsEmpty() ? L"" : L" ";
313 if (sBarText.IsEmpty())
314 sBarText += L" / ";
317 if (nRemovedLines)
319 sTemp.Format(IDS_STATUSBAR_REMOVEDLINES, nRemovedLines);
320 if (!sBarText.IsEmpty())
321 sBarText += L" / ";
322 sBarText += sTemp;
324 if (nAddedLines)
326 sTemp.Format(IDS_STATUSBAR_ADDEDLINES, nAddedLines);
327 if (!sBarText.IsEmpty())
328 sBarText += L" / ";
329 sBarText += sTemp;
331 if (nConflictedLines)
333 sTemp.Format(IDS_STATUSBAR_CONFLICTEDLINES, nConflictedLines);
334 if (!sBarText.IsEmpty())
335 sBarText += L" / ";
336 sBarText += sTemp;
338 if (m_pwndStatusBar || m_pwndRibbonStatusBar)
340 if (m_pwndStatusBar)
342 UINT nID;
343 UINT nStyle;
344 int cxWidth;
345 if (m_nStatusBarID == ID_INDICATOR_BOTTOMVIEW)
347 sBarText.Format(IDS_STATUSBAR_CONFLICTS, nConflictedLines);
349 if (m_nStatusBarID == ID_INDICATOR_LEFTVIEW)
351 sTemp.LoadString(IDS_STATUSBAR_LEFTVIEW);
352 sBarText = sTemp+sBarText;
354 if (m_nStatusBarID == ID_INDICATOR_RIGHTVIEW)
356 sTemp.LoadString(IDS_STATUSBAR_RIGHTVIEW);
357 sBarText = sTemp+sBarText;
359 int nIndex = m_pwndStatusBar->CommandToIndex(m_nStatusBarID);
360 m_pwndStatusBar->GetPaneInfo(nIndex, nID, nStyle, cxWidth);
361 //calculate the width of the text
362 CDC * pDC = m_pwndStatusBar->GetDC();
363 if (pDC)
365 CSize size = pDC->GetTextExtent(sBarText);
366 m_pwndStatusBar->SetPaneInfo(nIndex, nID, nStyle, size.cx+2);
367 ReleaseDC(pDC);
369 m_pwndStatusBar->SetPaneText(nIndex, sBarText);
371 else if (m_pwndRibbonStatusBar)
373 if (!IsViewGood(m_pwndBottom))
374 m_pwndRibbonStatusBar->RemoveElement(ID_INDICATOR_BOTTOMVIEW);
375 if ((m_nStatusBarID == ID_INDICATOR_BOTTOMVIEW) && (IsViewGood(this)))
377 m_pwndRibbonStatusBar->RemoveElement(ID_INDICATOR_BOTTOMVIEW);
378 auto apBtnGroupBottom = std::make_unique<CMFCRibbonButtonsGroup>();
379 apBtnGroupBottom->SetID(ID_INDICATOR_BOTTOMVIEW);
380 apBtnGroupBottom->AddButton(new CMFCRibbonStatusBarPane(ID_SEPARATOR, CString(MAKEINTRESOURCE(IDS_STATUSBAR_BOTTOMVIEW)), TRUE));
381 CMFCRibbonButton * pButton = new CMFCRibbonButton(ID_INDICATOR_BOTTOMVIEWCOMBOENCODING, L"");
382 m_pMainFrame->FillEncodingButton(pButton, ID_INDICATOR_BOTTOMENCODINGSTART);
383 apBtnGroupBottom->AddButton(pButton);
384 pButton = new CMFCRibbonButton(ID_INDICATOR_BOTTOMVIEWCOMBOEOL, L"");
385 m_pMainFrame->FillEOLButton(pButton, ID_INDICATOR_BOTTOMEOLSTART);
386 apBtnGroupBottom->AddButton(pButton);
387 pButton = new CMFCRibbonButton(ID_INDICATOR_BOTTOMVIEWCOMBOTABMODE, L"");
388 m_pMainFrame->FillTabModeButton(pButton, ID_INDICATOR_BOTTOMTABMODESTART);
389 apBtnGroupBottom->AddButton(pButton);
390 apBtnGroupBottom->AddButton(new CMFCRibbonStatusBarPane(ID_INDICATOR_BOTTOMVIEW, L"", TRUE));
391 m_pwndRibbonStatusBar->AddExtendedElement(apBtnGroupBottom.release(), L"");
394 CMFCRibbonButtonsGroup * pGroup = DYNAMIC_DOWNCAST(CMFCRibbonButtonsGroup, m_pwndRibbonStatusBar->FindByID(m_nStatusBarID));
395 if (pGroup)
397 CMFCRibbonStatusBarPane* pPane = DYNAMIC_DOWNCAST(CMFCRibbonStatusBarPane, pGroup->GetButton(4));
398 if (pPane)
400 pPane->SetText(sBarText);
402 CMFCRibbonButton * pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pGroup->GetButton(1));
403 if (pButton)
405 pButton->SetText(CFileTextLines::GetEncodingName(m_texttype));
406 pButton->SetDescription(CFileTextLines::GetEncodingName(m_texttype));
408 pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pGroup->GetButton(2));
409 if (pButton)
411 pButton->SetText(GetEolName(m_lineendings));
412 pButton->SetDescription(GetEolName(m_lineendings));
414 pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pGroup->GetButton(3));
415 if (pButton)
417 pButton->SetText(GetTabModeString(m_nTabMode, m_nTabSize, m_bEditorConfigEnabled && m_bEditorConfigLoaded));
418 pButton->SetDescription(GetTabModeString(m_nTabMode, m_nTabSize, m_bEditorConfigEnabled && m_bEditorConfigLoaded));
421 if (m_pwndRibbonStatusBar->GetSafeHwnd())
423 m_pwndRibbonStatusBar->RecalcLayout();
424 m_pwndRibbonStatusBar->Invalidate();
430 BOOL CBaseView::PreCreateWindow(CREATESTRUCT& cs)
432 if (!CView::PreCreateWindow(cs))
433 return FALSE;
435 cs.dwExStyle |= WS_EX_CLIENTEDGE;
436 cs.style &= ~WS_BORDER;
437 cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
438 ::LoadCursor(nullptr, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1), nullptr);
440 CWnd *pParentWnd = CWnd::FromHandlePermanent(cs.hwndParent);
441 if (!pParentWnd || !pParentWnd->IsKindOf(RUNTIME_CLASS(CSplitterWnd)))
443 // View must always create its own scrollbars,
444 // if only it's not used within splitter
445 cs.style |= (WS_HSCROLL | WS_VSCROLL);
447 cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS);
448 return TRUE;
451 CFont* CBaseView::GetFont(BOOL bItalic /*= FALSE*/, BOOL bBold /*= FALSE*/)
453 int nIndex = 0;
454 if (bBold)
455 nIndex |= 1;
456 if (bItalic)
457 nIndex |= 2;
458 if (!m_apFonts[nIndex])
460 m_apFonts[nIndex] = new CFont;
461 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
462 m_lfBaseFont.lfWeight = bBold ? FW_BOLD : FW_NORMAL;
463 m_lfBaseFont.lfItalic = static_cast<BYTE>(bItalic);
464 m_lfBaseFont.lfHeight = -CDPIAware::Instance().PointsToPixelsY(*this, static_cast<DWORD>(CRegDWORD(L"Software\\TortoiseGitMerge\\LogFontSize", 10)));
465 wcsncpy_s(m_lfBaseFont.lfFaceName, static_cast<LPCWSTR>(static_cast<CString>(CRegString(L"Software\\TortoiseGitMerge\\LogFontName", L"Consolas"))), _countof(m_lfBaseFont.lfFaceName) - 1);
466 if (!m_apFonts[nIndex]->CreateFontIndirect(&m_lfBaseFont))
468 delete m_apFonts[nIndex];
469 m_apFonts[nIndex] = nullptr;
470 return CView::GetFont();
473 return m_apFonts[nIndex];
476 void CBaseView::CalcLineCharDim()
478 CDC *pDC = GetDC();
479 if (!pDC)
480 return;
481 CFont *pOldFont = pDC->SelectObject(GetFont());
482 const CSize szCharExt = pDC->GetTextExtent(L"X");
483 pDC->SelectObject(pOldFont);
484 ReleaseDC(pDC);
486 m_nLineHeight = szCharExt.cy;
487 if (m_nLineHeight <= 0)
488 m_nLineHeight = -1;
489 m_nCharWidth = szCharExt.cx;
490 if (m_nCharWidth <= 0)
491 m_nCharWidth = -1;
494 int CBaseView::GetScreenChars()
496 if (m_nScreenChars == -1)
498 CRect rect;
499 GetClientRect(&rect);
500 m_nScreenChars = (rect.Width() - GetMarginWidth() - GetSystemMetrics(SM_CXVSCROLL)) / GetCharWidth();
501 if (m_nScreenChars < 0)
502 m_nScreenChars = 0;
504 return m_nScreenChars;
507 int CBaseView::GetAllMinScreenChars() const
509 int nChars = INT_MAX;
510 if (IsLeftViewGood())
511 nChars = std::min<int>(nChars, m_pwndLeft->GetScreenChars());
512 if (IsRightViewGood())
513 nChars = std::min<int>(nChars, m_pwndRight->GetScreenChars());
514 if (IsBottomViewGood())
515 nChars = std::min<int>(nChars, m_pwndBottom->GetScreenChars());
516 return (nChars==INT_MAX) ? 0 : nChars;
519 int CBaseView::GetAllMaxLineLength() const
521 int nLength = 0;
522 if (IsLeftViewGood())
523 nLength = std::max<int>(nLength, m_pwndLeft->GetMaxLineLength());
524 if (IsRightViewGood())
525 nLength = std::max<int>(nLength, m_pwndRight->GetMaxLineLength());
526 if (IsBottomViewGood())
527 nLength = std::max<int>(nLength, m_pwndBottom->GetMaxLineLength());
528 return nLength;
531 int CBaseView::GetLineHeight()
533 if (m_nLineHeight == -1)
534 CalcLineCharDim();
535 if (m_nLineHeight <= 0)
536 return 1;
537 return m_nLineHeight;
540 int CBaseView::GetCharWidth()
542 if (m_nCharWidth == -1)
543 CalcLineCharDim();
544 if (m_nCharWidth <= 0)
545 return 1;
546 return m_nCharWidth;
549 int CBaseView::GetMaxLineLength()
551 if (m_nMaxLineLength == -1)
553 m_nMaxLineLength = 0;
554 int nLineCount = GetLineCount();
555 if (nLineCount == 1)
557 m_nMaxLineLength = GetLineLengthWithTabsConverted(0);
558 return m_nMaxLineLength;
560 for (int i=0; i<nLineCount; i++)
562 int nActualLength = GetLineLengthWithTabsConverted(i);
563 if (m_nMaxLineLength < nActualLength)
564 m_nMaxLineLength = nActualLength;
567 return m_nMaxLineLength;
570 int CBaseView::GetLineLengthWithTabsConverted(int index)
572 if (!m_pViewData)
573 return 0;
574 if (m_pViewData->GetCount() == 0)
575 return 0;
576 if (m_Screen2View.size() <= index)
577 return 0;
578 CString sLine;
579 if (m_pMainFrame->m_bWrapLines)
580 sLine = GetLineChars(index);
581 else
583 int viewLine = GetViewLineForScreen(index);
584 sLine = m_pViewData->GetLine(viewLine);
586 int tabCount = 0;
587 const wchar_t* pChar = sLine;
588 auto nLineLength = sLine.GetLength();
589 for (int i = 0; i < nLineLength; ++i)
591 if (*pChar == '\t')
592 ++tabCount;
593 ++pChar;
595 // GetTabSize() - 1 because the tabs are already counted
596 nLineLength = nLineLength + (tabCount * (GetTabSize() - 1));
597 ASSERT(nLineLength >= 0);
598 return nLineLength;
601 int CBaseView::GetLineLength(int index)
603 if (!m_pViewData)
604 return 0;
605 if (m_pViewData->GetCount() == 0)
606 return 0;
607 if (m_Screen2View.size() <= index)
608 return 0;
609 int viewLine = GetViewLineForScreen(index);
610 if (m_pMainFrame->m_bWrapLines)
612 int nLineLength = GetLineChars(index).GetLength();
613 ASSERT(nLineLength >= 0);
614 return nLineLength;
616 int nLineLength = m_pViewData->GetLine(viewLine).GetLength();
617 ASSERT(nLineLength >= 0);
618 return nLineLength;
621 int CBaseView::GetViewLineLength(int nViewLine) const
623 if (!m_pViewData)
624 return 0;
625 if (m_pViewData->GetCount() <= nViewLine)
626 return 0;
627 int nLineLength = m_pViewData->GetLine(nViewLine).GetLength();
628 ASSERT(nLineLength >= 0);
629 return nLineLength;
632 int CBaseView::GetLineCount() const
634 if (!m_pViewData)
635 return 1;
636 int nLineCount = m_Screen2View.size();
637 ASSERT(nLineCount >= 0);
638 return nLineCount;
641 int CBaseView::GetSubLineOffset(int index)
643 return m_Screen2View.GetSubLineOffset(index);
646 CString CBaseView::GetViewLineChars(int nViewLine) const
648 if (!m_pViewData)
649 return 0;
650 if (m_pViewData->GetCount() <= nViewLine)
651 return 0;
652 return m_pViewData->GetLine(nViewLine);
655 CString CBaseView::GetLineChars(int index)
657 if (!m_pViewData)
658 return 0;
659 if (m_pViewData->GetCount() == 0)
660 return 0;
661 if (m_Screen2View.size() <= index)
662 return 0;
663 int viewLine = GetViewLineForScreen(index);
664 if (m_pMainFrame->m_bWrapLines)
666 int subLine = GetSubLineOffset(index);
667 if (subLine >= 0)
669 if (subLine < CountMultiLines(viewLine))
671 return m_ScreenedViewLine[viewLine].SubLines[subLine];
673 return L"";
676 return m_pViewData->GetLine(viewLine);
679 void CBaseView::CheckOtherView()
681 if (m_bOtherDiffChecked)
682 return;
683 // find out what the 'other' file is
684 m_pOtherViewData = nullptr;
685 m_pOtherView = nullptr;
686 if (this == m_pwndLeft && IsRightViewGood())
688 m_pOtherViewData = m_pwndRight->m_pViewData;
689 m_pOtherView = m_pwndRight;
692 if (this == m_pwndRight && IsLeftViewGood())
694 m_pOtherViewData = m_pwndLeft->m_pViewData;
695 m_pOtherView = m_pwndLeft;
698 m_bOtherDiffChecked = true;
702 void CBaseView::GetWhitespaceBlock(CViewData *viewData, int nLineIndex, int & nStartBlock, int & nEndBlock)
704 enum { MAX_WHITESPACEBLOCK_SIZE = 8 };
705 ASSERT(viewData);
707 DiffState origstate = viewData->GetState(nLineIndex);
709 // Go back and forward at most MAX_WHITESPACEBLOCK_SIZE lines to see where this block ends
710 nStartBlock = nLineIndex;
711 nEndBlock = nLineIndex;
712 while ((nStartBlock > 0) && (nStartBlock > (nLineIndex - MAX_WHITESPACEBLOCK_SIZE)))
714 DiffState state = viewData->GetState(nStartBlock - 1);
715 if ((origstate == DiffState::Empty) && (state != DiffState::Normal))
716 origstate = state;
717 if ((origstate == state) || (state == DiffState::Empty))
718 nStartBlock--;
719 else
720 break;
722 while ((nEndBlock < (viewData->GetCount() - 1)) && (nEndBlock < (nLineIndex + MAX_WHITESPACEBLOCK_SIZE)))
724 DiffState state = viewData->GetState(nEndBlock + 1);
725 if ((origstate == DiffState::Empty) && (state != DiffState::Normal))
726 origstate = state;
727 if ((origstate == state) || (state == DiffState::Empty))
728 nEndBlock++;
729 else
730 break;
734 CString CBaseView::GetWhitespaceString(CViewData *viewData, int nStartBlock, int nEndBlock)
736 enum { MAX_WHITESPACEBLOCK_SIZE = 8 };
738 int len = 0;
739 for (int i = nStartBlock; i <= nEndBlock; ++i)
740 len += viewData->GetLine(i).GetLength()+2;
742 CString block;
743 // do not check for whitespace blocks if the line is too long, because
744 // reserving a lot of memory here takes too much time (performance hog)
745 if (len > MAX_WHITESPACEBLOCK_SIZE*256)
746 return block;
747 block.Preallocate(len+1);
748 for (int i = nStartBlock; i <= nEndBlock; ++i)
750 block += viewData->GetLine(i);
751 block += m_Eols[static_cast<int>(viewData->GetLineEnding(i))];
753 return block;
756 bool CBaseView::IsBlockWhitespaceOnly(int nLineIndex, bool& bIdentical, int& blockstart, int& blockend)
758 if (!m_pViewData)
759 return false;
760 bIdentical = false;
761 CheckOtherView();
762 if (!m_pOtherViewData)
763 return false;
764 int viewLine = GetViewLineForScreen(nLineIndex);
765 if (
766 (m_pViewData->GetState(viewLine) == DiffState::Normal) &&
767 (m_pOtherViewData->GetLine(viewLine) == m_pViewData->GetLine(viewLine))
770 bIdentical = true;
771 return false;
773 // first check whether the line itself only has whitespace changes
774 CString mine = m_pViewData->GetLine(viewLine);
775 CString other = m_pOtherViewData->GetLine(min(viewLine, m_pOtherViewData->GetCount() - 1));
776 if (mine.IsEmpty() && other.IsEmpty())
778 bIdentical = true;
779 return false;
782 if (mine == other)
784 bIdentical = true;
785 return true;
787 FilterWhitespaces(mine, other);
788 if (mine == other)
789 return true;
791 int nStartBlock2, nEndBlock2;
792 GetWhitespaceBlock(m_pViewData, viewLine, blockstart, blockend);
793 GetWhitespaceBlock(m_pOtherViewData, min(viewLine, m_pOtherViewData->GetCount() - 1), nStartBlock2, nEndBlock2);
794 mine = GetWhitespaceString(m_pViewData, blockstart, blockend);
795 if (mine.IsEmpty())
796 bIdentical = false;
797 else
799 other = GetWhitespaceString(m_pOtherViewData, nStartBlock2, nEndBlock2);
800 bIdentical = mine == other;
801 FilterWhitespaces(mine, other);
804 return (!mine.IsEmpty()) && (mine == other);
807 bool CBaseView::IsViewLineHidden(int nViewLine)
809 return IsViewLineHidden(m_pViewData, nViewLine);
812 bool CBaseView::IsViewLineHidden(CViewData * pViewData, int nViewLine)
814 return m_pMainFrame->m_bCollapsed && (pViewData->GetHideState(nViewLine)!=HideState::Shown);
817 int CBaseView::GetLineNumber(int index) const
819 if (!m_pViewData)
820 return -1;
821 int viewLine = GetViewLineForScreen(index);
822 if (m_pViewData->GetLineNumber(viewLine)==DIFF_EMPTYLINENUMBER)
823 return -1;
824 return m_pViewData->GetLineNumber(viewLine);
827 int CBaseView::GetScreenLines()
829 if (m_nScreenLines == -1)
831 CRect rect;
832 GetClientRect(&rect);
833 SCROLLBARINFO sbi = { sizeof(sbi) };
834 if (GetScrollBarInfo(OBJID_HSCROLL, &sbi))
836 // only use the scroll bar size if the info is correct and the scrollbar is visible
837 // if anything isn't proper, assume the scrollbar has a size of zero
838 // and calculate the screen lines without it.
839 if (!(sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE) && !(sbi.rgstate[0] & STATE_SYSTEM_UNAVAILABLE))
841 int scrollBarHeight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top;
842 m_nScreenLines = (rect.Height() - HEADERHEIGHT - scrollBarHeight) / GetLineHeight();
843 if (m_nScreenLines < 0)
844 m_nScreenLines = 0;
845 return m_nScreenLines;
848 // if the scroll bar is not visible, unavailable or there was an error,
849 // assume the scroll bar height is zero and return the screen lines here.
850 // Of course, that means the cache (using the member variable) won't work
851 // and we fetch the scroll bar info every time. But in this case the overhead is necessary.
852 return (rect.Height() - HEADERHEIGHT) / GetLineHeight();
854 return m_nScreenLines;
857 int CBaseView::GetAllMinScreenLines() const
859 int nLines = INT_MAX;
860 if (IsLeftViewGood())
861 nLines = m_pwndLeft->GetScreenLines();
862 if (IsRightViewGood())
863 nLines = std::min<int>(nLines, m_pwndRight->GetScreenLines());
864 if (IsBottomViewGood())
865 nLines = std::min<int>(nLines, m_pwndBottom->GetScreenLines());
866 return (nLines == INT_MAX) || (nLines < 0) ? 0 : nLines;
869 int CBaseView::GetAllLineCount() const
871 int nLines = 0;
872 if (IsLeftViewGood())
873 nLines = m_pwndLeft->GetLineCount();
874 if (IsRightViewGood())
875 nLines = std::max<int>(nLines, m_pwndRight->GetLineCount());
876 if (IsBottomViewGood())
877 nLines = std::max<int>(nLines, m_pwndBottom->GetLineCount());
878 return nLines;
881 void CBaseView::RecalcAllVertScrollBars(BOOL bPositionOnly /*= FALSE*/)
883 if (IsLeftViewGood())
884 m_pwndLeft->RecalcVertScrollBar(bPositionOnly);
885 if (IsRightViewGood())
886 m_pwndRight->RecalcVertScrollBar(bPositionOnly);
887 if (IsBottomViewGood())
888 m_pwndBottom->RecalcVertScrollBar(bPositionOnly);
891 void CBaseView::RecalcVertScrollBar(BOOL bPositionOnly /*= FALSE*/)
893 SCROLLINFO si;
894 si.cbSize = sizeof(si);
895 if (bPositionOnly)
897 si.fMask = SIF_POS;
898 si.nPos = m_nTopLine;
900 else
902 EnableScrollBarCtrl(SB_VERT, TRUE);
903 if (GetAllMinScreenLines() >= GetAllLineCount() && m_nTopLine > 0)
905 m_nTopLine = 0;
906 Invalidate();
908 si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_POS | SIF_RANGE;
909 si.nMin = 0;
910 si.nMax = GetAllLineCount();
911 si.nPage = GetAllMinScreenLines();
912 si.nPos = m_nTopLine;
914 VERIFY(SetScrollInfo(SB_VERT, &si));
917 void CBaseView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
919 CView::OnVScroll(nSBCode, nPos, pScrollBar);
920 if (m_pwndLeft)
921 m_pwndLeft->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
922 if (m_pwndRight)
923 m_pwndRight->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
924 if (m_pwndBottom)
925 m_pwndBottom->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
926 if (m_pwndLocator)
927 m_pwndLocator->Invalidate();
930 void CBaseView::OnDoVScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/, CBaseView * master)
932 // Note we cannot use nPos because of its 16-bit nature
933 SCROLLINFO si;
934 si.cbSize = sizeof(si);
935 si.fMask = SIF_ALL;
936 VERIFY(master->GetScrollInfo(SB_VERT, &si));
938 int nPageLines = GetScreenLines();
939 int nLineCount = GetLineCount();
941 int nNewTopLine;
943 static LONG textwidth = 0;
944 static CString sFormat(MAKEINTRESOURCE(IDS_VIEWSCROLLTIPTEXT));
945 switch (nSBCode)
947 case SB_TOP:
948 nNewTopLine = 0;
949 break;
950 case SB_BOTTOM:
951 nNewTopLine = nLineCount - nPageLines + 1;
952 break;
953 case SB_LINEUP:
954 nNewTopLine = m_nTopLine - 1;
955 break;
956 case SB_LINEDOWN:
957 nNewTopLine = m_nTopLine + 1;
958 break;
959 case SB_PAGEUP:
960 nNewTopLine = m_nTopLine - si.nPage + 1;
961 break;
962 case SB_PAGEDOWN:
963 nNewTopLine = m_nTopLine + si.nPage - 1;
964 break;
965 case SB_THUMBPOSITION:
966 m_ScrollTool.Clear();
967 nNewTopLine = si.nTrackPos;
968 textwidth = 0;
969 break;
970 case SB_THUMBTRACK:
971 nNewTopLine = si.nTrackPos;
972 if (GetFocus() == this)
974 RECT thumbrect;
975 GetClientRect(&thumbrect);
976 ClientToScreen(&thumbrect);
978 POINT thumbpoint;
979 thumbpoint.x = thumbrect.right;
980 thumbpoint.y = thumbrect.top + ((thumbrect.bottom-thumbrect.top)*si.nTrackPos)/(si.nMax-si.nMin);
981 m_ScrollTool.Init(&thumbpoint);
982 if (textwidth == 0)
984 CString sTemp = sFormat;
985 sTemp.Format(sFormat, m_nDigits, 10*m_nDigits-1);
986 textwidth = m_ScrollTool.GetTextWidth(sTemp);
988 thumbpoint.x -= textwidth;
989 int line = GetLineNumber(nNewTopLine);
990 if (line >= 0)
991 m_ScrollTool.SetText(&thumbpoint, sFormat, m_nDigits, GetLineNumber(nNewTopLine)+1);
992 else
993 m_ScrollTool.SetText(&thumbpoint, m_sNoLineNr);
995 break;
996 default:
997 return;
1000 if (nNewTopLine < 0)
1001 nNewTopLine = 0;
1002 if (nNewTopLine >= nLineCount)
1003 nNewTopLine = nLineCount - 1;
1004 ScrollToLine(nNewTopLine);
1007 void CBaseView::RecalcAllHorzScrollBars(BOOL bPositionOnly /*= FALSE*/)
1009 if (IsLeftViewGood())
1010 m_pwndLeft->RecalcHorzScrollBar(bPositionOnly);
1011 if (IsRightViewGood())
1012 m_pwndRight->RecalcHorzScrollBar(bPositionOnly);
1013 if (IsBottomViewGood())
1014 m_pwndBottom->RecalcHorzScrollBar(bPositionOnly);
1017 void CBaseView::RecalcHorzScrollBar(BOOL bPositionOnly /*= FALSE*/)
1019 SCROLLINFO si;
1020 si.cbSize = sizeof(si);
1021 if (bPositionOnly)
1023 si.fMask = SIF_POS;
1024 si.nPos = m_nOffsetChar;
1026 else
1028 EnableScrollBarCtrl(SB_HORZ, !m_pMainFrame->m_bWrapLines);
1029 if (!m_pMainFrame->m_bWrapLines)
1031 int minScreenChars = GetAllMinScreenChars();
1032 int maxLineLength = GetAllMaxLineLength();
1033 if (minScreenChars >= maxLineLength && m_nOffsetChar > 0)
1035 m_nOffsetChar = 0;
1036 Invalidate();
1038 si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_POS | SIF_RANGE;
1039 si.nMin = 0;
1040 si.nMax = m_pMainFrame->m_bWrapLines ? minScreenChars : maxLineLength;
1041 si.nMax += GetMarginWidth()/GetCharWidth();
1042 si.nPage = GetScreenChars();
1043 si.nPos = m_nOffsetChar;
1046 VERIFY(SetScrollInfo(SB_HORZ, &si));
1049 void CBaseView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1051 CView::OnHScroll(nSBCode, nPos, pScrollBar);
1052 if (m_pwndLeft)
1053 m_pwndLeft->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1054 if (m_pwndRight)
1055 m_pwndRight->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1056 if (m_pwndBottom)
1057 m_pwndBottom->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1058 if (m_pwndLocator)
1059 m_pwndLocator->Invalidate();
1062 void CBaseView::OnDoHScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/, CBaseView * master)
1064 SCROLLINFO si;
1065 si.cbSize = sizeof(si);
1066 si.fMask = SIF_ALL;
1067 VERIFY(master->GetScrollInfo(SB_HORZ, &si));
1069 int nPageChars = GetScreenChars();
1070 int nMaxLineLength = GetMaxLineLength();
1072 int nNewOffset;
1073 switch (nSBCode)
1075 case SB_LEFT:
1076 nNewOffset = 0;
1077 break;
1078 case SB_BOTTOM:
1079 nNewOffset = nMaxLineLength - nPageChars + 1;
1080 break;
1081 case SB_LINEUP:
1082 nNewOffset = m_nOffsetChar - 1;
1083 break;
1084 case SB_LINEDOWN:
1085 nNewOffset = m_nOffsetChar + 1;
1086 break;
1087 case SB_PAGEUP:
1088 nNewOffset = m_nOffsetChar - si.nPage + 1;
1089 break;
1090 case SB_PAGEDOWN:
1091 nNewOffset = m_nOffsetChar + si.nPage - 1;
1092 break;
1093 case SB_THUMBPOSITION:
1094 case SB_THUMBTRACK:
1095 nNewOffset = si.nTrackPos;
1096 break;
1097 default:
1098 return;
1101 if (nNewOffset >= nMaxLineLength)
1102 nNewOffset = nMaxLineLength - 1;
1103 if (nNewOffset < 0)
1104 nNewOffset = 0;
1105 ScrollToChar(nNewOffset, TRUE);
1108 void CBaseView::ScrollToChar(int nNewOffsetChar, BOOL bTrackScrollBar /*= TRUE*/)
1110 if (m_nOffsetChar != nNewOffsetChar)
1112 int nScrollChars = m_nOffsetChar - nNewOffsetChar;
1113 m_nOffsetChar = nNewOffsetChar;
1114 CRect rcScroll;
1115 GetClientRect(&rcScroll);
1116 rcScroll.left += GetMarginWidth();
1117 rcScroll.top += GetLineHeight()+HEADERHEIGHT;
1118 ScrollWindow(nScrollChars * GetCharWidth(), 0, &rcScroll, &rcScroll);
1119 // update the view header
1120 rcScroll.left = 0;
1121 rcScroll.top = 0;
1122 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
1123 InvalidateRect(&rcScroll, FALSE);
1124 UpdateWindow();
1125 if (bTrackScrollBar)
1126 RecalcHorzScrollBar(TRUE);
1127 UpdateCaret();
1128 if (m_pwndLineDiffBar)
1129 m_pwndLineDiffBar->Invalidate();
1133 void CBaseView::ScrollAllToChar(int nNewOffsetChar, BOOL bTrackScrollBar /* = TRUE */)
1135 if (m_pwndLeft)
1136 m_pwndLeft->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1137 if (m_pwndRight)
1138 m_pwndRight->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1139 if (m_pwndBottom)
1140 m_pwndBottom->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1143 void CBaseView::ScrollAllSide(int delta)
1145 int nNewOffset = m_nOffsetChar;
1146 nNewOffset += delta;
1147 int nMaxLineLength = GetMaxLineLength();
1148 if (nNewOffset >= nMaxLineLength)
1149 nNewOffset = nMaxLineLength - 1;
1150 if (nNewOffset < 0)
1151 nNewOffset = 0;
1152 ScrollAllToChar(nNewOffset, TRUE);
1153 if (m_pwndLineDiffBar)
1154 m_pwndLineDiffBar->Invalidate();
1155 UpdateCaret();
1158 void CBaseView::ScrollSide(int delta)
1160 int nNewOffset = m_nOffsetChar;
1161 nNewOffset += delta;
1162 int nMaxLineLength = GetMaxLineLength();
1163 if (nNewOffset >= nMaxLineLength)
1164 nNewOffset = nMaxLineLength - 1;
1165 if (nNewOffset < 0)
1166 nNewOffset = 0;
1167 ScrollToChar(nNewOffset, TRUE);
1168 if (m_pwndLineDiffBar)
1169 m_pwndLineDiffBar->Invalidate();
1170 UpdateCaret();
1173 void CBaseView::ScrollVertical(short zDelta)
1175 const int nLineCount = GetLineCount();
1176 int nTopLine = m_nTopLine;
1177 nTopLine -= (zDelta/30);
1178 if (nTopLine < 0)
1179 nTopLine = 0;
1180 if (nTopLine >= nLineCount)
1181 nTopLine = nLineCount - 1;
1182 ScrollToLine(nTopLine, TRUE);
1185 void CBaseView::ScrollToLine(int nNewTopLine, BOOL bTrackScrollBar /*= TRUE*/)
1187 if (m_nTopLine != nNewTopLine)
1189 if (nNewTopLine < 0)
1190 nNewTopLine = 0;
1192 int nScrollLines = m_nTopLine - nNewTopLine;
1194 m_nTopLine = nNewTopLine;
1195 CRect rcScroll;
1196 GetClientRect(&rcScroll);
1197 rcScroll.top += GetLineHeight()+HEADERHEIGHT;
1198 ScrollWindow(0, nScrollLines * GetLineHeight(), &rcScroll, &rcScroll);
1199 UpdateWindow();
1200 if (bTrackScrollBar)
1201 RecalcVertScrollBar(TRUE);
1202 UpdateCaret();
1207 void CBaseView::DrawMargin(CDC *pdc, const CRect &rect, int nLineIndex)
1209 pdc->FillSolidRect(rect, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_SCROLLBAR)));
1211 if ((nLineIndex >= 0)&&(m_pViewData)&&(m_pViewData->GetCount()))
1213 int nViewLine = GetViewLineForScreen(nLineIndex);
1214 HICON icon = nullptr;
1215 ASSERT(nViewLine < static_cast<int>(m_ScreenedViewLine.size()));
1216 TScreenedViewLine::EIcon eIcon = m_ScreenedViewLine[nViewLine].eIcon;
1217 if (eIcon==TScreenedViewLine::ICN_UNKNOWN)
1219 DiffState state = m_pViewData->GetState(nViewLine);
1220 switch (state)
1222 case DiffState::Added:
1223 case DiffState::TheirsAdded:
1224 case DiffState::YoursAdded:
1225 case DiffState::IdenticalAdded:
1226 case DiffState::ConflictAdded:
1227 eIcon = TScreenedViewLine::ICN_ADD;
1228 break;
1229 case DiffState::Removed:
1230 case DiffState::TheirsRemoved:
1231 case DiffState::YoursRemoved:
1232 case DiffState::IdenticalRemoved:
1233 eIcon = TScreenedViewLine::ICN_REMOVED;
1234 break;
1235 case DiffState::Conflicted:
1236 eIcon = TScreenedViewLine::ICN_CONFLICT;
1237 break;
1238 case DiffState::Conflicted_Ignored:
1239 eIcon = TScreenedViewLine::ICN_CONFLICTIGNORED;
1240 break;
1241 case DiffState::Edited:
1242 eIcon = TScreenedViewLine::ICN_EDIT;
1243 break;
1244 default:
1245 break;
1247 bool bIdentical = false;
1248 int blockstart = -1;
1249 int blockend = -1;
1250 if ((state != DiffState::Edited)&&(IsBlockWhitespaceOnly(nLineIndex, bIdentical, blockstart, blockend)))
1252 if (bIdentical)
1253 eIcon = TScreenedViewLine::ICN_SAME;
1254 else
1255 eIcon = TScreenedViewLine::ICN_WHITESPACEDIFF;
1256 if (((blockstart >= 0) && (blockend >= 0)) && (blockstart < blockend))
1258 if (nViewLine > blockstart)
1259 Invalidate(); // redraw the upper icons since they're now changing
1260 while (blockstart <= blockend)
1261 m_ScreenedViewLine[blockstart++].eIcon = eIcon;
1264 if (m_pViewData->GetMovedIndex(nViewLine) >= 0)
1265 eIcon = TScreenedViewLine::ICN_MOVED;
1266 if (m_pViewData->GetMarked(nViewLine))
1267 eIcon = TScreenedViewLine::ICN_MARKED;
1268 m_ScreenedViewLine[nViewLine].eIcon = eIcon;
1270 switch (eIcon)
1272 case TScreenedViewLine::ICN_UNKNOWN:
1273 case TScreenedViewLine::ICN_NONE:
1274 break;
1275 case TScreenedViewLine::ICN_SAME:
1276 icon = m_hEqualIcon;
1277 break;
1278 case TScreenedViewLine::ICN_EDIT:
1279 icon = m_hEditedIcon;
1280 break;
1281 case TScreenedViewLine::ICN_WHITESPACEDIFF:
1282 icon = m_hWhitespaceBlockIcon;
1283 break;
1284 case TScreenedViewLine::ICN_ADD:
1285 icon = m_hAddedIcon;
1286 break;
1287 case TScreenedViewLine::ICN_CONFLICT:
1288 icon = m_hConflictedIcon;
1289 break;
1290 case TScreenedViewLine::ICN_CONFLICTIGNORED:
1291 icon = m_hConflictedIgnoredIcon;
1292 break;
1293 case TScreenedViewLine::ICN_REMOVED:
1294 icon = m_hRemovedIcon;
1295 break;
1296 case TScreenedViewLine::ICN_MOVED:
1297 icon = m_hMovedIcon;
1298 break;
1299 case TScreenedViewLine::ICN_MARKED:
1300 icon = m_hMarkedIcon;
1301 break;
1304 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1305 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1306 if (icon)
1308 ::DrawIconEx(pdc->m_hDC, rect.left + CDPIAware::Instance().ScaleX(GetSafeHwnd(), 2), rect.top + (rect.Height() - iconHeight) / 2, icon, iconWidth, iconHeight, 0, nullptr, DI_NORMAL);
1310 if ((m_bViewLinenumbers)&&(m_nDigits))
1312 int nSubLine = GetSubLineOffset(nLineIndex);
1313 bool bIsFirstSubline = (nSubLine == 0) || (nSubLine == -1);
1314 CString sLinenumber;
1315 if (bIsFirstSubline)
1317 CString sLinenumberFormat;
1318 int nLineNumber = GetLineNumber(nLineIndex);
1319 if (IsViewLineHidden(GetViewLineForScreen(nLineIndex)))
1321 // TODO: do not show if there is no number hidden
1322 // TODO: show number if there is only one
1323 sLinenumberFormat.Format(L"%%%ds", m_nDigits);
1324 sLinenumber.Format(sLinenumberFormat, (m_nDigits>1) ? L"↕⁞" : L"⁞"); // alternative …
1326 else if (nLineNumber >= 0)
1328 sLinenumberFormat.Format(L"%%%dd", m_nDigits);
1329 sLinenumber.Format(sLinenumberFormat, nLineNumber+1);
1331 else if (m_pMainFrame->m_bWrapLines)
1333 sLinenumberFormat.Format(L"%%%ds", m_nDigits);
1334 sLinenumber.Format(sLinenumberFormat, L"·");
1336 if (!sLinenumber.IsEmpty())
1338 pdc->SetBkColor(CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_SCROLLBAR)));
1339 pdc->SetTextColor(CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT));
1341 pdc->SelectObject(GetFont());
1342 pdc->ExtTextOut(rect.left + iconWidth + CDPIAware::Instance().ScaleX(GetSafeHwnd(), 2), rect.top, ETO_CLIPPED, &rect, sLinenumber, nullptr);
1349 int CBaseView::GetMarginWidth()
1351 int marginWidth = GetSystemMetrics(SM_CXSMICON) + CDPIAware::Instance().ScaleX(GetSafeHwnd(), 4);
1353 if ((m_bViewLinenumbers)&&(m_pViewData)&&(m_pViewData->GetCount()))
1355 if (m_nDigits <= 0)
1357 int nLength = m_pViewData->GetCount();
1358 // find out how many digits are needed to show the highest line number
1359 CString sMax;
1360 sMax.Format(L"%d", nLength);
1361 m_nDigits = sMax.GetLength();
1363 int nWidth = GetCharWidth();
1364 marginWidth += (m_nDigits * nWidth) + CDPIAware::Instance().ScaleX(GetSafeHwnd(), 2);
1367 return marginWidth;
1370 void CBaseView::DrawHeader(CDC *pdc, const CRect &rect)
1372 CRect textrect(rect.left, rect.top, rect.Width(), GetLineHeight()+HEADERHEIGHT);
1373 COLORREF crBk, crFg;
1374 if (IsBottomViewGood())
1376 CDiffColors::GetInstance().GetColors(DiffState::Normal, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBk, crFg);
1377 crBk = CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_SCROLLBAR));
1379 else
1381 DiffState state = DiffState::Removed;
1382 if (this == m_pwndRight)
1384 state = DiffState::Added;
1386 CDiffColors::GetInstance().GetColors(state, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBk, crFg);
1388 pdc->SetBkColor(crBk);
1389 pdc->FillSolidRect(textrect, crBk);
1391 pdc->SetTextColor(crFg);
1393 pdc->SelectObject(GetFont(FALSE, TRUE));
1395 CString sViewTitle;
1396 if (IsModified())
1398 sViewTitle = L"* " + m_sWindowName;
1400 else
1402 sViewTitle = m_sWindowName;
1404 int nStringLength = (GetCharWidth()*m_sWindowName.GetLength());
1405 if (nStringLength > rect.Width())
1407 int offset = std::min<int>(m_nOffsetChar, (nStringLength-rect.Width())/GetCharWidth()+1);
1408 sViewTitle = m_sWindowName.Mid(offset);
1410 RECT titleRC = textrect;
1411 titleRC.left = std::max<int>(rect.left + (rect.Width() - nStringLength) / 2, 1);
1412 titleRC.top = rect.top + (HEADERHEIGHT / 2);
1413 pdc->DrawText(sViewTitle, &titleRC, DT_HIDEPREFIX | DT_NOPREFIX | DT_SINGLELINE);
1414 if (this->GetFocus() == this)
1415 pdc->DrawEdge(textrect, EDGE_BUMP, BF_RECT);
1416 else
1417 pdc->DrawEdge(textrect, EDGE_ETCHED, BF_RECT);
1420 void CBaseView::OnDraw(CDC * pDC)
1422 CRect rcClient;
1423 GetClientRect(rcClient);
1425 int nLineCount = GetLineCount();
1426 int nLineHeight = GetLineHeight();
1428 CDC cacheDC;
1429 VERIFY(cacheDC.CreateCompatibleDC(pDC));
1430 if (!m_pCacheBitmap)
1432 m_pCacheBitmap = new CBitmap;
1433 VERIFY(m_pCacheBitmap->CreateCompatibleBitmap(pDC, rcClient.Width(), nLineHeight));
1435 CBitmap *pOldBitmap = cacheDC.SelectObject(m_pCacheBitmap);
1437 DrawHeader(pDC, rcClient);
1439 CRect rcLine;
1440 rcLine = rcClient;
1441 rcLine.top += nLineHeight+HEADERHEIGHT;
1442 rcLine.bottom = rcLine.top + nLineHeight;
1443 CRect rcCacheMargin(0, 0, GetMarginWidth(), nLineHeight);
1444 CRect rcCacheLine(GetMarginWidth(), 0, rcLine.Width(), nLineHeight);
1446 int nCurrentLine = m_nTopLine;
1447 bool bBeyondFileLineCached = false;
1448 while (rcLine.top < rcClient.bottom)
1450 if (nCurrentLine < nLineCount)
1452 DrawMargin(&cacheDC, rcCacheMargin, nCurrentLine);
1453 DrawSingleLine(&cacheDC, rcCacheLine, nCurrentLine);
1454 bBeyondFileLineCached = false;
1456 else if (!bBeyondFileLineCached)
1458 DrawMargin(&cacheDC, rcCacheMargin, -1);
1459 DrawSingleLine(&cacheDC, rcCacheLine, -1);
1460 bBeyondFileLineCached = true;
1463 VERIFY(pDC->BitBlt(rcLine.left, rcLine.top, rcLine.Width(), rcLine.Height(), &cacheDC, 0, 0, SRCCOPY));
1465 nCurrentLine ++;
1466 rcLine.OffsetRect(0, nLineHeight);
1469 cacheDC.SelectObject(pOldBitmap);
1470 cacheDC.DeleteDC();
1473 bool CBaseView::IsStateConflicted(DiffState state)
1475 switch (state)
1477 case DiffState::Conflicted:
1478 case DiffState::Conflicted_Ignored:
1479 case DiffState::ConflictEmpty:
1480 case DiffState::ConflictAdded:
1481 return true;
1483 return false;
1486 bool CBaseView::IsStateEmpty(DiffState state)
1488 switch (state)
1490 case DiffState::ConflictEmpty:
1491 case DiffState::Unknown:
1492 case DiffState::Empty:
1493 return true;
1495 return false;
1498 bool CBaseView::IsStateRemoved(DiffState state)
1500 switch (state)
1502 case DiffState::Removed:
1503 case DiffState::TheirsRemoved:
1504 case DiffState::YoursRemoved:
1505 case DiffState::IdenticalRemoved:
1506 return true;
1508 return false;
1511 DiffState CBaseView::ResolveState(DiffState state)
1513 if (IsStateConflicted(state))
1515 if (state == DiffState::ConflictEmpty)
1516 return DiffState::ConflictResolvedEmpty;
1517 else
1518 return DiffState::ConflictResolved;
1520 return state;
1524 bool CBaseView::IsLineEmpty(int nLineIndex)
1526 if (m_pViewData == 0)
1527 return FALSE;
1528 int nViewLine = GetViewLineForScreen(nLineIndex);
1529 return IsViewLineEmpty(nViewLine);
1532 bool CBaseView::IsViewLineEmpty(int nViewLine)
1534 if (m_pViewData == 0)
1535 return FALSE;
1536 const DiffState state = m_pViewData->GetState(nViewLine);
1537 return IsStateEmpty(state);
1540 bool CBaseView::IsLineRemoved(int nLineIndex)
1542 if (m_pViewData == 0)
1543 return FALSE;
1544 int nViewLine = GetViewLineForScreen(nLineIndex);
1545 return IsViewLineRemoved(nViewLine);
1548 bool CBaseView::IsViewLineRemoved(int nViewLine)
1550 if (m_pViewData == 0)
1551 return FALSE;
1552 const DiffState state = m_pViewData->GetState(nViewLine);
1553 return IsStateRemoved(state);
1556 COLORREF CBaseView::InlineDiffColor(int nLineIndex)
1558 if (m_bDark)
1559 return IsLineRemoved(nLineIndex) ? m_InlineRemovedDarkBk : m_InlineAddedDarkBk;
1560 return IsLineRemoved(nLineIndex) ? m_InlineRemovedBk : m_InlineAddedBk;
1563 COLORREF CBaseView::InlineViewLineDiffColor(int nViewLine)
1565 if (m_bDark)
1566 return IsViewLineRemoved(nViewLine) ? m_InlineRemovedDarkBk : m_InlineAddedDarkBk;
1567 return IsViewLineRemoved(nViewLine) ? m_InlineRemovedBk : m_InlineAddedBk;
1570 void CBaseView::DrawLineEnding(CDC *pDC, const CRect &rc, int nLineIndex, const CPoint& origin)
1572 if (origin.x < (rc.left - GetCharWidth() + 1))
1573 return;
1574 if (!(m_bViewWhitespace && m_pViewData && (nLineIndex >= 0) && (nLineIndex < GetLineCount())))
1575 return;
1576 int viewLine = GetViewLineForScreen(nLineIndex);
1577 EOL ending = m_pViewData->GetLineEnding(viewLine);
1578 if (m_bIconLFs)
1580 HICON hEndingIcon = nullptr;
1581 switch (ending)
1583 case EOL::CR:
1584 hEndingIcon = m_hLineEndingCR;
1585 break;
1586 case EOL::CRLF:
1587 hEndingIcon = m_hLineEndingCRLF;
1588 break;
1589 case EOL::LF:
1590 hEndingIcon = m_hLineEndingLF;
1591 break;
1592 default: return;
1594 // If EOL style has changed, color end-of-line markers as inline differences.
1596 m_bShowInlineDiff && m_pOtherViewData &&
1597 (viewLine < m_pOtherViewData->GetCount()) &&
1598 (ending != EOL::NoEnding) &&
1599 (ending != m_pOtherViewData->GetLineEnding(viewLine) &&
1600 (m_pOtherViewData->GetLineEnding(viewLine) != EOL::NoEnding))
1603 pDC->FillSolidRect(origin.x, origin.y, rc.Height(), rc.Height(), InlineDiffColor(nLineIndex));
1606 DrawIconEx(pDC->GetSafeHdc(), origin.x, origin.y, hEndingIcon, rc.Height(), rc.Height(), 0, nullptr, DI_NORMAL);
1608 else
1610 CPen pen(PS_SOLID, 0, CTheme::Instance().GetThemeColor(m_WhiteSpaceFg));
1611 CPen* oldpen = pDC->SelectObject(&pen);
1612 int yMiddle = origin.y + rc.Height() / 2;
1613 int xMiddle = origin.x + GetCharWidth() / 2;
1614 bool bMultiline = false;
1615 const auto onepix = CDPIAware::Instance().ScaleX(GetSafeHwnd(), 1);
1616 const auto twopix = CDPIAware::Instance().ScaleX(GetSafeHwnd(), 2);
1617 const auto fourpix = CDPIAware::Instance().ScaleX(GetSafeHwnd(), 4);
1618 const auto fivepix = CDPIAware::Instance().ScaleX(GetSafeHwnd(), 5);
1619 if (((int)m_Screen2View.size() > nLineIndex + 1) && (GetViewLineForScreen(nLineIndex + 1) == viewLine))
1621 if (GetLineLength(nLineIndex + 1))
1623 // multiline
1624 bMultiline = true;
1625 pDC->MoveTo(origin.x, yMiddle - twopix);
1626 pDC->LineTo(origin.x + GetCharWidth() - onepix, yMiddle - twopix);
1627 pDC->LineTo(origin.x + GetCharWidth() - onepix, yMiddle + twopix);
1628 pDC->LineTo(origin.x, yMiddle + twopix);
1630 else if (GetLineLength(nLineIndex) == 0)
1631 bMultiline = true;
1633 else if ((nLineIndex > 0) && (GetViewLineForScreen(nLineIndex - 1) == viewLine) && (GetLineLength(nLineIndex) == 0))
1634 bMultiline = true;
1636 if (!bMultiline)
1638 switch (ending)
1640 case EOL::AutoLine:
1641 case EOL::CRLF:
1642 // arrow from top to middle+2, then left
1643 pDC->MoveTo(origin.x + GetCharWidth() - onepix, rc.top + onepix);
1644 pDC->LineTo(origin.x + GetCharWidth() - onepix, yMiddle);
1645 case EOL::CR:
1646 // arrow from right to left
1647 pDC->MoveTo(origin.x + GetCharWidth() - onepix, yMiddle);
1648 pDC->LineTo(origin.x, yMiddle);
1649 pDC->LineTo(origin.x + fourpix, yMiddle + fourpix);
1650 pDC->MoveTo(origin.x, yMiddle);
1651 pDC->LineTo(origin.x + fourpix, yMiddle - fourpix);
1652 break;
1653 case EOL::LFCR:
1654 // from right-upper to left then down
1655 pDC->MoveTo(origin.x + GetCharWidth() - onepix, yMiddle - twopix);
1656 pDC->LineTo(xMiddle, yMiddle - twopix);
1657 pDC->LineTo(xMiddle, rc.bottom - onepix);
1658 pDC->LineTo(xMiddle + fourpix, rc.bottom - fivepix);
1659 pDC->MoveTo(xMiddle, rc.bottom - onepix);
1660 pDC->LineTo(xMiddle - fourpix, rc.bottom - fivepix);
1661 break;
1662 case EOL::LF:
1663 // arrow from top to bottom
1664 pDC->MoveTo(xMiddle, rc.top);
1665 pDC->LineTo(xMiddle, rc.bottom - onepix);
1666 pDC->LineTo(xMiddle + fourpix, rc.bottom - fivepix);
1667 pDC->MoveTo(xMiddle, rc.bottom - onepix);
1668 pDC->LineTo(xMiddle - fourpix, rc.bottom - fivepix);
1669 break;
1670 case EOL::FF: // Form Feed, U+000C
1671 case EOL::NEL: // Next Line, U+0085
1672 case EOL::LS: // Line Separator, U+2028
1673 case EOL::PS: // Paragraph Separator, U+2029
1674 // draw a horizontal line at the bottom of this line
1675 pDC->FillSolidRect(rc.left, rc.bottom - 1, rc.right, rc.bottom, CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT));
1676 pDC->MoveTo(origin.x + GetCharWidth() - 1, rc.bottom - GetCharWidth() - 2);
1677 pDC->LineTo(origin.x, rc.bottom - 2);
1678 pDC->LineTo(origin.x + 5, rc.bottom - 2);
1679 pDC->MoveTo(origin.x, rc.bottom - 2);
1680 pDC->LineTo(origin.x + 1, rc.bottom - 6);
1681 break;
1682 default: // other EOLs
1683 // arrow from top right to bottom left
1684 pDC->MoveTo(origin.x + GetCharWidth() - 1, rc.bottom - GetCharWidth());
1685 pDC->LineTo(origin.x, rc.bottom - 1);
1686 pDC->LineTo(origin.x + 5, rc.bottom - 2);
1687 pDC->MoveTo(origin.x, rc.bottom - 1);
1688 pDC->LineTo(origin.x + 1, rc.bottom - 6);
1689 break;
1690 case EOL::NoEnding:
1691 break;
1694 pDC->SelectObject(oldpen);
1698 void CBaseView::DrawBlockLine(CDC *pDC, const CRect &rc, int nLineIndex)
1700 if (!m_bShowSelection)
1701 return;
1703 int nSelBlockStart;
1704 int nSelBlockEnd;
1705 if (!GetViewSelection(nSelBlockStart, nSelBlockEnd))
1706 return;
1708 const int THICKNESS = 2;
1709 COLORREF rectcol = 0;
1710 if (m_bFocused)
1711 rectcol = CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT);
1712 else
1713 rectcol = CTheme::Instance().GetThemeColor(GetSysColor(COLOR_GRAYTEXT));
1715 int nViewLineIndex = GetViewLineForScreen(nLineIndex);
1716 int nSubLine = GetSubLineOffset(nLineIndex);
1717 bool bFirstLineOfViewLine = (nSubLine==0 || nSubLine==-1);
1718 if ((nViewLineIndex == nSelBlockStart) && bFirstLineOfViewLine)
1720 pDC->FillSolidRect(rc.left, rc.top, rc.Width(), THICKNESS, rectcol);
1723 bool bLastLineOfViewLine = (nLineIndex+1 == m_Screen2View.size()) || (GetViewLineForScreen(nLineIndex) != GetViewLineForScreen(nLineIndex+1));
1724 if ((nViewLineIndex == nSelBlockEnd) && bLastLineOfViewLine)
1726 pDC->FillSolidRect(rc.left, rc.bottom - THICKNESS, rc.Width(), THICKNESS, rectcol);
1730 void CBaseView::DrawTextLine(
1731 CDC * pDC, const CRect &rc, int nLineIndex, POINT& coords)
1733 ASSERT(nLineIndex < GetLineCount());
1734 int nViewLine = GetViewLineForScreen(nLineIndex);
1735 ASSERT(m_pViewData && (nViewLine < m_pViewData->GetCount()));
1737 CRgn rgn;
1738 rgn.CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
1739 pDC->SelectClipRgn(&rgn);
1740 SCOPE_EXIT { pDC->SelectClipRgn(nullptr); };
1742 LineColors lineCols = GetLineColors(nViewLine);
1744 CString sViewLine = GetViewLineChars(nViewLine);
1745 // mark selection
1746 if (m_bShowSelection && HasTextSelection())
1748 // has this line selection ?
1749 if ((m_ptSelectionViewPosStart.y <= nViewLine) && (nViewLine <= m_ptSelectionViewPosEnd.y))
1751 int nViewLineLength = sViewLine.GetLength();
1753 // first suppose the whole line is selected
1754 int selectedStart = 0;
1755 int selectedEnd = nViewLineLength;
1757 // the view line is partially selected
1758 if (m_ptSelectionViewPosStart.y == nViewLine)
1760 selectedStart = m_ptSelectionViewPosStart.x;
1763 if (m_ptSelectionViewPosEnd.y == nViewLine)
1765 selectedEnd = m_ptSelectionViewPosEnd.x;
1767 // apply selection coloring
1768 // First enforce start and end point
1769 lineCols.SplitBlock(selectedStart);
1770 lineCols.SplitBlock(selectedEnd);
1771 // change color of affected parts
1772 long intenseColorScale = m_bFocused ? 70 : 30;
1773 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(selectedStart);
1774 for ( ; it != lineCols.end() && it->first < selectedEnd; ++it)
1776 auto& second = it->second;
1777 COLORREF crBk = CAppUtils::IntenseColor(intenseColorScale, second.background);
1778 if (second.shot == second.background)
1779 second.shot = crBk;
1780 second.background = crBk;
1781 second.text = CAppUtils::IntenseColor(intenseColorScale, second.text);
1786 // TODO: remove duplicate from selection and mark
1787 if (!m_sMarkedWord.IsEmpty())
1789 int nMarkLength = m_sMarkedWord.GetLength();
1790 //int nViewLineLength = sViewLine.GetLength();
1791 const wchar_t* text = sViewLine;
1792 const wchar_t* findText = text;
1793 while ((findText = wcsstr(findText, static_cast<LPCWSTR>(m_sMarkedWord))) != 0)
1795 int nMarkStart = static_cast<int>(findText - text);
1796 int nMarkEnd = nMarkStart + nMarkLength;
1797 findText += nMarkLength;
1798 ECharGroup eLeft = GetCharGroup(sViewLine, nMarkStart - 1);
1799 ECharGroup eStart = GetCharGroup(sViewLine, nMarkStart);
1800 if (eLeft == eStart)
1801 continue;
1802 ECharGroup eRight = GetCharGroup(sViewLine, nMarkEnd);
1803 ECharGroup eEnd = GetCharGroup(sViewLine, nMarkEnd - 1);
1804 if (eRight == eEnd)
1805 continue;
1807 // First enforce start and end point
1808 lineCols.SplitBlock(nMarkStart);
1809 lineCols.SplitBlock(nMarkEnd);
1810 // change color of affected parts
1811 const long int nIntenseColorScale = 200;
1812 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(nMarkStart);
1813 for ( ; it != lineCols.end() && it->first < nMarkEnd; ++it)
1815 auto& second = it->second;
1816 COLORREF crBk = CAppUtils::IntenseColor(nIntenseColorScale, second.background);
1817 if (second.shot == second.background)
1818 second.shot = crBk;
1819 second.background = crBk;
1820 second.text = CAppUtils::IntenseColor(nIntenseColorScale, second.text);
1821 if (CTheme::Instance().IsDarkTheme())
1823 int bkGray = (static_cast<int>(GetRValue(crBk)) + GetGValue(crBk) + GetBValue(crBk)) / 3;
1824 int frGray = (static_cast<int>(GetRValue(second.text)) + GetGValue(second.text) + GetBValue(second.text)) / 3;
1825 if (abs(bkGray - frGray) < 100)
1826 second.text = CAppUtils::IntenseColor(nIntenseColorScale, second.text);
1831 if (!m_sFindText.IsEmpty())
1833 int nMarkStart = 0;
1834 int nMarkEnd = 0;
1835 int nStringPos = nMarkStart;
1836 CString searchLine = sViewLine;
1837 if (!m_bMatchCase)
1838 searchLine.MakeLower();
1839 while (StringFound(searchLine, SearchNext, nMarkStart, nMarkEnd)!=0)
1841 // First enforce start and end point
1842 lineCols.SplitBlock(nMarkStart+nStringPos);
1843 lineCols.SplitBlock(nMarkEnd+nStringPos);
1844 // change color of affected parts
1845 const long int nIntenseColorScale = 30;
1846 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(nMarkStart+nStringPos);
1847 for ( ; it != lineCols.end() && it->first < nMarkEnd + nStringPos; ++it)
1849 auto& second = it->second;
1850 COLORREF crBk = CAppUtils::IntenseColor(nIntenseColorScale, second.background);
1851 if (second.shot == second.background)
1852 second.shot = crBk;
1853 second.background = crBk;
1854 second.text = CAppUtils::IntenseColor(nIntenseColorScale, second.text);
1856 searchLine = searchLine.Mid(nMarkEnd);
1857 nStringPos += nMarkEnd;
1858 nMarkStart = 0;
1859 nMarkEnd = 0;
1863 // @ this point we may cache data for next line which may be same in wrapped mode
1865 int nTextOffset = 0;
1866 int nSubline = GetSubLineOffset(nLineIndex);
1867 for (int n=0; n<nSubline; n++)
1869 CString sLine = m_ScreenedViewLine[nViewLine].SubLines[n];
1870 nTextOffset += sLine.GetLength();
1873 CString sLine = GetLineChars(nLineIndex);
1874 int nLineLength = sLine.GetLength();
1875 CString sLineExp = ExpandChars(sLine);
1876 LPCWSTR textExp = sLineExp;
1877 //int nLineLengthExp = sLineExp.GetLength();
1878 int nStartExp = 0;
1879 int nLeft = coords.x;
1880 for (std::map<int, linecolors_t>::const_iterator itStart = lineCols.begin(); itStart != lineCols.end(); ++itStart)
1882 std::map<int, linecolors_t>::const_iterator itEnd = itStart;
1883 ++itEnd;
1884 int nStart = std::max<int>(0, itStart->first - nTextOffset);
1885 int nEnd = nLineLength;
1886 if (itEnd != lineCols.end())
1888 nEnd = std::min<int>(nEnd, itEnd->first - nTextOffset);
1890 int nBlockLength = nEnd - nStart;
1891 if (nBlockLength > 0 && nEnd>=0)
1893 auto& second = itStart->second;
1894 pDC->SetBkColor(second.background);
1895 pDC->SetTextColor(second.text);
1896 int nEndExp = CountExpandedChars(sLine, nEnd);
1897 int nTextLength = nEndExp - nStartExp;
1898 LPCWSTR p_zBlockText = textExp + nStartExp;
1899 SIZE Size;
1900 GetTextExtentPoint32(pDC->GetSafeHdc(), p_zBlockText, nTextLength, &Size); // falls time-2-tme
1901 int nRight = nLeft + Size.cx;
1902 if ((nRight > rc.left) && (nLeft < rc.right))
1904 // note: ExtTextOut has a limit for the length of the string. That limit is supposed
1905 // to be 8192, but that's not really true: I found that the limit (at least on my machine and a few others)
1906 // is 4094 (4095 doesn't work anymore).
1907 // So we limit the length here to that 4094 chars.
1908 // In case we're scrolled to the right, there's no need to draw the string
1909 // from way outside our window, so we also offset the drawing to the start of the window.
1910 // This reduces the string length as well.
1911 int offset = 0;
1912 int leftcoord = nLeft;
1913 if (nLeft < 0)
1915 int fit = nTextLength;
1916 auto posBuffer = std::make_unique<int[]>(fit);
1917 GetTextExtentExPoint(pDC->GetSafeHdc(), p_zBlockText, nTextLength, INT_MAX, &fit, posBuffer.get(), &Size);
1918 int lower = 0, upper = fit - 1;
1921 int middle = (upper + lower + 1) / 2;
1922 int width = posBuffer[middle];
1923 if (rc.left - nLeft < width)
1924 upper = middle - 1;
1925 else
1926 lower = middle;
1927 } while (lower < upper);
1929 offset = lower;
1930 nTextLength -= offset;
1931 leftcoord += lower > 0 ? posBuffer[lower - 1] : 0;
1934 RECT drawRC = rc;
1935 drawRC.left = leftcoord;
1936 drawRC.top = coords.y;
1937 pDC->DrawText(p_zBlockText + offset, min(nTextLength, 4094), &drawRC, DT_HIDEPREFIX | DT_NOPREFIX | DT_SINGLELINE);
1938 if ((second.shot != second.background) && (itStart->first == nStart + nTextOffset))
1939 pDC->FillSolidRect(nLeft - 1, rc.top, 1, rc.Height(), second.shot);
1941 nLeft = nRight;
1942 coords.x = nRight;
1943 nStartExp = nEndExp;
1948 void CBaseView::DrawSingleLine(CDC *pDC, const CRect &rc, int nLineIndex)
1950 if (nLineIndex >= GetLineCount())
1951 nLineIndex = -1;
1952 ASSERT(nLineIndex >= -1);
1954 if ((nLineIndex == -1) || !m_pViewData)
1956 // Draw line beyond the text
1957 COLORREF crBkgnd, crText;
1958 CDiffColors::GetInstance().GetColors(DiffState::Unknown, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBkgnd, crText);
1959 pDC->FillSolidRect(rc, crBkgnd);
1960 return;
1963 int viewLine = GetViewLineForScreen(nLineIndex);
1964 if (m_pMainFrame->m_bCollapsed)
1966 if (m_pViewData->GetHideState(viewLine) == HideState::Marker)
1968 COLORREF crBkgnd, crText;
1969 CDiffColors::GetInstance().GetColors(DiffState::Unknown, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBkgnd, crText);
1970 pDC->FillSolidRect(rc, crBkgnd);
1972 const int THICKNESS = 2;
1973 COLORREF rectcol = CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT);
1974 pDC->FillSolidRect(rc.left, rc.top + (rc.Height()/2), rc.Width(), THICKNESS, rectcol);
1975 pDC->SetTextColor(CTheme::Instance().GetThemeColor(GetSysColor(COLOR_GRAYTEXT)));
1976 pDC->SetBkColor(crBkgnd);
1977 CRect rect = rc;
1978 pDC->DrawText(L"{...}", &rect, DT_NOPREFIX|DT_SINGLELINE|DT_CENTER);
1979 return;
1983 DiffState diffState = m_pViewData->GetState(viewLine);
1984 COLORREF crBkgnd, crText;
1985 CDiffColors::GetInstance().GetColors(diffState, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBkgnd, crText);
1987 if (diffState == DiffState::Conflicted)
1989 // conflicted lines are shown without 'text' on them
1990 CRect rect = rc;
1991 pDC->FillSolidRect(rc, crBkgnd);
1992 // now draw some faint text patterns
1993 pDC->SetTextColor(CAppUtils::IntenseColor(130, crBkgnd));
1994 pDC->DrawText(m_sConflictedText, rect, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE);
1995 DrawBlockLine(pDC, rc, nLineIndex);
1996 return;
1999 CPoint origin(rc.left - m_nOffsetChar * GetCharWidth(), rc.top);
2000 CString sLine = GetLineChars(nLineIndex);
2001 if (sLine.IsEmpty())
2003 pDC->FillSolidRect(rc, crBkgnd);
2004 DrawBlockLine(pDC, rc, nLineIndex);
2005 DrawLineEnding(pDC, rc, nLineIndex, origin);
2006 return;
2009 CheckOtherView();
2011 // Draw the line
2013 pDC->SelectObject(GetFont(FALSE, FALSE));
2015 DrawTextLine(pDC, rc, nLineIndex, origin);
2017 // draw white space after the end of line
2018 CRect frect = rc;
2019 if (origin.x > frect.left)
2020 frect.left = origin.x;
2021 if (frect.right > frect.left)
2022 pDC->FillSolidRect(frect, crBkgnd);
2024 // draw the whitespace chars
2025 auto pszChars = static_cast<LPCWSTR>(sLine);
2026 if (m_bViewWhitespace)
2028 int xpos = 0;
2029 int nChars = 0;
2030 LPCWSTR pLastSpace = pszChars;
2031 int y = rc.top + (rc.bottom-rc.top)/2;
2032 xpos -= m_nOffsetChar * GetCharWidth();
2034 CPen pen(PS_SOLID, 0, CTheme::Instance().GetThemeColor(m_WhiteSpaceFg));
2035 const auto twopix = CDPIAware::Instance().ScaleX(GetSafeHwnd(), 2);
2036 const auto fourpixY = CDPIAware::Instance().ScaleY(GetSafeHwnd(), 4);
2037 const auto sixpixY = CDPIAware::Instance().ScaleX(GetSafeHwnd(), 5);
2038 while (*pszChars)
2040 switch (*pszChars)
2042 case '\t':
2044 xpos += pDC->GetTextExtent(pLastSpace, static_cast<int>(pszChars - pLastSpace)).cx;
2045 pLastSpace = pszChars + 1;
2046 // draw an arrow
2047 int nSpaces = GetTabSize() - nChars % GetTabSize();
2048 if (xpos + nSpaces * GetCharWidth() > 0)
2050 int xposreal = max(xpos, 0);
2051 if ((xposreal > 0) || (nSpaces > 0))
2053 CPen * oldPen = pDC->SelectObject(&pen);
2054 pDC->MoveTo(xposreal + rc.left + twopix, y);
2055 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - twopix, y);
2056 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - sixpixY, y - fourpixY);
2057 pDC->MoveTo((xpos + nSpaces * GetCharWidth()) + rc.left - twopix, y);
2058 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - sixpixY, y + fourpixY);
2059 pDC->SelectObject(oldPen);
2062 xpos += nSpaces * GetCharWidth();
2063 nChars += nSpaces;
2065 break;
2066 case ' ':
2068 xpos += pDC->GetTextExtent(pLastSpace, static_cast<int>(pszChars - pLastSpace)).cx;
2069 pLastSpace = pszChars + 1;
2070 if (xpos >= 0)
2072 const int cxWhitespace = twopix;
2073 const int cyWhitespace = twopix;
2074 // draw 2-logical pixel rectangle, like Scintilla editor.
2075 pDC->FillSolidRect(xpos + rc.left + GetCharWidth() / 2 - cxWhitespace/2, y, cxWhitespace, cyWhitespace, CTheme::Instance().GetThemeColor(m_WhiteSpaceFg));
2077 xpos += GetCharWidth();
2078 nChars++;
2080 break;
2081 default:
2082 nChars++;
2083 break;
2085 pszChars++;
2088 DrawBlockLine(pDC, rc, nLineIndex);
2089 if (origin.x >= rc.left)
2090 DrawLineEnding(pDC, rc, nLineIndex, origin);
2093 void CBaseView::ExpandChars(const CString &sLine, int nOffset, int nCount, CString &line)
2095 if (nCount <= 0)
2097 line.Empty();
2098 return;
2101 int nTabSize = GetTabSize();
2103 int nActualOffset = CountExpandedChars(sLine, nOffset);
2105 auto pszChars = static_cast<LPCWSTR>(sLine);
2106 pszChars += nOffset;
2107 int nLength = nCount;
2109 int nTabCount = 0;
2110 for (int i=0; i<nLength; i++)
2112 if (pszChars[i] == L'\t')
2113 nTabCount ++;
2116 LPWSTR pszBuf = line.GetBuffer(nLength + nTabCount * (nTabSize - 1) + 1);
2117 int nCurPos = 0;
2118 if (nTabCount > 0 || m_bViewWhitespace)
2120 for (int i=0; i<nLength; i++)
2122 if (pszChars[i] == L'\t')
2124 int nSpaces = nTabSize - (nActualOffset + nCurPos) % nTabSize;
2125 while (nSpaces > 0)
2127 pszBuf[nCurPos ++] = L' ';
2128 nSpaces --;
2131 else
2133 pszBuf[nCurPos] = pszChars[i];
2134 nCurPos ++;
2138 else
2140 memcpy(pszBuf, pszChars, sizeof(wchar_t) * nLength);
2141 nCurPos = nLength;
2143 pszBuf[nCurPos] = 0;
2144 line.ReleaseBuffer();
2147 CString CBaseView::ExpandChars(const CString &sLine, int nOffset)
2149 CString sRet;
2150 int nLength = sLine.GetLength();
2151 ExpandChars(sLine, nOffset, nLength, sRet);
2152 return sRet;
2155 int CBaseView::CountExpandedChars(const CString &sLine, int nLength)
2157 int nTabSize = GetTabSize();
2159 int nActualOffset = 0;
2160 for (int i=0; i<nLength; i++)
2162 if (sLine[i] == L'\t')
2163 nActualOffset += (nTabSize - nActualOffset % nTabSize);
2164 else
2165 nActualOffset ++;
2167 return nActualOffset;
2170 void CBaseView::ScrollAllToLine(int nNewTopLine, BOOL bTrackScrollBar)
2172 if (m_pwndLeft)
2173 m_pwndLeft->ScrollToLine(nNewTopLine, bTrackScrollBar);
2174 if (m_pwndRight)
2175 m_pwndRight->ScrollToLine(nNewTopLine, bTrackScrollBar);
2176 if (m_pwndBottom)
2177 m_pwndBottom->ScrollToLine(nNewTopLine, bTrackScrollBar);
2178 if (m_pwndLocator)
2179 m_pwndLocator->Invalidate();
2182 void CBaseView::GoToLine(int nNewLine, BOOL bAll)
2184 //almost the same as ScrollAllToLine, but try to put the line in the
2185 //middle of the view, not on top
2186 int nNewTopLine = nNewLine - GetScreenLines()/2;
2187 if (nNewTopLine < 0)
2188 nNewTopLine = 0;
2189 if (nNewTopLine >= m_Screen2View.size())
2190 nNewTopLine = m_Screen2View.size() - 1;
2191 if (bAll)
2192 ScrollAllToLine(nNewTopLine);
2193 else
2194 ScrollToLine(nNewTopLine);
2197 BOOL CBaseView::OnEraseBkgnd(CDC* /*pDC*/)
2199 return TRUE;
2202 int CBaseView::OnCreate(LPCREATESTRUCT lpCreateStruct)
2204 if (CView::OnCreate(lpCreateStruct) == -1)
2205 return -1;
2207 SecureZeroMemory(&m_lfBaseFont, sizeof(m_lfBaseFont));
2208 //lstrcpy(m_lfBaseFont.lfFaceName, L"Courier New");
2209 //lstrcpy(m_lfBaseFont.lfFaceName, L"FixedSys");
2210 m_lfBaseFont.lfHeight = 0;
2211 m_lfBaseFont.lfWeight = FW_NORMAL;
2212 m_lfBaseFont.lfItalic = FALSE;
2213 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
2214 m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
2215 m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
2216 m_lfBaseFont.lfQuality = DEFAULT_QUALITY;
2217 m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;
2219 return 0;
2222 void CBaseView::OnDestroy()
2224 CView::OnDestroy();
2225 DeleteFonts();
2226 ReleaseBitmap();
2229 void CBaseView::OnSize(UINT nType, int cx, int cy)
2231 CView::OnSize(nType, cx, cy);
2232 ReleaseBitmap();
2234 m_nScreenLines = -1;
2235 m_nScreenChars = -1;
2236 m_nCharWidth = -1;
2237 if (m_nLastScreenChars != GetScreenChars())
2239 auto oldCaretLine = m_ptCaretViewPos.y;
2240 m_nLastScreenChars = m_nScreenChars;
2241 BuildAllScreen2ViewVector();
2242 if (m_pMainFrame && m_pMainFrame->m_bWrapLines)
2244 ScrollToLine(oldCaretLine, false);
2245 EnsureCaretVisible();
2246 // if we're in wrap mode, the line wrapping most likely changed
2247 // and that means we have to redraw the whole window, not just the
2248 // scrolled part.
2249 Invalidate(FALSE);
2251 else
2253 // make sure the view header is redrawn
2254 CRect rcScroll;
2255 GetClientRect(&rcScroll);
2256 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
2257 InvalidateRect(&rcScroll, FALSE);
2260 else
2262 // make sure the view header is redrawn
2263 CRect rcScroll;
2264 GetClientRect(&rcScroll);
2265 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
2266 InvalidateRect(&rcScroll, FALSE);
2268 UpdateLocator();
2269 RecalcVertScrollBar();
2270 RecalcHorzScrollBar();
2272 UpdateCaret();
2275 BOOL CBaseView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
2277 if (m_pwndLeft)
2278 m_pwndLeft->OnDoMouseWheel(nFlags, zDelta, pt);
2279 if (m_pwndRight)
2280 m_pwndRight->OnDoMouseWheel(nFlags, zDelta, pt);
2281 if (m_pwndBottom)
2282 m_pwndBottom->OnDoMouseWheel(nFlags, zDelta, pt);
2283 if (m_pwndLocator)
2284 m_pwndLocator->Invalidate();
2285 return CView::OnMouseWheel(nFlags, zDelta, pt);
2288 void CBaseView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
2290 if (m_pwndLeft)
2291 m_pwndLeft->OnDoMouseHWheel(nFlags, zDelta, pt);
2292 if (m_pwndRight)
2293 m_pwndRight->OnDoMouseHWheel(nFlags, zDelta, pt);
2294 if (m_pwndBottom)
2295 m_pwndBottom->OnDoMouseHWheel(nFlags, zDelta, pt);
2296 if (m_pwndLocator)
2297 m_pwndLocator->Invalidate();
2300 void CBaseView::OnDoMouseWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/)
2302 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
2303 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
2305 if (bControl || bShift)
2307 if (m_pMainFrame->m_bWrapLines)
2308 return;
2309 // Ctrl-Wheel scrolls sideways
2310 ScrollSide(-zDelta/30);
2312 else
2314 ScrollVertical(zDelta);
2318 void CBaseView::OnDoMouseHWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/)
2320 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
2321 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
2323 if (bControl || bShift)
2325 // Ctrl-H-Wheel scrolls vertical
2326 ScrollVertical(zDelta);
2328 else
2330 if (m_pMainFrame->m_bWrapLines)
2331 return;
2332 // Ctrl-Wheel scrolls sideways
2333 ScrollSide(zDelta/30);
2337 BOOL CBaseView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
2339 if (nHitTest == HTCLIENT)
2341 if ((m_pViewData)&&(m_pMainFrame->m_bCollapsed))
2343 if (m_nMouseLine < m_Screen2View.size())
2345 if (m_nMouseLine >= 0)
2347 int viewLine = GetViewLineForScreen(m_nMouseLine);
2348 if (viewLine < m_pViewData->GetCount())
2350 if (m_pViewData->GetHideState(viewLine) == HideState::Marker)
2352 ::SetCursor(::LoadCursor(nullptr, IDC_HAND));
2353 return TRUE;
2359 if (m_mouseInMargin)
2361 ::SetCursor(m_margincursor);
2362 return TRUE;
2364 if (m_nMouseLine >= 0)
2366 ::SetCursor(::LoadCursor(nullptr, IDC_IBEAM)); // Set To Edit Cursor
2367 return TRUE;
2370 ::SetCursor(::LoadCursor(nullptr, IDC_ARROW)); // Set To Arrow Cursor
2371 return TRUE;
2373 return CView::OnSetCursor(pWnd, nHitTest, message);
2376 void CBaseView::OnKillFocus(CWnd* pNewWnd)
2378 CView::OnKillFocus(pNewWnd);
2379 m_bFocused = FALSE;
2380 UpdateCaret();
2381 Invalidate();
2384 void CBaseView::OnSetFocus(CWnd* pOldWnd)
2386 CView::OnSetFocus(pOldWnd);
2387 m_bFocused = TRUE;
2388 UpdateCaret();
2389 Invalidate();
2392 int CBaseView::GetLineFromPoint(CPoint point)
2394 ScreenToClient(&point);
2395 return (((point.y - HEADERHEIGHT) / GetLineHeight()) + m_nTopLine);
2398 void CBaseView::OnContextMenu(CPoint point, DiffState state)
2400 CRect rcClient;
2401 GetClientRect(rcClient);
2402 CRect textrect(rcClient.left, rcClient.top, rcClient.Width(), m_nLineHeight + HEADERHEIGHT);
2404 CRect borderrect(rcClient.left, rcClient.top + m_nLineHeight + HEADERHEIGHT, 0, rcClient.bottom);
2406 CPoint ptLocal = point;
2407 ScreenToClient(&ptLocal);
2409 if (textrect.PtInRect(ptLocal) || borderrect.PtInRect(ptLocal))
2411 // inside the header part of the view (showing the filename)
2412 if (IsViewGood(m_pwndBottom))
2414 CString temp;
2415 if (this == m_pwndLeft)
2417 CIconMenu popup;
2418 if (!popup.CreatePopupMenu())
2419 return;
2421 temp.LoadString(IDS_HEADER_DIFFLEFTTOBASE);
2422 popup.AppendMenu(MF_STRING | MF_ENABLED, 10, temp);
2423 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2424 if (cmd == 10)
2425 m_pMainFrame->DiffLeftToBase();
2427 if (this == m_pwndRight)
2429 CIconMenu popup;
2430 if (!popup.CreatePopupMenu())
2431 return;
2433 temp.LoadString(IDS_HEADER_DIFFRIGHTTOBASE);
2434 popup.AppendMenu(MF_STRING | MF_ENABLED, 10, temp);
2435 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2436 if (cmd == 10)
2437 m_pMainFrame->DiffRightToBase();
2440 return;
2443 if (!this->IsWindowVisible())
2444 return;
2446 CIconMenu popup;
2447 if (!popup.CreatePopupMenu())
2448 return;
2450 AddContextItems(popup, state);
2452 CMenu popupEols;
2453 CMenu popupUnicode;
2454 int nEncodingCommandBase = POPUPCOMMAND__LAST;
2455 int nEolCommandBase = nEncodingCommandBase+_countof(uctArray);
2456 if (IsWritable())
2458 CString temp;
2459 TWhitecharsProperties oWhites = GetWhitecharsProperties();
2460 temp.LoadString(IDS_EDIT_TAB2SPACE);
2461 popup.AppendMenu(MF_STRING | (oWhites.HasTabsToConvert ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_TABTOSPACES, temp);
2462 temp.LoadString(IDS_EDIT_SPACE2TAB);
2463 popup.AppendMenu(MF_STRING | (oWhites.HasSpacesToConvert ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_SPACESTOTABS, temp);
2464 temp.LoadString(IDS_EDIT_TRIM);
2465 popup.AppendMenu(MF_STRING | (oWhites.HasTrailWhiteChars ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_REMOVETRAILWHITES, temp);
2467 // add eol submenu
2468 if (!popupEols.CreatePopupMenu())
2469 return;
2471 EOL eEolType = GetLineEndings(oWhites.HasMixedEols);
2472 for (int i = 1; i < _countof(eolArray); i++)
2474 temp = GetEolName(eolArray[i]);
2475 bool bChecked = (eEolType == eolArray[i]);
2476 popupEols.AppendMenu(MF_STRING | MF_ENABLED | (bChecked ? MF_CHECKED : 0), nEolCommandBase+i, temp);
2479 temp.LoadString(IDS_VIEWCONTEXTMENU_EOL);
2480 popup.AppendMenuW(MF_POPUP | MF_ENABLED, reinterpret_cast<UINT_PTR>(popupEols.GetSafeHmenu()), temp);
2482 // add encoding submenu
2483 if (!popupUnicode.CreatePopupMenu())
2484 return;
2485 for (int i = 0; i < _countof(uctArray); i++)
2487 temp = CFileTextLines::GetEncodingName(uctArray[i]);
2488 bool bChecked = (m_texttype == uctArray[i]);
2489 popupUnicode.AppendMenu(MF_STRING | MF_ENABLED | (bChecked ? MF_CHECKED : 0), nEncodingCommandBase+i, temp);
2491 temp.LoadString(IDS_VIEWCONTEXTMENU_ENCODING);
2492 popup.AppendMenuW(MF_POPUP | MF_ENABLED, reinterpret_cast<UINT_PTR>(popupUnicode.GetSafeHmenu()), temp);
2496 CompensateForKeyboard(point);
2498 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2499 ResetUndoStep();
2500 if (cmd >= nEncodingCommandBase && (cmd < nEncodingCommandBase + static_cast<int>(_countof(uctArray))))
2502 SetTextType(uctArray[cmd-nEncodingCommandBase]);
2504 if (cmd >= nEolCommandBase && (cmd < nEolCommandBase + static_cast<int>(_countof(eolArray))))
2506 ReplaceLineEndings(eolArray[cmd-nEolCommandBase]);
2507 SaveUndoStep();
2509 switch (cmd)
2511 // 2-pane view commands; target is right view
2512 case POPUPCOMMAND_USELEFTBLOCK:
2513 m_pwndRight->UseLeftBlock();
2514 break;
2515 case POPUPCOMMAND_USELEFTFILE:
2516 m_pwndRight->UseLeftFile();
2517 break;
2518 case POPUPCOMMAND_USEBOTHLEFTFIRST:
2519 m_pwndRight->UseBothLeftFirst();
2520 break;
2521 case POPUPCOMMAND_USEBOTHRIGHTFIRST:
2522 m_pwndRight->UseBothRightFirst();
2523 break;
2524 case POPUPCOMMAND_MARKBLOCK:
2525 m_pwndRight->MarkBlock(true);
2526 break;
2527 case POPUPCOMMAND_UNMARKBLOCK:
2528 m_pwndRight->MarkBlock(false);
2529 break;
2530 case POPUPCOMMAND_LEAVEONLYMARKEDBLOCKS:
2531 m_pwndRight->LeaveOnlyMarkedBlocks();
2532 break;
2533 // 2-pane view multiedit commands; target is left view
2534 case POPUPCOMMAND_PREPENDFROMRIGHT:
2535 if (!m_pwndLeft->IsReadonly())
2536 m_pwndLeft->UseBothRightFirst();
2537 break;
2538 case POPUPCOMMAND_REPLACEBYRIGHT:
2539 if (!m_pwndLeft->IsReadonly())
2540 m_pwndLeft->UseRightBlock();
2541 break;
2542 case POPUPCOMMAND_APPENDFROMRIGHT:
2543 if (!m_pwndLeft->IsReadonly())
2544 m_pwndLeft->UseBothLeftFirst();
2545 break;
2546 case POPUPCOMMAND_USERIGHTFILE:
2547 m_pwndLeft->UseRightFile();
2548 break;
2549 // 3-pane view commands; target is bottom view
2550 case POPUPCOMMAND_USEYOURANDTHEIRBLOCK:
2551 m_pwndBottom->UseBothRightFirst();
2552 break;
2553 case POPUPCOMMAND_USETHEIRANDYOURBLOCK:
2554 m_pwndBottom->UseBothLeftFirst();
2555 break;
2556 case POPUPCOMMAND_USEYOURBLOCK:
2557 m_pwndBottom->UseRightBlock();
2558 break;
2559 case POPUPCOMMAND_USEYOURFILE:
2560 m_pwndBottom->UseRightFile();
2561 break;
2562 case POPUPCOMMAND_USETHEIRBLOCK:
2563 m_pwndBottom->UseLeftBlock();
2564 break;
2565 case POPUPCOMMAND_USETHEIRFILE:
2566 m_pwndBottom->UseLeftFile();
2567 break;
2568 // copy, cut and paste commands
2569 case ID_EDIT_COPY:
2570 OnEditCopy();
2571 break;
2572 case ID_EDIT_CUT:
2573 OnEditCut();
2574 break;
2575 case ID_EDIT_PASTE:
2576 OnEditPaste();
2577 break;
2578 // white chars manipulations
2579 case POPUPCOMMAND_TABTOSPACES:
2580 ConvertTabToSpaces();
2581 break;
2582 case POPUPCOMMAND_SPACESTOTABS:
2583 Tabularize();
2584 break;
2585 case POPUPCOMMAND_REMOVETRAILWHITES:
2586 RemoveTrailWhiteChars();
2587 break;
2588 default:
2589 return;
2590 } // switch (cmd)
2591 SaveUndoStep(); // all except copy, cut paste save undo step, but this should not be harmful as step is empty already and thus not saved
2592 return;
2595 void CBaseView::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
2597 if (!m_pViewData)
2598 return;
2600 int nViewBlockStart = -1;
2601 int nViewBlockEnd = -1;
2602 GetViewSelection(nViewBlockStart, nViewBlockEnd);
2603 if ((point.x != -1) && (point.y != -1))
2605 int nLine = GetLineFromPoint(point)-1;
2606 if ((nLine >= 0) && (nLine < m_Screen2View.size()))
2608 int nViewLine = GetViewLineForScreen(nLine);
2609 if (((nViewLine < nViewBlockStart) || (nViewBlockEnd < nViewLine)))
2611 ClearSelection(); // Clear text-copy selection
2613 nViewBlockStart = nViewLine;
2614 nViewBlockEnd = nViewLine;
2615 DiffState state = m_pViewData->GetState(nViewLine);
2616 while (nViewBlockStart > 0)
2618 const DiffState lineState = m_pViewData->GetState(nViewBlockStart - 1);
2619 if (!LinesInOneChange(-1, state, lineState))
2620 break;
2621 nViewBlockStart--;
2624 while (nViewBlockEnd < (m_pViewData->GetCount()-1))
2626 const DiffState lineState = m_pViewData->GetState(nViewBlockEnd + 1);
2627 if (!LinesInOneChange(1, state, lineState))
2628 break;
2629 nViewBlockEnd++;
2632 SetupAllViewSelection(nViewBlockStart, nViewBlockEnd);
2633 UpdateCaretPosition(SetupPoint(0, nViewLine));
2638 // FixSelection(); fix selection range
2639 /*if (m_nSelBlockEnd >= m_pViewData->GetCount())
2640 m_nSelBlockEnd = m_pViewData->GetCount()-1;//*/
2642 DiffState state = DiffState::Unknown;
2643 if (GetViewSelection(nViewBlockStart, nViewBlockEnd))
2645 // find a more 'relevant' state in the selection
2646 for (int i=nViewBlockStart; i<=nViewBlockEnd; ++i)
2648 state = m_pViewData->GetState(i);
2649 if ((state != DiffState::Normal) && (state != DiffState::Unknown))
2650 break;
2653 OnContextMenu(point, state);
2656 void CBaseView::RefreshViews()
2658 if (m_pwndLeft)
2660 m_pwndLeft->UpdateStatusBar();
2661 m_pwndLeft->UpdateCaret();
2662 m_pwndLeft->Invalidate();
2664 if (m_pwndRight)
2666 m_pwndRight->UpdateStatusBar();
2667 m_pwndRight->UpdateCaret();
2668 m_pwndRight->Invalidate();
2670 if (m_pwndBottom)
2672 m_pwndBottom->UpdateStatusBar();
2673 m_pwndBottom->UpdateCaret();
2674 m_pwndBottom->Invalidate();
2676 if (m_pwndLocator)
2677 m_pwndLocator->Invalidate();
2680 void CBaseView::GoToFirstDifference()
2682 SetCaretToFirstViewLine();
2683 SelectNextBlock(1, false, false);
2686 void CBaseView::GoToFirstConflict()
2688 SetCaretToFirstViewLine();
2689 SelectNextBlock(1, true, false);
2692 void CBaseView::HighlightViewLines(int nStart, int nEnd /* = -1 */)
2694 ClearSelection();
2695 SetupAllViewSelection(nStart, max(nStart, nEnd));
2697 UpdateCaretViewPosition(SetupPoint(0, nStart));
2698 Invalidate();
2701 void CBaseView::SetupAllViewSelection(int start, int end)
2703 SetupViewSelection(m_pwndBottom, start, end);
2704 SetupViewSelection(m_pwndLeft, start, end);
2705 SetupViewSelection(m_pwndRight, start, end);
2708 void CBaseView::SetupAllSelection(int start, int end)
2710 SetupAllViewSelection(GetViewLineForScreen(start), GetViewLineForScreen(end));
2713 //void CBaseView::SetupSelection(CBaseView* view, int start, int end) { }
2715 void CBaseView::SetupSelection(int start, int end)
2717 SetupViewSelection(GetViewLineForScreen(start), GetViewLineForScreen(end));
2720 void CBaseView::SetupViewSelection(CBaseView* view, int start, int end)
2722 if (!IsViewGood(view))
2723 return;
2724 view->SetupViewSelection(start, end);
2727 void CBaseView::SetupViewSelection(int start, int end)
2729 // clear text selection before setting line selection ?
2730 m_nSelViewBlockStart = start;
2731 m_nSelViewBlockEnd = end;
2732 Invalidate();
2736 void CBaseView::OnMergePreviousconflict()
2738 SelectNextBlock(-1, true);
2741 void CBaseView::OnMergeNextconflict()
2743 SelectNextBlock(1, true);
2746 void CBaseView::OnMergeNextdifference()
2748 SelectNextBlock(1, false);
2751 void CBaseView::OnMergePreviousdifference()
2753 SelectNextBlock(-1, false);
2756 bool CBaseView::HasNextConflict()
2758 return SelectNextBlock(1, true, true, true);
2761 bool CBaseView::HasPrevConflict()
2763 return SelectNextBlock(-1, true, true, true);
2766 bool CBaseView::HasNextDiff()
2768 return SelectNextBlock(1, false, true, true);
2771 bool CBaseView::HasPrevDiff()
2773 return SelectNextBlock(-1, false, true, true);
2776 bool CBaseView::LinesInOneChange(int direction,
2777 DiffState initialLineState, DiffState currentLineState)
2779 // Checks whether all the adjacent lines starting from the initial line
2780 // and up to the current line form the single change
2782 // First of all, if the two lines have identical states, they surely
2783 // belong to one change.
2784 if (initialLineState == currentLineState)
2785 return true;
2787 // Either we move down and initial line state is "added" or "removed" and
2788 // current line state is "empty"...
2789 if (direction > 0)
2791 if (currentLineState == DiffState::Empty)
2793 if (initialLineState == DiffState::Added || initialLineState == DiffState::Removed)
2794 return true;
2796 if (initialLineState == DiffState::ConflictAdded && currentLineState == DiffState::ConflictEmpty)
2797 return true;
2799 // ...or we move up and initial line state is "empty" and current line
2800 // state is "added" or "removed".
2801 if (direction < 0)
2803 if (initialLineState == DiffState::Empty)
2805 if (currentLineState == DiffState::Added || currentLineState == DiffState::Removed)
2806 return true;
2808 if (initialLineState == DiffState::ConflictEmpty && currentLineState == DiffState::ConflictAdded)
2809 return true;
2811 return false;
2814 bool CBaseView::SelectNextBlock(int nDirection, bool bConflict, bool bSkipEndOfCurrentBlock /* = true */, bool dryrun /* = false */)
2816 if (! m_pViewData)
2817 return false;
2819 const int linesCount = m_Screen2View.size();
2820 if(linesCount == 0)
2821 return false;
2823 int nCenterPos = GetCaretPosition().y;
2824 int nLimit = -1;
2825 if (nDirection > 0)
2826 nLimit = linesCount;
2828 if (nCenterPos >= linesCount)
2829 nCenterPos = linesCount-1;
2831 if (bSkipEndOfCurrentBlock)
2833 // Find end of current block
2834 const DiffState state = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2835 while (nCenterPos != nLimit)
2837 const DiffState lineState = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2838 if (!LinesInOneChange(nDirection, state, lineState))
2839 break;
2840 nCenterPos += nDirection;
2844 // Find next diff/conflict block
2845 while (nCenterPos != nLimit)
2847 DiffState linestate = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2848 if (!bConflict &&
2849 (linestate != DiffState::Normal) &&
2850 (linestate != DiffState::Unknown) &&
2851 (linestate != DiffState::FilteredDiff))
2853 break;
2855 if (bConflict &&
2856 ((linestate == DiffState::ConflictAdded) ||
2857 (linestate == DiffState::Conflicted_Ignored) ||
2858 (linestate == DiffState::Conflicted) ||
2859 (linestate == DiffState::ConflictEmpty)))
2861 break;
2864 nCenterPos += nDirection;
2866 if (nCenterPos == nLimit)
2867 return false;
2868 if (dryrun)
2869 return (nCenterPos != nLimit);
2871 // Find end of new block
2872 DiffState state = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2873 int nBlockEnd = nCenterPos;
2874 const int maxAllowedLine = nLimit-nDirection;
2875 while (nBlockEnd != maxAllowedLine)
2877 const int lineIndex = nBlockEnd + nDirection;
2878 if (lineIndex >= linesCount)
2879 break;
2880 DiffState lineState = m_pViewData->GetState(GetViewLineForScreen(lineIndex));
2881 if (!LinesInOneChange(nDirection, state, lineState))
2882 break;
2883 nBlockEnd += nDirection;
2886 int nTopPos = nCenterPos - (GetScreenLines()/2);
2887 if (nTopPos < 0)
2888 nTopPos = 0;
2890 POINT ptCaretPos = {0, nCenterPos};
2891 SetCaretPosition(ptCaretPos);
2892 ClearSelection();
2893 if (nDirection > 0)
2894 SetupAllSelection(nCenterPos, nBlockEnd);
2895 else
2896 SetupAllSelection(nBlockEnd, nCenterPos);
2898 ScrollAllToLine(nTopPos, FALSE);
2899 RecalcAllVertScrollBars(TRUE);
2900 SetCaretToLineStart();
2901 EnsureCaretVisible();
2902 OnNavigateNextinlinediff();
2904 UpdateViewsCaretPosition();
2905 UpdateCaret();
2906 ShowDiffLines(nCenterPos);
2907 return true;
2910 BOOL CBaseView::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
2912 if (pNMHDR->idFrom != reinterpret_cast<UINT_PTR>(m_hWnd))
2913 return FALSE;
2915 CString strTipText;
2916 strTipText = m_sWindowName + L"\r\n" + m_sFullFilePath;
2918 DWORD pos = GetMessagePos();
2919 CPoint point(GET_X_LPARAM(pos), GET_Y_LPARAM(pos));
2920 ScreenToClient(&point);
2921 const int nLine = GetButtonEventLineIndex(point);
2923 if (nLine >= 0)
2925 int nViewLine = GetViewLineForScreen(nLine);
2926 if((m_pViewData)&&(nViewLine < m_pViewData->GetCount()))
2928 auto movedIndex = m_pViewData->GetMovedIndex(nViewLine);
2929 if (movedIndex >= 0)
2931 if (m_pViewData->IsMovedFrom(nViewLine))
2933 strTipText.Format(IDS_MOVED_TO_TT, movedIndex+1);
2935 else
2937 strTipText.Format(IDS_MOVED_FROM_TT, movedIndex+1);
2944 *pResult = 0;
2945 if (strTipText.IsEmpty())
2946 return TRUE;
2948 // need to handle both ANSI and UNICODE versions of the message
2949 if (pNMHDR->code == TTN_NEEDTEXTA)
2951 auto pTTTA = reinterpret_cast<TOOLTIPTEXTA*>(pNMHDR);
2952 pTTTA->lpszText = m_szTip;
2953 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
2955 else
2957 auto pTTTW = reinterpret_cast<TOOLTIPTEXTW*>(pNMHDR);
2958 lstrcpyn(m_wszTip, strTipText, min(strTipText.GetLength() + 1, static_cast<int>(_countof(m_wszTip)) - 1));
2959 pTTTW->lpszText = m_wszTip;
2962 return TRUE; // message was handled
2965 INT_PTR CBaseView::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
2967 CRect rcClient;
2968 GetClientRect(rcClient);
2969 CRect textrect(rcClient.left, rcClient.top, rcClient.Width(), m_nLineHeight+HEADERHEIGHT);
2971 int marginwidth = GetSystemMetrics(SM_CXSMICON) + CDPIAware::Instance().ScaleX(GetSafeHwnd(), 4);
2972 if ((m_bViewLinenumbers)&&(m_pViewData)&&(m_pViewData->GetCount())&&(m_nDigits > 0))
2974 marginwidth += (m_nDigits * m_nCharWidth) + CDPIAware::Instance().ScaleX(GetSafeHwnd(), 2);
2976 CRect borderrect(rcClient.left, rcClient.top+m_nLineHeight+HEADERHEIGHT, marginwidth, rcClient.bottom);
2978 if (textrect.PtInRect(point) || borderrect.PtInRect(point))
2980 // inside the header part of the view (showing the filename)
2981 pTI->hwnd = this->m_hWnd;
2982 this->GetClientRect(&pTI->rect);
2983 pTI->uFlags |= TTF_ALWAYSTIP | TTF_IDISHWND;
2984 pTI->uId = reinterpret_cast<UINT_PTR>(m_hWnd);
2985 pTI->lpszText = LPSTR_TEXTCALLBACK;
2987 // we want multi line tooltips
2988 CToolTipCtrl* pToolTip = AfxGetModuleThreadState()->m_pToolTip;
2989 if (pToolTip->GetSafeHwnd())
2990 pToolTip->SetMaxTipWidth(SHRT_MAX);
2992 return (textrect.PtInRect(point) ? 1 : 2);
2995 return -1;
2998 void CBaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
3000 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
3001 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
3003 switch (nChar)
3005 case VK_TAB:
3006 if (bControl)
3008 if (this==m_pwndLeft)
3010 if (IsViewGood(m_pwndRight))
3012 m_pwndRight->SetFocus();
3014 else if (IsViewGood(m_pwndBottom))
3016 m_pwndBottom->SetFocus();
3019 else if (this==m_pwndRight)
3021 if (IsViewGood(m_pwndBottom))
3023 m_pwndBottom->SetFocus();
3025 else if (IsViewGood(m_pwndLeft))
3027 m_pwndLeft->SetFocus();
3030 else if (this==m_pwndBottom)
3032 if (IsViewGood(m_pwndLeft))
3034 m_pwndLeft->SetFocus();
3036 else if (IsViewGood(m_pwndRight))
3038 m_pwndRight->SetFocus();
3042 break;
3043 case VK_PRIOR:
3045 POINT ptCaretPos = GetCaretPosition();
3046 ptCaretPos.y -= GetScreenLines();
3047 ptCaretPos.y = max(ptCaretPos.y, 0l);
3048 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos, false);
3049 SetCaretPosition(ptCaretPos);
3050 OnCaretMove(MOVELEFT, bShift);
3051 ShowDiffLines(ptCaretPos.y);
3053 break;
3054 case VK_NEXT:
3056 POINT ptCaretPos = GetCaretPosition();
3057 ptCaretPos.y += GetScreenLines();
3058 if (ptCaretPos.y >= GetLineCount())
3059 ptCaretPos.y = GetLineCount()-1;
3060 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos, false);
3061 SetCaretPosition(ptCaretPos);
3062 OnCaretMove(MOVERIGHT, bShift);
3063 ShowDiffLines(ptCaretPos.y);
3065 break;
3066 case VK_HOME:
3068 if (bControl)
3070 ScrollAllToLine(0);
3071 ScrollAllToChar(0);
3072 SetCaretToViewStart();
3073 m_nCaretGoalPos = 0;
3074 if (bShift)
3075 AdjustSelection(MOVELEFT);
3076 else
3077 ClearSelection();
3078 UpdateCaret();
3080 else
3082 POINT ptCaretPos = GetCaretPosition();
3083 CString sLine = GetLineChars(ptCaretPos.y);
3084 int pos = 0;
3085 while (pos < sLine.GetLength())
3087 if (sLine[pos] != ' ' && sLine[pos] != '\t')
3088 break;
3089 ++pos;
3091 if (ptCaretPos.x == pos)
3093 SetCaretToLineStart();
3094 m_nCaretGoalPos = 0;
3095 OnCaretMove(MOVERIGHT, bShift);
3096 ScrollAllToChar(0);
3098 else
3100 ptCaretPos.x = pos;
3101 SetCaretAndGoalPosition(ptCaretPos);
3102 OnCaretMove(MOVELEFT, bShift);
3106 break;
3107 case VK_END:
3109 if (bControl)
3111 ScrollAllToLine(GetLineCount()-GetAllMinScreenLines());
3112 POINT ptCaretPos;
3113 ptCaretPos.y = GetLineCount()-1;
3114 ptCaretPos.x = GetLineLength(ptCaretPos.y);
3115 SetCaretAndGoalPosition(ptCaretPos);
3116 if (bShift)
3117 AdjustSelection(MOVERIGHT);
3118 else
3119 ClearSelection();
3121 else
3123 POINT ptCaretPos = GetCaretPosition();
3124 ptCaretPos.x = GetLineLength(ptCaretPos.y);
3125 if ((GetSubLineOffset(ptCaretPos.y) != -1) && (GetSubLineOffset(ptCaretPos.y) != CountMultiLines(GetViewLineForScreen(ptCaretPos.y))-1)) // not last screen line of view line
3127 ptCaretPos.x--;
3129 SetCaretAndGoalPosition(ptCaretPos);
3130 OnCaretMove(MOVERIGHT, bShift);
3133 break;
3134 case VK_BACK:
3135 if (IsWritable())
3137 if (! HasTextSelection())
3139 POINT ptCaretPos = GetCaretPosition();
3140 if (ptCaretPos.y == 0 && ptCaretPos.x == 0)
3141 break;
3142 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3143 if (bControl)
3144 MoveCaretWordLeft();
3145 else
3147 while (MoveCaretLeft() && IsViewLineEmpty(GetCaretViewPosition().y))
3151 m_ptSelectionViewPosStart = GetCaretViewPosition();
3153 RemoveSelectedText();
3155 break;
3156 case VK_DELETE:
3157 if (IsWritable())
3159 if (! HasTextSelection())
3161 if (bControl)
3163 m_ptSelectionViewPosStart = GetCaretViewPosition();
3164 MoveCaretWordRight();
3165 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3167 else
3169 if (! MoveCaretRight())
3170 break;
3171 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3172 MoveCaretLeft();
3173 m_ptSelectionViewPosStart = GetCaretViewPosition();
3176 RemoveSelectedText();
3178 break;
3179 case VK_INSERT:
3180 m_bInsertMode = !m_bInsertMode;
3181 UpdateCaret();
3182 break;
3183 case 'M':
3184 if (bControl && m_pwndRight)
3186 int nFirstViewLine = 0;
3187 int nLastViewLine = 0;
3188 if (GetViewSelection(nFirstViewLine, nLastViewLine))
3189 m_pwndRight->MarkBlock(!m_pwndRight->GetViewMarked(nFirstViewLine));
3191 break;
3193 CView::OnKeyDown(nChar, nRepCnt, nFlags);
3196 void CBaseView::OnLButtonDown(UINT nFlags, CPoint point)
3198 const int nClickedLine = GetButtonEventLineIndex(point);
3199 m_nLDownLine = nClickedLine;
3200 if ((nClickedLine >= m_nTopLine)&&(nClickedLine < GetLineCount()))
3202 POINT ptCaretPos;
3203 ptCaretPos.y = nClickedLine;
3204 int xpos2 = CalcColFromPoint(point.x, nClickedLine);
3205 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, xpos2, false);
3206 SetCaretAndGoalPosition(ptCaretPos);
3208 if (nFlags & MK_SHIFT)
3209 AdjustSelection(MOVERIGHT);
3210 else
3212 ClearSelection();
3213 SetupAllSelection(ptCaretPos.y, ptCaretPos.y);
3214 if (point.x < GetMarginWidth())
3216 // select the whole line
3217 m_ptSelectionViewPosStart = m_ptSelectionViewPosEnd = GetCaretViewPosition();
3218 m_ptSelectionViewPosStart.x = 0;
3219 m_ptSelectionViewPosEnd.x = GetViewLineLength(m_ptSelectionViewPosEnd.y);
3223 UpdateViewsCaretPosition();
3224 Invalidate();
3227 CView::OnLButtonDown(nFlags, point);
3230 CBaseView::ECharGroup CBaseView::GetCharGroup(wchar_t zChar) const
3232 if (zChar == ' ' || zChar == '\t' )
3234 return CHG_WHITESPACE;
3236 if (zChar < 0x20)
3238 return CHG_CONTROL;
3240 if (m_sWordSeparators.Find(zChar) >= 0)
3242 return CHG_WORDSEPARATOR;
3244 return CHG_WORDLETTER;
3247 void CBaseView::OnLButtonDblClk(UINT nFlags, CPoint point)
3249 if (m_pViewData == 0) {
3250 CView::OnLButtonDblClk(nFlags, point);
3251 return;
3254 const int nClickedLine = GetButtonEventLineIndex(point);
3255 if ( nClickedLine < 0)
3256 return;
3257 int nViewLine = GetViewLineForScreen(nClickedLine);
3258 if (point.x < GetMarginWidth()) // only if double clicked on the margin
3260 if((nViewLine < m_pViewData->GetCount())) // a double click on moved line scrolls to corresponding line
3262 if (m_pViewData->GetMovedIndex(nViewLine)>=0)
3264 int movedindex = m_pViewData->GetMovedIndex(nViewLine);
3265 int screenLine = FindViewLineNumber(movedindex);
3266 int nTop = screenLine - GetScreenLines()/2;
3267 if (nTop < 0)
3268 nTop = 0;
3269 ScrollAllToLine(nTop);
3270 // find and select the whole moved block
3271 int startSel = movedindex;
3272 int endSel = movedindex;
3273 while ((startSel > 0) && (m_pOtherViewData->GetMovedIndex(startSel) >= 0))
3274 startSel--;
3275 startSel++;
3276 while ((endSel < GetLineCount()) && (m_pOtherViewData->GetMovedIndex(endSel) >= 0))
3277 endSel++;
3278 endSel--;
3279 m_pOtherView->SetupSelection(startSel, endSel);
3280 return CView::OnLButtonDblClk(nFlags, point);
3284 if ((m_pMainFrame->m_bCollapsed)&&(m_pViewData->GetHideState(nViewLine) == HideState::Marker))
3286 // a double click on a marker expands the hidden text
3287 int i = nViewLine;
3288 while ((i < m_pViewData->GetCount())&&(m_pViewData->GetHideState(i) != HideState::Shown))
3290 if ((m_pwndLeft)&&(m_pwndLeft->m_pViewData))
3291 m_pwndLeft->m_pViewData->SetLineHideState(i, HideState::Shown);
3292 if ((m_pwndRight)&&(m_pwndRight->m_pViewData))
3293 m_pwndRight->m_pViewData->SetLineHideState(i, HideState::Shown);
3294 if ((m_pwndBottom)&&(m_pwndBottom->m_pViewData))
3295 m_pwndBottom->m_pViewData->SetLineHideState(i, HideState::Shown);
3296 i++;
3298 BuildAllScreen2ViewVector();
3299 if (m_pwndLeft)
3300 m_pwndLeft->Invalidate();
3301 if (m_pwndRight)
3302 m_pwndRight->Invalidate();
3303 if (m_pwndBottom)
3304 m_pwndBottom->Invalidate();
3306 else
3308 POINT ptCaretPos;
3309 ptCaretPos.y = nClickedLine;
3310 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth(), false);
3311 SetCaretPosition(ptCaretPos);
3312 ClearSelection();
3314 POINT ptViewCarret = GetCaretViewPosition();
3315 nViewLine = ptViewCarret.y;
3316 if (nViewLine >= GetViewCount())
3317 return;
3318 const CString &sLine = GetViewLine(nViewLine);
3319 int nLineLength = sLine.GetLength();
3320 int nBasePos = ptViewCarret.x;
3321 // get target char group
3322 ECharGroup eLeft = CHG_UNKNOWN;
3323 if (nBasePos > 0)
3325 eLeft = GetCharGroup(sLine[nBasePos-1]);
3327 ECharGroup eRight = CHG_UNKNOWN;
3328 if (nBasePos < nLineLength)
3330 eRight = GetCharGroup(sLine[nBasePos]);
3332 ECharGroup eTarget = max(eRight, eLeft);
3333 // find left margin
3334 int nLeft = nBasePos;
3335 while (nLeft > 0 && GetCharGroup(sLine[nLeft-1]) == eTarget)
3337 nLeft--;
3339 // get right margin
3340 int nRight = nBasePos;
3341 while (nRight < nLineLength && GetCharGroup(sLine[nRight]) == eTarget)
3343 nRight++;
3345 // set selection
3346 m_ptSelectionViewPosStart.x = nLeft;
3347 m_ptSelectionViewPosStart.y = nViewLine;
3348 m_ptSelectionViewPosEnd.x = nRight;
3349 m_ptSelectionViewPosEnd.y = nViewLine;
3350 m_ptSelectionViewPosOrigin = m_ptSelectionViewPosStart;
3351 SetupAllViewSelection(nViewLine, nViewLine);
3352 // set caret
3353 ptCaretPos = ConvertViewPosToScreen(m_ptSelectionViewPosEnd);
3354 UpdateViewsCaretPosition();
3355 UpdateGoalPos();
3357 // set mark word
3358 m_sPreviousMarkedWord = m_sMarkedWord; // store marked word to recall in case of triple click
3359 int nMarkWidth = max(nRight - nLeft, 0);
3360 m_sMarkedWord = sLine.Mid(m_ptSelectionViewPosStart.x, nMarkWidth).Trim();
3361 if (m_sMarkedWord.Compare(m_sPreviousMarkedWord) == 0)
3363 m_sMarkedWord.Empty();
3366 if (GetKeyState(VK_CONTROL) & 0x8000)
3368 // if the ctrl key is pressed, only
3369 // mark the words in this view
3370 SetMarkedWord(m_sMarkedWord);
3372 else
3374 if (m_pwndLeft)
3375 m_pwndLeft->SetMarkedWord(m_sMarkedWord);
3376 if (m_pwndRight)
3377 m_pwndRight->SetMarkedWord(m_sMarkedWord);
3378 if (m_pwndBottom)
3379 m_pwndBottom->SetMarkedWord(m_sMarkedWord);
3382 Invalidate();
3383 if (m_pwndLocator)
3384 m_pwndLocator->Invalidate();
3387 CView::OnLButtonDblClk(nFlags, point);
3390 void CBaseView::OnLButtonTrippleClick( UINT /*nFlags*/, CPoint point )
3392 const int nClickedLine = GetButtonEventLineIndex(point);
3393 if (((point.y - (GetLineHeight() + HEADERHEIGHT)) / GetLineHeight()) <= 0)
3395 if (!m_sConvertedFilePath.IsEmpty() && (GetKeyState(VK_CONTROL)&0x8000))
3397 PCIDLIST_ABSOLUTE __unaligned pidl = ILCreateFromPath(static_cast<LPCWSTR>(m_sConvertedFilePath));
3398 if (pidl)
3400 SHOpenFolderAndSelectItems(pidl,0,0,0);
3401 CoTaskMemFree((LPVOID)pidl);
3404 return;
3406 POINT ptCaretPos;
3407 ptCaretPos.y = nClickedLine;
3408 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth(), false);
3409 SetCaretAndGoalPosition(ptCaretPos);
3410 m_sMarkedWord = m_sPreviousMarkedWord; // recall previous Marked word
3411 if (m_pwndLeft)
3412 m_pwndLeft->SetMarkedWord(m_sMarkedWord);
3413 if (m_pwndRight)
3414 m_pwndRight->SetMarkedWord(m_sMarkedWord);
3415 if (m_pwndBottom)
3416 m_pwndBottom->SetMarkedWord(m_sMarkedWord);
3417 ClearSelection();
3418 m_ptSelectionViewPosStart.x = 0;
3419 m_ptSelectionViewPosStart.y = nClickedLine;
3420 m_ptSelectionViewPosEnd.x = GetLineLength(nClickedLine);
3421 m_ptSelectionViewPosEnd.y = nClickedLine;
3422 SetupSelection(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
3423 UpdateViewsCaretPosition();
3424 Invalidate();
3425 if (m_pwndLocator)
3426 m_pwndLocator->Invalidate();
3429 void CBaseView::OnEditCopy()
3431 CString sCopyData = GetSelectedText();
3433 if (!sCopyData.IsEmpty())
3435 CStringUtils::WriteAsciiStringToClipboard(sCopyData, m_hWnd);
3439 void CBaseView::OnMouseMove(UINT nFlags, CPoint point)
3441 if (m_pMainFrame->m_nMoveMovesToIgnore > 0)
3443 --m_pMainFrame->m_nMoveMovesToIgnore;
3444 CView::OnMouseMove(nFlags, point);
3445 return;
3447 int nMouseLine = GetButtonEventLineIndex(point);
3448 if (nMouseLine < -1)
3449 nMouseLine = -1;
3450 m_mouseInMargin = point.x < GetMarginWidth();
3452 ShowDiffLines(nMouseLine);
3454 KillTimer(IDT_SCROLLTIMER);
3455 if (nFlags & MK_LBUTTON)
3457 int saveMouseLine = nMouseLine >= 0 ? nMouseLine : 0;
3458 saveMouseLine = saveMouseLine < GetLineCount() ? saveMouseLine : GetLineCount() - 1;
3459 if (saveMouseLine < 0)
3460 return;
3461 int col = CalcColFromPoint(point.x, saveMouseLine);
3462 int charIndex = CalculateCharIndex(saveMouseLine, col, true);
3463 if (HasSelection() &&
3464 ((nMouseLine >= m_nTopLine)&&(nMouseLine < GetLineCount())))
3466 POINT ptCaretPos = {charIndex, nMouseLine};
3467 SetCaretAndGoalPosition(ptCaretPos);
3468 AdjustSelection(MOVERIGHT);
3469 Invalidate();
3470 UpdateWindow();
3472 if (m_nLDownLine < 0)
3473 return; // no scrolling if the click started on the header
3474 if (nMouseLine < m_nTopLine)
3476 ScrollAllToLine(m_nTopLine-1, TRUE);
3477 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3479 if (nMouseLine >= m_nTopLine + GetScreenLines() - 2)
3481 ScrollAllToLine(m_nTopLine+1, TRUE);
3482 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3484 if (!m_pMainFrame->m_bWrapLines && ((m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()) <= m_nOffsetChar))
3486 ScrollAllSide(-1);
3487 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3489 if (!m_pMainFrame->m_bWrapLines && (charIndex >= (GetScreenChars()+m_nOffsetChar-4)))
3491 ScrollAllSide(1);
3492 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3494 SetCapture();
3498 CView::OnMouseMove(nFlags, point);
3501 void CBaseView::OnLButtonUp(UINT nFlags, CPoint point)
3503 ShowDiffLines(-1);
3504 ReleaseCapture();
3505 KillTimer(IDT_SCROLLTIMER);
3507 __super::OnLButtonUp(nFlags, point);
3510 void CBaseView::OnTimer(UINT_PTR nIDEvent)
3512 if (nIDEvent == IDT_SCROLLTIMER)
3514 POINT point;
3515 GetCursorPos(&point);
3516 ScreenToClient(&point);
3517 int nMouseLine = GetButtonEventLineIndex(point);
3518 if (nMouseLine < -1)
3520 nMouseLine = -1;
3522 if (GetKeyState(VK_LBUTTON)&0x8000)
3524 int saveMouseLine = nMouseLine >= 0 ? nMouseLine : 0;
3525 saveMouseLine = saveMouseLine < GetLineCount() ? saveMouseLine : GetLineCount() - 1;
3526 int charIndex = CalculateCharIndex(saveMouseLine, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth(), false);
3527 if (nMouseLine < m_nTopLine)
3529 ScrollAllToLine(m_nTopLine-1, TRUE);
3530 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3532 if (nMouseLine >= m_nTopLine + GetScreenLines() - 2)
3534 ScrollAllToLine(m_nTopLine+1, TRUE);
3535 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3537 if (!m_pMainFrame->m_bWrapLines && ((m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()) <= m_nOffsetChar))
3539 ScrollAllSide(-1);
3540 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3542 if (!m_pMainFrame->m_bWrapLines && (charIndex >= (GetScreenChars()+m_nOffsetChar-4)))
3544 ScrollAllSide(1);
3545 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3551 CView::OnTimer(nIDEvent);
3554 void CBaseView::ShowDiffLines(int nLine)
3556 if ((nLine < m_nTopLine)||(nLine >= GetLineCount()))
3558 m_pwndLineDiffBar->ShowLines(nLine);
3559 nLine = -1;
3560 m_nMouseLine = nLine;
3561 return;
3564 if ((!m_pwndRight)||(!m_pwndLeft))
3565 return;
3566 if(m_pMainFrame->m_bOneWay)
3567 return;
3569 nLine = (nLine > m_pwndRight->m_Screen2View.size() ? -1 : nLine);
3570 nLine = (nLine > m_pwndLeft->m_Screen2View.size() ? -1 : nLine);
3572 if (nLine < 0)
3573 return;
3575 if (nLine != m_nMouseLine)
3577 if (nLine >= GetLineCount())
3578 nLine = -1;
3579 m_nMouseLine = nLine;
3580 m_pwndLineDiffBar->ShowLines(nLine);
3582 m_pMainFrame->m_nMoveMovesToIgnore = MOVESTOIGNORE;
3585 const viewdata& CBaseView::GetEmptyLineData()
3587 static const viewdata emptyLine(L"", DiffState::Empty, -1, EOL::NoEnding, HideState::Shown);
3588 return emptyLine;
3591 void CBaseView::InsertViewEmptyLines(int nFirstView, int nCount)
3593 for (int i = 0; i < nCount; i++)
3595 InsertViewData(nFirstView, GetEmptyLineData());
3600 void CBaseView::UpdateCaret()
3602 POINT ptCaretPos = GetCaretPosition();
3603 ptCaretPos.y = std::max<int>(std::min<int>(ptCaretPos.y, GetLineCount()-1), 0);
3604 ptCaretPos.x = std::max<int>(std::min<int>(ptCaretPos.x, GetLineLength(ptCaretPos.y)), 0);
3605 SetCaretPosition(ptCaretPos);
3607 int nCaretOffset = CalculateActualOffset(ptCaretPos);
3609 if (m_bFocused &&
3610 ptCaretPos.y >= m_nTopLine &&
3611 ptCaretPos.y < (m_nTopLine+GetScreenLines()) &&
3612 nCaretOffset >= m_nOffsetChar &&
3613 nCaretOffset < (m_nOffsetChar+GetScreenChars()))
3615 POINT pt1 = TextToClient(ptCaretPos);
3616 if (m_bInsertMode)
3617 CreateSolidCaret(2, GetLineHeight());
3618 else
3620 POINT pt = { ptCaretPos.x + 1, ptCaretPos.y };
3621 POINT pt2 = TextToClient(pt);
3622 int width = max(GetCharWidth(), static_cast<int>(pt2.x - pt1.x));
3623 CreateSolidCaret(width, GetLineHeight());
3625 SetCaretPos(pt1);
3626 ShowCaret();
3628 else
3630 HideCaret();
3634 POINT CBaseView::ConvertScreenPosToView(const POINT& pt)
3636 POINT ptViewPos;
3637 ptViewPos.x = pt.x;
3639 int nSubLine = GetSubLineOffset(pt.y);
3640 if (nSubLine > 0)
3642 for (int nScreenLine = pt.y-1; nScreenLine >= pt.y-nSubLine; nScreenLine--)
3644 ptViewPos.x += GetLineChars(nScreenLine).GetLength();
3648 ptViewPos.y = GetViewLineForScreen(pt.y);
3649 return ptViewPos;
3652 POINT CBaseView::ConvertViewPosToScreen(const POINT& pt)
3654 POINT ptPos;
3655 int nViewLineLenLeft = GetViewLineLength(pt.y);
3656 ptPos.x = min(static_cast<LONG>(nViewLineLenLeft), pt.x);
3657 ptPos.y = FindScreenLineForViewLine(pt.y);
3658 if (GetViewLineForScreen(ptPos.y) != pt.y )
3660 ptPos.x = 0;
3662 else if (GetSubLineOffset(ptPos.y) >= 0) // sublined
3664 int nSubLineLength = GetLineChars(ptPos.y).GetLength();
3665 while (nSubLineLength < ptPos.x)
3667 ptPos.x -= nSubLineLength;
3668 nViewLineLenLeft -= nSubLineLength;
3669 ptPos.y++;
3670 nSubLineLength = GetLineChars(ptPos.y).GetLength();
3671 // particular case when only first line exist. Without below statement we
3672 // have infinite loop because nSublineLengh = 0 and ptPos.x = 1, 0 < 1
3673 if (nSubLineLength == 0)
3675 ptPos.y = 0;
3676 ptPos.x = 0;
3679 // last pos of non last sub-line go to start of next screen line
3680 // Note: while this works correctly, it's not what a user might expect:
3681 // cursor-right when the caret is before the last char of a wrapped line
3682 // now moves the caret to the next line. But users expect the caret to
3683 // move to the right of the last char instead, and with another cursor-right
3684 // keystroke to move the caret to the next line.
3685 // Basically, this would require to handle two caret positions for the same
3686 // logical position in the line string (one on the last position of the first line,
3687 // one on the first position of the new line. For non-wrapped lines this works
3688 // because there's an 'invisible' newline char at the end of the first line.
3689 if (nSubLineLength == ptPos.x && nViewLineLenLeft > nSubLineLength)
3691 ptPos.x = 0;
3692 ptPos.y++;
3696 return ptPos;
3700 void CBaseView::EnsureCaretVisible()
3702 POINT ptCaretPos = GetCaretPosition();
3703 int nCaretOffset = CalculateActualOffset(ptCaretPos);
3705 if (ptCaretPos.y < m_nTopLine)
3706 ScrollAllToLine(ptCaretPos.y);
3707 int screnLines = GetScreenLines();
3708 if (screnLines)
3710 if (ptCaretPos.y >= (m_nTopLine+screnLines)-1)
3711 ScrollAllToLine(ptCaretPos.y-screnLines+2);
3712 if (nCaretOffset < m_nOffsetChar)
3713 ScrollAllToChar(nCaretOffset);
3714 if (nCaretOffset > (m_nOffsetChar+GetScreenChars()-1))
3715 ScrollAllToChar(nCaretOffset-GetScreenChars()+1);
3719 int CBaseView::CalculateActualOffset(const POINT& point)
3721 int nLineIndex = point.y;
3722 int nCharIndex = point.x;
3723 ASSERT(nCharIndex >= 0);
3724 CString sLine = GetLineChars(nLineIndex);
3725 int nLineLength = sLine.GetLength();
3726 return CountExpandedChars(sLine, min(nCharIndex, nLineLength));
3729 int CBaseView::CalculateCharIndex(int nLineIndex, int nActualOffset, bool allowEOL)
3731 int nLength = GetLineLength(nLineIndex);
3732 int nSubLine = GetSubLineOffset(nLineIndex);
3733 if (!allowEOL && nSubLine >= 0)
3735 int nViewLine = GetViewLineForScreen(nLineIndex);
3736 if (nViewLine >= 0 && nViewLine < static_cast<int>(m_ScreenedViewLine.size()))
3738 int nMultilineCount = CountMultiLines(nViewLine);
3739 if ((nMultilineCount>0) && (nSubLine<nMultilineCount-1))
3741 nLength--;
3745 CString Line = GetLineChars(nLineIndex);
3746 int nIndex = 0;
3747 int nOffset = 0;
3748 int nTabSize = GetTabSize();
3749 while (nOffset < nActualOffset && nIndex < nLength)
3751 if (Line.GetAt(nIndex) == L'\t')
3752 nOffset += (nTabSize - nOffset % nTabSize);
3753 else
3754 ++nOffset;
3755 ++nIndex;
3757 return nIndex;
3761 * @param xpos X coordinate in CBaseView
3762 * @param lineIndex logical line index (e.g. wrap/collapse)
3764 int CBaseView::CalcColFromPoint(int xpos, int lineIndex)
3766 int xpos2;
3767 CDC *pDC = GetDC();
3768 if (pDC)
3770 CString text = ExpandChars(GetLineChars(lineIndex), 0);
3771 int fit = text.GetLength();
3772 auto posBuffer = std::make_unique<int[]>(fit);
3773 pDC->SelectObject(GetFont()); // is this right font ?
3774 SIZE size;
3775 GetTextExtentExPoint(pDC->GetSafeHdc(), text, fit, INT_MAX, &fit, posBuffer.get(), &size);
3776 ReleaseDC(pDC);
3777 int lower = -1, upper = fit - 1;
3778 int xcheck = xpos - GetMarginWidth() + m_nOffsetChar * GetCharWidth();
3781 int middle = (upper + lower + 1) / 2;
3782 int width = posBuffer[middle];
3783 if (xcheck < width)
3784 upper = middle - 1;
3785 else
3786 lower = middle;
3787 } while (lower < upper);
3788 lower++;
3789 xpos2 = lower;
3790 if (lower < fit - 1)
3792 int charWidth = posBuffer[lower] - (lower > 0 ? posBuffer[lower - 1] : 0);
3793 if (posBuffer[lower] - xcheck <= charWidth / 2)
3794 xpos2++;
3797 else
3799 xpos2 = (xpos - GetMarginWidth()) / GetCharWidth() + m_nOffsetChar;
3800 if ((xpos % GetCharWidth()) >= (GetCharWidth()/2))
3801 xpos2++;
3803 return xpos2;
3806 POINT CBaseView::TextToClient(const POINT& point)
3808 POINT pt;
3809 int nOffsetScreenLine = max(0, static_cast<int>(point.y - m_nTopLine));
3810 pt.y = nOffsetScreenLine * GetLineHeight();
3811 pt.x = CalculateActualOffset(point);
3813 int nLeft = GetMarginWidth() - m_nOffsetChar * GetCharWidth();
3814 CDC * pDC = GetDC();
3815 if (pDC)
3817 pDC->SelectObject(GetFont()); // is this right font ?
3818 int nScreenLine = nOffsetScreenLine + m_nTopLine;
3819 CString sLine = GetLineChars(nScreenLine);
3820 ExpandChars(sLine, 0, std::min<int>(pt.x, sLine.GetLength()), sLine);
3821 nLeft += pDC->GetTextExtent(sLine, pt.x).cx;
3822 ReleaseDC(pDC);
3823 } else {
3824 nLeft += pt.x * GetCharWidth();
3827 pt.x = nLeft;
3828 pt.y = (pt.y + GetLineHeight() + HEADERHEIGHT);
3829 return pt;
3832 void CBaseView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
3834 CView::OnChar(nChar, nRepCnt, nFlags);
3836 bool bControl = !!(GetKeyState(VK_CONTROL) & 0x8000);
3837 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
3838 bool bSkipSelectionClear = false;
3840 if (IsReadonly())
3841 return;
3843 if ((::GetKeyState(VK_LBUTTON) & 0x8000) != 0 ||
3844 (::GetKeyState(VK_RBUTTON) & 0x8000) != 0)
3846 return;
3849 if (!m_pViewData) // no data - nothing to do
3850 return;
3852 if (nChar == VK_F16)
3854 // generated by a ctrl+backspace - ignore.
3856 else if (nChar==VK_TAB && HasTextLineSelection())
3858 // change indentation for selected lines
3859 if (bShift)
3861 RemoveIndentationForSelectedBlock();
3863 else
3865 AddIndentationForSelectedBlock();
3867 bSkipSelectionClear = true;
3869 else if ((nChar > 31)||(nChar == VK_TAB))
3871 ResetUndoStep();
3872 RemoveSelectedText();
3873 POINT ptCaretViewPos = GetCaretViewPosition();
3874 int nViewLine = ptCaretViewPos.y;
3875 if ((nViewLine==0)&&(GetViewCount()==0))
3876 OnChar(VK_RETURN, 0, 0);
3877 int charCount = 1;
3878 viewdata lineData = GetViewData(nViewLine);
3879 if (nChar == VK_TAB)
3881 int indentChars = GetIndentCharsForLine(ptCaretViewPos.x, nViewLine);
3882 if (indentChars > 0)
3884 lineData.sLine.Insert(ptCaretViewPos.x, CString(L' ', indentChars));
3885 charCount = indentChars;
3887 else
3888 lineData.sLine.Insert(ptCaretViewPos.x, L'\t');
3890 else
3892 if (m_bInsertMode)
3893 lineData.sLine.Insert(ptCaretViewPos.x, static_cast<wchar_t>(nChar));
3894 else
3896 if (lineData.sLine.GetLength() > ptCaretViewPos.x)
3897 lineData.sLine.SetAt(ptCaretViewPos.x, static_cast<wchar_t>(nChar));
3898 else
3899 lineData.sLine.Insert(ptCaretViewPos.x, static_cast<wchar_t>(nChar));
3902 if (IsStateEmpty(lineData.state) || IsStateConflicted(lineData.state) || lineData.state == DiffState::IdenticalRemoved)
3904 // if not last line set EOL
3905 for (int nCheckViewLine = nViewLine+1; nCheckViewLine < GetViewCount(); nCheckViewLine++)
3907 if (!IsViewLineEmpty(nCheckViewLine))
3909 lineData.ending = m_lineendings;
3910 break;
3913 // make sure previous (non empty) line have EOL set
3914 for (int nCheckViewLine = nViewLine-1; nCheckViewLine > 0; nCheckViewLine--)
3916 if (!IsViewLineEmpty(nCheckViewLine) && GetViewState(nCheckViewLine) != DiffState::IdenticalRemoved)
3918 if (GetViewLineEnding(nCheckViewLine) == EOL::NoEnding)
3920 SetViewLineEnding(nCheckViewLine, m_lineendings);
3922 break;
3926 lineData.state = DiffState::Edited;
3927 bool bNeedRenumber = false;
3928 if (lineData.linenumber == -1)
3930 lineData.linenumber = 0;
3931 bNeedRenumber = true;
3933 SetViewData(nViewLine, lineData);
3934 SetModified();
3935 SaveUndoStep();
3936 BuildAllScreen2ViewVector(nViewLine);
3937 if (bNeedRenumber)
3939 UpdateViewLineNumbers();
3941 for (int i = 0; i < charCount; ++i)
3942 MoveCaretRight();
3943 UpdateGoalPos();
3945 else if (nChar == 10)
3947 int nViewLine = GetViewLineForScreen(GetCaretPosition().y);
3948 EOL eol = m_pViewData->GetLineEnding(nViewLine);
3949 EOL newEOL = EOL::CRLF;
3950 switch (eol)
3952 case EOL::CRLF:
3953 newEOL = EOL::CR;
3954 break;
3955 case EOL::CR:
3956 newEOL = EOL::LF;
3957 break;
3958 case EOL::LF:
3959 newEOL = EOL::CRLF;
3960 break;
3962 if (eol == EOL::NoEnding || eol == newEOL)
3963 // don't allow to change enter on empty line, or last text line (lines with EOL::NoEnding)
3964 // to add EOL on newly edited empty line hit enter
3965 // don't store into UNDO if no change happened
3966 // and don't mark file as modified
3967 return;
3968 AddUndoViewLine(nViewLine);
3969 m_pViewData->SetLineEnding(nViewLine, newEOL);
3970 m_pViewData->SetState(nViewLine, DiffState::Edited);
3971 UpdateGoalPos();
3973 else if ((nChar == VK_RETURN) && !bControl)
3975 // insert a new, fresh and empty line below the cursor
3976 RemoveSelectedText();
3978 CUndo::GetInstance().BeginGrouping();
3980 POINT ptCaretViewPos = GetCaretViewPosition();
3981 int nViewLine = ptCaretViewPos.y;
3982 int nLeft = ptCaretViewPos.x;
3983 CString sLine = GetViewLineChars(nViewLine);
3984 CString sLineLeft = sLine.Left(nLeft);
3985 CString sLineRight = sLine.Right(sLine.GetLength() - nLeft);
3986 EOL eOriginalEnding = EOL::AutoLine;
3987 if (m_pViewData->GetCount() > nViewLine)
3988 eOriginalEnding = GetViewLineEnding(nViewLine);
3990 if (!sLineRight.IsEmpty() || (eOriginalEnding!=m_lineendings))
3992 viewdata newFirstLine(sLineLeft, DiffState::Edited, 1, m_lineendings, HideState::Shown);
3993 SetViewData(nViewLine, newFirstLine);
3996 int nInsertLine = (m_pViewData->GetCount()==0) ? 0 : nViewLine + 1;
3997 viewdata newLastLine(sLineRight, DiffState::Edited, 1, eOriginalEnding, HideState::Shown);
3998 InsertViewData(nInsertLine, newLastLine);
3999 SetModified();
4000 SaveUndoStep();
4002 // adds new line everywhere except me
4003 if (IsViewGood(m_pwndLeft) && m_pwndLeft!=this)
4005 m_pwndLeft->InsertViewEmptyLines(nInsertLine, 1);
4007 if (IsViewGood(m_pwndRight) && m_pwndRight!=this)
4009 m_pwndRight->InsertViewEmptyLines(nInsertLine, 1);
4011 if (IsViewGood(m_pwndBottom) && m_pwndBottom!=this)
4013 m_pwndBottom->InsertViewEmptyLines(nInsertLine, 1);
4015 SaveUndoStep();
4017 UpdateViewLineNumbers();
4018 SaveUndoStep();
4019 CUndo::GetInstance().EndGrouping();
4021 BuildAllScreen2ViewVector();
4022 // move the cursor to the new line
4023 ptCaretViewPos = SetupPoint(0, nViewLine+1);
4024 SetCaretAndGoalViewPosition(ptCaretViewPos);
4026 else
4027 return; // Unknown control character -- ignore it.
4028 if (!bSkipSelectionClear)
4029 ClearSelection();
4030 EnsureCaretVisible();
4031 UpdateCaret();
4032 Invalidate(FALSE);
4035 void CBaseView::AddUndoViewLine(int nViewLine, bool bAddEmptyLine)
4037 ResetUndoStep();
4038 m_AllState.left.AddViewLineFromView(m_pwndLeft, nViewLine, bAddEmptyLine);
4039 m_AllState.right.AddViewLineFromView(m_pwndRight, nViewLine, bAddEmptyLine);
4040 m_AllState.bottom.AddViewLineFromView(m_pwndBottom, nViewLine, bAddEmptyLine);
4041 SetModified();
4042 SaveUndoStep();
4043 RecalcAllVertScrollBars();
4044 Invalidate(FALSE);
4047 void CBaseView::AddEmptyViewLine(int nViewLineIndex)
4049 if (!m_pViewData)
4050 return;
4051 int viewLine = nViewLineIndex;
4052 EOL ending = m_pViewData->GetLineEnding(viewLine);
4053 if (ending == EOL::NoEnding)
4055 ending = m_lineendings;
4057 viewdata newLine(L"", DiffState::Edited, -1, ending, HideState::Shown);
4058 if (IsTarget()) // TODO: once more wievs will writable this is not correct anymore
4060 CString sPartLine = GetViewLineChars(nViewLineIndex);
4061 int nPosx = GetCaretPosition().x; // should be view pos ?
4062 m_pViewData->SetLine(viewLine, sPartLine.Left(nPosx));
4063 sPartLine = sPartLine.Mid(nPosx);
4064 newLine.sLine = sPartLine;
4066 m_pViewData->InsertData(viewLine+1, newLine);
4067 BuildAllScreen2ViewVector();
4070 void CBaseView::RemoveSelectedText()
4072 if (!m_pViewData)
4073 return;
4074 if (!HasTextSelection())
4075 return;
4077 // fix selection if starts or ends on empty line
4078 SetCaretViewPosition(m_ptSelectionViewPosEnd);
4079 while (IsViewLineEmpty(GetCaretViewPosition().y) && MoveCaretRight())
4082 m_ptSelectionViewPosEnd = GetCaretViewPosition();
4083 SetCaretViewPosition(m_ptSelectionViewPosStart);
4084 while (IsViewLineEmpty(GetCaretViewPosition().y) && MoveCaretRight())
4087 m_ptSelectionViewPosStart = GetCaretViewPosition();
4088 if (!HasTextSelection())
4090 ClearSelection();
4091 return;
4094 // We want to undo the insertion in a single step.
4095 ResetUndoStep();
4096 CUndo::GetInstance().BeginGrouping();
4098 // combine first and last line
4099 viewdata oFirstLine = GetViewData(m_ptSelectionViewPosStart.y);
4100 viewdata oLastLine = GetViewData(m_ptSelectionViewPosEnd.y);
4101 oFirstLine.sLine = oFirstLine.sLine.Left(m_ptSelectionViewPosStart.x) + oLastLine.sLine.Mid(m_ptSelectionViewPosEnd.x);
4102 oFirstLine.ending = (oLastLine.ending != EOL::NoEnding || m_ptSelectionViewPosEnd.y == (GetLineCount() - 1)) ? oLastLine.ending : oFirstLine.ending;
4103 oFirstLine.state = DiffState::Edited;
4104 SetViewData(m_ptSelectionViewPosStart.y, oFirstLine);
4106 // clean up middle lines if any
4107 if (m_ptSelectionViewPosStart.y != m_ptSelectionViewPosEnd.y)
4109 viewdata oEmptyLine = GetEmptyLineData();
4110 for (int nViewLine = m_ptSelectionViewPosStart.y+1; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
4112 SetViewData(nViewLine, oEmptyLine);
4114 SaveUndoStep();
4116 if (CleanEmptyLines())
4118 BuildAllScreen2ViewVector(); // schedule full rebuild
4120 SaveUndoStep();
4121 UpdateViewLineNumbers();
4124 SetModified(); //TODO set modified only if real data was changed
4125 SaveUndoStep();
4126 CUndo::GetInstance().EndGrouping();
4128 BuildAllScreen2ViewVector(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
4129 SetCaretViewPosition(m_ptSelectionViewPosStart);
4130 UpdateGoalPos();
4131 ClearSelection();
4132 UpdateCaret();
4133 EnsureCaretVisible();
4134 Invalidate(FALSE);
4137 void CBaseView::PasteText()
4139 CClipboardHelper clipboardHelper;
4140 if (!clipboardHelper.Open(nullptr))
4141 return;
4143 CString sClipboardText;
4144 HGLOBAL hglb = GetClipboardData(CF_TEXT);
4145 if (hglb)
4147 LPCSTR lpstr = static_cast<LPCSTR>(GlobalLock(hglb));
4148 sClipboardText = CString(lpstr);
4149 GlobalUnlock(hglb);
4151 hglb = GetClipboardData(CF_UNICODETEXT);
4152 if (hglb)
4154 LPCWSTR lpstr = static_cast<LPCWSTR>(GlobalLock(hglb));
4155 sClipboardText = lpstr;
4156 GlobalUnlock(hglb);
4159 if (sClipboardText.IsEmpty())
4160 return;
4162 sClipboardText.Replace(L"\r\n", L"\r");
4163 sClipboardText.Replace('\n', '\r');
4165 InsertText(sClipboardText);
4168 void CBaseView::OnCaretDown()
4170 POINT ptCaretPos = GetCaretPosition();
4171 int nLine = ptCaretPos.y;
4172 int nNextLine = nLine + 1;
4173 if (nNextLine >= GetLineCount()) // already at last line
4175 return;
4178 POINT ptCaretViewPos = GetCaretViewPosition();
4179 int nViewLine = ptCaretViewPos.y;
4180 int nNextViewLine = GetViewLineForScreen(nNextLine);
4181 if (!((nNextViewLine == nViewLine) && (GetSubLineOffset(nNextLine)<CountMultiLines(nNextViewLine)))) // not on same view line
4183 // find next suitable screen line
4184 while ((nNextViewLine == nViewLine) || IsViewLineHidden(nNextViewLine))
4186 nNextLine++;
4187 if (nNextLine >= GetLineCount())
4189 return;
4191 nNextViewLine = GetViewLineForScreen(nNextLine);
4194 ptCaretPos.y = nNextLine;
4195 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos, false);
4196 SetCaretPosition(ptCaretPos);
4197 OnCaretMove(MOVELEFT);
4198 ShowDiffLines(ptCaretPos.y);
4201 bool CBaseView::MoveCaretLeft()
4203 POINT ptCaretViewPos = GetCaretViewPosition();
4205 //int nViewLine = ptCaretViewPos.y;
4206 if (ptCaretViewPos.x == 0)
4208 int nPrevLine = GetCaretPosition().y;
4209 int nPrevViewLine;
4210 do {
4211 nPrevLine--;
4212 if (nPrevLine < 0)
4214 return false;
4216 nPrevViewLine = GetViewLineForScreen(nPrevLine);
4217 } while ((GetSubLineOffset(nPrevLine) >= CountMultiLines(nPrevViewLine)) || IsViewLineHidden(nPrevViewLine));
4218 ptCaretViewPos = ConvertScreenPosToView(SetupPoint(GetLineLength(nPrevLine), nPrevLine));
4219 ShowDiffLines(nPrevLine);
4221 else
4222 --ptCaretViewPos.x;
4224 SetCaretAndGoalViewPosition(ptCaretViewPos);
4225 return true;
4228 bool CBaseView::MoveCaretRight()
4230 POINT ptCaretViewPos = GetCaretViewPosition();
4232 int nViewLine = ptCaretViewPos.y;
4233 int nViewLineLen = GetViewLineLength(nViewLine);
4234 if (ptCaretViewPos.x >= nViewLineLen)
4236 int nNextLine = GetCaretPosition().y;
4237 int nNextViewLine;
4238 do {
4239 nNextLine++;
4240 if (nNextLine >= GetLineCount())
4242 return false;
4244 nNextViewLine = GetViewLineForScreen(nNextLine);
4245 } while (nNextViewLine == nViewLine || IsViewLineHidden(nNextViewLine));
4246 ptCaretViewPos.y = nNextViewLine;
4247 ptCaretViewPos.x = 0;
4248 ShowDiffLines(nNextLine);
4250 else
4251 ++ptCaretViewPos.x;
4253 SetCaretAndGoalViewPosition(ptCaretViewPos);
4254 return true;
4257 void CBaseView::UpdateGoalPos()
4259 m_nCaretGoalPos = CalculateActualOffset(GetCaretPosition());
4262 void CBaseView::OnCaretLeft()
4264 MoveCaretLeft();
4265 OnCaretMove(MOVELEFT);
4268 void CBaseView::OnCaretRight()
4270 MoveCaretRight();
4271 OnCaretMove(MOVERIGHT);
4274 void CBaseView::OnCaretUp()
4276 POINT ptCaretPos = GetCaretPosition();
4277 int nLine = ptCaretPos.y;
4278 if (nLine <= 0) // already at first line
4280 return;
4282 int nPrevLine = nLine - 1;
4284 POINT ptCaretViewPos = GetCaretViewPosition();
4285 int nViewLine = ptCaretViewPos.y;
4286 int nPrevViewLine = GetViewLineForScreen(nPrevLine);
4287 if (nPrevViewLine != nViewLine) // not on same view line
4289 // find previous suitable screen line
4290 while ((GetSubLineOffset(nPrevLine) >= CountMultiLines(nPrevViewLine)) || IsViewLineHidden(nPrevViewLine))
4292 if (nPrevLine <= 0)
4294 return;
4296 nPrevLine--;
4297 nPrevViewLine = GetViewLineForScreen(nPrevLine);
4300 ptCaretPos.y = nPrevLine;
4301 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos, false);
4302 SetCaretPosition(ptCaretPos);
4303 OnCaretMove(MOVELEFT);
4304 ShowDiffLines(ptCaretPos.y);
4307 bool CBaseView::IsWordSeparator(const wchar_t ch) const
4309 switch (GetCharGroup(ch))
4311 case CHG_CONTROL:
4312 case CHG_WHITESPACE:
4313 case CHG_WORDSEPARATOR:
4314 return true;
4316 return false;
4319 bool CBaseView::IsCaretAtWordBoundary()
4321 POINT ptViewCaret = GetCaretViewPosition();
4322 CString line = GetViewLineChars(ptViewCaret.y);
4323 if (line.IsEmpty())
4324 return false; // no boundary at the empty lines
4325 if (ptViewCaret.x == 0)
4326 return !IsWordSeparator(line.GetAt(ptViewCaret.x));
4327 if (ptViewCaret.x >= GetViewLineLength(ptViewCaret.y))
4328 return !IsWordSeparator(line.GetAt(ptViewCaret.x - 1));
4329 return
4330 IsWordSeparator(line.GetAt(ptViewCaret.x)) !=
4331 IsWordSeparator(line.GetAt(ptViewCaret.x - 1));
4334 void CBaseView::UpdateViewsCaretPosition()
4336 POINT ptCaretPos = GetCaretPosition();
4337 if (m_pwndBottom && m_pwndBottom!=this)
4338 m_pwndBottom->UpdateCaretPosition(ptCaretPos);
4339 if (m_pwndLeft && m_pwndLeft!=this)
4340 m_pwndLeft->UpdateCaretPosition(ptCaretPos);
4341 if (m_pwndRight && m_pwndRight!=this)
4342 m_pwndRight->UpdateCaretPosition(ptCaretPos);
4345 void CBaseView::OnCaretWordleft()
4347 MoveCaretWordLeft();
4348 OnCaretMove(MOVELEFT);
4351 void CBaseView::OnCaretWordright()
4353 MoveCaretWordRight();
4354 OnCaretMove(MOVERIGHT);
4357 void CBaseView::MoveCaretWordLeft()
4359 while (MoveCaretLeft() && !IsCaretAtWordBoundary())
4364 void CBaseView::MoveCaretWordRight()
4366 while (MoveCaretRight() && !IsCaretAtWordBoundary())
4371 void CBaseView::ClearCurrentSelection()
4373 m_ptSelectionViewPosStart = GetCaretViewPosition();
4374 m_ptSelectionViewPosEnd = m_ptSelectionViewPosStart;
4375 m_ptSelectionViewPosOrigin = m_ptSelectionViewPosStart;
4376 m_nSelViewBlockStart = -1;
4377 m_nSelViewBlockEnd = -1;
4378 Invalidate(FALSE);
4381 void CBaseView::ClearSelection()
4383 if (m_pwndLeft)
4384 m_pwndLeft->ClearCurrentSelection();
4385 if (m_pwndRight)
4386 m_pwndRight->ClearCurrentSelection();
4387 if (m_pwndBottom)
4388 m_pwndBottom->ClearCurrentSelection();
4391 void CBaseView::AdjustSelection(bool bMoveLeft)
4393 POINT ptCaretViewPos = GetCaretViewPosition();
4394 if (ArePointsSame(m_ptSelectionViewPosOrigin, SetupPoint(-1, -1)))
4396 // select all have been used recently update origin
4397 m_ptSelectionViewPosOrigin = bMoveLeft ? m_ptSelectionViewPosEnd : m_ptSelectionViewPosStart;
4399 if ((ptCaretViewPos.y < m_ptSelectionViewPosOrigin.y) ||
4400 (ptCaretViewPos.y == m_ptSelectionViewPosOrigin.y && ptCaretViewPos.x <= m_ptSelectionViewPosOrigin.x))
4402 m_ptSelectionViewPosStart = ptCaretViewPos;
4403 m_ptSelectionViewPosEnd = m_ptSelectionViewPosOrigin;
4405 else
4407 m_ptSelectionViewPosStart = m_ptSelectionViewPosOrigin;
4408 m_ptSelectionViewPosEnd = ptCaretViewPos;
4411 SetupAllViewSelection(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
4413 Invalidate(FALSE);
4416 void CBaseView::OnEditCut()
4418 if (IsWritable())
4420 OnEditCopy();
4421 RemoveSelectedText();
4425 void CBaseView::OnEditPaste()
4427 if (IsWritable())
4429 CUndo::GetInstance().BeginGrouping();
4430 RemoveSelectedText();
4431 PasteText();
4432 CUndo::GetInstance().EndGrouping();
4436 void CBaseView::DeleteFonts()
4438 for (int i=0; i<fontsCount; i++)
4440 if (m_apFonts[i])
4442 m_apFonts[i]->DeleteObject();
4443 delete m_apFonts[i];
4444 m_apFonts[i] = nullptr;
4449 void CBaseView::OnCaretMove(bool bMoveLeft)
4451 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
4452 OnCaretMove(bMoveLeft, bShift);
4455 void CBaseView::OnCaretMove(bool bMoveLeft, bool isShiftPressed)
4457 if(isShiftPressed)
4458 AdjustSelection(bMoveLeft);
4459 else
4460 ClearSelection();
4461 EnsureCaretVisible();
4462 UpdateCaret();
4465 void CBaseView::AddContextItems(CIconMenu& popup, DiffState /*state*/)
4467 AddCutCopyAndPaste(popup);
4470 void CBaseView::AddCutCopyAndPaste(CIconMenu& popup)
4472 popup.AppendMenu(MF_SEPARATOR, NULL);
4473 CString temp;
4474 temp.LoadString(IDS_EDIT_COPY);
4475 popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_COPY, temp);
4476 if (IsWritable())
4478 temp.LoadString(IDS_EDIT_CUT);
4479 popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_CUT, temp);
4480 temp.LoadString(IDS_EDIT_PASTE);
4481 popup.AppendMenu(MF_STRING | (CAppUtils::HasClipboardFormat(CF_UNICODETEXT)||CAppUtils::HasClipboardFormat(CF_TEXT) ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_PASTE, temp);
4482 popup.AppendMenu(MF_SEPARATOR, NULL);
4486 void CBaseView::CompensateForKeyboard(CPoint& point)
4488 // if the context menu is invoked through the keyboard, we have to use
4489 // a calculated position on where to anchor the menu on
4490 if (ArePointsSame(point, SetupPoint(-1, -1)))
4492 CRect rect;
4493 GetWindowRect(&rect);
4494 point = rect.CenterPoint();
4498 void CBaseView::ReleaseBitmap()
4500 if (m_pCacheBitmap)
4502 m_pCacheBitmap->DeleteObject();
4503 delete m_pCacheBitmap;
4504 m_pCacheBitmap = nullptr;
4508 void CBaseView::BuildMarkedWordArray()
4510 int lineCount = GetLineCount();
4511 m_arMarkedWordLines.clear();
4512 m_arMarkedWordLines.reserve(lineCount);
4513 bool bDoit = !m_sMarkedWord.IsEmpty();
4514 m_MarkedWordCount = 0;
4515 for (int i = 0; i < lineCount; ++i)
4517 if (bDoit)
4519 CString line = GetLineChars(i);
4521 if (!line.IsEmpty())
4523 int found = 0;
4524 int nMarkStart = -1;
4525 while ((nMarkStart = line.Find(m_sMarkedWord, ++nMarkStart)) >= 0)
4527 int nMarkEnd = nMarkStart + m_sMarkedWord.GetLength();
4528 ECharGroup eLeft = GetCharGroup(line, nMarkStart - 1);
4529 ECharGroup eStart = GetCharGroup(line, nMarkStart);
4530 if (eLeft != eStart)
4532 ECharGroup eRight = GetCharGroup(line, nMarkEnd);
4533 ECharGroup eEnd = GetCharGroup(line, nMarkEnd - 1);
4534 if (eRight != eEnd)
4536 found = 1;
4537 ++m_MarkedWordCount;
4538 break;
4542 m_arMarkedWordLines.push_back(found);
4544 else
4545 m_arMarkedWordLines.push_back(0);
4547 else
4548 m_arMarkedWordLines.push_back(0);
4552 void CBaseView::BuildFindStringArray()
4554 int lineCount = GetLineCount();
4555 m_arFindStringLines.clear();
4556 m_arFindStringLines.reserve(lineCount);
4557 bool bDoit = !m_sFindText.IsEmpty();
4558 int s = 0;
4559 int e = 0;
4560 for (int i = 0; i < lineCount; ++i)
4562 if (bDoit)
4564 CString line = GetLineChars(i);
4566 if (!line.IsEmpty())
4568 switch (m_pViewData->GetState(GetViewLineForScreen(i)))
4570 case DiffState::Empty:
4571 m_arFindStringLines.push_back(0);
4572 break;
4573 case DiffState::Unknown:
4574 case DiffState::Normal:
4575 case DiffState::FilteredDiff:
4576 if (m_bLimitToDiff)
4578 m_arFindStringLines.push_back(0);
4579 break;
4581 case DiffState::Removed:
4582 case DiffState::RemovedWhitespace:
4583 case DiffState::Added:
4584 case DiffState::AddedWhitespace:
4585 case DiffState::Whitespace:
4586 case DiffState::WhitespaceDiff:
4587 case DiffState::Conflicted:
4588 case DiffState::Conflicted_Ignored:
4589 case DiffState::ConflictAdded:
4590 case DiffState::ConflictEmpty:
4591 case DiffState::ConflictResolved:
4592 case DiffState::IdenticalRemoved:
4593 case DiffState::IdenticalAdded:
4594 case DiffState::TheirsRemoved:
4595 case DiffState::TheirsAdded:
4596 case DiffState::YoursRemoved:
4597 case DiffState::YoursAdded:
4598 case DiffState::Edited:
4600 if (!m_bMatchCase)
4601 line = line.MakeLower();
4602 s = 0;
4603 e = 0;
4604 int match = 0;
4605 while (StringFound(line, SearchNext, s, e))
4607 match++;
4608 s = e;
4609 e = 0;
4611 m_arFindStringLines.push_back(match);
4612 break;
4614 default:
4615 m_arFindStringLines.push_back(0);
4618 else
4619 m_arFindStringLines.push_back(0);
4621 else
4622 m_arFindStringLines.push_back(0);
4624 UpdateLocator();
4627 bool CBaseView::GetInlineDiffPositions(int nViewLine, std::vector<inlineDiffPos>& positions)
4629 if (!m_bShowInlineDiff)
4630 return false;
4631 if (m_pwndBottom && !(m_pwndBottom->IsHidden()))
4632 return false;
4634 if (!m_pViewData || m_pViewData->GetCount() <= nViewLine)
4635 return false;
4636 const CString &sLine = m_pViewData->GetLine(nViewLine);
4637 if (sLine.IsEmpty())
4638 return false;
4640 CheckOtherView();
4641 if (!m_pOtherViewData)
4642 return false;
4644 const CString &sDiffLine = m_pOtherViewData->GetLine(nViewLine);
4645 if (sDiffLine.IsEmpty())
4646 return false;
4648 svn_diff_t* diff = nullptr;
4649 auto pLine1 = (this == m_pwndLeft) ? &sLine : &sDiffLine;
4650 auto pLine2 = (this == m_pwndLeft) ? &sDiffLine : &sLine;
4651 m_svnlinediff.Diff(&diff, *pLine1, pLine1->GetLength(), *pLine2, pLine2->GetLength(), m_bInlineWordDiff);
4652 if (!diff || !SVNLineDiff::ShowInlineDiff(diff))
4653 return false;
4655 size_t lineoffset = 0;
4656 size_t position = 0;
4657 while (diff)
4659 if (this == m_pwndRight)
4661 apr_off_t nTmp = diff->modified_length;
4662 diff->modified_length = diff->original_length;
4663 diff->original_length = nTmp;
4665 nTmp = diff->modified_start;
4666 diff->modified_start = diff->original_start;
4667 diff->original_start = nTmp;
4669 apr_off_t len = diff->original_length;
4670 size_t oldpos = position;
4672 for (apr_off_t i = 0; i < len; ++i)
4674 position += (this == m_pwndRight) ? m_svnlinediff.m_line2tokens[lineoffset].size() : m_svnlinediff.m_line1tokens[lineoffset].size();
4675 lineoffset++;
4678 if (diff->type == svn_diff__type_diff_modified)
4680 inlineDiffPos p;
4681 p.start = oldpos;
4682 p.end = position;
4683 positions.push_back(p);
4686 diff = diff->next;
4689 return !positions.empty();
4692 void CBaseView::OnNavigateNextinlinediff()
4694 int nX;
4695 if (GetNextInlineDiff(nX))
4697 POINT ptCaretViewPos = GetCaretViewPosition();
4698 ptCaretViewPos.x = nX;
4699 SetCaretAndGoalViewPosition(ptCaretViewPos);
4700 m_ptSelectionViewPosOrigin = ptCaretViewPos;
4701 EnsureCaretVisible();
4705 void CBaseView::OnNavigatePrevinlinediff()
4707 int nX;
4708 if (GetPrevInlineDiff(nX))
4710 POINT ptCaretViewPos = GetCaretViewPosition();
4711 ptCaretViewPos.x = nX;
4712 SetCaretAndGoalViewPosition(ptCaretViewPos);
4713 m_ptSelectionViewPosOrigin = ptCaretViewPos;
4714 EnsureCaretVisible();
4718 bool CBaseView::HasNextInlineDiff()
4720 int nPos;
4721 return GetNextInlineDiff(nPos);
4724 bool CBaseView::GetNextInlineDiff(int & nPos)
4726 POINT ptCaretViewPos = GetCaretViewPosition();
4727 std::vector<inlineDiffPos> positions;
4728 if (GetInlineDiffPositions(ptCaretViewPos.y, positions))
4730 for (auto it = positions.cbegin(); it != positions.cend(); ++it)
4732 if (it->start > ptCaretViewPos.x)
4734 nPos = static_cast<LONG>(it->start);
4735 return true;
4737 if (it->end > ptCaretViewPos.x)
4739 nPos = static_cast<LONG>(it->end);
4740 return true;
4744 return false;
4747 bool CBaseView::HasPrevInlineDiff()
4749 int nPos;
4750 return GetPrevInlineDiff(nPos);
4753 bool CBaseView::GetPrevInlineDiff(int & nPos)
4755 POINT ptCaretViewPos = GetCaretViewPosition();
4756 std::vector<inlineDiffPos> positions;
4757 if (GetInlineDiffPositions(ptCaretViewPos.y, positions))
4759 for (auto it = positions.crbegin(); it != positions.crend(); ++it)
4761 if ( it->end < ptCaretViewPos.x)
4763 nPos = static_cast<LONG>(it->end);
4764 return true;
4766 if ( it->start < ptCaretViewPos.x)
4768 nPos = static_cast<LONG>(it->start);
4769 return true;
4773 return false;
4776 CBaseView * CBaseView::GetFirstGoodView()
4778 if (IsViewGood(m_pwndLeft))
4779 return m_pwndLeft;
4780 if (IsViewGood(m_pwndRight))
4781 return m_pwndRight;
4782 if (IsViewGood(m_pwndBottom))
4783 return m_pwndBottom;
4784 return nullptr;
4787 void CBaseView::BuildAllScreen2ViewVector()
4789 CBaseView * p_pwndView = GetFirstGoodView();
4790 if (p_pwndView)
4792 m_Screen2View.ScheduleFullRebuild(p_pwndView->m_pViewData);
4796 void CBaseView::BuildAllScreen2ViewVector(int nViewLine)
4798 BuildAllScreen2ViewVector(nViewLine, nViewLine);
4801 void CBaseView::BuildAllScreen2ViewVector(int nFirstViewLine, int nLastViewLine)
4803 CBaseView * p_pwndView = GetFirstGoodView();
4804 if (p_pwndView)
4806 m_Screen2View.ScheduleRangeRebuild(p_pwndView->m_pViewData, nFirstViewLine, nLastViewLine);
4810 void CBaseView::UpdateViewLineNumbers()
4812 int nLineNumber = 0;
4813 int nViewLineCount = GetViewCount();
4814 for (int nViewLine = 0; nViewLine < nViewLineCount; nViewLine++)
4816 int oldLine = GetViewLineNumber(nViewLine);
4817 if (oldLine >= 0)
4818 SetViewLineNumber(nViewLine, nLineNumber++);
4820 m_nDigits = 0;
4823 int CBaseView::CleanEmptyLines()
4825 int nRemovedCount = 0;
4826 int nViewLineCount = GetViewCount();
4827 bool bCheckLeft = IsViewGood(m_pwndLeft);
4828 bool bCheckRight = IsViewGood(m_pwndRight);
4829 bool bCheckBottom = IsViewGood(m_pwndBottom);
4830 for (int nViewLine = 0; nViewLine < nViewLineCount; )
4832 bool bAllEmpty = true;
4833 bAllEmpty &= !bCheckLeft || IsStateEmpty(m_pwndLeft->GetViewState(nViewLine));
4834 bAllEmpty &= !bCheckRight || IsStateEmpty(m_pwndRight->GetViewState(nViewLine));
4835 bAllEmpty &= !bCheckBottom || IsStateEmpty(m_pwndBottom->GetViewState(nViewLine));
4836 if (bAllEmpty)
4838 if (bCheckLeft)
4840 m_pwndLeft->RemoveViewData(nViewLine);
4842 if (bCheckRight)
4844 m_pwndRight->RemoveViewData(nViewLine);
4846 if (bCheckBottom)
4848 m_pwndBottom->RemoveViewData(nViewLine);
4850 if (CUndo::GetInstance().IsGrouping()) // if use group undo -> ensure back adding goes in right (reversed) order
4852 SaveUndoStep();
4854 nViewLineCount--;
4855 nRemovedCount++;
4856 continue;
4858 nViewLine++;
4860 return nRemovedCount;
4863 int CBaseView::FindScreenLineForViewLine( int viewLine )
4865 return m_Screen2View.FindScreenLineForViewLine(viewLine);
4868 int CBaseView::CountMultiLines( int nViewLine )
4870 if (m_ScreenedViewLine.empty())
4871 return 0; // in case the view is completely empty
4873 ASSERT(nViewLine < static_cast<int>(m_ScreenedViewLine.size()));
4875 if (m_ScreenedViewLine[nViewLine].bSublinesSet)
4877 return static_cast<int>(m_ScreenedViewLine[nViewLine].SubLines.size());
4880 auto multiLines = CStringUtils::WordWrap(m_pViewData->GetLine(nViewLine), GetScreenChars() - 1, GetTabSize());
4882 TScreenedViewLine oScreenedLine;
4883 bool subLinesSet = true;
4884 if (m_pMainFrame->m_bWrapLines)
4886 CDC* pDC = GetDC();
4887 CFont* pOldFont = pDC->SelectObject(GetFont());
4889 for (const auto& line : multiLines)
4891 if (line.GetLength())
4893 // we use the 'X' char to determine the char width,
4894 // but e.g. chinese chars are much wider. To make sure
4895 // that we wrap correctly, we calculate the average char width
4896 // here by using the real line text
4897 const CSize szCharExt = pDC->GetTextExtent(line);
4898 if (szCharExt.cx / line.GetLength() > m_nCharWidth)
4900 m_nCharWidth = szCharExt.cx / line.GetLength();
4901 subLinesSet = false;
4904 oScreenedLine.SubLines.push_back(line);
4906 pDC->SelectObject(pOldFont);
4907 ReleaseDC(pDC);
4909 else
4911 for (const auto& line : multiLines)
4913 oScreenedLine.SubLines.push_back(line);
4917 oScreenedLine.bSublinesSet = subLinesSet;
4918 m_ScreenedViewLine[nViewLine] = oScreenedLine;
4920 return CountMultiLines(nViewLine);
4923 /// prepare inline diff cache
4924 LineColors & CBaseView::GetLineColors(int nViewLine)
4926 ASSERT(nViewLine < static_cast<int>(m_ScreenedViewLine.size()));
4928 if (m_bWhitespaceInlineDiffs)
4930 if (m_ScreenedViewLine[nViewLine].bLineColorsSetWhiteSpace)
4931 return m_ScreenedViewLine[nViewLine].lineColorsWhiteSpace;
4933 else
4935 if (m_ScreenedViewLine[nViewLine].bLineColorsSet)
4936 return m_ScreenedViewLine[nViewLine].lineColors;
4939 LineColors oLineColors;
4940 // set main line color
4941 COLORREF crBkgnd, crText;
4942 DiffState diffState = m_pViewData->GetState(nViewLine);
4943 CDiffColors::GetInstance().GetColors(diffState, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBkgnd, crText);
4944 oLineColors.SetColor(0, crText, crBkgnd);
4946 do {
4947 if (!m_bShowInlineDiff)
4948 break;
4950 if (((diffState == DiffState::Normal) || (diffState == DiffState::FilteredDiff)) && (!m_bWhitespaceInlineDiffs))
4951 break;
4953 CString sLine = GetViewLineChars(nViewLine);
4954 if (sLine.IsEmpty())
4955 break;
4956 CString sDiffLine;
4957 if (!m_pOtherView)
4959 switch (diffState)
4961 case DiffState::Added:
4963 if ((nViewLine > 0) && (m_pViewData->GetState(nViewLine - 1) == DiffState::Removed))
4964 sDiffLine = GetViewLineChars(nViewLine - 1);
4966 break;
4967 case DiffState::Removed:
4969 if (((nViewLine + 1) < m_pViewData->GetCount()) && (m_pViewData->GetState(nViewLine + 1) == DiffState::Added))
4970 sDiffLine = GetViewLineChars(nViewLine + 1);
4972 break;
4975 else
4976 sDiffLine = m_pOtherView->GetViewLineChars(nViewLine);
4977 if (sDiffLine.IsEmpty())
4978 break;
4980 svn_diff_t* diff = nullptr;
4981 if (sLine.GetLength() > static_cast<int>(m_nInlineDiffMaxLineLength))
4982 break;
4983 auto pLine1 = (this == m_pwndLeft) ? &sLine : &sDiffLine;
4984 auto pLine2 = (this == m_pwndLeft) ? &sDiffLine : &sLine;
4985 m_svnlinediff.Diff(&diff, *pLine1, pLine1->GetLength(), *pLine2, pLine2->GetLength(), m_bInlineWordDiff);
4986 if (!diff || !SVNLineDiff::ShowInlineDiff(diff) || !diff->next)
4987 break;
4989 int lineoffset = 0;
4990 int nTextStartOffset = 0;
4991 std::map<int, COLORREF> removedPositions;
4992 while (diff)
4994 if (this == m_pwndRight)
4996 apr_off_t nTmp = diff->modified_length;
4997 diff->modified_length = diff->original_length;
4998 diff->original_length = nTmp;
5000 nTmp = diff->modified_start;
5001 diff->modified_start = diff->original_start;
5002 diff->original_start = nTmp;
5004 apr_off_t len = diff->original_length;
5006 size_t nTextLength = 0;
5007 for (int i = 0; i < len; ++i)
5009 nTextLength += (this == m_pwndRight) ? m_svnlinediff.m_line2tokens[lineoffset].size() : m_svnlinediff.m_line1tokens[lineoffset].size();
5010 lineoffset++;
5012 bool bInlineDiff = (diff->type == svn_diff__type_diff_modified);
5014 CDiffColors::GetInstance().GetColors(diffState, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBkgnd, crText);
5015 if ((m_bShowInlineDiff)&&(bInlineDiff))
5017 crBkgnd = InlineViewLineDiffColor(nViewLine);
5019 else if (m_pOtherView)
5021 crBkgnd = m_bDark ? m_ModifiedDarkBk : m_ModifiedBk;
5024 if (len < diff->modified_length)
5026 removedPositions[nTextStartOffset] = m_bDark ? m_InlineRemovedDarkBk : m_InlineRemovedBk;
5028 oLineColors.SetColor(nTextStartOffset, crText, crBkgnd);
5030 nTextStartOffset += static_cast<int>(nTextLength);
5031 diff = diff->next;
5033 for (std::map<int, COLORREF>::const_iterator it = removedPositions.begin(); it != removedPositions.end(); ++it)
5035 oLineColors.AddShotColor(it->first, it->second);
5037 } while (false); // error catch
5039 if (!m_bWhitespaceInlineDiffs)
5041 m_ScreenedViewLine[nViewLine].lineColors = oLineColors;
5042 m_ScreenedViewLine[nViewLine].bLineColorsSet = true;
5044 else
5046 m_ScreenedViewLine[nViewLine].lineColorsWhiteSpace = oLineColors;
5047 m_ScreenedViewLine[nViewLine].bLineColorsSetWhiteSpace = true;
5050 return GetLineColors(nViewLine);
5053 void CBaseView::OnEditSelectall()
5055 if (!m_pViewData)
5056 return;
5057 int nLastViewLine = m_pViewData->GetCount()-1;
5058 if (nLastViewLine < 0)
5059 return;
5060 SetupAllViewSelection(0, nLastViewLine);
5062 CString sLine = GetViewLineChars(nLastViewLine);
5063 m_ptSelectionViewPosStart = SetupPoint(0, 0);
5064 m_ptSelectionViewPosEnd = SetupPoint(sLine.GetLength(), nLastViewLine);
5065 m_ptSelectionViewPosOrigin = SetupPoint(-1, -1);
5067 UpdateWindow();
5070 void CBaseView::FilterWhitespaces(CString& first, CString& second)
5072 FilterWhitespaces(first);
5073 FilterWhitespaces(second);
5076 void CBaseView::FilterWhitespaces(CString& line)
5078 line.Remove(' ');
5079 line.Remove('\t');
5080 line.Remove('\r');
5081 line.Remove('\n');
5084 int CBaseView::GetButtonEventLineIndex(const POINT& point)
5086 if (point.y < (GetLineHeight() + HEADERHEIGHT))
5087 return -1;
5088 const int nLineFromTop = (point.y - HEADERHEIGHT) / GetLineHeight();
5089 int nEventLine = nLineFromTop + m_nTopLine;
5090 nEventLine--; //we need the index
5091 return nEventLine;
5095 BOOL CBaseView::PreTranslateMessage(MSG* pMsg)
5097 if (RelayTrippleClick(pMsg))
5098 return TRUE;
5099 return CView::PreTranslateMessage(pMsg);
5103 void CBaseView::ResetUndoStep()
5105 m_AllState.Clear();
5108 void CBaseView::SaveUndoStep()
5110 if (!m_AllState.IsEmpty())
5112 CUndo::GetInstance().AddState(m_AllState, GetCaretViewPosition());
5114 ResetUndoStep();
5117 void CBaseView::SetTheme(bool bDark)
5119 m_bDark = bDark || CTheme::Instance().IsHighContrastModeDark();
5120 DarkModeHelper::Instance().AllowDarkModeForWindow(GetSafeHwnd(), m_bDark);
5121 if (m_bDark)
5122 ModifyStyleEx(WS_EX_CLIENTEDGE, 0);
5123 else
5124 ModifyStyleEx(0, WS_EX_CLIENTEDGE);
5125 CDiffColors::GetInstance().LoadRegistry();
5126 BuildAllScreen2ViewVector();
5127 if (IsWindow(GetSafeHwnd()))
5129 if (m_bDark)
5131 if (FAILED(SetWindowTheme(GetSafeHwnd(), L"DarkMode_Explorer", nullptr)))
5132 SetWindowTheme(GetSafeHwnd(), L"Explorer", nullptr);
5134 else
5135 SetWindowTheme(GetSafeHwnd(), L"Explorer", nullptr);
5136 Invalidate();
5138 m_WhiteSpaceFg = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\Whitespace", CTheme::Instance().GetThemeColor(GetSysColor(COLOR_3DSHADOW)));
5141 void CBaseView::InsertViewData(int index, const CString& sLine, DiffState state, int linenumber, EOL ending, HideState hide, int movedline)
5143 m_pState->addedlines.push_back(index);
5144 m_pViewData->InsertData(index, sLine, state, linenumber, ending, hide, movedline);
5147 void CBaseView::InsertViewData( int index, const viewdata& data )
5149 m_pState->addedlines.push_back(index);
5150 m_pViewData->InsertData(index, data);
5153 void CBaseView::RemoveViewData( int index )
5155 m_pState->removedlines[index] = m_pViewData->GetData(index);
5156 m_pViewData->RemoveData(index);
5159 void CBaseView::SetViewData( int index, const viewdata& data )
5161 m_pState->replacedlines[index] = m_pViewData->GetData(index);
5162 m_pViewData->SetData(index, data);
5165 void CBaseView::SetViewState(int index, DiffState state)
5167 m_pState->linestates[index] = m_pViewData->GetState(index);
5168 m_pViewData->SetState(index, state);
5171 void CBaseView::SetViewLine( int index, const CString& sLine )
5173 m_pState->difflines[index] = m_pViewData->GetLine(index);
5174 m_pViewData->SetLine(index, sLine);
5177 void CBaseView::SetViewLineNumber( int index, int linenumber )
5179 int oldLineNumber = m_pViewData->GetLineNumber(index);
5180 if (oldLineNumber != linenumber) {
5181 m_pState->linelines[index] = oldLineNumber;
5182 m_pViewData->SetLineNumber(index, linenumber);
5186 void CBaseView::SetViewLineEnding( int index, EOL ending )
5188 m_pState->linesEOL[index] = m_pViewData->GetLineEnding(index);
5189 m_pViewData->SetLineEnding(index, ending);
5192 void CBaseView::SetViewMarked( int index, bool marked )
5194 m_pState->markedlines[index] = m_pViewData->GetMarked(index);
5195 m_pViewData->SetMarked(index, marked);
5199 BOOL CBaseView::GetViewSelection( int& start, int& end ) const
5201 if (HasSelection())
5203 start = m_nSelViewBlockStart;
5204 end = m_nSelViewBlockEnd;
5205 return true;
5207 return false;
5210 int CBaseView::Screen2View::GetViewLineForScreen( int screenLine )
5212 RebuildIfNecessary();
5213 if ((size() <= screenLine) || (screenLine < 0))
5214 return 0;
5215 return m_Screen2View[screenLine].nViewLine;
5218 int CBaseView::Screen2View::size()
5220 RebuildIfNecessary();
5221 return static_cast<int>(m_Screen2View.size());
5224 int CBaseView::Screen2View::GetSubLineOffset( int screenLine )
5226 RebuildIfNecessary();
5227 if (size() <= screenLine)
5228 return 0;
5229 return m_Screen2View[screenLine].nViewSubLine;
5233 doing partial rebuild, whole screen2view vector is built, but uses ScreenedViewLine cache to do it faster
5235 void CBaseView::Screen2View::RebuildIfNecessary()
5237 if (!m_pViewData)
5238 return; // rebuild not necessary
5240 FixScreenedCacheSize(m_pwndLeft);
5241 FixScreenedCacheSize(m_pwndRight);
5242 FixScreenedCacheSize(m_pwndBottom);
5243 if (!m_bFull)
5245 for (auto it = m_RebuildRanges.cbegin(); it != m_RebuildRanges.cend(); ++it)
5247 ResetScreenedViewLineCache(m_pwndLeft, *it);
5248 ResetScreenedViewLineCache(m_pwndRight, *it);
5249 ResetScreenedViewLineCache(m_pwndBottom, *it);
5252 else
5254 ResetScreenedViewLineCache(m_pwndLeft);
5255 ResetScreenedViewLineCache(m_pwndRight);
5256 ResetScreenedViewLineCache(m_pwndBottom);
5258 m_RebuildRanges.clear();
5259 m_bFull = false;
5261 size_t OldSize = m_Screen2View.size();
5262 m_Screen2View.clear();
5263 m_Screen2View.reserve(OldSize); // guess same size
5264 for (int i = 0; i < m_pViewData->GetCount(); ++i)
5266 if (m_pMainFrame->m_bCollapsed)
5268 while ((i < m_pViewData->GetCount())&&(m_pViewData->GetHideState(i) == HideState::Hidden))
5269 ++i;
5270 if (!(i < m_pViewData->GetCount()))
5271 break;
5273 TScreenLineInfo oLineInfo;
5274 oLineInfo.nViewLine = i;
5275 oLineInfo.nViewSubLine = -1; // no wrap
5276 if (m_pMainFrame->m_bWrapLines && !IsViewLineHidden(m_pViewData, i))
5278 int nMaxLines = 0;
5279 if (IsLeftViewGood())
5280 nMaxLines = std::max<int>(nMaxLines, m_pwndLeft->CountMultiLines(i));
5281 if (IsRightViewGood())
5282 nMaxLines = std::max<int>(nMaxLines, m_pwndRight->CountMultiLines(i));
5283 if (IsBottomViewGood())
5284 nMaxLines = std::max<int>(nMaxLines, m_pwndBottom->CountMultiLines(i));
5285 for (int l = 0; l < (nMaxLines-1); ++l)
5287 oLineInfo.nViewSubLine++;
5288 m_Screen2View.push_back(oLineInfo);
5290 oLineInfo.nViewSubLine++;
5292 m_Screen2View.push_back(oLineInfo);
5294 m_pViewData = nullptr;
5296 if (IsLeftViewGood())
5297 m_pwndLeft->BuildMarkedWordArray();
5298 if (IsRightViewGood())
5299 m_pwndRight->BuildMarkedWordArray();
5300 if (IsBottomViewGood())
5301 m_pwndBottom->BuildMarkedWordArray();
5302 UpdateLocator();
5303 RecalcAllVertScrollBars();
5304 RecalcAllHorzScrollBars();
5307 int CBaseView::Screen2View::FindScreenLineForViewLine( int viewLine )
5309 RebuildIfNecessary();
5311 int nScreenLineCount = static_cast<int>(m_Screen2View.size());
5313 int nPos = 0;
5314 if (nScreenLineCount>16)
5316 // for enough long data search for last screen
5317 // with viewline less than one we are looking for
5318 // use approximate method (based on) binary search using asymmetric start point
5319 // in form 2**n (determined as MSB of length) to go around division and rounding;
5320 // this effectively looks for bit values from MSB to LSB
5322 int nTestBit;
5323 //GetMostSignificantBitValue
5324 // note _BitScanReverse(&nTestBit, nScreenLineCount); can be used instead
5325 nTestBit = nScreenLineCount;
5326 nTestBit |= nTestBit>>1;
5327 nTestBit |= nTestBit>>2;
5328 nTestBit |= nTestBit>>4;
5329 nTestBit |= nTestBit>>8;
5330 nTestBit |= nTestBit>>16;
5331 nTestBit ^= (nTestBit>>1);
5333 while (nTestBit)
5335 int nTestPos = nPos | nTestBit;
5336 if (nTestPos < nScreenLineCount && m_Screen2View[nTestPos].nViewLine < viewLine)
5338 nPos = nTestPos;
5340 nTestBit >>= 1;
5343 while (nPos < nScreenLineCount && m_Screen2View[nPos].nViewLine < viewLine)
5345 nPos++;
5348 return nPos;
5351 void CBaseView::Screen2View::ScheduleFullRebuild(CViewData * pViewData) {
5352 m_bFull = true;
5354 m_pViewData = pViewData;
5357 void CBaseView::Screen2View::ScheduleRangeRebuild(CViewData * pViewData, int nFirstViewLine, int nLastViewLine)
5359 if (m_bFull)
5360 return;
5362 m_pViewData = pViewData;
5364 TRebuildRange Range;
5365 Range.FirstViewLine=nFirstViewLine;
5366 Range.LastViewLine=nLastViewLine;
5367 m_RebuildRanges.push_back(Range);
5370 bool CBaseView::Screen2View::FixScreenedCacheSize(CBaseView* pwndView)
5372 if (!IsViewGood(pwndView))
5374 return false;
5376 const int nOldSize = static_cast<int>(pwndView->m_ScreenedViewLine.size());
5377 const int nViewCount = std::max<int>(pwndView->GetViewCount(), 0);
5378 if (nOldSize == nViewCount)
5380 return false;
5382 pwndView->m_ScreenedViewLine.resize(nViewCount);
5383 return true;
5386 bool CBaseView::Screen2View::ResetScreenedViewLineCache(CBaseView* pwndView) const
5388 if (!IsViewGood(pwndView))
5390 return false;
5392 TRebuildRange Range={0, pwndView->GetViewCount()-1};
5393 ResetScreenedViewLineCache(pwndView, Range);
5394 return true;
5397 bool CBaseView::Screen2View::ResetScreenedViewLineCache(CBaseView* pwndView, const TRebuildRange& Range) const
5399 if (!IsViewGood(pwndView))
5401 return false;
5403 if (Range.LastViewLine == -1)
5405 return false;
5407 ASSERT(Range.FirstViewLine >= 0);
5408 ASSERT(Range.LastViewLine < pwndView->GetViewCount());
5409 for (int i = Range.FirstViewLine; i <= Range.LastViewLine; i++)
5411 pwndView->m_ScreenedViewLine[i].Clear();
5413 return false;
5416 void CBaseView::WrapChanged()
5418 m_nMaxLineLength = -1;
5419 m_nOffsetChar = 0;
5420 RecalcHorzScrollBar();
5423 void CBaseView::OnEditFind()
5425 if (m_pFindDialog)
5427 m_pFindDialog->SetFocus();
5428 return;
5431 int id = 0;
5432 if (this == m_pwndLeft)
5433 id = 1;
5434 if (this == m_pwndRight)
5435 id = 2;
5436 if (this == m_pwndBottom)
5437 id = 3;
5439 m_pFindDialog = new CFindDlg(this);
5440 m_pFindDialog->Create(this, id);
5442 m_pFindDialog->SetFindString(HasTextSelection() ? GetSelectedText() : CString());
5443 m_pFindDialog->SetReadonly(m_bReadonly);
5446 LRESULT CBaseView::OnFindDialogMessage(WPARAM wParam, LPARAM /*lParam*/)
5448 ASSERT(m_pFindDialog != nullptr);
5450 if (m_pFindDialog->IsTerminating())
5452 // invalidate the handle identifying the dialog box.
5453 m_pFindDialog = nullptr;
5454 return 0;
5457 if(m_pFindDialog->FindNext())
5459 //read data from dialog
5460 m_sFindText = m_pFindDialog->GetFindString();
5461 m_bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
5462 m_bLimitToDiff = m_pFindDialog->LimitToDiffs();
5463 m_bWholeWord = m_pFindDialog->WholeWord();
5465 if (!m_bMatchCase)
5466 m_sFindText = m_sFindText.MakeLower();
5468 BuildFindStringArray();
5469 if (static_cast<CFindDlg::FindType>(wParam) == CFindDlg::FindType::Find)
5471 if (m_pFindDialog->SearchUp())
5472 OnEditFindprev();
5473 else
5474 OnEditFindnext();
5476 else if (static_cast<CFindDlg::FindType>(wParam) == CFindDlg::FindType::Count)
5478 size_t count = 0;
5479 for (size_t i = 0; i < m_arFindStringLines.size(); ++i)
5480 count += m_arFindStringLines[i];
5481 CString matches;
5482 matches.Format(IDS_FIND_COUNT, count);
5483 m_pFindDialog->SetStatusText(matches);
5485 else if (static_cast<CFindDlg::FindType>(wParam) == CFindDlg::FindType::Replace)
5487 if (!IsWritable())
5488 return 0;
5489 bool bFound = false;
5490 if (m_pFindDialog->SearchUp())
5491 bFound = Search(SearchPrevious, true, true, false);
5492 else
5493 bFound = Search(SearchNext, true, true, false);
5494 if (bFound)
5496 CString sReplaceText = m_pFindDialog->GetReplaceString();
5497 CUndo::GetInstance().BeginGrouping();
5498 RemoveSelectedText();
5499 InsertText(sReplaceText);
5500 CUndo::GetInstance().EndGrouping();
5504 else if (static_cast<CFindDlg::FindType>(wParam) == CFindDlg::FindType::ReplaceAll)
5506 if (!IsWritable())
5507 return 0;
5508 bool bFound = false;
5509 int replaceCount = 0;
5510 POINT lastPoint = m_ptSelectionViewPosStart;
5511 m_ptSelectionViewPosStart.x = m_ptSelectionViewPosStart.y = 0;
5512 CUndo::GetInstance().BeginGrouping();
5515 bFound = Search(SearchNext, true, false, true);
5516 if (bFound)
5518 CString sReplaceText = m_pFindDialog->GetReplaceString();
5519 RemoveSelectedText();
5520 InsertText(sReplaceText);
5521 ++replaceCount;
5523 } while (bFound);
5524 CUndo::GetInstance().EndGrouping();
5525 if (replaceCount == 0)
5526 m_ptSelectionViewPosStart = lastPoint;
5527 CString message;
5528 message.Format(IDS_FIND_REPLACED, replaceCount);
5529 m_pFindDialog->SetStatusText(message, RGB(0, 0, 0));
5533 return 0;
5536 void CBaseView::OnEditFindnextStart()
5538 if (!m_pViewData)
5539 return;
5540 if (HasTextSelection())
5542 m_sFindText = GetSelectedText();
5543 m_bMatchCase = false;
5544 m_bLimitToDiff = false;
5545 m_bWholeWord = false;
5546 m_sFindText = m_sFindText.MakeLower();
5548 BuildFindStringArray();
5549 OnEditFindnext();
5551 else
5553 m_sFindText.Empty();
5554 BuildFindStringArray();
5558 void CBaseView::OnEditFindprevStart()
5560 if (!m_pViewData)
5561 return;
5562 if (HasTextSelection())
5564 m_sFindText = GetSelectedText();
5565 m_bMatchCase = false;
5566 m_bLimitToDiff = false;
5567 m_bWholeWord = false;
5568 m_sFindText = m_sFindText.MakeLower();
5570 BuildFindStringArray();
5571 OnEditFindprev();
5573 else
5575 m_sFindText.Empty();
5576 BuildFindStringArray();
5580 bool CBaseView::StringFound(const CString& str, SearchDirection srchDir, int& start, int& end) const
5582 bool bStringFound;
5585 if (srchDir == SearchPrevious)
5587 int laststart = -1;
5588 int laststart2 = -1;
5591 laststart2 = laststart;
5592 laststart = str.Find(m_sFindText, laststart + 1);
5593 } while (laststart >= 0 && laststart < start);
5594 start = laststart2;
5596 else
5597 start = str.Find(m_sFindText, start);
5598 end = start + m_sFindText.GetLength();
5599 bStringFound = (start >= 0);
5600 if (bStringFound && m_bWholeWord)
5602 if (start)
5603 bStringFound = IsWordSeparator(str.Mid(start - 1, 1).GetAt(0));
5605 if (bStringFound)
5607 if (str.GetLength() > end)
5608 bStringFound = IsWordSeparator(str.Mid(end, 1).GetAt(0));
5611 if (!bStringFound)
5613 if (srchDir == SearchPrevious)
5614 start--;
5615 else
5616 start = end;
5619 } while (!bStringFound && start >= 0);
5621 return bStringFound;
5624 void CBaseView::OnEditFindprev()
5626 Search(SearchPrevious, false, true, false);
5629 void CBaseView::OnEditFindnext()
5631 Search(SearchNext, false, true, false);
5634 bool CBaseView::Search(SearchDirection srchDir, bool useStart, bool flashIfNotFound, bool stopEof)
5636 if (m_sFindText.IsEmpty())
5637 return false;
5638 if(!m_pViewData)
5639 return false;
5641 POINT start = useStart ? m_ptSelectionViewPosStart : m_ptSelectionViewPosEnd;
5642 POINT end;
5643 end.y = m_pViewData->GetCount()-1;
5644 if (end.y < 0)
5645 return false;
5647 if (srchDir==SearchNext)
5648 end.x = GetViewLineLength(end.y);
5649 else
5651 end.x = m_ptSelectionViewPosStart.x;
5652 start.x = 0;
5655 if (!HasTextSelection())
5657 start.y = m_ptCaretViewPos.y;
5658 if (srchDir==SearchNext)
5659 start.x = m_ptCaretViewPos.x;
5660 else
5662 start.x = 0;
5663 end.x = m_ptCaretViewPos.x;
5666 CString sSelectedText;
5667 int startline = -1;
5668 for (int nViewLine=start.y; ;srchDir==SearchNext ? nViewLine++ : nViewLine--)
5670 if (nViewLine < 0)
5672 if (stopEof)
5673 return false;
5674 nViewLine = m_pViewData->GetCount()-1;
5675 startline = start.y;
5676 if (flashIfNotFound)
5678 if (m_pFindDialog)
5679 m_pFindDialog->SetStatusText(CString(MAKEINTRESOURCE(IDS_FIND_TOPREACHED)), RGB(63, 127, 47));
5680 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 2, 100);
5683 if (nViewLine > end.y)
5685 if (stopEof)
5686 return false;
5687 nViewLine = 0;
5688 startline = start.y;
5689 if (flashIfNotFound)
5691 if (m_pFindDialog)
5692 m_pFindDialog->SetStatusText(CString(MAKEINTRESOURCE(IDS_FIND_BOTTOMREACHED)), RGB(63, 127, 47));
5693 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 2, 100);
5696 switch (m_pViewData->GetState(nViewLine))
5698 case DiffState::Empty:
5699 break;
5700 case DiffState::Unknown:
5701 case DiffState::Normal:
5702 case DiffState::FilteredDiff:
5703 if (m_bLimitToDiff)
5704 break;
5705 [[fallthrough]];
5706 case DiffState::Removed:
5707 case DiffState::RemovedWhitespace:
5708 case DiffState::Added:
5709 case DiffState::AddedWhitespace:
5710 case DiffState::Whitespace:
5711 case DiffState::WhitespaceDiff:
5712 case DiffState::Conflicted:
5713 case DiffState::Conflicted_Ignored:
5714 case DiffState::ConflictAdded:
5715 case DiffState::ConflictEmpty:
5716 case DiffState::ConflictResolved:
5717 case DiffState::IdenticalRemoved:
5718 case DiffState::IdenticalAdded:
5719 case DiffState::TheirsRemoved:
5720 case DiffState::TheirsAdded:
5721 case DiffState::YoursRemoved:
5722 case DiffState::YoursAdded:
5723 case DiffState::Edited:
5725 sSelectedText = GetViewLineChars(nViewLine);
5726 if (nViewLine == start.y && startline < 0)
5727 sSelectedText = srchDir == SearchNext ? sSelectedText.Mid(start.x) : sSelectedText.Left(end.x);
5728 if (!m_bMatchCase)
5729 sSelectedText = sSelectedText.MakeLower();
5730 int startfound = srchDir == SearchNext ? 0 : sSelectedText.GetLength();
5731 int endfound = 0;
5732 if (StringFound(sSelectedText, srchDir, startfound, endfound))
5734 HighlightViewLines(nViewLine, nViewLine);
5735 m_ptSelectionViewPosStart.x = startfound;
5736 m_ptSelectionViewPosEnd.x = endfound;
5737 if (nViewLine == start.y && startline < 0)
5739 m_ptSelectionViewPosStart.x += start.x;
5740 m_ptSelectionViewPosEnd.x += start.x;
5742 m_ptSelectionViewPosEnd.x = m_ptSelectionViewPosStart.x + m_sFindText.GetLength();
5743 m_ptSelectionViewPosStart.y = nViewLine;
5744 m_ptSelectionViewPosEnd.y = nViewLine;
5745 m_ptCaretViewPos = m_ptSelectionViewPosStart;
5746 UpdateViewsCaretPosition();
5747 EnsureCaretVisible();
5748 Invalidate();
5749 return true;
5752 break;
5755 if (startline >= 0)
5757 if (nViewLine == startline)
5759 if (flashIfNotFound)
5761 CString message;
5762 message.Format(IDS_FIND_NOTFOUND, static_cast<LPCWSTR>(m_sFindText));
5763 if (m_pFindDialog)
5764 m_pFindDialog->SetStatusText(message, RGB(255, 0, 0));
5765 ::MessageBeep(0xFFFFFFFF);
5766 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 3, 100);
5768 break;
5772 m_pMainFrame->m_nMoveMovesToIgnore = MOVESTOIGNORE;
5773 return false;
5776 CString CBaseView::GetSelectedText() const
5778 CString sSelectedText;
5779 POINT start = m_ptSelectionViewPosStart;
5780 POINT end = m_ptSelectionViewPosEnd;
5781 if (!HasTextSelection())
5783 if (!HasSelection())
5784 return sSelectedText;
5785 start.y = m_nSelViewBlockStart;
5786 start.x = 0;
5787 end.y = m_nSelViewBlockEnd;
5788 end.x = GetViewLineLength(m_nSelViewBlockEnd);
5790 if (!m_pViewData)
5791 return sSelectedText;
5792 // first store the selected lines in one CString
5793 for (int nViewLine=start.y; nViewLine<=end.y; nViewLine++)
5795 switch (m_pViewData->GetState(nViewLine))
5797 case DiffState::Empty:
5798 break;
5799 case DiffState::Unknown:
5800 case DiffState::Normal:
5801 case DiffState::Removed:
5802 case DiffState::RemovedWhitespace:
5803 case DiffState::Added:
5804 case DiffState::AddedWhitespace:
5805 case DiffState::Whitespace:
5806 case DiffState::WhitespaceDiff:
5807 case DiffState::Conflicted:
5808 case DiffState::Conflicted_Ignored:
5809 case DiffState::ConflictAdded:
5810 case DiffState::ConflictEmpty:
5811 case DiffState::ConflictResolved:
5812 case DiffState::IdenticalRemoved:
5813 case DiffState::IdenticalAdded:
5814 case DiffState::TheirsRemoved:
5815 case DiffState::TheirsAdded:
5816 case DiffState::YoursRemoved:
5817 case DiffState::YoursAdded:
5818 case DiffState::Edited:
5819 case DiffState::FilteredDiff:
5820 sSelectedText += GetViewLineChars(nViewLine);
5821 sSelectedText += L"\r\n";
5822 break;
5825 // remove the non-selected chars from the first line, last line and last \r\n
5826 int nLeftCut = start.x;
5827 int nRightCut = GetViewLineChars(end.y).GetLength() - end.x + 2;
5828 sSelectedText = sSelectedText.Mid(nLeftCut, sSelectedText.GetLength()-nLeftCut-nRightCut);
5829 return sSelectedText;
5832 void CBaseView::CheckModifications(bool& hasMods, bool& hasConflicts, bool& hasWhitespaceMods, bool& hasFilteredMods)
5834 hasMods = false;
5835 hasConflicts = false;
5836 hasWhitespaceMods = false;
5837 hasFilteredMods = false;
5839 if (m_pViewData)
5841 for (int i=0; i<m_pViewData->GetCount(); i++)
5843 DiffState state = m_pViewData->GetState(i);
5844 switch (state)
5846 case DiffState::Added:
5847 case DiffState::IdenticalAdded:
5848 case DiffState::TheirsAdded:
5849 case DiffState::YoursAdded:
5850 case DiffState::ConflictAdded:
5851 case DiffState::IdenticalRemoved:
5852 case DiffState::Removed:
5853 case DiffState::TheirsRemoved:
5854 case DiffState::YoursRemoved:
5855 case DiffState::Empty:
5856 hasMods = true;
5857 break;
5858 case DiffState::Conflicted:
5859 case DiffState::Conflicted_Ignored:
5860 hasConflicts = true;
5861 break;
5862 case DiffState::RemovedWhitespace:
5863 case DiffState::AddedWhitespace:
5864 case DiffState::Whitespace:
5865 case DiffState::WhitespaceDiff:
5866 hasWhitespaceMods = true;
5867 break;
5868 case DiffState::FilteredDiff:
5869 hasFilteredMods = true;
5870 break;
5876 void CBaseView::OnEditGotoline()
5878 if (!m_pViewData)
5879 return;
5880 // find the last and first line number
5881 int nViewLineCount = m_pViewData->GetCount();
5883 int nLastLineNumber = DIFF_EMPTYLINENUMBER;
5884 for (int nViewLine=nViewLineCount-1; nViewLine>=0; --nViewLine)
5886 nLastLineNumber = m_pViewData->GetLineNumber(nViewLine);
5887 if (nLastLineNumber!=DIFF_EMPTYLINENUMBER)
5889 break;
5892 if (nLastLineNumber==DIFF_EMPTYLINENUMBER || nLastLineNumber==0) // not numbered line foud or last one is first
5894 return;
5896 nLastLineNumber++;
5897 int nFirstLineNumber=1; // first is always 1
5899 CString sText;
5900 sText.FormatMessage(IDS_GOTOLINE, nFirstLineNumber, nLastLineNumber);
5902 CGotoLineDlg dlg(this);
5903 dlg.SetLabel(sText);
5904 dlg.SetLimits(nFirstLineNumber, nLastLineNumber);
5905 if (dlg.DoModal() == IDOK)
5907 for (int nViewLine = 0; nViewLine < nViewLineCount; ++nViewLine)
5909 if ((m_pViewData->GetLineNumber(nViewLine)+1) == dlg.GetLineNumber())
5911 HighlightViewLines(nViewLine, nViewLine);
5912 return;
5918 int CBaseView::SaveFile(int nFlags)
5920 Invalidate();
5921 if (m_pViewData && m_pWorkingFile)
5923 CFileTextLines file;
5924 m_SaveParams.m_LineEndings = m_lineendings;
5925 m_SaveParams.m_UnicodeType = m_texttype;
5926 file.SetSaveParams(m_SaveParams);
5928 for (int i=0; i<m_pViewData->GetCount(); i++)
5930 //only copy non-removed lines
5931 DiffState state = m_pViewData->GetState(i);
5932 switch (state)
5934 case DiffState::Conflicted:
5935 case DiffState::Conflicted_Ignored:
5937 int first = i;
5938 int last = i;
5941 last++;
5942 } while((last<m_pViewData->GetCount()) && ((m_pViewData->GetState(last)==DiffState::Conflicted)||(m_pViewData->GetState(last)==DiffState::Conflicted_Ignored)));
5943 file.Add(L"<<<<<<< .mine", EOL::NoEnding);
5944 for (int j=first; j<last; j++)
5946 file.Add(m_pwndRight->m_pViewData->GetLine(j), m_pwndRight->m_pViewData->GetLineEnding(j));
5948 file.Add(L"=======", EOL::NoEnding);
5949 for (int j=first; j<last; j++)
5951 file.Add(m_pwndLeft->m_pViewData->GetLine(j), m_pwndLeft->m_pViewData->GetLineEnding(j));
5953 file.Add(L">>>>>>> .theirs", EOL::NoEnding);
5954 i = last-1;
5956 break;
5957 case DiffState::Empty:
5958 break;
5959 case DiffState::ConflictEmpty:
5960 case DiffState::IdenticalRemoved:
5961 case DiffState::Removed:
5962 case DiffState::TheirsRemoved:
5963 case DiffState::YoursRemoved:
5964 case DiffState::ConflictResolvedEmpty:
5965 if ((nFlags&SAVE_REMOVEDLINES) == 0)
5967 // do not save removed lines
5968 break;
5970 default:
5971 file.Add(m_pViewData->GetLine(i), m_pViewData->GetLineEnding(i));
5972 break;
5975 CString filename = m_pWorkingFile->GetFilename();
5976 if (m_pWorkingFile->IsReadonly())
5977 if (!CCommonAppUtils::FileOpenSave(filename, nullptr, IDS_SAVEASTITLE, IDS_COMMONFILEFILTER, false, m_hWnd))
5978 return -1;
5979 if (!file.Save(filename))
5981 ::MessageBox(m_hWnd, file.GetErrorString(), L"TortoiseGitMerge", MB_ICONERROR);
5982 return -1;
5984 m_pWorkingFile->SetFileName(filename);
5985 m_pWorkingFile->StoreFileAttributes();
5986 // m_dlgFilePatches.SetFileStatusAsPatched(sFilePath);
5987 SetModified(FALSE);
5988 CUndo::GetInstance().MarkAsOriginalState(
5989 this == m_pwndLeft,
5990 this == m_pwndRight,
5991 this == m_pwndBottom);
5992 if (file.GetCount() == 1 && file.GetAt(0).IsEmpty() && file.GetLineEnding(0) == EOL::NoEnding)
5993 return 0;
5994 return file.GetCount();
5996 return 1;
6000 int CBaseView::SaveFileTo(CString sFileName, int nFlags)
6002 if (m_pWorkingFile)
6004 m_pWorkingFile->SetFileName(sFileName);
6005 return SaveFile(nFlags);
6007 return -1;
6011 EOL CBaseView::GetLineEndings()
6013 return GetLineEndings(GetWhitecharsProperties().HasMixedEols);
6016 EOL CBaseView::GetLineEndings(bool bHasMixedEols)
6018 if (bHasMixedEols)
6020 return EOL::AutoLine; // mixed eols - hack value
6022 if (m_lineendings == EOL::AutoLine)
6024 return EOL::CRLF;
6026 return m_lineendings;
6029 void CBaseView::ReplaceLineEndings(EOL eEol)
6031 if (eEol == EOL::AutoLine)
6033 return;
6035 // set AUTOLINE
6036 m_lineendings = eEol;
6037 // replace all set EOLs
6038 // TODO store line endings and lineendings in undo
6039 //CUndo::BeginGrouping();
6040 for (int i = 0; i < GetViewCount(); ++i)
6042 if (IsLineEmpty(i))
6044 continue;
6046 EOL eLineEol = GetViewLineEnding(i);
6047 if (eLineEol == EOL::AutoLine || eLineEol == EOL::NoEnding || eLineEol == m_lineendings)
6049 continue;
6051 SetViewLineEnding(i, eEol);
6053 //CUndo::EndGrouping();
6054 //CUndo::saveundostep;
6055 DocumentUpdated();
6056 SetModified();
6059 void CBaseView::SetLineEndingStyle(EOL eEol)
6061 m_lineendings = eEol;
6064 void CBaseView::SetTextType(CFileTextLines::UnicodeType eTextType)
6066 if (m_texttype == eTextType)
6068 return;
6070 m_texttype = eTextType;
6071 DocumentUpdated();
6072 SetModified();
6075 void CBaseView::AskUserForNewLineEndingsAndTextType(int nTextId)
6077 if (IsReadonly())
6078 return; // nothing to be changed in read-only view
6079 CEncodingDlg dlg;
6080 dlg.view.LoadString(nTextId);
6081 dlg.texttype = m_texttype;
6082 dlg.lineendings = GetLineEndings();
6083 if (dlg.DoModal() != IDOK)
6084 return;
6085 SetTextType(dlg.texttype);
6086 ReplaceLineEndings(dlg.lineendings);
6090 Replaces lines from source view to this
6092 void CBaseView::UseViewBlock(CBaseView * pwndView, int nFirstViewLine, int nLastViewLine, std::function<bool(int)> fnSkip)
6094 if (!IsViewGood(pwndView))
6095 return;
6096 if (!IsWritable())
6097 return;
6098 CUndo::GetInstance().BeginGrouping();
6100 for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++)
6102 bool skip = fnSkip(viewLine);
6103 if (skip)
6105 if (GetViewMarked(viewLine))
6106 SetViewMarked(viewLine, false);
6107 continue;
6109 viewdata line = pwndView->GetViewData(viewLine);
6110 if (line.ending != EOL::NoEnding)
6111 line.ending = m_lineendings;
6112 switch (line.state)
6114 case DiffState::ConflictEmpty:
6115 case DiffState::Unknown:
6116 line.state = DiffState::Empty;
6117 [[fallthrough]];
6118 case DiffState::Empty:
6119 break;
6120 case DiffState::Added:
6121 case DiffState::ConflictAdded:
6122 case DiffState::Conflicted:
6123 case DiffState::Conflicted_Ignored:
6124 case DiffState::IdenticalAdded:
6125 case DiffState::TheirsAdded:
6126 case DiffState::YoursAdded:
6127 case DiffState::IdenticalRemoved:
6128 case DiffState::Removed:
6129 case DiffState::TheirsRemoved:
6130 case DiffState::YoursRemoved:
6131 pwndView->SetViewState(viewLine, DiffState::Normal);
6132 line.state = DiffState::Normal;
6133 [[fallthrough]];
6134 case DiffState::Normal:
6135 break;
6136 default:
6137 break;
6139 bool marked = GetViewMarked(viewLine);
6140 SetViewData(viewLine, line);
6141 if (marked)
6142 SetViewMarked(viewLine, false);
6143 if ((m_texttype == UnicodeType::ASCII) && (pwndView->GetTextType() != UnicodeType::ASCII))
6145 // if this view is in ASCII and the other is not, we have to make sure that
6146 // the text we copy from the other view can actually be saved in ASCII encoding.
6147 // if not, we have to change this views encoding to the same encoding as the other view
6148 BOOL useDefault = FALSE;
6149 WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, line.sLine, -1, nullptr, 0, 0, &useDefault);
6150 if (useDefault) // a default char is required, so the char can not be saved as ASCII
6151 SetTextType(pwndView->GetTextType());
6154 // normal lines is mostly same but may differ in EOL so any copied line change view state to modified
6155 // TODO: check if copied line is same as original one set modified only when differ
6156 SetModified();
6157 SaveUndoStep();
6159 int nRemovedLines = CleanEmptyLines();
6160 SaveUndoStep();
6161 //VerifyEols();
6162 // make sure all non empty line have EOL set but last
6163 // wrong can be last copied line(have eol, but no line under),
6164 // or old last line (line before copied block missing eol, but have line under)
6165 // we'll check all lines to be sure
6166 int nLine = GetViewCount();
6167 // check last line have no EOL set
6168 while (--nLine>=0)
6170 if (!IsViewLineEmpty(nLine))
6172 if (GetViewLineEnding(nLine) != EOL::NoEnding)
6174 // we added non last line into empty block on the end (or should we remove eol from this one ?)
6175 // so next line is empty
6176 ASSERT(IsViewLineEmpty(nLine+1));
6177 // and we can turn it to normal empty line
6178 SetViewData(nLine + 1, viewdata(CString(), DiffState::Added, 1, EOL::NoEnding, HideState::Shown));
6180 break;
6183 // check all (nonlast) line have EOL set
6184 while (--nLine>=0)
6186 if (!IsViewLineEmpty(nLine))
6188 if (GetViewLineEnding(nLine) == EOL::NoEnding)
6190 SetViewLineEnding(nLine, m_lineendings);
6191 // in theory there should be only one line needing fix, but most of time we get over all anyway
6192 // break;
6196 SaveUndoStep();
6197 UpdateViewLineNumbers();
6198 SaveUndoStep();
6200 CUndo::GetInstance().EndGrouping();
6202 if (nRemovedLines!=0)
6204 // some lines are gone update selection
6205 ClearSelection();
6206 SetupAllViewSelection(nFirstViewLine, nLastViewLine - nRemovedLines);
6208 BuildAllScreen2ViewVector();
6209 pwndView->Invalidate();
6210 RefreshViews();
6213 void CBaseView::MarkBlock(bool marked, int nFirstViewLine, int nLastViewLine)
6215 if (!IsWritable())
6216 return;
6217 CUndo::GetInstance().BeginGrouping();
6219 for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++)
6220 SetViewMarked(viewLine, marked);
6222 SetModified();
6223 SaveUndoStep();
6224 CUndo::GetInstance().EndGrouping();
6226 BuildAllScreen2ViewVector();
6227 Invalidate();
6228 RefreshViews();
6231 void CBaseView::LeaveOnlyMarkedBlocks(CBaseView *pwndView)
6233 auto fn = [this](int viewLine) -> bool { return GetViewMarked(viewLine) || GetViewState(viewLine) == DiffState::Edited; };
6234 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6237 void CBaseView::UseViewFileOfMarked(CBaseView *pwndView)
6239 auto fn = [this](int viewLine) -> bool { return !GetViewMarked(viewLine) || GetViewState(viewLine) == DiffState::Edited; };
6240 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6243 void CBaseView::UseViewFileExceptEdited(CBaseView *pwndView)
6245 auto fn = [this](int viewLine) -> bool { return GetViewState(viewLine) == DiffState::Edited; };
6246 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6249 int CBaseView::GetLargestSpaceStreak(const CString& line)
6251 int count = 0;
6252 int maxstreak = 0;
6253 for (int i = 0; i < line.GetLength(); ++i)
6255 if (line[i] == ' ')
6256 ++count;
6257 else
6259 maxstreak = std::max(count, maxstreak);
6260 count = 0;
6263 return std::max(count, maxstreak);
6266 int CBaseView::GetIndentCharsForLine(int x, int y)
6268 const int maxGuessLine = 100;
6269 int nTabMode = -1;
6270 const CString& line = GetViewLine(y);
6271 if (m_nTabMode & TABMODE_SMARTINDENT)
6273 // if the line contains one tab, use tabs
6274 // we can not test for spaces, since even if tabs are used,
6275 // spaces are used in a tabified file for alignment.
6276 if (line.Find(L'\t') >= 0)
6277 nTabMode = 0; // use tabs
6278 else if (GetLargestSpaceStreak(line) > m_nTabSize)
6279 nTabMode = 1; // use spaces
6281 // detect lines nearby
6282 for (int i = y - 1, j = y + 1; nTabMode == -1; --i, ++j)
6284 bool above = i >= 0 && i >= y - maxGuessLine;
6285 bool below = j < GetViewCount() && j <= y + maxGuessLine;
6286 if (!(above || below))
6287 break;
6288 auto ac = CString();
6289 auto bc = CString();
6290 if (above)
6291 ac = GetViewLine(i);
6292 if (below)
6293 bc = GetViewLine(j);
6294 if ((ac.Find(L'\t') >= 0) || (bc.Find(L'\t') >= 0))
6296 nTabMode = 0;
6297 break;
6299 else if ((GetLargestSpaceStreak(ac) > m_nTabSize) && (GetLargestSpaceStreak(bc) > m_nTabSize))
6301 nTabMode = 1;
6302 break;
6306 else
6307 nTabMode = m_nTabMode & TABMODE_USESPACES;
6309 if (nTabMode > 0)
6311 // use spaces
6312 x = CountExpandedChars(line, x);
6313 return (m_nTabSize - (x % m_nTabSize));
6316 // use tab
6317 return 0;
6320 void CBaseView::AddIndentationForSelectedBlock()
6322 bool bModified = false;
6323 for (int nViewLine = m_ptSelectionViewPosStart.y; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
6325 // skip the line if no character is selected in the last selected line
6326 if (nViewLine == m_ptSelectionViewPosEnd.y && m_ptSelectionViewPosEnd.x == 0)
6328 continue;
6330 // skip empty lines
6331 if (IsLineEmpty(nViewLine))
6333 continue;
6335 const CString &sLine = GetViewLine(nViewLine);
6336 CString sTemp = sLine;
6337 if (sTemp.Trim().IsEmpty())
6339 // skip empty and whitechar only lines
6340 continue;
6342 // add tab to line start (alternatively m_nTabSize spaces can be used)
6343 CString tabStr;
6344 int indentChars = GetIndentCharsForLine(0, nViewLine);
6345 tabStr = indentChars > 0 ? CString(L' ', indentChars) : CString("\t");
6346 SetViewLine(nViewLine, tabStr + sLine);
6347 bModified = true;
6349 if (bModified)
6351 SetModified();
6352 SaveUndoStep();
6353 BuildAllScreen2ViewVector();
6357 void CBaseView::RemoveIndentationForSelectedBlock()
6359 bool bModified = false;
6360 for (int nViewLine = m_ptSelectionViewPosStart.y; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
6362 // skip the line if no character is selected in the last selected line
6363 if (nViewLine == m_ptSelectionViewPosEnd.y && m_ptSelectionViewPosEnd.x == 0)
6365 continue;
6367 // skip empty lines
6368 if (IsLineEmpty(nViewLine))
6370 continue;
6372 CString sLine = GetViewLine(nViewLine);
6373 // remove up to n spaces from line start
6374 // and one tab (if less then n spaces was removed)
6375 int nPos = 0;
6376 while (nPos<m_nTabSize)
6378 switch (sLine[nPos])
6380 case ' ':
6381 nPos++;
6382 continue;
6383 case '\t':
6384 nPos++;
6386 break;
6388 if (nPos>0)
6390 sLine.Delete(0, nPos);
6391 SetViewLine(nViewLine, sLine);
6392 bModified = true;
6395 if (bModified)
6397 SetModified();
6398 SaveUndoStep();
6399 BuildAllScreen2ViewVector();
6404 there are two possible versions
6405 - convert tabs to spaces only in front of text (implemented)
6406 - convert all tabs to spaces
6408 void CBaseView::ConvertTabToSpaces()
6410 bool bModified = false;
6411 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6413 if (IsLineEmpty(nViewLine))
6415 continue;
6417 const CString &sLine = GetViewLine(nViewLine);
6418 bool bTabToConvertFound = false;
6419 int nPosIn = 0;
6420 int nPosOut = 0;
6421 while (nPosIn<sLine.GetLength())
6423 switch (sLine[nPosIn])
6425 case ' ':
6426 nPosIn++;
6427 nPosOut++;
6428 continue;
6429 case '\t':
6430 nPosIn++;
6431 bTabToConvertFound = true;
6432 nPosOut = (nPosOut+m_nTabSize) - nPosOut%m_nTabSize;
6433 continue;
6435 break;
6437 if (bTabToConvertFound)
6439 CString sLineNew = sLine;
6440 sLineNew.Delete(0, nPosIn);
6441 sLineNew = CString(' ', nPosOut) + sLineNew;
6442 SetViewLine(nViewLine, sLineNew);
6443 bModified = true;
6446 if (bModified)
6448 SetModified();
6449 SaveUndoStep();
6450 BuildAllScreen2ViewVector();
6455 there are two possible version
6456 - convert spaces to tabs only in front of text (implemented)
6457 - convert all spaces to tabs
6459 void CBaseView::Tabularize()
6461 bool bModified = false;
6462 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6464 if (IsLineEmpty(nViewLine))
6466 continue;
6468 const CString &sLine = GetViewLine(nViewLine);
6469 int nDel = 0;
6470 int nTabCount = 0; // total tabs to be used
6471 int nSpaceCount = 0; // number of spaces in tab size run
6472 int nPos = 0;
6473 while (nPos<sLine.GetLength())
6475 switch (sLine[nPos++])
6477 case ' ':
6478 //bSpace = true;
6479 if (++nSpaceCount < m_nTabSize)
6481 continue;
6483 [[fallthrough]];
6484 case '\t':
6485 nTabCount++;
6486 nSpaceCount = 0;
6487 nDel = nPos;
6488 continue;
6490 break;
6492 if (nDel > 0)
6494 CString sLineNew = sLine;
6495 sLineNew.Delete(0, nDel);
6496 sLineNew = CString('\t', nTabCount) + sLineNew;
6497 if (sLine!=sLineNew)
6499 SetViewLine(nViewLine, sLineNew);
6500 bModified = true;
6504 if (bModified)
6506 SetModified();
6507 SaveUndoStep();
6508 BuildAllScreen2ViewVector();
6512 void CBaseView::RemoveTrailWhiteChars()
6514 bool bModified = false;
6515 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6517 if (IsLineEmpty(nViewLine))
6519 continue;
6521 const CString &sLine = GetViewLine(nViewLine);
6522 CString sLineNew = sLine;
6523 sLineNew.TrimRight();
6524 if (sLine.GetLength()!=sLineNew.GetLength())
6526 SetViewLine(nViewLine, sLineNew);
6527 bModified = true;
6530 if (bModified)
6532 SetModified();
6533 SaveUndoStep();
6534 BuildAllScreen2ViewVector();
6538 CBaseView::TWhitecharsProperties CBaseView::GetWhitecharsProperties()
6540 if (GetViewCount()>10000)
6542 // 10k lines is enough to check
6543 TWhitecharsProperties oRet = {true, true, true, true};
6544 return oRet;
6546 TWhitecharsProperties oRet = {};
6547 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6549 if (IsLineEmpty(nViewLine))
6551 continue;
6553 const CString &sLine = GetViewLine(nViewLine);
6554 if (sLine.IsEmpty())
6556 continue;
6558 // check leading whites for convertible tabs and spaces
6559 int nPos = 0;
6560 int nSpaceCount = 0; // number of spaces in tab size run
6561 while (nPos<sLine.GetLength() && (!oRet.HasSpacesToConvert || !oRet.HasTabsToConvert))
6563 switch (sLine[nPos++])
6565 case ' ':
6566 if (++nSpaceCount >= m_nTabSize)
6568 oRet.HasSpacesToConvert = true;
6570 continue;
6571 case '\t':
6572 oRet.HasTabsToConvert = true;
6573 if (nSpaceCount!=0)
6575 oRet.HasSpacesToConvert = true;
6577 continue;
6579 break;
6582 // check trailing whites for removable chars
6583 switch (sLine[sLine.GetLength()-1])
6585 case ' ':
6586 case '\t':
6587 oRet.HasTrailWhiteChars = true;
6590 // check EOLs
6591 EOL eLineEol = GetViewLineEnding(nViewLine);
6592 if (!oRet.HasMixedEols && (eLineEol != m_lineendings) && (eLineEol != EOL::AutoLine) && (eLineEol != EOL::NoEnding))
6594 oRet.HasMixedEols = true;
6597 return oRet;
6600 void CBaseView::InsertText(const CString& sText)
6602 ResetUndoStep();
6604 POINT ptCaretViewPos = GetCaretViewPosition();
6605 int nLeft = ptCaretViewPos.x;
6606 int nViewLine = ptCaretViewPos.y;
6608 if ((nViewLine == 0) && (GetViewCount() == 0))
6609 OnChar(VK_RETURN, 0, 0);
6611 std::vector<CString> lines;
6612 int nStart = 0;
6613 int nEolPos = 0;
6614 while ((nEolPos = sText.Find('\r', nEolPos)) >= 0)
6616 CString sLine = sText.Mid(nStart, nEolPos - nStart);
6617 lines.push_back(sLine);
6618 nEolPos++;
6619 nStart = nEolPos;
6621 CString sLine = sText.Mid(nStart);
6622 lines.push_back(sLine);
6624 int nLinesToPaste = static_cast<int>(lines.size());
6625 if (nLinesToPaste > 1)
6627 // multiline text
6629 // We want to undo the multiline insertion in a single step.
6630 CUndo::GetInstance().BeginGrouping();
6632 sLine = GetViewLineChars(nViewLine);
6633 CString sLineLeft = sLine.Left(nLeft);
6634 CString sLineRight = sLine.Right(sLine.GetLength() - nLeft);
6635 EOL eOriginalEnding = GetViewLineEnding(nViewLine);
6636 viewdata newLine(L"", DiffState::Edited, 1, m_lineendings, HideState::Shown);
6637 if (!lines[0].IsEmpty() || !sLineRight.IsEmpty() || (eOriginalEnding != m_lineendings))
6639 newLine.sLine = sLineLeft + lines[0];
6640 SetViewData(nViewLine, newLine);
6643 int nInsertLine = nViewLine;
6644 for (int i = 1; i < nLinesToPaste - 1; i++)
6646 newLine.sLine = lines[i];
6647 InsertViewData(++nInsertLine, newLine);
6649 newLine.sLine = lines[nLinesToPaste - 1] + sLineRight;
6650 newLine.ending = eOriginalEnding;
6651 InsertViewData(++nInsertLine, newLine);
6653 SetModified();
6654 SaveUndoStep();
6656 // adds new lines everywhere except me
6657 if (IsViewGood(m_pwndLeft) && m_pwndLeft != this)
6659 m_pwndLeft->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6661 if (IsViewGood(m_pwndRight) && m_pwndRight != this)
6663 m_pwndRight->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6665 if (IsViewGood(m_pwndBottom) && m_pwndBottom != this)
6667 m_pwndBottom->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6669 UpdateViewLineNumbers();
6670 SaveUndoStep();
6672 CUndo::GetInstance().EndGrouping();
6674 ptCaretViewPos = SetupPoint(lines[nLinesToPaste - 1].GetLength(), nInsertLine);
6676 else
6678 // single line text - just insert it
6679 sLine = GetViewLineChars(nViewLine);
6680 sLine.Insert(nLeft, sText);
6681 ptCaretViewPos = SetupPoint(nLeft + sText.GetLength(), nViewLine);
6682 SetViewLine(nViewLine, sLine);
6684 auto viewState = GetViewState(nViewLine);
6685 if (IsStateEmpty(viewState) || IsStateConflicted(viewState) || viewState == DiffState::IdenticalRemoved)
6687 // if not last line set EOL
6688 for (int nCheckViewLine = nViewLine + 1; nCheckViewLine < GetViewCount(); ++nCheckViewLine)
6690 if (!IsViewLineEmpty(nCheckViewLine))
6692 SetViewLineEnding(nViewLine, m_lineendings);
6693 break;
6696 // make sure previous (non empty) line have EOL set
6697 for (int nCheckViewLine = nViewLine - 1; nCheckViewLine > 0; --nCheckViewLine)
6699 if (!IsViewLineEmpty(nCheckViewLine))
6701 if (GetViewLineEnding(nCheckViewLine) == EOL::NoEnding)
6702 SetViewLineEnding(nCheckViewLine, m_lineendings);
6703 break;
6708 SetViewState(nViewLine, DiffState::Edited);
6709 SetModified();
6710 SaveUndoStep();
6713 RefreshViews();
6714 BuildAllScreen2ViewVector();
6715 UpdateCaretViewPosition(ptCaretViewPos);
6718 ULONG CBaseView::GetGestureStatus(CPoint /*ptTouch*/)
6720 return 0;