Calculate the char width when wrapping using real text
[TortoiseGit.git] / src / TortoiseMerge / BaseView.cpp
blob40afb829b7d2d7a1524cbf42f60adb7e323291e5
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2003-2021 - TortoiseSVN
4 // Copyright (C) 2011-2012, 2017-2021 Sven Strickroth <email@cs-ware.de>
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(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()
73 : m_pCacheBitmap(nullptr)
74 , m_pViewData(nullptr)
75 , m_pOtherViewData(nullptr)
76 , m_pOtherView(nullptr)
77 , m_nLineHeight(-1)
78 , m_nCharWidth(-1)
79 , m_nScreenChars(-1)
80 , m_nLastScreenChars(-1)
81 , m_nMaxLineLength(-1)
82 , m_nScreenLines(-1)
83 , m_nTopLine(0)
84 , m_nOffsetChar(0)
85 , m_nDigits(0)
86 , m_nMouseLine(-1)
87 , m_nLDownLine(-1)
88 , m_mouseInMargin(false)
89 , m_bIsHidden(FALSE)
90 , m_lineendings(EOL_AUTOLINE)
91 , m_bReadonly(true)
92 , m_bReadonlyIsChangable(false)
93 , m_bTarget(false)
94 , m_nCaretGoalPos(0)
95 , m_nSelViewBlockStart(-1)
96 , m_nSelViewBlockEnd(-1)
97 , m_bFocused(FALSE)
98 , m_bShowSelection(true)
99 , m_texttype(CFileTextLines::AUTOTYPE)
100 , m_bModified(FALSE)
101 , m_bOtherDiffChecked(false)
102 , m_bInlineWordDiff(true)
103 , m_bWhitespaceInlineDiffs(false)
104 , m_pState(nullptr)
105 , m_pFindDialog(nullptr)
106 , m_nStatusBarID(0)
107 , m_bMatchCase(false)
108 , m_bLimitToDiff(true)
109 , m_bWholeWord(false)
110 , m_pDC(nullptr)
111 , m_pWorkingFile(nullptr)
112 , m_bInsertMode(true)
113 , m_bEditorConfigEnabled(false)
114 , m_bEditorConfigLoaded(2) // 2 = not evaluated
115 , m_bDark(false)
116 , m_themeCallbackId(0)
117 , m_MarkedWordCount(0)
119 m_ptCaretViewPos.x = 0;
120 m_ptCaretViewPos.y = 0;
121 m_ptSelectionViewPosStart = m_ptCaretViewPos;
122 m_ptSelectionViewPosEnd = m_ptSelectionViewPosStart;
123 m_ptSelectionViewPosOrigin = m_ptSelectionViewPosEnd;
124 m_bViewWhitespace = CRegDWORD(L"Software\\TortoiseGitMerge\\ViewWhitespaces", 1);
125 m_bViewLinenumbers = CRegDWORD(L"Software\\TortoiseGitMerge\\ViewLinenumbers", 1);
126 m_bShowInlineDiff = CRegDWORD(L"Software\\TortoiseGitMerge\\DisplayBinDiff", TRUE);
127 m_nInlineDiffMaxLineLength = CRegDWORD(L"Software\\TortoiseGitMerge\\InlineDiffMaxLineLength", 3000);
128 m_InlineAddedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\InlineAdded", INLINEADDED_COLOR);
129 m_InlineRemovedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\InlineRemoved", INLINEREMOVED_COLOR);
130 m_ModifiedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\ColorModifiedB", MODIFIED_COLOR);
131 m_InlineAddedDarkBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\DarkInlineAdded", INLINEADDED_DARK_COLOR);
132 m_InlineRemovedDarkBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\DarkInlineRemoved", INLINEREMOVED_DARK_COLOR);
133 m_ModifiedDarkBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\DarkColorModifiedB", MODIFIED_DARK_COLOR);
134 m_WhiteSpaceFg = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\Whitespace", CTheme::Instance().GetThemeColor(GetSysColor(COLOR_3DSHADOW)));
135 m_sWordSeparators = CRegString(L"Software\\TortoiseGitMerge\\WordSeparators", L"[]();:.,{}!@#$%^&*-+=|/\\<>'`~\"?");
136 m_bIconLFs = CRegDWORD(L"Software\\TortoiseGitMerge\\IconLFs", 0);
137 m_nTabSize = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabSize", 4));
138 m_nTabMode = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabMode", TABMODE_NONE));
139 m_bEditorConfigEnabled = !!static_cast<DWORD>(CRegDWORD(L"Software\\TortoiseGitMerge\\EnableEditorConfig", FALSE));
140 std::fill_n(m_apFonts, fontsCount, static_cast<CFont*>(nullptr));
142 int cxIcon = GetSystemMetrics(SM_CXSMICON);
143 int cyIcon = GetSystemMetrics(SM_CYSMICON);
144 m_hConflictedIcon = CCommonAppUtils::LoadIconEx(IDI_CONFLICTEDLINE, cxIcon, cyIcon);
145 m_hConflictedIgnoredIcon = CCommonAppUtils::LoadIconEx(IDI_CONFLICTEDIGNOREDLINE, cxIcon, cyIcon);
146 m_hRemovedIcon = CCommonAppUtils::LoadIconEx(IDI_REMOVEDLINE, cxIcon, cyIcon);
147 m_hAddedIcon = CCommonAppUtils::LoadIconEx(IDI_ADDEDLINE, cxIcon, cyIcon);
148 m_hWhitespaceBlockIcon = CCommonAppUtils::LoadIconEx(IDI_WHITESPACELINE, cxIcon, cyIcon);
149 m_hEqualIcon = CCommonAppUtils::LoadIconEx(IDI_EQUALLINE, cxIcon, cyIcon);
150 m_hLineEndingCR = CCommonAppUtils::LoadIconEx(IDI_LINEENDINGCR, cxIcon, cyIcon);
151 m_hLineEndingCRLF = CCommonAppUtils::LoadIconEx(IDI_LINEENDINGCRLF, cxIcon, cyIcon);
152 m_hLineEndingLF = CCommonAppUtils::LoadIconEx(IDI_LINEENDINGLF, cxIcon, cyIcon);
153 m_hEditedIcon = CCommonAppUtils::LoadIconEx(IDI_LINEEDITED, cxIcon, cyIcon);
154 m_hMovedIcon = CCommonAppUtils::LoadIconEx(IDI_MOVEDLINE, cxIcon, cyIcon);
155 m_hMarkedIcon = CCommonAppUtils::LoadIconEx(IDI_LINEMARKED, cxIcon, cyIcon);
156 m_margincursor = static_cast<HCURSOR>(LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_MARGINCURSOR), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE));
158 for (int i=0; i<1024; ++i)
159 m_sConflictedText += L"??";
160 m_sNoLineNr.LoadString(IDS_EMPTYLINETT);
162 m_szTip[0] = 0;
163 m_wszTip[0] = 0;
164 SecureZeroMemory(&m_lfBaseFont, sizeof(m_lfBaseFont));
165 EnableToolTips();
167 m_Eols[EOL_LF] = L"\n"; // x0a
168 m_Eols[EOL_CR] = L"\r"; // x0d
169 m_Eols[EOL_CRLF] = L"\r\n"; // x0d x0a
170 m_Eols[EOL_LFCR] = L"\n\r";
171 m_Eols[EOL_VT] = L"\v"; // x0b
172 m_Eols[EOL_FF] = L"\f"; // x0c
173 m_Eols[EOL_NEL] = L"\x85";
174 m_Eols[EOL_LS] = L"\x2028";
175 m_Eols[EOL_PS] = L"\x2029";
176 m_Eols[EOL_AUTOLINE] = m_Eols[m_lineendings==EOL_AUTOLINE
177 ? EOL_CRLF
178 : m_lineendings];
179 m_SaveParams.m_LineEndings = EOL::EOL_AUTOLINE;
180 m_SaveParams.m_UnicodeType = CFileTextLines::AUTOTYPE;
182 m_themeCallbackId = CTheme::Instance().RegisterThemeChangeCallback([this]() { SetTheme(CTheme::Instance().IsDarkTheme()); });
185 CBaseView::~CBaseView()
187 CTheme::Instance().RemoveRegisteredCallback(m_themeCallbackId);
188 ReleaseBitmap();
189 DeleteFonts();
190 DestroyCursor(m_margincursor);
193 BEGIN_MESSAGE_MAP(CBaseView, CView)
194 ON_WM_VSCROLL()
195 ON_WM_HSCROLL()
196 ON_WM_ERASEBKGND()
197 ON_WM_CREATE()
198 ON_WM_DESTROY()
199 ON_WM_SIZE()
200 ON_WM_MOUSEWHEEL()
201 ON_WM_MOUSEHWHEEL()
202 ON_WM_SETCURSOR()
203 ON_WM_KILLFOCUS()
204 ON_WM_SETFOCUS()
205 ON_WM_CONTEXTMENU()
206 ON_COMMAND(ID_NAVIGATE_NEXTDIFFERENCE, OnMergeNextdifference)
207 ON_COMMAND(ID_NAVIGATE_PREVIOUSDIFFERENCE, OnMergePreviousdifference)
208 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
209 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
210 ON_WM_KEYDOWN()
211 ON_WM_LBUTTONDOWN()
212 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
213 ON_WM_MOUSEMOVE()
214 ON_COMMAND(ID_NAVIGATE_PREVIOUSCONFLICT, OnMergePreviousconflict)
215 ON_COMMAND(ID_NAVIGATE_NEXTCONFLICT, OnMergeNextconflict)
216 ON_WM_CHAR()
217 ON_COMMAND(ID_CARET_DOWN, &CBaseView::OnCaretDown)
218 ON_COMMAND(ID_CARET_LEFT, &CBaseView::OnCaretLeft)
219 ON_COMMAND(ID_CARET_RIGHT, &CBaseView::OnCaretRight)
220 ON_COMMAND(ID_CARET_UP, &CBaseView::OnCaretUp)
221 ON_COMMAND(ID_CARET_WORDLEFT, &CBaseView::OnCaretWordleft)
222 ON_COMMAND(ID_CARET_WORDRIGHT, &CBaseView::OnCaretWordright)
223 ON_COMMAND(ID_EDIT_CUT, &CBaseView::OnEditCut)
224 ON_COMMAND(ID_EDIT_PASTE, &CBaseView::OnEditPaste)
225 ON_WM_TIMER()
226 ON_WM_LBUTTONDBLCLK()
227 ON_COMMAND(ID_NAVIGATE_NEXTINLINEDIFF, &CBaseView::OnNavigateNextinlinediff)
228 ON_COMMAND(ID_NAVIGATE_PREVINLINEDIFF, &CBaseView::OnNavigatePrevinlinediff)
229 ON_COMMAND(ID_EDIT_SELECTALL, &CBaseView::OnEditSelectall)
230 ON_COMMAND(ID_EDIT_FIND, OnEditFind)
231 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
232 ON_COMMAND(ID_EDIT_FINDNEXT, OnEditFindnext)
233 ON_COMMAND(ID_EDIT_FINDPREV, OnEditFindprev)
234 ON_COMMAND(ID_EDIT_FINDNEXTSTART, OnEditFindnextStart)
235 ON_COMMAND(ID_EDIT_FINDPREVSTART, OnEditFindprevStart)
236 ON_COMMAND(ID_EDIT_GOTOLINE, &CBaseView::OnEditGotoline)
237 ON_WM_LBUTTONUP()
238 END_MESSAGE_MAP()
241 void CBaseView::DocumentUpdated()
243 ReleaseBitmap();
244 m_nLineHeight = -1;
245 m_nCharWidth = -1;
246 m_nScreenChars = -1;
247 m_nLastScreenChars = -1;
248 m_nMaxLineLength = -1;
249 m_nScreenLines = -1;
250 m_nTopLine = 0;
251 m_bModified = FALSE;
252 m_bOtherDiffChecked = false;
253 m_nDigits = 0;
254 m_nMouseLine = -1;
255 m_nLDownLine = -1;
256 m_nTabSize = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabSize", 4));
257 m_nTabMode = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabMode", TABMODE_NONE));
258 m_bViewLinenumbers = CRegDWORD(L"Software\\TortoiseGitMerge\\ViewLinenumbers", 1);
259 m_bIconLFs = CRegDWORD(L"Software\\TortoiseGitMerge\\IconLFs", 0);
260 m_nInlineDiffMaxLineLength = CRegDWORD(L"Software\\TortoiseGitMerge\\InlineDiffMaxLineLength", 3000);
261 m_Eols[EOL_AUTOLINE] = m_Eols[m_lineendings==EOL_AUTOLINE
262 ? EOL_CRLF
263 : m_lineendings];
264 SetEditorConfigEnabled(m_bEditorConfigEnabled);
265 DeleteFonts();
266 ClearCurrentSelection();
267 UpdateStatusBar();
268 SetTheme(CTheme::Instance().IsDarkTheme());
269 Invalidate();
272 void CBaseView::SetEditorConfigEnabled(bool bEditorConfigEnabled)
274 m_bEditorConfigEnabled = bEditorConfigEnabled;
275 m_nTabSize = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabSize", 4));
276 m_nTabMode = static_cast<int>(CRegDWORD(L"Software\\TortoiseGitMerge\\TabMode", TABMODE_NONE));
277 if (m_bEditorConfigEnabled)
279 m_bEditorConfigLoaded = FALSE; // no editorconfig entries loaded
280 CEditorConfigWrapper ec;
281 if (ec.Load(m_sReflectedName.IsEmpty() ? m_sFullFilePath : m_sReflectedName))
283 m_bEditorConfigLoaded = TRUE;
284 if (ec.m_nTabWidth != nullptr)
285 m_nTabSize = ec.m_nTabWidth;
286 if (ec.m_bIndentStyle != nullptr)
287 m_nTabMode = (m_nTabMode & ~TABMODE_USESPACES) | (ec.m_bIndentStyle ? TABMODE_USESPACES : TABMODE_NONE);
292 static CString GetTabModeString(int nTabMode, int nTabSize, bool bEditorConfig)
294 CString text;
295 if (nTabMode & TABMODE_USESPACES)
296 text = L"Space";
297 else
298 text = L"Tab";
299 text.AppendFormat(L" %d", nTabSize);
300 if (nTabMode & TABMODE_SMARTINDENT)
301 text += L" Smart";
302 if (bEditorConfig)
303 text += L" EC";
304 return text;
307 void CBaseView::UpdateStatusBar()
309 int nRemovedLines = 0;
310 int nAddedLines = 0;
311 int nConflictedLines = 0;
313 if (m_pViewData)
315 for (int i=0; i<m_pViewData->GetCount(); i++)
317 DiffStates state = m_pViewData->GetState(i);
318 switch (state)
320 case DIFFSTATE_ADDED:
321 case DIFFSTATE_IDENTICALADDED:
322 case DIFFSTATE_THEIRSADDED:
323 case DIFFSTATE_YOURSADDED:
324 case DIFFSTATE_CONFLICTADDED:
325 nAddedLines++;
326 break;
327 case DIFFSTATE_IDENTICALREMOVED:
328 case DIFFSTATE_REMOVED:
329 case DIFFSTATE_THEIRSREMOVED:
330 case DIFFSTATE_YOURSREMOVED:
331 nRemovedLines++;
332 break;
333 case DIFFSTATE_CONFLICTED:
334 case DIFFSTATE_CONFLICTED_IGNORED:
335 nConflictedLines++;
336 break;
341 CString sBarText;
342 CString sTemp;
344 if (m_pwndStatusBar)
346 sBarText += CFileTextLines::GetEncodingName(m_texttype);
347 sBarText += sBarText.IsEmpty() ? L"" : L" ";
348 sBarText += GetEolName(m_lineendings);
349 sBarText += sBarText.IsEmpty() ? L"" : L" ";
351 if (sBarText.IsEmpty())
352 sBarText += L" / ";
355 if (nRemovedLines)
357 sTemp.Format(IDS_STATUSBAR_REMOVEDLINES, nRemovedLines);
358 if (!sBarText.IsEmpty())
359 sBarText += L" / ";
360 sBarText += sTemp;
362 if (nAddedLines)
364 sTemp.Format(IDS_STATUSBAR_ADDEDLINES, nAddedLines);
365 if (!sBarText.IsEmpty())
366 sBarText += L" / ";
367 sBarText += sTemp;
369 if (nConflictedLines)
371 sTemp.Format(IDS_STATUSBAR_CONFLICTEDLINES, nConflictedLines);
372 if (!sBarText.IsEmpty())
373 sBarText += L" / ";
374 sBarText += sTemp;
376 if (m_pwndStatusBar || m_pwndRibbonStatusBar)
378 if (m_pwndStatusBar)
380 UINT nID;
381 UINT nStyle;
382 int cxWidth;
383 if (m_nStatusBarID == ID_INDICATOR_BOTTOMVIEW)
385 sBarText.Format(IDS_STATUSBAR_CONFLICTS, nConflictedLines);
387 if (m_nStatusBarID == ID_INDICATOR_LEFTVIEW)
389 sTemp.LoadString(IDS_STATUSBAR_LEFTVIEW);
390 sBarText = sTemp+sBarText;
392 if (m_nStatusBarID == ID_INDICATOR_RIGHTVIEW)
394 sTemp.LoadString(IDS_STATUSBAR_RIGHTVIEW);
395 sBarText = sTemp+sBarText;
397 int nIndex = m_pwndStatusBar->CommandToIndex(m_nStatusBarID);
398 m_pwndStatusBar->GetPaneInfo(nIndex, nID, nStyle, cxWidth);
399 //calculate the width of the text
400 CDC * pDC = m_pwndStatusBar->GetDC();
401 if (pDC)
403 CSize size = pDC->GetTextExtent(sBarText);
404 m_pwndStatusBar->SetPaneInfo(nIndex, nID, nStyle, size.cx+2);
405 ReleaseDC(pDC);
407 m_pwndStatusBar->SetPaneText(nIndex, sBarText);
409 else if (m_pwndRibbonStatusBar)
411 if (!IsViewGood(m_pwndBottom))
412 m_pwndRibbonStatusBar->RemoveElement(ID_INDICATOR_BOTTOMVIEW);
413 if ((m_nStatusBarID == ID_INDICATOR_BOTTOMVIEW) && (IsViewGood(this)))
415 m_pwndRibbonStatusBar->RemoveElement(ID_INDICATOR_BOTTOMVIEW);
416 auto apBtnGroupBottom = std::make_unique<CMFCRibbonButtonsGroup>();
417 apBtnGroupBottom->SetID(ID_INDICATOR_BOTTOMVIEW);
418 apBtnGroupBottom->AddButton(new CMFCRibbonStatusBarPane(ID_SEPARATOR, CString(MAKEINTRESOURCE(IDS_STATUSBAR_BOTTOMVIEW)), TRUE));
419 CMFCRibbonButton * pButton = new CMFCRibbonButton(ID_INDICATOR_BOTTOMVIEWCOMBOENCODING, L"");
420 m_pMainFrame->FillEncodingButton(pButton, ID_INDICATOR_BOTTOMENCODINGSTART);
421 apBtnGroupBottom->AddButton(pButton);
422 pButton = new CMFCRibbonButton(ID_INDICATOR_BOTTOMVIEWCOMBOEOL, L"");
423 m_pMainFrame->FillEOLButton(pButton, ID_INDICATOR_BOTTOMEOLSTART);
424 apBtnGroupBottom->AddButton(pButton);
425 pButton = new CMFCRibbonButton(ID_INDICATOR_BOTTOMVIEWCOMBOTABMODE, L"");
426 m_pMainFrame->FillTabModeButton(pButton, ID_INDICATOR_BOTTOMTABMODESTART);
427 apBtnGroupBottom->AddButton(pButton);
428 apBtnGroupBottom->AddButton(new CMFCRibbonStatusBarPane(ID_INDICATOR_BOTTOMVIEW, L"", TRUE));
429 m_pwndRibbonStatusBar->AddExtendedElement(apBtnGroupBottom.release(), L"");
432 CMFCRibbonButtonsGroup * pGroup = DYNAMIC_DOWNCAST(CMFCRibbonButtonsGroup, m_pwndRibbonStatusBar->FindByID(m_nStatusBarID));
433 if (pGroup)
435 CMFCRibbonStatusBarPane* pPane = DYNAMIC_DOWNCAST(CMFCRibbonStatusBarPane, pGroup->GetButton(4));
436 if (pPane)
438 pPane->SetText(sBarText);
440 CMFCRibbonButton * pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pGroup->GetButton(1));
441 if (pButton)
443 pButton->SetText(CFileTextLines::GetEncodingName(m_texttype));
444 pButton->SetDescription(CFileTextLines::GetEncodingName(m_texttype));
446 pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pGroup->GetButton(2));
447 if (pButton)
449 pButton->SetText(GetEolName(m_lineendings));
450 pButton->SetDescription(GetEolName(m_lineendings));
452 pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pGroup->GetButton(3));
453 if (pButton)
455 pButton->SetText(GetTabModeString(m_nTabMode, m_nTabSize, m_bEditorConfigEnabled && m_bEditorConfigLoaded));
456 pButton->SetDescription(GetTabModeString(m_nTabMode, m_nTabSize, m_bEditorConfigEnabled && m_bEditorConfigLoaded));
459 m_pwndRibbonStatusBar->RecalcLayout();
460 m_pwndRibbonStatusBar->Invalidate();
465 BOOL CBaseView::PreCreateWindow(CREATESTRUCT& cs)
467 if (!CView::PreCreateWindow(cs))
468 return FALSE;
470 cs.dwExStyle |= WS_EX_CLIENTEDGE;
471 cs.style &= ~WS_BORDER;
472 cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
473 ::LoadCursor(nullptr, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1), nullptr);
475 CWnd *pParentWnd = CWnd::FromHandlePermanent(cs.hwndParent);
476 if (!pParentWnd || !pParentWnd->IsKindOf(RUNTIME_CLASS(CSplitterWnd)))
478 // View must always create its own scrollbars,
479 // if only it's not used within splitter
480 cs.style |= (WS_HSCROLL | WS_VSCROLL);
482 cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS);
483 return TRUE;
486 CFont* CBaseView::GetFont(BOOL bItalic /*= FALSE*/, BOOL bBold /*= FALSE*/)
488 int nIndex = 0;
489 if (bBold)
490 nIndex |= 1;
491 if (bItalic)
492 nIndex |= 2;
493 if (!m_apFonts[nIndex])
495 m_apFonts[nIndex] = new CFont;
496 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
497 m_lfBaseFont.lfWeight = bBold ? FW_BOLD : FW_NORMAL;
498 m_lfBaseFont.lfItalic = static_cast<BYTE>(bItalic);
499 m_lfBaseFont.lfHeight = -CDPIAware::Instance().PointsToPixelsY(static_cast<DWORD>(CRegDWORD(L"Software\\TortoiseGitMerge\\LogFontSize", 10)));
500 wcsncpy_s(m_lfBaseFont.lfFaceName, static_cast<LPCWSTR>(static_cast<CString>(CRegString(L"Software\\TortoiseGitMerge\\LogFontName", L"Consolas"))), _countof(m_lfBaseFont.lfFaceName) - 1);
501 if (!m_apFonts[nIndex]->CreateFontIndirect(&m_lfBaseFont))
503 delete m_apFonts[nIndex];
504 m_apFonts[nIndex] = nullptr;
505 return CView::GetFont();
508 return m_apFonts[nIndex];
511 void CBaseView::CalcLineCharDim()
513 CDC *pDC = GetDC();
514 if (!pDC)
515 return;
516 CFont *pOldFont = pDC->SelectObject(GetFont());
517 const CSize szCharExt = pDC->GetTextExtent(L"X");
518 pDC->SelectObject(pOldFont);
519 ReleaseDC(pDC);
521 m_nLineHeight = szCharExt.cy;
522 if (m_nLineHeight <= 0)
523 m_nLineHeight = -1;
524 m_nCharWidth = szCharExt.cx;
525 if (m_nCharWidth <= 0)
526 m_nCharWidth = -1;
529 int CBaseView::GetScreenChars()
531 if (m_nScreenChars == -1)
533 CRect rect;
534 GetClientRect(&rect);
535 m_nScreenChars = (rect.Width() - GetMarginWidth() - GetSystemMetrics(SM_CXVSCROLL)) / GetCharWidth();
536 if (m_nScreenChars < 0)
537 m_nScreenChars = 0;
539 return m_nScreenChars;
542 int CBaseView::GetAllMinScreenChars() const
544 int nChars = INT_MAX;
545 if (IsLeftViewGood())
546 nChars = std::min<int>(nChars, m_pwndLeft->GetScreenChars());
547 if (IsRightViewGood())
548 nChars = std::min<int>(nChars, m_pwndRight->GetScreenChars());
549 if (IsBottomViewGood())
550 nChars = std::min<int>(nChars, m_pwndBottom->GetScreenChars());
551 return (nChars==INT_MAX) ? 0 : nChars;
554 int CBaseView::GetAllMaxLineLength() const
556 int nLength = 0;
557 if (IsLeftViewGood())
558 nLength = std::max<int>(nLength, m_pwndLeft->GetMaxLineLength());
559 if (IsRightViewGood())
560 nLength = std::max<int>(nLength, m_pwndRight->GetMaxLineLength());
561 if (IsBottomViewGood())
562 nLength = std::max<int>(nLength, m_pwndBottom->GetMaxLineLength());
563 return nLength;
566 int CBaseView::GetLineHeight()
568 if (m_nLineHeight == -1)
569 CalcLineCharDim();
570 if (m_nLineHeight <= 0)
571 return 1;
572 return m_nLineHeight;
575 int CBaseView::GetCharWidth()
577 if (m_nCharWidth == -1)
578 CalcLineCharDim();
579 if (m_nCharWidth <= 0)
580 return 1;
581 return m_nCharWidth;
584 int CBaseView::GetMaxLineLength()
586 if (m_nMaxLineLength == -1)
588 m_nMaxLineLength = 0;
589 int nLineCount = GetLineCount();
590 if (nLineCount == 1)
592 m_nMaxLineLength = GetLineLengthWithTabsConverted(0);
593 return m_nMaxLineLength;
595 for (int i=0; i<nLineCount; i++)
597 int nActualLength = GetLineLengthWithTabsConverted(i);
598 if (m_nMaxLineLength < nActualLength)
599 m_nMaxLineLength = nActualLength;
602 return m_nMaxLineLength;
605 int CBaseView::GetLineLengthWithTabsConverted(int index)
607 if (!m_pViewData)
608 return 0;
609 if (m_pViewData->GetCount() == 0)
610 return 0;
611 if (m_Screen2View.size() <= index)
612 return 0;
613 CString sLine;
614 if (m_pMainFrame->m_bWrapLines)
615 sLine = GetLineChars(index);
616 else
618 int viewLine = GetViewLineForScreen(index);
619 sLine = m_pViewData->GetLine(viewLine);
621 int tabCount = 0;
622 const wchar_t* pChar = sLine;
623 auto nLineLength = sLine.GetLength();
624 for (int i = 0; i < nLineLength; ++i)
626 if (*pChar == '\t')
627 ++tabCount;
628 ++pChar;
630 // GetTabSize() - 1 because the tabs are already counted
631 nLineLength = nLineLength + (tabCount * (GetTabSize() - 1));
632 ASSERT(nLineLength >= 0);
633 return nLineLength;
636 int CBaseView::GetLineLength(int index)
638 if (!m_pViewData)
639 return 0;
640 if (m_pViewData->GetCount() == 0)
641 return 0;
642 if (m_Screen2View.size() <= index)
643 return 0;
644 int viewLine = GetViewLineForScreen(index);
645 if (m_pMainFrame->m_bWrapLines)
647 int nLineLength = GetLineChars(index).GetLength();
648 ASSERT(nLineLength >= 0);
649 return nLineLength;
651 int nLineLength = m_pViewData->GetLine(viewLine).GetLength();
652 ASSERT(nLineLength >= 0);
653 return nLineLength;
656 int CBaseView::GetViewLineLength(int nViewLine) const
658 if (!m_pViewData)
659 return 0;
660 if (m_pViewData->GetCount() <= nViewLine)
661 return 0;
662 int nLineLength = m_pViewData->GetLine(nViewLine).GetLength();
663 ASSERT(nLineLength >= 0);
664 return nLineLength;
667 int CBaseView::GetLineCount() const
669 if (!m_pViewData)
670 return 1;
671 int nLineCount = m_Screen2View.size();
672 ASSERT(nLineCount >= 0);
673 return nLineCount;
676 int CBaseView::GetSubLineOffset(int index)
678 return m_Screen2View.GetSubLineOffset(index);
681 CString CBaseView::GetViewLineChars(int nViewLine) const
683 if (!m_pViewData)
684 return 0;
685 if (m_pViewData->GetCount() <= nViewLine)
686 return 0;
687 return m_pViewData->GetLine(nViewLine);
690 CString CBaseView::GetLineChars(int index)
692 if (!m_pViewData)
693 return 0;
694 if (m_pViewData->GetCount() == 0)
695 return 0;
696 if (m_Screen2View.size() <= index)
697 return 0;
698 int viewLine = GetViewLineForScreen(index);
699 if (m_pMainFrame->m_bWrapLines)
701 int subLine = GetSubLineOffset(index);
702 if (subLine >= 0)
704 if (subLine < CountMultiLines(viewLine))
706 return m_ScreenedViewLine[viewLine].SubLines[subLine];
708 return L"";
711 return m_pViewData->GetLine(viewLine);
714 void CBaseView::CheckOtherView()
716 if (m_bOtherDiffChecked)
717 return;
718 // find out what the 'other' file is
719 m_pOtherViewData = nullptr;
720 m_pOtherView = nullptr;
721 if (this == m_pwndLeft && IsRightViewGood())
723 m_pOtherViewData = m_pwndRight->m_pViewData;
724 m_pOtherView = m_pwndRight;
727 if (this == m_pwndRight && IsLeftViewGood())
729 m_pOtherViewData = m_pwndLeft->m_pViewData;
730 m_pOtherView = m_pwndLeft;
733 m_bOtherDiffChecked = true;
737 void CBaseView::GetWhitespaceBlock(CViewData *viewData, int nLineIndex, int & nStartBlock, int & nEndBlock)
739 enum { MAX_WHITESPACEBLOCK_SIZE = 8 };
740 ASSERT(viewData);
742 DiffStates origstate = viewData->GetState(nLineIndex);
744 // Go back and forward at most MAX_WHITESPACEBLOCK_SIZE lines to see where this block ends
745 nStartBlock = nLineIndex;
746 nEndBlock = nLineIndex;
747 while ((nStartBlock > 0) && (nStartBlock > (nLineIndex - MAX_WHITESPACEBLOCK_SIZE)))
749 DiffStates state = viewData->GetState(nStartBlock - 1);
750 if ((origstate == DIFFSTATE_EMPTY) && (state != DIFFSTATE_NORMAL))
751 origstate = state;
752 if ((origstate == state) || (state == DIFFSTATE_EMPTY))
753 nStartBlock--;
754 else
755 break;
757 while ((nEndBlock < (viewData->GetCount() - 1)) && (nEndBlock < (nLineIndex + MAX_WHITESPACEBLOCK_SIZE)))
759 DiffStates state = viewData->GetState(nEndBlock + 1);
760 if ((origstate == DIFFSTATE_EMPTY) && (state != DIFFSTATE_NORMAL))
761 origstate = state;
762 if ((origstate == state) || (state == DIFFSTATE_EMPTY))
763 nEndBlock++;
764 else
765 break;
769 CString CBaseView::GetWhitespaceString(CViewData *viewData, int nStartBlock, int nEndBlock)
771 enum { MAX_WHITESPACEBLOCK_SIZE = 8 };
773 int len = 0;
774 for (int i = nStartBlock; i <= nEndBlock; ++i)
775 len += viewData->GetLine(i).GetLength()+2;
777 CString block;
778 // do not check for whitespace blocks if the line is too long, because
779 // reserving a lot of memory here takes too much time (performance hog)
780 if (len > MAX_WHITESPACEBLOCK_SIZE*256)
781 return block;
782 block.Preallocate(len+1);
783 for (int i = nStartBlock; i <= nEndBlock; ++i)
785 block += viewData->GetLine(i);
786 block += m_Eols[viewData->GetLineEnding(i)];
788 return block;
791 bool CBaseView::IsBlockWhitespaceOnly(int nLineIndex, bool& bIdentical, int& blockstart, int& blockend)
793 if (!m_pViewData)
794 return false;
795 bIdentical = false;
796 CheckOtherView();
797 if (!m_pOtherViewData)
798 return false;
799 int viewLine = GetViewLineForScreen(nLineIndex);
800 if (
801 (m_pViewData->GetState(viewLine) == DIFFSTATE_NORMAL) &&
802 (m_pOtherViewData->GetLine(viewLine) == m_pViewData->GetLine(viewLine))
805 bIdentical = true;
806 return false;
808 // first check whether the line itself only has whitespace changes
809 CString mine = m_pViewData->GetLine(viewLine);
810 CString other = m_pOtherViewData->GetLine(min(viewLine, m_pOtherViewData->GetCount() - 1));
811 if (mine.IsEmpty() && other.IsEmpty())
813 bIdentical = true;
814 return false;
817 if (mine == other)
819 bIdentical = true;
820 return true;
822 FilterWhitespaces(mine, other);
823 if (mine == other)
824 return true;
826 int nStartBlock2, nEndBlock2;
827 GetWhitespaceBlock(m_pViewData, viewLine, blockstart, blockend);
828 GetWhitespaceBlock(m_pOtherViewData, min(viewLine, m_pOtherViewData->GetCount() - 1), nStartBlock2, nEndBlock2);
829 mine = GetWhitespaceString(m_pViewData, blockstart, blockend);
830 if (mine.IsEmpty())
831 bIdentical = false;
832 else
834 other = GetWhitespaceString(m_pOtherViewData, nStartBlock2, nEndBlock2);
835 bIdentical = mine == other;
836 FilterWhitespaces(mine, other);
839 return (!mine.IsEmpty()) && (mine == other);
842 bool CBaseView::IsViewLineHidden(int nViewLine)
844 return IsViewLineHidden(m_pViewData, nViewLine);
847 bool CBaseView::IsViewLineHidden(CViewData * pViewData, int nViewLine)
849 return m_pMainFrame->m_bCollapsed && (pViewData->GetHideState(nViewLine)!=HIDESTATE_SHOWN);
852 int CBaseView::GetLineNumber(int index) const
854 if (!m_pViewData)
855 return -1;
856 int viewLine = GetViewLineForScreen(index);
857 if (m_pViewData->GetLineNumber(viewLine)==DIFF_EMPTYLINENUMBER)
858 return -1;
859 return m_pViewData->GetLineNumber(viewLine);
862 int CBaseView::GetScreenLines()
864 if (m_nScreenLines == -1)
866 CRect rect;
867 GetClientRect(&rect);
868 SCROLLBARINFO sbi = { sizeof(sbi) };
869 if (GetScrollBarInfo(OBJID_HSCROLL, &sbi))
871 // only use the scroll bar size if the info is correct and the scrollbar is visible
872 // if anything isn't proper, assume the scrollbar has a size of zero
873 // and calculate the screen lines without it.
874 if (!(sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE) && !(sbi.rgstate[0] & STATE_SYSTEM_UNAVAILABLE))
876 int scrollBarHeight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top;
877 m_nScreenLines = (rect.Height() - HEADERHEIGHT - scrollBarHeight) / GetLineHeight();
878 if (m_nScreenLines < 0)
879 m_nScreenLines = 0;
880 return m_nScreenLines;
883 // if the scroll bar is not visible, unavailable or there was an error,
884 // assume the scroll bar height is zero and return the screen lines here.
885 // Of course, that means the cache (using the member variable) won't work
886 // and we fetch the scroll bar info every time. But in this case the overhead is necessary.
887 return (rect.Height() - HEADERHEIGHT) / GetLineHeight();
889 return m_nScreenLines;
892 int CBaseView::GetAllMinScreenLines() const
894 int nLines = INT_MAX;
895 if (IsLeftViewGood())
896 nLines = m_pwndLeft->GetScreenLines();
897 if (IsRightViewGood())
898 nLines = std::min<int>(nLines, m_pwndRight->GetScreenLines());
899 if (IsBottomViewGood())
900 nLines = std::min<int>(nLines, m_pwndBottom->GetScreenLines());
901 return (nLines == INT_MAX) || (nLines < 0) ? 0 : nLines;
904 int CBaseView::GetAllLineCount() const
906 int nLines = 0;
907 if (IsLeftViewGood())
908 nLines = m_pwndLeft->GetLineCount();
909 if (IsRightViewGood())
910 nLines = std::max<int>(nLines, m_pwndRight->GetLineCount());
911 if (IsBottomViewGood())
912 nLines = std::max<int>(nLines, m_pwndBottom->GetLineCount());
913 return nLines;
916 void CBaseView::RecalcAllVertScrollBars(BOOL bPositionOnly /*= FALSE*/)
918 if (IsLeftViewGood())
919 m_pwndLeft->RecalcVertScrollBar(bPositionOnly);
920 if (IsRightViewGood())
921 m_pwndRight->RecalcVertScrollBar(bPositionOnly);
922 if (IsBottomViewGood())
923 m_pwndBottom->RecalcVertScrollBar(bPositionOnly);
926 void CBaseView::RecalcVertScrollBar(BOOL bPositionOnly /*= FALSE*/)
928 SCROLLINFO si;
929 si.cbSize = sizeof(si);
930 if (bPositionOnly)
932 si.fMask = SIF_POS;
933 si.nPos = m_nTopLine;
935 else
937 EnableScrollBarCtrl(SB_VERT, TRUE);
938 if (GetAllMinScreenLines() >= GetAllLineCount() && m_nTopLine > 0)
940 m_nTopLine = 0;
941 Invalidate();
943 si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_POS | SIF_RANGE;
944 si.nMin = 0;
945 si.nMax = GetAllLineCount();
946 si.nPage = GetAllMinScreenLines();
947 si.nPos = m_nTopLine;
949 VERIFY(SetScrollInfo(SB_VERT, &si));
952 void CBaseView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
954 CView::OnVScroll(nSBCode, nPos, pScrollBar);
955 if (m_pwndLeft)
956 m_pwndLeft->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
957 if (m_pwndRight)
958 m_pwndRight->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
959 if (m_pwndBottom)
960 m_pwndBottom->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
961 if (m_pwndLocator)
962 m_pwndLocator->Invalidate();
965 void CBaseView::OnDoVScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/, CBaseView * master)
967 // Note we cannot use nPos because of its 16-bit nature
968 SCROLLINFO si;
969 si.cbSize = sizeof(si);
970 si.fMask = SIF_ALL;
971 VERIFY(master->GetScrollInfo(SB_VERT, &si));
973 int nPageLines = GetScreenLines();
974 int nLineCount = GetLineCount();
976 int nNewTopLine;
978 static LONG textwidth = 0;
979 static CString sFormat(MAKEINTRESOURCE(IDS_VIEWSCROLLTIPTEXT));
980 switch (nSBCode)
982 case SB_TOP:
983 nNewTopLine = 0;
984 break;
985 case SB_BOTTOM:
986 nNewTopLine = nLineCount - nPageLines + 1;
987 break;
988 case SB_LINEUP:
989 nNewTopLine = m_nTopLine - 1;
990 break;
991 case SB_LINEDOWN:
992 nNewTopLine = m_nTopLine + 1;
993 break;
994 case SB_PAGEUP:
995 nNewTopLine = m_nTopLine - si.nPage + 1;
996 break;
997 case SB_PAGEDOWN:
998 nNewTopLine = m_nTopLine + si.nPage - 1;
999 break;
1000 case SB_THUMBPOSITION:
1001 m_ScrollTool.Clear();
1002 nNewTopLine = si.nTrackPos;
1003 textwidth = 0;
1004 break;
1005 case SB_THUMBTRACK:
1006 nNewTopLine = si.nTrackPos;
1007 if (GetFocus() == this)
1009 RECT thumbrect;
1010 GetClientRect(&thumbrect);
1011 ClientToScreen(&thumbrect);
1013 POINT thumbpoint;
1014 thumbpoint.x = thumbrect.right;
1015 thumbpoint.y = thumbrect.top + ((thumbrect.bottom-thumbrect.top)*si.nTrackPos)/(si.nMax-si.nMin);
1016 m_ScrollTool.Init(&thumbpoint);
1017 if (textwidth == 0)
1019 CString sTemp = sFormat;
1020 sTemp.Format(sFormat, m_nDigits, 10*m_nDigits-1);
1021 textwidth = m_ScrollTool.GetTextWidth(sTemp);
1023 thumbpoint.x -= textwidth;
1024 int line = GetLineNumber(nNewTopLine);
1025 if (line >= 0)
1026 m_ScrollTool.SetText(&thumbpoint, sFormat, m_nDigits, GetLineNumber(nNewTopLine)+1);
1027 else
1028 m_ScrollTool.SetText(&thumbpoint, m_sNoLineNr);
1030 break;
1031 default:
1032 return;
1035 if (nNewTopLine < 0)
1036 nNewTopLine = 0;
1037 if (nNewTopLine >= nLineCount)
1038 nNewTopLine = nLineCount - 1;
1039 ScrollToLine(nNewTopLine);
1042 void CBaseView::RecalcAllHorzScrollBars(BOOL bPositionOnly /*= FALSE*/)
1044 if (IsLeftViewGood())
1045 m_pwndLeft->RecalcHorzScrollBar(bPositionOnly);
1046 if (IsRightViewGood())
1047 m_pwndRight->RecalcHorzScrollBar(bPositionOnly);
1048 if (IsBottomViewGood())
1049 m_pwndBottom->RecalcHorzScrollBar(bPositionOnly);
1052 void CBaseView::RecalcHorzScrollBar(BOOL bPositionOnly /*= FALSE*/)
1054 SCROLLINFO si;
1055 si.cbSize = sizeof(si);
1056 if (bPositionOnly)
1058 si.fMask = SIF_POS;
1059 si.nPos = m_nOffsetChar;
1061 else
1063 EnableScrollBarCtrl(SB_HORZ, !m_pMainFrame->m_bWrapLines);
1064 if (!m_pMainFrame->m_bWrapLines)
1066 int minScreenChars = GetAllMinScreenChars();
1067 int maxLineLength = GetAllMaxLineLength();
1068 if (minScreenChars >= maxLineLength && m_nOffsetChar > 0)
1070 m_nOffsetChar = 0;
1071 Invalidate();
1073 si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_POS | SIF_RANGE;
1074 si.nMin = 0;
1075 si.nMax = m_pMainFrame->m_bWrapLines ? minScreenChars : maxLineLength;
1076 si.nMax += GetMarginWidth()/GetCharWidth();
1077 si.nPage = GetScreenChars();
1078 si.nPos = m_nOffsetChar;
1081 VERIFY(SetScrollInfo(SB_HORZ, &si));
1084 void CBaseView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1086 CView::OnHScroll(nSBCode, nPos, pScrollBar);
1087 if (m_pwndLeft)
1088 m_pwndLeft->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1089 if (m_pwndRight)
1090 m_pwndRight->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1091 if (m_pwndBottom)
1092 m_pwndBottom->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1093 if (m_pwndLocator)
1094 m_pwndLocator->Invalidate();
1097 void CBaseView::OnDoHScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/, CBaseView * master)
1099 SCROLLINFO si;
1100 si.cbSize = sizeof(si);
1101 si.fMask = SIF_ALL;
1102 VERIFY(master->GetScrollInfo(SB_HORZ, &si));
1104 int nPageChars = GetScreenChars();
1105 int nMaxLineLength = GetMaxLineLength();
1107 int nNewOffset;
1108 switch (nSBCode)
1110 case SB_LEFT:
1111 nNewOffset = 0;
1112 break;
1113 case SB_BOTTOM:
1114 nNewOffset = nMaxLineLength - nPageChars + 1;
1115 break;
1116 case SB_LINEUP:
1117 nNewOffset = m_nOffsetChar - 1;
1118 break;
1119 case SB_LINEDOWN:
1120 nNewOffset = m_nOffsetChar + 1;
1121 break;
1122 case SB_PAGEUP:
1123 nNewOffset = m_nOffsetChar - si.nPage + 1;
1124 break;
1125 case SB_PAGEDOWN:
1126 nNewOffset = m_nOffsetChar + si.nPage - 1;
1127 break;
1128 case SB_THUMBPOSITION:
1129 case SB_THUMBTRACK:
1130 nNewOffset = si.nTrackPos;
1131 break;
1132 default:
1133 return;
1136 if (nNewOffset >= nMaxLineLength)
1137 nNewOffset = nMaxLineLength - 1;
1138 if (nNewOffset < 0)
1139 nNewOffset = 0;
1140 ScrollToChar(nNewOffset, TRUE);
1143 void CBaseView::ScrollToChar(int nNewOffsetChar, BOOL bTrackScrollBar /*= TRUE*/)
1145 if (m_nOffsetChar != nNewOffsetChar)
1147 int nScrollChars = m_nOffsetChar - nNewOffsetChar;
1148 m_nOffsetChar = nNewOffsetChar;
1149 CRect rcScroll;
1150 GetClientRect(&rcScroll);
1151 rcScroll.left += GetMarginWidth();
1152 rcScroll.top += GetLineHeight()+HEADERHEIGHT;
1153 ScrollWindow(nScrollChars * GetCharWidth(), 0, &rcScroll, &rcScroll);
1154 // update the view header
1155 rcScroll.left = 0;
1156 rcScroll.top = 0;
1157 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
1158 InvalidateRect(&rcScroll, FALSE);
1159 UpdateWindow();
1160 if (bTrackScrollBar)
1161 RecalcHorzScrollBar(TRUE);
1162 UpdateCaret();
1163 if (m_pwndLineDiffBar)
1164 m_pwndLineDiffBar->Invalidate();
1168 void CBaseView::ScrollAllToChar(int nNewOffsetChar, BOOL bTrackScrollBar /* = TRUE */)
1170 if (m_pwndLeft)
1171 m_pwndLeft->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1172 if (m_pwndRight)
1173 m_pwndRight->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1174 if (m_pwndBottom)
1175 m_pwndBottom->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1178 void CBaseView::ScrollAllSide(int delta)
1180 int nNewOffset = m_nOffsetChar;
1181 nNewOffset += delta;
1182 int nMaxLineLength = GetMaxLineLength();
1183 if (nNewOffset >= nMaxLineLength)
1184 nNewOffset = nMaxLineLength - 1;
1185 if (nNewOffset < 0)
1186 nNewOffset = 0;
1187 ScrollAllToChar(nNewOffset, TRUE);
1188 if (m_pwndLineDiffBar)
1189 m_pwndLineDiffBar->Invalidate();
1190 UpdateCaret();
1193 void CBaseView::ScrollSide(int delta)
1195 int nNewOffset = m_nOffsetChar;
1196 nNewOffset += delta;
1197 int nMaxLineLength = GetMaxLineLength();
1198 if (nNewOffset >= nMaxLineLength)
1199 nNewOffset = nMaxLineLength - 1;
1200 if (nNewOffset < 0)
1201 nNewOffset = 0;
1202 ScrollToChar(nNewOffset, TRUE);
1203 if (m_pwndLineDiffBar)
1204 m_pwndLineDiffBar->Invalidate();
1205 UpdateCaret();
1208 void CBaseView::ScrollVertical(short zDelta)
1210 const int nLineCount = GetLineCount();
1211 int nTopLine = m_nTopLine;
1212 nTopLine -= (zDelta/30);
1213 if (nTopLine < 0)
1214 nTopLine = 0;
1215 if (nTopLine >= nLineCount)
1216 nTopLine = nLineCount - 1;
1217 ScrollToLine(nTopLine, TRUE);
1220 void CBaseView::ScrollToLine(int nNewTopLine, BOOL bTrackScrollBar /*= TRUE*/)
1222 if (m_nTopLine != nNewTopLine)
1224 if (nNewTopLine < 0)
1225 nNewTopLine = 0;
1227 int nScrollLines = m_nTopLine - nNewTopLine;
1229 m_nTopLine = nNewTopLine;
1230 CRect rcScroll;
1231 GetClientRect(&rcScroll);
1232 rcScroll.top += GetLineHeight()+HEADERHEIGHT;
1233 ScrollWindow(0, nScrollLines * GetLineHeight(), &rcScroll, &rcScroll);
1234 UpdateWindow();
1235 if (bTrackScrollBar)
1236 RecalcVertScrollBar(TRUE);
1237 UpdateCaret();
1242 void CBaseView::DrawMargin(CDC *pdc, const CRect &rect, int nLineIndex)
1244 pdc->FillSolidRect(rect, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_SCROLLBAR)));
1246 if ((nLineIndex >= 0)&&(m_pViewData)&&(m_pViewData->GetCount()))
1248 int nViewLine = GetViewLineForScreen(nLineIndex);
1249 HICON icon = nullptr;
1250 ASSERT(nViewLine < static_cast<int>(m_ScreenedViewLine.size()));
1251 TScreenedViewLine::EIcon eIcon = m_ScreenedViewLine[nViewLine].eIcon;
1252 if (eIcon==TScreenedViewLine::ICN_UNKNOWN)
1254 DiffStates state = m_pViewData->GetState(nViewLine);
1255 switch (state)
1257 case DIFFSTATE_ADDED:
1258 case DIFFSTATE_THEIRSADDED:
1259 case DIFFSTATE_YOURSADDED:
1260 case DIFFSTATE_IDENTICALADDED:
1261 case DIFFSTATE_CONFLICTADDED:
1262 eIcon = TScreenedViewLine::ICN_ADD;
1263 break;
1264 case DIFFSTATE_REMOVED:
1265 case DIFFSTATE_THEIRSREMOVED:
1266 case DIFFSTATE_YOURSREMOVED:
1267 case DIFFSTATE_IDENTICALREMOVED:
1268 eIcon = TScreenedViewLine::ICN_REMOVED;
1269 break;
1270 case DIFFSTATE_CONFLICTED:
1271 eIcon = TScreenedViewLine::ICN_CONFLICT;
1272 break;
1273 case DIFFSTATE_CONFLICTED_IGNORED:
1274 eIcon = TScreenedViewLine::ICN_CONFLICTIGNORED;
1275 break;
1276 case DIFFSTATE_EDITED:
1277 eIcon = TScreenedViewLine::ICN_EDIT;
1278 break;
1279 default:
1280 break;
1282 bool bIdentical = false;
1283 int blockstart = -1;
1284 int blockend = -1;
1285 if ((state != DIFFSTATE_EDITED)&&(IsBlockWhitespaceOnly(nLineIndex, bIdentical, blockstart, blockend)))
1287 if (bIdentical)
1288 eIcon = TScreenedViewLine::ICN_SAME;
1289 else
1290 eIcon = TScreenedViewLine::ICN_WHITESPACEDIFF;
1291 if (((blockstart >= 0) && (blockend >= 0)) && (blockstart < blockend))
1293 if (nViewLine > blockstart)
1294 Invalidate(); // redraw the upper icons since they're now changing
1295 while (blockstart <= blockend)
1296 m_ScreenedViewLine[blockstart++].eIcon = eIcon;
1299 if (m_pViewData->GetMovedIndex(nViewLine) >= 0)
1300 eIcon = TScreenedViewLine::ICN_MOVED;
1301 if (m_pViewData->GetMarked(nViewLine))
1302 eIcon = TScreenedViewLine::ICN_MARKED;
1303 m_ScreenedViewLine[nViewLine].eIcon = eIcon;
1305 switch (eIcon)
1307 case TScreenedViewLine::ICN_UNKNOWN:
1308 case TScreenedViewLine::ICN_NONE:
1309 break;
1310 case TScreenedViewLine::ICN_SAME:
1311 icon = m_hEqualIcon;
1312 break;
1313 case TScreenedViewLine::ICN_EDIT:
1314 icon = m_hEditedIcon;
1315 break;
1316 case TScreenedViewLine::ICN_WHITESPACEDIFF:
1317 icon = m_hWhitespaceBlockIcon;
1318 break;
1319 case TScreenedViewLine::ICN_ADD:
1320 icon = m_hAddedIcon;
1321 break;
1322 case TScreenedViewLine::ICN_CONFLICT:
1323 icon = m_hConflictedIcon;
1324 break;
1325 case TScreenedViewLine::ICN_CONFLICTIGNORED:
1326 icon = m_hConflictedIgnoredIcon;
1327 break;
1328 case TScreenedViewLine::ICN_REMOVED:
1329 icon = m_hRemovedIcon;
1330 break;
1331 case TScreenedViewLine::ICN_MOVED:
1332 icon = m_hMovedIcon;
1333 break;
1334 case TScreenedViewLine::ICN_MARKED:
1335 icon = m_hMarkedIcon;
1336 break;
1339 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1340 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1341 if (icon)
1343 ::DrawIconEx(pdc->m_hDC, rect.left + CDPIAware::Instance().ScaleX(2), rect.top + (rect.Height() - iconHeight) / 2, icon, iconWidth, iconHeight, 0, nullptr, DI_NORMAL);
1345 if ((m_bViewLinenumbers)&&(m_nDigits))
1347 int nSubLine = GetSubLineOffset(nLineIndex);
1348 bool bIsFirstSubline = (nSubLine == 0) || (nSubLine == -1);
1349 CString sLinenumber;
1350 if (bIsFirstSubline)
1352 CString sLinenumberFormat;
1353 int nLineNumber = GetLineNumber(nLineIndex);
1354 if (IsViewLineHidden(GetViewLineForScreen(nLineIndex)))
1356 // TODO: do not show if there is no number hidden
1357 // TODO: show number if there is only one
1358 sLinenumberFormat.Format(L"%%%ds", m_nDigits);
1359 sLinenumber.Format(sLinenumberFormat, (m_nDigits>1) ? L"↕⁞" : L"⁞"); // alternative …
1361 else if (nLineNumber >= 0)
1363 sLinenumberFormat.Format(L"%%%dd", m_nDigits);
1364 sLinenumber.Format(sLinenumberFormat, nLineNumber+1);
1366 else if (m_pMainFrame->m_bWrapLines)
1368 sLinenumberFormat.Format(L"%%%ds", m_nDigits);
1369 sLinenumber.Format(sLinenumberFormat, L"·");
1371 if (!sLinenumber.IsEmpty())
1373 pdc->SetBkColor(CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_SCROLLBAR)));
1374 pdc->SetTextColor(CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT));
1376 pdc->SelectObject(GetFont());
1377 pdc->ExtTextOut(rect.left + iconWidth + CDPIAware::Instance().ScaleX(2), rect.top, ETO_CLIPPED, &rect, sLinenumber, nullptr);
1384 int CBaseView::GetMarginWidth()
1386 int marginWidth = GetSystemMetrics(SM_CXSMICON) + CDPIAware::Instance().ScaleX(4);
1388 if ((m_bViewLinenumbers)&&(m_pViewData)&&(m_pViewData->GetCount()))
1390 if (m_nDigits <= 0)
1392 int nLength = m_pViewData->GetCount();
1393 // find out how many digits are needed to show the highest line number
1394 CString sMax;
1395 sMax.Format(L"%d", nLength);
1396 m_nDigits = sMax.GetLength();
1398 int nWidth = GetCharWidth();
1399 marginWidth += (m_nDigits * nWidth) + CDPIAware::Instance().ScaleX(2);
1402 return marginWidth;
1405 void CBaseView::DrawHeader(CDC *pdc, const CRect &rect)
1407 CRect textrect(rect.left, rect.top, rect.Width(), GetLineHeight()+HEADERHEIGHT);
1408 COLORREF crBk, crFg;
1409 if (IsBottomViewGood())
1411 CDiffColors::GetInstance().GetColors(DIFFSTATE_NORMAL, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBk, crFg);
1412 crBk = CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_SCROLLBAR));
1414 else
1416 DiffStates state = DIFFSTATE_REMOVED;
1417 if (this == m_pwndRight)
1419 state = DIFFSTATE_ADDED;
1421 CDiffColors::GetInstance().GetColors(state, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBk, crFg);
1423 pdc->SetBkColor(crBk);
1424 pdc->FillSolidRect(textrect, crBk);
1426 pdc->SetTextColor(crFg);
1428 pdc->SelectObject(GetFont(FALSE, TRUE));
1430 CString sViewTitle;
1431 if (IsModified())
1433 sViewTitle = L"* " + m_sWindowName;
1435 else
1437 sViewTitle = m_sWindowName;
1439 int nStringLength = (GetCharWidth()*m_sWindowName.GetLength());
1440 if (nStringLength > rect.Width())
1442 int offset = std::min<int>(m_nOffsetChar, (nStringLength-rect.Width())/GetCharWidth()+1);
1443 sViewTitle = m_sWindowName.Mid(offset);
1445 RECT titleRC = textrect;
1446 titleRC.left = std::max<int>(rect.left + (rect.Width() - nStringLength) / 2, 1);
1447 titleRC.top = rect.top + (HEADERHEIGHT / 2);
1448 pdc->DrawText(sViewTitle, &titleRC, DT_HIDEPREFIX | DT_NOPREFIX | DT_SINGLELINE);
1449 if (this->GetFocus() == this)
1450 pdc->DrawEdge(textrect, EDGE_BUMP, BF_RECT);
1451 else
1452 pdc->DrawEdge(textrect, EDGE_ETCHED, BF_RECT);
1455 void CBaseView::OnDraw(CDC * pDC)
1457 CRect rcClient;
1458 GetClientRect(rcClient);
1460 int nLineCount = GetLineCount();
1461 int nLineHeight = GetLineHeight();
1463 CDC cacheDC;
1464 VERIFY(cacheDC.CreateCompatibleDC(pDC));
1465 if (!m_pCacheBitmap)
1467 m_pCacheBitmap = new CBitmap;
1468 VERIFY(m_pCacheBitmap->CreateCompatibleBitmap(pDC, rcClient.Width(), nLineHeight));
1470 CBitmap *pOldBitmap = cacheDC.SelectObject(m_pCacheBitmap);
1472 DrawHeader(pDC, rcClient);
1474 CRect rcLine;
1475 rcLine = rcClient;
1476 rcLine.top += nLineHeight+HEADERHEIGHT;
1477 rcLine.bottom = rcLine.top + nLineHeight;
1478 CRect rcCacheMargin(0, 0, GetMarginWidth(), nLineHeight);
1479 CRect rcCacheLine(GetMarginWidth(), 0, rcLine.Width(), nLineHeight);
1481 int nCurrentLine = m_nTopLine;
1482 bool bBeyondFileLineCached = false;
1483 while (rcLine.top < rcClient.bottom)
1485 if (nCurrentLine < nLineCount)
1487 DrawMargin(&cacheDC, rcCacheMargin, nCurrentLine);
1488 DrawSingleLine(&cacheDC, rcCacheLine, nCurrentLine);
1489 bBeyondFileLineCached = false;
1491 else if (!bBeyondFileLineCached)
1493 DrawMargin(&cacheDC, rcCacheMargin, -1);
1494 DrawSingleLine(&cacheDC, rcCacheLine, -1);
1495 bBeyondFileLineCached = true;
1498 VERIFY(pDC->BitBlt(rcLine.left, rcLine.top, rcLine.Width(), rcLine.Height(), &cacheDC, 0, 0, SRCCOPY));
1500 nCurrentLine ++;
1501 rcLine.OffsetRect(0, nLineHeight);
1504 cacheDC.SelectObject(pOldBitmap);
1505 cacheDC.DeleteDC();
1508 bool CBaseView::IsStateConflicted(DiffStates state)
1510 switch (state)
1512 case DIFFSTATE_CONFLICTED:
1513 case DIFFSTATE_CONFLICTED_IGNORED:
1514 case DIFFSTATE_CONFLICTEMPTY:
1515 case DIFFSTATE_CONFLICTADDED:
1516 return true;
1518 return false;
1521 bool CBaseView::IsStateEmpty(DiffStates state)
1523 switch (state)
1525 case DIFFSTATE_CONFLICTEMPTY:
1526 case DIFFSTATE_UNKNOWN:
1527 case DIFFSTATE_EMPTY:
1528 return true;
1530 return false;
1533 bool CBaseView::IsStateRemoved(DiffStates state)
1535 switch (state)
1537 case DIFFSTATE_REMOVED:
1538 case DIFFSTATE_THEIRSREMOVED:
1539 case DIFFSTATE_YOURSREMOVED:
1540 case DIFFSTATE_IDENTICALREMOVED:
1541 return true;
1543 return false;
1546 DiffStates CBaseView::ResolveState(DiffStates state)
1548 if (IsStateConflicted(state))
1550 if (state == DIFFSTATE_CONFLICTEMPTY)
1551 return DIFFSTATE_CONFLICTRESOLVEDEMPTY;
1552 else
1553 return DIFFSTATE_CONFLICTRESOLVED;
1555 return state;
1559 bool CBaseView::IsLineEmpty(int nLineIndex)
1561 if (m_pViewData == 0)
1562 return FALSE;
1563 int nViewLine = GetViewLineForScreen(nLineIndex);
1564 return IsViewLineEmpty(nViewLine);
1567 bool CBaseView::IsViewLineEmpty(int nViewLine)
1569 if (m_pViewData == 0)
1570 return FALSE;
1571 const DiffStates state = m_pViewData->GetState(nViewLine);
1572 return IsStateEmpty(state);
1575 bool CBaseView::IsLineRemoved(int nLineIndex)
1577 if (m_pViewData == 0)
1578 return FALSE;
1579 int nViewLine = GetViewLineForScreen(nLineIndex);
1580 return IsViewLineRemoved(nViewLine);
1583 bool CBaseView::IsViewLineRemoved(int nViewLine)
1585 if (m_pViewData == 0)
1586 return FALSE;
1587 const DiffStates state = m_pViewData->GetState(nViewLine);
1588 return IsStateRemoved(state);
1591 COLORREF CBaseView::InlineDiffColor(int nLineIndex)
1593 if (m_bDark)
1594 return IsLineRemoved(nLineIndex) ? m_InlineRemovedDarkBk : m_InlineAddedDarkBk;
1595 return IsLineRemoved(nLineIndex) ? m_InlineRemovedBk : m_InlineAddedBk;
1598 COLORREF CBaseView::InlineViewLineDiffColor(int nViewLine)
1600 if (m_bDark)
1601 return IsViewLineRemoved(nViewLine) ? m_InlineRemovedDarkBk : m_InlineAddedDarkBk;
1602 return IsViewLineRemoved(nViewLine) ? m_InlineRemovedBk : m_InlineAddedBk;
1605 void CBaseView::DrawLineEnding(CDC *pDC, const CRect &rc, int nLineIndex, const CPoint& origin)
1607 if (origin.x < (rc.left - GetCharWidth() +1))
1608 return;
1609 if (!(m_bViewWhitespace && m_pViewData && (nLineIndex >= 0) && (nLineIndex < GetLineCount())))
1610 return;
1611 int viewLine = GetViewLineForScreen(nLineIndex);
1612 EOL ending = m_pViewData->GetLineEnding(viewLine);
1613 if (m_bIconLFs)
1615 HICON hEndingIcon = nullptr;
1616 switch (ending)
1618 case EOL_CR: hEndingIcon = m_hLineEndingCR; break;
1619 case EOL_CRLF: hEndingIcon = m_hLineEndingCRLF; break;
1620 case EOL_LF: hEndingIcon = m_hLineEndingLF; break;
1621 default: return;
1623 // If EOL style has changed, color end-of-line markers as inline differences.
1625 m_bShowInlineDiff && m_pOtherViewData &&
1626 (viewLine < m_pOtherViewData->GetCount()) &&
1627 (ending != EOL_NOENDING) &&
1628 (ending != m_pOtherViewData->GetLineEnding(viewLine) &&
1629 (m_pOtherViewData->GetLineEnding(viewLine) != EOL_NOENDING))
1632 pDC->FillSolidRect(origin.x, origin.y, rc.Height(), rc.Height(), InlineDiffColor(nLineIndex));
1635 DrawIconEx(pDC->GetSafeHdc(), origin.x, origin.y, hEndingIcon, rc.Height(), rc.Height(), 0, nullptr, DI_NORMAL);
1637 else
1639 CPen pen(PS_SOLID, 0, CTheme::Instance().GetThemeColor(m_WhiteSpaceFg));
1640 CPen * oldpen = pDC->SelectObject(&pen);
1641 int yMiddle = origin.y + rc.Height()/2;
1642 int xMiddle = origin.x+GetCharWidth()/2;
1643 bool bMultiline = false;
1644 if ((m_Screen2View.size() > nLineIndex + 1) && (GetViewLineForScreen(nLineIndex + 1) == viewLine))
1646 if (GetLineLength(nLineIndex+1))
1648 // multiline
1649 bMultiline = true;
1650 pDC->MoveTo(origin.x, yMiddle - CDPIAware::Instance().ScaleY(2));
1651 pDC->LineTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), yMiddle - CDPIAware::Instance().ScaleY(2));
1652 pDC->LineTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), yMiddle + CDPIAware::Instance().ScaleY(2));
1653 pDC->LineTo(origin.x, yMiddle + CDPIAware::Instance().ScaleY(2));
1655 else if (GetLineLength(nLineIndex) == 0)
1656 bMultiline = true;
1658 else if ((nLineIndex > 0) && (GetViewLineForScreen(nLineIndex-1) == viewLine) && (GetLineLength(nLineIndex) == 0))
1659 bMultiline = true;
1661 if (!bMultiline)
1663 switch (ending)
1665 case EOL_AUTOLINE:
1666 case EOL_CRLF:
1667 // arrow from top to middle+2, then left
1668 pDC->MoveTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), rc.top + CDPIAware::Instance().ScaleY(1));
1669 pDC->LineTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), yMiddle);
1670 case EOL_CR:
1671 // arrow from right to left
1672 pDC->MoveTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), yMiddle);
1673 pDC->LineTo(origin.x, yMiddle);
1674 pDC->LineTo(origin.x + CDPIAware::Instance().ScaleX(4), yMiddle + CDPIAware::Instance().ScaleY(4));
1675 pDC->MoveTo(origin.x, yMiddle);
1676 pDC->LineTo(origin.x + CDPIAware::Instance().ScaleX(4), yMiddle - CDPIAware::Instance().ScaleY(4));
1677 break;
1678 case EOL_LFCR:
1679 // from right-upper to left then down
1680 pDC->MoveTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), yMiddle - CDPIAware::Instance().ScaleY(2));
1681 pDC->LineTo(xMiddle, yMiddle - CDPIAware::Instance().ScaleY(2));
1682 pDC->LineTo(xMiddle, rc.bottom - CDPIAware::Instance().ScaleY(1));
1683 pDC->LineTo(xMiddle + CDPIAware::Instance().ScaleX(4), rc.bottom - CDPIAware::Instance().ScaleY(5));
1684 pDC->MoveTo(xMiddle, rc.bottom - CDPIAware::Instance().ScaleY(1));
1685 pDC->LineTo(xMiddle - CDPIAware::Instance().ScaleX(4), rc.bottom - CDPIAware::Instance().ScaleY(5));
1686 break;
1687 case EOL_LF:
1688 // arrow from top to bottom
1689 pDC->MoveTo(xMiddle, rc.top);
1690 pDC->LineTo(xMiddle, rc.bottom - CDPIAware::Instance().ScaleY(1));
1691 pDC->LineTo(xMiddle + CDPIAware::Instance().ScaleX(4), rc.bottom - CDPIAware::Instance().ScaleY(5));
1692 pDC->MoveTo(xMiddle, rc.bottom - CDPIAware::Instance().ScaleY(1));
1693 pDC->LineTo(xMiddle - CDPIAware::Instance().ScaleX(4), rc.bottom - CDPIAware::Instance().ScaleY(5));
1694 break;
1695 case EOL_FF: // Form Feed, U+000C
1696 case EOL_NEL: // Next Line, U+0085
1697 case EOL_LS: // Line Separator, U+2028
1698 case EOL_PS: // Paragraph Separator, U+2029
1699 // draw a horizontal line at the bottom of this line
1700 pDC->FillSolidRect(rc.left, rc.bottom - 1, rc.right, rc.bottom, CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT));
1701 pDC->MoveTo(origin.x+GetCharWidth()-1, rc.bottom-GetCharWidth()-2);
1702 pDC->LineTo(origin.x, rc.bottom-2);
1703 pDC->LineTo(origin.x+5, rc.bottom-2);
1704 pDC->MoveTo(origin.x, rc.bottom-2);
1705 pDC->LineTo(origin.x+1, rc.bottom-6);
1706 break;
1707 default: // other EOLs
1708 // arrow from top right to bottom left
1709 pDC->MoveTo(origin.x+GetCharWidth()-1, rc.bottom-GetCharWidth());
1710 pDC->LineTo(origin.x, rc.bottom-1);
1711 pDC->LineTo(origin.x+5, rc.bottom-2);
1712 pDC->MoveTo(origin.x, rc.bottom-1);
1713 pDC->LineTo(origin.x+1, rc.bottom-6);
1714 break;
1715 case EOL_NOENDING:
1716 break;
1719 pDC->SelectObject(oldpen);
1723 void CBaseView::DrawBlockLine(CDC *pDC, const CRect &rc, int nLineIndex)
1725 if (!m_bShowSelection)
1726 return;
1728 int nSelBlockStart;
1729 int nSelBlockEnd;
1730 if (!GetViewSelection(nSelBlockStart, nSelBlockEnd))
1731 return;
1733 const int THICKNESS = 2;
1734 COLORREF rectcol = 0;
1735 if (m_bFocused)
1736 rectcol = CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT);
1737 else
1738 rectcol = CTheme::Instance().GetThemeColor(GetSysColor(COLOR_GRAYTEXT));
1740 int nViewLineIndex = GetViewLineForScreen(nLineIndex);
1741 int nSubLine = GetSubLineOffset(nLineIndex);
1742 bool bFirstLineOfViewLine = (nSubLine==0 || nSubLine==-1);
1743 if ((nViewLineIndex == nSelBlockStart) && bFirstLineOfViewLine)
1745 pDC->FillSolidRect(rc.left, rc.top, rc.Width(), THICKNESS, rectcol);
1748 bool bLastLineOfViewLine = (nLineIndex+1 == m_Screen2View.size()) || (GetViewLineForScreen(nLineIndex) != GetViewLineForScreen(nLineIndex+1));
1749 if ((nViewLineIndex == nSelBlockEnd) && bLastLineOfViewLine)
1751 pDC->FillSolidRect(rc.left, rc.bottom - THICKNESS, rc.Width(), THICKNESS, rectcol);
1755 void CBaseView::DrawTextLine(
1756 CDC * pDC, const CRect &rc, int nLineIndex, POINT& coords)
1758 ASSERT(nLineIndex < GetLineCount());
1759 int nViewLine = GetViewLineForScreen(nLineIndex);
1760 ASSERT(m_pViewData && (nViewLine < m_pViewData->GetCount()));
1762 CRgn rgn;
1763 rgn.CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
1764 pDC->SelectClipRgn(&rgn);
1765 SCOPE_EXIT { pDC->SelectClipRgn(nullptr); };
1767 LineColors lineCols = GetLineColors(nViewLine);
1769 CString sViewLine = GetViewLineChars(nViewLine);
1770 // mark selection
1771 if (m_bShowSelection && HasTextSelection())
1773 // has this line selection ?
1774 if ((m_ptSelectionViewPosStart.y <= nViewLine) && (nViewLine <= m_ptSelectionViewPosEnd.y))
1776 int nViewLineLength = sViewLine.GetLength();
1778 // first suppose the whole line is selected
1779 int selectedStart = 0;
1780 int selectedEnd = nViewLineLength;
1782 // the view line is partially selected
1783 if (m_ptSelectionViewPosStart.y == nViewLine)
1785 selectedStart = m_ptSelectionViewPosStart.x;
1788 if (m_ptSelectionViewPosEnd.y == nViewLine)
1790 selectedEnd = m_ptSelectionViewPosEnd.x;
1792 // apply selection coloring
1793 // First enforce start and end point
1794 lineCols.SplitBlock(selectedStart);
1795 lineCols.SplitBlock(selectedEnd);
1796 // change color of affected parts
1797 long intenseColorScale = m_bFocused ? 70 : 30;
1798 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(selectedStart);
1799 for ( ; it != lineCols.end() && it->first < selectedEnd; ++it)
1801 auto& second = it->second;
1802 COLORREF crBk = CAppUtils::IntenseColor(intenseColorScale, second.background);
1803 if (second.shot == second.background)
1804 second.shot = crBk;
1805 second.background = crBk;
1806 second.text = CAppUtils::IntenseColor(intenseColorScale, second.text);
1811 // TODO: remove duplicate from selection and mark
1812 if (!m_sMarkedWord.IsEmpty())
1814 int nMarkLength = m_sMarkedWord.GetLength();
1815 //int nViewLineLength = sViewLine.GetLength();
1816 const wchar_t* text = sViewLine;
1817 const wchar_t* findText = text;
1818 while ((findText = wcsstr(findText, static_cast<LPCWSTR>(m_sMarkedWord))) != 0)
1820 int nMarkStart = static_cast<int>(findText - text);
1821 int nMarkEnd = nMarkStart + nMarkLength;
1822 findText += nMarkLength;
1823 ECharGroup eLeft = GetCharGroup(sViewLine, nMarkStart - 1);
1824 ECharGroup eStart = GetCharGroup(sViewLine, nMarkStart);
1825 if (eLeft == eStart)
1826 continue;
1827 ECharGroup eRight = GetCharGroup(sViewLine, nMarkEnd);
1828 ECharGroup eEnd = GetCharGroup(sViewLine, nMarkEnd - 1);
1829 if (eRight == eEnd)
1830 continue;
1832 // First enforce start and end point
1833 lineCols.SplitBlock(nMarkStart);
1834 lineCols.SplitBlock(nMarkEnd);
1835 // change color of affected parts
1836 const long int nIntenseColorScale = 200;
1837 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(nMarkStart);
1838 for ( ; it != lineCols.end() && it->first < nMarkEnd; ++it)
1840 auto& second = it->second;
1841 COLORREF crBk = CAppUtils::IntenseColor(nIntenseColorScale, second.background);
1842 if (second.shot == second.background)
1843 second.shot = crBk;
1844 second.background = crBk;
1845 second.text = CAppUtils::IntenseColor(nIntenseColorScale, second.text);
1846 if (CTheme::Instance().IsDarkTheme())
1848 int bkGray = (static_cast<int>(GetRValue(crBk)) + GetGValue(crBk) + GetBValue(crBk)) / 3;
1849 int frGray = (static_cast<int>(GetRValue(second.text)) + GetGValue(second.text) + GetBValue(second.text)) / 3;
1850 if (abs(bkGray - frGray) < 100)
1851 second.text = CAppUtils::IntenseColor(nIntenseColorScale, second.text);
1856 if (!m_sFindText.IsEmpty())
1858 int nMarkStart = 0;
1859 int nMarkEnd = 0;
1860 int nStringPos = nMarkStart;
1861 CString searchLine = sViewLine;
1862 if (!m_bMatchCase)
1863 searchLine.MakeLower();
1864 while (StringFound(searchLine, SearchNext, nMarkStart, nMarkEnd)!=0)
1866 // First enforce start and end point
1867 lineCols.SplitBlock(nMarkStart+nStringPos);
1868 lineCols.SplitBlock(nMarkEnd+nStringPos);
1869 // change color of affected parts
1870 const long int nIntenseColorScale = 30;
1871 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(nMarkStart+nStringPos);
1872 for ( ; it != lineCols.end() && it->first < nMarkEnd + nStringPos; ++it)
1874 auto& second = it->second;
1875 COLORREF crBk = CAppUtils::IntenseColor(nIntenseColorScale, second.background);
1876 if (second.shot == second.background)
1877 second.shot = crBk;
1878 second.background = crBk;
1879 second.text = CAppUtils::IntenseColor(nIntenseColorScale, second.text);
1881 searchLine = searchLine.Mid(nMarkEnd);
1882 nStringPos += nMarkEnd;
1883 nMarkStart = 0;
1884 nMarkEnd = 0;
1888 // @ this point we may cache data for next line which may be same in wrapped mode
1890 int nTextOffset = 0;
1891 int nSubline = GetSubLineOffset(nLineIndex);
1892 for (int n=0; n<nSubline; n++)
1894 CString sLine = m_ScreenedViewLine[nViewLine].SubLines[n];
1895 nTextOffset += sLine.GetLength();
1898 CString sLine = GetLineChars(nLineIndex);
1899 int nLineLength = sLine.GetLength();
1900 CString sLineExp = ExpandChars(sLine);
1901 LPCWSTR textExp = sLineExp;
1902 //int nLineLengthExp = sLineExp.GetLength();
1903 int nStartExp = 0;
1904 int nLeft = coords.x;
1905 for (std::map<int, linecolors_t>::const_iterator itStart = lineCols.begin(); itStart != lineCols.end(); ++itStart)
1907 std::map<int, linecolors_t>::const_iterator itEnd = itStart;
1908 ++itEnd;
1909 int nStart = std::max<int>(0, itStart->first - nTextOffset);
1910 int nEnd = nLineLength;
1911 if (itEnd != lineCols.end())
1913 nEnd = std::min<int>(nEnd, itEnd->first - nTextOffset);
1915 int nBlockLength = nEnd - nStart;
1916 if (nBlockLength > 0 && nEnd>=0)
1918 auto& second = itStart->second;
1919 pDC->SetBkColor(second.background);
1920 pDC->SetTextColor(second.text);
1921 int nEndExp = CountExpandedChars(sLine, nEnd);
1922 int nTextLength = nEndExp - nStartExp;
1923 LPCWSTR p_zBlockText = textExp + nStartExp;
1924 SIZE Size;
1925 GetTextExtentPoint32(pDC->GetSafeHdc(), p_zBlockText, nTextLength, &Size); // falls time-2-tme
1926 int nRight = nLeft + Size.cx;
1927 if ((nRight > rc.left) && (nLeft < rc.right))
1929 // note: ExtTextOut has a limit for the length of the string. That limit is supposed
1930 // to be 8192, but that's not really true: I found that the limit (at least on my machine and a few others)
1931 // is 4094 (4095 doesn't work anymore).
1932 // So we limit the length here to that 4094 chars.
1933 // In case we're scrolled to the right, there's no need to draw the string
1934 // from way outside our window, so we also offset the drawing to the start of the window.
1935 // This reduces the string length as well.
1936 int offset = 0;
1937 int leftcoord = nLeft;
1938 if (nLeft < 0)
1940 int fit = nTextLength;
1941 auto posBuffer = std::make_unique<int[]>(fit);
1942 GetTextExtentExPoint(pDC->GetSafeHdc(), p_zBlockText, nTextLength, INT_MAX, &fit, posBuffer.get(), &Size);
1943 int lower = 0, upper = fit - 1;
1946 int middle = (upper + lower + 1) / 2;
1947 int width = posBuffer[middle];
1948 if (rc.left - nLeft < width)
1949 upper = middle - 1;
1950 else
1951 lower = middle;
1952 } while (lower < upper);
1954 offset = lower;
1955 nTextLength -= offset;
1956 leftcoord += lower > 0 ? posBuffer[lower - 1] : 0;
1959 RECT drawRC = rc;
1960 drawRC.left = leftcoord;
1961 drawRC.top = coords.y;
1962 pDC->DrawText(p_zBlockText + offset, min(nTextLength, 4094), &drawRC, DT_HIDEPREFIX | DT_NOPREFIX | DT_SINGLELINE);
1963 if ((second.shot != second.background) && (itStart->first == nStart + nTextOffset))
1964 pDC->FillSolidRect(nLeft - 1, rc.top, 1, rc.Height(), second.shot);
1966 nLeft = nRight;
1967 coords.x = nRight;
1968 nStartExp = nEndExp;
1973 void CBaseView::DrawSingleLine(CDC *pDC, const CRect &rc, int nLineIndex)
1975 if (nLineIndex >= GetLineCount())
1976 nLineIndex = -1;
1977 ASSERT(nLineIndex >= -1);
1979 if ((nLineIndex == -1) || !m_pViewData)
1981 // Draw line beyond the text
1982 COLORREF crBkgnd, crText;
1983 CDiffColors::GetInstance().GetColors(DIFFSTATE_UNKNOWN, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBkgnd, crText);
1984 pDC->FillSolidRect(rc, crBkgnd);
1985 return;
1988 int viewLine = GetViewLineForScreen(nLineIndex);
1989 if (m_pMainFrame->m_bCollapsed)
1991 if (m_pViewData->GetHideState(viewLine) == HIDESTATE_MARKER)
1993 COLORREF crBkgnd, crText;
1994 CDiffColors::GetInstance().GetColors(DIFFSTATE_UNKNOWN, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBkgnd, crText);
1995 pDC->FillSolidRect(rc, crBkgnd);
1997 const int THICKNESS = 2;
1998 COLORREF rectcol = CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT);
1999 pDC->FillSolidRect(rc.left, rc.top + (rc.Height()/2), rc.Width(), THICKNESS, rectcol);
2000 pDC->SetTextColor(CTheme::Instance().GetThemeColor(GetSysColor(COLOR_GRAYTEXT)));
2001 pDC->SetBkColor(crBkgnd);
2002 CRect rect = rc;
2003 pDC->DrawText(L"{...}", &rect, DT_NOPREFIX|DT_SINGLELINE|DT_CENTER);
2004 return;
2008 DiffStates diffState = m_pViewData->GetState(viewLine);
2009 COLORREF crBkgnd, crText;
2010 CDiffColors::GetInstance().GetColors(diffState, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBkgnd, crText);
2012 if (diffState == DIFFSTATE_CONFLICTED)
2014 // conflicted lines are shown without 'text' on them
2015 CRect rect = rc;
2016 pDC->FillSolidRect(rc, crBkgnd);
2017 // now draw some faint text patterns
2018 pDC->SetTextColor(CAppUtils::IntenseColor(130, crBkgnd));
2019 pDC->DrawText(m_sConflictedText, rect, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE);
2020 DrawBlockLine(pDC, rc, nLineIndex);
2021 return;
2024 CPoint origin(rc.left - m_nOffsetChar * GetCharWidth(), rc.top);
2025 CString sLine = GetLineChars(nLineIndex);
2026 if (sLine.IsEmpty())
2028 pDC->FillSolidRect(rc, crBkgnd);
2029 DrawBlockLine(pDC, rc, nLineIndex);
2030 DrawLineEnding(pDC, rc, nLineIndex, origin);
2031 return;
2034 CheckOtherView();
2036 // Draw the line
2038 pDC->SelectObject(GetFont(FALSE, FALSE));
2040 DrawTextLine(pDC, rc, nLineIndex, origin);
2042 // draw white space after the end of line
2043 CRect frect = rc;
2044 if (origin.x > frect.left)
2045 frect.left = origin.x;
2046 if (frect.right > frect.left)
2047 pDC->FillSolidRect(frect, crBkgnd);
2049 // draw the whitespace chars
2050 auto pszChars = static_cast<LPCWSTR>(sLine);
2051 if (m_bViewWhitespace)
2053 int xpos = 0;
2054 int nChars = 0;
2055 LPCWSTR pLastSpace = pszChars;
2056 int y = rc.top + (rc.bottom-rc.top)/2;
2057 xpos -= m_nOffsetChar * GetCharWidth();
2059 CPen pen(PS_SOLID, 0, CTheme::Instance().GetThemeColor(m_WhiteSpaceFg));
2060 while (*pszChars)
2062 switch (*pszChars)
2064 case '\t':
2066 xpos += pDC->GetTextExtent(pLastSpace, static_cast<int>(pszChars - pLastSpace)).cx;
2067 pLastSpace = pszChars + 1;
2068 // draw an arrow
2069 int nSpaces = GetTabSize() - nChars % GetTabSize();
2070 if (xpos + nSpaces * GetCharWidth() > 0)
2072 int xposreal = max(xpos, 0);
2073 if ((xposreal > 0) || (nSpaces > 0))
2075 CPen * oldPen = pDC->SelectObject(&pen);
2076 pDC->MoveTo(xposreal + rc.left + CDPIAware::Instance().ScaleX(2), y);
2077 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - CDPIAware::Instance().ScaleX(2), y);
2078 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - CDPIAware::Instance().ScaleX(6), y - CDPIAware::Instance().ScaleY(4));
2079 pDC->MoveTo((xpos + nSpaces * GetCharWidth()) + rc.left - CDPIAware::Instance().ScaleX(2), y);
2080 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - CDPIAware::Instance().ScaleX(6), y + CDPIAware::Instance().ScaleY(4));
2081 pDC->SelectObject(oldPen);
2084 xpos += nSpaces * GetCharWidth();
2085 nChars += nSpaces;
2087 break;
2088 case ' ':
2090 xpos += pDC->GetTextExtent(pLastSpace, static_cast<int>(pszChars - pLastSpace)).cx;
2091 pLastSpace = pszChars + 1;
2092 if (xpos >= 0)
2094 const int cxWhitespace = CDPIAware::Instance().ScaleX(2);
2095 const int cyWhitespace = CDPIAware::Instance().ScaleY(2);
2096 // draw 2-logical pixel rectangle, like Scintilla editor.
2097 pDC->FillSolidRect(xpos + rc.left + GetCharWidth() / 2 - cxWhitespace/2, y, cxWhitespace, cyWhitespace, CTheme::Instance().GetThemeColor(m_WhiteSpaceFg));
2099 xpos += GetCharWidth();
2100 nChars++;
2102 break;
2103 default:
2104 nChars++;
2105 break;
2107 pszChars++;
2110 DrawBlockLine(pDC, rc, nLineIndex);
2111 if (origin.x >= rc.left)
2112 DrawLineEnding(pDC, rc, nLineIndex, origin);
2115 void CBaseView::ExpandChars(const CString &sLine, int nOffset, int nCount, CString &line)
2117 if (nCount <= 0)
2119 line.Empty();
2120 return;
2123 int nTabSize = GetTabSize();
2125 int nActualOffset = CountExpandedChars(sLine, nOffset);
2127 auto pszChars = static_cast<LPCWSTR>(sLine);
2128 pszChars += nOffset;
2129 int nLength = nCount;
2131 int nTabCount = 0;
2132 for (int i=0; i<nLength; i++)
2134 if (pszChars[i] == L'\t')
2135 nTabCount ++;
2138 LPWSTR pszBuf = line.GetBuffer(nLength + nTabCount * (nTabSize - 1) + 1);
2139 int nCurPos = 0;
2140 if (nTabCount > 0 || m_bViewWhitespace)
2142 for (int i=0; i<nLength; i++)
2144 if (pszChars[i] == L'\t')
2146 int nSpaces = nTabSize - (nActualOffset + nCurPos) % nTabSize;
2147 while (nSpaces > 0)
2149 pszBuf[nCurPos ++] = L' ';
2150 nSpaces --;
2153 else
2155 pszBuf[nCurPos] = pszChars[i];
2156 nCurPos ++;
2160 else
2162 memcpy(pszBuf, pszChars, sizeof(wchar_t) * nLength);
2163 nCurPos = nLength;
2165 pszBuf[nCurPos] = 0;
2166 line.ReleaseBuffer();
2169 CString CBaseView::ExpandChars(const CString &sLine, int nOffset)
2171 CString sRet;
2172 int nLength = sLine.GetLength();
2173 ExpandChars(sLine, nOffset, nLength, sRet);
2174 return sRet;
2177 int CBaseView::CountExpandedChars(const CString &sLine, int nLength)
2179 int nTabSize = GetTabSize();
2181 int nActualOffset = 0;
2182 for (int i=0; i<nLength; i++)
2184 if (sLine[i] == L'\t')
2185 nActualOffset += (nTabSize - nActualOffset % nTabSize);
2186 else
2187 nActualOffset ++;
2189 return nActualOffset;
2192 void CBaseView::ScrollAllToLine(int nNewTopLine, BOOL bTrackScrollBar)
2194 if (m_pwndLeft)
2195 m_pwndLeft->ScrollToLine(nNewTopLine, bTrackScrollBar);
2196 if (m_pwndRight)
2197 m_pwndRight->ScrollToLine(nNewTopLine, bTrackScrollBar);
2198 if (m_pwndBottom)
2199 m_pwndBottom->ScrollToLine(nNewTopLine, bTrackScrollBar);
2200 if (m_pwndLocator)
2201 m_pwndLocator->Invalidate();
2204 void CBaseView::GoToLine(int nNewLine, BOOL bAll)
2206 //almost the same as ScrollAllToLine, but try to put the line in the
2207 //middle of the view, not on top
2208 int nNewTopLine = nNewLine - GetScreenLines()/2;
2209 if (nNewTopLine < 0)
2210 nNewTopLine = 0;
2211 if (nNewTopLine >= m_Screen2View.size())
2212 nNewTopLine = m_Screen2View.size() - 1;
2213 if (bAll)
2214 ScrollAllToLine(nNewTopLine);
2215 else
2216 ScrollToLine(nNewTopLine);
2219 BOOL CBaseView::OnEraseBkgnd(CDC* /*pDC*/)
2221 return TRUE;
2224 int CBaseView::OnCreate(LPCREATESTRUCT lpCreateStruct)
2226 if (CView::OnCreate(lpCreateStruct) == -1)
2227 return -1;
2229 SecureZeroMemory(&m_lfBaseFont, sizeof(m_lfBaseFont));
2230 //lstrcpy(m_lfBaseFont.lfFaceName, L"Courier New");
2231 //lstrcpy(m_lfBaseFont.lfFaceName, L"FixedSys");
2232 m_lfBaseFont.lfHeight = 0;
2233 m_lfBaseFont.lfWeight = FW_NORMAL;
2234 m_lfBaseFont.lfItalic = FALSE;
2235 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
2236 m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
2237 m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
2238 m_lfBaseFont.lfQuality = DEFAULT_QUALITY;
2239 m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;
2241 return 0;
2244 void CBaseView::OnDestroy()
2246 CView::OnDestroy();
2247 DeleteFonts();
2248 ReleaseBitmap();
2251 void CBaseView::OnSize(UINT nType, int cx, int cy)
2253 CView::OnSize(nType, cx, cy);
2254 ReleaseBitmap();
2256 m_nScreenLines = -1;
2257 m_nScreenChars = -1;
2258 if (m_nLastScreenChars != GetScreenChars())
2260 auto oldCaretLine = m_ptCaretViewPos.y;
2261 BuildAllScreen2ViewVector();
2262 m_nLastScreenChars = m_nScreenChars;
2263 if (m_pMainFrame && m_pMainFrame->m_bWrapLines)
2265 auto newCaretLine = FindScreenLineForViewLine(oldCaretLine);
2266 int nNewTopLine = newCaretLine - GetScreenLines() / 2;
2267 if (nNewTopLine < 0)
2268 nNewTopLine = 0;
2269 if (nNewTopLine >= static_cast<int>(m_Screen2View.size()))
2270 nNewTopLine = static_cast<int>(m_Screen2View.size()) - 1;
2271 ScrollToLine(nNewTopLine);
2272 // if we're in wrap mode, the line wrapping most likely changed
2273 // and that means we have to redraw the whole window, not just the
2274 // scrolled part.
2275 Invalidate(FALSE);
2277 else
2279 // make sure the view header is redrawn
2280 CRect rcScroll;
2281 GetClientRect(&rcScroll);
2282 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
2283 InvalidateRect(&rcScroll, FALSE);
2286 else
2288 // make sure the view header is redrawn
2289 CRect rcScroll;
2290 GetClientRect(&rcScroll);
2291 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
2292 InvalidateRect(&rcScroll, FALSE);
2294 UpdateLocator();
2295 RecalcVertScrollBar();
2296 RecalcHorzScrollBar();
2298 UpdateCaret();
2301 BOOL CBaseView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
2303 if (m_pwndLeft)
2304 m_pwndLeft->OnDoMouseWheel(nFlags, zDelta, pt);
2305 if (m_pwndRight)
2306 m_pwndRight->OnDoMouseWheel(nFlags, zDelta, pt);
2307 if (m_pwndBottom)
2308 m_pwndBottom->OnDoMouseWheel(nFlags, zDelta, pt);
2309 if (m_pwndLocator)
2310 m_pwndLocator->Invalidate();
2311 return CView::OnMouseWheel(nFlags, zDelta, pt);
2314 void CBaseView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
2316 if (m_pwndLeft)
2317 m_pwndLeft->OnDoMouseHWheel(nFlags, zDelta, pt);
2318 if (m_pwndRight)
2319 m_pwndRight->OnDoMouseHWheel(nFlags, zDelta, pt);
2320 if (m_pwndBottom)
2321 m_pwndBottom->OnDoMouseHWheel(nFlags, zDelta, pt);
2322 if (m_pwndLocator)
2323 m_pwndLocator->Invalidate();
2326 void CBaseView::OnDoMouseWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/)
2328 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
2329 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
2331 if (bControl || bShift)
2333 if (m_pMainFrame->m_bWrapLines)
2334 return;
2335 // Ctrl-Wheel scrolls sideways
2336 ScrollSide(-zDelta/30);
2338 else
2340 ScrollVertical(zDelta);
2344 void CBaseView::OnDoMouseHWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/)
2346 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
2347 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
2349 if (bControl || bShift)
2351 // Ctrl-H-Wheel scrolls vertical
2352 ScrollVertical(zDelta);
2354 else
2356 if (m_pMainFrame->m_bWrapLines)
2357 return;
2358 // Ctrl-Wheel scrolls sideways
2359 ScrollSide(zDelta/30);
2363 BOOL CBaseView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
2365 if (nHitTest == HTCLIENT)
2367 if ((m_pViewData)&&(m_pMainFrame->m_bCollapsed))
2369 if (m_nMouseLine < m_Screen2View.size())
2371 if (m_nMouseLine >= 0)
2373 int viewLine = GetViewLineForScreen(m_nMouseLine);
2374 if (viewLine < m_pViewData->GetCount())
2376 if (m_pViewData->GetHideState(viewLine) == HIDESTATE_MARKER)
2378 ::SetCursor(::LoadCursor(nullptr, IDC_HAND));
2379 return TRUE;
2385 if (m_mouseInMargin)
2387 ::SetCursor(m_margincursor);
2388 return TRUE;
2390 if (m_nMouseLine >= 0)
2392 ::SetCursor(::LoadCursor(nullptr, IDC_IBEAM)); // Set To Edit Cursor
2393 return TRUE;
2396 ::SetCursor(::LoadCursor(nullptr, IDC_ARROW)); // Set To Arrow Cursor
2397 return TRUE;
2399 return CView::OnSetCursor(pWnd, nHitTest, message);
2402 void CBaseView::OnKillFocus(CWnd* pNewWnd)
2404 CView::OnKillFocus(pNewWnd);
2405 m_bFocused = FALSE;
2406 UpdateCaret();
2407 Invalidate();
2410 void CBaseView::OnSetFocus(CWnd* pOldWnd)
2412 CView::OnSetFocus(pOldWnd);
2413 m_bFocused = TRUE;
2414 UpdateCaret();
2415 Invalidate();
2418 int CBaseView::GetLineFromPoint(CPoint point)
2420 ScreenToClient(&point);
2421 return (((point.y - HEADERHEIGHT) / GetLineHeight()) + m_nTopLine);
2424 void CBaseView::OnContextMenu(CPoint point, DiffStates state)
2426 CRect rcClient;
2427 GetClientRect(rcClient);
2428 CRect textrect(rcClient.left, rcClient.top, rcClient.Width(), m_nLineHeight + HEADERHEIGHT);
2430 CRect borderrect(rcClient.left, rcClient.top + m_nLineHeight + HEADERHEIGHT, 0, rcClient.bottom);
2432 CPoint ptLocal = point;
2433 ScreenToClient(&ptLocal);
2435 if (textrect.PtInRect(ptLocal) || borderrect.PtInRect(ptLocal))
2437 // inside the header part of the view (showing the filename)
2438 if (IsViewGood(m_pwndBottom))
2440 CString temp;
2441 if (this == m_pwndLeft)
2443 CIconMenu popup;
2444 if (!popup.CreatePopupMenu())
2445 return;
2447 temp.LoadString(IDS_HEADER_DIFFLEFTTOBASE);
2448 popup.AppendMenu(MF_STRING | MF_ENABLED, 10, temp);
2449 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2450 if (cmd == 10)
2451 m_pMainFrame->DiffLeftToBase();
2453 if (this == m_pwndRight)
2455 CIconMenu popup;
2456 if (!popup.CreatePopupMenu())
2457 return;
2459 temp.LoadString(IDS_HEADER_DIFFRIGHTTOBASE);
2460 popup.AppendMenu(MF_STRING | MF_ENABLED, 10, temp);
2461 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2462 if (cmd == 10)
2463 m_pMainFrame->DiffRightToBase();
2466 return;
2469 if (!this->IsWindowVisible())
2470 return;
2472 CIconMenu popup;
2473 if (!popup.CreatePopupMenu())
2474 return;
2476 AddContextItems(popup, state);
2478 CMenu popupEols;
2479 CMenu popupUnicode;
2480 int nEncodingCommandBase = POPUPCOMMAND__LAST;
2481 int nEolCommandBase = nEncodingCommandBase+_countof(uctArray);
2482 if (IsWritable())
2484 CString temp;
2485 TWhitecharsProperties oWhites = GetWhitecharsProperties();
2486 temp.LoadString(IDS_EDIT_TAB2SPACE);
2487 popup.AppendMenu(MF_STRING | (oWhites.HasTabsToConvert ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_TABTOSPACES, temp);
2488 temp.LoadString(IDS_EDIT_SPACE2TAB);
2489 popup.AppendMenu(MF_STRING | (oWhites.HasSpacesToConvert ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_SPACESTOTABS, temp);
2490 temp.LoadString(IDS_EDIT_TRIM);
2491 popup.AppendMenu(MF_STRING | (oWhites.HasTrailWhiteChars ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_REMOVETRAILWHITES, temp);
2493 // add eol submenu
2494 if (!popupEols.CreatePopupMenu())
2495 return;
2497 EOL eEolType = GetLineEndings(oWhites.HasMixedEols);
2498 for (int i = 1; i < _countof(eolArray); i++)
2500 temp = GetEolName(eolArray[i]);
2501 bool bChecked = (eEolType == eolArray[i]);
2502 popupEols.AppendMenu(MF_STRING | MF_ENABLED | (bChecked ? MF_CHECKED : 0), nEolCommandBase+i, temp);
2505 temp.LoadString(IDS_VIEWCONTEXTMENU_EOL);
2506 popup.AppendMenuW(MF_POPUP | MF_ENABLED, reinterpret_cast<UINT_PTR>(popupEols.GetSafeHmenu()), temp);
2508 // add encoding submenu
2509 if (!popupUnicode.CreatePopupMenu())
2510 return;
2511 for (int i = 0; i < _countof(uctArray); i++)
2513 temp = CFileTextLines::GetEncodingName(uctArray[i]);
2514 bool bChecked = (m_texttype == uctArray[i]);
2515 popupUnicode.AppendMenu(MF_STRING | MF_ENABLED | (bChecked ? MF_CHECKED : 0), nEncodingCommandBase+i, temp);
2517 temp.LoadString(IDS_VIEWCONTEXTMENU_ENCODING);
2518 popup.AppendMenuW(MF_POPUP | MF_ENABLED, reinterpret_cast<UINT_PTR>(popupUnicode.GetSafeHmenu()), temp);
2522 CompensateForKeyboard(point);
2524 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2525 ResetUndoStep();
2526 if (cmd >= nEncodingCommandBase && (cmd < nEncodingCommandBase + static_cast<int>(_countof(uctArray))))
2528 SetTextType(uctArray[cmd-nEncodingCommandBase]);
2530 if (cmd >= nEolCommandBase && (cmd < nEolCommandBase + static_cast<int>(_countof(eolArray))))
2532 ReplaceLineEndings(eolArray[cmd-nEolCommandBase]);
2533 SaveUndoStep();
2535 switch (cmd)
2537 // 2-pane view commands; target is right view
2538 case POPUPCOMMAND_USELEFTBLOCK:
2539 m_pwndRight->UseLeftBlock();
2540 break;
2541 case POPUPCOMMAND_USELEFTFILE:
2542 m_pwndRight->UseLeftFile();
2543 break;
2544 case POPUPCOMMAND_USEBOTHLEFTFIRST:
2545 m_pwndRight->UseBothLeftFirst();
2546 break;
2547 case POPUPCOMMAND_USEBOTHRIGHTFIRST:
2548 m_pwndRight->UseBothRightFirst();
2549 break;
2550 case POPUPCOMMAND_MARKBLOCK:
2551 m_pwndRight->MarkBlock(true);
2552 break;
2553 case POPUPCOMMAND_UNMARKBLOCK:
2554 m_pwndRight->MarkBlock(false);
2555 break;
2556 case POPUPCOMMAND_LEAVEONLYMARKEDBLOCKS:
2557 m_pwndRight->LeaveOnlyMarkedBlocks();
2558 break;
2559 // 2-pane view multiedit commands; target is left view
2560 case POPUPCOMMAND_PREPENDFROMRIGHT:
2561 if (!m_pwndLeft->IsReadonly())
2562 m_pwndLeft->UseBothRightFirst();
2563 break;
2564 case POPUPCOMMAND_REPLACEBYRIGHT:
2565 if (!m_pwndLeft->IsReadonly())
2566 m_pwndLeft->UseRightBlock();
2567 break;
2568 case POPUPCOMMAND_APPENDFROMRIGHT:
2569 if (!m_pwndLeft->IsReadonly())
2570 m_pwndLeft->UseBothLeftFirst();
2571 break;
2572 case POPUPCOMMAND_USERIGHTFILE:
2573 m_pwndLeft->UseRightFile();
2574 break;
2575 // 3-pane view commands; target is bottom view
2576 case POPUPCOMMAND_USEYOURANDTHEIRBLOCK:
2577 m_pwndBottom->UseBothRightFirst();
2578 break;
2579 case POPUPCOMMAND_USETHEIRANDYOURBLOCK:
2580 m_pwndBottom->UseBothLeftFirst();
2581 break;
2582 case POPUPCOMMAND_USEYOURBLOCK:
2583 m_pwndBottom->UseRightBlock();
2584 break;
2585 case POPUPCOMMAND_USEYOURFILE:
2586 m_pwndBottom->UseRightFile();
2587 break;
2588 case POPUPCOMMAND_USETHEIRBLOCK:
2589 m_pwndBottom->UseLeftBlock();
2590 break;
2591 case POPUPCOMMAND_USETHEIRFILE:
2592 m_pwndBottom->UseLeftFile();
2593 break;
2594 // copy, cut and paste commands
2595 case ID_EDIT_COPY:
2596 OnEditCopy();
2597 break;
2598 case ID_EDIT_CUT:
2599 OnEditCut();
2600 break;
2601 case ID_EDIT_PASTE:
2602 OnEditPaste();
2603 break;
2604 // white chars manipulations
2605 case POPUPCOMMAND_TABTOSPACES:
2606 ConvertTabToSpaces();
2607 break;
2608 case POPUPCOMMAND_SPACESTOTABS:
2609 Tabularize();
2610 break;
2611 case POPUPCOMMAND_REMOVETRAILWHITES:
2612 RemoveTrailWhiteChars();
2613 break;
2614 default:
2615 return;
2616 } // switch (cmd)
2617 SaveUndoStep(); // all except copy, cut paste save undo step, but this should not be harmful as step is empty already and thus not saved
2618 return;
2621 void CBaseView::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
2623 if (!m_pViewData)
2624 return;
2626 int nViewBlockStart = -1;
2627 int nViewBlockEnd = -1;
2628 GetViewSelection(nViewBlockStart, nViewBlockEnd);
2629 if ((point.x != -1) && (point.y != -1))
2631 int nLine = GetLineFromPoint(point)-1;
2632 if ((nLine >= 0) && (nLine < m_Screen2View.size()))
2634 int nViewLine = GetViewLineForScreen(nLine);
2635 if (((nViewLine < nViewBlockStart) || (nViewBlockEnd < nViewLine)))
2637 ClearSelection(); // Clear text-copy selection
2639 nViewBlockStart = nViewLine;
2640 nViewBlockEnd = nViewLine;
2641 DiffStates state = m_pViewData->GetState(nViewLine);
2642 while (nViewBlockStart > 0)
2644 const DiffStates lineState = m_pViewData->GetState(nViewBlockStart-1);
2645 if (!LinesInOneChange(-1, state, lineState))
2646 break;
2647 nViewBlockStart--;
2650 while (nViewBlockEnd < (m_pViewData->GetCount()-1))
2652 const DiffStates lineState = m_pViewData->GetState(nViewBlockEnd+1);
2653 if (!LinesInOneChange(1, state, lineState))
2654 break;
2655 nViewBlockEnd++;
2658 SetupAllViewSelection(nViewBlockStart, nViewBlockEnd);
2659 UpdateCaretPosition(SetupPoint(0, nViewLine));
2664 // FixSelection(); fix selection range
2665 /*if (m_nSelBlockEnd >= m_pViewData->GetCount())
2666 m_nSelBlockEnd = m_pViewData->GetCount()-1;//*/
2668 DiffStates state = DIFFSTATE_UNKNOWN;
2669 if (GetViewSelection(nViewBlockStart, nViewBlockEnd))
2671 // find a more 'relevant' state in the selection
2672 for (int i=nViewBlockStart; i<=nViewBlockEnd; ++i)
2674 state = m_pViewData->GetState(i);
2675 if ((state != DIFFSTATE_NORMAL) && (state != DIFFSTATE_UNKNOWN))
2676 break;
2679 OnContextMenu(point, state);
2682 void CBaseView::RefreshViews()
2684 if (m_pwndLeft)
2686 m_pwndLeft->UpdateStatusBar();
2687 m_pwndLeft->UpdateCaret();
2688 m_pwndLeft->Invalidate();
2690 if (m_pwndRight)
2692 m_pwndRight->UpdateStatusBar();
2693 m_pwndRight->UpdateCaret();
2694 m_pwndRight->Invalidate();
2696 if (m_pwndBottom)
2698 m_pwndBottom->UpdateStatusBar();
2699 m_pwndBottom->UpdateCaret();
2700 m_pwndBottom->Invalidate();
2702 if (m_pwndLocator)
2703 m_pwndLocator->Invalidate();
2706 void CBaseView::GoToFirstDifference()
2708 SetCaretToFirstViewLine();
2709 SelectNextBlock(1, false, false);
2712 void CBaseView::GoToFirstConflict()
2714 SetCaretToFirstViewLine();
2715 SelectNextBlock(1, true, false);
2718 void CBaseView::HighlightViewLines(int nStart, int nEnd /* = -1 */)
2720 ClearSelection();
2721 SetupAllViewSelection(nStart, max(nStart, nEnd));
2723 UpdateCaretViewPosition(SetupPoint(0, nStart));
2724 Invalidate();
2727 void CBaseView::SetupAllViewSelection(int start, int end)
2729 SetupViewSelection(m_pwndBottom, start, end);
2730 SetupViewSelection(m_pwndLeft, start, end);
2731 SetupViewSelection(m_pwndRight, start, end);
2734 void CBaseView::SetupAllSelection(int start, int end)
2736 SetupAllViewSelection(GetViewLineForScreen(start), GetViewLineForScreen(end));
2739 //void CBaseView::SetupSelection(CBaseView* view, int start, int end) { }
2741 void CBaseView::SetupSelection(int start, int end)
2743 SetupViewSelection(GetViewLineForScreen(start), GetViewLineForScreen(end));
2746 void CBaseView::SetupViewSelection(CBaseView* view, int start, int end)
2748 if (!IsViewGood(view))
2749 return;
2750 view->SetupViewSelection(start, end);
2753 void CBaseView::SetupViewSelection(int start, int end)
2755 // clear text selection before setting line selection ?
2756 m_nSelViewBlockStart = start;
2757 m_nSelViewBlockEnd = end;
2758 Invalidate();
2762 void CBaseView::OnMergePreviousconflict()
2764 SelectNextBlock(-1, true);
2767 void CBaseView::OnMergeNextconflict()
2769 SelectNextBlock(1, true);
2772 void CBaseView::OnMergeNextdifference()
2774 SelectNextBlock(1, false);
2777 void CBaseView::OnMergePreviousdifference()
2779 SelectNextBlock(-1, false);
2782 bool CBaseView::HasNextConflict()
2784 return SelectNextBlock(1, true, true, true);
2787 bool CBaseView::HasPrevConflict()
2789 return SelectNextBlock(-1, true, true, true);
2792 bool CBaseView::HasNextDiff()
2794 return SelectNextBlock(1, false, true, true);
2797 bool CBaseView::HasPrevDiff()
2799 return SelectNextBlock(-1, false, true, true);
2802 bool CBaseView::LinesInOneChange(int direction,
2803 DiffStates initialLineState, DiffStates currentLineState)
2805 // Checks whether all the adjacent lines starting from the initial line
2806 // and up to the current line form the single change
2808 // First of all, if the two lines have identical states, they surely
2809 // belong to one change.
2810 if (initialLineState == currentLineState)
2811 return true;
2813 // Either we move down and initial line state is "added" or "removed" and
2814 // current line state is "empty"...
2815 if (direction > 0)
2817 if (currentLineState == DIFFSTATE_EMPTY)
2819 if (initialLineState == DIFFSTATE_ADDED || initialLineState == DIFFSTATE_REMOVED)
2820 return true;
2822 if (initialLineState == DIFFSTATE_CONFLICTADDED && currentLineState == DIFFSTATE_CONFLICTEMPTY)
2823 return true;
2825 // ...or we move up and initial line state is "empty" and current line
2826 // state is "added" or "removed".
2827 if (direction < 0)
2829 if (initialLineState == DIFFSTATE_EMPTY)
2831 if (currentLineState == DIFFSTATE_ADDED || currentLineState == DIFFSTATE_REMOVED)
2832 return true;
2834 if (initialLineState == DIFFSTATE_CONFLICTEMPTY && currentLineState == DIFFSTATE_CONFLICTADDED)
2835 return true;
2837 return false;
2840 bool CBaseView::SelectNextBlock(int nDirection, bool bConflict, bool bSkipEndOfCurrentBlock /* = true */, bool dryrun /* = false */)
2842 if (! m_pViewData)
2843 return false;
2845 const int linesCount = m_Screen2View.size();
2846 if(linesCount == 0)
2847 return false;
2849 int nCenterPos = GetCaretPosition().y;
2850 int nLimit = -1;
2851 if (nDirection > 0)
2852 nLimit = linesCount;
2854 if (nCenterPos >= linesCount)
2855 nCenterPos = linesCount-1;
2857 if (bSkipEndOfCurrentBlock)
2859 // Find end of current block
2860 const DiffStates state = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2861 while (nCenterPos != nLimit)
2863 const DiffStates lineState = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2864 if (!LinesInOneChange(nDirection, state, lineState))
2865 break;
2866 nCenterPos += nDirection;
2870 // Find next diff/conflict block
2871 while (nCenterPos != nLimit)
2873 DiffStates linestate = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2874 if (!bConflict &&
2875 (linestate != DIFFSTATE_NORMAL) &&
2876 (linestate != DIFFSTATE_UNKNOWN) &&
2877 (linestate != DIFFSTATE_FILTEREDDIFF))
2879 break;
2881 if (bConflict &&
2882 ((linestate == DIFFSTATE_CONFLICTADDED) ||
2883 (linestate == DIFFSTATE_CONFLICTED_IGNORED) ||
2884 (linestate == DIFFSTATE_CONFLICTED) ||
2885 (linestate == DIFFSTATE_CONFLICTEMPTY)))
2887 break;
2890 nCenterPos += nDirection;
2892 if (nCenterPos == nLimit)
2893 return false;
2894 if (dryrun)
2895 return (nCenterPos != nLimit);
2897 // Find end of new block
2898 DiffStates state = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2899 int nBlockEnd = nCenterPos;
2900 const int maxAllowedLine = nLimit-nDirection;
2901 while (nBlockEnd != maxAllowedLine)
2903 const int lineIndex = nBlockEnd + nDirection;
2904 if (lineIndex >= linesCount)
2905 break;
2906 DiffStates lineState = m_pViewData->GetState(GetViewLineForScreen(lineIndex));
2907 if (!LinesInOneChange(nDirection, state, lineState))
2908 break;
2909 nBlockEnd += nDirection;
2912 int nTopPos = nCenterPos - (GetScreenLines()/2);
2913 if (nTopPos < 0)
2914 nTopPos = 0;
2916 POINT ptCaretPos = {0, nCenterPos};
2917 SetCaretPosition(ptCaretPos);
2918 ClearSelection();
2919 if (nDirection > 0)
2920 SetupAllSelection(nCenterPos, nBlockEnd);
2921 else
2922 SetupAllSelection(nBlockEnd, nCenterPos);
2924 ScrollAllToLine(nTopPos, FALSE);
2925 RecalcAllVertScrollBars(TRUE);
2926 SetCaretToLineStart();
2927 EnsureCaretVisible();
2928 OnNavigateNextinlinediff();
2930 UpdateViewsCaretPosition();
2931 UpdateCaret();
2932 ShowDiffLines(nCenterPos);
2933 return true;
2936 BOOL CBaseView::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
2938 if (pNMHDR->idFrom != reinterpret_cast<UINT_PTR>(m_hWnd))
2939 return FALSE;
2941 CString strTipText;
2942 strTipText = m_sWindowName + L"\r\n" + m_sFullFilePath;
2944 DWORD pos = GetMessagePos();
2945 CPoint point(GET_X_LPARAM(pos), GET_Y_LPARAM(pos));
2946 ScreenToClient(&point);
2947 const int nLine = GetButtonEventLineIndex(point);
2949 if (nLine >= 0)
2951 int nViewLine = GetViewLineForScreen(nLine);
2952 if((m_pViewData)&&(nViewLine < m_pViewData->GetCount()))
2954 auto movedIndex = m_pViewData->GetMovedIndex(nViewLine);
2955 if (movedIndex >= 0)
2957 if (m_pViewData->IsMovedFrom(nViewLine))
2959 strTipText.Format(IDS_MOVED_TO_TT, movedIndex+1);
2961 else
2963 strTipText.Format(IDS_MOVED_FROM_TT, movedIndex+1);
2970 *pResult = 0;
2971 if (strTipText.IsEmpty())
2972 return TRUE;
2974 // need to handle both ANSI and UNICODE versions of the message
2975 if (pNMHDR->code == TTN_NEEDTEXTA)
2977 auto pTTTA = reinterpret_cast<TOOLTIPTEXTA*>(pNMHDR);
2978 pTTTA->lpszText = m_szTip;
2979 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
2981 else
2983 auto pTTTW = reinterpret_cast<TOOLTIPTEXTW*>(pNMHDR);
2984 lstrcpyn(m_wszTip, strTipText, min(strTipText.GetLength() + 1, static_cast<int>(_countof(m_wszTip)) - 1));
2985 pTTTW->lpszText = m_wszTip;
2988 return TRUE; // message was handled
2991 INT_PTR CBaseView::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
2993 CRect rcClient;
2994 GetClientRect(rcClient);
2995 CRect textrect(rcClient.left, rcClient.top, rcClient.Width(), m_nLineHeight+HEADERHEIGHT);
2997 int marginwidth = GetSystemMetrics(SM_CXSMICON) + CDPIAware::Instance().ScaleX(4);
2998 if ((m_bViewLinenumbers)&&(m_pViewData)&&(m_pViewData->GetCount())&&(m_nDigits > 0))
3000 marginwidth += (m_nDigits * m_nCharWidth) + CDPIAware::Instance().ScaleX(2);
3002 CRect borderrect(rcClient.left, rcClient.top+m_nLineHeight+HEADERHEIGHT, marginwidth, rcClient.bottom);
3004 if (textrect.PtInRect(point) || borderrect.PtInRect(point))
3006 // inside the header part of the view (showing the filename)
3007 pTI->hwnd = this->m_hWnd;
3008 this->GetClientRect(&pTI->rect);
3009 pTI->uFlags |= TTF_ALWAYSTIP | TTF_IDISHWND;
3010 pTI->uId = reinterpret_cast<UINT_PTR>(m_hWnd);
3011 pTI->lpszText = LPSTR_TEXTCALLBACK;
3013 // we want multi line tooltips
3014 CToolTipCtrl* pToolTip = AfxGetModuleThreadState()->m_pToolTip;
3015 if (pToolTip->GetSafeHwnd())
3016 pToolTip->SetMaxTipWidth(SHRT_MAX);
3018 return (textrect.PtInRect(point) ? 1 : 2);
3021 return -1;
3024 void CBaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
3026 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
3027 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
3029 switch (nChar)
3031 case VK_TAB:
3032 if (bControl)
3034 if (this==m_pwndLeft)
3036 if (IsViewGood(m_pwndRight))
3038 m_pwndRight->SetFocus();
3040 else if (IsViewGood(m_pwndBottom))
3042 m_pwndBottom->SetFocus();
3045 else if (this==m_pwndRight)
3047 if (IsViewGood(m_pwndBottom))
3049 m_pwndBottom->SetFocus();
3051 else if (IsViewGood(m_pwndLeft))
3053 m_pwndLeft->SetFocus();
3056 else if (this==m_pwndBottom)
3058 if (IsViewGood(m_pwndLeft))
3060 m_pwndLeft->SetFocus();
3062 else if (IsViewGood(m_pwndRight))
3064 m_pwndRight->SetFocus();
3068 break;
3069 case VK_PRIOR:
3071 POINT ptCaretPos = GetCaretPosition();
3072 ptCaretPos.y -= GetScreenLines();
3073 ptCaretPos.y = max(ptCaretPos.y, 0l);
3074 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
3075 SetCaretPosition(ptCaretPos);
3076 OnCaretMove(MOVELEFT, bShift);
3077 ShowDiffLines(ptCaretPos.y);
3079 break;
3080 case VK_NEXT:
3082 POINT ptCaretPos = GetCaretPosition();
3083 ptCaretPos.y += GetScreenLines();
3084 if (ptCaretPos.y >= GetLineCount())
3085 ptCaretPos.y = GetLineCount()-1;
3086 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
3087 SetCaretPosition(ptCaretPos);
3088 OnCaretMove(MOVERIGHT, bShift);
3089 ShowDiffLines(ptCaretPos.y);
3091 break;
3092 case VK_HOME:
3094 if (bControl)
3096 ScrollAllToLine(0);
3097 ScrollAllToChar(0);
3098 SetCaretToViewStart();
3099 m_nCaretGoalPos = 0;
3100 if (bShift)
3101 AdjustSelection(MOVELEFT);
3102 else
3103 ClearSelection();
3104 UpdateCaret();
3106 else
3108 POINT ptCaretPos = GetCaretPosition();
3109 CString sLine = GetLineChars(ptCaretPos.y);
3110 int pos = 0;
3111 while (pos < sLine.GetLength())
3113 if (sLine[pos] != ' ' && sLine[pos] != '\t')
3114 break;
3115 ++pos;
3117 if (ptCaretPos.x == pos)
3119 SetCaretToLineStart();
3120 m_nCaretGoalPos = 0;
3121 OnCaretMove(MOVERIGHT, bShift);
3122 ScrollAllToChar(0);
3124 else
3126 ptCaretPos.x = pos;
3127 SetCaretAndGoalPosition(ptCaretPos);
3128 OnCaretMove(MOVELEFT, bShift);
3132 break;
3133 case VK_END:
3135 if (bControl)
3137 ScrollAllToLine(GetLineCount()-GetAllMinScreenLines());
3138 POINT ptCaretPos;
3139 ptCaretPos.y = GetLineCount()-1;
3140 ptCaretPos.x = GetLineLength(ptCaretPos.y);
3141 SetCaretAndGoalPosition(ptCaretPos);
3142 if (bShift)
3143 AdjustSelection(MOVERIGHT);
3144 else
3145 ClearSelection();
3147 else
3149 POINT ptCaretPos = GetCaretPosition();
3150 ptCaretPos.x = GetLineLength(ptCaretPos.y);
3151 if ((GetSubLineOffset(ptCaretPos.y) != -1) && (GetSubLineOffset(ptCaretPos.y) != CountMultiLines(GetViewLineForScreen(ptCaretPos.y))-1)) // not last screen line of view line
3153 ptCaretPos.x--;
3155 SetCaretAndGoalPosition(ptCaretPos);
3156 OnCaretMove(MOVERIGHT, bShift);
3159 break;
3160 case VK_BACK:
3161 if (IsWritable())
3163 if (! HasTextSelection())
3165 POINT ptCaretPos = GetCaretPosition();
3166 if (ptCaretPos.y == 0 && ptCaretPos.x == 0)
3167 break;
3168 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3169 if (bControl)
3170 MoveCaretWordLeft();
3171 else
3173 while (MoveCaretLeft() && IsViewLineEmpty(GetCaretViewPosition().y))
3177 m_ptSelectionViewPosStart = GetCaretViewPosition();
3179 RemoveSelectedText();
3181 break;
3182 case VK_DELETE:
3183 if (IsWritable())
3185 if (! HasTextSelection())
3187 if (bControl)
3189 m_ptSelectionViewPosStart = GetCaretViewPosition();
3190 MoveCaretWordRight();
3191 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3193 else
3195 if (! MoveCaretRight())
3196 break;
3197 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3198 MoveCaretLeft();
3199 m_ptSelectionViewPosStart = GetCaretViewPosition();
3202 RemoveSelectedText();
3204 break;
3205 case VK_INSERT:
3206 m_bInsertMode = !m_bInsertMode;
3207 UpdateCaret();
3208 break;
3209 case 'M':
3210 if (bControl && m_pwndRight)
3212 int nFirstViewLine = 0;
3213 int nLastViewLine = 0;
3214 if (GetViewSelection(nFirstViewLine, nLastViewLine))
3215 m_pwndRight->MarkBlock(!m_pwndRight->GetViewMarked(nFirstViewLine));
3217 break;
3219 CView::OnKeyDown(nChar, nRepCnt, nFlags);
3222 void CBaseView::OnLButtonDown(UINT nFlags, CPoint point)
3224 const int nClickedLine = GetButtonEventLineIndex(point);
3225 m_nLDownLine = nClickedLine;
3226 if ((nClickedLine >= m_nTopLine)&&(nClickedLine < GetLineCount()))
3228 POINT ptCaretPos;
3229 ptCaretPos.y = nClickedLine;
3230 int xpos2 = CalcColFromPoint(point.x, nClickedLine);
3231 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, xpos2);
3232 SetCaretAndGoalPosition(ptCaretPos);
3234 if (nFlags & MK_SHIFT)
3235 AdjustSelection(MOVERIGHT);
3236 else
3238 ClearSelection();
3239 SetupAllSelection(ptCaretPos.y, ptCaretPos.y);
3240 if (point.x < GetMarginWidth())
3242 // select the whole line
3243 m_ptSelectionViewPosStart = m_ptSelectionViewPosEnd = GetCaretViewPosition();
3244 m_ptSelectionViewPosStart.x = 0;
3245 m_ptSelectionViewPosEnd.x = GetViewLineLength(m_ptSelectionViewPosEnd.y);
3249 UpdateViewsCaretPosition();
3250 Invalidate();
3253 CView::OnLButtonDown(nFlags, point);
3256 CBaseView::ECharGroup CBaseView::GetCharGroup(wchar_t zChar) const
3258 if (zChar == ' ' || zChar == '\t' )
3260 return CHG_WHITESPACE;
3262 if (zChar < 0x20)
3264 return CHG_CONTROL;
3266 if (m_sWordSeparators.Find(zChar) >= 0)
3268 return CHG_WORDSEPARATOR;
3270 return CHG_WORDLETTER;
3273 void CBaseView::OnLButtonDblClk(UINT nFlags, CPoint point)
3275 if (m_pViewData == 0) {
3276 CView::OnLButtonDblClk(nFlags, point);
3277 return;
3280 const int nClickedLine = GetButtonEventLineIndex(point);
3281 if ( nClickedLine < 0)
3282 return;
3283 int nViewLine = GetViewLineForScreen(nClickedLine);
3284 if (point.x < GetMarginWidth()) // only if double clicked on the margin
3286 if((nViewLine < m_pViewData->GetCount())) // a double click on moved line scrolls to corresponding line
3288 if (m_pViewData->GetMovedIndex(nViewLine)>=0)
3290 int movedindex = m_pViewData->GetMovedIndex(nViewLine);
3291 int screenLine = FindViewLineNumber(movedindex);
3292 int nTop = screenLine - GetScreenLines()/2;
3293 if (nTop < 0)
3294 nTop = 0;
3295 ScrollAllToLine(nTop);
3296 // find and select the whole moved block
3297 int startSel = movedindex;
3298 int endSel = movedindex;
3299 while ((startSel > 0) && (m_pOtherViewData->GetMovedIndex(startSel) >= 0))
3300 startSel--;
3301 startSel++;
3302 while ((endSel < GetLineCount()) && (m_pOtherViewData->GetMovedIndex(endSel) >= 0))
3303 endSel++;
3304 endSel--;
3305 m_pOtherView->SetupSelection(startSel, endSel);
3306 return CView::OnLButtonDblClk(nFlags, point);
3310 if ((m_pMainFrame->m_bCollapsed)&&(m_pViewData->GetHideState(nViewLine) == HIDESTATE_MARKER))
3312 // a double click on a marker expands the hidden text
3313 int i = nViewLine;
3314 while ((i < m_pViewData->GetCount())&&(m_pViewData->GetHideState(i) != HIDESTATE_SHOWN))
3316 if ((m_pwndLeft)&&(m_pwndLeft->m_pViewData))
3317 m_pwndLeft->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN);
3318 if ((m_pwndRight)&&(m_pwndRight->m_pViewData))
3319 m_pwndRight->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN);
3320 if ((m_pwndBottom)&&(m_pwndBottom->m_pViewData))
3321 m_pwndBottom->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN);
3322 i++;
3324 BuildAllScreen2ViewVector();
3325 if (m_pwndLeft)
3326 m_pwndLeft->Invalidate();
3327 if (m_pwndRight)
3328 m_pwndRight->Invalidate();
3329 if (m_pwndBottom)
3330 m_pwndBottom->Invalidate();
3332 else
3334 POINT ptCaretPos;
3335 ptCaretPos.y = nClickedLine;
3336 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth());
3337 SetCaretPosition(ptCaretPos);
3338 ClearSelection();
3340 POINT ptViewCarret = GetCaretViewPosition();
3341 nViewLine = ptViewCarret.y;
3342 if (nViewLine >= GetViewCount())
3343 return;
3344 const CString &sLine = GetViewLine(nViewLine);
3345 int nLineLength = sLine.GetLength();
3346 int nBasePos = ptViewCarret.x;
3347 // get target char group
3348 ECharGroup eLeft = CHG_UNKNOWN;
3349 if (nBasePos > 0)
3351 eLeft = GetCharGroup(sLine[nBasePos-1]);
3353 ECharGroup eRight = CHG_UNKNOWN;
3354 if (nBasePos < nLineLength)
3356 eRight = GetCharGroup(sLine[nBasePos]);
3358 ECharGroup eTarget = max(eRight, eLeft);
3359 // find left margin
3360 int nLeft = nBasePos;
3361 while (nLeft > 0 && GetCharGroup(sLine[nLeft-1]) == eTarget)
3363 nLeft--;
3365 // get right margin
3366 int nRight = nBasePos;
3367 while (nRight < nLineLength && GetCharGroup(sLine[nRight]) == eTarget)
3369 nRight++;
3371 // set selection
3372 m_ptSelectionViewPosStart.x = nLeft;
3373 m_ptSelectionViewPosStart.y = nViewLine;
3374 m_ptSelectionViewPosEnd.x = nRight;
3375 m_ptSelectionViewPosEnd.y = nViewLine;
3376 m_ptSelectionViewPosOrigin = m_ptSelectionViewPosStart;
3377 SetupAllViewSelection(nViewLine, nViewLine);
3378 // set caret
3379 ptCaretPos = ConvertViewPosToScreen(m_ptSelectionViewPosEnd);
3380 UpdateViewsCaretPosition();
3381 UpdateGoalPos();
3383 // set mark word
3384 m_sPreviousMarkedWord = m_sMarkedWord; // store marked word to recall in case of triple click
3385 int nMarkWidth = max(nRight - nLeft, 0);
3386 m_sMarkedWord = sLine.Mid(m_ptSelectionViewPosStart.x, nMarkWidth).Trim();
3387 if (m_sMarkedWord.Compare(m_sPreviousMarkedWord) == 0)
3389 m_sMarkedWord.Empty();
3392 if (GetKeyState(VK_CONTROL) & 0x8000)
3394 // if the ctrl key is pressed, only
3395 // mark the words in this view
3396 SetMarkedWord(m_sMarkedWord);
3398 else
3400 if (m_pwndLeft)
3401 m_pwndLeft->SetMarkedWord(m_sMarkedWord);
3402 if (m_pwndRight)
3403 m_pwndRight->SetMarkedWord(m_sMarkedWord);
3404 if (m_pwndBottom)
3405 m_pwndBottom->SetMarkedWord(m_sMarkedWord);
3408 Invalidate();
3409 if (m_pwndLocator)
3410 m_pwndLocator->Invalidate();
3413 CView::OnLButtonDblClk(nFlags, point);
3416 void CBaseView::OnLButtonTrippleClick( UINT /*nFlags*/, CPoint point )
3418 const int nClickedLine = GetButtonEventLineIndex(point);
3419 if (((point.y - (GetLineHeight() + HEADERHEIGHT)) / GetLineHeight()) <= 0)
3421 if (!m_sConvertedFilePath.IsEmpty() && (GetKeyState(VK_CONTROL)&0x8000))
3423 PCIDLIST_ABSOLUTE __unaligned pidl = ILCreateFromPath(static_cast<LPCWSTR>(m_sConvertedFilePath));
3424 if (pidl)
3426 SHOpenFolderAndSelectItems(pidl,0,0,0);
3427 CoTaskMemFree((LPVOID)pidl);
3430 return;
3432 POINT ptCaretPos;
3433 ptCaretPos.y = nClickedLine;
3434 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth());
3435 SetCaretAndGoalPosition(ptCaretPos);
3436 m_sMarkedWord = m_sPreviousMarkedWord; // recall previous Marked word
3437 if (m_pwndLeft)
3438 m_pwndLeft->SetMarkedWord(m_sMarkedWord);
3439 if (m_pwndRight)
3440 m_pwndRight->SetMarkedWord(m_sMarkedWord);
3441 if (m_pwndBottom)
3442 m_pwndBottom->SetMarkedWord(m_sMarkedWord);
3443 ClearSelection();
3444 m_ptSelectionViewPosStart.x = 0;
3445 m_ptSelectionViewPosStart.y = nClickedLine;
3446 m_ptSelectionViewPosEnd.x = GetLineLength(nClickedLine);
3447 m_ptSelectionViewPosEnd.y = nClickedLine;
3448 SetupSelection(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
3449 UpdateViewsCaretPosition();
3450 Invalidate();
3451 if (m_pwndLocator)
3452 m_pwndLocator->Invalidate();
3455 void CBaseView::OnEditCopy()
3457 CString sCopyData = GetSelectedText();
3459 if (!sCopyData.IsEmpty())
3461 CStringUtils::WriteAsciiStringToClipboard(sCopyData, m_hWnd);
3465 void CBaseView::OnMouseMove(UINT nFlags, CPoint point)
3467 if (m_pMainFrame->m_nMoveMovesToIgnore > 0)
3469 --m_pMainFrame->m_nMoveMovesToIgnore;
3470 CView::OnMouseMove(nFlags, point);
3471 return;
3473 int nMouseLine = GetButtonEventLineIndex(point);
3474 if (nMouseLine < -1)
3475 nMouseLine = -1;
3476 m_mouseInMargin = point.x < GetMarginWidth();
3478 ShowDiffLines(nMouseLine);
3480 KillTimer(IDT_SCROLLTIMER);
3481 if (nFlags & MK_LBUTTON)
3483 int saveMouseLine = nMouseLine >= 0 ? nMouseLine : 0;
3484 saveMouseLine = saveMouseLine < GetLineCount() ? saveMouseLine : GetLineCount() - 1;
3485 if (saveMouseLine < 0)
3486 return;
3487 int col = CalcColFromPoint(point.x, saveMouseLine);
3488 int charIndex = CalculateCharIndex(saveMouseLine, col);
3489 if (HasSelection() &&
3490 ((nMouseLine >= m_nTopLine)&&(nMouseLine < GetLineCount())))
3492 POINT ptCaretPos = {charIndex, nMouseLine};
3493 SetCaretAndGoalPosition(ptCaretPos);
3494 AdjustSelection(MOVERIGHT);
3495 Invalidate();
3496 UpdateWindow();
3498 if (m_nLDownLine < 0)
3499 return; // no scrolling if the click started on the header
3500 if (nMouseLine < m_nTopLine)
3502 ScrollAllToLine(m_nTopLine-1, TRUE);
3503 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3505 if (nMouseLine >= m_nTopLine + GetScreenLines() - 2)
3507 ScrollAllToLine(m_nTopLine+1, TRUE);
3508 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3510 if (!m_pMainFrame->m_bWrapLines && ((m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()) <= m_nOffsetChar))
3512 ScrollAllSide(-1);
3513 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3515 if (!m_pMainFrame->m_bWrapLines && (charIndex >= (GetScreenChars()+m_nOffsetChar-4)))
3517 ScrollAllSide(1);
3518 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3520 SetCapture();
3524 CView::OnMouseMove(nFlags, point);
3527 void CBaseView::OnLButtonUp(UINT nFlags, CPoint point)
3529 ShowDiffLines(-1);
3530 ReleaseCapture();
3531 KillTimer(IDT_SCROLLTIMER);
3533 __super::OnLButtonUp(nFlags, point);
3536 void CBaseView::OnTimer(UINT_PTR nIDEvent)
3538 if (nIDEvent == IDT_SCROLLTIMER)
3540 POINT point;
3541 GetCursorPos(&point);
3542 ScreenToClient(&point);
3543 int nMouseLine = GetButtonEventLineIndex(point);
3544 if (nMouseLine < -1)
3546 nMouseLine = -1;
3548 if (GetKeyState(VK_LBUTTON)&0x8000)
3550 int saveMouseLine = nMouseLine >= 0 ? nMouseLine : 0;
3551 saveMouseLine = saveMouseLine < GetLineCount() ? saveMouseLine : GetLineCount() - 1;
3552 int charIndex = CalculateCharIndex(saveMouseLine, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth());
3553 if (nMouseLine < m_nTopLine)
3555 ScrollAllToLine(m_nTopLine-1, TRUE);
3556 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3558 if (nMouseLine >= m_nTopLine + GetScreenLines() - 2)
3560 ScrollAllToLine(m_nTopLine+1, TRUE);
3561 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3563 if (!m_pMainFrame->m_bWrapLines && ((m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()) <= m_nOffsetChar))
3565 ScrollAllSide(-1);
3566 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3568 if (!m_pMainFrame->m_bWrapLines && (charIndex >= (GetScreenChars()+m_nOffsetChar-4)))
3570 ScrollAllSide(1);
3571 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3577 CView::OnTimer(nIDEvent);
3580 void CBaseView::ShowDiffLines(int nLine)
3582 if ((nLine < m_nTopLine)||(nLine >= GetLineCount()))
3584 m_pwndLineDiffBar->ShowLines(nLine);
3585 nLine = -1;
3586 m_nMouseLine = nLine;
3587 return;
3590 if ((!m_pwndRight)||(!m_pwndLeft))
3591 return;
3592 if(m_pMainFrame->m_bOneWay)
3593 return;
3595 nLine = (nLine > m_pwndRight->m_Screen2View.size() ? -1 : nLine);
3596 nLine = (nLine > m_pwndLeft->m_Screen2View.size() ? -1 : nLine);
3598 if (nLine < 0)
3599 return;
3601 if (nLine != m_nMouseLine)
3603 if (nLine >= GetLineCount())
3604 nLine = -1;
3605 m_nMouseLine = nLine;
3606 m_pwndLineDiffBar->ShowLines(nLine);
3608 m_pMainFrame->m_nMoveMovesToIgnore = MOVESTOIGNORE;
3611 const viewdata& CBaseView::GetEmptyLineData()
3613 static const viewdata emptyLine(L"", DIFFSTATE_EMPTY, -1, EOL_NOENDING, HIDESTATE_SHOWN);
3614 return emptyLine;
3617 void CBaseView::InsertViewEmptyLines(int nFirstView, int nCount)
3619 for (int i = 0; i < nCount; i++)
3621 InsertViewData(nFirstView, GetEmptyLineData());
3626 void CBaseView::UpdateCaret()
3628 POINT ptCaretPos = GetCaretPosition();
3629 ptCaretPos.y = std::max<int>(std::min<int>(ptCaretPos.y, GetLineCount()-1), 0);
3630 ptCaretPos.x = std::max<int>(std::min<int>(ptCaretPos.x, GetLineLength(ptCaretPos.y)), 0);
3631 SetCaretPosition(ptCaretPos);
3633 int nCaretOffset = CalculateActualOffset(ptCaretPos);
3635 if (m_bFocused &&
3636 ptCaretPos.y >= m_nTopLine &&
3637 ptCaretPos.y < (m_nTopLine+GetScreenLines()) &&
3638 nCaretOffset >= m_nOffsetChar &&
3639 nCaretOffset < (m_nOffsetChar+GetScreenChars()))
3641 POINT pt1 = TextToClient(ptCaretPos);
3642 if (m_bInsertMode)
3643 CreateSolidCaret(2, GetLineHeight());
3644 else
3646 POINT pt = { ptCaretPos.x + 1, ptCaretPos.y };
3647 POINT pt2 = TextToClient(pt);
3648 int width = max(GetCharWidth(), static_cast<int>(pt2.x - pt1.x));
3649 CreateSolidCaret(width, GetLineHeight());
3651 SetCaretPos(pt1);
3652 ShowCaret();
3654 else
3656 HideCaret();
3660 POINT CBaseView::ConvertScreenPosToView(const POINT& pt)
3662 POINT ptViewPos;
3663 ptViewPos.x = pt.x;
3665 int nSubLine = GetSubLineOffset(pt.y);
3666 if (nSubLine > 0)
3668 for (int nScreenLine = pt.y-1; nScreenLine >= pt.y-nSubLine; nScreenLine--)
3670 ptViewPos.x += GetLineChars(nScreenLine).GetLength();
3674 ptViewPos.y = GetViewLineForScreen(pt.y);
3675 return ptViewPos;
3678 POINT CBaseView::ConvertViewPosToScreen(const POINT& pt)
3680 POINT ptPos;
3681 int nViewLineLenLeft = GetViewLineLength(pt.y);
3682 ptPos.x = min(static_cast<LONG>(nViewLineLenLeft), pt.x);
3683 ptPos.y = FindScreenLineForViewLine(pt.y);
3684 if (GetViewLineForScreen(ptPos.y) != pt.y )
3686 ptPos.x = 0;
3688 else if (GetSubLineOffset(ptPos.y) >= 0) // sublined
3690 int nSubLineLength = GetLineChars(ptPos.y).GetLength();
3691 while (nSubLineLength < ptPos.x)
3693 ptPos.x -= nSubLineLength;
3694 nViewLineLenLeft -= nSubLineLength;
3695 ptPos.y++;
3696 nSubLineLength = GetLineChars(ptPos.y).GetLength();
3698 // last pos of non last sub-line go to start of next screen line
3699 // Note: while this works correctly, it's not what a user might expect:
3700 // cursor-right when the caret is before the last char of a wrapped line
3701 // now moves the caret to the next line. But users expect the caret to
3702 // move to the right of the last char instead, and with another cursor-right
3703 // keystroke to move the caret to the next line.
3704 // Basically, this would require to handle two caret positions for the same
3705 // logical position in the line string (one on the last position of the first line,
3706 // one on the first position of the new line. For non-wrapped lines this works
3707 // because there's an 'invisible' newline char at the end of the first line.
3708 if (nSubLineLength == ptPos.x && nViewLineLenLeft > nSubLineLength)
3710 ptPos.x = 0;
3711 ptPos.y++;
3715 return ptPos;
3719 void CBaseView::EnsureCaretVisible()
3721 POINT ptCaretPos = GetCaretPosition();
3722 int nCaretOffset = CalculateActualOffset(ptCaretPos);
3724 if (ptCaretPos.y < m_nTopLine)
3725 ScrollAllToLine(ptCaretPos.y);
3726 int screnLines = GetScreenLines();
3727 if (screnLines)
3729 if (ptCaretPos.y >= (m_nTopLine+screnLines)-1)
3730 ScrollAllToLine(ptCaretPos.y-screnLines+2);
3731 if (nCaretOffset < m_nOffsetChar)
3732 ScrollAllToChar(nCaretOffset);
3733 if (nCaretOffset > (m_nOffsetChar+GetScreenChars()-1))
3734 ScrollAllToChar(nCaretOffset-GetScreenChars()+1);
3738 int CBaseView::CalculateActualOffset(const POINT& point)
3740 int nLineIndex = point.y;
3741 int nCharIndex = point.x;
3742 ASSERT(nCharIndex >= 0);
3743 CString sLine = GetLineChars(nLineIndex);
3744 int nLineLength = sLine.GetLength();
3745 return CountExpandedChars(sLine, min(nCharIndex, nLineLength));
3748 int CBaseView::CalculateCharIndex(int nLineIndex, int nActualOffset)
3750 int nLength = GetLineLength(nLineIndex);
3751 int nSubLine = GetSubLineOffset(nLineIndex);
3752 if (nSubLine>=0)
3754 int nViewLine = GetViewLineForScreen(nLineIndex);
3755 if (nViewLine >= 0 && nViewLine < static_cast<int>(m_ScreenedViewLine.size()))
3757 int nMultilineCount = CountMultiLines(nViewLine);
3758 if ((nMultilineCount>0) && (nSubLine<nMultilineCount-1))
3760 nLength--;
3764 CString Line = GetLineChars(nLineIndex);
3765 int nIndex = 0;
3766 int nOffset = 0;
3767 int nTabSize = GetTabSize();
3768 while (nOffset < nActualOffset && nIndex < nLength)
3770 if (Line.GetAt(nIndex) == L'\t')
3771 nOffset += (nTabSize - nOffset % nTabSize);
3772 else
3773 ++nOffset;
3774 ++nIndex;
3776 return nIndex;
3780 * @param xpos X coordinate in CBaseView
3781 * @param lineIndex logical line index (e.g. wrap/collapse)
3783 int CBaseView::CalcColFromPoint(int xpos, int lineIndex)
3785 int xpos2;
3786 CDC *pDC = GetDC();
3787 if (pDC)
3789 CString text = ExpandChars(GetLineChars(lineIndex), 0);
3790 int fit = text.GetLength();
3791 auto posBuffer = std::make_unique<int[]>(fit);
3792 pDC->SelectObject(GetFont()); // is this right font ?
3793 SIZE size;
3794 GetTextExtentExPoint(pDC->GetSafeHdc(), text, fit, INT_MAX, &fit, posBuffer.get(), &size);
3795 ReleaseDC(pDC);
3796 int lower = -1, upper = fit - 1;
3797 int xcheck = xpos - GetMarginWidth() + m_nOffsetChar * GetCharWidth();
3800 int middle = (upper + lower + 1) / 2;
3801 int width = posBuffer[middle];
3802 if (xcheck < width)
3803 upper = middle - 1;
3804 else
3805 lower = middle;
3806 } while (lower < upper);
3807 lower++;
3808 xpos2 = lower;
3809 if (lower < fit - 1)
3811 int charWidth = posBuffer[lower] - (lower > 0 ? posBuffer[lower - 1] : 0);
3812 if (posBuffer[lower] - xcheck <= charWidth / 2)
3813 xpos2++;
3816 else
3818 xpos2 = (xpos - GetMarginWidth()) / GetCharWidth() + m_nOffsetChar;
3819 if ((xpos % GetCharWidth()) >= (GetCharWidth()/2))
3820 xpos2++;
3822 return xpos2;
3825 POINT CBaseView::TextToClient(const POINT& point)
3827 POINT pt;
3828 int nOffsetScreenLine = max(0, static_cast<int>(point.y - m_nTopLine));
3829 pt.y = nOffsetScreenLine * GetLineHeight();
3830 pt.x = CalculateActualOffset(point);
3832 int nLeft = GetMarginWidth() - m_nOffsetChar * GetCharWidth();
3833 CDC * pDC = GetDC();
3834 if (pDC)
3836 pDC->SelectObject(GetFont()); // is this right font ?
3837 int nScreenLine = nOffsetScreenLine + m_nTopLine;
3838 CString sLine = GetLineChars(nScreenLine);
3839 ExpandChars(sLine, 0, std::min<int>(pt.x, sLine.GetLength()), sLine);
3840 nLeft += pDC->GetTextExtent(sLine, pt.x).cx;
3841 ReleaseDC(pDC);
3842 } else {
3843 nLeft += pt.x * GetCharWidth();
3846 pt.x = nLeft;
3847 pt.y = (pt.y + GetLineHeight() + HEADERHEIGHT);
3848 return pt;
3851 void CBaseView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
3853 CView::OnChar(nChar, nRepCnt, nFlags);
3855 bool bControl = !!(GetKeyState(VK_CONTROL) & 0x8000);
3856 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
3857 bool bSkipSelectionClear = false;
3859 if (IsReadonly())
3860 return;
3862 if ((::GetKeyState(VK_LBUTTON) & 0x8000) != 0 ||
3863 (::GetKeyState(VK_RBUTTON) & 0x8000) != 0)
3865 return;
3868 if (!m_pViewData) // no data - nothing to do
3869 return;
3871 if (nChar == VK_F16)
3873 // generated by a ctrl+backspace - ignore.
3875 else if (nChar==VK_TAB && HasTextLineSelection())
3877 // change indentation for selected lines
3878 if (bShift)
3880 RemoveIndentationForSelectedBlock();
3882 else
3884 AddIndentationForSelectedBlock();
3886 bSkipSelectionClear = true;
3888 else if ((nChar > 31)||(nChar == VK_TAB))
3890 ResetUndoStep();
3891 RemoveSelectedText();
3892 POINT ptCaretViewPos = GetCaretViewPosition();
3893 int nViewLine = ptCaretViewPos.y;
3894 if ((nViewLine==0)&&(GetViewCount()==0))
3895 OnChar(VK_RETURN, 0, 0);
3896 int charCount = 1;
3897 viewdata lineData = GetViewData(nViewLine);
3898 if (nChar == VK_TAB)
3900 int indentChars = GetIndentCharsForLine(ptCaretViewPos.x, nViewLine);
3901 if (indentChars > 0)
3903 lineData.sLine.Insert(ptCaretViewPos.x, CString(L' ', indentChars));
3904 charCount = indentChars;
3906 else
3907 lineData.sLine.Insert(ptCaretViewPos.x, L'\t');
3909 else
3911 if (m_bInsertMode)
3912 lineData.sLine.Insert(ptCaretViewPos.x, static_cast<wchar_t>(nChar));
3913 else
3915 if (lineData.sLine.GetLength() > ptCaretViewPos.x)
3916 lineData.sLine.SetAt(ptCaretViewPos.x, static_cast<wchar_t>(nChar));
3917 else
3918 lineData.sLine.Insert(ptCaretViewPos.x, static_cast<wchar_t>(nChar));
3921 if (IsStateEmpty(lineData.state) || IsStateConflicted(lineData.state) || lineData.state == DIFFSTATE_IDENTICALREMOVED)
3923 // if not last line set EOL
3924 for (int nCheckViewLine = nViewLine+1; nCheckViewLine < GetViewCount(); nCheckViewLine++)
3926 if (!IsViewLineEmpty(nCheckViewLine))
3928 lineData.ending = m_lineendings;
3929 break;
3932 // make sure previous (non empty) line have EOL set
3933 for (int nCheckViewLine = nViewLine-1; nCheckViewLine > 0; nCheckViewLine--)
3935 if (!IsViewLineEmpty(nCheckViewLine) && GetViewState(nCheckViewLine) != DIFFSTATE_IDENTICALREMOVED)
3937 if (GetViewLineEnding(nCheckViewLine) == EOL_NOENDING)
3939 SetViewLineEnding(nCheckViewLine, m_lineendings);
3941 break;
3945 lineData.state = DIFFSTATE_EDITED;
3946 bool bNeedRenumber = false;
3947 if (lineData.linenumber == -1)
3949 lineData.linenumber = 0;
3950 bNeedRenumber = true;
3952 SetViewData(nViewLine, lineData);
3953 SetModified();
3954 SaveUndoStep();
3955 BuildAllScreen2ViewVector(nViewLine);
3956 if (bNeedRenumber)
3958 UpdateViewLineNumbers();
3960 for (int i = 0; i < charCount; ++i)
3961 MoveCaretRight();
3962 UpdateGoalPos();
3964 else if (nChar == 10)
3966 int nViewLine = GetViewLineForScreen(GetCaretPosition().y);
3967 EOL eol = m_pViewData->GetLineEnding(nViewLine);
3968 EOL newEOL = EOL_CRLF;
3969 switch (eol)
3971 case EOL_CRLF:
3972 newEOL = EOL_CR;
3973 break;
3974 case EOL_CR:
3975 newEOL = EOL_LF;
3976 break;
3977 case EOL_LF:
3978 newEOL = EOL_CRLF;
3979 break;
3981 if (eol==EOL_NOENDING || eol==newEOL)
3982 // don't allow to change enter on empty line, or last text line (lines with EOL_NOENDING)
3983 // to add EOL on newly edited empty line hit enter
3984 // don't store into UNDO if no change happened
3985 // and don't mark file as modified
3986 return;
3987 AddUndoViewLine(nViewLine);
3988 m_pViewData->SetLineEnding(nViewLine, newEOL);
3989 m_pViewData->SetState(nViewLine, DIFFSTATE_EDITED);
3990 UpdateGoalPos();
3992 else if ((nChar == VK_RETURN) && !bControl)
3994 // insert a new, fresh and empty line below the cursor
3995 RemoveSelectedText();
3997 CUndo::GetInstance().BeginGrouping();
3999 POINT ptCaretViewPos = GetCaretViewPosition();
4000 int nViewLine = ptCaretViewPos.y;
4001 int nLeft = ptCaretViewPos.x;
4002 CString sLine = GetViewLineChars(nViewLine);
4003 CString sLineLeft = sLine.Left(nLeft);
4004 CString sLineRight = sLine.Right(sLine.GetLength() - nLeft);
4005 EOL eOriginalEnding = EOL_AUTOLINE;
4006 if (m_pViewData->GetCount() > nViewLine)
4007 eOriginalEnding = GetViewLineEnding(nViewLine);
4009 if (!sLineRight.IsEmpty() || (eOriginalEnding!=m_lineendings))
4011 viewdata newFirstLine(sLineLeft, DIFFSTATE_EDITED, 1, m_lineendings, HIDESTATE_SHOWN);
4012 SetViewData(nViewLine, newFirstLine);
4015 int nInsertLine = (m_pViewData->GetCount()==0) ? 0 : nViewLine + 1;
4016 viewdata newLastLine(sLineRight, DIFFSTATE_EDITED, 1, eOriginalEnding, HIDESTATE_SHOWN);
4017 InsertViewData(nInsertLine, newLastLine);
4018 SetModified();
4019 SaveUndoStep();
4021 // adds new line everywhere except me
4022 if (IsViewGood(m_pwndLeft) && m_pwndLeft!=this)
4024 m_pwndLeft->InsertViewEmptyLines(nInsertLine, 1);
4026 if (IsViewGood(m_pwndRight) && m_pwndRight!=this)
4028 m_pwndRight->InsertViewEmptyLines(nInsertLine, 1);
4030 if (IsViewGood(m_pwndBottom) && m_pwndBottom!=this)
4032 m_pwndBottom->InsertViewEmptyLines(nInsertLine, 1);
4034 SaveUndoStep();
4036 UpdateViewLineNumbers();
4037 SaveUndoStep();
4038 CUndo::GetInstance().EndGrouping();
4040 BuildAllScreen2ViewVector();
4041 // move the cursor to the new line
4042 ptCaretViewPos = SetupPoint(0, nViewLine+1);
4043 SetCaretAndGoalViewPosition(ptCaretViewPos);
4045 else
4046 return; // Unknown control character -- ignore it.
4047 if (!bSkipSelectionClear)
4048 ClearSelection();
4049 EnsureCaretVisible();
4050 UpdateCaret();
4051 Invalidate(FALSE);
4054 void CBaseView::AddUndoViewLine(int nViewLine, bool bAddEmptyLine)
4056 ResetUndoStep();
4057 m_AllState.left.AddViewLineFromView(m_pwndLeft, nViewLine, bAddEmptyLine);
4058 m_AllState.right.AddViewLineFromView(m_pwndRight, nViewLine, bAddEmptyLine);
4059 m_AllState.bottom.AddViewLineFromView(m_pwndBottom, nViewLine, bAddEmptyLine);
4060 SetModified();
4061 SaveUndoStep();
4062 RecalcAllVertScrollBars();
4063 Invalidate(FALSE);
4066 void CBaseView::AddEmptyViewLine(int nViewLineIndex)
4068 if (!m_pViewData)
4069 return;
4070 int viewLine = nViewLineIndex;
4071 EOL ending = m_pViewData->GetLineEnding(viewLine);
4072 if (ending == EOL_NOENDING)
4074 ending = m_lineendings;
4076 viewdata newLine(L"", DIFFSTATE_EDITED, -1, ending, HIDESTATE_SHOWN);
4077 if (IsTarget()) // TODO: once more wievs will writable this is not correct anymore
4079 CString sPartLine = GetViewLineChars(nViewLineIndex);
4080 int nPosx = GetCaretPosition().x; // should be view pos ?
4081 m_pViewData->SetLine(viewLine, sPartLine.Left(nPosx));
4082 sPartLine = sPartLine.Mid(nPosx);
4083 newLine.sLine = sPartLine;
4085 m_pViewData->InsertData(viewLine+1, newLine);
4086 BuildAllScreen2ViewVector();
4089 void CBaseView::RemoveSelectedText()
4091 if (!m_pViewData)
4092 return;
4093 if (!HasTextSelection())
4094 return;
4096 // fix selection if starts or ends on empty line
4097 SetCaretViewPosition(m_ptSelectionViewPosEnd);
4098 while (IsViewLineEmpty(GetCaretViewPosition().y) && MoveCaretRight())
4101 m_ptSelectionViewPosEnd = GetCaretViewPosition();
4102 SetCaretViewPosition(m_ptSelectionViewPosStart);
4103 while (IsViewLineEmpty(GetCaretViewPosition().y) && MoveCaretRight())
4106 m_ptSelectionViewPosStart = GetCaretViewPosition();
4107 if (!HasTextSelection())
4109 ClearSelection();
4110 return;
4113 // We want to undo the insertion in a single step.
4114 ResetUndoStep();
4115 CUndo::GetInstance().BeginGrouping();
4117 // combine first and last line
4118 viewdata oFirstLine = GetViewData(m_ptSelectionViewPosStart.y);
4119 viewdata oLastLine = GetViewData(m_ptSelectionViewPosEnd.y);
4120 oFirstLine.sLine = oFirstLine.sLine.Left(m_ptSelectionViewPosStart.x) + oLastLine.sLine.Mid(m_ptSelectionViewPosEnd.x);
4121 oFirstLine.ending = oLastLine.ending;
4122 oFirstLine.state = DIFFSTATE_EDITED;
4123 SetViewData(m_ptSelectionViewPosStart.y, oFirstLine);
4125 // clean up middle lines if any
4126 if (m_ptSelectionViewPosStart.y != m_ptSelectionViewPosEnd.y)
4128 viewdata oEmptyLine = GetEmptyLineData();
4129 for (int nViewLine = m_ptSelectionViewPosStart.y+1; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
4131 SetViewData(nViewLine, oEmptyLine);
4133 SaveUndoStep();
4135 if (CleanEmptyLines())
4137 BuildAllScreen2ViewVector(); // schedule full rebuild
4139 SaveUndoStep();
4140 UpdateViewLineNumbers();
4143 SetModified(); //TODO set modified only if real data was changed
4144 SaveUndoStep();
4145 CUndo::GetInstance().EndGrouping();
4147 BuildAllScreen2ViewVector(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
4148 SetCaretViewPosition(m_ptSelectionViewPosStart);
4149 UpdateGoalPos();
4150 ClearSelection();
4151 UpdateCaret();
4152 EnsureCaretVisible();
4153 Invalidate(FALSE);
4156 void CBaseView::PasteText()
4158 CClipboardHelper clipboardHelper;
4159 if (!clipboardHelper.Open(nullptr))
4160 return;
4162 CString sClipboardText;
4163 HGLOBAL hglb = GetClipboardData(CF_TEXT);
4164 if (hglb)
4166 LPCSTR lpstr = static_cast<LPCSTR>(GlobalLock(hglb));
4167 sClipboardText = CString(lpstr);
4168 GlobalUnlock(hglb);
4170 hglb = GetClipboardData(CF_UNICODETEXT);
4171 if (hglb)
4173 LPCWSTR lpstr = static_cast<LPCWSTR>(GlobalLock(hglb));
4174 sClipboardText = lpstr;
4175 GlobalUnlock(hglb);
4178 if (sClipboardText.IsEmpty())
4179 return;
4181 sClipboardText.Replace(L"\r\n", L"\r");
4182 sClipboardText.Replace('\n', '\r');
4184 InsertText(sClipboardText);
4187 void CBaseView::OnCaretDown()
4189 POINT ptCaretPos = GetCaretPosition();
4190 int nLine = ptCaretPos.y;
4191 int nNextLine = nLine + 1;
4192 if (nNextLine >= GetLineCount()) // already at last line
4194 return;
4197 POINT ptCaretViewPos = GetCaretViewPosition();
4198 int nViewLine = ptCaretViewPos.y;
4199 int nNextViewLine = GetViewLineForScreen(nNextLine);
4200 if (!((nNextViewLine == nViewLine) && (GetSubLineOffset(nNextLine)<CountMultiLines(nNextViewLine)))) // not on same view line
4202 // find next suitable screen line
4203 while ((nNextViewLine == nViewLine) || IsViewLineHidden(nNextViewLine))
4205 nNextLine++;
4206 if (nNextLine >= GetLineCount())
4208 return;
4210 nNextViewLine = GetViewLineForScreen(nNextLine);
4213 ptCaretPos.y = nNextLine;
4214 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
4215 SetCaretPosition(ptCaretPos);
4216 OnCaretMove(MOVELEFT);
4217 ShowDiffLines(ptCaretPos.y);
4220 bool CBaseView::MoveCaretLeft()
4222 POINT ptCaretViewPos = GetCaretViewPosition();
4224 //int nViewLine = ptCaretViewPos.y;
4225 if (ptCaretViewPos.x == 0)
4227 int nPrevLine = GetCaretPosition().y;
4228 int nPrevViewLine;
4229 do {
4230 nPrevLine--;
4231 if (nPrevLine < 0)
4233 return false;
4235 nPrevViewLine = GetViewLineForScreen(nPrevLine);
4236 } while ((GetSubLineOffset(nPrevLine) >= CountMultiLines(nPrevViewLine)) || IsViewLineHidden(nPrevViewLine));
4237 ptCaretViewPos = ConvertScreenPosToView(SetupPoint(GetLineLength(nPrevLine), nPrevLine));
4238 ShowDiffLines(nPrevLine);
4240 else
4241 --ptCaretViewPos.x;
4243 SetCaretAndGoalViewPosition(ptCaretViewPos);
4244 return true;
4247 bool CBaseView::MoveCaretRight()
4249 POINT ptCaretViewPos = GetCaretViewPosition();
4251 int nViewLine = ptCaretViewPos.y;
4252 int nViewLineLen = GetViewLineLength(nViewLine);
4253 if (ptCaretViewPos.x >= nViewLineLen)
4255 int nNextLine = GetCaretPosition().y;
4256 int nNextViewLine;
4257 do {
4258 nNextLine++;
4259 if (nNextLine >= GetLineCount())
4261 return false;
4263 nNextViewLine = GetViewLineForScreen(nNextLine);
4264 } while (nNextViewLine == nViewLine || IsViewLineHidden(nNextViewLine));
4265 ptCaretViewPos.y = nNextViewLine;
4266 ptCaretViewPos.x = 0;
4267 ShowDiffLines(nNextLine);
4269 else
4270 ++ptCaretViewPos.x;
4272 SetCaretAndGoalViewPosition(ptCaretViewPos);
4273 return true;
4276 void CBaseView::UpdateGoalPos()
4278 m_nCaretGoalPos = CalculateActualOffset(GetCaretPosition());
4281 void CBaseView::OnCaretLeft()
4283 MoveCaretLeft();
4284 OnCaretMove(MOVELEFT);
4287 void CBaseView::OnCaretRight()
4289 MoveCaretRight();
4290 OnCaretMove(MOVERIGHT);
4293 void CBaseView::OnCaretUp()
4295 POINT ptCaretPos = GetCaretPosition();
4296 int nLine = ptCaretPos.y;
4297 if (nLine <= 0) // already at first line
4299 return;
4301 int nPrevLine = nLine - 1;
4303 POINT ptCaretViewPos = GetCaretViewPosition();
4304 int nViewLine = ptCaretViewPos.y;
4305 int nPrevViewLine = GetViewLineForScreen(nPrevLine);
4306 if (nPrevViewLine != nViewLine) // not on same view line
4308 // find previous suitable screen line
4309 while ((GetSubLineOffset(nPrevLine) >= CountMultiLines(nPrevViewLine)) || IsViewLineHidden(nPrevViewLine))
4311 if (nPrevLine <= 0)
4313 return;
4315 nPrevLine--;
4316 nPrevViewLine = GetViewLineForScreen(nPrevLine);
4319 ptCaretPos.y = nPrevLine;
4320 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
4321 SetCaretPosition(ptCaretPos);
4322 OnCaretMove(MOVELEFT);
4323 ShowDiffLines(ptCaretPos.y);
4326 bool CBaseView::IsWordSeparator(const wchar_t ch) const
4328 switch (GetCharGroup(ch))
4330 case CHG_CONTROL:
4331 case CHG_WHITESPACE:
4332 case CHG_WORDSEPARATOR:
4333 return true;
4335 return false;
4338 bool CBaseView::IsCaretAtWordBoundary()
4340 POINT ptViewCaret = GetCaretViewPosition();
4341 CString line = GetViewLineChars(ptViewCaret.y);
4342 if (line.IsEmpty())
4343 return false; // no boundary at the empty lines
4344 if (ptViewCaret.x == 0)
4345 return !IsWordSeparator(line.GetAt(ptViewCaret.x));
4346 if (ptViewCaret.x >= GetViewLineLength(ptViewCaret.y))
4347 return !IsWordSeparator(line.GetAt(ptViewCaret.x - 1));
4348 return
4349 IsWordSeparator(line.GetAt(ptViewCaret.x)) !=
4350 IsWordSeparator(line.GetAt(ptViewCaret.x - 1));
4353 void CBaseView::UpdateViewsCaretPosition()
4355 POINT ptCaretPos = GetCaretPosition();
4356 if (m_pwndBottom && m_pwndBottom!=this)
4357 m_pwndBottom->UpdateCaretPosition(ptCaretPos);
4358 if (m_pwndLeft && m_pwndLeft!=this)
4359 m_pwndLeft->UpdateCaretPosition(ptCaretPos);
4360 if (m_pwndRight && m_pwndRight!=this)
4361 m_pwndRight->UpdateCaretPosition(ptCaretPos);
4364 void CBaseView::OnCaretWordleft()
4366 MoveCaretWordLeft();
4367 OnCaretMove(MOVELEFT);
4370 void CBaseView::OnCaretWordright()
4372 MoveCaretWordRight();
4373 OnCaretMove(MOVERIGHT);
4376 void CBaseView::MoveCaretWordLeft()
4378 while (MoveCaretLeft() && !IsCaretAtWordBoundary())
4383 void CBaseView::MoveCaretWordRight()
4385 while (MoveCaretRight() && !IsCaretAtWordBoundary())
4390 void CBaseView::ClearCurrentSelection()
4392 m_ptSelectionViewPosStart = GetCaretViewPosition();
4393 m_ptSelectionViewPosEnd = m_ptSelectionViewPosStart;
4394 m_ptSelectionViewPosOrigin = m_ptSelectionViewPosStart;
4395 m_nSelViewBlockStart = -1;
4396 m_nSelViewBlockEnd = -1;
4397 Invalidate(FALSE);
4400 void CBaseView::ClearSelection()
4402 if (m_pwndLeft)
4403 m_pwndLeft->ClearCurrentSelection();
4404 if (m_pwndRight)
4405 m_pwndRight->ClearCurrentSelection();
4406 if (m_pwndBottom)
4407 m_pwndBottom->ClearCurrentSelection();
4410 void CBaseView::AdjustSelection(bool bMoveLeft)
4412 POINT ptCaretViewPos = GetCaretViewPosition();
4413 if (ArePointsSame(m_ptSelectionViewPosOrigin, SetupPoint(-1, -1)))
4415 // select all have been used recently update origin
4416 m_ptSelectionViewPosOrigin = bMoveLeft ? m_ptSelectionViewPosEnd : m_ptSelectionViewPosStart;
4418 if ((ptCaretViewPos.y < m_ptSelectionViewPosOrigin.y) ||
4419 (ptCaretViewPos.y == m_ptSelectionViewPosOrigin.y && ptCaretViewPos.x <= m_ptSelectionViewPosOrigin.x))
4421 m_ptSelectionViewPosStart = ptCaretViewPos;
4422 m_ptSelectionViewPosEnd = m_ptSelectionViewPosOrigin;
4424 else
4426 m_ptSelectionViewPosStart = m_ptSelectionViewPosOrigin;
4427 m_ptSelectionViewPosEnd = ptCaretViewPos;
4430 SetupAllViewSelection(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
4432 Invalidate(FALSE);
4435 void CBaseView::OnEditCut()
4437 if (IsWritable())
4439 OnEditCopy();
4440 RemoveSelectedText();
4444 void CBaseView::OnEditPaste()
4446 if (IsWritable())
4448 CUndo::GetInstance().BeginGrouping();
4449 RemoveSelectedText();
4450 PasteText();
4451 CUndo::GetInstance().EndGrouping();
4455 void CBaseView::DeleteFonts()
4457 for (int i=0; i<fontsCount; i++)
4459 if (m_apFonts[i])
4461 m_apFonts[i]->DeleteObject();
4462 delete m_apFonts[i];
4463 m_apFonts[i] = nullptr;
4468 void CBaseView::OnCaretMove(bool bMoveLeft)
4470 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
4471 OnCaretMove(bMoveLeft, bShift);
4474 void CBaseView::OnCaretMove(bool bMoveLeft, bool isShiftPressed)
4476 if(isShiftPressed)
4477 AdjustSelection(bMoveLeft);
4478 else
4479 ClearSelection();
4480 EnsureCaretVisible();
4481 UpdateCaret();
4484 void CBaseView::AddContextItems(CIconMenu& popup, DiffStates /*state*/)
4486 AddCutCopyAndPaste(popup);
4489 void CBaseView::AddCutCopyAndPaste(CIconMenu& popup)
4491 popup.AppendMenu(MF_SEPARATOR, NULL);
4492 CString temp;
4493 temp.LoadString(IDS_EDIT_COPY);
4494 popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_COPY, temp);
4495 if (IsWritable())
4497 temp.LoadString(IDS_EDIT_CUT);
4498 popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_CUT, temp);
4499 temp.LoadString(IDS_EDIT_PASTE);
4500 popup.AppendMenu(MF_STRING | (CAppUtils::HasClipboardFormat(CF_UNICODETEXT)||CAppUtils::HasClipboardFormat(CF_TEXT) ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_PASTE, temp);
4501 popup.AppendMenu(MF_SEPARATOR, NULL);
4505 void CBaseView::CompensateForKeyboard(CPoint& point)
4507 // if the context menu is invoked through the keyboard, we have to use
4508 // a calculated position on where to anchor the menu on
4509 if (ArePointsSame(point, SetupPoint(-1, -1)))
4511 CRect rect;
4512 GetWindowRect(&rect);
4513 point = rect.CenterPoint();
4517 void CBaseView::ReleaseBitmap()
4519 if (m_pCacheBitmap)
4521 m_pCacheBitmap->DeleteObject();
4522 delete m_pCacheBitmap;
4523 m_pCacheBitmap = nullptr;
4527 void CBaseView::BuildMarkedWordArray()
4529 int lineCount = GetLineCount();
4530 m_arMarkedWordLines.clear();
4531 m_arMarkedWordLines.reserve(lineCount);
4532 bool bDoit = !m_sMarkedWord.IsEmpty();
4533 m_MarkedWordCount = 0;
4534 for (int i = 0; i < lineCount; ++i)
4536 if (bDoit)
4538 CString line = GetLineChars(i);
4540 if (!line.IsEmpty())
4542 int found = 0;
4543 int nMarkStart = -1;
4544 while ((nMarkStart = line.Find(m_sMarkedWord, ++nMarkStart)) >= 0)
4546 int nMarkEnd = nMarkStart + m_sMarkedWord.GetLength();
4547 ECharGroup eLeft = GetCharGroup(line, nMarkStart - 1);
4548 ECharGroup eStart = GetCharGroup(line, nMarkStart);
4549 if (eLeft != eStart)
4551 ECharGroup eRight = GetCharGroup(line, nMarkEnd);
4552 ECharGroup eEnd = GetCharGroup(line, nMarkEnd - 1);
4553 if (eRight != eEnd)
4555 found = 1;
4556 ++m_MarkedWordCount;
4557 break;
4561 m_arMarkedWordLines.push_back(found);
4563 else
4564 m_arMarkedWordLines.push_back(0);
4566 else
4567 m_arMarkedWordLines.push_back(0);
4571 void CBaseView::BuildFindStringArray()
4573 int lineCount = GetLineCount();
4574 m_arFindStringLines.clear();
4575 m_arFindStringLines.reserve(lineCount);
4576 bool bDoit = !m_sFindText.IsEmpty();
4577 int s = 0;
4578 int e = 0;
4579 for (int i = 0; i < lineCount; ++i)
4581 if (bDoit)
4583 CString line = GetLineChars(i);
4585 if (!line.IsEmpty())
4587 switch (m_pViewData->GetState(GetViewLineForScreen(i)))
4589 case DIFFSTATE_EMPTY:
4590 m_arFindStringLines.push_back(0);
4591 break;
4592 case DIFFSTATE_UNKNOWN:
4593 case DIFFSTATE_NORMAL:
4594 case DIFFSTATE_FILTEREDDIFF:
4595 if (m_bLimitToDiff)
4597 m_arFindStringLines.push_back(0);
4598 break;
4600 case DIFFSTATE_REMOVED:
4601 case DIFFSTATE_REMOVEDWHITESPACE:
4602 case DIFFSTATE_ADDED:
4603 case DIFFSTATE_ADDEDWHITESPACE:
4604 case DIFFSTATE_WHITESPACE:
4605 case DIFFSTATE_WHITESPACE_DIFF:
4606 case DIFFSTATE_CONFLICTED:
4607 case DIFFSTATE_CONFLICTED_IGNORED:
4608 case DIFFSTATE_CONFLICTADDED:
4609 case DIFFSTATE_CONFLICTEMPTY:
4610 case DIFFSTATE_CONFLICTRESOLVED:
4611 case DIFFSTATE_IDENTICALREMOVED:
4612 case DIFFSTATE_IDENTICALADDED:
4613 case DIFFSTATE_THEIRSREMOVED:
4614 case DIFFSTATE_THEIRSADDED:
4615 case DIFFSTATE_YOURSREMOVED:
4616 case DIFFSTATE_YOURSADDED:
4617 case DIFFSTATE_EDITED:
4619 if (!m_bMatchCase)
4620 line = line.MakeLower();
4621 s = 0;
4622 e = 0;
4623 int match = 0;
4624 while (StringFound(line, SearchNext, s, e))
4626 match++;
4627 s = e;
4628 e = 0;
4630 m_arFindStringLines.push_back(match);
4631 break;
4633 default:
4634 m_arFindStringLines.push_back(0);
4637 else
4638 m_arFindStringLines.push_back(0);
4640 else
4641 m_arFindStringLines.push_back(0);
4643 UpdateLocator();
4646 bool CBaseView::GetInlineDiffPositions(int nViewLine, std::vector<inlineDiffPos>& positions)
4648 if (!m_bShowInlineDiff)
4649 return false;
4650 if (m_pwndBottom && !(m_pwndBottom->IsHidden()))
4651 return false;
4653 if (!m_pViewData || m_pViewData->GetCount() <= nViewLine)
4654 return false;
4655 const CString &sLine = m_pViewData->GetLine(nViewLine);
4656 if (sLine.IsEmpty())
4657 return false;
4659 CheckOtherView();
4660 if (!m_pOtherViewData)
4661 return false;
4663 const CString &sDiffLine = m_pOtherViewData->GetLine(nViewLine);
4664 if (sDiffLine.IsEmpty())
4665 return false;
4667 svn_diff_t* diff = nullptr;
4668 auto pLine1 = (this == m_pwndLeft) ? &sLine : &sDiffLine;
4669 auto pLine2 = (this == m_pwndLeft) ? &sDiffLine : &sLine;
4670 m_svnlinediff.Diff(&diff, *pLine1, pLine1->GetLength(), *pLine2, pLine2->GetLength(), m_bInlineWordDiff);
4671 if (!diff || !SVNLineDiff::ShowInlineDiff(diff))
4672 return false;
4674 size_t lineoffset = 0;
4675 size_t position = 0;
4676 while (diff)
4678 if (this == m_pwndRight)
4680 apr_off_t nTmp = diff->modified_length;
4681 diff->modified_length = diff->original_length;
4682 diff->original_length = nTmp;
4684 nTmp = diff->modified_start;
4685 diff->modified_start = diff->original_start;
4686 diff->original_start = nTmp;
4688 apr_off_t len = diff->original_length;
4689 size_t oldpos = position;
4691 for (apr_off_t i = 0; i < len; ++i)
4693 position += (this == m_pwndRight) ? m_svnlinediff.m_line2tokens[lineoffset].size() : m_svnlinediff.m_line1tokens[lineoffset].size();
4694 lineoffset++;
4697 if (diff->type == svn_diff__type_diff_modified)
4699 inlineDiffPos p;
4700 p.start = oldpos;
4701 p.end = position;
4702 positions.push_back(p);
4705 diff = diff->next;
4708 return !positions.empty();
4711 void CBaseView::OnNavigateNextinlinediff()
4713 int nX;
4714 if (GetNextInlineDiff(nX))
4716 POINT ptCaretViewPos = GetCaretViewPosition();
4717 ptCaretViewPos.x = nX;
4718 SetCaretAndGoalViewPosition(ptCaretViewPos);
4719 m_ptSelectionViewPosOrigin = ptCaretViewPos;
4720 EnsureCaretVisible();
4724 void CBaseView::OnNavigatePrevinlinediff()
4726 int nX;
4727 if (GetPrevInlineDiff(nX))
4729 POINT ptCaretViewPos = GetCaretViewPosition();
4730 ptCaretViewPos.x = nX;
4731 SetCaretAndGoalViewPosition(ptCaretViewPos);
4732 m_ptSelectionViewPosOrigin = ptCaretViewPos;
4733 EnsureCaretVisible();
4737 bool CBaseView::HasNextInlineDiff()
4739 int nPos;
4740 return GetNextInlineDiff(nPos);
4743 bool CBaseView::GetNextInlineDiff(int & nPos)
4745 POINT ptCaretViewPos = GetCaretViewPosition();
4746 std::vector<inlineDiffPos> positions;
4747 if (GetInlineDiffPositions(ptCaretViewPos.y, positions))
4749 for (auto it = positions.cbegin(); it != positions.cend(); ++it)
4751 if (it->start > ptCaretViewPos.x)
4753 nPos = static_cast<LONG>(it->start);
4754 return true;
4756 if (it->end > ptCaretViewPos.x)
4758 nPos = static_cast<LONG>(it->end);
4759 return true;
4763 return false;
4766 bool CBaseView::HasPrevInlineDiff()
4768 int nPos;
4769 return GetPrevInlineDiff(nPos);
4772 bool CBaseView::GetPrevInlineDiff(int & nPos)
4774 POINT ptCaretViewPos = GetCaretViewPosition();
4775 std::vector<inlineDiffPos> positions;
4776 if (GetInlineDiffPositions(ptCaretViewPos.y, positions))
4778 for (auto it = positions.crbegin(); it != positions.crend(); ++it)
4780 if ( it->end < ptCaretViewPos.x)
4782 nPos = static_cast<LONG>(it->end);
4783 return true;
4785 if ( it->start < ptCaretViewPos.x)
4787 nPos = static_cast<LONG>(it->start);
4788 return true;
4792 return false;
4795 CBaseView * CBaseView::GetFirstGoodView()
4797 if (IsViewGood(m_pwndLeft))
4798 return m_pwndLeft;
4799 if (IsViewGood(m_pwndRight))
4800 return m_pwndRight;
4801 if (IsViewGood(m_pwndBottom))
4802 return m_pwndBottom;
4803 return nullptr;
4806 void CBaseView::BuildAllScreen2ViewVector()
4808 CBaseView * p_pwndView = GetFirstGoodView();
4809 if (p_pwndView)
4811 m_Screen2View.ScheduleFullRebuild(p_pwndView->m_pViewData);
4815 void CBaseView::BuildAllScreen2ViewVector(int nViewLine)
4817 BuildAllScreen2ViewVector(nViewLine, nViewLine);
4820 void CBaseView::BuildAllScreen2ViewVector(int nFirstViewLine, int nLastViewLine)
4822 CBaseView * p_pwndView = GetFirstGoodView();
4823 if (p_pwndView)
4825 m_Screen2View.ScheduleRangeRebuild(p_pwndView->m_pViewData, nFirstViewLine, nLastViewLine);
4829 void CBaseView::UpdateViewLineNumbers()
4831 int nLineNumber = 0;
4832 int nViewLineCount = GetViewCount();
4833 for (int nViewLine = 0; nViewLine < nViewLineCount; nViewLine++)
4835 int oldLine = GetViewLineNumber(nViewLine);
4836 if (oldLine >= 0)
4837 SetViewLineNumber(nViewLine, nLineNumber++);
4839 m_nDigits = 0;
4842 int CBaseView::CleanEmptyLines()
4844 int nRemovedCount = 0;
4845 int nViewLineCount = GetViewCount();
4846 bool bCheckLeft = IsViewGood(m_pwndLeft);
4847 bool bCheckRight = IsViewGood(m_pwndRight);
4848 bool bCheckBottom = IsViewGood(m_pwndBottom);
4849 for (int nViewLine = 0; nViewLine < nViewLineCount; )
4851 bool bAllEmpty = true;
4852 bAllEmpty &= !bCheckLeft || IsStateEmpty(m_pwndLeft->GetViewState(nViewLine));
4853 bAllEmpty &= !bCheckRight || IsStateEmpty(m_pwndRight->GetViewState(nViewLine));
4854 bAllEmpty &= !bCheckBottom || IsStateEmpty(m_pwndBottom->GetViewState(nViewLine));
4855 if (bAllEmpty)
4857 if (bCheckLeft)
4859 m_pwndLeft->RemoveViewData(nViewLine);
4861 if (bCheckRight)
4863 m_pwndRight->RemoveViewData(nViewLine);
4865 if (bCheckBottom)
4867 m_pwndBottom->RemoveViewData(nViewLine);
4869 if (CUndo::GetInstance().IsGrouping()) // if use group undo -> ensure back adding goes in right (reversed) order
4871 SaveUndoStep();
4873 nViewLineCount--;
4874 nRemovedCount++;
4875 continue;
4877 nViewLine++;
4879 return nRemovedCount;
4882 int CBaseView::FindScreenLineForViewLine( int viewLine )
4884 return m_Screen2View.FindScreenLineForViewLine(viewLine);
4887 int CBaseView::CountMultiLines( int nViewLine )
4889 if (m_ScreenedViewLine.empty())
4890 return 0; // in case the view is completely empty
4892 ASSERT(nViewLine < static_cast<int>(m_ScreenedViewLine.size()));
4894 if (m_ScreenedViewLine[nViewLine].bSublinesSet)
4896 return static_cast<int>(m_ScreenedViewLine[nViewLine].SubLines.size());
4899 auto multiLines = CStringUtils::WordWrap(m_pViewData->GetLine(nViewLine), GetScreenChars() - 1, GetTabSize());
4901 TScreenedViewLine oScreenedLine;
4902 bool subLinesSet = true;
4903 if (m_pMainFrame->m_bWrapLines)
4905 CDC* pDC = GetDC();
4906 CFont* pOldFont = pDC->SelectObject(GetFont());
4908 for (const auto& line : multiLines)
4910 if (line.GetLength())
4912 // we use the 'X' char to determine the char width,
4913 // but e.g. chinese chars are much wider. To make sure
4914 // that we wrap correctly, we calculate the average char width
4915 // here by using the real line text
4916 const CSize szCharExt = pDC->GetTextExtent(line);
4917 if (szCharExt.cx / line.GetLength() > m_nCharWidth)
4919 m_nCharWidth = szCharExt.cx / line.GetLength();
4920 subLinesSet = false;
4923 oScreenedLine.SubLines.push_back(line);
4925 pDC->SelectObject(pOldFont);
4926 ReleaseDC(pDC);
4928 else
4930 for (const auto& line : multiLines)
4932 oScreenedLine.SubLines.push_back(line);
4936 oScreenedLine.bSublinesSet = subLinesSet;
4937 m_ScreenedViewLine[nViewLine] = oScreenedLine;
4939 return CountMultiLines(nViewLine);
4942 /// prepare inline diff cache
4943 LineColors & CBaseView::GetLineColors(int nViewLine)
4945 ASSERT(nViewLine < static_cast<int>(m_ScreenedViewLine.size()));
4947 if (m_bWhitespaceInlineDiffs)
4949 if (m_ScreenedViewLine[nViewLine].bLineColorsSetWhiteSpace)
4950 return m_ScreenedViewLine[nViewLine].lineColorsWhiteSpace;
4952 else
4954 if (m_ScreenedViewLine[nViewLine].bLineColorsSet)
4955 return m_ScreenedViewLine[nViewLine].lineColors;
4958 LineColors oLineColors;
4959 // set main line color
4960 COLORREF crBkgnd, crText;
4961 DiffStates diffState = m_pViewData->GetState(nViewLine);
4962 CDiffColors::GetInstance().GetColors(diffState, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBkgnd, crText);
4963 oLineColors.SetColor(0, crText, crBkgnd);
4965 do {
4966 if (!m_bShowInlineDiff)
4967 break;
4969 if (((diffState == DIFFSTATE_NORMAL) || (diffState == DIFFSTATE_FILTEREDDIFF)) && (!m_bWhitespaceInlineDiffs))
4970 break;
4972 CString sLine = GetViewLineChars(nViewLine);
4973 if (sLine.IsEmpty())
4974 break;
4975 CString sDiffLine;
4976 if (!m_pOtherView)
4978 switch (diffState)
4980 case DIFFSTATE_ADDED:
4982 if ((nViewLine > 0) && (m_pViewData->GetState(nViewLine - 1) == DIFFSTATE_REMOVED))
4983 sDiffLine = GetViewLineChars(nViewLine - 1);
4985 break;
4986 case DIFFSTATE_REMOVED:
4988 if (((nViewLine + 1) < m_pViewData->GetCount()) && (m_pViewData->GetState(nViewLine + 1) == DIFFSTATE_ADDED))
4989 sDiffLine = GetViewLineChars(nViewLine + 1);
4991 break;
4994 else
4995 sDiffLine = m_pOtherView->GetViewLineChars(nViewLine);
4996 if (sDiffLine.IsEmpty())
4997 break;
4999 svn_diff_t* diff = nullptr;
5000 if (sLine.GetLength() > static_cast<int>(m_nInlineDiffMaxLineLength))
5001 break;
5002 auto pLine1 = (this == m_pwndLeft) ? &sLine : &sDiffLine;
5003 auto pLine2 = (this == m_pwndLeft) ? &sDiffLine : &sLine;
5004 m_svnlinediff.Diff(&diff, *pLine1, pLine1->GetLength(), *pLine2, pLine2->GetLength(), m_bInlineWordDiff);
5005 if (!diff || !SVNLineDiff::ShowInlineDiff(diff) || !diff->next)
5006 break;
5008 int lineoffset = 0;
5009 int nTextStartOffset = 0;
5010 std::map<int, COLORREF> removedPositions;
5011 while (diff)
5013 if (this == m_pwndRight)
5015 apr_off_t nTmp = diff->modified_length;
5016 diff->modified_length = diff->original_length;
5017 diff->original_length = nTmp;
5019 nTmp = diff->modified_start;
5020 diff->modified_start = diff->original_start;
5021 diff->original_start = nTmp;
5023 apr_off_t len = diff->original_length;
5025 size_t nTextLength = 0;
5026 for (int i = 0; i < len; ++i)
5028 nTextLength += (this == m_pwndRight) ? m_svnlinediff.m_line2tokens[lineoffset].size() : m_svnlinediff.m_line1tokens[lineoffset].size();
5029 lineoffset++;
5031 bool bInlineDiff = (diff->type == svn_diff__type_diff_modified);
5033 CDiffColors::GetInstance().GetColors(diffState, CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark(), crBkgnd, crText);
5034 if ((m_bShowInlineDiff)&&(bInlineDiff))
5036 crBkgnd = InlineViewLineDiffColor(nViewLine);
5038 else if (m_pOtherView)
5040 crBkgnd = m_bDark ? m_ModifiedDarkBk : m_ModifiedBk;
5043 if (len < diff->modified_length)
5045 removedPositions[nTextStartOffset] = m_bDark ? m_InlineRemovedDarkBk : m_InlineRemovedBk;
5047 oLineColors.SetColor(nTextStartOffset, crText, crBkgnd);
5049 nTextStartOffset += static_cast<int>(nTextLength);
5050 diff = diff->next;
5052 for (std::map<int, COLORREF>::const_iterator it = removedPositions.begin(); it != removedPositions.end(); ++it)
5054 oLineColors.AddShotColor(it->first, it->second);
5056 } while (false); // error catch
5058 if (!m_bWhitespaceInlineDiffs)
5060 m_ScreenedViewLine[nViewLine].lineColors = oLineColors;
5061 m_ScreenedViewLine[nViewLine].bLineColorsSet = true;
5063 else
5065 m_ScreenedViewLine[nViewLine].lineColorsWhiteSpace = oLineColors;
5066 m_ScreenedViewLine[nViewLine].bLineColorsSetWhiteSpace = true;
5069 return GetLineColors(nViewLine);
5072 void CBaseView::OnEditSelectall()
5074 if (!m_pViewData)
5075 return;
5076 int nLastViewLine = m_pViewData->GetCount()-1;
5077 if (nLastViewLine < 0)
5078 return;
5079 SetupAllViewSelection(0, nLastViewLine);
5081 CString sLine = GetViewLineChars(nLastViewLine);
5082 m_ptSelectionViewPosStart = SetupPoint(0, 0);
5083 m_ptSelectionViewPosEnd = SetupPoint(sLine.GetLength(), nLastViewLine);
5084 m_ptSelectionViewPosOrigin = SetupPoint(-1, -1);
5086 UpdateWindow();
5089 void CBaseView::FilterWhitespaces(CString& first, CString& second)
5091 FilterWhitespaces(first);
5092 FilterWhitespaces(second);
5095 void CBaseView::FilterWhitespaces(CString& line)
5097 line.Remove(' ');
5098 line.Remove('\t');
5099 line.Remove('\r');
5100 line.Remove('\n');
5103 int CBaseView::GetButtonEventLineIndex(const POINT& point)
5105 if (point.y < (GetLineHeight() + HEADERHEIGHT))
5106 return -1;
5107 const int nLineFromTop = (point.y - HEADERHEIGHT) / GetLineHeight();
5108 int nEventLine = nLineFromTop + m_nTopLine;
5109 nEventLine--; //we need the index
5110 return nEventLine;
5114 BOOL CBaseView::PreTranslateMessage(MSG* pMsg)
5116 if (RelayTrippleClick(pMsg))
5117 return TRUE;
5118 return CView::PreTranslateMessage(pMsg);
5122 void CBaseView::ResetUndoStep()
5124 m_AllState.Clear();
5127 void CBaseView::SaveUndoStep()
5129 if (!m_AllState.IsEmpty())
5131 CUndo::GetInstance().AddState(m_AllState, GetCaretViewPosition());
5133 ResetUndoStep();
5136 void CBaseView::SetTheme(bool bDark)
5138 m_bDark = bDark || CTheme::Instance().IsHighContrastModeDark();
5139 DarkModeHelper::Instance().AllowDarkModeForWindow(GetSafeHwnd(), m_bDark);
5140 if (m_bDark)
5141 ModifyStyleEx(WS_EX_CLIENTEDGE, 0);
5142 else
5143 ModifyStyleEx(0, WS_EX_CLIENTEDGE);
5144 CDiffColors::GetInstance().LoadRegistry();
5145 BuildAllScreen2ViewVector();
5146 if (IsWindow(GetSafeHwnd()))
5148 if (m_bDark)
5150 if (FAILED(SetWindowTheme(GetSafeHwnd(), L"DarkMode_Explorer", nullptr)))
5151 SetWindowTheme(GetSafeHwnd(), L"Explorer", nullptr);
5153 else
5154 SetWindowTheme(GetSafeHwnd(), L"Explorer", nullptr);
5155 Invalidate();
5157 m_WhiteSpaceFg = CRegDWORD(L"Software\\TortoiseMerge\\Colors\\Whitespace", CTheme::Instance().GetThemeColor(GetSysColor(COLOR_3DSHADOW)));
5160 void CBaseView::InsertViewData( int index, const CString& sLine, DiffStates state, int linenumber, EOL ending, HIDESTATE hide, int movedline )
5162 m_pState->addedlines.push_back(index);
5163 m_pViewData->InsertData(index, sLine, state, linenumber, ending, hide, movedline);
5166 void CBaseView::InsertViewData( int index, const viewdata& data )
5168 m_pState->addedlines.push_back(index);
5169 m_pViewData->InsertData(index, data);
5172 void CBaseView::RemoveViewData( int index )
5174 m_pState->removedlines[index] = m_pViewData->GetData(index);
5175 m_pViewData->RemoveData(index);
5178 void CBaseView::SetViewData( int index, const viewdata& data )
5180 m_pState->replacedlines[index] = m_pViewData->GetData(index);
5181 m_pViewData->SetData(index, data);
5184 void CBaseView::SetViewState( int index, DiffStates state )
5186 m_pState->linestates[index] = m_pViewData->GetState(index);
5187 m_pViewData->SetState(index, state);
5190 void CBaseView::SetViewLine( int index, const CString& sLine )
5192 m_pState->difflines[index] = m_pViewData->GetLine(index);
5193 m_pViewData->SetLine(index, sLine);
5196 void CBaseView::SetViewLineNumber( int index, int linenumber )
5198 int oldLineNumber = m_pViewData->GetLineNumber(index);
5199 if (oldLineNumber != linenumber) {
5200 m_pState->linelines[index] = oldLineNumber;
5201 m_pViewData->SetLineNumber(index, linenumber);
5205 void CBaseView::SetViewLineEnding( int index, EOL ending )
5207 m_pState->linesEOL[index] = m_pViewData->GetLineEnding(index);
5208 m_pViewData->SetLineEnding(index, ending);
5211 void CBaseView::SetViewMarked( int index, bool marked )
5213 m_pState->markedlines[index] = m_pViewData->GetMarked(index);
5214 m_pViewData->SetMarked(index, marked);
5218 BOOL CBaseView::GetViewSelection( int& start, int& end ) const
5220 if (HasSelection())
5222 start = m_nSelViewBlockStart;
5223 end = m_nSelViewBlockEnd;
5224 return true;
5226 return false;
5229 int CBaseView::Screen2View::GetViewLineForScreen( int screenLine )
5231 RebuildIfNecessary();
5232 if ((size() <= screenLine) || (screenLine < 0))
5233 return 0;
5234 return m_Screen2View[screenLine].nViewLine;
5237 int CBaseView::Screen2View::size()
5239 RebuildIfNecessary();
5240 return static_cast<int>(m_Screen2View.size());
5243 int CBaseView::Screen2View::GetSubLineOffset( int screenLine )
5245 RebuildIfNecessary();
5246 if (size() <= screenLine)
5247 return 0;
5248 return m_Screen2View[screenLine].nViewSubLine;
5252 doing partial rebuild, whole screen2view vector is built, but uses ScreenedViewLine cache to do it faster
5254 void CBaseView::Screen2View::RebuildIfNecessary()
5256 if (!m_pViewData)
5257 return; // rebuild not necessary
5259 FixScreenedCacheSize(m_pwndLeft);
5260 FixScreenedCacheSize(m_pwndRight);
5261 FixScreenedCacheSize(m_pwndBottom);
5262 if (!m_bFull)
5264 for (auto it = m_RebuildRanges.cbegin(); it != m_RebuildRanges.cend(); ++it)
5266 ResetScreenedViewLineCache(m_pwndLeft, *it);
5267 ResetScreenedViewLineCache(m_pwndRight, *it);
5268 ResetScreenedViewLineCache(m_pwndBottom, *it);
5271 else
5273 ResetScreenedViewLineCache(m_pwndLeft);
5274 ResetScreenedViewLineCache(m_pwndRight);
5275 ResetScreenedViewLineCache(m_pwndBottom);
5277 m_RebuildRanges.clear();
5278 m_bFull = false;
5280 size_t OldSize = m_Screen2View.size();
5281 m_Screen2View.clear();
5282 m_Screen2View.reserve(OldSize); // guess same size
5283 for (int i = 0; i < m_pViewData->GetCount(); ++i)
5285 if (m_pMainFrame->m_bCollapsed)
5287 while ((i < m_pViewData->GetCount())&&(m_pViewData->GetHideState(i) == HIDESTATE_HIDDEN))
5288 ++i;
5289 if (!(i < m_pViewData->GetCount()))
5290 break;
5292 TScreenLineInfo oLineInfo;
5293 oLineInfo.nViewLine = i;
5294 oLineInfo.nViewSubLine = -1; // no wrap
5295 if (m_pMainFrame->m_bWrapLines && !IsViewLineHidden(m_pViewData, i))
5297 int nMaxLines = 0;
5298 if (IsLeftViewGood())
5299 nMaxLines = std::max<int>(nMaxLines, m_pwndLeft->CountMultiLines(i));
5300 if (IsRightViewGood())
5301 nMaxLines = std::max<int>(nMaxLines, m_pwndRight->CountMultiLines(i));
5302 if (IsBottomViewGood())
5303 nMaxLines = std::max<int>(nMaxLines, m_pwndBottom->CountMultiLines(i));
5304 for (int l = 0; l < (nMaxLines-1); ++l)
5306 oLineInfo.nViewSubLine++;
5307 m_Screen2View.push_back(oLineInfo);
5309 oLineInfo.nViewSubLine++;
5311 m_Screen2View.push_back(oLineInfo);
5313 m_pViewData = nullptr;
5315 if (IsLeftViewGood())
5316 m_pwndLeft->BuildMarkedWordArray();
5317 if (IsRightViewGood())
5318 m_pwndRight->BuildMarkedWordArray();
5319 if (IsBottomViewGood())
5320 m_pwndBottom->BuildMarkedWordArray();
5321 UpdateLocator();
5322 RecalcAllVertScrollBars();
5323 RecalcAllHorzScrollBars();
5326 int CBaseView::Screen2View::FindScreenLineForViewLine( int viewLine )
5328 RebuildIfNecessary();
5330 int nScreenLineCount = static_cast<int>(m_Screen2View.size());
5332 int nPos = 0;
5333 if (nScreenLineCount>16)
5335 // for enough long data search for last screen
5336 // with viewline less than one we are looking for
5337 // use approximate method (based on) binary search using asymmetric start point
5338 // in form 2**n (determined as MSB of length) to go around division and rounding;
5339 // this effectively looks for bit values from MSB to LSB
5341 int nTestBit;
5342 //GetMostSignificantBitValue
5343 // note _BitScanReverse(&nTestBit, nScreenLineCount); can be used instead
5344 nTestBit = nScreenLineCount;
5345 nTestBit |= nTestBit>>1;
5346 nTestBit |= nTestBit>>2;
5347 nTestBit |= nTestBit>>4;
5348 nTestBit |= nTestBit>>8;
5349 nTestBit |= nTestBit>>16;
5350 nTestBit ^= (nTestBit>>1);
5352 while (nTestBit)
5354 int nTestPos = nPos | nTestBit;
5355 if (nTestPos < nScreenLineCount && m_Screen2View[nTestPos].nViewLine < viewLine)
5357 nPos = nTestPos;
5359 nTestBit >>= 1;
5362 while (nPos < nScreenLineCount && m_Screen2View[nPos].nViewLine < viewLine)
5364 nPos++;
5367 return nPos;
5370 void CBaseView::Screen2View::ScheduleFullRebuild(CViewData * pViewData) {
5371 m_bFull = true;
5373 m_pViewData = pViewData;
5376 void CBaseView::Screen2View::ScheduleRangeRebuild(CViewData * pViewData, int nFirstViewLine, int nLastViewLine)
5378 if (m_bFull)
5379 return;
5381 m_pViewData = pViewData;
5383 TRebuildRange Range;
5384 Range.FirstViewLine=nFirstViewLine;
5385 Range.LastViewLine=nLastViewLine;
5386 m_RebuildRanges.push_back(Range);
5389 bool CBaseView::Screen2View::FixScreenedCacheSize(CBaseView* pwndView)
5391 if (!IsViewGood(pwndView))
5393 return false;
5395 const int nOldSize = static_cast<int>(pwndView->m_ScreenedViewLine.size());
5396 const int nViewCount = std::max<int>(pwndView->GetViewCount(), 0);
5397 if (nOldSize == nViewCount)
5399 return false;
5401 pwndView->m_ScreenedViewLine.resize(nViewCount);
5402 return true;
5405 bool CBaseView::Screen2View::ResetScreenedViewLineCache(CBaseView* pwndView) const
5407 if (!IsViewGood(pwndView))
5409 return false;
5411 TRebuildRange Range={0, pwndView->GetViewCount()-1};
5412 ResetScreenedViewLineCache(pwndView, Range);
5413 return true;
5416 bool CBaseView::Screen2View::ResetScreenedViewLineCache(CBaseView* pwndView, const TRebuildRange& Range) const
5418 if (!IsViewGood(pwndView))
5420 return false;
5422 if (Range.LastViewLine == -1)
5424 return false;
5426 ASSERT(Range.FirstViewLine >= 0);
5427 ASSERT(Range.LastViewLine < pwndView->GetViewCount());
5428 for (int i = Range.FirstViewLine; i <= Range.LastViewLine; i++)
5430 pwndView->m_ScreenedViewLine[i].Clear();
5432 return false;
5435 void CBaseView::WrapChanged()
5437 m_nMaxLineLength = -1;
5438 m_nOffsetChar = 0;
5439 RecalcHorzScrollBar();
5442 void CBaseView::OnEditFind()
5444 if (m_pFindDialog)
5446 m_pFindDialog->SetFocus();
5447 return;
5450 int id = 0;
5451 if (this == m_pwndLeft)
5452 id = 1;
5453 if (this == m_pwndRight)
5454 id = 2;
5455 if (this == m_pwndBottom)
5456 id = 3;
5458 m_pFindDialog = new CFindDlg(this);
5459 m_pFindDialog->Create(this, id);
5461 m_pFindDialog->SetFindString(HasTextSelection() ? GetSelectedText() : CString());
5462 m_pFindDialog->SetReadonly(m_bReadonly);
5465 LRESULT CBaseView::OnFindDialogMessage(WPARAM wParam, LPARAM /*lParam*/)
5467 ASSERT(m_pFindDialog != nullptr);
5469 if (m_pFindDialog->IsTerminating())
5471 // invalidate the handle identifying the dialog box.
5472 m_pFindDialog = nullptr;
5473 return 0;
5476 if(m_pFindDialog->FindNext())
5478 //read data from dialog
5479 m_sFindText = m_pFindDialog->GetFindString();
5480 m_bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
5481 m_bLimitToDiff = m_pFindDialog->LimitToDiffs();
5482 m_bWholeWord = m_pFindDialog->WholeWord();
5484 if (!m_bMatchCase)
5485 m_sFindText = m_sFindText.MakeLower();
5487 BuildFindStringArray();
5488 if (static_cast<CFindDlg::FindType>(wParam) == CFindDlg::FindType::Find)
5490 if (m_pFindDialog->SearchUp())
5491 OnEditFindprev();
5492 else
5493 OnEditFindnext();
5495 else if (static_cast<CFindDlg::FindType>(wParam) == CFindDlg::FindType::Count)
5497 size_t count = 0;
5498 for (size_t i = 0; i < m_arFindStringLines.size(); ++i)
5499 count += m_arFindStringLines[i];
5500 CString matches;
5501 matches.Format(IDS_FIND_COUNT, count);
5502 m_pFindDialog->SetStatusText(matches);
5504 else if (static_cast<CFindDlg::FindType>(wParam) == CFindDlg::FindType::Replace)
5506 if (!IsWritable())
5507 return 0;
5508 bool bFound = false;
5509 if (m_pFindDialog->SearchUp())
5510 bFound = Search(SearchPrevious, true, true, false);
5511 else
5512 bFound = Search(SearchNext, true, true, false);
5513 if (bFound)
5515 CString sReplaceText = m_pFindDialog->GetReplaceString();
5516 CUndo::GetInstance().BeginGrouping();
5517 RemoveSelectedText();
5518 InsertText(sReplaceText);
5519 CUndo::GetInstance().EndGrouping();
5523 else if (static_cast<CFindDlg::FindType>(wParam) == CFindDlg::FindType::ReplaceAll)
5525 if (!IsWritable())
5526 return 0;
5527 bool bFound = false;
5528 int replaceCount = 0;
5529 POINT lastPoint = m_ptSelectionViewPosStart;
5530 m_ptSelectionViewPosStart.x = m_ptSelectionViewPosStart.y = 0;
5531 CUndo::GetInstance().BeginGrouping();
5534 bFound = Search(SearchNext, true, false, true);
5535 if (bFound)
5537 CString sReplaceText = m_pFindDialog->GetReplaceString();
5538 RemoveSelectedText();
5539 InsertText(sReplaceText);
5540 ++replaceCount;
5542 } while (bFound);
5543 CUndo::GetInstance().EndGrouping();
5544 if (replaceCount == 0)
5545 m_ptSelectionViewPosStart = lastPoint;
5546 CString message;
5547 message.Format(IDS_FIND_REPLACED, replaceCount);
5548 m_pFindDialog->SetStatusText(message, RGB(0, 0, 0));
5552 return 0;
5555 void CBaseView::OnEditFindnextStart()
5557 if (!m_pViewData)
5558 return;
5559 if (HasTextSelection())
5561 m_sFindText = GetSelectedText();
5562 m_bMatchCase = false;
5563 m_bLimitToDiff = false;
5564 m_bWholeWord = false;
5565 m_sFindText = m_sFindText.MakeLower();
5567 BuildFindStringArray();
5568 OnEditFindnext();
5570 else
5572 m_sFindText.Empty();
5573 BuildFindStringArray();
5577 void CBaseView::OnEditFindprevStart()
5579 if (!m_pViewData)
5580 return;
5581 if (HasTextSelection())
5583 m_sFindText = GetSelectedText();
5584 m_bMatchCase = false;
5585 m_bLimitToDiff = false;
5586 m_bWholeWord = false;
5587 m_sFindText = m_sFindText.MakeLower();
5589 BuildFindStringArray();
5590 OnEditFindprev();
5592 else
5594 m_sFindText.Empty();
5595 BuildFindStringArray();
5599 bool CBaseView::StringFound(const CString& str, SearchDirection srchDir, int& start, int& end) const
5601 bool bStringFound;
5604 if (srchDir == SearchPrevious)
5606 int laststart = -1;
5607 int laststart2 = -1;
5610 laststart2 = laststart;
5611 laststart = str.Find(m_sFindText, laststart + 1);
5612 } while (laststart >= 0 && laststart < start);
5613 start = laststart2;
5615 else
5616 start = str.Find(m_sFindText, start);
5617 end = start + m_sFindText.GetLength();
5618 bStringFound = (start >= 0);
5619 if (bStringFound && m_bWholeWord)
5621 if (start)
5622 bStringFound = IsWordSeparator(str.Mid(start - 1, 1).GetAt(0));
5624 if (bStringFound)
5626 if (str.GetLength() > end)
5627 bStringFound = IsWordSeparator(str.Mid(end, 1).GetAt(0));
5630 if (!bStringFound)
5632 if (srchDir == SearchPrevious)
5633 start--;
5634 else
5635 start = end;
5638 } while (!bStringFound && start >= 0);
5640 return bStringFound;
5643 void CBaseView::OnEditFindprev()
5645 Search(SearchPrevious, false, true, false);
5648 void CBaseView::OnEditFindnext()
5650 Search(SearchNext, false, true, false);
5653 bool CBaseView::Search(SearchDirection srchDir, bool useStart, bool flashIfNotFound, bool stopEof)
5655 if (m_sFindText.IsEmpty())
5656 return false;
5657 if(!m_pViewData)
5658 return false;
5660 POINT start = useStart ? m_ptSelectionViewPosStart : m_ptSelectionViewPosEnd;
5661 POINT end;
5662 end.y = m_pViewData->GetCount()-1;
5663 if (end.y < 0)
5664 return false;
5666 if (srchDir==SearchNext)
5667 end.x = GetViewLineLength(end.y);
5668 else
5670 end.x = m_ptSelectionViewPosStart.x;
5671 start.x = 0;
5674 if (!HasTextSelection())
5676 start.y = m_ptCaretViewPos.y;
5677 if (srchDir==SearchNext)
5678 start.x = m_ptCaretViewPos.x;
5679 else
5681 start.x = 0;
5682 end.x = m_ptCaretViewPos.x;
5685 CString sSelectedText;
5686 int startline = -1;
5687 for (int nViewLine=start.y; ;srchDir==SearchNext ? nViewLine++ : nViewLine--)
5689 if (nViewLine < 0)
5691 if (stopEof)
5692 return false;
5693 nViewLine = m_pViewData->GetCount()-1;
5694 startline = start.y;
5695 if (flashIfNotFound)
5697 if (m_pFindDialog)
5698 m_pFindDialog->SetStatusText(CString(MAKEINTRESOURCE(IDS_FIND_TOPREACHED)), RGB(63, 127, 47));
5699 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 2, 100);
5702 if (nViewLine > end.y)
5704 if (stopEof)
5705 return false;
5706 nViewLine = 0;
5707 startline = start.y;
5708 if (flashIfNotFound)
5710 if (m_pFindDialog)
5711 m_pFindDialog->SetStatusText(CString(MAKEINTRESOURCE(IDS_FIND_BOTTOMREACHED)), RGB(63, 127, 47));
5712 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 2, 100);
5715 switch (m_pViewData->GetState(nViewLine))
5717 case DIFFSTATE_EMPTY:
5718 break;
5719 case DIFFSTATE_UNKNOWN:
5720 case DIFFSTATE_NORMAL:
5721 case DIFFSTATE_FILTEREDDIFF:
5722 if (m_bLimitToDiff)
5723 break;
5724 case DIFFSTATE_REMOVED:
5725 case DIFFSTATE_REMOVEDWHITESPACE:
5726 case DIFFSTATE_ADDED:
5727 case DIFFSTATE_ADDEDWHITESPACE:
5728 case DIFFSTATE_WHITESPACE:
5729 case DIFFSTATE_WHITESPACE_DIFF:
5730 case DIFFSTATE_CONFLICTED:
5731 case DIFFSTATE_CONFLICTED_IGNORED:
5732 case DIFFSTATE_CONFLICTADDED:
5733 case DIFFSTATE_CONFLICTEMPTY:
5734 case DIFFSTATE_CONFLICTRESOLVED:
5735 case DIFFSTATE_IDENTICALREMOVED:
5736 case DIFFSTATE_IDENTICALADDED:
5737 case DIFFSTATE_THEIRSREMOVED:
5738 case DIFFSTATE_THEIRSADDED:
5739 case DIFFSTATE_YOURSREMOVED:
5740 case DIFFSTATE_YOURSADDED:
5741 case DIFFSTATE_EDITED:
5743 sSelectedText = GetViewLineChars(nViewLine);
5744 if (nViewLine == start.y && startline < 0)
5745 sSelectedText = srchDir == SearchNext ? sSelectedText.Mid(start.x) : sSelectedText.Left(end.x);
5746 if (!m_bMatchCase)
5747 sSelectedText = sSelectedText.MakeLower();
5748 int startfound = srchDir == SearchNext ? 0 : sSelectedText.GetLength();
5749 int endfound = 0;
5750 if (StringFound(sSelectedText, srchDir, startfound, endfound))
5752 HighlightViewLines(nViewLine, nViewLine);
5753 m_ptSelectionViewPosStart.x = startfound;
5754 m_ptSelectionViewPosEnd.x = endfound;
5755 if (nViewLine == start.y && startline < 0)
5757 m_ptSelectionViewPosStart.x += start.x;
5758 m_ptSelectionViewPosEnd.x += start.x;
5760 m_ptSelectionViewPosEnd.x = m_ptSelectionViewPosStart.x + m_sFindText.GetLength();
5761 m_ptSelectionViewPosStart.y = nViewLine;
5762 m_ptSelectionViewPosEnd.y = nViewLine;
5763 m_ptCaretViewPos = m_ptSelectionViewPosStart;
5764 UpdateViewsCaretPosition();
5765 EnsureCaretVisible();
5766 Invalidate();
5767 return true;
5770 break;
5773 if (startline >= 0)
5775 if (nViewLine == startline)
5777 if (flashIfNotFound)
5779 CString message;
5780 message.Format(IDS_FIND_NOTFOUND, static_cast<LPCWSTR>(m_sFindText));
5781 if (m_pFindDialog)
5782 m_pFindDialog->SetStatusText(message, RGB(255, 0, 0));
5783 ::MessageBeep(0xFFFFFFFF);
5784 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 3, 100);
5786 break;
5790 m_pMainFrame->m_nMoveMovesToIgnore = MOVESTOIGNORE;
5791 return false;
5794 CString CBaseView::GetSelectedText() const
5796 CString sSelectedText;
5797 POINT start = m_ptSelectionViewPosStart;
5798 POINT end = m_ptSelectionViewPosEnd;
5799 if (!HasTextSelection())
5801 if (!HasSelection())
5802 return sSelectedText;
5803 start.y = m_nSelViewBlockStart;
5804 start.x = 0;
5805 end.y = m_nSelViewBlockEnd;
5806 end.x = GetViewLineLength(m_nSelViewBlockEnd);
5808 if (!m_pViewData)
5809 return sSelectedText;
5810 // first store the selected lines in one CString
5811 for (int nViewLine=start.y; nViewLine<=end.y; nViewLine++)
5813 switch (m_pViewData->GetState(nViewLine))
5815 case DIFFSTATE_EMPTY:
5816 break;
5817 case DIFFSTATE_UNKNOWN:
5818 case DIFFSTATE_NORMAL:
5819 case DIFFSTATE_REMOVED:
5820 case DIFFSTATE_REMOVEDWHITESPACE:
5821 case DIFFSTATE_ADDED:
5822 case DIFFSTATE_ADDEDWHITESPACE:
5823 case DIFFSTATE_WHITESPACE:
5824 case DIFFSTATE_WHITESPACE_DIFF:
5825 case DIFFSTATE_CONFLICTED:
5826 case DIFFSTATE_CONFLICTED_IGNORED:
5827 case DIFFSTATE_CONFLICTADDED:
5828 case DIFFSTATE_CONFLICTEMPTY:
5829 case DIFFSTATE_CONFLICTRESOLVED:
5830 case DIFFSTATE_IDENTICALREMOVED:
5831 case DIFFSTATE_IDENTICALADDED:
5832 case DIFFSTATE_THEIRSREMOVED:
5833 case DIFFSTATE_THEIRSADDED:
5834 case DIFFSTATE_YOURSREMOVED:
5835 case DIFFSTATE_YOURSADDED:
5836 case DIFFSTATE_EDITED:
5837 case DIFFSTATE_FILTEREDDIFF:
5838 sSelectedText += GetViewLineChars(nViewLine);
5839 sSelectedText += L"\r\n";
5840 break;
5843 // remove the non-selected chars from the first line, last line and last \r\n
5844 int nLeftCut = start.x;
5845 int nRightCut = GetViewLineChars(end.y).GetLength() - end.x + 2;
5846 sSelectedText = sSelectedText.Mid(nLeftCut, sSelectedText.GetLength()-nLeftCut-nRightCut);
5847 return sSelectedText;
5850 void CBaseView::CheckModifications(bool& hasMods, bool& hasConflicts, bool& hasWhitespaceMods, bool& hasFilteredMods)
5852 hasMods = false;
5853 hasConflicts = false;
5854 hasWhitespaceMods = false;
5855 hasFilteredMods = false;
5857 if (m_pViewData)
5859 for (int i=0; i<m_pViewData->GetCount(); i++)
5861 DiffStates state = m_pViewData->GetState(i);
5862 switch (state)
5864 case DIFFSTATE_ADDED:
5865 case DIFFSTATE_IDENTICALADDED:
5866 case DIFFSTATE_THEIRSADDED:
5867 case DIFFSTATE_YOURSADDED:
5868 case DIFFSTATE_CONFLICTADDED:
5869 case DIFFSTATE_IDENTICALREMOVED:
5870 case DIFFSTATE_REMOVED:
5871 case DIFFSTATE_THEIRSREMOVED:
5872 case DIFFSTATE_YOURSREMOVED:
5873 case DIFFSTATE_EMPTY:
5874 hasMods = true;
5875 break;
5876 case DIFFSTATE_CONFLICTED:
5877 case DIFFSTATE_CONFLICTED_IGNORED:
5878 hasConflicts = true;
5879 break;
5880 case DIFFSTATE_REMOVEDWHITESPACE:
5881 case DIFFSTATE_ADDEDWHITESPACE:
5882 case DIFFSTATE_WHITESPACE:
5883 case DIFFSTATE_WHITESPACE_DIFF:
5884 hasWhitespaceMods = true;
5885 break;
5886 case DIFFSTATE_FILTEREDDIFF:
5887 hasFilteredMods = true;
5888 break;
5894 void CBaseView::OnEditGotoline()
5896 if (!m_pViewData)
5897 return;
5898 // find the last and first line number
5899 int nViewLineCount = m_pViewData->GetCount();
5901 int nLastLineNumber = DIFF_EMPTYLINENUMBER;
5902 for (int nViewLine=nViewLineCount-1; nViewLine>=0; --nViewLine)
5904 nLastLineNumber = m_pViewData->GetLineNumber(nViewLine);
5905 if (nLastLineNumber!=DIFF_EMPTYLINENUMBER)
5907 break;
5910 if (nLastLineNumber==DIFF_EMPTYLINENUMBER || nLastLineNumber==0) // not numbered line foud or last one is first
5912 return;
5914 nLastLineNumber++;
5915 int nFirstLineNumber=1; // first is always 1
5917 CString sText;
5918 sText.FormatMessage(IDS_GOTOLINE, nFirstLineNumber, nLastLineNumber);
5920 CGotoLineDlg dlg(this);
5921 dlg.SetLabel(sText);
5922 dlg.SetLimits(nFirstLineNumber, nLastLineNumber);
5923 if (dlg.DoModal() == IDOK)
5925 for (int nViewLine = 0; nViewLine < nViewLineCount; ++nViewLine)
5927 if ((m_pViewData->GetLineNumber(nViewLine)+1) == dlg.GetLineNumber())
5929 HighlightViewLines(nViewLine, nViewLine);
5930 return;
5936 int CBaseView::SaveFile(int nFlags)
5938 Invalidate();
5939 if (m_pViewData && m_pWorkingFile)
5941 CFileTextLines file;
5942 m_SaveParams.m_LineEndings = m_lineendings;
5943 m_SaveParams.m_UnicodeType = m_texttype;
5944 file.SetSaveParams(m_SaveParams);
5946 for (int i=0; i<m_pViewData->GetCount(); i++)
5948 //only copy non-removed lines
5949 DiffStates state = m_pViewData->GetState(i);
5950 switch (state)
5952 case DIFFSTATE_CONFLICTED:
5953 case DIFFSTATE_CONFLICTED_IGNORED:
5955 int first = i;
5956 int last = i;
5959 last++;
5960 } while((last<m_pViewData->GetCount()) && ((m_pViewData->GetState(last)==DIFFSTATE_CONFLICTED)||(m_pViewData->GetState(last)==DIFFSTATE_CONFLICTED_IGNORED)));
5961 file.Add(L"<<<<<<< .mine", EOL_NOENDING);
5962 for (int j=first; j<last; j++)
5964 file.Add(m_pwndRight->m_pViewData->GetLine(j), m_pwndRight->m_pViewData->GetLineEnding(j));
5966 file.Add(L"=======", EOL_NOENDING);
5967 for (int j=first; j<last; j++)
5969 file.Add(m_pwndLeft->m_pViewData->GetLine(j), m_pwndLeft->m_pViewData->GetLineEnding(j));
5971 file.Add(L">>>>>>> .theirs", EOL_NOENDING);
5972 i = last-1;
5974 break;
5975 case DIFFSTATE_EMPTY:
5976 break;
5977 case DIFFSTATE_CONFLICTEMPTY:
5978 case DIFFSTATE_IDENTICALREMOVED:
5979 case DIFFSTATE_REMOVED:
5980 case DIFFSTATE_THEIRSREMOVED:
5981 case DIFFSTATE_YOURSREMOVED:
5982 case DIFFSTATE_CONFLICTRESOLVEDEMPTY:
5983 if ((nFlags&SAVE_REMOVEDLINES) == 0)
5985 // do not save removed lines
5986 break;
5988 default:
5989 file.Add(m_pViewData->GetLine(i), m_pViewData->GetLineEnding(i));
5990 break;
5993 CString filename = m_pWorkingFile->GetFilename();
5994 if (m_pWorkingFile->IsReadonly())
5995 if (!CCommonAppUtils::FileOpenSave(filename, nullptr, IDS_SAVEASTITLE, IDS_COMMONFILEFILTER, false, m_hWnd))
5996 return -1;
5997 if (!file.Save(filename))
5999 ::MessageBox(m_hWnd, file.GetErrorString(), L"TortoiseGitMerge", MB_ICONERROR);
6000 return -1;
6002 m_pWorkingFile->SetFileName(filename);
6003 m_pWorkingFile->StoreFileAttributes();
6004 // m_dlgFilePatches.SetFileStatusAsPatched(sFilePath);
6005 SetModified(FALSE);
6006 CUndo::GetInstance().MarkAsOriginalState(
6007 this == m_pwndLeft,
6008 this == m_pwndRight,
6009 this == m_pwndBottom);
6010 if (file.GetCount() == 1 && file.GetAt(0).IsEmpty() && file.GetLineEnding(0) == EOL_NOENDING)
6011 return 0;
6012 return file.GetCount();
6014 return 1;
6018 int CBaseView::SaveFileTo(CString sFileName, int nFlags)
6020 if (m_pWorkingFile)
6022 m_pWorkingFile->SetFileName(sFileName);
6023 return SaveFile(nFlags);
6025 return -1;
6029 EOL CBaseView::GetLineEndings()
6031 return GetLineEndings(GetWhitecharsProperties().HasMixedEols);
6034 EOL CBaseView::GetLineEndings(bool bHasMixedEols)
6036 if (bHasMixedEols)
6038 return EOL_AUTOLINE; // mixed eols - hack value
6040 if (m_lineendings == EOL_AUTOLINE)
6042 return EOL_CRLF;
6044 return m_lineendings;
6047 void CBaseView::ReplaceLineEndings(EOL eEol)
6049 if (eEol == EOL_AUTOLINE)
6051 return;
6053 // set AUTOLINE
6054 m_lineendings = eEol;
6055 // replace all set EOLs
6056 // TODO store line endings and lineendings in undo
6057 //CUndo::BeginGrouping();
6058 for (int i = 0; i < GetViewCount(); ++i)
6060 if (IsLineEmpty(i))
6062 continue;
6064 EOL eLineEol = GetViewLineEnding(i);
6065 if (eLineEol == EOL_AUTOLINE || eLineEol == EOL_NOENDING || eLineEol == m_lineendings)
6067 continue;
6069 SetViewLineEnding(i, eEol);
6071 //CUndo::EndGrouping();
6072 //CUndo::saveundostep;
6073 DocumentUpdated();
6074 SetModified();
6077 void CBaseView::SetLineEndingStyle(EOL eEol)
6079 m_lineendings = eEol;
6082 void CBaseView::SetTextType(CFileTextLines::UnicodeType eTextType)
6084 if (m_texttype == eTextType)
6086 return;
6088 m_texttype = eTextType;
6089 DocumentUpdated();
6090 SetModified();
6093 void CBaseView::AskUserForNewLineEndingsAndTextType(int nTextId)
6095 if (IsReadonly())
6096 return; // nothing to be changed in read-only view
6097 CEncodingDlg dlg;
6098 dlg.view.LoadString(nTextId);
6099 dlg.texttype = m_texttype;
6100 dlg.lineendings = GetLineEndings();
6101 if (dlg.DoModal() != IDOK)
6102 return;
6103 SetTextType(dlg.texttype);
6104 ReplaceLineEndings(dlg.lineendings);
6108 Replaces lines from source view to this
6110 void CBaseView::UseViewBlock(CBaseView * pwndView, int nFirstViewLine, int nLastViewLine, std::function<bool(int)> fnSkip)
6112 if (!IsViewGood(pwndView))
6113 return;
6114 if (!IsWritable())
6115 return;
6116 CUndo::GetInstance().BeginGrouping();
6118 for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++)
6120 bool skip = fnSkip(viewLine);
6121 if (skip)
6123 if (GetViewMarked(viewLine))
6124 SetViewMarked(viewLine, false);
6125 continue;
6127 viewdata line = pwndView->GetViewData(viewLine);
6128 if (line.ending != EOL_NOENDING)
6129 line.ending = m_lineendings;
6130 switch (line.state)
6132 case DIFFSTATE_CONFLICTEMPTY:
6133 case DIFFSTATE_UNKNOWN:
6134 line.state = DIFFSTATE_EMPTY;
6135 case DIFFSTATE_EMPTY:
6136 break;
6137 case DIFFSTATE_ADDED:
6138 case DIFFSTATE_CONFLICTADDED:
6139 case DIFFSTATE_CONFLICTED:
6140 case DIFFSTATE_CONFLICTED_IGNORED:
6141 case DIFFSTATE_IDENTICALADDED:
6142 case DIFFSTATE_THEIRSADDED:
6143 case DIFFSTATE_YOURSADDED:
6144 case DIFFSTATE_IDENTICALREMOVED:
6145 case DIFFSTATE_REMOVED:
6146 case DIFFSTATE_THEIRSREMOVED:
6147 case DIFFSTATE_YOURSREMOVED:
6148 pwndView->SetViewState(viewLine, DIFFSTATE_NORMAL);
6149 line.state = DIFFSTATE_NORMAL;
6150 case DIFFSTATE_NORMAL:
6151 break;
6152 default:
6153 break;
6155 bool marked = GetViewMarked(viewLine);
6156 SetViewData(viewLine, line);
6157 if (marked)
6158 SetViewMarked(viewLine, false);
6159 if ((m_texttype == UnicodeType::ASCII) && (pwndView->GetTextType() != UnicodeType::ASCII))
6161 // if this view is in ASCII and the other is not, we have to make sure that
6162 // the text we copy from the other view can actually be saved in ASCII encoding.
6163 // if not, we have to change this views encoding to the same encoding as the other view
6164 BOOL useDefault = FALSE;
6165 WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, line.sLine, -1, nullptr, 0, 0, &useDefault);
6166 if (useDefault) // a default char is required, so the char can not be saved as ASCII
6167 SetTextType(pwndView->GetTextType());
6170 // normal lines is mostly same but may differ in EOL so any copied line change view state to modified
6171 // TODO: check if copied line is same as original one set modified only when differ
6172 SetModified();
6173 SaveUndoStep();
6175 int nRemovedLines = CleanEmptyLines();
6176 SaveUndoStep();
6177 //VerifyEols();
6178 // make sure all non empty line have EOL set but last
6179 // wrong can be last copied line(have eol, but no line under),
6180 // or old last line (line before copied block missing eol, but have line under)
6181 // we'll check all lines to be sure
6182 int nLine = GetViewCount();
6183 // check last line have no EOL set
6184 while (--nLine>=0)
6186 if (!IsViewLineEmpty(nLine))
6188 if (GetViewLineEnding(nLine) != EOL_NOENDING)
6190 // we added non last line into empty block on the end (or should we remove eol from this one ?)
6191 // so next line is empty
6192 ASSERT(IsViewLineEmpty(nLine+1));
6193 // and we can turn it to normal empty line
6194 SetViewData(nLine+1, viewdata(CString(), DIFFSTATE_ADDED, 1, EOL_NOENDING, HIDESTATE_SHOWN));
6196 break;
6199 // check all (nonlast) line have EOL set
6200 while (--nLine>=0)
6202 if (!IsViewLineEmpty(nLine))
6204 if (GetViewLineEnding(nLine) == EOL_NOENDING)
6206 SetViewLineEnding(nLine, m_lineendings);
6207 // in theory there should be only one line needing fix, but most of time we get over all anyway
6208 // break;
6212 SaveUndoStep();
6213 UpdateViewLineNumbers();
6214 SaveUndoStep();
6216 CUndo::GetInstance().EndGrouping();
6218 if (nRemovedLines!=0)
6220 // some lines are gone update selection
6221 ClearSelection();
6222 SetupAllViewSelection(nFirstViewLine, nLastViewLine - nRemovedLines);
6224 BuildAllScreen2ViewVector();
6225 pwndView->Invalidate();
6226 RefreshViews();
6229 void CBaseView::MarkBlock(bool marked, int nFirstViewLine, int nLastViewLine)
6231 if (!IsWritable())
6232 return;
6233 CUndo::GetInstance().BeginGrouping();
6235 for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++)
6236 SetViewMarked(viewLine, marked);
6238 SetModified();
6239 SaveUndoStep();
6240 CUndo::GetInstance().EndGrouping();
6242 BuildAllScreen2ViewVector();
6243 Invalidate();
6244 RefreshViews();
6247 void CBaseView::LeaveOnlyMarkedBlocks(CBaseView *pwndView)
6249 auto fn = [this](int viewLine) -> bool { return GetViewMarked(viewLine) || GetViewState(viewLine) == DIFFSTATE_EDITED; };
6250 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6253 void CBaseView::UseViewFileOfMarked(CBaseView *pwndView)
6255 auto fn = [this](int viewLine) -> bool { return !GetViewMarked(viewLine) || GetViewState(viewLine) == DIFFSTATE_EDITED; };
6256 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6259 void CBaseView::UseViewFileExceptEdited(CBaseView *pwndView)
6261 auto fn = [this](int viewLine) -> bool { return GetViewState(viewLine) == DIFFSTATE_EDITED; };
6262 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6265 int CBaseView::GetLargestSpaceStreak(const CString& line)
6267 int count = 0;
6268 int maxstreak = 0;
6269 for (int i = 0; i < line.GetLength(); ++i)
6271 if (line[i] == ' ')
6272 ++count;
6273 else
6275 maxstreak = std::max(count, maxstreak);
6276 count = 0;
6279 return std::max(count, maxstreak);
6282 int CBaseView::GetIndentCharsForLine(int x, int y)
6284 const int maxGuessLine = 100;
6285 int nTabMode = -1;
6286 const CString& line = GetViewLine(y);
6287 if (m_nTabMode & TABMODE_SMARTINDENT)
6289 // if the line contains one tab, use tabs
6290 // we can not test for spaces, since even if tabs are used,
6291 // spaces are used in a tabified file for alignment.
6292 if (line.Find(L'\t') >= 0)
6293 nTabMode = 0; // use tabs
6294 else if (GetLargestSpaceStreak(line) > m_nTabSize)
6295 nTabMode = 1; // use spaces
6297 // detect lines nearby
6298 for (int i = y - 1, j = y + 1; nTabMode == -1; --i, ++j)
6300 bool above = i >= 0 && i >= y - maxGuessLine;
6301 bool below = j < GetViewCount() && j <= y + maxGuessLine;
6302 if (!(above || below))
6303 break;
6304 auto ac = CString();
6305 auto bc = CString();
6306 if (above)
6307 ac = GetViewLine(i);
6308 if (below)
6309 bc = GetViewLine(j);
6310 if ((ac.Find(L'\t') >= 0) || (bc.Find(L'\t') >= 0))
6312 nTabMode = 0;
6313 break;
6315 else if ((GetLargestSpaceStreak(ac) > m_nTabSize) && (GetLargestSpaceStreak(bc) > m_nTabSize))
6317 nTabMode = 1;
6318 break;
6322 else
6323 nTabMode = m_nTabMode & TABMODE_USESPACES;
6325 if (nTabMode > 0)
6327 // use spaces
6328 x = CountExpandedChars(line, x);
6329 return (m_nTabSize - (x % m_nTabSize));
6332 // use tab
6333 return 0;
6336 void CBaseView::AddIndentationForSelectedBlock()
6338 bool bModified = false;
6339 for (int nViewLine = m_ptSelectionViewPosStart.y; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
6341 // skip the line if no character is selected in the last selected line
6342 if (nViewLine == m_ptSelectionViewPosEnd.y && m_ptSelectionViewPosEnd.x == 0)
6344 continue;
6346 // skip empty lines
6347 if (IsLineEmpty(nViewLine))
6349 continue;
6351 const CString &sLine = GetViewLine(nViewLine);
6352 CString sTemp = sLine;
6353 if (sTemp.Trim().IsEmpty())
6355 // skip empty and whitechar only lines
6356 continue;
6358 // add tab to line start (alternatively m_nTabSize spaces can be used)
6359 CString tabStr;
6360 int indentChars = GetIndentCharsForLine(0, nViewLine);
6361 tabStr = indentChars > 0 ? CString(L' ', indentChars) : CString("\t");
6362 SetViewLine(nViewLine, tabStr + sLine);
6363 bModified = true;
6365 if (bModified)
6367 SetModified();
6368 SaveUndoStep();
6369 BuildAllScreen2ViewVector();
6373 void CBaseView::RemoveIndentationForSelectedBlock()
6375 bool bModified = false;
6376 for (int nViewLine = m_ptSelectionViewPosStart.y; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
6378 // skip the line if no character is selected in the last selected line
6379 if (nViewLine == m_ptSelectionViewPosEnd.y && m_ptSelectionViewPosEnd.x == 0)
6381 continue;
6383 // skip empty lines
6384 if (IsLineEmpty(nViewLine))
6386 continue;
6388 CString sLine = GetViewLine(nViewLine);
6389 // remove up to n spaces from line start
6390 // and one tab (if less then n spaces was removed)
6391 int nPos = 0;
6392 while (nPos<m_nTabSize)
6394 switch (sLine[nPos])
6396 case ' ':
6397 nPos++;
6398 continue;
6399 case '\t':
6400 nPos++;
6402 break;
6404 if (nPos>0)
6406 sLine.Delete(0, nPos);
6407 SetViewLine(nViewLine, sLine);
6408 bModified = true;
6411 if (bModified)
6413 SetModified();
6414 SaveUndoStep();
6415 BuildAllScreen2ViewVector();
6420 there are two possible versions
6421 - convert tabs to spaces only in front of text (implemented)
6422 - convert all tabs to spaces
6424 void CBaseView::ConvertTabToSpaces()
6426 bool bModified = false;
6427 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6429 if (IsLineEmpty(nViewLine))
6431 continue;
6433 const CString &sLine = GetViewLine(nViewLine);
6434 bool bTabToConvertFound = false;
6435 int nPosIn = 0;
6436 int nPosOut = 0;
6437 while (nPosIn<sLine.GetLength())
6439 switch (sLine[nPosIn])
6441 case ' ':
6442 nPosIn++;
6443 nPosOut++;
6444 continue;
6445 case '\t':
6446 nPosIn++;
6447 bTabToConvertFound = true;
6448 nPosOut = (nPosOut+m_nTabSize) - nPosOut%m_nTabSize;
6449 continue;
6451 break;
6453 if (bTabToConvertFound)
6455 CString sLineNew = sLine;
6456 sLineNew.Delete(0, nPosIn);
6457 sLineNew = CString(' ', nPosOut) + sLineNew;
6458 SetViewLine(nViewLine, sLineNew);
6459 bModified = true;
6462 if (bModified)
6464 SetModified();
6465 SaveUndoStep();
6466 BuildAllScreen2ViewVector();
6471 there are two possible version
6472 - convert spaces to tabs only in front of text (implemented)
6473 - convert all spaces to tabs
6475 void CBaseView::Tabularize()
6477 bool bModified = false;
6478 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6480 if (IsLineEmpty(nViewLine))
6482 continue;
6484 const CString &sLine = GetViewLine(nViewLine);
6485 int nDel = 0;
6486 int nTabCount = 0; // total tabs to be used
6487 int nSpaceCount = 0; // number of spaces in tab size run
6488 int nPos = 0;
6489 while (nPos<sLine.GetLength())
6491 switch (sLine[nPos++])
6493 case ' ':
6494 //bSpace = true;
6495 if (++nSpaceCount < m_nTabSize)
6497 continue;
6499 case '\t':
6500 nTabCount++;
6501 nSpaceCount = 0;
6502 nDel = nPos;
6503 continue;
6505 break;
6507 if (nDel > 0)
6509 CString sLineNew = sLine;
6510 sLineNew.Delete(0, nDel);
6511 sLineNew = CString('\t', nTabCount) + sLineNew;
6512 if (sLine!=sLineNew)
6514 SetViewLine(nViewLine, sLineNew);
6515 bModified = true;
6519 if (bModified)
6521 SetModified();
6522 SaveUndoStep();
6523 BuildAllScreen2ViewVector();
6527 void CBaseView::RemoveTrailWhiteChars()
6529 bool bModified = false;
6530 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6532 if (IsLineEmpty(nViewLine))
6534 continue;
6536 const CString &sLine = GetViewLine(nViewLine);
6537 CString sLineNew = sLine;
6538 sLineNew.TrimRight();
6539 if (sLine.GetLength()!=sLineNew.GetLength())
6541 SetViewLine(nViewLine, sLineNew);
6542 bModified = true;
6545 if (bModified)
6547 SetModified();
6548 SaveUndoStep();
6549 BuildAllScreen2ViewVector();
6553 CBaseView::TWhitecharsProperties CBaseView::GetWhitecharsProperties()
6555 if (GetViewCount()>10000)
6557 // 10k lines is enough to check
6558 TWhitecharsProperties oRet = {true, true, true, true};
6559 return oRet;
6561 TWhitecharsProperties oRet = {};
6562 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6564 if (IsLineEmpty(nViewLine))
6566 continue;
6568 const CString &sLine = GetViewLine(nViewLine);
6569 if (sLine.IsEmpty())
6571 continue;
6573 // check leading whites for convertible tabs and spaces
6574 int nPos = 0;
6575 int nSpaceCount = 0; // number of spaces in tab size run
6576 while (nPos<sLine.GetLength() && (!oRet.HasSpacesToConvert || !oRet.HasTabsToConvert))
6578 switch (sLine[nPos++])
6580 case ' ':
6581 if (++nSpaceCount >= m_nTabSize)
6583 oRet.HasSpacesToConvert = true;
6585 continue;
6586 case '\t':
6587 oRet.HasTabsToConvert = true;
6588 if (nSpaceCount!=0)
6590 oRet.HasSpacesToConvert = true;
6592 continue;
6594 break;
6597 // check trailing whites for removable chars
6598 switch (sLine[sLine.GetLength()-1])
6600 case ' ':
6601 case '\t':
6602 oRet.HasTrailWhiteChars = true;
6605 // check EOLs
6606 EOL eLineEol = GetViewLineEnding(nViewLine);
6607 if (!oRet.HasMixedEols && (eLineEol != m_lineendings) && (eLineEol != EOL_AUTOLINE) && (eLineEol != EOL_NOENDING))
6609 oRet.HasMixedEols = true;
6612 return oRet;
6615 void CBaseView::InsertText(const CString& sText)
6617 ResetUndoStep();
6619 POINT ptCaretViewPos = GetCaretViewPosition();
6620 int nLeft = ptCaretViewPos.x;
6621 int nViewLine = ptCaretViewPos.y;
6623 if ((nViewLine == 0) && (GetViewCount() == 0))
6624 OnChar(VK_RETURN, 0, 0);
6626 std::vector<CString> lines;
6627 int nStart = 0;
6628 int nEolPos = 0;
6629 while ((nEolPos = sText.Find('\r', nEolPos)) >= 0)
6631 CString sLine = sText.Mid(nStart, nEolPos - nStart);
6632 lines.push_back(sLine);
6633 nEolPos++;
6634 nStart = nEolPos;
6636 CString sLine = sText.Mid(nStart);
6637 lines.push_back(sLine);
6639 int nLinesToPaste = static_cast<int>(lines.size());
6640 if (nLinesToPaste > 1)
6642 // multiline text
6644 // We want to undo the multiline insertion in a single step.
6645 CUndo::GetInstance().BeginGrouping();
6647 sLine = GetViewLineChars(nViewLine);
6648 CString sLineLeft = sLine.Left(nLeft);
6649 CString sLineRight = sLine.Right(sLine.GetLength() - nLeft);
6650 EOL eOriginalEnding = GetViewLineEnding(nViewLine);
6651 viewdata newLine(L"", DIFFSTATE_EDITED, 1, m_lineendings, HIDESTATE_SHOWN);
6652 if (!lines[0].IsEmpty() || !sLineRight.IsEmpty() || (eOriginalEnding != m_lineendings))
6654 newLine.sLine = sLineLeft + lines[0];
6655 SetViewData(nViewLine, newLine);
6658 int nInsertLine = nViewLine;
6659 for (int i = 1; i < nLinesToPaste - 1; i++)
6661 newLine.sLine = lines[i];
6662 InsertViewData(++nInsertLine, newLine);
6664 newLine.sLine = lines[nLinesToPaste - 1] + sLineRight;
6665 newLine.ending = eOriginalEnding;
6666 InsertViewData(++nInsertLine, newLine);
6668 SetModified();
6669 SaveUndoStep();
6671 // adds new lines everywhere except me
6672 if (IsViewGood(m_pwndLeft) && m_pwndLeft != this)
6674 m_pwndLeft->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6676 if (IsViewGood(m_pwndRight) && m_pwndRight != this)
6678 m_pwndRight->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6680 if (IsViewGood(m_pwndBottom) && m_pwndBottom != this)
6682 m_pwndBottom->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6684 SaveUndoStep();
6686 UpdateViewLineNumbers();
6687 CUndo::GetInstance().EndGrouping();
6689 ptCaretViewPos = SetupPoint(lines[nLinesToPaste - 1].GetLength(), nInsertLine);
6691 else
6693 // single line text - just insert it
6694 sLine = GetViewLineChars(nViewLine);
6695 sLine.Insert(nLeft, sText);
6696 ptCaretViewPos = SetupPoint(nLeft + sText.GetLength(), nViewLine);
6697 SetViewLine(nViewLine, sLine);
6699 auto viewState = GetViewState(nViewLine);
6700 if (IsStateEmpty(viewState) || IsStateConflicted(viewState) || viewState == DIFFSTATE_IDENTICALREMOVED)
6702 // if not last line set EOL
6703 for (int nCheckViewLine = nViewLine + 1; nCheckViewLine < GetViewCount(); ++nCheckViewLine)
6705 if (!IsViewLineEmpty(nCheckViewLine))
6707 SetViewLineEnding(nViewLine, m_lineendings);
6708 break;
6711 // make sure previous (non empty) line have EOL set
6712 for (int nCheckViewLine = nViewLine - 1; nCheckViewLine > 0; --nCheckViewLine)
6714 if (!IsViewLineEmpty(nCheckViewLine))
6716 if (GetViewLineEnding(nCheckViewLine) == EOL_NOENDING)
6717 SetViewLineEnding(nCheckViewLine, m_lineendings);
6718 break;
6723 SetViewState(nViewLine, DIFFSTATE_EDITED);
6724 SetModified();
6725 SaveUndoStep();
6728 RefreshViews();
6729 BuildAllScreen2ViewVector();
6730 UpdateCaretViewPosition(ptCaretViewPos);
6733 ULONG CBaseView::GetGestureStatus(CPoint /*ptTouch*/)
6735 return 0;