Integrate the DPIAware.h changes from TortoiseSVN
[TortoiseGit.git] / src / TortoiseMerge / BaseView.cpp
blob9335c3a93f50ac211192ff17f144e2ea421d3a24
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2003-2018 - TortoiseSVN
4 // Copyright (C) 2011-2012, 2017-2018 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"
34 // Note about lines:
35 // We use three different kind of lines here:
36 // 1. The real lines of the original files.
37 // These are shown in the view margin and are not used elsewhere, they're only for user information.
38 // 2. Screen lines.
39 // The lines actually shown on screen. All methods use screen lines as parameters/outputs if not explicitly specified otherwise.
40 // 3. View lines.
41 // These are the lines of the diff data. If unmodified sections are collapsed, not all of those lines are shown.
43 // Basically view lines are the line data, while screen lines are shown lines.
46 #ifdef _DEBUG
47 #define new DEBUG_NEW
48 #endif
50 #define HEADERHEIGHT 10
52 #define IDT_SCROLLTIMER 101
54 CBaseView* CBaseView::m_pwndLeft = nullptr;
55 CBaseView* CBaseView::m_pwndRight = nullptr;
56 CBaseView* CBaseView::m_pwndBottom = nullptr;
57 CLocatorBar* CBaseView::m_pwndLocator = nullptr;
58 CLineDiffBar* CBaseView::m_pwndLineDiffBar = nullptr;
59 CMFCStatusBar* CBaseView::m_pwndStatusBar = nullptr;
60 CMFCRibbonStatusBar* CBaseView::m_pwndRibbonStatusBar = nullptr;
61 CMainFrame* CBaseView::m_pMainFrame = nullptr;
62 CBaseView::Screen2View CBaseView::m_Screen2View;
63 const UINT CBaseView::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING);
65 allviewstate CBaseView::m_AllState;
67 IMPLEMENT_DYNCREATE(CBaseView, CView)
69 CBaseView::CBaseView()
70 : m_pCacheBitmap(nullptr)
71 , m_pViewData(nullptr)
72 , m_pOtherViewData(nullptr)
73 , m_pOtherView(nullptr)
74 , m_nLineHeight(-1)
75 , m_nCharWidth(-1)
76 , m_nScreenChars(-1)
77 , m_nLastScreenChars(-1)
78 , m_nMaxLineLength(-1)
79 , m_nScreenLines(-1)
80 , m_nTopLine(0)
81 , m_nOffsetChar(0)
82 , m_nDigits(0)
83 , m_nMouseLine(-1)
84 , m_mouseInMargin(false)
85 , m_bIsHidden(FALSE)
86 , m_lineendings(EOL_AUTOLINE)
87 , m_bReadonly(true)
88 , m_bReadonlyIsChangable(false)
89 , m_bTarget(false)
90 , m_nCaretGoalPos(0)
91 , m_nSelViewBlockStart(-1)
92 , m_nSelViewBlockEnd(-1)
93 , m_bFocused(FALSE)
94 , m_bShowSelection(true)
95 , m_texttype(CFileTextLines::AUTOTYPE)
96 , m_bModified(FALSE)
97 , m_bOtherDiffChecked(false)
98 , m_bInlineWordDiff(true)
99 , m_bWhitespaceInlineDiffs(false)
100 , m_pState(nullptr)
101 , m_pFindDialog(nullptr)
102 , m_nStatusBarID(0)
103 , m_bMatchCase(false)
104 , m_bLimitToDiff(true)
105 , m_bWholeWord(false)
106 , m_pDC(nullptr)
107 , m_pWorkingFile(nullptr)
108 , m_bInsertMode(true)
109 , m_bEditorConfigEnabled(false)
110 , m_bEditorConfigLoaded(2) // 2 = not evaluated
112 m_ptCaretViewPos.x = 0;
113 m_ptCaretViewPos.y = 0;
114 m_ptSelectionViewPosStart = m_ptCaretViewPos;
115 m_ptSelectionViewPosEnd = m_ptSelectionViewPosStart;
116 m_ptSelectionViewPosOrigin = m_ptSelectionViewPosEnd;
117 m_bViewWhitespace = CRegDWORD(L"Software\\TortoiseGitMerge\\ViewWhitespaces", 1);
118 m_bViewLinenumbers = CRegDWORD(L"Software\\TortoiseGitMerge\\ViewLinenumbers", 1);
119 m_bShowInlineDiff = CRegDWORD(L"Software\\TortoiseGitMerge\\DisplayBinDiff", TRUE);
120 m_nInlineDiffMaxLineLength = CRegDWORD(L"Software\\TortoiseGitMerge\\InlineDiffMaxLineLength", 3000);
121 m_InlineAddedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\InlineAdded", INLINEADDED_COLOR);
122 m_InlineRemovedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\InlineRemoved", INLINEREMOVED_COLOR);
123 m_ModifiedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\ColorModifiedB", MODIFIED_COLOR);
124 m_WhiteSpaceFg = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\Whitespace", GetSysColor(COLOR_3DSHADOW));
125 m_sWordSeparators = CRegString(L"Software\\TortoiseGitMerge\\WordSeparators", L"[]();:.,{}!@#$%^&*-+=|/\\<>'`~\"?");
126 m_bIconLFs = CRegDWORD(L"Software\\TortoiseGitMerge\\IconLFs", 0);
127 m_nTabSize = (int)(DWORD)CRegDWORD(L"Software\\TortoiseGitMerge\\TabSize", 4);
128 m_nTabMode = (int)(DWORD)CRegDWORD(L"Software\\TortoiseGitMerge\\TabMode", TABMODE_NONE);
129 m_bEditorConfigEnabled = !!(DWORD)CRegDWORD(L"Software\\TortoiseGitMerge\\EnableEditorConfig", FALSE);
130 std::fill_n(m_apFonts, fontsCount, (CFont*)nullptr);
132 int cxIcon = GetSystemMetrics(SM_CXSMICON);
133 int cyIcon = GetSystemMetrics(SM_CYSMICON);
134 m_hConflictedIcon = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_CONFLICTEDLINE), IMAGE_ICON, cxIcon, cyIcon, 0);
135 m_hConflictedIgnoredIcon = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_CONFLICTEDIGNOREDLINE), IMAGE_ICON, cxIcon, cyIcon, 0);
136 m_hRemovedIcon = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_REMOVEDLINE), IMAGE_ICON, cxIcon, cyIcon, 0);
137 m_hAddedIcon = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ADDEDLINE), IMAGE_ICON, cxIcon, cyIcon, 0);
138 m_hWhitespaceBlockIcon = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_WHITESPACELINE), IMAGE_ICON, cxIcon, cyIcon, 0);
139 m_hEqualIcon = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EQUALLINE), IMAGE_ICON, cxIcon, cyIcon, 0);
140 m_hLineEndingCR = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_LINEENDINGCR), IMAGE_ICON, cxIcon, cyIcon, 0);
141 m_hLineEndingCRLF = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_LINEENDINGCRLF), IMAGE_ICON, cxIcon, cyIcon, 0);
142 m_hLineEndingLF = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_LINEENDINGLF), IMAGE_ICON, cxIcon, cyIcon, 0);
143 m_hEditedIcon = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_LINEEDITED), IMAGE_ICON, cxIcon, cyIcon, 0);
144 m_hMovedIcon = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_MOVEDLINE), IMAGE_ICON, cxIcon, cyIcon, 0);
145 m_hMarkedIcon = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_LINEMARKED), IMAGE_ICON, cxIcon, cyIcon, 0);
146 m_margincursor = (HCURSOR)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_MARGINCURSOR), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE);
148 for (int i=0; i<1024; ++i)
149 m_sConflictedText += L"??";
150 m_sNoLineNr.LoadString(IDS_EMPTYLINETT);
152 m_szTip[0] = 0;
153 m_wszTip[0] = 0;
154 SecureZeroMemory(&m_lfBaseFont, sizeof(m_lfBaseFont));
155 EnableToolTips();
157 m_Eols[EOL_LF] = L"\n"; // x0a
158 m_Eols[EOL_CR] = L"\r"; // x0d
159 m_Eols[EOL_CRLF] = L"\r\n"; // x0d x0a
160 m_Eols[EOL_LFCR] = L"\n\r";
161 m_Eols[EOL_VT] = L"\v"; // x0b
162 m_Eols[EOL_FF] = L"\f"; // x0c
163 m_Eols[EOL_NEL] = L"\x85";
164 m_Eols[EOL_LS] = L"\x2028";
165 m_Eols[EOL_PS] = L"\x2029";
166 m_Eols[EOL_AUTOLINE] = m_Eols[m_lineendings==EOL_AUTOLINE
167 ? EOL_CRLF
168 : m_lineendings];
169 m_SaveParams.m_LineEndings = EOL::EOL_AUTOLINE;
170 m_SaveParams.m_UnicodeType = CFileTextLines::AUTOTYPE;
173 CBaseView::~CBaseView()
175 ReleaseBitmap();
176 DeleteFonts();
177 DestroyIcon(m_hAddedIcon);
178 DestroyIcon(m_hRemovedIcon);
179 DestroyIcon(m_hConflictedIcon);
180 DestroyIcon(m_hConflictedIgnoredIcon);
181 DestroyIcon(m_hWhitespaceBlockIcon);
182 DestroyIcon(m_hEqualIcon);
183 DestroyIcon(m_hLineEndingCR);
184 DestroyIcon(m_hLineEndingCRLF);
185 DestroyIcon(m_hLineEndingLF);
186 DestroyIcon(m_hEditedIcon);
187 DestroyIcon(m_hMovedIcon);
188 DestroyIcon(m_hMarkedIcon);
189 DestroyCursor(m_margincursor);
192 BEGIN_MESSAGE_MAP(CBaseView, CView)
193 ON_WM_VSCROLL()
194 ON_WM_HSCROLL()
195 ON_WM_ERASEBKGND()
196 ON_WM_CREATE()
197 ON_WM_DESTROY()
198 ON_WM_SIZE()
199 ON_WM_MOUSEWHEEL()
200 ON_WM_MOUSEHWHEEL()
201 ON_WM_SETCURSOR()
202 ON_WM_KILLFOCUS()
203 ON_WM_SETFOCUS()
204 ON_WM_CONTEXTMENU()
205 ON_COMMAND(ID_NAVIGATE_NEXTDIFFERENCE, OnMergeNextdifference)
206 ON_COMMAND(ID_NAVIGATE_PREVIOUSDIFFERENCE, OnMergePreviousdifference)
207 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
208 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
209 ON_WM_KEYDOWN()
210 ON_WM_LBUTTONDOWN()
211 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
212 ON_WM_MOUSEMOVE()
213 ON_COMMAND(ID_NAVIGATE_PREVIOUSCONFLICT, OnMergePreviousconflict)
214 ON_COMMAND(ID_NAVIGATE_NEXTCONFLICT, OnMergeNextconflict)
215 ON_WM_CHAR()
216 ON_COMMAND(ID_CARET_DOWN, &CBaseView::OnCaretDown)
217 ON_COMMAND(ID_CARET_LEFT, &CBaseView::OnCaretLeft)
218 ON_COMMAND(ID_CARET_RIGHT, &CBaseView::OnCaretRight)
219 ON_COMMAND(ID_CARET_UP, &CBaseView::OnCaretUp)
220 ON_COMMAND(ID_CARET_WORDLEFT, &CBaseView::OnCaretWordleft)
221 ON_COMMAND(ID_CARET_WORDRIGHT, &CBaseView::OnCaretWordright)
222 ON_COMMAND(ID_EDIT_CUT, &CBaseView::OnEditCut)
223 ON_COMMAND(ID_EDIT_PASTE, &CBaseView::OnEditPaste)
224 ON_WM_TIMER()
225 ON_WM_LBUTTONDBLCLK()
226 ON_COMMAND(ID_NAVIGATE_NEXTINLINEDIFF, &CBaseView::OnNavigateNextinlinediff)
227 ON_COMMAND(ID_NAVIGATE_PREVINLINEDIFF, &CBaseView::OnNavigatePrevinlinediff)
228 ON_COMMAND(ID_EDIT_SELECTALL, &CBaseView::OnEditSelectall)
229 ON_COMMAND(ID_EDIT_FIND, OnEditFind)
230 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
231 ON_COMMAND(ID_EDIT_FINDNEXT, OnEditFindnext)
232 ON_COMMAND(ID_EDIT_FINDPREV, OnEditFindprev)
233 ON_COMMAND(ID_EDIT_FINDNEXTSTART, OnEditFindnextStart)
234 ON_COMMAND(ID_EDIT_FINDPREVSTART, OnEditFindprevStart)
235 ON_COMMAND(ID_EDIT_GOTOLINE, &CBaseView::OnEditGotoline)
236 ON_WM_LBUTTONUP()
237 END_MESSAGE_MAP()
240 void CBaseView::DocumentUpdated()
242 ReleaseBitmap();
243 m_nLineHeight = -1;
244 m_nCharWidth = -1;
245 m_nScreenChars = -1;
246 m_nLastScreenChars = -1;
247 m_nMaxLineLength = -1;
248 m_nScreenLines = -1;
249 m_nTopLine = 0;
250 m_bModified = FALSE;
251 m_bOtherDiffChecked = false;
252 m_nDigits = 0;
253 m_nMouseLine = -1;
254 m_nTabSize = (int)(DWORD)CRegDWORD(L"Software\\TortoiseGitMerge\\TabSize", 4);
255 m_nTabMode = (int)(DWORD)CRegDWORD(L"Software\\TortoiseGitMerge\\TabMode", TABMODE_NONE);
256 m_bViewLinenumbers = CRegDWORD(L"Software\\TortoiseGitMerge\\ViewLinenumbers", 1);
257 m_InlineAddedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\InlineAdded", INLINEADDED_COLOR);
258 m_InlineRemovedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\InlineRemoved", INLINEREMOVED_COLOR);
259 m_ModifiedBk = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\ColorModifiedB", MODIFIED_COLOR);
260 m_WhiteSpaceFg = CRegDWORD(L"Software\\TortoiseGitMerge\\Colors\\Whitespace", GetSysColor(COLOR_3DSHADOW));
261 m_bIconLFs = CRegDWORD(L"Software\\TortoiseGitMerge\\IconLFs", 0);
262 m_nInlineDiffMaxLineLength = CRegDWORD(L"Software\\TortoiseGitMerge\\InlineDiffMaxLineLength", 3000);
263 m_Eols[EOL_AUTOLINE] = m_Eols[m_lineendings==EOL_AUTOLINE
264 ? EOL_CRLF
265 : m_lineendings];
266 SetEditorConfigEnabled(m_bEditorConfigEnabled);
267 DeleteFonts();
268 ClearCurrentSelection();
269 UpdateStatusBar();
270 Invalidate();
273 void CBaseView::SetEditorConfigEnabled(bool bEditorConfigEnabled)
275 m_bEditorConfigEnabled = bEditorConfigEnabled;
276 m_nTabSize = (int)(DWORD)CRegDWORD(L"Software\\TortoiseGitMerge\\TabSize", 4);
277 m_nTabMode = (int)(DWORD)CRegDWORD(L"Software\\TortoiseGitMerge\\TabMode", TABMODE_NONE);
278 if (m_bEditorConfigEnabled)
280 m_bEditorConfigLoaded = FALSE; // no editorconfig entries loaded
281 CEditorConfigWrapper ec;
282 if (ec.Load(m_sReflectedName.IsEmpty() ? m_sFullFilePath : m_sReflectedName))
284 m_bEditorConfigLoaded = TRUE;
285 if (ec.m_nTabWidth != nullptr)
286 m_nTabSize = ec.m_nTabWidth;
287 if (ec.m_bIndentStyle != nullptr)
288 m_nTabMode = (m_nTabMode & ~TABMODE_USESPACES) | (ec.m_bIndentStyle ? TABMODE_USESPACES : TABMODE_NONE);
293 static CString GetTabModeString(int nTabMode, int nTabSize, bool bEditorConfig)
295 CString text;
296 if (nTabMode & TABMODE_USESPACES)
297 text = L"Space";
298 else
299 text = L"Tab";
300 text.AppendFormat(L" %d", nTabSize);
301 if (nTabMode & TABMODE_SMARTINDENT)
302 text += L" Smart";
303 if (bEditorConfig)
304 text += L" EC";
305 return text;
308 void CBaseView::UpdateStatusBar()
310 int nRemovedLines = 0;
311 int nAddedLines = 0;
312 int nConflictedLines = 0;
314 if (m_pViewData)
316 for (int i=0; i<m_pViewData->GetCount(); i++)
318 DiffStates state = m_pViewData->GetState(i);
319 switch (state)
321 case DIFFSTATE_ADDED:
322 case DIFFSTATE_IDENTICALADDED:
323 case DIFFSTATE_THEIRSADDED:
324 case DIFFSTATE_YOURSADDED:
325 case DIFFSTATE_CONFLICTADDED:
326 nAddedLines++;
327 break;
328 case DIFFSTATE_IDENTICALREMOVED:
329 case DIFFSTATE_REMOVED:
330 case DIFFSTATE_THEIRSREMOVED:
331 case DIFFSTATE_YOURSREMOVED:
332 nRemovedLines++;
333 break;
334 case DIFFSTATE_CONFLICTED:
335 case DIFFSTATE_CONFLICTED_IGNORED:
336 nConflictedLines++;
337 break;
342 CString sBarText;
343 CString sTemp;
345 if (m_pwndStatusBar)
347 sBarText += CFileTextLines::GetEncodingName(m_texttype);
348 sBarText += sBarText.IsEmpty() ? L"" : L" ";
349 sBarText += GetEolName(m_lineendings);
350 sBarText += sBarText.IsEmpty() ? L"" : L" ";
352 if (sBarText.IsEmpty())
353 sBarText += L" / ";
356 if (nRemovedLines)
358 sTemp.Format(IDS_STATUSBAR_REMOVEDLINES, nRemovedLines);
359 if (!sBarText.IsEmpty())
360 sBarText += L" / ";
361 sBarText += sTemp;
363 if (nAddedLines)
365 sTemp.Format(IDS_STATUSBAR_ADDEDLINES, nAddedLines);
366 if (!sBarText.IsEmpty())
367 sBarText += L" / ";
368 sBarText += sTemp;
370 if (nConflictedLines)
372 sTemp.Format(IDS_STATUSBAR_CONFLICTEDLINES, nConflictedLines);
373 if (!sBarText.IsEmpty())
374 sBarText += L" / ";
375 sBarText += sTemp;
377 if (m_pwndStatusBar || m_pwndRibbonStatusBar)
379 if (m_pwndStatusBar)
381 UINT nID;
382 UINT nStyle;
383 int cxWidth;
384 if (m_nStatusBarID == ID_INDICATOR_BOTTOMVIEW)
386 sBarText.Format(IDS_STATUSBAR_CONFLICTS, nConflictedLines);
388 if (m_nStatusBarID == ID_INDICATOR_LEFTVIEW)
390 sTemp.LoadString(IDS_STATUSBAR_LEFTVIEW);
391 sBarText = sTemp+sBarText;
393 if (m_nStatusBarID == ID_INDICATOR_RIGHTVIEW)
395 sTemp.LoadString(IDS_STATUSBAR_RIGHTVIEW);
396 sBarText = sTemp+sBarText;
398 int nIndex = m_pwndStatusBar->CommandToIndex(m_nStatusBarID);
399 m_pwndStatusBar->GetPaneInfo(nIndex, nID, nStyle, cxWidth);
400 //calculate the width of the text
401 CDC * pDC = m_pwndStatusBar->GetDC();
402 if (pDC)
404 CSize size = pDC->GetTextExtent(sBarText);
405 m_pwndStatusBar->SetPaneInfo(nIndex, nID, nStyle, size.cx+2);
406 ReleaseDC(pDC);
408 m_pwndStatusBar->SetPaneText(nIndex, sBarText);
410 else if (m_pwndRibbonStatusBar)
412 if (!IsViewGood(m_pwndBottom))
413 m_pwndRibbonStatusBar->RemoveElement(ID_INDICATOR_BOTTOMVIEW);
414 if ((m_nStatusBarID == ID_INDICATOR_BOTTOMVIEW) && (IsViewGood(this)))
416 m_pwndRibbonStatusBar->RemoveElement(ID_INDICATOR_BOTTOMVIEW);
417 auto apBtnGroupBottom = std::make_unique<CMFCRibbonButtonsGroup>();
418 apBtnGroupBottom->SetID(ID_INDICATOR_BOTTOMVIEW);
419 apBtnGroupBottom->AddButton(new CMFCRibbonStatusBarPane(ID_SEPARATOR, CString(MAKEINTRESOURCE(IDS_STATUSBAR_BOTTOMVIEW)), TRUE));
420 CMFCRibbonButton * pButton = new CMFCRibbonButton(ID_INDICATOR_BOTTOMVIEWCOMBOENCODING, L"");
421 m_pMainFrame->FillEncodingButton(pButton, ID_INDICATOR_BOTTOMENCODINGSTART);
422 apBtnGroupBottom->AddButton(pButton);
423 pButton = new CMFCRibbonButton(ID_INDICATOR_BOTTOMVIEWCOMBOEOL, L"");
424 m_pMainFrame->FillEOLButton(pButton, ID_INDICATOR_BOTTOMEOLSTART);
425 apBtnGroupBottom->AddButton(pButton);
426 pButton = new CMFCRibbonButton(ID_INDICATOR_BOTTOMVIEWCOMBOTABMODE, L"");
427 m_pMainFrame->FillTabModeButton(pButton, ID_INDICATOR_BOTTOMTABMODESTART);
428 apBtnGroupBottom->AddButton(pButton);
429 apBtnGroupBottom->AddButton(new CMFCRibbonStatusBarPane(ID_INDICATOR_BOTTOMVIEW, L"", TRUE));
430 m_pwndRibbonStatusBar->AddExtendedElement(apBtnGroupBottom.release(), L"");
433 CMFCRibbonButtonsGroup * pGroup = DYNAMIC_DOWNCAST(CMFCRibbonButtonsGroup, m_pwndRibbonStatusBar->FindByID(m_nStatusBarID));
434 if (pGroup)
436 CMFCRibbonStatusBarPane* pPane = DYNAMIC_DOWNCAST(CMFCRibbonStatusBarPane, pGroup->GetButton(4));
437 if (pPane)
439 pPane->SetText(sBarText);
441 CMFCRibbonButton * pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pGroup->GetButton(1));
442 if (pButton)
444 pButton->SetText(CFileTextLines::GetEncodingName(m_texttype));
445 pButton->SetDescription(CFileTextLines::GetEncodingName(m_texttype));
447 pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pGroup->GetButton(2));
448 if (pButton)
450 pButton->SetText(GetEolName(m_lineendings));
451 pButton->SetDescription(GetEolName(m_lineendings));
453 pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pGroup->GetButton(3));
454 if (pButton)
456 pButton->SetText(GetTabModeString(m_nTabMode, m_nTabSize, m_bEditorConfigEnabled && m_bEditorConfigLoaded));
457 pButton->SetDescription(GetTabModeString(m_nTabMode, m_nTabSize, m_bEditorConfigEnabled && m_bEditorConfigLoaded));
460 m_pwndRibbonStatusBar->RecalcLayout();
461 m_pwndRibbonStatusBar->Invalidate();
466 BOOL CBaseView::PreCreateWindow(CREATESTRUCT& cs)
468 if (!CView::PreCreateWindow(cs))
469 return FALSE;
471 cs.dwExStyle |= WS_EX_CLIENTEDGE;
472 cs.style &= ~WS_BORDER;
473 cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
474 ::LoadCursor(nullptr, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1), nullptr);
476 CWnd *pParentWnd = CWnd::FromHandlePermanent(cs.hwndParent);
477 if (!pParentWnd || !pParentWnd->IsKindOf(RUNTIME_CLASS(CSplitterWnd)))
479 // View must always create its own scrollbars,
480 // if only it's not used within splitter
481 cs.style |= (WS_HSCROLL | WS_VSCROLL);
483 cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS);
484 return TRUE;
487 CFont* CBaseView::GetFont(BOOL bItalic /*= FALSE*/, BOOL bBold /*= FALSE*/)
489 int nIndex = 0;
490 if (bBold)
491 nIndex |= 1;
492 if (bItalic)
493 nIndex |= 2;
494 if (!m_apFonts[nIndex])
496 m_apFonts[nIndex] = new CFont;
497 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
498 m_lfBaseFont.lfWeight = bBold ? FW_BOLD : FW_NORMAL;
499 m_lfBaseFont.lfItalic = (BYTE) bItalic;
500 m_lfBaseFont.lfHeight = -CDPIAware::Instance().PointsToPixelsY((DWORD)CRegDWORD(L"Software\\TortoiseGitMerge\\LogFontSize", 10));
501 wcsncpy_s(m_lfBaseFont.lfFaceName, (LPCTSTR)(CString)CRegString(L"Software\\TortoiseGitMerge\\LogFontName", L"Consolas"), _countof(m_lfBaseFont.lfFaceName) - 1);
502 if (!m_apFonts[nIndex]->CreateFontIndirect(&m_lfBaseFont))
504 delete m_apFonts[nIndex];
505 m_apFonts[nIndex] = nullptr;
506 return CView::GetFont();
509 return m_apFonts[nIndex];
512 void CBaseView::CalcLineCharDim()
514 CDC *pDC = GetDC();
515 if (!pDC)
516 return;
517 CFont *pOldFont = pDC->SelectObject(GetFont());
518 const CSize szCharExt = pDC->GetTextExtent(L"X");
519 pDC->SelectObject(pOldFont);
520 ReleaseDC(pDC);
522 m_nLineHeight = szCharExt.cy;
523 if (m_nLineHeight <= 0)
524 m_nLineHeight = -1;
525 m_nCharWidth = szCharExt.cx;
526 if (m_nCharWidth <= 0)
527 m_nCharWidth = -1;
530 int CBaseView::GetScreenChars()
532 if (m_nScreenChars == -1)
534 CRect rect;
535 GetClientRect(&rect);
536 m_nScreenChars = (rect.Width() - GetMarginWidth() - GetSystemMetrics(SM_CXVSCROLL)) / GetCharWidth();
537 if (m_nScreenChars < 0)
538 m_nScreenChars = 0;
540 return m_nScreenChars;
543 int CBaseView::GetAllMinScreenChars() const
545 int nChars = INT_MAX;
546 if (IsLeftViewGood())
547 nChars = std::min<int>(nChars, m_pwndLeft->GetScreenChars());
548 if (IsRightViewGood())
549 nChars = std::min<int>(nChars, m_pwndRight->GetScreenChars());
550 if (IsBottomViewGood())
551 nChars = std::min<int>(nChars, m_pwndBottom->GetScreenChars());
552 return (nChars==INT_MAX) ? 0 : nChars;
555 int CBaseView::GetAllMaxLineLength() const
557 int nLength = 0;
558 if (IsLeftViewGood())
559 nLength = std::max<int>(nLength, m_pwndLeft->GetMaxLineLength());
560 if (IsRightViewGood())
561 nLength = std::max<int>(nLength, m_pwndRight->GetMaxLineLength());
562 if (IsBottomViewGood())
563 nLength = std::max<int>(nLength, m_pwndBottom->GetMaxLineLength());
564 return nLength;
567 int CBaseView::GetLineHeight()
569 if (m_nLineHeight == -1)
570 CalcLineCharDim();
571 if (m_nLineHeight <= 0)
572 return 1;
573 return m_nLineHeight;
576 int CBaseView::GetCharWidth()
578 if (m_nCharWidth == -1)
579 CalcLineCharDim();
580 if (m_nCharWidth <= 0)
581 return 1;
582 return m_nCharWidth;
585 int CBaseView::GetMaxLineLength()
587 if (m_nMaxLineLength == -1)
589 m_nMaxLineLength = 0;
590 int nLineCount = GetLineCount();
591 if (nLineCount == 1)
593 m_nMaxLineLength = GetLineLengthWithTabsConverted(0);
594 return m_nMaxLineLength;
596 for (int i=0; i<nLineCount; i++)
598 int nActualLength = GetLineLengthWithTabsConverted(i);
599 if (m_nMaxLineLength < nActualLength)
600 m_nMaxLineLength = nActualLength;
603 return m_nMaxLineLength;
606 int CBaseView::GetLineLengthWithTabsConverted(int index)
608 if (!m_pViewData)
609 return 0;
610 if (m_pViewData->GetCount() == 0)
611 return 0;
612 if ((int)m_Screen2View.size() <= index)
613 return 0;
614 CString sLine;
615 if (m_pMainFrame->m_bWrapLines)
616 sLine = GetLineChars(index);
617 else
619 int viewLine = GetViewLineForScreen(index);
620 sLine = m_pViewData->GetLine(viewLine);
622 int tabCount = 0;
623 wchar_t* pChar = (LPWSTR)(LPCWSTR)sLine;
624 auto nLineLength = sLine.GetLength();
625 for (int i = 0; i < nLineLength; ++i)
627 if (*pChar == '\t')
628 ++tabCount;
629 ++pChar;
631 // GetTabSize() - 1 because the tabs are already counted
632 nLineLength = nLineLength + (tabCount * (GetTabSize() - 1));
633 ASSERT(nLineLength >= 0);
634 return nLineLength;
637 int CBaseView::GetLineLength(int index)
639 if (!m_pViewData)
640 return 0;
641 if (m_pViewData->GetCount() == 0)
642 return 0;
643 if ((int)m_Screen2View.size() <= index)
644 return 0;
645 int viewLine = GetViewLineForScreen(index);
646 if (m_pMainFrame->m_bWrapLines)
648 int nLineLength = GetLineChars(index).GetLength();
649 ASSERT(nLineLength >= 0);
650 return nLineLength;
652 int nLineLength = m_pViewData->GetLine(viewLine).GetLength();
653 ASSERT(nLineLength >= 0);
654 return nLineLength;
657 int CBaseView::GetViewLineLength(int nViewLine) const
659 if (!m_pViewData)
660 return 0;
661 if (m_pViewData->GetCount() <= nViewLine)
662 return 0;
663 int nLineLength = m_pViewData->GetLine(nViewLine).GetLength();
664 ASSERT(nLineLength >= 0);
665 return nLineLength;
668 int CBaseView::GetLineCount() const
670 if (!m_pViewData)
671 return 1;
672 int nLineCount = (int)m_Screen2View.size();
673 ASSERT(nLineCount >= 0);
674 return nLineCount;
677 int CBaseView::GetSubLineOffset(int index)
679 return m_Screen2View.GetSubLineOffset(index);
682 CString CBaseView::GetViewLineChars(int nViewLine) const
684 if (!m_pViewData)
685 return 0;
686 if (m_pViewData->GetCount() <= nViewLine)
687 return 0;
688 return m_pViewData->GetLine(nViewLine);
691 CString CBaseView::GetLineChars(int index)
693 if (!m_pViewData)
694 return 0;
695 if (m_pViewData->GetCount() == 0)
696 return 0;
697 if ((int)m_Screen2View.size() <= index)
698 return 0;
699 int viewLine = GetViewLineForScreen(index);
700 if (m_pMainFrame->m_bWrapLines)
702 int subLine = GetSubLineOffset(index);
703 if (subLine >= 0)
705 if (subLine < CountMultiLines(viewLine))
707 return m_ScreenedViewLine[viewLine].SubLines[subLine];
709 return L"";
712 return m_pViewData->GetLine(viewLine);
715 void CBaseView::CheckOtherView()
717 if (m_bOtherDiffChecked)
718 return;
719 // find out what the 'other' file is
720 m_pOtherViewData = nullptr;
721 m_pOtherView = nullptr;
722 if (this == m_pwndLeft && IsRightViewGood())
724 m_pOtherViewData = m_pwndRight->m_pViewData;
725 m_pOtherView = m_pwndRight;
728 if (this == m_pwndRight && IsLeftViewGood())
730 m_pOtherViewData = m_pwndLeft->m_pViewData;
731 m_pOtherView = m_pwndLeft;
734 m_bOtherDiffChecked = true;
738 void CBaseView::GetWhitespaceBlock(CViewData *viewData, int nLineIndex, int & nStartBlock, int & nEndBlock)
740 enum { MAX_WHITESPACEBLOCK_SIZE = 8 };
741 ASSERT(viewData);
743 DiffStates origstate = viewData->GetState(nLineIndex);
745 // Go back and forward at most MAX_WHITESPACEBLOCK_SIZE lines to see where this block ends
746 nStartBlock = nLineIndex;
747 nEndBlock = nLineIndex;
748 while ((nStartBlock > 0) && (nStartBlock > (nLineIndex - MAX_WHITESPACEBLOCK_SIZE)))
750 DiffStates state = viewData->GetState(nStartBlock - 1);
751 if ((origstate == DIFFSTATE_EMPTY) && (state != DIFFSTATE_NORMAL))
752 origstate = state;
753 if ((origstate == state) || (state == DIFFSTATE_EMPTY))
754 nStartBlock--;
755 else
756 break;
758 while ((nEndBlock < (viewData->GetCount() - 1)) && (nEndBlock < (nLineIndex + MAX_WHITESPACEBLOCK_SIZE)))
760 DiffStates state = viewData->GetState(nEndBlock + 1);
761 if ((origstate == DIFFSTATE_EMPTY) && (state != DIFFSTATE_NORMAL))
762 origstate = state;
763 if ((origstate == state) || (state == DIFFSTATE_EMPTY))
764 nEndBlock++;
765 else
766 break;
770 CString CBaseView::GetWhitespaceString(CViewData *viewData, int nStartBlock, int nEndBlock)
772 enum { MAX_WHITESPACEBLOCK_SIZE = 8 };
774 int len = 0;
775 for (int i = nStartBlock; i <= nEndBlock; ++i)
776 len += viewData->GetLine(i).GetLength()+2;
778 CString block;
779 // do not check for whitespace blocks if the line is too long, because
780 // reserving a lot of memory here takes too much time (performance hog)
781 if (len > MAX_WHITESPACEBLOCK_SIZE*256)
782 return block;
783 block.Preallocate(len+1);
784 for (int i = nStartBlock; i <= nEndBlock; ++i)
786 block += viewData->GetLine(i);
787 block += m_Eols[viewData->GetLineEnding(i)];
789 return block;
792 bool CBaseView::IsBlockWhitespaceOnly(int nLineIndex, bool& bIdentical, int& blockstart, int& blockend)
794 if (!m_pViewData)
795 return false;
796 bIdentical = false;
797 CheckOtherView();
798 if (!m_pOtherViewData)
799 return false;
800 int viewLine = GetViewLineForScreen(nLineIndex);
801 if (
802 (m_pViewData->GetState(viewLine) == DIFFSTATE_NORMAL) &&
803 (m_pOtherViewData->GetLine(viewLine) == m_pViewData->GetLine(viewLine))
806 bIdentical = true;
807 return false;
809 // first check whether the line itself only has whitespace changes
810 CString mine = m_pViewData->GetLine(viewLine);
811 CString other = m_pOtherViewData->GetLine(min(viewLine, m_pOtherViewData->GetCount() - 1));
812 if (mine.IsEmpty() && other.IsEmpty())
814 bIdentical = true;
815 return false;
818 if (mine == other)
820 bIdentical = true;
821 return true;
823 FilterWhitespaces(mine, other);
824 if (mine == other)
825 return true;
827 int nStartBlock2, nEndBlock2;
828 GetWhitespaceBlock(m_pViewData, viewLine, blockstart, blockend);
829 GetWhitespaceBlock(m_pOtherViewData, min(viewLine, m_pOtherViewData->GetCount() - 1), nStartBlock2, nEndBlock2);
830 mine = GetWhitespaceString(m_pViewData, blockstart, blockend);
831 if (mine.IsEmpty())
832 bIdentical = false;
833 else
835 other = GetWhitespaceString(m_pOtherViewData, nStartBlock2, nEndBlock2);
836 bIdentical = mine == other;
837 FilterWhitespaces(mine, other);
840 return (!mine.IsEmpty()) && (mine == other);
843 bool CBaseView::IsViewLineHidden(int nViewLine)
845 return IsViewLineHidden(m_pViewData, nViewLine);
848 bool CBaseView::IsViewLineHidden(CViewData * pViewData, int nViewLine)
850 return m_pMainFrame->m_bCollapsed && (pViewData->GetHideState(nViewLine)!=HIDESTATE_SHOWN);
853 int CBaseView::GetLineNumber(int index) const
855 if (!m_pViewData)
856 return -1;
857 int viewLine = GetViewLineForScreen(index);
858 if (m_pViewData->GetLineNumber(viewLine)==DIFF_EMPTYLINENUMBER)
859 return -1;
860 return m_pViewData->GetLineNumber(viewLine);
863 int CBaseView::GetScreenLines()
865 if (m_nScreenLines == -1)
867 CRect rect;
868 GetClientRect(&rect);
869 SCROLLBARINFO sbi = { sizeof(sbi) };
870 if (GetScrollBarInfo(OBJID_HSCROLL, &sbi))
872 // only use the scroll bar size if the info is correct and the scrollbar is visible
873 // if anything isn't proper, assume the scrollbar has a size of zero
874 // and calculate the screen lines without it.
875 if (!(sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE) && !(sbi.rgstate[0] & STATE_SYSTEM_UNAVAILABLE))
877 int scrollBarHeight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top;
878 m_nScreenLines = (rect.Height() - HEADERHEIGHT - scrollBarHeight) / GetLineHeight();
879 if (m_nScreenLines < 0)
880 m_nScreenLines = 0;
881 return m_nScreenLines;
884 // if the scroll bar is not visible, unavailable or there was an error,
885 // assume the scroll bar height is zero and return the screen lines here.
886 // Of course, that means the cache (using the member variable) won't work
887 // and we fetch the scroll bar info every time. But in this case the overhead is necessary.
888 return (rect.Height() - HEADERHEIGHT) / GetLineHeight();
890 return m_nScreenLines;
893 int CBaseView::GetAllMinScreenLines() const
895 int nLines = INT_MAX;
896 if (IsLeftViewGood())
897 nLines = m_pwndLeft->GetScreenLines();
898 if (IsRightViewGood())
899 nLines = std::min<int>(nLines, m_pwndRight->GetScreenLines());
900 if (IsBottomViewGood())
901 nLines = std::min<int>(nLines, m_pwndBottom->GetScreenLines());
902 return (nLines == INT_MAX) || (nLines < 0) ? 0 : nLines;
905 int CBaseView::GetAllLineCount() const
907 int nLines = 0;
908 if (IsLeftViewGood())
909 nLines = m_pwndLeft->GetLineCount();
910 if (IsRightViewGood())
911 nLines = std::max<int>(nLines, m_pwndRight->GetLineCount());
912 if (IsBottomViewGood())
913 nLines = std::max<int>(nLines, m_pwndBottom->GetLineCount());
914 return nLines;
917 void CBaseView::RecalcAllVertScrollBars(BOOL bPositionOnly /*= FALSE*/)
919 if (IsLeftViewGood())
920 m_pwndLeft->RecalcVertScrollBar(bPositionOnly);
921 if (IsRightViewGood())
922 m_pwndRight->RecalcVertScrollBar(bPositionOnly);
923 if (IsBottomViewGood())
924 m_pwndBottom->RecalcVertScrollBar(bPositionOnly);
927 void CBaseView::RecalcVertScrollBar(BOOL bPositionOnly /*= FALSE*/)
929 SCROLLINFO si;
930 si.cbSize = sizeof(si);
931 if (bPositionOnly)
933 si.fMask = SIF_POS;
934 si.nPos = m_nTopLine;
936 else
938 EnableScrollBarCtrl(SB_VERT, TRUE);
939 if (GetAllMinScreenLines() >= GetAllLineCount() && m_nTopLine > 0)
941 m_nTopLine = 0;
942 Invalidate();
944 si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_POS | SIF_RANGE;
945 si.nMin = 0;
946 si.nMax = GetAllLineCount();
947 si.nPage = GetAllMinScreenLines();
948 si.nPos = m_nTopLine;
950 VERIFY(SetScrollInfo(SB_VERT, &si));
953 void CBaseView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
955 CView::OnVScroll(nSBCode, nPos, pScrollBar);
956 if (m_pwndLeft)
957 m_pwndLeft->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
958 if (m_pwndRight)
959 m_pwndRight->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
960 if (m_pwndBottom)
961 m_pwndBottom->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
962 if (m_pwndLocator)
963 m_pwndLocator->Invalidate();
966 void CBaseView::OnDoVScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/, CBaseView * master)
968 // Note we cannot use nPos because of its 16-bit nature
969 SCROLLINFO si;
970 si.cbSize = sizeof(si);
971 si.fMask = SIF_ALL;
972 VERIFY(master->GetScrollInfo(SB_VERT, &si));
974 int nPageLines = GetScreenLines();
975 int nLineCount = GetLineCount();
977 int nNewTopLine;
979 static LONG textwidth = 0;
980 static CString sFormat(MAKEINTRESOURCE(IDS_VIEWSCROLLTIPTEXT));
981 switch (nSBCode)
983 case SB_TOP:
984 nNewTopLine = 0;
985 break;
986 case SB_BOTTOM:
987 nNewTopLine = nLineCount - nPageLines + 1;
988 break;
989 case SB_LINEUP:
990 nNewTopLine = m_nTopLine - 1;
991 break;
992 case SB_LINEDOWN:
993 nNewTopLine = m_nTopLine + 1;
994 break;
995 case SB_PAGEUP:
996 nNewTopLine = m_nTopLine - si.nPage + 1;
997 break;
998 case SB_PAGEDOWN:
999 nNewTopLine = m_nTopLine + si.nPage - 1;
1000 break;
1001 case SB_THUMBPOSITION:
1002 m_ScrollTool.Clear();
1003 nNewTopLine = si.nTrackPos;
1004 textwidth = 0;
1005 break;
1006 case SB_THUMBTRACK:
1007 nNewTopLine = si.nTrackPos;
1008 if (GetFocus() == this)
1010 RECT thumbrect;
1011 GetClientRect(&thumbrect);
1012 ClientToScreen(&thumbrect);
1014 POINT thumbpoint;
1015 thumbpoint.x = thumbrect.right;
1016 thumbpoint.y = thumbrect.top + ((thumbrect.bottom-thumbrect.top)*si.nTrackPos)/(si.nMax-si.nMin);
1017 m_ScrollTool.Init(&thumbpoint);
1018 if (textwidth == 0)
1020 CString sTemp = sFormat;
1021 sTemp.Format(sFormat, m_nDigits, 10*m_nDigits-1);
1022 textwidth = m_ScrollTool.GetTextWidth(sTemp);
1024 thumbpoint.x -= textwidth;
1025 int line = GetLineNumber(nNewTopLine);
1026 if (line >= 0)
1027 m_ScrollTool.SetText(&thumbpoint, sFormat, m_nDigits, GetLineNumber(nNewTopLine)+1);
1028 else
1029 m_ScrollTool.SetText(&thumbpoint, m_sNoLineNr);
1031 break;
1032 default:
1033 return;
1036 if (nNewTopLine < 0)
1037 nNewTopLine = 0;
1038 if (nNewTopLine >= nLineCount)
1039 nNewTopLine = nLineCount - 1;
1040 ScrollToLine(nNewTopLine);
1043 void CBaseView::RecalcAllHorzScrollBars(BOOL bPositionOnly /*= FALSE*/)
1045 if (IsLeftViewGood())
1046 m_pwndLeft->RecalcHorzScrollBar(bPositionOnly);
1047 if (IsRightViewGood())
1048 m_pwndRight->RecalcHorzScrollBar(bPositionOnly);
1049 if (IsBottomViewGood())
1050 m_pwndBottom->RecalcHorzScrollBar(bPositionOnly);
1053 void CBaseView::RecalcHorzScrollBar(BOOL bPositionOnly /*= FALSE*/)
1055 SCROLLINFO si;
1056 si.cbSize = sizeof(si);
1057 if (bPositionOnly)
1059 si.fMask = SIF_POS;
1060 si.nPos = m_nOffsetChar;
1062 else
1064 EnableScrollBarCtrl(SB_HORZ, !m_pMainFrame->m_bWrapLines);
1065 if (!m_pMainFrame->m_bWrapLines)
1067 int minScreenChars = GetAllMinScreenChars();
1068 int maxLineLength = GetAllMaxLineLength();
1069 if (minScreenChars >= maxLineLength && m_nOffsetChar > 0)
1071 m_nOffsetChar = 0;
1072 Invalidate();
1074 si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_POS | SIF_RANGE;
1075 si.nMin = 0;
1076 si.nMax = m_pMainFrame->m_bWrapLines ? minScreenChars : maxLineLength;
1077 si.nMax += GetMarginWidth()/GetCharWidth();
1078 si.nPage = GetScreenChars();
1079 si.nPos = m_nOffsetChar;
1082 VERIFY(SetScrollInfo(SB_HORZ, &si));
1085 void CBaseView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1087 CView::OnHScroll(nSBCode, nPos, pScrollBar);
1088 if (m_pwndLeft)
1089 m_pwndLeft->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1090 if (m_pwndRight)
1091 m_pwndRight->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1092 if (m_pwndBottom)
1093 m_pwndBottom->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1094 if (m_pwndLocator)
1095 m_pwndLocator->Invalidate();
1098 void CBaseView::OnDoHScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/, CBaseView * master)
1100 SCROLLINFO si;
1101 si.cbSize = sizeof(si);
1102 si.fMask = SIF_ALL;
1103 VERIFY(master->GetScrollInfo(SB_HORZ, &si));
1105 int nPageChars = GetScreenChars();
1106 int nMaxLineLength = GetMaxLineLength();
1108 int nNewOffset;
1109 switch (nSBCode)
1111 case SB_LEFT:
1112 nNewOffset = 0;
1113 break;
1114 case SB_BOTTOM:
1115 nNewOffset = nMaxLineLength - nPageChars + 1;
1116 break;
1117 case SB_LINEUP:
1118 nNewOffset = m_nOffsetChar - 1;
1119 break;
1120 case SB_LINEDOWN:
1121 nNewOffset = m_nOffsetChar + 1;
1122 break;
1123 case SB_PAGEUP:
1124 nNewOffset = m_nOffsetChar - si.nPage + 1;
1125 break;
1126 case SB_PAGEDOWN:
1127 nNewOffset = m_nOffsetChar + si.nPage - 1;
1128 break;
1129 case SB_THUMBPOSITION:
1130 case SB_THUMBTRACK:
1131 nNewOffset = si.nTrackPos;
1132 break;
1133 default:
1134 return;
1137 if (nNewOffset >= nMaxLineLength)
1138 nNewOffset = nMaxLineLength - 1;
1139 if (nNewOffset < 0)
1140 nNewOffset = 0;
1141 ScrollToChar(nNewOffset, TRUE);
1144 void CBaseView::ScrollToChar(int nNewOffsetChar, BOOL bTrackScrollBar /*= TRUE*/)
1146 if (m_nOffsetChar != nNewOffsetChar)
1148 int nScrollChars = m_nOffsetChar - nNewOffsetChar;
1149 m_nOffsetChar = nNewOffsetChar;
1150 CRect rcScroll;
1151 GetClientRect(&rcScroll);
1152 rcScroll.left += GetMarginWidth();
1153 rcScroll.top += GetLineHeight()+HEADERHEIGHT;
1154 ScrollWindow(nScrollChars * GetCharWidth(), 0, &rcScroll, &rcScroll);
1155 // update the view header
1156 rcScroll.left = 0;
1157 rcScroll.top = 0;
1158 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
1159 InvalidateRect(&rcScroll, FALSE);
1160 UpdateWindow();
1161 if (bTrackScrollBar)
1162 RecalcHorzScrollBar(TRUE);
1163 UpdateCaret();
1164 if (m_pwndLineDiffBar)
1165 m_pwndLineDiffBar->Invalidate();
1169 void CBaseView::ScrollAllToChar(int nNewOffsetChar, BOOL bTrackScrollBar /* = TRUE */)
1171 if (m_pwndLeft)
1172 m_pwndLeft->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1173 if (m_pwndRight)
1174 m_pwndRight->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1175 if (m_pwndBottom)
1176 m_pwndBottom->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1179 void CBaseView::ScrollAllSide(int delta)
1181 int nNewOffset = m_nOffsetChar;
1182 nNewOffset += delta;
1183 int nMaxLineLength = GetMaxLineLength();
1184 if (nNewOffset >= nMaxLineLength)
1185 nNewOffset = nMaxLineLength - 1;
1186 if (nNewOffset < 0)
1187 nNewOffset = 0;
1188 ScrollAllToChar(nNewOffset, TRUE);
1189 if (m_pwndLineDiffBar)
1190 m_pwndLineDiffBar->Invalidate();
1191 UpdateCaret();
1194 void CBaseView::ScrollSide(int delta)
1196 int nNewOffset = m_nOffsetChar;
1197 nNewOffset += delta;
1198 int nMaxLineLength = GetMaxLineLength();
1199 if (nNewOffset >= nMaxLineLength)
1200 nNewOffset = nMaxLineLength - 1;
1201 if (nNewOffset < 0)
1202 nNewOffset = 0;
1203 ScrollToChar(nNewOffset, TRUE);
1204 if (m_pwndLineDiffBar)
1205 m_pwndLineDiffBar->Invalidate();
1206 UpdateCaret();
1209 void CBaseView::ScrollVertical(short zDelta)
1211 const int nLineCount = GetLineCount();
1212 int nTopLine = m_nTopLine;
1213 nTopLine -= (zDelta/30);
1214 if (nTopLine < 0)
1215 nTopLine = 0;
1216 if (nTopLine >= nLineCount)
1217 nTopLine = nLineCount - 1;
1218 ScrollToLine(nTopLine, TRUE);
1221 void CBaseView::ScrollToLine(int nNewTopLine, BOOL bTrackScrollBar /*= TRUE*/)
1223 if (m_nTopLine != nNewTopLine)
1225 if (nNewTopLine < 0)
1226 nNewTopLine = 0;
1228 int nScrollLines = m_nTopLine - nNewTopLine;
1230 m_nTopLine = nNewTopLine;
1231 CRect rcScroll;
1232 GetClientRect(&rcScroll);
1233 rcScroll.top += GetLineHeight()+HEADERHEIGHT;
1234 ScrollWindow(0, nScrollLines * GetLineHeight(), &rcScroll, &rcScroll);
1235 UpdateWindow();
1236 if (bTrackScrollBar)
1237 RecalcVertScrollBar(TRUE);
1238 UpdateCaret();
1243 void CBaseView::DrawMargin(CDC *pdc, const CRect &rect, int nLineIndex)
1245 pdc->FillSolidRect(rect, ::GetSysColor(COLOR_SCROLLBAR));
1247 if ((nLineIndex >= 0)&&(m_pViewData)&&(m_pViewData->GetCount()))
1249 int nViewLine = GetViewLineForScreen(nLineIndex);
1250 HICON icon = nullptr;
1251 ASSERT(nViewLine<(int)m_ScreenedViewLine.size());
1252 TScreenedViewLine::EIcon eIcon = m_ScreenedViewLine[nViewLine].eIcon;
1253 if (eIcon==TScreenedViewLine::ICN_UNKNOWN)
1255 DiffStates state = m_pViewData->GetState(nViewLine);
1256 switch (state)
1258 case DIFFSTATE_ADDED:
1259 case DIFFSTATE_THEIRSADDED:
1260 case DIFFSTATE_YOURSADDED:
1261 case DIFFSTATE_IDENTICALADDED:
1262 case DIFFSTATE_CONFLICTADDED:
1263 eIcon = TScreenedViewLine::ICN_ADD;
1264 break;
1265 case DIFFSTATE_REMOVED:
1266 case DIFFSTATE_THEIRSREMOVED:
1267 case DIFFSTATE_YOURSREMOVED:
1268 case DIFFSTATE_IDENTICALREMOVED:
1269 eIcon = TScreenedViewLine::ICN_REMOVED;
1270 break;
1271 case DIFFSTATE_CONFLICTED:
1272 eIcon = TScreenedViewLine::ICN_CONFLICT;
1273 break;
1274 case DIFFSTATE_CONFLICTED_IGNORED:
1275 eIcon = TScreenedViewLine::ICN_CONFLICTIGNORED;
1276 break;
1277 case DIFFSTATE_EDITED:
1278 eIcon = TScreenedViewLine::ICN_EDIT;
1279 break;
1280 default:
1281 break;
1283 bool bIdentical = false;
1284 int blockstart = -1;
1285 int blockend = -1;
1286 if ((state != DIFFSTATE_EDITED)&&(IsBlockWhitespaceOnly(nLineIndex, bIdentical, blockstart, blockend)))
1288 if (bIdentical)
1289 eIcon = TScreenedViewLine::ICN_SAME;
1290 else
1291 eIcon = TScreenedViewLine::ICN_WHITESPACEDIFF;
1292 if (((blockstart >= 0) && (blockend >= 0)) && (blockstart < blockend))
1294 if (nViewLine > blockstart)
1295 Invalidate(); // redraw the upper icons since they're now changing
1296 while (blockstart <= blockend)
1297 m_ScreenedViewLine[blockstart++].eIcon = eIcon;
1300 if (m_pViewData->GetMovedIndex(nViewLine) >= 0)
1301 eIcon = TScreenedViewLine::ICN_MOVED;
1302 if (m_pViewData->GetMarked(nViewLine))
1303 eIcon = TScreenedViewLine::ICN_MARKED;
1304 m_ScreenedViewLine[nViewLine].eIcon = eIcon;
1306 switch (eIcon)
1308 case TScreenedViewLine::ICN_UNKNOWN:
1309 case TScreenedViewLine::ICN_NONE:
1310 break;
1311 case TScreenedViewLine::ICN_SAME:
1312 icon = m_hEqualIcon;
1313 break;
1314 case TScreenedViewLine::ICN_EDIT:
1315 icon = m_hEditedIcon;
1316 break;
1317 case TScreenedViewLine::ICN_WHITESPACEDIFF:
1318 icon = m_hWhitespaceBlockIcon;
1319 break;
1320 case TScreenedViewLine::ICN_ADD:
1321 icon = m_hAddedIcon;
1322 break;
1323 case TScreenedViewLine::ICN_CONFLICT:
1324 icon = m_hConflictedIcon;
1325 break;
1326 case TScreenedViewLine::ICN_CONFLICTIGNORED:
1327 icon = m_hConflictedIgnoredIcon;
1328 break;
1329 case TScreenedViewLine::ICN_REMOVED:
1330 icon = m_hRemovedIcon;
1331 break;
1332 case TScreenedViewLine::ICN_MOVED:
1333 icon = m_hMovedIcon;
1334 break;
1335 case TScreenedViewLine::ICN_MARKED:
1336 icon = m_hMarkedIcon;
1337 break;
1340 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1341 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1342 if (icon)
1344 ::DrawIconEx(pdc->m_hDC, rect.left + 2, rect.top + (rect.Height() - iconHeight) / 2, icon, iconWidth, iconHeight, 0, nullptr, DI_NORMAL);
1346 if ((m_bViewLinenumbers)&&(m_nDigits))
1348 int nSubLine = GetSubLineOffset(nLineIndex);
1349 bool bIsFirstSubline = (nSubLine == 0) || (nSubLine == -1);
1350 CString sLinenumber;
1351 if (bIsFirstSubline)
1353 CString sLinenumberFormat;
1354 int nLineNumber = GetLineNumber(nLineIndex);
1355 if (IsViewLineHidden(GetViewLineForScreen(nLineIndex)))
1357 // TODO: do not show if there is no number hidden
1358 // TODO: show number if there is only one
1359 sLinenumberFormat.Format(L"%%%ds", m_nDigits);
1360 sLinenumber.Format(sLinenumberFormat, (m_nDigits>1) ? L"↕⁞" : L"⁞"); // alternative …
1362 else if (nLineNumber >= 0)
1364 sLinenumberFormat.Format(L"%%%dd", m_nDigits);
1365 sLinenumber.Format(sLinenumberFormat, nLineNumber+1);
1367 else if (m_pMainFrame->m_bWrapLines)
1369 sLinenumberFormat.Format(L"%%%ds", m_nDigits);
1370 sLinenumber.Format(sLinenumberFormat, L"·");
1372 if (!sLinenumber.IsEmpty())
1374 pdc->SetBkColor(::GetSysColor(COLOR_SCROLLBAR));
1375 pdc->SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
1377 pdc->SelectObject(GetFont());
1378 pdc->ExtTextOut(rect.left + iconWidth + 2, rect.top, ETO_CLIPPED, &rect, sLinenumber, nullptr);
1385 int CBaseView::GetMarginWidth()
1387 int marginWidth = GetSystemMetrics(SM_CXSMICON) + 2 + 2;
1389 if ((m_bViewLinenumbers)&&(m_pViewData)&&(m_pViewData->GetCount()))
1391 if (m_nDigits <= 0)
1393 int nLength = (int)m_pViewData->GetCount();
1394 // find out how many digits are needed to show the highest line number
1395 CString sMax;
1396 sMax.Format(L"%d", nLength);
1397 m_nDigits = sMax.GetLength();
1399 int nWidth = GetCharWidth();
1400 marginWidth += (m_nDigits * nWidth) + 2;
1403 return marginWidth;
1406 void CBaseView::DrawHeader(CDC *pdc, const CRect &rect)
1408 CRect textrect(rect.left, rect.top, rect.Width(), GetLineHeight()+HEADERHEIGHT);
1409 COLORREF crBk, crFg;
1410 if (IsBottomViewGood())
1412 CDiffColors::GetInstance().GetColors(DIFFSTATE_NORMAL, crBk, crFg);
1413 crBk = ::GetSysColor(COLOR_SCROLLBAR);
1415 else
1417 DiffStates state = DIFFSTATE_REMOVED;
1418 if (this == m_pwndRight)
1420 state = DIFFSTATE_ADDED;
1422 CDiffColors::GetInstance().GetColors(state, crBk, crFg);
1424 pdc->SetBkColor(crBk);
1425 pdc->FillSolidRect(textrect, crBk);
1427 pdc->SetTextColor(crFg);
1429 pdc->SelectObject(GetFont(FALSE, TRUE));
1431 CString sViewTitle;
1432 if (IsModified())
1434 sViewTitle = L"* " + m_sWindowName;
1436 else
1438 sViewTitle = m_sWindowName;
1440 int nStringLength = (GetCharWidth()*m_sWindowName.GetLength());
1441 if (nStringLength > rect.Width())
1443 int offset = std::min<int>(m_nOffsetChar, (nStringLength-rect.Width())/GetCharWidth()+1);
1444 sViewTitle = m_sWindowName.Mid(offset);
1446 pdc->ExtTextOut(std::max<int>(rect.left + (rect.Width()-nStringLength)/2, 1),
1447 rect.top + (HEADERHEIGHT / 2), ETO_CLIPPED, textrect, sViewTitle, nullptr);
1448 if (this->GetFocus() == this)
1449 pdc->DrawEdge(textrect, EDGE_BUMP, BF_RECT);
1450 else
1451 pdc->DrawEdge(textrect, EDGE_ETCHED, BF_RECT);
1454 void CBaseView::OnDraw(CDC * pDC)
1456 CRect rcClient;
1457 GetClientRect(rcClient);
1459 int nLineCount = GetLineCount();
1460 int nLineHeight = GetLineHeight();
1462 CDC cacheDC;
1463 VERIFY(cacheDC.CreateCompatibleDC(pDC));
1464 if (!m_pCacheBitmap)
1466 m_pCacheBitmap = new CBitmap;
1467 VERIFY(m_pCacheBitmap->CreateCompatibleBitmap(pDC, rcClient.Width(), nLineHeight));
1469 CBitmap *pOldBitmap = cacheDC.SelectObject(m_pCacheBitmap);
1471 DrawHeader(pDC, rcClient);
1473 CRect rcLine;
1474 rcLine = rcClient;
1475 rcLine.top += nLineHeight+HEADERHEIGHT;
1476 rcLine.bottom = rcLine.top + nLineHeight;
1477 CRect rcCacheMargin(0, 0, GetMarginWidth(), nLineHeight);
1478 CRect rcCacheLine(GetMarginWidth(), 0, rcLine.Width(), nLineHeight);
1480 int nCurrentLine = m_nTopLine;
1481 bool bBeyondFileLineCached = false;
1482 while (rcLine.top < rcClient.bottom)
1484 if (nCurrentLine < nLineCount)
1486 DrawMargin(&cacheDC, rcCacheMargin, nCurrentLine);
1487 DrawSingleLine(&cacheDC, rcCacheLine, nCurrentLine);
1488 bBeyondFileLineCached = false;
1490 else if (!bBeyondFileLineCached)
1492 DrawMargin(&cacheDC, rcCacheMargin, -1);
1493 DrawSingleLine(&cacheDC, rcCacheLine, -1);
1494 bBeyondFileLineCached = true;
1497 VERIFY(pDC->BitBlt(rcLine.left, rcLine.top, rcLine.Width(), rcLine.Height(), &cacheDC, 0, 0, SRCCOPY));
1499 nCurrentLine ++;
1500 rcLine.OffsetRect(0, nLineHeight);
1503 cacheDC.SelectObject(pOldBitmap);
1504 cacheDC.DeleteDC();
1507 bool CBaseView::IsStateConflicted(DiffStates state)
1509 switch (state)
1511 case DIFFSTATE_CONFLICTED:
1512 case DIFFSTATE_CONFLICTED_IGNORED:
1513 case DIFFSTATE_CONFLICTEMPTY:
1514 case DIFFSTATE_CONFLICTADDED:
1515 return true;
1517 return false;
1520 bool CBaseView::IsStateEmpty(DiffStates state)
1522 switch (state)
1524 case DIFFSTATE_CONFLICTEMPTY:
1525 case DIFFSTATE_UNKNOWN:
1526 case DIFFSTATE_EMPTY:
1527 return true;
1529 return false;
1532 bool CBaseView::IsStateRemoved(DiffStates state)
1534 switch (state)
1536 case DIFFSTATE_REMOVED:
1537 case DIFFSTATE_THEIRSREMOVED:
1538 case DIFFSTATE_YOURSREMOVED:
1539 case DIFFSTATE_IDENTICALREMOVED:
1540 return true;
1542 return false;
1545 DiffStates CBaseView::ResolveState(DiffStates state)
1547 if (IsStateConflicted(state))
1549 if (state == DIFFSTATE_CONFLICTEMPTY)
1550 return DIFFSTATE_CONFLICTRESOLVEDEMPTY;
1551 else
1552 return DIFFSTATE_CONFLICTRESOLVED;
1554 return state;
1558 bool CBaseView::IsLineEmpty(int nLineIndex)
1560 if (m_pViewData == 0)
1561 return FALSE;
1562 int nViewLine = GetViewLineForScreen(nLineIndex);
1563 return IsViewLineEmpty(nViewLine);
1566 bool CBaseView::IsViewLineEmpty(int nViewLine)
1568 if (m_pViewData == 0)
1569 return FALSE;
1570 const DiffStates state = m_pViewData->GetState(nViewLine);
1571 return IsStateEmpty(state);
1574 bool CBaseView::IsLineRemoved(int nLineIndex)
1576 if (m_pViewData == 0)
1577 return FALSE;
1578 int nViewLine = GetViewLineForScreen(nLineIndex);
1579 return IsViewLineRemoved(nViewLine);
1582 bool CBaseView::IsViewLineRemoved(int nViewLine)
1584 if (m_pViewData == 0)
1585 return FALSE;
1586 const DiffStates state = m_pViewData->GetState(nViewLine);
1587 return IsStateRemoved(state);
1590 bool CBaseView::IsViewLineConflicted(int nLineIndex)
1592 if (m_pViewData == 0)
1593 return false;
1594 const DiffStates state = m_pViewData->GetState(nLineIndex);
1595 return IsStateConflicted(state);
1598 COLORREF CBaseView::InlineDiffColor(int nLineIndex)
1600 return IsLineRemoved(nLineIndex) ? m_InlineRemovedBk : m_InlineAddedBk;
1603 COLORREF CBaseView::InlineViewLineDiffColor(int nViewLine)
1605 return IsViewLineRemoved(nViewLine) ? m_InlineRemovedBk : m_InlineAddedBk;
1608 void CBaseView::DrawLineEnding(CDC *pDC, const CRect &rc, int nLineIndex, const CPoint& origin)
1610 if (origin.x < (rc.left - GetCharWidth() +1))
1611 return;
1612 if (!(m_bViewWhitespace && m_pViewData && (nLineIndex >= 0) && (nLineIndex < GetLineCount())))
1613 return;
1614 int viewLine = GetViewLineForScreen(nLineIndex);
1615 EOL ending = m_pViewData->GetLineEnding(viewLine);
1616 if (m_bIconLFs)
1618 HICON hEndingIcon = nullptr;
1619 switch (ending)
1621 case EOL_CR: hEndingIcon = m_hLineEndingCR; break;
1622 case EOL_CRLF: hEndingIcon = m_hLineEndingCRLF; break;
1623 case EOL_LF: hEndingIcon = m_hLineEndingLF; break;
1624 default: return;
1626 // If EOL style has changed, color end-of-line markers as inline differences.
1628 m_bShowInlineDiff && m_pOtherViewData &&
1629 (viewLine < m_pOtherViewData->GetCount()) &&
1630 (ending != EOL_NOENDING) &&
1631 (ending != m_pOtherViewData->GetLineEnding(viewLine) &&
1632 (m_pOtherViewData->GetLineEnding(viewLine) != EOL_NOENDING))
1635 pDC->FillSolidRect(origin.x, origin.y, rc.Height(), rc.Height(), InlineDiffColor(nLineIndex));
1638 DrawIconEx(pDC->GetSafeHdc(), origin.x, origin.y, hEndingIcon, rc.Height(), rc.Height(), 0, nullptr, DI_NORMAL);
1640 else
1642 CPen pen(PS_SOLID, 0, m_WhiteSpaceFg);
1643 CPen * oldpen = pDC->SelectObject(&pen);
1644 int yMiddle = origin.y + rc.Height()/2;
1645 int xMiddle = origin.x+GetCharWidth()/2;
1646 bool bMultiline = false;
1647 if (((int)m_Screen2View.size() > nLineIndex+1) && (GetViewLineForScreen(nLineIndex+1) == viewLine))
1649 if (GetLineLength(nLineIndex+1))
1651 // multiline
1652 bMultiline = true;
1653 pDC->MoveTo(origin.x, yMiddle - CDPIAware::Instance().ScaleY(2));
1654 pDC->LineTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), yMiddle - CDPIAware::Instance().ScaleY(2));
1655 pDC->LineTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), yMiddle + CDPIAware::Instance().ScaleY(2));
1656 pDC->LineTo(origin.x, yMiddle + CDPIAware::Instance().ScaleY(2));
1658 else if (GetLineLength(nLineIndex) == 0)
1659 bMultiline = true;
1661 else if ((nLineIndex > 0) && (GetViewLineForScreen(nLineIndex-1) == viewLine) && (GetLineLength(nLineIndex) == 0))
1662 bMultiline = true;
1664 if (!bMultiline)
1666 switch (ending)
1668 case EOL_AUTOLINE:
1669 case EOL_CRLF:
1670 // arrow from top to middle+2, then left
1671 pDC->MoveTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), rc.top + CDPIAware::Instance().ScaleY(1));
1672 pDC->LineTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), yMiddle);
1673 case EOL_CR:
1674 // arrow from right to left
1675 pDC->MoveTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), yMiddle);
1676 pDC->LineTo(origin.x, yMiddle);
1677 pDC->LineTo(origin.x + CDPIAware::Instance().ScaleX(4), yMiddle + CDPIAware::Instance().ScaleY(4));
1678 pDC->MoveTo(origin.x, yMiddle);
1679 pDC->LineTo(origin.x + CDPIAware::Instance().ScaleX(4), yMiddle - CDPIAware::Instance().ScaleY(4));
1680 break;
1681 case EOL_LFCR:
1682 // from right-upper to left then down
1683 pDC->MoveTo(origin.x + GetCharWidth() - CDPIAware::Instance().ScaleX(1), yMiddle - CDPIAware::Instance().ScaleY(2));
1684 pDC->LineTo(xMiddle, yMiddle - CDPIAware::Instance().ScaleY(2));
1685 pDC->LineTo(xMiddle, rc.bottom - CDPIAware::Instance().ScaleY(1));
1686 pDC->LineTo(xMiddle + CDPIAware::Instance().ScaleX(4), rc.bottom - CDPIAware::Instance().ScaleY(5));
1687 pDC->MoveTo(xMiddle, rc.bottom - CDPIAware::Instance().ScaleY(1));
1688 pDC->LineTo(xMiddle - CDPIAware::Instance().ScaleX(4), rc.bottom - CDPIAware::Instance().ScaleY(5));
1689 break;
1690 case EOL_LF:
1691 // arrow from top to bottom
1692 pDC->MoveTo(xMiddle, rc.top);
1693 pDC->LineTo(xMiddle, rc.bottom - CDPIAware::Instance().ScaleY(1));
1694 pDC->LineTo(xMiddle + CDPIAware::Instance().ScaleX(4), rc.bottom - CDPIAware::Instance().ScaleY(5));
1695 pDC->MoveTo(xMiddle, rc.bottom - CDPIAware::Instance().ScaleY(1));
1696 pDC->LineTo(xMiddle - CDPIAware::Instance().ScaleX(4), rc.bottom - CDPIAware::Instance().ScaleY(5));
1697 break;
1698 case EOL_FF: // Form Feed, U+000C
1699 case EOL_NEL: // Next Line, U+0085
1700 case EOL_LS: // Line Separator, U+2028
1701 case EOL_PS: // Paragraph Separator, U+2029
1702 // draw a horizontal line at the bottom of this line
1703 pDC->FillSolidRect(rc.left, rc.bottom-1, rc.right, rc.bottom, GetSysColor(COLOR_WINDOWTEXT));
1704 pDC->MoveTo(origin.x+GetCharWidth()-1, rc.bottom-GetCharWidth()-2);
1705 pDC->LineTo(origin.x, rc.bottom-2);
1706 pDC->LineTo(origin.x+5, rc.bottom-2);
1707 pDC->MoveTo(origin.x, rc.bottom-2);
1708 pDC->LineTo(origin.x+1, rc.bottom-6);
1709 break;
1710 default: // other EOLs
1711 // arrow from top right to bottom left
1712 pDC->MoveTo(origin.x+GetCharWidth()-1, rc.bottom-GetCharWidth());
1713 pDC->LineTo(origin.x, rc.bottom-1);
1714 pDC->LineTo(origin.x+5, rc.bottom-2);
1715 pDC->MoveTo(origin.x, rc.bottom-1);
1716 pDC->LineTo(origin.x+1, rc.bottom-6);
1717 break;
1718 case EOL_NOENDING:
1719 break;
1722 pDC->SelectObject(oldpen);
1726 void CBaseView::DrawBlockLine(CDC *pDC, const CRect &rc, int nLineIndex)
1728 if (!m_bShowSelection)
1729 return;
1731 int nSelBlockStart;
1732 int nSelBlockEnd;
1733 if (!GetViewSelection(nSelBlockStart, nSelBlockEnd))
1734 return;
1736 const int THICKNESS = 2;
1737 COLORREF rectcol = GetSysColor(m_bFocused ? COLOR_WINDOWTEXT : COLOR_GRAYTEXT);
1739 int nViewLineIndex = GetViewLineForScreen(nLineIndex);
1740 int nSubLine = GetSubLineOffset(nLineIndex);
1741 bool bFirstLineOfViewLine = (nSubLine==0 || nSubLine==-1);
1742 if ((nViewLineIndex == nSelBlockStart) && bFirstLineOfViewLine)
1744 pDC->FillSolidRect(rc.left, rc.top, rc.Width(), THICKNESS, rectcol);
1747 bool bLastLineOfViewLine = (nLineIndex+1 == m_Screen2View.size()) || (GetViewLineForScreen(nLineIndex) != GetViewLineForScreen(nLineIndex+1));
1748 if ((nViewLineIndex == nSelBlockEnd) && bLastLineOfViewLine)
1750 pDC->FillSolidRect(rc.left, rc.bottom - THICKNESS, rc.Width(), THICKNESS, rectcol);
1754 void CBaseView::DrawTextLine(
1755 CDC * pDC, const CRect &rc, int nLineIndex, POINT& coords)
1757 ASSERT(nLineIndex < GetLineCount());
1758 int nViewLine = GetViewLineForScreen(nLineIndex);
1759 ASSERT(m_pViewData && (nViewLine < m_pViewData->GetCount()));
1761 LineColors lineCols = GetLineColors(nViewLine);
1763 CString sViewLine = GetViewLineChars(nViewLine);
1764 // mark selection
1765 if (m_bShowSelection && HasTextSelection())
1767 // has this line selection ?
1768 if ((m_ptSelectionViewPosStart.y <= nViewLine) && (nViewLine <= m_ptSelectionViewPosEnd.y))
1770 int nViewLineLength = sViewLine.GetLength();
1772 // first suppose the whole line is selected
1773 int selectedStart = 0;
1774 int selectedEnd = nViewLineLength;
1776 // the view line is partially selected
1777 if (m_ptSelectionViewPosStart.y == nViewLine)
1779 selectedStart = m_ptSelectionViewPosStart.x;
1782 if (m_ptSelectionViewPosEnd.y == nViewLine)
1784 selectedEnd = m_ptSelectionViewPosEnd.x;
1786 // apply selection coloring
1787 // First enforce start and end point
1788 lineCols.SplitBlock(selectedStart);
1789 lineCols.SplitBlock(selectedEnd);
1790 // change color of affected parts
1791 long intenseColorScale = m_bFocused ? 70 : 30;
1792 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(selectedStart);
1793 for ( ; it != lineCols.end() && it->first < selectedEnd; ++it)
1795 auto& second = it->second;
1796 COLORREF crBk = CAppUtils::IntenseColor(intenseColorScale, second.background);
1797 if (second.shot == second.background)
1798 second.shot = crBk;
1799 second.background = crBk;
1800 second.text = CAppUtils::IntenseColor(intenseColorScale, second.text);
1805 // TODO: remove duplicate from selection and mark
1806 if (!m_sMarkedWord.IsEmpty())
1808 int nMarkLength = m_sMarkedWord.GetLength();
1809 //int nViewLineLength = sViewLine.GetLength();
1810 const TCHAR * text = sViewLine;
1811 const TCHAR * findText = text;
1812 while ((findText = wcsstr(findText, (LPCTSTR)m_sMarkedWord))!=0)
1814 int nMarkStart = static_cast<int>(findText - text);
1815 int nMarkEnd = nMarkStart + nMarkLength;
1816 findText += nMarkLength;
1817 ECharGroup eLeft = GetCharGroup(sViewLine, nMarkStart - 1);
1818 ECharGroup eStart = GetCharGroup(sViewLine, nMarkStart);
1819 if (eLeft == eStart)
1820 continue;
1821 ECharGroup eRight = GetCharGroup(sViewLine, nMarkEnd);
1822 ECharGroup eEnd = GetCharGroup(sViewLine, nMarkEnd - 1);
1823 if (eRight == eEnd)
1824 continue;
1826 // First enforce start and end point
1827 lineCols.SplitBlock(nMarkStart);
1828 lineCols.SplitBlock(nMarkEnd);
1829 // change color of affected parts
1830 const long int nIntenseColorScale = 200;
1831 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(nMarkStart);
1832 for ( ; it != lineCols.end() && it->first < nMarkEnd; ++it)
1834 auto& second = it->second;
1835 COLORREF crBk = CAppUtils::IntenseColor(nIntenseColorScale, second.background);
1836 if (second.shot == second.background)
1837 second.shot = crBk;
1838 second.background = crBk;
1839 second.text = CAppUtils::IntenseColor(nIntenseColorScale, second.text);
1843 if (!m_sFindText.IsEmpty())
1845 int nMarkStart = 0;
1846 int nMarkEnd = 0;
1847 int nStringPos = nMarkStart;
1848 CString searchLine = sViewLine;
1849 if (!m_bMatchCase)
1850 searchLine.MakeLower();
1851 while (StringFound(searchLine, SearchNext, nMarkStart, nMarkEnd)!=0)
1853 // First enforce start and end point
1854 lineCols.SplitBlock(nMarkStart+nStringPos);
1855 lineCols.SplitBlock(nMarkEnd+nStringPos);
1856 // change color of affected parts
1857 const long int nIntenseColorScale = 30;
1858 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(nMarkStart+nStringPos);
1859 for ( ; it != lineCols.end() && it->first < nMarkEnd; ++it)
1861 auto& second = it->second;
1862 COLORREF crBk = CAppUtils::IntenseColor(nIntenseColorScale, second.background);
1863 if (second.shot == second.background)
1864 second.shot = crBk;
1865 second.background = crBk;
1866 second.text = CAppUtils::IntenseColor(nIntenseColorScale, second.text);
1868 searchLine = searchLine.Mid(nMarkEnd);
1869 nStringPos = nMarkEnd;
1870 nMarkStart = 0;
1871 nMarkEnd = 0;
1875 // @ this point we may cache data for next line which may be same in wrapped mode
1877 int nTextOffset = 0;
1878 int nSubline = GetSubLineOffset(nLineIndex);
1879 for (int n=0; n<nSubline; n++)
1881 CString sLine = m_ScreenedViewLine[nViewLine].SubLines[n];
1882 nTextOffset += sLine.GetLength();
1885 CString sLine = GetLineChars(nLineIndex);
1886 int nLineLength = sLine.GetLength();
1887 CString sLineExp = ExpandChars(sLine);
1888 LPCTSTR textExp = sLineExp;
1889 //int nLineLengthExp = sLineExp.GetLength();
1890 int nStartExp = 0;
1891 int nLeft = coords.x;
1892 for (std::map<int, linecolors_t>::const_iterator itStart = lineCols.begin(); itStart != lineCols.end(); ++itStart)
1894 std::map<int, linecolors_t>::const_iterator itEnd = itStart;
1895 ++itEnd;
1896 int nStart = std::max<int>(0, itStart->first - nTextOffset);
1897 int nEnd = nLineLength;
1898 if (itEnd != lineCols.end())
1900 nEnd = std::min<int>(nEnd, itEnd->first - nTextOffset);
1902 int nBlockLength = nEnd - nStart;
1903 if (nBlockLength > 0 && nEnd>=0)
1905 auto& second = itStart->second;
1906 pDC->SetBkColor(second.background);
1907 pDC->SetTextColor(second.text);
1908 int nEndExp = CountExpandedChars(sLine, nEnd);
1909 int nTextLength = nEndExp - nStartExp;
1910 LPCTSTR p_zBlockText = textExp + nStartExp;
1911 SIZE Size;
1912 GetTextExtentPoint32(pDC->GetSafeHdc(), p_zBlockText, nTextLength, &Size); // falls time-2-tme
1913 int nRight = nLeft + Size.cx;
1914 if ((nRight > rc.left) && (nLeft < rc.right))
1916 // note: ExtTextOut has a limit for the length of the string. That limit is supposed
1917 // to be 8192, but that's not really true: I found that the limit (at least on my machine and a few others)
1918 // is 4094 (4095 doesn't work anymore).
1919 // So we limit the length here to that 4094 chars.
1920 // In case we're scrolled to the right, there's no need to draw the string
1921 // from way outside our window, so we also offset the drawing to the start of the window.
1922 // This reduces the string length as well.
1923 int offset = 0;
1924 int leftcoord = nLeft;
1925 if (nLeft < 0)
1927 int fit = nTextLength;
1928 auto posBuffer = std::make_unique<int[]>(fit);
1929 GetTextExtentExPoint(pDC->GetSafeHdc(), p_zBlockText, nTextLength, INT_MAX, &fit, posBuffer.get(), &Size);
1930 int lower = 0, upper = fit - 1;
1933 int middle = (upper + lower + 1) / 2;
1934 int width = posBuffer[middle];
1935 if (rc.left - nLeft < width)
1936 upper = middle - 1;
1937 else
1938 lower = middle;
1939 } while (lower < upper);
1941 offset = lower;
1942 nTextLength -= offset;
1943 leftcoord += lower > 0 ? posBuffer[lower - 1] : 0;
1946 pDC->ExtTextOut(leftcoord, coords.y, ETO_CLIPPED, &rc, p_zBlockText + offset, min(nTextLength, 4094), nullptr);
1947 if ((second.shot != second.background) && (itStart->first == nStart + nTextOffset))
1948 pDC->FillSolidRect(nLeft - 1, rc.top, 1, rc.Height(), second.shot);
1950 nLeft = nRight;
1951 coords.x = nRight;
1952 nStartExp = nEndExp;
1957 void CBaseView::DrawSingleLine(CDC *pDC, const CRect &rc, int nLineIndex)
1959 if (nLineIndex >= GetLineCount())
1960 nLineIndex = -1;
1961 ASSERT(nLineIndex >= -1);
1963 if ((nLineIndex == -1) || !m_pViewData)
1965 // Draw line beyond the text
1966 COLORREF crBkgnd, crText;
1967 CDiffColors::GetInstance().GetColors(DIFFSTATE_UNKNOWN, crBkgnd, crText);
1968 pDC->FillSolidRect(rc, crBkgnd);
1969 return;
1972 int viewLine = GetViewLineForScreen(nLineIndex);
1973 if (m_pMainFrame->m_bCollapsed)
1975 if (m_pViewData->GetHideState(viewLine) == HIDESTATE_MARKER)
1977 COLORREF crBkgnd, crText;
1978 CDiffColors::GetInstance().GetColors(DIFFSTATE_UNKNOWN, crBkgnd, crText);
1979 pDC->FillSolidRect(rc, crBkgnd);
1981 const int THICKNESS = 2;
1982 COLORREF rectcol = GetSysColor(COLOR_WINDOWTEXT);
1983 pDC->FillSolidRect(rc.left, rc.top + (rc.Height()/2), rc.Width(), THICKNESS, rectcol);
1984 pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
1985 pDC->SetBkColor(crBkgnd);
1986 CRect rect = rc;
1987 pDC->DrawText(L"{...}", &rect, DT_NOPREFIX|DT_SINGLELINE|DT_CENTER);
1988 return;
1992 DiffStates diffState = m_pViewData->GetState(viewLine);
1993 COLORREF crBkgnd, crText;
1994 CDiffColors::GetInstance().GetColors(diffState, crBkgnd, crText);
1996 if (diffState == DIFFSTATE_CONFLICTED)
1998 // conflicted lines are shown without 'text' on them
1999 CRect rect = rc;
2000 pDC->FillSolidRect(rc, crBkgnd);
2001 // now draw some faint text patterns
2002 pDC->SetTextColor(CAppUtils::IntenseColor(130, crBkgnd));
2003 pDC->DrawText(m_sConflictedText, rect, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE);
2004 DrawBlockLine(pDC, rc, nLineIndex);
2005 return;
2008 CPoint origin(rc.left - m_nOffsetChar * GetCharWidth(), rc.top);
2009 CString sLine = GetLineChars(nLineIndex);
2010 if (sLine.IsEmpty())
2012 pDC->FillSolidRect(rc, crBkgnd);
2013 DrawBlockLine(pDC, rc, nLineIndex);
2014 DrawLineEnding(pDC, rc, nLineIndex, origin);
2015 return;
2018 CheckOtherView();
2020 // Draw the line
2022 pDC->SelectObject(GetFont(FALSE, FALSE));
2024 DrawTextLine(pDC, rc, nLineIndex, origin);
2026 // draw white space after the end of line
2027 CRect frect = rc;
2028 if (origin.x > frect.left)
2029 frect.left = origin.x;
2030 if (frect.right > frect.left)
2031 pDC->FillSolidRect(frect, crBkgnd);
2033 // draw the whitespace chars
2034 LPCTSTR pszChars = (LPCWSTR)sLine;
2035 if (m_bViewWhitespace)
2037 int xpos = 0;
2038 int nChars = 0;
2039 LPCTSTR pLastSpace = pszChars;
2040 int y = rc.top + (rc.bottom-rc.top)/2;
2041 xpos -= m_nOffsetChar * GetCharWidth();
2043 CPen pen(PS_SOLID, 0, m_WhiteSpaceFg);
2044 while (*pszChars)
2046 switch (*pszChars)
2048 case '\t':
2050 xpos += pDC->GetTextExtent(pLastSpace, (int)(pszChars - pLastSpace)).cx;
2051 pLastSpace = pszChars + 1;
2052 // draw an arrow
2053 int nSpaces = GetTabSize() - nChars % GetTabSize();
2054 if (xpos + nSpaces * GetCharWidth() > 0)
2056 int xposreal = max(xpos, 0);
2057 if ((xposreal > 0) || (nSpaces > 0))
2059 CPen * oldPen = pDC->SelectObject(&pen);
2060 pDC->MoveTo(xposreal + rc.left + CDPIAware::Instance().ScaleX(2), y);
2061 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - CDPIAware::Instance().ScaleX(2), y);
2062 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - CDPIAware::Instance().ScaleX(6), y - CDPIAware::Instance().ScaleY(4));
2063 pDC->MoveTo((xpos + nSpaces * GetCharWidth()) + rc.left - CDPIAware::Instance().ScaleX(2), y);
2064 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - CDPIAware::Instance().ScaleX(6), y + CDPIAware::Instance().ScaleY(4));
2065 pDC->SelectObject(oldPen);
2068 xpos += nSpaces * GetCharWidth();
2069 nChars += nSpaces;
2071 break;
2072 case ' ':
2074 xpos += pDC->GetTextExtent(pLastSpace, (int)(pszChars - pLastSpace)).cx;
2075 pLastSpace = pszChars + 1;
2076 if (xpos >= 0)
2078 const int cxWhitespace = CDPIAware::Instance().ScaleX(2);
2079 const int cyWhitespace = CDPIAware::Instance().ScaleY(2);
2080 // draw 2-logical pixel rectangle, like Scintilla editor.
2081 pDC->FillSolidRect(xpos + rc.left + GetCharWidth() / 2 - cxWhitespace/2, y, cxWhitespace, cyWhitespace, m_WhiteSpaceFg);
2083 xpos += GetCharWidth();
2084 nChars++;
2086 break;
2087 default:
2088 nChars++;
2089 break;
2091 pszChars++;
2094 DrawBlockLine(pDC, rc, nLineIndex);
2095 if (origin.x >= rc.left)
2096 DrawLineEnding(pDC, rc, nLineIndex, origin);
2099 void CBaseView::ExpandChars(const CString &sLine, int nOffset, int nCount, CString &line)
2101 if (nCount <= 0)
2103 line.Empty();
2104 return;
2107 int nTabSize = GetTabSize();
2109 int nActualOffset = CountExpandedChars(sLine, nOffset);
2111 LPCTSTR pszChars = (LPCWSTR)sLine;
2112 pszChars += nOffset;
2113 int nLength = nCount;
2115 int nTabCount = 0;
2116 for (int i=0; i<nLength; i++)
2118 if (pszChars[i] == L'\t')
2119 nTabCount ++;
2122 LPTSTR pszBuf = line.GetBuffer(nLength + nTabCount * (nTabSize - 1) + 1);
2123 int nCurPos = 0;
2124 if (nTabCount > 0 || m_bViewWhitespace)
2126 for (int i=0; i<nLength; i++)
2128 if (pszChars[i] == L'\t')
2130 int nSpaces = nTabSize - (nActualOffset + nCurPos) % nTabSize;
2131 while (nSpaces > 0)
2133 pszBuf[nCurPos ++] = L' ';
2134 nSpaces --;
2137 else
2139 pszBuf[nCurPos] = pszChars[i];
2140 nCurPos ++;
2144 else
2146 memcpy(pszBuf, pszChars, sizeof(TCHAR) * nLength);
2147 nCurPos = nLength;
2149 pszBuf[nCurPos] = 0;
2150 line.ReleaseBuffer();
2153 CString CBaseView::ExpandChars(const CString &sLine, int nOffset)
2155 CString sRet;
2156 int nLength = sLine.GetLength();
2157 ExpandChars(sLine, nOffset, nLength, sRet);
2158 return sRet;
2161 int CBaseView::CountExpandedChars(const CString &sLine, int nLength)
2163 int nTabSize = GetTabSize();
2165 int nActualOffset = 0;
2166 for (int i=0; i<nLength; i++)
2168 if (sLine[i] == L'\t')
2169 nActualOffset += (nTabSize - nActualOffset % nTabSize);
2170 else
2171 nActualOffset ++;
2173 return nActualOffset;
2176 void CBaseView::ScrollAllToLine(int nNewTopLine, BOOL bTrackScrollBar)
2178 if (m_pwndLeft)
2179 m_pwndLeft->ScrollToLine(nNewTopLine, bTrackScrollBar);
2180 if (m_pwndRight)
2181 m_pwndRight->ScrollToLine(nNewTopLine, bTrackScrollBar);
2182 if (m_pwndBottom)
2183 m_pwndBottom->ScrollToLine(nNewTopLine, bTrackScrollBar);
2184 if (m_pwndLocator)
2185 m_pwndLocator->Invalidate();
2188 void CBaseView::GoToLine(int nNewLine, BOOL bAll)
2190 //almost the same as ScrollAllToLine, but try to put the line in the
2191 //middle of the view, not on top
2192 int nNewTopLine = nNewLine - GetScreenLines()/2;
2193 if (nNewTopLine < 0)
2194 nNewTopLine = 0;
2195 if (nNewTopLine >= (int)m_Screen2View.size())
2196 nNewTopLine = (int)m_Screen2View.size()-1;
2197 if (bAll)
2198 ScrollAllToLine(nNewTopLine);
2199 else
2200 ScrollToLine(nNewTopLine);
2203 BOOL CBaseView::OnEraseBkgnd(CDC* /*pDC*/)
2205 return TRUE;
2208 int CBaseView::OnCreate(LPCREATESTRUCT lpCreateStruct)
2210 if (CView::OnCreate(lpCreateStruct) == -1)
2211 return -1;
2213 SecureZeroMemory(&m_lfBaseFont, sizeof(m_lfBaseFont));
2214 //lstrcpy(m_lfBaseFont.lfFaceName, L"Courier New");
2215 //lstrcpy(m_lfBaseFont.lfFaceName, L"FixedSys");
2216 m_lfBaseFont.lfHeight = 0;
2217 m_lfBaseFont.lfWeight = FW_NORMAL;
2218 m_lfBaseFont.lfItalic = FALSE;
2219 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
2220 m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
2221 m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
2222 m_lfBaseFont.lfQuality = DEFAULT_QUALITY;
2223 m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;
2225 return 0;
2228 void CBaseView::OnDestroy()
2230 if ((m_pFindDialog)&&(!m_pFindDialog->IsTerminating()))
2232 m_pFindDialog->SendMessage(WM_CLOSE);
2233 return;
2235 CView::OnDestroy();
2236 DeleteFonts();
2237 ReleaseBitmap();
2240 void CBaseView::OnSize(UINT nType, int cx, int cy)
2242 CView::OnSize(nType, cx, cy);
2243 ReleaseBitmap();
2245 m_nScreenLines = -1;
2246 m_nScreenChars = -1;
2247 if (m_nLastScreenChars != GetScreenChars())
2249 BuildAllScreen2ViewVector();
2250 m_nLastScreenChars = m_nScreenChars;
2251 if (m_pMainFrame && m_pMainFrame->m_bWrapLines)
2253 // if we're in wrap mode, the line wrapping most likely changed
2254 // and that means we have to redraw the whole window, not just the
2255 // scrolled part.
2256 Invalidate(FALSE);
2258 else
2260 // make sure the view header is redrawn
2261 CRect rcScroll;
2262 GetClientRect(&rcScroll);
2263 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
2264 InvalidateRect(&rcScroll, FALSE);
2267 else
2269 // make sure the view header is redrawn
2270 CRect rcScroll;
2271 GetClientRect(&rcScroll);
2272 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
2273 InvalidateRect(&rcScroll, FALSE);
2275 UpdateLocator();
2276 RecalcVertScrollBar();
2277 RecalcHorzScrollBar();
2279 UpdateCaret();
2282 BOOL CBaseView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
2284 if (m_pwndLeft)
2285 m_pwndLeft->OnDoMouseWheel(nFlags, zDelta, pt);
2286 if (m_pwndRight)
2287 m_pwndRight->OnDoMouseWheel(nFlags, zDelta, pt);
2288 if (m_pwndBottom)
2289 m_pwndBottom->OnDoMouseWheel(nFlags, zDelta, pt);
2290 if (m_pwndLocator)
2291 m_pwndLocator->Invalidate();
2292 return CView::OnMouseWheel(nFlags, zDelta, pt);
2295 void CBaseView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
2297 if (m_pwndLeft)
2298 m_pwndLeft->OnDoMouseHWheel(nFlags, zDelta, pt);
2299 if (m_pwndRight)
2300 m_pwndRight->OnDoMouseHWheel(nFlags, zDelta, pt);
2301 if (m_pwndBottom)
2302 m_pwndBottom->OnDoMouseHWheel(nFlags, zDelta, pt);
2303 if (m_pwndLocator)
2304 m_pwndLocator->Invalidate();
2307 void CBaseView::OnDoMouseWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/)
2309 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
2310 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
2312 if (bControl || bShift)
2314 if (m_pMainFrame->m_bWrapLines)
2315 return;
2316 // Ctrl-Wheel scrolls sideways
2317 ScrollSide(-zDelta/30);
2319 else
2321 ScrollVertical(zDelta);
2325 void CBaseView::OnDoMouseHWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/)
2327 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
2328 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
2330 if (bControl || bShift)
2332 // Ctrl-H-Wheel scrolls vertical
2333 ScrollVertical(zDelta);
2335 else
2337 if (m_pMainFrame->m_bWrapLines)
2338 return;
2339 // Ctrl-Wheel scrolls sideways
2340 ScrollSide(zDelta/30);
2344 BOOL CBaseView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
2346 if (nHitTest == HTCLIENT)
2348 if ((m_pViewData)&&(m_pMainFrame->m_bCollapsed))
2350 if (m_nMouseLine < (int)m_Screen2View.size())
2352 if (m_nMouseLine >= 0)
2354 int viewLine = GetViewLineForScreen(m_nMouseLine);
2355 if (viewLine < m_pViewData->GetCount())
2357 if (m_pViewData->GetHideState(viewLine) == HIDESTATE_MARKER)
2359 ::SetCursor(::LoadCursor(nullptr, IDC_HAND));
2360 return TRUE;
2366 if (m_mouseInMargin)
2368 ::SetCursor(m_margincursor);
2369 return TRUE;
2371 if (m_nMouseLine >= 0)
2373 ::SetCursor(::LoadCursor(nullptr, IDC_IBEAM)); // Set To Edit Cursor
2374 return TRUE;
2377 ::SetCursor(::LoadCursor(nullptr, IDC_ARROW)); // Set To Arrow Cursor
2378 return TRUE;
2380 return CView::OnSetCursor(pWnd, nHitTest, message);
2383 void CBaseView::OnKillFocus(CWnd* pNewWnd)
2385 CView::OnKillFocus(pNewWnd);
2386 m_bFocused = FALSE;
2387 UpdateCaret();
2388 Invalidate();
2391 void CBaseView::OnSetFocus(CWnd* pOldWnd)
2393 CView::OnSetFocus(pOldWnd);
2394 m_bFocused = TRUE;
2395 UpdateCaret();
2396 Invalidate();
2399 int CBaseView::GetLineFromPoint(CPoint point)
2401 ScreenToClient(&point);
2402 return (((point.y - HEADERHEIGHT) / GetLineHeight()) + m_nTopLine);
2405 void CBaseView::OnContextMenu(CPoint point, DiffStates state)
2407 CRect rcClient;
2408 GetClientRect(rcClient);
2409 CRect textrect(rcClient.left, rcClient.top, rcClient.Width(), m_nLineHeight + HEADERHEIGHT);
2411 CRect borderrect(rcClient.left, rcClient.top + m_nLineHeight + HEADERHEIGHT, 0, rcClient.bottom);
2413 CPoint ptLocal = point;
2414 ScreenToClient(&ptLocal);
2416 if (textrect.PtInRect(ptLocal) || borderrect.PtInRect(ptLocal))
2418 // inside the header part of the view (showing the filename)
2419 if (IsViewGood(m_pwndBottom))
2421 CString temp;
2422 if (this == m_pwndLeft)
2424 CIconMenu popup;
2425 if (!popup.CreatePopupMenu())
2426 return;
2428 temp.LoadString(IDS_HEADER_DIFFLEFTTOBASE);
2429 popup.AppendMenu(MF_STRING | MF_ENABLED, 10, temp);
2430 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2431 if (cmd == 10)
2432 m_pMainFrame->DiffLeftToBase();
2434 if (this == m_pwndRight)
2436 CIconMenu popup;
2437 if (!popup.CreatePopupMenu())
2438 return;
2440 temp.LoadString(IDS_HEADER_DIFFRIGHTTOBASE);
2441 popup.AppendMenu(MF_STRING | MF_ENABLED, 10, temp);
2442 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2443 if (cmd == 10)
2444 m_pMainFrame->DiffRightToBase();
2447 return;
2450 if (!this->IsWindowVisible())
2451 return;
2453 CIconMenu popup;
2454 if (!popup.CreatePopupMenu())
2455 return;
2457 AddContextItems(popup, state);
2459 CMenu popupEols;
2460 CMenu popupUnicode;
2461 int nEncodingCommandBase = POPUPCOMMAND__LAST;
2462 int nEolCommandBase = nEncodingCommandBase+_countof(uctArray);
2463 if (IsWritable())
2465 CString temp;
2466 TWhitecharsProperties oWhites = GetWhitecharsProperties();
2467 temp.LoadString(IDS_EDIT_TAB2SPACE);
2468 popup.AppendMenu(MF_STRING | (oWhites.HasTabsToConvert ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_TABTOSPACES, temp);
2469 temp.LoadString(IDS_EDIT_SPACE2TAB);
2470 popup.AppendMenu(MF_STRING | (oWhites.HasSpacesToConvert ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_SPACESTOTABS, temp);
2471 temp.LoadString(IDS_EDIT_TRIM);
2472 popup.AppendMenu(MF_STRING | (oWhites.HasTrailWhiteChars ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_REMOVETRAILWHITES, temp);
2474 // add eol submenu
2475 if (!popupEols.CreatePopupMenu())
2476 return;
2478 EOL eEolType = GetLineEndings(oWhites.HasMixedEols);
2479 for (int i = 1; i < _countof(eolArray); i++)
2481 temp = GetEolName(eolArray[i]);
2482 bool bChecked = (eEolType == eolArray[i]);
2483 popupEols.AppendMenu(MF_STRING | MF_ENABLED | (bChecked ? MF_CHECKED : 0), nEolCommandBase+i, temp);
2486 temp.LoadString(IDS_VIEWCONTEXTMENU_EOL);
2487 popup.AppendMenuW(MF_POPUP | MF_ENABLED, (UINT_PTR)popupEols.GetSafeHmenu(), temp);
2489 // add encoding submenu
2490 if (!popupUnicode.CreatePopupMenu())
2491 return;
2492 for (int i = 0; i < _countof(uctArray); i++)
2494 temp = CFileTextLines::GetEncodingName(uctArray[i]);
2495 bool bChecked = (m_texttype == uctArray[i]);
2496 popupUnicode.AppendMenu(MF_STRING | MF_ENABLED | (bChecked ? MF_CHECKED : 0), nEncodingCommandBase+i, temp);
2498 temp.LoadString(IDS_VIEWCONTEXTMENU_ENCODING);
2499 popup.AppendMenuW(MF_POPUP | MF_ENABLED, (UINT_PTR)popupUnicode.GetSafeHmenu(), temp);
2503 CompensateForKeyboard(point);
2505 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2506 ResetUndoStep();
2507 if ((cmd>=nEncodingCommandBase) && (cmd<nEncodingCommandBase+(int)_countof(uctArray)))
2509 SetTextType(uctArray[cmd-nEncodingCommandBase]);
2511 if ((cmd>=nEolCommandBase) && (cmd<nEolCommandBase+(int)_countof(eolArray)))
2513 ReplaceLineEndings(eolArray[cmd-nEolCommandBase]);
2514 SaveUndoStep();
2516 switch (cmd)
2518 // 2-pane view commands; target is right view
2519 case POPUPCOMMAND_USELEFTBLOCK:
2520 m_pwndRight->UseLeftBlock();
2521 break;
2522 case POPUPCOMMAND_USELEFTFILE:
2523 m_pwndRight->UseLeftFile();
2524 break;
2525 case POPUPCOMMAND_USEBOTHLEFTFIRST:
2526 m_pwndRight->UseBothLeftFirst();
2527 break;
2528 case POPUPCOMMAND_USEBOTHRIGHTFIRST:
2529 m_pwndRight->UseBothRightFirst();
2530 break;
2531 case POPUPCOMMAND_MARKBLOCK:
2532 m_pwndRight->MarkBlock(true);
2533 break;
2534 case POPUPCOMMAND_UNMARKBLOCK:
2535 m_pwndRight->MarkBlock(false);
2536 break;
2537 case POPUPCOMMAND_LEAVEONLYMARKEDBLOCKS:
2538 m_pwndRight->LeaveOnlyMarkedBlocks();
2539 break;
2540 // 2-pane view multiedit commands; target is left view
2541 case POPUPCOMMAND_PREPENDFROMRIGHT:
2542 if (!m_pwndLeft->IsReadonly())
2543 m_pwndLeft->UseBothRightFirst();
2544 break;
2545 case POPUPCOMMAND_REPLACEBYRIGHT:
2546 if (!m_pwndLeft->IsReadonly())
2547 m_pwndLeft->UseRightBlock();
2548 break;
2549 case POPUPCOMMAND_APPENDFROMRIGHT:
2550 if (!m_pwndLeft->IsReadonly())
2551 m_pwndLeft->UseBothLeftFirst();
2552 break;
2553 case POPUPCOMMAND_USERIGHTFILE:
2554 m_pwndLeft->UseRightFile();
2555 break;
2556 // 3-pane view commands; target is bottom view
2557 case POPUPCOMMAND_USEYOURANDTHEIRBLOCK:
2558 m_pwndBottom->UseBothRightFirst();
2559 break;
2560 case POPUPCOMMAND_USETHEIRANDYOURBLOCK:
2561 m_pwndBottom->UseBothLeftFirst();
2562 break;
2563 case POPUPCOMMAND_USEYOURBLOCK:
2564 m_pwndBottom->UseRightBlock();
2565 break;
2566 case POPUPCOMMAND_USEYOURFILE:
2567 m_pwndBottom->UseRightFile();
2568 break;
2569 case POPUPCOMMAND_USETHEIRBLOCK:
2570 m_pwndBottom->UseLeftBlock();
2571 break;
2572 case POPUPCOMMAND_USETHEIRFILE:
2573 m_pwndBottom->UseLeftFile();
2574 break;
2575 // copy, cut and paste commands
2576 case ID_EDIT_COPY:
2577 OnEditCopy();
2578 break;
2579 case ID_EDIT_CUT:
2580 OnEditCut();
2581 break;
2582 case ID_EDIT_PASTE:
2583 OnEditPaste();
2584 break;
2585 // white chars manipulations
2586 case POPUPCOMMAND_TABTOSPACES:
2587 ConvertTabToSpaces();
2588 break;
2589 case POPUPCOMMAND_SPACESTOTABS:
2590 Tabularize();
2591 break;
2592 case POPUPCOMMAND_REMOVETRAILWHITES:
2593 RemoveTrailWhiteChars();
2594 break;
2595 default:
2596 return;
2597 } // switch (cmd)
2598 SaveUndoStep(); // all except copy, cut paste save undo step, but this should not be harmful as step is empty already and thus not saved
2599 return;
2602 void CBaseView::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
2604 if (!m_pViewData)
2605 return;
2607 int nViewBlockStart = -1;
2608 int nViewBlockEnd = -1;
2609 GetViewSelection(nViewBlockStart, nViewBlockEnd);
2610 if ((point.x >= 0) && (point.y >= 0))
2612 int nLine = GetLineFromPoint(point)-1;
2613 if ((nLine >= 0) && (nLine < m_Screen2View.size()))
2615 int nViewLine = GetViewLineForScreen(nLine);
2616 if (((nViewLine < nViewBlockStart) || (nViewBlockEnd < nViewLine)))
2618 ClearSelection(); // Clear text-copy selection
2620 nViewBlockStart = nViewLine;
2621 nViewBlockEnd = nViewLine;
2622 DiffStates state = m_pViewData->GetState(nViewLine);
2623 while (nViewBlockStart > 0)
2625 const DiffStates lineState = m_pViewData->GetState(nViewBlockStart-1);
2626 if (!LinesInOneChange(-1, state, lineState))
2627 break;
2628 nViewBlockStart--;
2631 while (nViewBlockEnd < (m_pViewData->GetCount()-1))
2633 const DiffStates lineState = m_pViewData->GetState(nViewBlockEnd+1);
2634 if (!LinesInOneChange(1, state, lineState))
2635 break;
2636 nViewBlockEnd++;
2639 SetupAllViewSelection(nViewBlockStart, nViewBlockEnd);
2640 UpdateCaretPosition(SetupPoint(0, nViewLine));
2645 // FixSelection(); fix selection range
2646 /*if (m_nSelBlockEnd >= m_pViewData->GetCount())
2647 m_nSelBlockEnd = m_pViewData->GetCount()-1;//*/
2649 DiffStates state = DIFFSTATE_UNKNOWN;
2650 if (GetViewSelection(nViewBlockStart, nViewBlockEnd))
2652 // find a more 'relevant' state in the selection
2653 for (int i=nViewBlockStart; i<=nViewBlockEnd; ++i)
2655 state = m_pViewData->GetState(i);
2656 if ((state != DIFFSTATE_NORMAL) && (state != DIFFSTATE_UNKNOWN))
2657 break;
2660 OnContextMenu(point, state);
2663 void CBaseView::RefreshViews()
2665 if (m_pwndLeft)
2667 m_pwndLeft->UpdateStatusBar();
2668 m_pwndLeft->Invalidate();
2670 if (m_pwndRight)
2672 m_pwndRight->UpdateStatusBar();
2673 m_pwndRight->Invalidate();
2675 if (m_pwndBottom)
2677 m_pwndBottom->UpdateStatusBar();
2678 m_pwndBottom->Invalidate();
2680 if (m_pwndLocator)
2681 m_pwndLocator->Invalidate();
2684 void CBaseView::GoToFirstDifference()
2686 SetCaretToFirstViewLine();
2687 SelectNextBlock(1, false, false);
2690 void CBaseView::GoToFirstConflict()
2692 SetCaretToFirstViewLine();
2693 SelectNextBlock(1, true, false);
2696 void CBaseView::HighlightLines(int nStart, int nEnd /* = -1 */)
2698 ClearSelection();
2699 SetupAllSelection(nStart, max(nStart, nEnd));
2701 UpdateCaretPosition(SetupPoint(0, nStart));
2702 Invalidate();
2705 void CBaseView::HighlightViewLines(int nStart, int nEnd /* = -1 */)
2707 ClearSelection();
2708 SetupAllViewSelection(nStart, max(nStart, nEnd));
2710 UpdateCaretViewPosition(SetupPoint(0, nStart));
2711 Invalidate();
2714 void CBaseView::SetupAllViewSelection(int start, int end)
2716 SetupViewSelection(m_pwndBottom, start, end);
2717 SetupViewSelection(m_pwndLeft, start, end);
2718 SetupViewSelection(m_pwndRight, start, end);
2721 void CBaseView::SetupAllSelection(int start, int end)
2723 SetupAllViewSelection(GetViewLineForScreen(start), GetViewLineForScreen(end));
2726 //void CBaseView::SetupSelection(CBaseView* view, int start, int end) { }
2728 void CBaseView::SetupSelection(int start, int end)
2730 SetupViewSelection(GetViewLineForScreen(start), GetViewLineForScreen(end));
2733 void CBaseView::SetupViewSelection(CBaseView* view, int start, int end)
2735 if (!IsViewGood(view))
2736 return;
2737 view->SetupViewSelection(start, end);
2740 void CBaseView::SetupViewSelection(int start, int end)
2742 // clear text selection before setting line selection ?
2743 m_nSelViewBlockStart = start;
2744 m_nSelViewBlockEnd = end;
2745 Invalidate();
2749 void CBaseView::OnMergePreviousconflict()
2751 SelectNextBlock(-1, true);
2754 void CBaseView::OnMergeNextconflict()
2756 SelectNextBlock(1, true);
2759 void CBaseView::OnMergeNextdifference()
2761 SelectNextBlock(1, false);
2764 void CBaseView::OnMergePreviousdifference()
2766 SelectNextBlock(-1, false);
2769 bool CBaseView::HasNextConflict()
2771 return SelectNextBlock(1, true, true, true);
2774 bool CBaseView::HasPrevConflict()
2776 return SelectNextBlock(-1, true, true, true);
2779 bool CBaseView::HasNextDiff()
2781 return SelectNextBlock(1, false, true, true);
2784 bool CBaseView::HasPrevDiff()
2786 return SelectNextBlock(-1, false, true, true);
2789 bool CBaseView::LinesInOneChange(int direction,
2790 DiffStates initialLineState, DiffStates currentLineState)
2792 // Checks whether all the adjacent lines starting from the initial line
2793 // and up to the current line form the single change
2795 // First of all, if the two lines have identical states, they surely
2796 // belong to one change.
2797 if (initialLineState == currentLineState)
2798 return true;
2800 // Either we move down and initial line state is "added" or "removed" and
2801 // current line state is "empty"...
2802 if (direction > 0)
2804 if (currentLineState == DIFFSTATE_EMPTY)
2806 if (initialLineState == DIFFSTATE_ADDED || initialLineState == DIFFSTATE_REMOVED)
2807 return true;
2809 if (initialLineState == DIFFSTATE_CONFLICTADDED && currentLineState == DIFFSTATE_CONFLICTEMPTY)
2810 return true;
2812 // ...or we move up and initial line state is "empty" and current line
2813 // state is "added" or "removed".
2814 if (direction < 0)
2816 if (initialLineState == DIFFSTATE_EMPTY)
2818 if (currentLineState == DIFFSTATE_ADDED || currentLineState == DIFFSTATE_REMOVED)
2819 return true;
2821 if (initialLineState == DIFFSTATE_CONFLICTEMPTY && currentLineState == DIFFSTATE_CONFLICTADDED)
2822 return true;
2824 return false;
2827 bool CBaseView::SelectNextBlock(int nDirection, bool bConflict, bool bSkipEndOfCurrentBlock /* = true */, bool dryrun /* = false */)
2829 if (! m_pViewData)
2830 return false;
2832 const int linesCount = (int)m_Screen2View.size();
2833 if(linesCount == 0)
2834 return false;
2836 int nCenterPos = GetCaretPosition().y;
2837 int nLimit = -1;
2838 if (nDirection > 0)
2839 nLimit = linesCount;
2841 if (nCenterPos >= linesCount)
2842 nCenterPos = linesCount-1;
2844 if (bSkipEndOfCurrentBlock)
2846 // Find end of current block
2847 const DiffStates state = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2848 while (nCenterPos != nLimit)
2850 const DiffStates lineState = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2851 if (!LinesInOneChange(nDirection, state, lineState))
2852 break;
2853 nCenterPos += nDirection;
2857 // Find next diff/conflict block
2858 while (nCenterPos != nLimit)
2860 DiffStates linestate = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2861 if (!bConflict &&
2862 (linestate != DIFFSTATE_NORMAL) &&
2863 (linestate != DIFFSTATE_UNKNOWN) &&
2864 (linestate != DIFFSTATE_FILTEREDDIFF))
2866 break;
2868 if (bConflict &&
2869 ((linestate == DIFFSTATE_CONFLICTADDED) ||
2870 (linestate == DIFFSTATE_CONFLICTED_IGNORED) ||
2871 (linestate == DIFFSTATE_CONFLICTED) ||
2872 (linestate == DIFFSTATE_CONFLICTEMPTY)))
2874 break;
2877 nCenterPos += nDirection;
2879 if (nCenterPos == nLimit)
2880 return false;
2881 if (dryrun)
2882 return (nCenterPos != nLimit);
2884 // Find end of new block
2885 DiffStates state = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2886 int nBlockEnd = nCenterPos;
2887 const int maxAllowedLine = nLimit-nDirection;
2888 while (nBlockEnd != maxAllowedLine)
2890 const int lineIndex = nBlockEnd + nDirection;
2891 if (lineIndex >= linesCount)
2892 break;
2893 DiffStates lineState = m_pViewData->GetState(GetViewLineForScreen(lineIndex));
2894 if (!LinesInOneChange(nDirection, state, lineState))
2895 break;
2896 nBlockEnd += nDirection;
2899 int nTopPos = nCenterPos - (GetScreenLines()/2);
2900 if (nTopPos < 0)
2901 nTopPos = 0;
2903 POINT ptCaretPos = {0, nCenterPos};
2904 SetCaretPosition(ptCaretPos);
2905 ClearSelection();
2906 if (nDirection > 0)
2907 SetupAllSelection(nCenterPos, nBlockEnd);
2908 else
2909 SetupAllSelection(nBlockEnd, nCenterPos);
2911 ScrollAllToLine(nTopPos, FALSE);
2912 RecalcAllVertScrollBars(TRUE);
2913 SetCaretToLineStart();
2914 EnsureCaretVisible();
2915 OnNavigateNextinlinediff();
2917 UpdateViewsCaretPosition();
2918 UpdateCaret();
2919 ShowDiffLines(nCenterPos);
2920 return true;
2923 BOOL CBaseView::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
2925 if (pNMHDR->idFrom != (UINT_PTR)m_hWnd)
2926 return FALSE;
2928 CString strTipText;
2929 strTipText = m_sWindowName + L"\r\n" + m_sFullFilePath;
2931 DWORD pos = GetMessagePos();
2932 CPoint point(GET_X_LPARAM(pos), GET_Y_LPARAM(pos));
2933 ScreenToClient(&point);
2934 const int nLine = GetButtonEventLineIndex(point);
2936 if (nLine >= 0)
2938 int nViewLine = GetViewLineForScreen(nLine);
2939 if((m_pViewData)&&(nViewLine < m_pViewData->GetCount()))
2941 auto movedIndex = m_pViewData->GetMovedIndex(nViewLine);
2942 if (movedIndex >= 0)
2944 if (m_pViewData->IsMovedFrom(nViewLine))
2946 strTipText.Format(IDS_MOVED_TO_TT, movedIndex+1);
2948 else
2950 strTipText.Format(IDS_MOVED_FROM_TT, movedIndex+1);
2957 *pResult = 0;
2958 if (strTipText.IsEmpty())
2959 return TRUE;
2961 // need to handle both ANSI and UNICODE versions of the message
2962 if (pNMHDR->code == TTN_NEEDTEXTA)
2964 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
2965 pTTTA->lpszText = m_szTip;
2966 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
2968 else
2970 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
2971 lstrcpyn(m_wszTip, strTipText, min(strTipText.GetLength() + 1, _countof(m_wszTip) - 1));
2972 pTTTW->lpszText = m_wszTip;
2975 return TRUE; // message was handled
2978 INT_PTR CBaseView::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
2980 CRect rcClient;
2981 GetClientRect(rcClient);
2982 CRect textrect(rcClient.left, rcClient.top, rcClient.Width(), m_nLineHeight+HEADERHEIGHT);
2984 int marginwidth = GetSystemMetrics(SM_CXSMICON) + 2 + 2;
2985 if ((m_bViewLinenumbers)&&(m_pViewData)&&(m_pViewData->GetCount())&&(m_nDigits > 0))
2987 marginwidth += (m_nDigits * m_nCharWidth) + 2;
2989 CRect borderrect(rcClient.left, rcClient.top+m_nLineHeight+HEADERHEIGHT, marginwidth, rcClient.bottom);
2991 if (textrect.PtInRect(point) || borderrect.PtInRect(point))
2993 // inside the header part of the view (showing the filename)
2994 pTI->hwnd = this->m_hWnd;
2995 this->GetClientRect(&pTI->rect);
2996 pTI->uFlags |= TTF_ALWAYSTIP | TTF_IDISHWND;
2997 pTI->uId = (UINT_PTR)m_hWnd;
2998 pTI->lpszText = LPSTR_TEXTCALLBACK;
3000 // we want multi line tooltips
3001 CToolTipCtrl* pToolTip = AfxGetModuleThreadState()->m_pToolTip;
3002 if (pToolTip->GetSafeHwnd())
3003 pToolTip->SetMaxTipWidth(SHRT_MAX);
3005 return (textrect.PtInRect(point) ? 1 : 2);
3008 return -1;
3011 void CBaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
3013 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
3014 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
3016 switch (nChar)
3018 case VK_TAB:
3019 if (bControl)
3021 if (this==m_pwndLeft)
3023 if (IsViewGood(m_pwndRight))
3025 m_pwndRight->SetFocus();
3027 else if (IsViewGood(m_pwndBottom))
3029 m_pwndBottom->SetFocus();
3032 else if (this==m_pwndRight)
3034 if (IsViewGood(m_pwndBottom))
3036 m_pwndBottom->SetFocus();
3038 else if (IsViewGood(m_pwndLeft))
3040 m_pwndLeft->SetFocus();
3043 else if (this==m_pwndBottom)
3045 if (IsViewGood(m_pwndLeft))
3047 m_pwndLeft->SetFocus();
3049 else if (IsViewGood(m_pwndRight))
3051 m_pwndRight->SetFocus();
3055 break;
3056 case VK_PRIOR:
3058 POINT ptCaretPos = GetCaretPosition();
3059 ptCaretPos.y -= GetScreenLines();
3060 ptCaretPos.y = max(ptCaretPos.y, 0);
3061 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
3062 SetCaretPosition(ptCaretPos);
3063 OnCaretMove(MOVELEFT, bShift);
3064 ShowDiffLines(ptCaretPos.y);
3066 break;
3067 case VK_NEXT:
3069 POINT ptCaretPos = GetCaretPosition();
3070 ptCaretPos.y += GetScreenLines();
3071 if (ptCaretPos.y >= GetLineCount())
3072 ptCaretPos.y = GetLineCount()-1;
3073 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
3074 SetCaretPosition(ptCaretPos);
3075 OnCaretMove(MOVERIGHT, bShift);
3076 ShowDiffLines(ptCaretPos.y);
3078 break;
3079 case VK_HOME:
3081 if (bControl)
3083 ScrollAllToLine(0);
3084 ScrollAllToChar(0);
3085 SetCaretToViewStart();
3086 m_nCaretGoalPos = 0;
3087 if (bShift)
3088 AdjustSelection(MOVELEFT);
3089 else
3090 ClearSelection();
3091 UpdateCaret();
3093 else
3095 POINT ptCaretPos = GetCaretPosition();
3096 CString sLine = GetLineChars(ptCaretPos.y);
3097 int pos = 0;
3098 while (pos < sLine.GetLength())
3100 if (sLine[pos] != ' ' && sLine[pos] != '\t')
3101 break;
3102 ++pos;
3104 if (ptCaretPos.x == pos)
3106 SetCaretToLineStart();
3107 m_nCaretGoalPos = 0;
3108 OnCaretMove(MOVERIGHT, bShift);
3109 ScrollAllToChar(0);
3111 else
3113 ptCaretPos.x = pos;
3114 SetCaretAndGoalPosition(ptCaretPos);
3115 OnCaretMove(MOVELEFT, bShift);
3119 break;
3120 case VK_END:
3122 if (bControl)
3124 ScrollAllToLine(GetLineCount()-GetAllMinScreenLines());
3125 POINT ptCaretPos;
3126 ptCaretPos.y = GetLineCount()-1;
3127 ptCaretPos.x = GetLineLength(ptCaretPos.y);
3128 SetCaretAndGoalPosition(ptCaretPos);
3129 if (bShift)
3130 AdjustSelection(MOVERIGHT);
3131 else
3132 ClearSelection();
3134 else
3136 POINT ptCaretPos = GetCaretPosition();
3137 ptCaretPos.x = GetLineLength(ptCaretPos.y);
3138 if ((GetSubLineOffset(ptCaretPos.y) != -1) && (GetSubLineOffset(ptCaretPos.y) != CountMultiLines(GetViewLineForScreen(ptCaretPos.y))-1)) // not last screen line of view line
3140 ptCaretPos.x--;
3142 SetCaretAndGoalPosition(ptCaretPos);
3143 OnCaretMove(MOVERIGHT, bShift);
3146 break;
3147 case VK_BACK:
3148 if (IsWritable())
3150 if (! HasTextSelection())
3152 POINT ptCaretPos = GetCaretPosition();
3153 if (ptCaretPos.y == 0 && ptCaretPos.x == 0)
3154 break;
3155 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3156 if (bControl)
3157 MoveCaretWordLeft();
3158 else
3160 while (MoveCaretLeft() && IsViewLineEmpty(GetCaretViewPosition().y))
3164 m_ptSelectionViewPosStart = GetCaretViewPosition();
3166 RemoveSelectedText();
3168 break;
3169 case VK_DELETE:
3170 if (IsWritable())
3172 if (! HasTextSelection())
3174 if (bControl)
3176 m_ptSelectionViewPosStart = GetCaretViewPosition();
3177 MoveCaretWordRight();
3178 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3180 else
3182 if (! MoveCaretRight())
3183 break;
3184 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3185 MoveCaretLeft();
3186 m_ptSelectionViewPosStart = GetCaretViewPosition();
3189 RemoveSelectedText();
3191 break;
3192 case VK_INSERT:
3193 m_bInsertMode = !m_bInsertMode;
3194 UpdateCaret();
3195 break;
3197 CView::OnKeyDown(nChar, nRepCnt, nFlags);
3200 void CBaseView::OnLButtonDown(UINT nFlags, CPoint point)
3202 const int nClickedLine = GetButtonEventLineIndex(point);
3203 if ((nClickedLine >= m_nTopLine)&&(nClickedLine < GetLineCount()))
3205 POINT ptCaretPos;
3206 ptCaretPos.y = nClickedLine;
3207 int xpos2 = CalcColFromPoint(point.x, nClickedLine);
3208 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, xpos2);
3209 SetCaretAndGoalPosition(ptCaretPos);
3211 if (nFlags & MK_SHIFT)
3212 AdjustSelection(MOVERIGHT);
3213 else
3215 ClearSelection();
3216 SetupAllSelection(ptCaretPos.y, ptCaretPos.y);
3217 if (point.x < GetMarginWidth())
3219 // select the whole line
3220 m_ptSelectionViewPosStart = m_ptSelectionViewPosEnd = GetCaretViewPosition();
3221 m_ptSelectionViewPosStart.x = 0;
3222 m_ptSelectionViewPosEnd.x = GetViewLineLength(m_ptSelectionViewPosEnd.y);
3226 UpdateViewsCaretPosition();
3227 Invalidate();
3230 CView::OnLButtonDown(nFlags, point);
3233 CBaseView::ECharGroup CBaseView::GetCharGroup(wchar_t zChar) const
3235 if (zChar == ' ' || zChar == '\t' )
3237 return CHG_WHITESPACE;
3239 if (zChar < 0x20)
3241 return CHG_CONTROL;
3243 if (m_sWordSeparators.Find(zChar) >= 0)
3245 return CHG_WORDSEPARATOR;
3247 return CHG_WORDLETTER;
3250 void CBaseView::OnLButtonDblClk(UINT nFlags, CPoint point)
3252 if (m_pViewData == 0) {
3253 CView::OnLButtonDblClk(nFlags, point);
3254 return;
3257 const int nClickedLine = GetButtonEventLineIndex(point);
3258 if ( nClickedLine < 0)
3259 return;
3260 int nViewLine = GetViewLineForScreen(nClickedLine);
3261 if (point.x < GetMarginWidth()) // only if double clicked on the margin
3263 if((nViewLine < m_pViewData->GetCount())) // a double click on moved line scrolls to corresponding line
3265 if (m_pViewData->GetMovedIndex(nViewLine)>=0)
3267 int movedindex = m_pViewData->GetMovedIndex(nViewLine);
3268 int screenLine = FindViewLineNumber(movedindex);
3269 int nTop = screenLine - GetScreenLines()/2;
3270 if (nTop < 0)
3271 nTop = 0;
3272 ScrollAllToLine(nTop);
3273 // find and select the whole moved block
3274 int startSel = movedindex;
3275 int endSel = movedindex;
3276 while ((startSel > 0) && (m_pOtherViewData->GetMovedIndex(startSel) >= 0))
3277 startSel--;
3278 startSel++;
3279 while ((endSel < GetLineCount()) && (m_pOtherViewData->GetMovedIndex(endSel) >= 0))
3280 endSel++;
3281 endSel--;
3282 m_pOtherView->SetupSelection(startSel, endSel);
3283 return CView::OnLButtonDblClk(nFlags, point);
3287 if ((m_pMainFrame->m_bCollapsed)&&(m_pViewData->GetHideState(nViewLine) == HIDESTATE_MARKER))
3289 // a double click on a marker expands the hidden text
3290 int i = nViewLine;
3291 while ((i < m_pViewData->GetCount())&&(m_pViewData->GetHideState(i) != HIDESTATE_SHOWN))
3293 if ((m_pwndLeft)&&(m_pwndLeft->m_pViewData))
3294 m_pwndLeft->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN);
3295 if ((m_pwndRight)&&(m_pwndRight->m_pViewData))
3296 m_pwndRight->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN);
3297 if ((m_pwndBottom)&&(m_pwndBottom->m_pViewData))
3298 m_pwndBottom->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN);
3299 i++;
3301 BuildAllScreen2ViewVector();
3302 if (m_pwndLeft)
3303 m_pwndLeft->Invalidate();
3304 if (m_pwndRight)
3305 m_pwndRight->Invalidate();
3306 if (m_pwndBottom)
3307 m_pwndBottom->Invalidate();
3309 else
3311 POINT ptCaretPos;
3312 ptCaretPos.y = nClickedLine;
3313 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth());
3314 SetCaretPosition(ptCaretPos);
3315 ClearSelection();
3317 POINT ptViewCarret = GetCaretViewPosition();
3318 nViewLine = ptViewCarret.y;
3319 if (nViewLine >= GetViewCount())
3320 return;
3321 const CString &sLine = GetViewLine(nViewLine);
3322 int nLineLength = sLine.GetLength();
3323 int nBasePos = ptViewCarret.x;
3324 // get target char group
3325 ECharGroup eLeft = CHG_UNKNOWN;
3326 if (nBasePos > 0)
3328 eLeft = GetCharGroup(sLine[nBasePos-1]);
3330 ECharGroup eRight = CHG_UNKNOWN;
3331 if (nBasePos < nLineLength)
3333 eRight = GetCharGroup(sLine[nBasePos]);
3335 ECharGroup eTarget = max(eRight, eLeft);
3336 // find left margin
3337 int nLeft = nBasePos;
3338 while (nLeft > 0 && GetCharGroup(sLine[nLeft-1]) == eTarget)
3340 nLeft--;
3342 // get right margin
3343 int nRight = nBasePos;
3344 while (nRight < nLineLength && GetCharGroup(sLine[nRight]) == eTarget)
3346 nRight++;
3348 // set selection
3349 m_ptSelectionViewPosStart.x = nLeft;
3350 m_ptSelectionViewPosStart.y = nViewLine;
3351 m_ptSelectionViewPosEnd.x = nRight;
3352 m_ptSelectionViewPosEnd.y = nViewLine;
3353 m_ptSelectionViewPosOrigin = m_ptSelectionViewPosStart;
3354 SetupAllViewSelection(nViewLine, nViewLine);
3355 // set caret
3356 ptCaretPos = ConvertViewPosToScreen(m_ptSelectionViewPosEnd);
3357 UpdateViewsCaretPosition();
3358 UpdateGoalPos();
3360 // set mark word
3361 m_sPreviousMarkedWord = m_sMarkedWord; // store marked word to recall in case of triple click
3362 int nMarkWidth = max(nRight - nLeft, 0);
3363 m_sMarkedWord = sLine.Mid(m_ptSelectionViewPosStart.x, nMarkWidth).Trim();
3364 if (m_sMarkedWord.Compare(m_sPreviousMarkedWord) == 0)
3366 m_sMarkedWord.Empty();
3369 if (m_pwndLeft)
3370 m_pwndLeft->SetMarkedWord(m_sMarkedWord);
3371 if (m_pwndRight)
3372 m_pwndRight->SetMarkedWord(m_sMarkedWord);
3373 if (m_pwndBottom)
3374 m_pwndBottom->SetMarkedWord(m_sMarkedWord);
3376 Invalidate();
3377 if (m_pwndLocator)
3378 m_pwndLocator->Invalidate();
3381 CView::OnLButtonDblClk(nFlags, point);
3384 void CBaseView::OnLButtonTrippleClick( UINT /*nFlags*/, CPoint point )
3386 const int nClickedLine = GetButtonEventLineIndex(point);
3387 if (((point.y - HEADERHEIGHT) / GetLineHeight()) <= 0)
3389 if (!m_sConvertedFilePath.IsEmpty() && (GetKeyState(VK_CONTROL)&0x8000))
3391 PCIDLIST_ABSOLUTE __unaligned pidl = ILCreateFromPath((LPCTSTR)m_sConvertedFilePath);
3392 if (pidl)
3394 SHOpenFolderAndSelectItems(pidl,0,0,0);
3395 CoTaskMemFree((LPVOID)pidl);
3398 return;
3400 POINT ptCaretPos;
3401 ptCaretPos.y = nClickedLine;
3402 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth());
3403 SetCaretAndGoalPosition(ptCaretPos);
3404 m_sMarkedWord = m_sPreviousMarkedWord; // recall previous Marked word
3405 if (m_pwndLeft)
3406 m_pwndLeft->SetMarkedWord(m_sMarkedWord);
3407 if (m_pwndRight)
3408 m_pwndRight->SetMarkedWord(m_sMarkedWord);
3409 if (m_pwndBottom)
3410 m_pwndBottom->SetMarkedWord(m_sMarkedWord);
3411 ClearSelection();
3412 m_ptSelectionViewPosStart.x = 0;
3413 m_ptSelectionViewPosStart.y = nClickedLine;
3414 m_ptSelectionViewPosEnd.x = GetLineLength(nClickedLine);
3415 m_ptSelectionViewPosEnd.y = nClickedLine;
3416 SetupSelection(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
3417 UpdateViewsCaretPosition();
3418 Invalidate();
3419 if (m_pwndLocator)
3420 m_pwndLocator->Invalidate();
3423 void CBaseView::OnEditCopy()
3425 CString sCopyData = GetSelectedText();
3427 if (!sCopyData.IsEmpty())
3429 CStringUtils::WriteAsciiStringToClipboard(sCopyData, m_hWnd);
3433 void CBaseView::OnMouseMove(UINT nFlags, CPoint point)
3435 if (m_pMainFrame->m_nMoveMovesToIgnore > 0)
3437 --m_pMainFrame->m_nMoveMovesToIgnore;
3438 CView::OnMouseMove(nFlags, point);
3439 return;
3441 int nMouseLine = GetButtonEventLineIndex(point);
3442 if (nMouseLine < -1)
3443 nMouseLine = -1;
3444 m_mouseInMargin = point.x < GetMarginWidth();
3446 ShowDiffLines(nMouseLine);
3448 KillTimer(IDT_SCROLLTIMER);
3449 if (nFlags & MK_LBUTTON)
3451 int saveMouseLine = nMouseLine >= 0 ? nMouseLine : 0;
3452 saveMouseLine = saveMouseLine < GetLineCount() ? saveMouseLine : GetLineCount() - 1;
3453 if (saveMouseLine < 0)
3454 return;
3455 int col = CalcColFromPoint(point.x, saveMouseLine);
3456 int charIndex = CalculateCharIndex(saveMouseLine, col);
3457 if (HasSelection() &&
3458 ((nMouseLine >= m_nTopLine)&&(nMouseLine < GetLineCount())))
3460 POINT ptCaretPos = {charIndex, nMouseLine};
3461 SetCaretAndGoalPosition(ptCaretPos);
3462 AdjustSelection(MOVERIGHT);
3463 Invalidate();
3464 UpdateWindow();
3466 if (nMouseLine < m_nTopLine)
3468 ScrollAllToLine(m_nTopLine-1, TRUE);
3469 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3471 if (nMouseLine >= m_nTopLine + GetScreenLines() - 2)
3473 ScrollAllToLine(m_nTopLine+1, TRUE);
3474 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3476 if (!m_pMainFrame->m_bWrapLines && ((m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()) <= m_nOffsetChar))
3478 ScrollAllSide(-1);
3479 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3481 if (!m_pMainFrame->m_bWrapLines && (charIndex >= (GetScreenChars()+m_nOffsetChar-4)))
3483 ScrollAllSide(1);
3484 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3486 SetCapture();
3490 CView::OnMouseMove(nFlags, point);
3493 void CBaseView::OnLButtonUp(UINT nFlags, CPoint point)
3495 ShowDiffLines(-1);
3496 ReleaseCapture();
3497 KillTimer(IDT_SCROLLTIMER);
3499 __super::OnLButtonUp(nFlags, point);
3502 void CBaseView::OnTimer(UINT_PTR nIDEvent)
3504 if (nIDEvent == IDT_SCROLLTIMER)
3506 POINT point;
3507 GetCursorPos(&point);
3508 ScreenToClient(&point);
3509 int nMouseLine = GetButtonEventLineIndex(point);
3510 if (nMouseLine < -1)
3512 nMouseLine = -1;
3514 if (GetKeyState(VK_LBUTTON)&0x8000)
3516 int saveMouseLine = nMouseLine >= 0 ? nMouseLine : 0;
3517 saveMouseLine = saveMouseLine < GetLineCount() ? saveMouseLine : GetLineCount() - 1;
3518 int charIndex = CalculateCharIndex(saveMouseLine, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth());
3519 if (nMouseLine < m_nTopLine)
3521 ScrollAllToLine(m_nTopLine-1, TRUE);
3522 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3524 if (nMouseLine >= m_nTopLine + GetScreenLines() - 2)
3526 ScrollAllToLine(m_nTopLine+1, TRUE);
3527 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3529 if (!m_pMainFrame->m_bWrapLines && ((m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()) <= m_nOffsetChar))
3531 ScrollAllSide(-1);
3532 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3534 if (!m_pMainFrame->m_bWrapLines && (charIndex >= (GetScreenChars()+m_nOffsetChar-4)))
3536 ScrollAllSide(1);
3537 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3543 CView::OnTimer(nIDEvent);
3546 void CBaseView::ShowDiffLines(int nLine)
3548 if ((nLine < m_nTopLine)||(nLine >= GetLineCount()))
3550 m_pwndLineDiffBar->ShowLines(nLine);
3551 nLine = -1;
3552 m_nMouseLine = nLine;
3553 return;
3556 if ((!m_pwndRight)||(!m_pwndLeft))
3557 return;
3558 if(m_pMainFrame->m_bOneWay)
3559 return;
3561 nLine = (nLine > (int)m_pwndRight->m_Screen2View.size() ? -1 : nLine);
3562 nLine = (nLine > (int)m_pwndLeft->m_Screen2View.size() ? -1 : nLine);
3564 if (nLine < 0)
3565 return;
3567 if (nLine != m_nMouseLine)
3569 if (nLine >= GetLineCount())
3570 nLine = -1;
3571 m_nMouseLine = nLine;
3572 m_pwndLineDiffBar->ShowLines(nLine);
3574 m_pMainFrame->m_nMoveMovesToIgnore = MOVESTOIGNORE;
3577 const viewdata& CBaseView::GetEmptyLineData()
3579 static const viewdata emptyLine(L"", DIFFSTATE_EMPTY, -1, EOL_NOENDING, HIDESTATE_SHOWN);
3580 return emptyLine;
3583 void CBaseView::InsertViewEmptyLines(int nFirstView, int nCount)
3585 for (int i = 0; i < nCount; i++)
3587 InsertViewData(nFirstView, GetEmptyLineData());
3592 void CBaseView::UpdateCaret()
3594 POINT ptCaretPos = GetCaretPosition();
3595 ptCaretPos.y = std::max<int>(std::min<int>(ptCaretPos.y, GetLineCount()-1), 0);
3596 ptCaretPos.x = std::max<int>(std::min<int>(ptCaretPos.x, GetLineLength(ptCaretPos.y)), 0);
3597 SetCaretPosition(ptCaretPos);
3599 int nCaretOffset = CalculateActualOffset(ptCaretPos);
3601 if (m_bFocused &&
3602 ptCaretPos.y >= m_nTopLine &&
3603 ptCaretPos.y < (m_nTopLine+GetScreenLines()) &&
3604 nCaretOffset >= m_nOffsetChar &&
3605 nCaretOffset < (m_nOffsetChar+GetScreenChars()))
3607 POINT pt1 = TextToClient(ptCaretPos);
3608 if (m_bInsertMode)
3609 CreateSolidCaret(2, GetLineHeight());
3610 else
3612 POINT pt = { ptCaretPos.x + 1, ptCaretPos.y };
3613 POINT pt2 = TextToClient(pt);
3614 int width = max(GetCharWidth(), pt2.x - pt1.x);
3615 CreateSolidCaret(width, GetLineHeight());
3617 SetCaretPos(pt1);
3618 ShowCaret();
3620 else
3622 HideCaret();
3626 POINT CBaseView::ConvertScreenPosToView(const POINT& pt)
3628 POINT ptViewPos;
3629 ptViewPos.x = pt.x;
3631 int nSubLine = GetSubLineOffset(pt.y);
3632 if (nSubLine > 0)
3634 for (int nScreenLine = pt.y-1; nScreenLine >= pt.y-nSubLine; nScreenLine--)
3636 ptViewPos.x += GetLineChars(nScreenLine).GetLength();
3640 ptViewPos.y = GetViewLineForScreen(pt.y);
3641 return ptViewPos;
3644 POINT CBaseView::ConvertViewPosToScreen(const POINT& pt)
3646 POINT ptPos;
3647 int nViewLineLenLeft = GetViewLineLength(pt.y);
3648 ptPos.x = min(nViewLineLenLeft, pt.x);
3649 ptPos.y = FindScreenLineForViewLine(pt.y);
3650 if (GetViewLineForScreen(ptPos.y) != pt.y )
3652 ptPos.x = 0;
3654 else if (GetSubLineOffset(ptPos.y) >= 0) // sublined
3656 int nSubLineLength = GetLineChars(ptPos.y).GetLength();
3657 while (nSubLineLength < ptPos.x)
3659 ptPos.x -= nSubLineLength;
3660 nViewLineLenLeft -= nSubLineLength;
3661 ptPos.y++;
3662 nSubLineLength = GetLineChars(ptPos.y).GetLength();
3664 // last pos of non last sub-line go to start of next screen line
3665 // Note: while this works correctly, it's not what a user might expect:
3666 // cursor-right when the caret is before the last char of a wrapped line
3667 // now moves the caret to the next line. But users expect the caret to
3668 // move to the right of the last char instead, and with another cursor-right
3669 // keystroke to move the caret to the next line.
3670 // Basically, this would require to handle two caret positions for the same
3671 // logical position in the line string (one on the last position of the first line,
3672 // one on the first position of the new line. For non-wrapped lines this works
3673 // because there's an 'invisible' newline char at the end of the first line.
3674 if (nSubLineLength == ptPos.x && nViewLineLenLeft > nSubLineLength)
3676 ptPos.x = 0;
3677 ptPos.y++;
3681 return ptPos;
3685 void CBaseView::EnsureCaretVisible()
3687 POINT ptCaretPos = GetCaretPosition();
3688 int nCaretOffset = CalculateActualOffset(ptCaretPos);
3690 if (ptCaretPos.y < m_nTopLine)
3691 ScrollAllToLine(ptCaretPos.y);
3692 int screnLines = GetScreenLines();
3693 if (screnLines)
3695 if (ptCaretPos.y >= (m_nTopLine+screnLines)-1)
3696 ScrollAllToLine(ptCaretPos.y-screnLines+2);
3697 if (nCaretOffset < m_nOffsetChar)
3698 ScrollAllToChar(nCaretOffset);
3699 if (nCaretOffset > (m_nOffsetChar+GetScreenChars()-1))
3700 ScrollAllToChar(nCaretOffset-GetScreenChars()+1);
3704 int CBaseView::CalculateActualOffset(const POINT& point)
3706 int nLineIndex = point.y;
3707 int nCharIndex = point.x;
3708 ASSERT(nCharIndex >= 0);
3709 CString sLine = GetLineChars(nLineIndex);
3710 int nLineLength = sLine.GetLength();
3711 return CountExpandedChars(sLine, min(nCharIndex, nLineLength));
3714 int CBaseView::CalculateCharIndex(int nLineIndex, int nActualOffset)
3716 int nLength = GetLineLength(nLineIndex);
3717 int nSubLine = GetSubLineOffset(nLineIndex);
3718 if (nSubLine>=0)
3720 int nViewLine = GetViewLineForScreen(nLineIndex);
3721 if ((nViewLine>=0)&&(nViewLine < (int)m_ScreenedViewLine.size()))
3723 int nMultilineCount = CountMultiLines(nViewLine);
3724 if ((nMultilineCount>0) && (nSubLine<nMultilineCount-1))
3726 nLength--;
3730 CString Line = GetLineChars(nLineIndex);
3731 int nIndex = 0;
3732 int nOffset = 0;
3733 int nTabSize = GetTabSize();
3734 while (nOffset < nActualOffset && nIndex < nLength)
3736 if (Line.GetAt(nIndex) == L'\t')
3737 nOffset += (nTabSize - nOffset % nTabSize);
3738 else
3739 ++nOffset;
3740 ++nIndex;
3742 return nIndex;
3746 * @param xpos X coordinate in CBaseView
3747 * @param lineIndex logical line index (e.g. wrap/collapse)
3749 int CBaseView::CalcColFromPoint(int xpos, int lineIndex)
3751 int xpos2;
3752 CDC *pDC = GetDC();
3753 if (pDC)
3755 CString text = ExpandChars(GetLineChars(lineIndex), 0);
3756 int fit = text.GetLength();
3757 auto posBuffer = std::make_unique<int[]>(fit);
3758 pDC->SelectObject(GetFont()); // is this right font ?
3759 SIZE size;
3760 GetTextExtentExPoint(pDC->GetSafeHdc(), text, fit, INT_MAX, &fit, posBuffer.get(), &size);
3761 ReleaseDC(pDC);
3762 int lower = -1, upper = fit - 1;
3763 int xcheck = xpos - GetMarginWidth() + m_nOffsetChar * GetCharWidth();
3766 int middle = (upper + lower + 1) / 2;
3767 int width = posBuffer[middle];
3768 if (xcheck < width)
3769 upper = middle - 1;
3770 else
3771 lower = middle;
3772 } while (lower < upper);
3773 lower++;
3774 xpos2 = lower;
3775 if (lower < fit - 1)
3777 int charWidth = posBuffer[lower] - (lower > 0 ? posBuffer[lower - 1] : 0);
3778 if (posBuffer[lower] - xcheck <= charWidth / 2)
3779 xpos2++;
3782 else
3784 xpos2 = (xpos - GetMarginWidth()) / GetCharWidth() + m_nOffsetChar;
3785 if ((xpos % GetCharWidth()) >= (GetCharWidth()/2))
3786 xpos2++;
3788 return xpos2;
3791 POINT CBaseView::TextToClient(const POINT& point)
3793 POINT pt;
3794 int nOffsetScreenLine = max(0, (point.y - m_nTopLine));
3795 pt.y = nOffsetScreenLine * GetLineHeight();
3796 pt.x = CalculateActualOffset(point);
3798 int nLeft = GetMarginWidth() - m_nOffsetChar * GetCharWidth();
3799 CDC * pDC = GetDC();
3800 if (pDC)
3802 pDC->SelectObject(GetFont()); // is this right font ?
3803 int nScreenLine = nOffsetScreenLine + m_nTopLine;
3804 CString sLine = GetLineChars(nScreenLine);
3805 ExpandChars(sLine, 0, std::min<int>(pt.x, sLine.GetLength()), sLine);
3806 nLeft += pDC->GetTextExtent(sLine, pt.x).cx;
3807 ReleaseDC(pDC);
3808 } else {
3809 nLeft += pt.x * GetCharWidth();
3812 pt.x = nLeft;
3813 pt.y = (pt.y + GetLineHeight() + HEADERHEIGHT);
3814 return pt;
3817 void CBaseView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
3819 CView::OnChar(nChar, nRepCnt, nFlags);
3821 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
3822 bool bSkipSelectionClear = false;
3824 if (IsReadonly())
3825 return;
3827 if ((::GetKeyState(VK_LBUTTON) & 0x8000) != 0 ||
3828 (::GetKeyState(VK_RBUTTON) & 0x8000) != 0)
3830 return;
3833 if (!m_pViewData) // no data - nothing to do
3834 return;
3836 if (nChar == VK_F16)
3838 // generated by a ctrl+backspace - ignore.
3840 else if (nChar==VK_TAB && HasTextLineSelection())
3842 // change indentation for selected lines
3843 if (bShift)
3845 RemoveIndentationForSelectedBlock();
3847 else
3849 AddIndentationForSelectedBlock();
3851 bSkipSelectionClear = true;
3853 else if ((nChar > 31)||(nChar == VK_TAB))
3855 ResetUndoStep();
3856 RemoveSelectedText();
3857 POINT ptCaretViewPos = GetCaretViewPosition();
3858 int nViewLine = ptCaretViewPos.y;
3859 if ((nViewLine==0)&&(GetViewCount()==0))
3860 OnChar(VK_RETURN, 0, 0);
3861 int charCount = 1;
3862 viewdata lineData = GetViewData(nViewLine);
3863 if (nChar == VK_TAB)
3865 int indentChars = GetIndentCharsForLine(ptCaretViewPos.x, nViewLine);
3866 if (indentChars > 0)
3868 lineData.sLine.Insert(ptCaretViewPos.x, CString(L' ', indentChars));
3869 charCount = indentChars;
3871 else
3872 lineData.sLine.Insert(ptCaretViewPos.x, L'\t');
3874 else
3876 if (m_bInsertMode)
3877 lineData.sLine.Insert(ptCaretViewPos.x, (wchar_t)nChar);
3878 else
3880 if (lineData.sLine.GetLength() > ptCaretViewPos.x)
3881 lineData.sLine.SetAt(ptCaretViewPos.x, (wchar_t)nChar);
3882 else
3883 lineData.sLine.Insert(ptCaretViewPos.x, (wchar_t)nChar);
3886 if (IsStateEmpty(lineData.state))
3888 // if not last line set EOL
3889 for (int nCheckViewLine = nViewLine+1; nCheckViewLine < GetViewCount(); nCheckViewLine++)
3891 if (!IsViewLineEmpty(nCheckViewLine))
3893 lineData.ending = m_lineendings;
3894 break;
3897 // make sure previous (non empty) line have EOL set
3898 for (int nCheckViewLine = nViewLine-1; nCheckViewLine > 0; nCheckViewLine--)
3900 if (!IsViewLineEmpty(nCheckViewLine))
3902 if (GetViewLineEnding(nCheckViewLine) == EOL_NOENDING)
3904 SetViewLineEnding(nCheckViewLine, m_lineendings);
3906 break;
3910 lineData.state = DIFFSTATE_EDITED;
3911 bool bNeedRenumber = false;
3912 if (lineData.linenumber == -1)
3914 lineData.linenumber = 0;
3915 bNeedRenumber = true;
3917 SetViewData(nViewLine, lineData);
3918 SetModified();
3919 SaveUndoStep();
3920 BuildAllScreen2ViewVector(nViewLine);
3921 if (bNeedRenumber)
3923 UpdateViewLineNumbers();
3925 for (int i = 0; i < charCount; ++i)
3926 MoveCaretRight();
3927 UpdateGoalPos();
3929 else if (nChar == 10)
3931 int nViewLine = GetViewLineForScreen(GetCaretPosition().y);
3932 EOL eol = m_pViewData->GetLineEnding(nViewLine);
3933 EOL newEOL = EOL_CRLF;
3934 switch (eol)
3936 case EOL_CRLF:
3937 newEOL = EOL_CR;
3938 break;
3939 case EOL_CR:
3940 newEOL = EOL_LF;
3941 break;
3942 case EOL_LF:
3943 newEOL = EOL_CRLF;
3944 break;
3946 if (eol==EOL_NOENDING || eol==newEOL)
3947 // don't allow to change enter on empty line, or last text line (lines with EOL_NOENDING)
3948 // to add EOL on newly edited empty line hit enter
3949 // don't store into UNDO if no change happened
3950 // and don't mark file as modified
3951 return;
3952 AddUndoViewLine(nViewLine);
3953 m_pViewData->SetLineEnding(nViewLine, newEOL);
3954 m_pViewData->SetState(nViewLine, DIFFSTATE_EDITED);
3955 UpdateGoalPos();
3957 else if (nChar == VK_RETURN)
3959 // insert a new, fresh and empty line below the cursor
3960 RemoveSelectedText();
3962 CUndo::GetInstance().BeginGrouping();
3964 POINT ptCaretViewPos = GetCaretViewPosition();
3965 int nViewLine = ptCaretViewPos.y;
3966 int nLeft = ptCaretViewPos.x;
3967 CString sLine = GetViewLineChars(nViewLine);
3968 CString sLineLeft = sLine.Left(nLeft);
3969 CString sLineRight = sLine.Right(sLine.GetLength() - nLeft);
3970 EOL eOriginalEnding = EOL_AUTOLINE;
3971 if (m_pViewData->GetCount() > nViewLine)
3972 eOriginalEnding = GetViewLineEnding(nViewLine);
3974 if (!sLineRight.IsEmpty() || (eOriginalEnding!=m_lineendings))
3976 viewdata newFirstLine(sLineLeft, DIFFSTATE_EDITED, 1, m_lineendings, HIDESTATE_SHOWN);
3977 SetViewData(nViewLine, newFirstLine);
3980 int nInsertLine = (m_pViewData->GetCount()==0) ? 0 : nViewLine + 1;
3981 viewdata newLastLine(sLineRight, DIFFSTATE_EDITED, 1, eOriginalEnding, HIDESTATE_SHOWN);
3982 InsertViewData(nInsertLine, newLastLine);
3983 SetModified();
3984 SaveUndoStep();
3986 // adds new line everywhere except me
3987 if (IsViewGood(m_pwndLeft) && m_pwndLeft!=this)
3989 m_pwndLeft->InsertViewEmptyLines(nInsertLine, 1);
3991 if (IsViewGood(m_pwndRight) && m_pwndRight!=this)
3993 m_pwndRight->InsertViewEmptyLines(nInsertLine, 1);
3995 if (IsViewGood(m_pwndBottom) && m_pwndBottom!=this)
3997 m_pwndBottom->InsertViewEmptyLines(nInsertLine, 1);
3999 SaveUndoStep();
4001 UpdateViewLineNumbers();
4002 SaveUndoStep();
4003 CUndo::GetInstance().EndGrouping();
4005 BuildAllScreen2ViewVector();
4006 // move the cursor to the new line
4007 ptCaretViewPos = SetupPoint(0, nViewLine+1);
4008 SetCaretAndGoalViewPosition(ptCaretViewPos);
4010 else
4011 return; // Unknown control character -- ignore it.
4012 if (!bSkipSelectionClear)
4013 ClearSelection();
4014 EnsureCaretVisible();
4015 UpdateCaret();
4016 Invalidate(FALSE);
4019 void CBaseView::AddUndoViewLine(int nViewLine, bool bAddEmptyLine)
4021 ResetUndoStep();
4022 m_AllState.left.AddViewLineFromView(m_pwndLeft, nViewLine, bAddEmptyLine);
4023 m_AllState.right.AddViewLineFromView(m_pwndRight, nViewLine, bAddEmptyLine);
4024 m_AllState.bottom.AddViewLineFromView(m_pwndBottom, nViewLine, bAddEmptyLine);
4025 SetModified();
4026 SaveUndoStep();
4027 RecalcAllVertScrollBars();
4028 Invalidate(FALSE);
4031 void CBaseView::AddEmptyViewLine(int nViewLineIndex)
4033 if (!m_pViewData)
4034 return;
4035 int viewLine = nViewLineIndex;
4036 EOL ending = m_pViewData->GetLineEnding(viewLine);
4037 if (ending == EOL_NOENDING)
4039 ending = m_lineendings;
4041 viewdata newLine(L"", DIFFSTATE_EDITED, -1, ending, HIDESTATE_SHOWN);
4042 if (IsTarget()) // TODO: once more wievs will writable this is not correct anymore
4044 CString sPartLine = GetViewLineChars(nViewLineIndex);
4045 int nPosx = GetCaretPosition().x; // should be view pos ?
4046 m_pViewData->SetLine(viewLine, sPartLine.Left(nPosx));
4047 sPartLine = sPartLine.Mid(nPosx);
4048 newLine.sLine = sPartLine;
4050 m_pViewData->InsertData(viewLine+1, newLine);
4051 BuildAllScreen2ViewVector();
4054 void CBaseView::RemoveSelectedText()
4056 if (!m_pViewData)
4057 return;
4058 if (!HasTextSelection())
4059 return;
4061 // fix selection if starts or ends on empty line
4062 SetCaretViewPosition(m_ptSelectionViewPosEnd);
4063 while (IsViewLineEmpty(GetCaretViewPosition().y) && MoveCaretRight())
4066 m_ptSelectionViewPosEnd = GetCaretViewPosition();
4067 SetCaretViewPosition(m_ptSelectionViewPosStart);
4068 while (IsViewLineEmpty(GetCaretViewPosition().y) && MoveCaretRight())
4071 m_ptSelectionViewPosStart = GetCaretViewPosition();
4072 if (!HasTextSelection())
4074 ClearSelection();
4075 return;
4078 // We want to undo the insertion in a single step.
4079 ResetUndoStep();
4080 CUndo::GetInstance().BeginGrouping();
4082 // combine first and last line
4083 viewdata oFirstLine = GetViewData(m_ptSelectionViewPosStart.y);
4084 viewdata oLastLine = GetViewData(m_ptSelectionViewPosEnd.y);
4085 oFirstLine.sLine = oFirstLine.sLine.Left(m_ptSelectionViewPosStart.x) + oLastLine.sLine.Mid(m_ptSelectionViewPosEnd.x);
4086 oFirstLine.ending = oLastLine.ending;
4087 oFirstLine.state = DIFFSTATE_EDITED;
4088 SetViewData(m_ptSelectionViewPosStart.y, oFirstLine);
4090 // clean up middle lines if any
4091 if (m_ptSelectionViewPosStart.y != m_ptSelectionViewPosEnd.y)
4093 viewdata oEmptyLine = GetEmptyLineData();
4094 for (int nViewLine = m_ptSelectionViewPosStart.y+1; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
4096 SetViewData(nViewLine, oEmptyLine);
4098 SaveUndoStep();
4100 if (CleanEmptyLines())
4102 BuildAllScreen2ViewVector(); // schedule full rebuild
4104 SaveUndoStep();
4105 UpdateViewLineNumbers();
4108 SetModified(); //TODO set modified only if real data was changed
4109 SaveUndoStep();
4110 CUndo::GetInstance().EndGrouping();
4112 BuildAllScreen2ViewVector(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
4113 SetCaretViewPosition(m_ptSelectionViewPosStart);
4114 UpdateGoalPos();
4115 ClearSelection();
4116 UpdateCaret();
4117 EnsureCaretVisible();
4118 Invalidate(FALSE);
4121 void CBaseView::PasteText()
4123 if (!OpenClipboard())
4124 return;
4126 CString sClipboardText;
4127 HGLOBAL hglb = GetClipboardData(CF_TEXT);
4128 if (hglb)
4130 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
4131 sClipboardText = CString(lpstr);
4132 GlobalUnlock(hglb);
4134 hglb = GetClipboardData(CF_UNICODETEXT);
4135 if (hglb)
4137 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
4138 sClipboardText = lpstr;
4139 GlobalUnlock(hglb);
4141 CloseClipboard();
4143 if (sClipboardText.IsEmpty())
4144 return;
4146 sClipboardText.Replace(L"\r\n", L"\r");
4147 sClipboardText.Replace('\n', '\r');
4149 InsertText(sClipboardText);
4152 void CBaseView::OnCaretDown()
4154 POINT ptCaretPos = GetCaretPosition();
4155 int nLine = ptCaretPos.y;
4156 int nNextLine = nLine + 1;
4157 if (nNextLine >= GetLineCount()) // already at last line
4159 return;
4162 POINT ptCaretViewPos = GetCaretViewPosition();
4163 int nViewLine = ptCaretViewPos.y;
4164 int nNextViewLine = GetViewLineForScreen(nNextLine);
4165 if (!((nNextViewLine == nViewLine) && (GetSubLineOffset(nNextLine)<CountMultiLines(nNextViewLine)))) // not on same view line
4167 // find next suitable screen line
4168 while ((nNextViewLine == nViewLine) || IsViewLineHidden(nNextViewLine))
4170 nNextLine++;
4171 if (nNextLine >= GetLineCount())
4173 return;
4175 nNextViewLine = GetViewLineForScreen(nNextLine);
4178 ptCaretPos.y = nNextLine;
4179 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
4180 SetCaretPosition(ptCaretPos);
4181 OnCaretMove(MOVELEFT);
4182 ShowDiffLines(ptCaretPos.y);
4185 bool CBaseView::MoveCaretLeft()
4187 POINT ptCaretViewPos = GetCaretViewPosition();
4189 //int nViewLine = ptCaretViewPos.y;
4190 if (ptCaretViewPos.x == 0)
4192 int nPrevLine = GetCaretPosition().y;
4193 int nPrevViewLine;
4194 do {
4195 nPrevLine--;
4196 if (nPrevLine < 0)
4198 return false;
4200 nPrevViewLine = GetViewLineForScreen(nPrevLine);
4201 } while ((GetSubLineOffset(nPrevLine) >= CountMultiLines(nPrevViewLine)) || IsViewLineHidden(nPrevViewLine));
4202 ptCaretViewPos = ConvertScreenPosToView(SetupPoint(GetLineLength(nPrevLine), nPrevLine));
4203 ShowDiffLines(nPrevLine);
4205 else
4206 --ptCaretViewPos.x;
4208 SetCaretAndGoalViewPosition(ptCaretViewPos);
4209 return true;
4212 bool CBaseView::MoveCaretRight()
4214 POINT ptCaretViewPos = GetCaretViewPosition();
4216 int nViewLine = ptCaretViewPos.y;
4217 int nViewLineLen = GetViewLineLength(nViewLine);
4218 if (ptCaretViewPos.x >= nViewLineLen)
4220 int nNextLine = GetCaretPosition().y;
4221 int nNextViewLine;
4222 do {
4223 nNextLine++;
4224 if (nNextLine >= GetLineCount())
4226 return false;
4228 nNextViewLine = GetViewLineForScreen(nNextLine);
4229 } while (nNextViewLine == nViewLine || IsViewLineHidden(nNextViewLine));
4230 ptCaretViewPos.y = nNextViewLine;
4231 ptCaretViewPos.x = 0;
4232 ShowDiffLines(nNextLine);
4234 else
4235 ++ptCaretViewPos.x;
4237 SetCaretAndGoalViewPosition(ptCaretViewPos);
4238 return true;
4241 void CBaseView::UpdateGoalPos()
4243 m_nCaretGoalPos = CalculateActualOffset(GetCaretPosition());
4246 void CBaseView::OnCaretLeft()
4248 MoveCaretLeft();
4249 OnCaretMove(MOVELEFT);
4252 void CBaseView::OnCaretRight()
4254 MoveCaretRight();
4255 OnCaretMove(MOVERIGHT);
4258 void CBaseView::OnCaretUp()
4260 POINT ptCaretPos = GetCaretPosition();
4261 int nLine = ptCaretPos.y;
4262 if (nLine <= 0) // already at first line
4264 return;
4266 int nPrevLine = nLine - 1;
4268 POINT ptCaretViewPos = GetCaretViewPosition();
4269 int nViewLine = ptCaretViewPos.y;
4270 int nPrevViewLine = GetViewLineForScreen(nPrevLine);
4271 if (nPrevViewLine != nViewLine) // not on same view line
4273 // find previous suitable screen line
4274 while ((GetSubLineOffset(nPrevLine) >= CountMultiLines(nPrevViewLine)) || IsViewLineHidden(nPrevViewLine))
4276 if (nPrevLine <= 0)
4278 return;
4280 nPrevLine--;
4281 nPrevViewLine = GetViewLineForScreen(nPrevLine);
4284 ptCaretPos.y = nPrevLine;
4285 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
4286 SetCaretPosition(ptCaretPos);
4287 OnCaretMove(MOVELEFT);
4288 ShowDiffLines(ptCaretPos.y);
4291 bool CBaseView::IsWordSeparator(const wchar_t ch) const
4293 switch (GetCharGroup(ch))
4295 case CHG_CONTROL:
4296 case CHG_WHITESPACE:
4297 case CHG_WORDSEPARATOR:
4298 return true;
4300 return false;
4303 bool CBaseView::IsCaretAtWordBoundary()
4305 POINT ptViewCaret = GetCaretViewPosition();
4306 CString line = GetViewLineChars(ptViewCaret.y);
4307 if (line.IsEmpty())
4308 return false; // no boundary at the empty lines
4309 if (ptViewCaret.x == 0)
4310 return !IsWordSeparator(line.GetAt(ptViewCaret.x));
4311 if (ptViewCaret.x >= GetViewLineLength(ptViewCaret.y))
4312 return !IsWordSeparator(line.GetAt(ptViewCaret.x - 1));
4313 return
4314 IsWordSeparator(line.GetAt(ptViewCaret.x)) !=
4315 IsWordSeparator(line.GetAt(ptViewCaret.x - 1));
4318 void CBaseView::UpdateViewsCaretPosition()
4320 POINT ptCaretPos = GetCaretPosition();
4321 if (m_pwndBottom && m_pwndBottom!=this)
4322 m_pwndBottom->UpdateCaretPosition(ptCaretPos);
4323 if (m_pwndLeft && m_pwndLeft!=this)
4324 m_pwndLeft->UpdateCaretPosition(ptCaretPos);
4325 if (m_pwndRight && m_pwndRight!=this)
4326 m_pwndRight->UpdateCaretPosition(ptCaretPos);
4329 void CBaseView::OnCaretWordleft()
4331 MoveCaretWordLeft();
4332 OnCaretMove(MOVELEFT);
4335 void CBaseView::OnCaretWordright()
4337 MoveCaretWordRight();
4338 OnCaretMove(MOVERIGHT);
4341 void CBaseView::MoveCaretWordLeft()
4343 while (MoveCaretLeft() && !IsCaretAtWordBoundary())
4348 void CBaseView::MoveCaretWordRight()
4350 while (MoveCaretRight() && !IsCaretAtWordBoundary())
4355 void CBaseView::ClearCurrentSelection()
4357 m_ptSelectionViewPosStart = GetCaretViewPosition();
4358 m_ptSelectionViewPosEnd = m_ptSelectionViewPosStart;
4359 m_ptSelectionViewPosOrigin = m_ptSelectionViewPosStart;
4360 m_nSelViewBlockStart = -1;
4361 m_nSelViewBlockEnd = -1;
4362 Invalidate(FALSE);
4365 void CBaseView::ClearSelection()
4367 if (m_pwndLeft)
4368 m_pwndLeft->ClearCurrentSelection();
4369 if (m_pwndRight)
4370 m_pwndRight->ClearCurrentSelection();
4371 if (m_pwndBottom)
4372 m_pwndBottom->ClearCurrentSelection();
4375 void CBaseView::AdjustSelection(bool bMoveLeft)
4377 POINT ptCaretViewPos = GetCaretViewPosition();
4378 if (ArePointsSame(m_ptSelectionViewPosOrigin, SetupPoint(-1, -1)))
4380 // select all have been used recently update origin
4381 m_ptSelectionViewPosOrigin = bMoveLeft ? m_ptSelectionViewPosEnd : m_ptSelectionViewPosStart;
4383 if ((ptCaretViewPos.y < m_ptSelectionViewPosOrigin.y) ||
4384 (ptCaretViewPos.y == m_ptSelectionViewPosOrigin.y && ptCaretViewPos.x <= m_ptSelectionViewPosOrigin.x))
4386 m_ptSelectionViewPosStart = ptCaretViewPos;
4387 m_ptSelectionViewPosEnd = m_ptSelectionViewPosOrigin;
4389 else
4391 m_ptSelectionViewPosStart = m_ptSelectionViewPosOrigin;
4392 m_ptSelectionViewPosEnd = ptCaretViewPos;
4395 SetupAllViewSelection(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
4397 Invalidate(FALSE);
4400 void CBaseView::OnEditCut()
4402 if (IsWritable())
4404 OnEditCopy();
4405 RemoveSelectedText();
4409 void CBaseView::OnEditPaste()
4411 if (IsWritable())
4413 CUndo::GetInstance().BeginGrouping();
4414 RemoveSelectedText();
4415 PasteText();
4416 CUndo::GetInstance().EndGrouping();
4420 void CBaseView::DeleteFonts()
4422 for (int i=0; i<fontsCount; i++)
4424 if (m_apFonts[i])
4426 m_apFonts[i]->DeleteObject();
4427 delete m_apFonts[i];
4428 m_apFonts[i] = nullptr;
4433 void CBaseView::OnCaretMove(bool bMoveLeft)
4435 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
4436 OnCaretMove(bMoveLeft, bShift);
4439 void CBaseView::OnCaretMove(bool bMoveLeft, bool isShiftPressed)
4441 if(isShiftPressed)
4442 AdjustSelection(bMoveLeft);
4443 else
4444 ClearSelection();
4445 EnsureCaretVisible();
4446 UpdateCaret();
4449 void CBaseView::AddContextItems(CIconMenu& popup, DiffStates /*state*/)
4451 AddCutCopyAndPaste(popup);
4454 void CBaseView::AddCutCopyAndPaste(CIconMenu& popup)
4456 popup.AppendMenu(MF_SEPARATOR, NULL);
4457 CString temp;
4458 temp.LoadString(IDS_EDIT_COPY);
4459 popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_COPY, temp);
4460 if (IsWritable())
4462 temp.LoadString(IDS_EDIT_CUT);
4463 popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_CUT, temp);
4464 temp.LoadString(IDS_EDIT_PASTE);
4465 popup.AppendMenu(MF_STRING | (CAppUtils::HasClipboardFormat(CF_UNICODETEXT)||CAppUtils::HasClipboardFormat(CF_TEXT) ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_PASTE, temp);
4466 popup.AppendMenu(MF_SEPARATOR, NULL);
4470 void CBaseView::CompensateForKeyboard(CPoint& point)
4472 // if the context menu is invoked through the keyboard, we have to use
4473 // a calculated position on where to anchor the menu on
4474 if (ArePointsSame(point, SetupPoint(-1, -1)))
4476 CRect rect;
4477 GetWindowRect(&rect);
4478 point = rect.CenterPoint();
4482 HICON CBaseView::LoadIcon(WORD iconId)
4484 int iconWidth = GetSystemMetrics(SM_CXSMICON);
4485 int iconHeight = GetSystemMetrics(SM_CYSMICON);
4486 HANDLE icon = ::LoadImage( AfxGetResourceHandle(), MAKEINTRESOURCE(iconId),
4487 IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR);
4488 return (HICON)icon;
4491 void CBaseView::ReleaseBitmap()
4493 if (m_pCacheBitmap)
4495 m_pCacheBitmap->DeleteObject();
4496 delete m_pCacheBitmap;
4497 m_pCacheBitmap = nullptr;
4501 void CBaseView::BuildMarkedWordArray()
4503 int lineCount = GetLineCount();
4504 m_arMarkedWordLines.clear();
4505 m_arMarkedWordLines.reserve(lineCount);
4506 bool bDoit = !m_sMarkedWord.IsEmpty();
4507 for (int i = 0; i < lineCount; ++i)
4509 if (bDoit)
4511 CString line = GetLineChars(i);
4513 if (!line.IsEmpty())
4515 int found = 0;
4516 int nMarkStart = -1;
4517 while ((nMarkStart = line.Find(m_sMarkedWord, ++nMarkStart)) >= 0)
4519 int nMarkEnd = nMarkStart + m_sMarkedWord.GetLength();
4520 ECharGroup eLeft = GetCharGroup(line, nMarkStart - 1);
4521 ECharGroup eStart = GetCharGroup(line, nMarkStart);
4522 if (eLeft != eStart)
4524 ECharGroup eRight = GetCharGroup(line, nMarkEnd);
4525 ECharGroup eEnd = GetCharGroup(line, nMarkEnd - 1);
4526 if (eRight != eEnd)
4528 found = 1;
4529 break;
4533 m_arMarkedWordLines.push_back(found);
4535 else
4536 m_arMarkedWordLines.push_back(0);
4538 else
4539 m_arMarkedWordLines.push_back(0);
4543 void CBaseView::BuildFindStringArray()
4545 int lineCount = GetLineCount();
4546 m_arFindStringLines.clear();
4547 m_arFindStringLines.reserve(lineCount);
4548 bool bDoit = !m_sFindText.IsEmpty();
4549 int s = 0;
4550 int e = 0;
4551 for (int i = 0; i < lineCount; ++i)
4553 if (bDoit)
4555 CString line = GetLineChars(i);
4557 if (!line.IsEmpty())
4559 switch (m_pViewData->GetState(GetViewLineForScreen(i)))
4561 case DIFFSTATE_EMPTY:
4562 m_arFindStringLines.push_back(0);
4563 break;
4564 case DIFFSTATE_UNKNOWN:
4565 case DIFFSTATE_NORMAL:
4566 case DIFFSTATE_FILTEREDDIFF:
4567 if (m_bLimitToDiff)
4569 m_arFindStringLines.push_back(0);
4570 break;
4572 case DIFFSTATE_REMOVED:
4573 case DIFFSTATE_REMOVEDWHITESPACE:
4574 case DIFFSTATE_ADDED:
4575 case DIFFSTATE_ADDEDWHITESPACE:
4576 case DIFFSTATE_WHITESPACE:
4577 case DIFFSTATE_WHITESPACE_DIFF:
4578 case DIFFSTATE_CONFLICTED:
4579 case DIFFSTATE_CONFLICTED_IGNORED:
4580 case DIFFSTATE_CONFLICTADDED:
4581 case DIFFSTATE_CONFLICTEMPTY:
4582 case DIFFSTATE_CONFLICTRESOLVED:
4583 case DIFFSTATE_IDENTICALREMOVED:
4584 case DIFFSTATE_IDENTICALADDED:
4585 case DIFFSTATE_THEIRSREMOVED:
4586 case DIFFSTATE_THEIRSADDED:
4587 case DIFFSTATE_YOURSREMOVED:
4588 case DIFFSTATE_YOURSADDED:
4589 case DIFFSTATE_EDITED:
4591 if (!m_bMatchCase)
4592 line = line.MakeLower();
4593 s = 0;
4594 e = 0;
4595 int match = 0;
4596 while (StringFound(line, SearchNext, s, e))
4598 match++;
4599 s = e;
4600 e = 0;
4602 m_arFindStringLines.push_back(match);
4603 break;
4605 default:
4606 m_arFindStringLines.push_back(0);
4609 else
4610 m_arFindStringLines.push_back(0);
4612 else
4613 m_arFindStringLines.push_back(0);
4615 UpdateLocator();
4618 bool CBaseView::GetInlineDiffPositions(int nViewLine, std::vector<inlineDiffPos>& positions)
4620 if (!m_bShowInlineDiff)
4621 return false;
4622 if (m_pwndBottom && !(m_pwndBottom->IsHidden()))
4623 return false;
4625 if (!m_pViewData || m_pViewData->GetCount() <= nViewLine)
4626 return false;
4627 const CString &sLine = m_pViewData->GetLine(nViewLine);
4628 if (sLine.IsEmpty())
4629 return false;
4631 CheckOtherView();
4632 if (!m_pOtherViewData)
4633 return false;
4635 const CString &sDiffLine = m_pOtherViewData->GetLine(nViewLine);
4636 if (sDiffLine.IsEmpty())
4637 return false;
4639 svn_diff_t* diff = nullptr;
4640 auto pLine1 = (this == m_pwndLeft) ? &sLine : &sDiffLine;
4641 auto pLine2 = (this == m_pwndLeft) ? &sDiffLine : &sLine;
4642 m_svnlinediff.Diff(&diff, *pLine1, pLine1->GetLength(), *pLine2, pLine2->GetLength(), m_bInlineWordDiff);
4643 if (!diff || !SVNLineDiff::ShowInlineDiff(diff))
4644 return false;
4646 size_t lineoffset = 0;
4647 size_t position = 0;
4648 while (diff)
4650 if (this == m_pwndRight)
4652 apr_off_t nTmp = diff->modified_length;
4653 diff->modified_length = diff->original_length;
4654 diff->original_length = nTmp;
4656 nTmp = diff->modified_start;
4657 diff->modified_start = diff->original_start;
4658 diff->original_start = nTmp;
4660 apr_off_t len = diff->original_length;
4661 size_t oldpos = position;
4663 for (apr_off_t i = 0; i < len; ++i)
4665 position += (this == m_pwndRight) ? m_svnlinediff.m_line2tokens[lineoffset].size() : m_svnlinediff.m_line1tokens[lineoffset].size();
4666 lineoffset++;
4669 if (diff->type == svn_diff__type_diff_modified)
4671 inlineDiffPos p;
4672 p.start = oldpos;
4673 p.end = position;
4674 positions.push_back(p);
4677 diff = diff->next;
4680 return !positions.empty();
4683 void CBaseView::OnNavigateNextinlinediff()
4685 int nX;
4686 if (GetNextInlineDiff(nX))
4688 POINT ptCaretViewPos = GetCaretViewPosition();
4689 ptCaretViewPos.x = nX;
4690 SetCaretAndGoalViewPosition(ptCaretViewPos);
4691 m_ptSelectionViewPosOrigin = ptCaretViewPos;
4692 EnsureCaretVisible();
4696 void CBaseView::OnNavigatePrevinlinediff()
4698 int nX;
4699 if (GetPrevInlineDiff(nX))
4701 POINT ptCaretViewPos = GetCaretViewPosition();
4702 ptCaretViewPos.x = nX;
4703 SetCaretAndGoalViewPosition(ptCaretViewPos);
4704 m_ptSelectionViewPosOrigin = ptCaretViewPos;
4705 EnsureCaretVisible();
4709 bool CBaseView::HasNextInlineDiff()
4711 int nPos;
4712 return GetNextInlineDiff(nPos);
4715 bool CBaseView::GetNextInlineDiff(int & nPos)
4717 POINT ptCaretViewPos = GetCaretViewPosition();
4718 std::vector<inlineDiffPos> positions;
4719 if (GetInlineDiffPositions(ptCaretViewPos.y, positions))
4721 for (auto it = positions.cbegin(); it != positions.cend(); ++it)
4723 if (it->start > ptCaretViewPos.x)
4725 nPos = (LONG)it->start;
4726 return true;
4728 if (it->end > ptCaretViewPos.x)
4730 nPos = (LONG)it->end;
4731 return true;
4735 return false;
4738 bool CBaseView::HasPrevInlineDiff()
4740 int nPos;
4741 return GetPrevInlineDiff(nPos);
4744 bool CBaseView::GetPrevInlineDiff(int & nPos)
4746 POINT ptCaretViewPos = GetCaretViewPosition();
4747 std::vector<inlineDiffPos> positions;
4748 if (GetInlineDiffPositions(ptCaretViewPos.y, positions))
4750 for (auto it = positions.crbegin(); it != positions.crend(); ++it)
4752 if ( it->end < ptCaretViewPos.x)
4754 nPos = (LONG)it->end;
4755 return true;
4757 if ( it->start < ptCaretViewPos.x)
4759 nPos = (LONG)it->start;
4760 return true;
4764 return false;
4767 CBaseView * CBaseView::GetFirstGoodView()
4769 if (IsViewGood(m_pwndLeft))
4770 return m_pwndLeft;
4771 if (IsViewGood(m_pwndRight))
4772 return m_pwndRight;
4773 if (IsViewGood(m_pwndBottom))
4774 return m_pwndBottom;
4775 return nullptr;
4778 void CBaseView::BuildAllScreen2ViewVector()
4780 CBaseView * p_pwndView = GetFirstGoodView();
4781 if (p_pwndView)
4783 m_Screen2View.ScheduleFullRebuild(p_pwndView->m_pViewData);
4787 void CBaseView::BuildAllScreen2ViewVector(int nViewLine)
4789 BuildAllScreen2ViewVector(nViewLine, nViewLine);
4792 void CBaseView::BuildAllScreen2ViewVector(int nFirstViewLine, int nLastViewLine)
4794 CBaseView * p_pwndView = GetFirstGoodView();
4795 if (p_pwndView)
4797 m_Screen2View.ScheduleRangeRebuild(p_pwndView->m_pViewData, nFirstViewLine, nLastViewLine);
4801 void CBaseView::UpdateViewLineNumbers()
4803 int nLineNumber = 0;
4804 int nViewLineCount = GetViewCount();
4805 for (int nViewLine = 0; nViewLine < nViewLineCount; nViewLine++)
4807 int oldLine = (int)GetViewLineNumber(nViewLine);
4808 if (oldLine >= 0)
4809 SetViewLineNumber(nViewLine, nLineNumber++);
4811 m_nDigits = 0;
4814 int CBaseView::CleanEmptyLines()
4816 int nRemovedCount = 0;
4817 int nViewLineCount = GetViewCount();
4818 bool bCheckLeft = IsViewGood(m_pwndLeft);
4819 bool bCheckRight = IsViewGood(m_pwndRight);
4820 bool bCheckBottom = IsViewGood(m_pwndBottom);
4821 for (int nViewLine = 0; nViewLine < nViewLineCount; )
4823 bool bAllEmpty = true;
4824 bAllEmpty &= !bCheckLeft || IsStateEmpty(m_pwndLeft->GetViewState(nViewLine));
4825 bAllEmpty &= !bCheckRight || IsStateEmpty(m_pwndRight->GetViewState(nViewLine));
4826 bAllEmpty &= !bCheckBottom || IsStateEmpty(m_pwndBottom->GetViewState(nViewLine));
4827 if (bAllEmpty)
4829 if (bCheckLeft)
4831 m_pwndLeft->RemoveViewData(nViewLine);
4833 if (bCheckRight)
4835 m_pwndRight->RemoveViewData(nViewLine);
4837 if (bCheckBottom)
4839 m_pwndBottom->RemoveViewData(nViewLine);
4841 if (CUndo::GetInstance().IsGrouping()) // if use group undo -> ensure back adding goes in right (reversed) order
4843 SaveUndoStep();
4845 nViewLineCount--;
4846 nRemovedCount++;
4847 continue;
4849 nViewLine++;
4851 return nRemovedCount;
4854 int CBaseView::FindScreenLineForViewLine( int viewLine )
4856 return m_Screen2View.FindScreenLineForViewLine(viewLine);
4859 int CBaseView::CountMultiLines( int nViewLine )
4861 if (m_ScreenedViewLine.empty())
4862 return 0; // in case the view is completely empty
4864 ASSERT(nViewLine < (int)m_ScreenedViewLine.size());
4866 if (m_ScreenedViewLine[nViewLine].bSublinesSet)
4868 return (int)m_ScreenedViewLine[nViewLine].SubLines.size();
4871 CString multiline = CStringUtils::WordWrap(m_pViewData->GetLine(nViewLine), GetScreenChars()-1, false, true, GetTabSize()); // GetMultiLine(nLine);
4873 TScreenedViewLine oScreenedLine;
4874 // tokenize string
4875 int prevpos = 0;
4876 int pos = 0;
4877 while ((pos = multiline.Find('\n', pos)) >= 0)
4879 oScreenedLine.SubLines.push_back(multiline.Mid(prevpos, pos-prevpos)); // WordWrap could return vector/list of lines instead of string
4880 pos++;
4881 prevpos = pos;
4883 oScreenedLine.SubLines.push_back(multiline.Mid(prevpos));
4884 oScreenedLine.bSublinesSet = true;
4885 m_ScreenedViewLine[nViewLine] = oScreenedLine;
4887 return CountMultiLines(nViewLine);
4890 /// prepare inline diff cache
4891 LineColors & CBaseView::GetLineColors(int nViewLine)
4893 ASSERT(nViewLine < (int)m_ScreenedViewLine.size());
4895 if (m_bWhitespaceInlineDiffs)
4897 if (m_ScreenedViewLine[nViewLine].bLineColorsSetWhiteSpace)
4898 return m_ScreenedViewLine[nViewLine].lineColorsWhiteSpace;
4900 else
4902 if (m_ScreenedViewLine[nViewLine].bLineColorsSet)
4903 return m_ScreenedViewLine[nViewLine].lineColors;
4906 LineColors oLineColors;
4907 // set main line color
4908 COLORREF crBkgnd, crText;
4909 DiffStates diffState = m_pViewData->GetState(nViewLine);
4910 CDiffColors::GetInstance().GetColors(diffState, crBkgnd, crText);
4911 oLineColors.SetColor(0, crText, crBkgnd);
4913 do {
4914 if (!m_bShowInlineDiff)
4915 break;
4917 if (((diffState == DIFFSTATE_NORMAL) || (diffState == DIFFSTATE_FILTEREDDIFF)) && (!m_bWhitespaceInlineDiffs))
4918 break;
4920 CString sLine = GetViewLineChars(nViewLine);
4921 if (sLine.IsEmpty())
4922 break;
4923 CString sDiffLine;
4924 if (!m_pOtherView)
4926 switch (diffState)
4928 case DIFFSTATE_ADDED:
4930 if ((nViewLine > 0) && (m_pViewData->GetState(nViewLine - 1) == DIFFSTATE_REMOVED))
4931 sDiffLine = GetViewLineChars(nViewLine - 1);
4933 break;
4934 case DIFFSTATE_REMOVED:
4936 if (((nViewLine + 1) < m_pViewData->GetCount()) && (m_pViewData->GetState(nViewLine + 1) == DIFFSTATE_ADDED))
4937 sDiffLine = GetViewLineChars(nViewLine + 1);
4939 break;
4942 else
4943 sDiffLine = m_pOtherView->GetViewLineChars(nViewLine);
4944 if (sDiffLine.IsEmpty())
4945 break;
4947 svn_diff_t* diff = nullptr;
4948 if (sLine.GetLength() > (int)m_nInlineDiffMaxLineLength)
4949 break;
4950 auto pLine1 = (this == m_pwndLeft) ? &sLine : &sDiffLine;
4951 auto pLine2 = (this == m_pwndLeft) ? &sDiffLine : &sLine;
4952 m_svnlinediff.Diff(&diff, *pLine1, pLine1->GetLength(), *pLine2, pLine2->GetLength(), m_bInlineWordDiff);
4953 if (!diff || !SVNLineDiff::ShowInlineDiff(diff) || !diff->next)
4954 break;
4956 int lineoffset = 0;
4957 int nTextStartOffset = 0;
4958 std::map<int, COLORREF> removedPositions;
4959 while (diff)
4961 if (this == m_pwndRight)
4963 apr_off_t nTmp = diff->modified_length;
4964 diff->modified_length = diff->original_length;
4965 diff->original_length = nTmp;
4967 nTmp = diff->modified_start;
4968 diff->modified_start = diff->original_start;
4969 diff->original_start = nTmp;
4971 apr_off_t len = diff->original_length;
4973 size_t nTextLength = 0;
4974 for (int i = 0; i < len; ++i)
4976 nTextLength += (this == m_pwndRight) ? m_svnlinediff.m_line2tokens[lineoffset].size() : m_svnlinediff.m_line1tokens[lineoffset].size();
4977 lineoffset++;
4979 bool bInlineDiff = (diff->type == svn_diff__type_diff_modified);
4981 CDiffColors::GetInstance().GetColors(diffState, crBkgnd, crText);
4982 if ((m_bShowInlineDiff)&&(bInlineDiff))
4984 crBkgnd = InlineViewLineDiffColor(nViewLine);
4986 else if (m_pOtherView)
4988 crBkgnd = m_ModifiedBk;
4991 if (len < diff->modified_length)
4993 removedPositions[nTextStartOffset] = m_InlineRemovedBk;
4995 oLineColors.SetColor(nTextStartOffset, crText, crBkgnd);
4997 nTextStartOffset += (int)nTextLength;
4998 diff = diff->next;
5000 for (std::map<int, COLORREF>::const_iterator it = removedPositions.begin(); it != removedPositions.end(); ++it)
5002 oLineColors.AddShotColor(it->first, it->second);
5004 } while (false); // error catch
5006 if (!m_bWhitespaceInlineDiffs)
5008 m_ScreenedViewLine[nViewLine].lineColors = oLineColors;
5009 m_ScreenedViewLine[nViewLine].bLineColorsSet = true;
5011 else
5013 m_ScreenedViewLine[nViewLine].lineColorsWhiteSpace = oLineColors;
5014 m_ScreenedViewLine[nViewLine].bLineColorsSetWhiteSpace = true;
5017 return GetLineColors(nViewLine);
5020 void CBaseView::OnEditSelectall()
5022 if (!m_pViewData)
5023 return;
5024 int nLastViewLine = m_pViewData->GetCount()-1;
5025 if (nLastViewLine < 0)
5026 return;
5027 SetupAllViewSelection(0, nLastViewLine);
5029 CString sLine = GetViewLineChars(nLastViewLine);
5030 m_ptSelectionViewPosStart = SetupPoint(0, 0);
5031 m_ptSelectionViewPosEnd = SetupPoint(sLine.GetLength(), nLastViewLine);
5032 m_ptSelectionViewPosOrigin = SetupPoint(-1, -1);
5034 UpdateWindow();
5037 void CBaseView::FilterWhitespaces(CString& first, CString& second)
5039 FilterWhitespaces(first);
5040 FilterWhitespaces(second);
5043 void CBaseView::FilterWhitespaces(CString& line)
5045 line.Remove(' ');
5046 line.Remove('\t');
5047 line.Remove('\r');
5048 line.Remove('\n');
5051 int CBaseView::GetButtonEventLineIndex(const POINT& point)
5053 const int nLineFromTop = (point.y - HEADERHEIGHT) / GetLineHeight();
5054 int nEventLine = nLineFromTop + m_nTopLine;
5055 nEventLine--; //we need the index
5056 return nEventLine;
5060 BOOL CBaseView::PreTranslateMessage(MSG* pMsg)
5062 if (RelayTrippleClick(pMsg))
5063 return TRUE;
5064 return CView::PreTranslateMessage(pMsg);
5068 void CBaseView::ResetUndoStep()
5070 m_AllState.Clear();
5073 void CBaseView::SaveUndoStep()
5075 if (!m_AllState.IsEmpty())
5077 CUndo::GetInstance().AddState(m_AllState, GetCaretViewPosition());
5079 ResetUndoStep();
5082 void CBaseView::InsertViewData( int index, const CString& sLine, DiffStates state, int linenumber, EOL ending, HIDESTATE hide, int movedline )
5084 m_pState->addedlines.push_back(index);
5085 m_pViewData->InsertData(index, sLine, state, linenumber, ending, hide, movedline);
5088 void CBaseView::InsertViewData( int index, const viewdata& data )
5090 m_pState->addedlines.push_back(index);
5091 m_pViewData->InsertData(index, data);
5094 void CBaseView::RemoveViewData( int index )
5096 m_pState->removedlines[index] = m_pViewData->GetData(index);
5097 m_pViewData->RemoveData(index);
5100 void CBaseView::SetViewData( int index, const viewdata& data )
5102 m_pState->replacedlines[index] = m_pViewData->GetData(index);
5103 m_pViewData->SetData(index, data);
5106 void CBaseView::SetViewState( int index, DiffStates state )
5108 m_pState->linestates[index] = m_pViewData->GetState(index);
5109 m_pViewData->SetState(index, state);
5112 void CBaseView::SetViewLine( int index, const CString& sLine )
5114 m_pState->difflines[index] = m_pViewData->GetLine(index);
5115 m_pViewData->SetLine(index, sLine);
5118 void CBaseView::SetViewLineNumber( int index, int linenumber )
5120 int oldLineNumber = m_pViewData->GetLineNumber(index);
5121 if (oldLineNumber != linenumber) {
5122 m_pState->linelines[index] = oldLineNumber;
5123 m_pViewData->SetLineNumber(index, linenumber);
5127 void CBaseView::SetViewLineEnding( int index, EOL ending )
5129 m_pState->linesEOL[index] = m_pViewData->GetLineEnding(index);
5130 m_pViewData->SetLineEnding(index, ending);
5133 void CBaseView::SetViewMarked( int index, bool marked )
5135 m_pState->markedlines[index] = m_pViewData->GetMarked(index);
5136 m_pViewData->SetMarked(index, marked);
5140 BOOL CBaseView::GetViewSelection( int& start, int& end ) const
5142 if (HasSelection())
5144 start = m_nSelViewBlockStart;
5145 end = m_nSelViewBlockEnd;
5146 return true;
5148 return false;
5151 int CBaseView::Screen2View::GetViewLineForScreen( int screenLine )
5153 RebuildIfNecessary();
5154 if ((size() <= screenLine) || (screenLine < 0))
5155 return 0;
5156 return m_Screen2View[screenLine].nViewLine;
5159 int CBaseView::Screen2View::size()
5161 RebuildIfNecessary();
5162 return (int)m_Screen2View.size();
5165 int CBaseView::Screen2View::GetSubLineOffset( int screenLine )
5167 RebuildIfNecessary();
5168 if (size() <= screenLine)
5169 return 0;
5170 return m_Screen2View[screenLine].nViewSubLine;
5173 CBaseView::TScreenLineInfo CBaseView::Screen2View::GetScreenLineInfo( int screenLine )
5175 RebuildIfNecessary();
5176 return m_Screen2View[screenLine];
5180 doing partial rebuild, whole screen2view vector is built, but uses ScreenedViewLine cache to do it faster
5182 void CBaseView::Screen2View::RebuildIfNecessary()
5184 if (!m_pViewData)
5185 return; // rebuild not necessary
5187 FixScreenedCacheSize(m_pwndLeft);
5188 FixScreenedCacheSize(m_pwndRight);
5189 FixScreenedCacheSize(m_pwndBottom);
5190 if (!m_bFull)
5192 for (auto it = m_RebuildRanges.cbegin(); it != m_RebuildRanges.cend(); ++it)
5194 ResetScreenedViewLineCache(m_pwndLeft, *it);
5195 ResetScreenedViewLineCache(m_pwndRight, *it);
5196 ResetScreenedViewLineCache(m_pwndBottom, *it);
5199 else
5201 ResetScreenedViewLineCache(m_pwndLeft);
5202 ResetScreenedViewLineCache(m_pwndRight);
5203 ResetScreenedViewLineCache(m_pwndBottom);
5205 m_RebuildRanges.clear();
5206 m_bFull = false;
5208 size_t OldSize = m_Screen2View.size();
5209 m_Screen2View.clear();
5210 m_Screen2View.reserve(OldSize); // guess same size
5211 for (int i = 0; i < m_pViewData->GetCount(); ++i)
5213 if (m_pMainFrame->m_bCollapsed)
5215 while ((i < m_pViewData->GetCount())&&(m_pViewData->GetHideState(i) == HIDESTATE_HIDDEN))
5216 ++i;
5217 if (!(i < m_pViewData->GetCount()))
5218 break;
5220 TScreenLineInfo oLineInfo;
5221 oLineInfo.nViewLine = i;
5222 oLineInfo.nViewSubLine = -1; // no wrap
5223 if (m_pMainFrame->m_bWrapLines && !IsViewLineHidden(m_pViewData, i))
5225 int nMaxLines = 0;
5226 if (IsLeftViewGood())
5227 nMaxLines = std::max<int>(nMaxLines, m_pwndLeft->CountMultiLines(i));
5228 if (IsRightViewGood())
5229 nMaxLines = std::max<int>(nMaxLines, m_pwndRight->CountMultiLines(i));
5230 if (IsBottomViewGood())
5231 nMaxLines = std::max<int>(nMaxLines, m_pwndBottom->CountMultiLines(i));
5232 for (int l = 0; l < (nMaxLines-1); ++l)
5234 oLineInfo.nViewSubLine++;
5235 m_Screen2View.push_back(oLineInfo);
5237 oLineInfo.nViewSubLine++;
5239 m_Screen2View.push_back(oLineInfo);
5241 m_pViewData = nullptr;
5243 if (IsLeftViewGood())
5244 m_pwndLeft->BuildMarkedWordArray();
5245 if (IsRightViewGood())
5246 m_pwndRight->BuildMarkedWordArray();
5247 if (IsBottomViewGood())
5248 m_pwndBottom->BuildMarkedWordArray();
5249 UpdateLocator();
5250 RecalcAllVertScrollBars();
5251 RecalcAllHorzScrollBars();
5254 int CBaseView::Screen2View::FindScreenLineForViewLine( int viewLine )
5256 RebuildIfNecessary();
5258 int nScreenLineCount = (int)m_Screen2View.size();
5260 int nPos = 0;
5261 if (nScreenLineCount>16)
5263 // for enough long data search for last screen
5264 // with viewline less than one we are looking for
5265 // use approximate method (based on) binary search using asymmetric start point
5266 // in form 2**n (determined as MSB of length) to go around division and rounding;
5267 // this effectively looks for bit values from MSB to LSB
5269 int nTestBit;
5270 //GetMostSignificantBitValue
5271 // note _BitScanReverse(&nTestBit, nScreenLineCount); can be used instead
5272 nTestBit = nScreenLineCount;
5273 nTestBit |= nTestBit>>1;
5274 nTestBit |= nTestBit>>2;
5275 nTestBit |= nTestBit>>4;
5276 nTestBit |= nTestBit>>8;
5277 nTestBit |= nTestBit>>16;
5278 nTestBit ^= (nTestBit>>1);
5280 while (nTestBit)
5282 int nTestPos = nPos | nTestBit;
5283 if (nTestPos < nScreenLineCount && m_Screen2View[nTestPos].nViewLine < viewLine)
5285 nPos = nTestPos;
5287 nTestBit >>= 1;
5290 while (nPos < nScreenLineCount && m_Screen2View[nPos].nViewLine < viewLine)
5292 nPos++;
5295 return nPos;
5298 void CBaseView::Screen2View::ScheduleFullRebuild(CViewData * pViewData) {
5299 m_bFull = true;
5301 m_pViewData = pViewData;
5304 void CBaseView::Screen2View::ScheduleRangeRebuild(CViewData * pViewData, int nFirstViewLine, int nLastViewLine)
5306 if (m_bFull)
5307 return;
5309 m_pViewData = pViewData;
5311 TRebuildRange Range;
5312 Range.FirstViewLine=nFirstViewLine;
5313 Range.LastViewLine=nLastViewLine;
5314 m_RebuildRanges.push_back(Range);
5317 bool CBaseView::Screen2View::FixScreenedCacheSize(CBaseView* pwndView)
5319 if (!IsViewGood(pwndView))
5321 return false;
5323 const int nOldSize = (int)pwndView->m_ScreenedViewLine.size();
5324 const int nViewCount = std::max<int>(pwndView->GetViewCount(), 0);
5325 if (nOldSize == nViewCount)
5327 return false;
5329 pwndView->m_ScreenedViewLine.resize(nViewCount);
5330 return true;
5333 bool CBaseView::Screen2View::ResetScreenedViewLineCache(CBaseView* pwndView) const
5335 if (!IsViewGood(pwndView))
5337 return false;
5339 TRebuildRange Range={0, pwndView->GetViewCount()-1};
5340 ResetScreenedViewLineCache(pwndView, Range);
5341 return true;
5344 bool CBaseView::Screen2View::ResetScreenedViewLineCache(CBaseView* pwndView, const TRebuildRange& Range) const
5346 if (!IsViewGood(pwndView))
5348 return false;
5350 if (Range.LastViewLine == -1)
5352 return false;
5354 ASSERT(Range.FirstViewLine >= 0);
5355 ASSERT(Range.LastViewLine < pwndView->GetViewCount());
5356 for (int i = Range.FirstViewLine; i <= Range.LastViewLine; i++)
5358 pwndView->m_ScreenedViewLine[i].Clear();
5360 return false;
5363 void CBaseView::WrapChanged()
5365 m_nMaxLineLength = -1;
5366 m_nOffsetChar = 0;
5367 RecalcHorzScrollBar();
5370 void CBaseView::OnEditFind()
5372 if (m_pFindDialog)
5373 return;
5375 int id = 0;
5376 if (this == m_pwndLeft)
5377 id = 1;
5378 if (this == m_pwndRight)
5379 id = 2;
5380 if (this == m_pwndBottom)
5381 id = 3;
5383 m_pFindDialog = new CFindDlg(this);
5384 m_pFindDialog->Create(this, id);
5386 m_pFindDialog->SetFindString(HasTextSelection() ? GetSelectedText() : L"");
5387 m_pFindDialog->SetReadonly(m_bReadonly);
5390 LRESULT CBaseView::OnFindDialogMessage(WPARAM wParam, LPARAM /*lParam*/)
5392 ASSERT(m_pFindDialog != nullptr);
5394 if (m_pFindDialog->IsTerminating())
5396 // invalidate the handle identifying the dialog box.
5397 m_pFindDialog = nullptr;
5398 return 0;
5401 if(m_pFindDialog->FindNext())
5403 //read data from dialog
5404 m_sFindText = m_pFindDialog->GetFindString();
5405 m_bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
5406 m_bLimitToDiff = m_pFindDialog->LimitToDiffs();
5407 m_bWholeWord = m_pFindDialog->WholeWord();
5409 if (!m_bMatchCase)
5410 m_sFindText = m_sFindText.MakeLower();
5412 BuildFindStringArray();
5413 if ((CFindDlg::FindType)wParam == CFindDlg::FindType::Find)
5415 if (m_pFindDialog->SearchUp())
5416 OnEditFindprev();
5417 else
5418 OnEditFindnext();
5420 else if ((CFindDlg::FindType)wParam == CFindDlg::FindType::Count)
5422 size_t count = 0;
5423 for (size_t i = 0; i < m_arFindStringLines.size(); ++i)
5424 count += m_arFindStringLines[i];
5425 CString matches;
5426 matches.Format(IDS_FIND_COUNT, count);
5427 m_pFindDialog->SetStatusText(matches);
5429 else if ((CFindDlg::FindType)wParam == CFindDlg::FindType::Replace)
5431 if (!IsWritable())
5432 return 0;
5433 bool bFound = false;
5434 if (m_pFindDialog->SearchUp())
5435 bFound = Search(SearchPrevious, true, true, false);
5436 else
5437 bFound = Search(SearchNext, true, true, false);
5438 if (bFound)
5440 CString sReplaceText = m_pFindDialog->GetReplaceString();
5441 CUndo::GetInstance().BeginGrouping();
5442 RemoveSelectedText();
5443 InsertText(sReplaceText);
5444 CUndo::GetInstance().EndGrouping();
5448 else if ((CFindDlg::FindType)wParam == CFindDlg::FindType::ReplaceAll)
5450 if (!IsWritable())
5451 return 0;
5452 bool bFound = false;
5453 int replaceCount = 0;
5454 POINT lastPoint = m_ptSelectionViewPosStart;
5455 m_ptSelectionViewPosStart.x = m_ptSelectionViewPosStart.y = 0;
5456 CUndo::GetInstance().BeginGrouping();
5459 bFound = Search(SearchNext, true, false, true);
5460 if (bFound)
5462 CString sReplaceText = m_pFindDialog->GetReplaceString();
5463 RemoveSelectedText();
5464 InsertText(sReplaceText);
5465 ++replaceCount;
5467 } while (bFound);
5468 CUndo::GetInstance().EndGrouping();
5469 if (replaceCount == 0)
5470 m_ptSelectionViewPosStart = lastPoint;
5471 CString message;
5472 message.Format(IDS_FIND_REPLACED, replaceCount);
5473 m_pFindDialog->SetStatusText(message, RGB(0, 0, 0));
5477 return 0;
5480 void CBaseView::OnEditFindnextStart()
5482 if (!m_pViewData)
5483 return;
5484 if (HasTextSelection())
5486 m_sFindText = GetSelectedText();
5487 m_bMatchCase = false;
5488 m_bLimitToDiff = false;
5489 m_bWholeWord = false;
5490 m_sFindText = m_sFindText.MakeLower();
5492 BuildFindStringArray();
5493 OnEditFindnext();
5495 else
5497 m_sFindText.Empty();
5498 BuildFindStringArray();
5502 void CBaseView::OnEditFindprevStart()
5504 if (!m_pViewData)
5505 return;
5506 if (HasTextSelection())
5508 m_sFindText = GetSelectedText();
5509 m_bMatchCase = false;
5510 m_bLimitToDiff = false;
5511 m_bWholeWord = false;
5512 m_sFindText = m_sFindText.MakeLower();
5514 BuildFindStringArray();
5515 OnEditFindprev();
5517 else
5519 m_sFindText.Empty();
5520 BuildFindStringArray();
5524 bool CBaseView::StringFound(const CString& str, SearchDirection srchDir, int& start, int& end) const
5526 if (srchDir == SearchPrevious)
5528 int laststart = -1;
5529 int laststart2 = -1;
5532 laststart2 = laststart;
5533 laststart = str.Find(m_sFindText, laststart + 1);
5534 } while (laststart >= 0 && laststart < start);
5535 start = laststart2;
5537 else
5538 start = str.Find(m_sFindText, start);
5539 end = start + m_sFindText.GetLength();
5540 bool bStringFound = (start >= 0);
5541 if (bStringFound && m_bWholeWord)
5543 if (start)
5544 bStringFound = IsWordSeparator(str.Mid(start-1,1).GetAt(0));
5546 if (bStringFound)
5548 if (str.GetLength() > end)
5549 bStringFound = IsWordSeparator(str.Mid(end, 1).GetAt(0));
5552 return bStringFound;
5555 void CBaseView::OnEditFindprev()
5557 Search(SearchPrevious, false, true, false);
5560 void CBaseView::OnEditFindnext()
5562 Search(SearchNext, false, true, false);
5565 bool CBaseView::Search(SearchDirection srchDir, bool useStart, bool flashIfNotFound, bool stopEof)
5567 if (m_sFindText.IsEmpty())
5568 return false;
5569 if(!m_pViewData)
5570 return false;
5572 POINT start = useStart ? m_ptSelectionViewPosStart : m_ptSelectionViewPosEnd;
5573 POINT end;
5574 end.y = m_pViewData->GetCount()-1;
5575 if (end.y < 0)
5576 return false;
5578 if (srchDir==SearchNext)
5579 end.x = GetViewLineLength(end.y);
5580 else
5582 end.x = m_ptSelectionViewPosStart.x;
5583 start.x = 0;
5586 if (!HasTextSelection())
5588 start.y = m_ptCaretViewPos.y;
5589 if (srchDir==SearchNext)
5590 start.x = m_ptCaretViewPos.x;
5591 else
5593 start.x = 0;
5594 end.x = m_ptCaretViewPos.x;
5597 CString sSelectedText;
5598 int startline = -1;
5599 for (int nViewLine=start.y; ;srchDir==SearchNext ? nViewLine++ : nViewLine--)
5601 if (nViewLine < 0)
5603 if (stopEof)
5604 return false;
5605 nViewLine = m_pViewData->GetCount()-1;
5606 startline = start.y;
5607 if (flashIfNotFound)
5609 if (m_pFindDialog)
5610 m_pFindDialog->SetStatusText(CString(MAKEINTRESOURCE(IDS_FIND_TOPREACHED)), RGB(63, 127, 47));
5611 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 2, 100);
5614 if (nViewLine > end.y)
5616 if (stopEof)
5617 return false;
5618 nViewLine = 0;
5619 startline = start.y;
5620 if (flashIfNotFound)
5622 if (m_pFindDialog)
5623 m_pFindDialog->SetStatusText(CString(MAKEINTRESOURCE(IDS_FIND_BOTTOMREACHED)), RGB(63, 127, 47));
5624 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 2, 100);
5627 switch (m_pViewData->GetState(nViewLine))
5629 case DIFFSTATE_EMPTY:
5630 break;
5631 case DIFFSTATE_UNKNOWN:
5632 case DIFFSTATE_NORMAL:
5633 case DIFFSTATE_FILTEREDDIFF:
5634 if (m_bLimitToDiff)
5635 break;
5636 case DIFFSTATE_REMOVED:
5637 case DIFFSTATE_REMOVEDWHITESPACE:
5638 case DIFFSTATE_ADDED:
5639 case DIFFSTATE_ADDEDWHITESPACE:
5640 case DIFFSTATE_WHITESPACE:
5641 case DIFFSTATE_WHITESPACE_DIFF:
5642 case DIFFSTATE_CONFLICTED:
5643 case DIFFSTATE_CONFLICTED_IGNORED:
5644 case DIFFSTATE_CONFLICTADDED:
5645 case DIFFSTATE_CONFLICTEMPTY:
5646 case DIFFSTATE_CONFLICTRESOLVED:
5647 case DIFFSTATE_IDENTICALREMOVED:
5648 case DIFFSTATE_IDENTICALADDED:
5649 case DIFFSTATE_THEIRSREMOVED:
5650 case DIFFSTATE_THEIRSADDED:
5651 case DIFFSTATE_YOURSREMOVED:
5652 case DIFFSTATE_YOURSADDED:
5653 case DIFFSTATE_EDITED:
5655 sSelectedText = GetViewLineChars(nViewLine);
5656 if (nViewLine == start.y && startline < 0)
5657 sSelectedText = srchDir == SearchNext ? sSelectedText.Mid(start.x) : sSelectedText.Left(end.x);
5658 if (!m_bMatchCase)
5659 sSelectedText = sSelectedText.MakeLower();
5660 int startfound = srchDir == SearchNext ? 0 : sSelectedText.GetLength();
5661 int endfound = 0;
5662 if (StringFound(sSelectedText, srchDir, startfound, endfound))
5664 HighlightViewLines(nViewLine, nViewLine);
5665 m_ptSelectionViewPosStart.x = startfound;
5666 m_ptSelectionViewPosEnd.x = endfound;
5667 if (nViewLine == start.y && startline < 0)
5669 m_ptSelectionViewPosStart.x += start.x;
5670 m_ptSelectionViewPosEnd.x += start.x;
5672 m_ptSelectionViewPosEnd.x = m_ptSelectionViewPosStart.x + m_sFindText.GetLength();
5673 m_ptSelectionViewPosStart.y = nViewLine;
5674 m_ptSelectionViewPosEnd.y = nViewLine;
5675 m_ptCaretViewPos = m_ptSelectionViewPosStart;
5676 UpdateViewsCaretPosition();
5677 EnsureCaretVisible();
5678 Invalidate();
5679 return true;
5682 break;
5685 if (startline >= 0)
5687 if (nViewLine == startline)
5689 if (flashIfNotFound)
5691 CString message;
5692 message.Format(IDS_FIND_NOTFOUND, (LPCTSTR)m_sFindText);
5693 if (m_pFindDialog)
5694 m_pFindDialog->SetStatusText(message, RGB(255, 0, 0));
5695 ::MessageBeep(0xFFFFFFFF);
5696 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 3, 100);
5698 break;
5702 m_pMainFrame->m_nMoveMovesToIgnore = MOVESTOIGNORE;
5703 return false;
5706 CString CBaseView::GetSelectedText() const
5708 CString sSelectedText;
5709 POINT start = m_ptSelectionViewPosStart;
5710 POINT end = m_ptSelectionViewPosEnd;
5711 if (!HasTextSelection())
5713 if (!HasSelection())
5714 return sSelectedText;
5715 start.y = m_nSelViewBlockStart;
5716 start.x = 0;
5717 end.y = m_nSelViewBlockEnd;
5718 end.x = GetViewLineLength(m_nSelViewBlockEnd);
5720 if (!m_pViewData)
5721 return sSelectedText;
5722 // first store the selected lines in one CString
5723 for (int nViewLine=start.y; nViewLine<=end.y; nViewLine++)
5725 switch (m_pViewData->GetState(nViewLine))
5727 case DIFFSTATE_EMPTY:
5728 break;
5729 case DIFFSTATE_UNKNOWN:
5730 case DIFFSTATE_NORMAL:
5731 case DIFFSTATE_REMOVED:
5732 case DIFFSTATE_REMOVEDWHITESPACE:
5733 case DIFFSTATE_ADDED:
5734 case DIFFSTATE_ADDEDWHITESPACE:
5735 case DIFFSTATE_WHITESPACE:
5736 case DIFFSTATE_WHITESPACE_DIFF:
5737 case DIFFSTATE_CONFLICTED:
5738 case DIFFSTATE_CONFLICTED_IGNORED:
5739 case DIFFSTATE_CONFLICTADDED:
5740 case DIFFSTATE_CONFLICTEMPTY:
5741 case DIFFSTATE_CONFLICTRESOLVED:
5742 case DIFFSTATE_IDENTICALREMOVED:
5743 case DIFFSTATE_IDENTICALADDED:
5744 case DIFFSTATE_THEIRSREMOVED:
5745 case DIFFSTATE_THEIRSADDED:
5746 case DIFFSTATE_YOURSREMOVED:
5747 case DIFFSTATE_YOURSADDED:
5748 case DIFFSTATE_EDITED:
5749 case DIFFSTATE_FILTEREDDIFF:
5750 sSelectedText += GetViewLineChars(nViewLine);
5751 sSelectedText += L"\r\n";
5752 break;
5755 // remove the non-selected chars from the first line, last line and last \r\n
5756 int nLeftCut = start.x;
5757 int nRightCut = GetViewLineChars(end.y).GetLength() - end.x + 2;
5758 sSelectedText = sSelectedText.Mid(nLeftCut, sSelectedText.GetLength()-nLeftCut-nRightCut);
5759 return sSelectedText;
5762 void CBaseView::CheckModifications(bool& hasMods, bool& hasConflicts, bool& hasWhitespaceMods, bool& hasFilteredMods)
5764 hasMods = false;
5765 hasConflicts = false;
5766 hasWhitespaceMods = false;
5767 hasFilteredMods = false;
5769 if (m_pViewData)
5771 for (int i=0; i<m_pViewData->GetCount(); i++)
5773 DiffStates state = m_pViewData->GetState(i);
5774 switch (state)
5776 case DIFFSTATE_ADDED:
5777 case DIFFSTATE_IDENTICALADDED:
5778 case DIFFSTATE_THEIRSADDED:
5779 case DIFFSTATE_YOURSADDED:
5780 case DIFFSTATE_CONFLICTADDED:
5781 case DIFFSTATE_IDENTICALREMOVED:
5782 case DIFFSTATE_REMOVED:
5783 case DIFFSTATE_THEIRSREMOVED:
5784 case DIFFSTATE_YOURSREMOVED:
5785 case DIFFSTATE_EMPTY:
5786 hasMods = true;
5787 break;
5788 case DIFFSTATE_CONFLICTED:
5789 case DIFFSTATE_CONFLICTED_IGNORED:
5790 hasConflicts = true;
5791 break;
5792 case DIFFSTATE_REMOVEDWHITESPACE:
5793 case DIFFSTATE_ADDEDWHITESPACE:
5794 case DIFFSTATE_WHITESPACE:
5795 case DIFFSTATE_WHITESPACE_DIFF:
5796 hasWhitespaceMods = true;
5797 break;
5798 case DIFFSTATE_FILTEREDDIFF:
5799 hasFilteredMods = true;
5800 break;
5806 void CBaseView::OnEditGotoline()
5808 if (!m_pViewData)
5809 return;
5810 // find the last and first line number
5811 int nViewLineCount = m_pViewData->GetCount();
5813 int nLastLineNumber = DIFF_EMPTYLINENUMBER;
5814 for (int nViewLine=nViewLineCount-1; nViewLine>=0; --nViewLine)
5816 nLastLineNumber = m_pViewData->GetLineNumber(nViewLine);
5817 if (nLastLineNumber!=DIFF_EMPTYLINENUMBER)
5819 break;
5822 if (nLastLineNumber==DIFF_EMPTYLINENUMBER || nLastLineNumber==0) // not numbered line foud or last one is first
5824 return;
5826 nLastLineNumber++;
5827 int nFirstLineNumber=1; // first is always 1
5829 CString sText;
5830 sText.Format(IDS_GOTOLINE, nFirstLineNumber, nLastLineNumber);
5832 CGotoLineDlg dlg(this);
5833 dlg.SetLabel(sText);
5834 dlg.SetLimits(nFirstLineNumber, nLastLineNumber);
5835 if (dlg.DoModal() == IDOK)
5837 for (int nViewLine = 0; nViewLine < nViewLineCount; ++nViewLine)
5839 if ((m_pViewData->GetLineNumber(nViewLine)+1) == dlg.GetLineNumber())
5841 HighlightViewLines(nViewLine, nViewLine);
5842 return;
5848 void CBaseView::OnToggleReadonly()
5850 if (IsReadonlyChangable()) {
5851 SetWritable(IsReadonly());
5855 int CBaseView::SaveFile(int nFlags)
5857 Invalidate();
5858 if (m_pViewData && m_pWorkingFile)
5860 CFileTextLines file;
5861 m_SaveParams.m_LineEndings = m_lineendings;
5862 m_SaveParams.m_UnicodeType = m_texttype;
5863 file.SetSaveParams(m_SaveParams);
5865 for (int i=0; i<m_pViewData->GetCount(); i++)
5867 //only copy non-removed lines
5868 DiffStates state = m_pViewData->GetState(i);
5869 switch (state)
5871 case DIFFSTATE_CONFLICTED:
5872 case DIFFSTATE_CONFLICTED_IGNORED:
5874 int first = i;
5875 int last = i;
5878 last++;
5879 } while((last<m_pViewData->GetCount()) && ((m_pViewData->GetState(last)==DIFFSTATE_CONFLICTED)||(m_pViewData->GetState(last)==DIFFSTATE_CONFLICTED_IGNORED)));
5880 file.Add(L"<<<<<<< .mine", EOL_NOENDING);
5881 for (int j=first; j<last; j++)
5883 file.Add(m_pwndRight->m_pViewData->GetLine(j), m_pwndRight->m_pViewData->GetLineEnding(j));
5885 file.Add(L"=======", EOL_NOENDING);
5886 for (int j=first; j<last; j++)
5888 file.Add(m_pwndLeft->m_pViewData->GetLine(j), m_pwndLeft->m_pViewData->GetLineEnding(j));
5890 file.Add(L">>>>>>> .theirs", EOL_NOENDING);
5891 i = last-1;
5893 break;
5894 case DIFFSTATE_EMPTY:
5895 break;
5896 case DIFFSTATE_CONFLICTEMPTY:
5897 case DIFFSTATE_IDENTICALREMOVED:
5898 case DIFFSTATE_REMOVED:
5899 case DIFFSTATE_THEIRSREMOVED:
5900 case DIFFSTATE_YOURSREMOVED:
5901 case DIFFSTATE_CONFLICTRESOLVEDEMPTY:
5902 if ((nFlags&SAVE_REMOVEDLINES) == 0)
5904 // do not save removed lines
5905 break;
5907 default:
5908 file.Add(m_pViewData->GetLine(i), m_pViewData->GetLineEnding(i));
5909 break;
5912 CString filename = m_pWorkingFile->GetFilename();
5913 if (m_pWorkingFile->IsReadonly())
5914 if (!CCommonAppUtils::FileOpenSave(filename, nullptr, IDS_SAVEASTITLE, IDS_COMMONFILEFILTER, false, m_hWnd))
5915 return -1;
5916 if (!file.Save(filename))
5918 ::MessageBox(m_hWnd, file.GetErrorString(), L"TortoiseGitMerge", MB_ICONERROR);
5919 return -1;
5921 m_pWorkingFile->SetFileName(filename);
5922 m_pWorkingFile->StoreFileAttributes();
5923 // m_dlgFilePatches.SetFileStatusAsPatched(sFilePath);
5924 SetModified(FALSE);
5925 CUndo::GetInstance().MarkAsOriginalState(
5926 this == m_pwndLeft,
5927 this == m_pwndRight,
5928 this == m_pwndBottom);
5929 if (file.GetCount() == 1 && file.GetAt(0).IsEmpty() && file.GetLineEnding(0) == EOL_NOENDING)
5930 return 0;
5931 return file.GetCount();
5933 return 1;
5937 int CBaseView::SaveFileTo(CString sFileName, int nFlags)
5939 if (m_pWorkingFile)
5941 m_pWorkingFile->SetFileName(sFileName);
5942 return SaveFile(nFlags);
5944 return -1;
5948 EOL CBaseView::GetLineEndings()
5950 return GetLineEndings(GetWhitecharsProperties().HasMixedEols);
5953 EOL CBaseView::GetLineEndings(bool bHasMixedEols)
5955 if (bHasMixedEols)
5957 return EOL_AUTOLINE; // mixed eols - hack value
5959 if (m_lineendings == EOL_AUTOLINE)
5961 return EOL_CRLF;
5963 return m_lineendings;
5966 void CBaseView::ReplaceLineEndings(EOL eEol)
5968 if (eEol == EOL_AUTOLINE)
5970 return;
5972 // set AUTOLINE
5973 m_lineendings = eEol;
5974 // replace all set EOLs
5975 // TODO store line endings and lineendings in undo
5976 //CUndo::BeginGrouping();
5977 for (int i = 0; i < GetViewCount(); ++i)
5979 if (IsLineEmpty(i))
5981 continue;
5983 EOL eLineEol = GetViewLineEnding(i);
5984 if (eLineEol == EOL_AUTOLINE || eLineEol == EOL_NOENDING || eLineEol == m_lineendings)
5986 continue;
5988 SetViewLineEnding(i, eEol);
5990 //CUndo::EndGrouping();
5991 //CUndo::saveundostep;
5992 DocumentUpdated();
5993 SetModified();
5996 void CBaseView::SetLineEndingStyle(EOL eEol)
5998 m_lineendings = eEol;
6001 void CBaseView::SetTextType(CFileTextLines::UnicodeType eTextType)
6003 if (m_texttype == eTextType)
6005 return;
6007 m_texttype = eTextType;
6008 DocumentUpdated();
6009 SetModified();
6012 void CBaseView::AskUserForNewLineEndingsAndTextType(int nTextId)
6014 if (IsReadonly())
6015 return; // nothing to be changed in read-only view
6016 CEncodingDlg dlg;
6017 dlg.view.LoadString(nTextId);
6018 dlg.texttype = m_texttype;
6019 dlg.lineendings = GetLineEndings();
6020 if (dlg.DoModal() != IDOK)
6021 return;
6022 SetTextType(dlg.texttype);
6023 ReplaceLineEndings(dlg.lineendings);
6027 Replaces lines from source view to this
6029 void CBaseView::UseViewBlock(CBaseView * pwndView, int nFirstViewLine, int nLastViewLine, std::function<bool(int)> fnSkip)
6031 if (!IsViewGood(pwndView))
6032 return;
6033 if (!IsWritable())
6034 return;
6035 CUndo::GetInstance().BeginGrouping();
6037 for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++)
6039 bool skip = fnSkip(viewLine);
6040 if (skip)
6042 if (GetViewMarked(viewLine))
6043 SetViewMarked(viewLine, false);
6044 continue;
6046 viewdata line = pwndView->GetViewData(viewLine);
6047 if (line.ending != EOL_NOENDING)
6048 line.ending = m_lineendings;
6049 switch (line.state)
6051 case DIFFSTATE_CONFLICTEMPTY:
6052 case DIFFSTATE_UNKNOWN:
6053 line.state = DIFFSTATE_EMPTY;
6054 case DIFFSTATE_EMPTY:
6055 break;
6056 case DIFFSTATE_ADDED:
6057 case DIFFSTATE_CONFLICTADDED:
6058 case DIFFSTATE_CONFLICTED:
6059 case DIFFSTATE_CONFLICTED_IGNORED:
6060 case DIFFSTATE_IDENTICALADDED:
6061 case DIFFSTATE_THEIRSADDED:
6062 case DIFFSTATE_YOURSADDED:
6063 case DIFFSTATE_IDENTICALREMOVED:
6064 case DIFFSTATE_REMOVED:
6065 case DIFFSTATE_THEIRSREMOVED:
6066 case DIFFSTATE_YOURSREMOVED:
6067 pwndView->SetViewState(viewLine, DIFFSTATE_NORMAL);
6068 line.state = DIFFSTATE_NORMAL;
6069 case DIFFSTATE_NORMAL:
6070 break;
6071 default:
6072 break;
6074 bool marked = GetViewMarked(viewLine);
6075 SetViewData(viewLine, line);
6076 if (marked)
6077 SetViewMarked(viewLine, false);
6078 if ((m_texttype == UnicodeType::ASCII) && (pwndView->GetTextType() != UnicodeType::ASCII))
6080 // if this view is in ASCII and the other is not, we have to make sure that
6081 // the text we copy from the other view can actually be saved in ASCII encoding.
6082 // if not, we have to change this views encoding to the same encoding as the other view
6083 BOOL useDefault = FALSE;
6084 WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, line.sLine, -1, nullptr, 0, 0, &useDefault);
6085 if (useDefault) // a default char is required, so the char can not be saved as ASCII
6086 SetTextType(pwndView->GetTextType());
6089 // normal lines is mostly same but may differ in EOL so any copied line change view state to modified
6090 // TODO: check if copied line is same as original one set modified only when differ
6091 SetModified();
6092 SaveUndoStep();
6094 int nRemovedLines = CleanEmptyLines();
6095 SaveUndoStep();
6096 //VerifyEols();
6097 // make sure all non empty line have EOL set but last
6098 // wrong can be last copied line(have eol, but no line under),
6099 // or old last line (line before copied block missing eol, but have line under)
6100 // we'll check all lines to be sure
6101 int nLine = GetViewCount();
6102 // check last line have no EOL set
6103 while (--nLine>=0)
6105 if (!IsViewLineEmpty(nLine))
6107 if (GetViewLineEnding(nLine) != EOL_NOENDING)
6109 // we added non last line into empty block on the end (or should we remove eol from this one ?)
6110 // so next line is empty
6111 ASSERT(IsViewLineEmpty(nLine+1));
6112 // and we can turn it to normal empty line
6113 SetViewData(nLine+1, viewdata(CString(), DIFFSTATE_ADDED, 1, EOL_NOENDING, HIDESTATE_SHOWN));
6115 break;
6118 // check all (nonlast) line have EOL set
6119 while (--nLine>=0)
6121 if (!IsViewLineEmpty(nLine))
6123 if (GetViewLineEnding(nLine) == EOL_NOENDING)
6125 SetViewLineEnding(nLine, m_lineendings);
6126 // in theory there should be only one line needing fix, but most of time we get over all anyway
6127 // break;
6131 SaveUndoStep();
6132 UpdateViewLineNumbers();
6133 SaveUndoStep();
6135 CUndo::GetInstance().EndGrouping();
6137 if (nRemovedLines!=0)
6139 // some lines are gone update selection
6140 ClearSelection();
6141 SetupAllViewSelection(nFirstViewLine, nLastViewLine - nRemovedLines);
6143 BuildAllScreen2ViewVector();
6144 pwndView->Invalidate();
6145 RefreshViews();
6148 void CBaseView::MarkBlock(bool marked, int nFirstViewLine, int nLastViewLine)
6150 if (!IsWritable())
6151 return;
6152 CUndo::GetInstance().BeginGrouping();
6154 for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++)
6155 SetViewMarked(viewLine, marked);
6157 SetModified();
6158 SaveUndoStep();
6159 CUndo::GetInstance().EndGrouping();
6161 BuildAllScreen2ViewVector();
6162 Invalidate();
6163 RefreshViews();
6166 void CBaseView::LeaveOnlyMarkedBlocks(CBaseView *pwndView)
6168 auto fn = [this](int viewLine) -> bool { return GetViewMarked(viewLine) || GetViewState(viewLine) == DIFFSTATE_EDITED; };
6169 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6172 void CBaseView::UseViewFileOfMarked(CBaseView *pwndView)
6174 auto fn = [this](int viewLine) -> bool { return !GetViewMarked(viewLine) || GetViewState(viewLine) == DIFFSTATE_EDITED; };
6175 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6178 void CBaseView::UseViewFileExceptEdited(CBaseView *pwndView)
6180 auto fn = [this](int viewLine) -> bool { return GetViewState(viewLine) == DIFFSTATE_EDITED; };
6181 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6184 int CBaseView::GetIndentCharsForLine(int x, int y)
6186 const int maxGuessLine = 100;
6187 int nTabMode = -1;
6188 const CString& line = GetViewLine(y);
6189 if (m_nTabMode & TABMODE_SMARTINDENT)
6191 // if the line contains one tab, use tabs
6192 // we can not test for spaces, since even if tabs are used,
6193 // spaces are used in a tabified file for alignment.
6194 if (line.Find(L'\t') >= 0)
6195 nTabMode = 0; // use tabs
6196 else if (line.GetLength() > m_nTabSize)
6197 nTabMode = 1; // use spaces
6199 if (m_nTabMode & TABMODE_SMARTINDENT)
6201 // detect lines nearby
6202 for (int i = y - 1, j = y + 1; nTabMode == -1; --i, ++j)
6204 bool above = i > 0 && i >= y - maxGuessLine;
6205 bool below = j < GetViewCount() && j <= y + maxGuessLine;
6206 if (!(above || below))
6207 break;
6208 auto ac = GetViewLine(i);
6209 auto bc = GetViewLine(j);
6210 if ((ac.Find(L'\t') >= 0) || (bc.Find(L'\t') >= 0))
6212 nTabMode = 0;
6213 break;
6215 else if ((ac.GetLength() > m_nTabSize) && (bc.GetLength() > m_nTabSize))
6217 nTabMode = 1;
6218 break;
6223 else
6224 nTabMode = m_nTabMode & TABMODE_USESPACES;
6226 if (nTabMode > 0)
6228 // use spaces
6229 x = CountExpandedChars(line, x);
6230 return (m_nTabSize - (x % m_nTabSize));
6233 // use tab
6234 return 0;
6237 void CBaseView::AddIndentationForSelectedBlock()
6239 bool bModified = false;
6240 for (int nViewLine = m_ptSelectionViewPosStart.y; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
6242 // skip the line if no character is selected in the last selected line
6243 if (nViewLine == m_ptSelectionViewPosEnd.y && m_ptSelectionViewPosEnd.x == 0)
6245 continue;
6247 // skip empty lines
6248 if (IsLineEmpty(nViewLine))
6250 continue;
6252 const CString &sLine = GetViewLine(nViewLine);
6253 CString sTemp = sLine;
6254 if (sTemp.Trim().IsEmpty())
6256 // skip empty and whitechar only lines
6257 continue;
6259 // add tab to line start (alternatively m_nTabSize spaces can be used)
6260 CString tabStr;
6261 int indentChars = GetIndentCharsForLine(0, nViewLine);
6262 tabStr = indentChars > 0 ? CString(L' ', indentChars) : L"\t";
6263 SetViewLine(nViewLine, tabStr + sLine);
6264 bModified = true;
6266 if (bModified)
6268 SetModified();
6269 SaveUndoStep();
6270 BuildAllScreen2ViewVector();
6274 void CBaseView::RemoveIndentationForSelectedBlock()
6276 bool bModified = false;
6277 for (int nViewLine = m_ptSelectionViewPosStart.y; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
6279 // skip the line if no character is selected in the last selected line
6280 if (nViewLine == m_ptSelectionViewPosEnd.y && m_ptSelectionViewPosEnd.x == 0)
6282 continue;
6284 // skip empty lines
6285 if (IsLineEmpty(nViewLine))
6287 continue;
6289 CString sLine = GetViewLine(nViewLine);
6290 // remove up to n spaces from line start
6291 // and one tab (if less then n spaces was removed)
6292 int nPos = 0;
6293 while (nPos<m_nTabSize)
6295 switch (sLine[nPos])
6297 case ' ':
6298 nPos++;
6299 continue;
6300 case '\t':
6301 nPos++;
6303 break;
6305 if (nPos>0)
6307 sLine.Delete(0, nPos);
6308 SetViewLine(nViewLine, sLine);
6309 bModified = true;
6312 if (bModified)
6314 SetModified();
6315 SaveUndoStep();
6316 BuildAllScreen2ViewVector();
6321 there are two possible versions
6322 - convert tabs to spaces only in front of text (implemented)
6323 - convert all tabs to spaces
6325 void CBaseView::ConvertTabToSpaces()
6327 bool bModified = false;
6328 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6330 if (IsLineEmpty(nViewLine))
6332 continue;
6334 const CString &sLine = GetViewLine(nViewLine);
6335 bool bTabToConvertFound = false;
6336 int nPosIn = 0;
6337 int nPosOut = 0;
6338 while (nPosIn<sLine.GetLength())
6340 switch (sLine[nPosIn])
6342 case ' ':
6343 nPosIn++;
6344 nPosOut++;
6345 continue;
6346 case '\t':
6347 nPosIn++;
6348 bTabToConvertFound = true;
6349 nPosOut = (nPosOut+m_nTabSize) - nPosOut%m_nTabSize;
6350 continue;
6352 break;
6354 if (bTabToConvertFound)
6356 CString sLineNew = sLine;
6357 sLineNew.Delete(0, nPosIn);
6358 sLineNew = CString(' ', nPosOut) + sLineNew;
6359 SetViewLine(nViewLine, sLineNew);
6360 bModified = true;
6363 if (bModified)
6365 SetModified();
6366 SaveUndoStep();
6367 BuildAllScreen2ViewVector();
6372 there are two possible version
6373 - convert spaces to tabs only in front of text (implemented)
6374 - convert all spaces to tabs
6376 void CBaseView::Tabularize()
6378 bool bModified = false;
6379 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6381 if (IsLineEmpty(nViewLine))
6383 continue;
6385 const CString &sLine = GetViewLine(nViewLine);
6386 int nDel = 0;
6387 int nTabCount = 0; // total tabs to be used
6388 int nSpaceCount = 0; // number of spaces in tab size run
6389 int nPos = 0;
6390 while (nPos<sLine.GetLength())
6392 switch (sLine[nPos++])
6394 case ' ':
6395 //bSpace = true;
6396 if (++nSpaceCount < m_nTabSize)
6398 continue;
6400 case '\t':
6401 nTabCount++;
6402 nSpaceCount = 0;
6403 nDel = nPos;
6404 continue;
6406 break;
6408 if (nDel > 0)
6410 CString sLineNew = sLine;
6411 sLineNew.Delete(0, nDel);
6412 sLineNew = CString('\t', nTabCount) + sLineNew;
6413 if (sLine!=sLineNew)
6415 SetViewLine(nViewLine, sLineNew);
6416 bModified = true;
6420 if (bModified)
6422 SetModified();
6423 SaveUndoStep();
6424 BuildAllScreen2ViewVector();
6428 void CBaseView::RemoveTrailWhiteChars()
6430 bool bModified = false;
6431 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6433 if (IsLineEmpty(nViewLine))
6435 continue;
6437 const CString &sLine = GetViewLine(nViewLine);
6438 CString sLineNew = sLine;
6439 sLineNew.TrimRight();
6440 if (sLine.GetLength()!=sLineNew.GetLength())
6442 SetViewLine(nViewLine, sLineNew);
6443 bModified = true;
6446 if (bModified)
6448 SetModified();
6449 SaveUndoStep();
6450 BuildAllScreen2ViewVector();
6454 CBaseView::TWhitecharsProperties CBaseView::GetWhitecharsProperties()
6456 if (GetViewCount()>10000)
6458 // 10k lines is enough to check
6459 TWhitecharsProperties oRet = {true, true, true, true};
6460 return oRet;
6462 TWhitecharsProperties oRet = {};
6463 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6465 if (IsLineEmpty(nViewLine))
6467 continue;
6469 const CString &sLine = GetViewLine(nViewLine);
6470 if (sLine.IsEmpty())
6472 continue;
6474 // check leading whites for convertible tabs and spaces
6475 int nPos = 0;
6476 int nSpaceCount = 0; // number of spaces in tab size run
6477 while (nPos<sLine.GetLength() && (!oRet.HasSpacesToConvert || !oRet.HasTabsToConvert))
6479 switch (sLine[nPos++])
6481 case ' ':
6482 if (++nSpaceCount >= m_nTabSize)
6484 oRet.HasSpacesToConvert = true;
6486 continue;
6487 case '\t':
6488 oRet.HasTabsToConvert = true;
6489 if (nSpaceCount!=0)
6491 oRet.HasSpacesToConvert = true;
6493 continue;
6495 break;
6498 // check trailing whites for removable chars
6499 switch (sLine[sLine.GetLength()-1])
6501 case ' ':
6502 case '\t':
6503 oRet.HasTrailWhiteChars = true;
6506 // check EOLs
6507 EOL eLineEol = GetViewLineEnding(nViewLine);
6508 if (!oRet.HasMixedEols && (eLineEol != m_lineendings) && (eLineEol != EOL_AUTOLINE) && (eLineEol != EOL_NOENDING))
6510 oRet.HasMixedEols = true;
6513 return oRet;
6516 void CBaseView::InsertText(const CString& sText)
6518 ResetUndoStep();
6520 POINT ptCaretViewPos = GetCaretViewPosition();
6521 int nLeft = ptCaretViewPos.x;
6522 int nViewLine = ptCaretViewPos.y;
6524 if ((nViewLine == 0) && (GetViewCount() == 0))
6525 OnChar(VK_RETURN, 0, 0);
6527 std::vector<CString> lines;
6528 int nStart = 0;
6529 int nEolPos = 0;
6530 while ((nEolPos = sText.Find('\r', nEolPos)) >= 0)
6532 CString sLine = sText.Mid(nStart, nEolPos - nStart);
6533 lines.push_back(sLine);
6534 nEolPos++;
6535 nStart = nEolPos;
6537 CString sLine = sText.Mid(nStart);
6538 lines.push_back(sLine);
6540 int nLinesToPaste = (int)lines.size();
6541 if (nLinesToPaste > 1)
6543 // multiline text
6545 // We want to undo the multiline insertion in a single step.
6546 CUndo::GetInstance().BeginGrouping();
6548 sLine = GetViewLineChars(nViewLine);
6549 CString sLineLeft = sLine.Left(nLeft);
6550 CString sLineRight = sLine.Right(sLine.GetLength() - nLeft);
6551 EOL eOriginalEnding = GetViewLineEnding(nViewLine);
6552 viewdata newLine(L"", DIFFSTATE_EDITED, 1, m_lineendings, HIDESTATE_SHOWN);
6553 if (!lines[0].IsEmpty() || !sLineRight.IsEmpty() || (eOriginalEnding != m_lineendings))
6555 newLine.sLine = sLineLeft + lines[0];
6556 SetViewData(nViewLine, newLine);
6559 int nInsertLine = nViewLine;
6560 for (int i = 1; i < nLinesToPaste - 1; i++)
6562 newLine.sLine = lines[i];
6563 InsertViewData(++nInsertLine, newLine);
6565 newLine.sLine = lines[nLinesToPaste - 1] + sLineRight;
6566 newLine.ending = eOriginalEnding;
6567 InsertViewData(++nInsertLine, newLine);
6569 SetModified();
6570 SaveUndoStep();
6572 // adds new lines everywhere except me
6573 if (IsViewGood(m_pwndLeft) && m_pwndLeft != this)
6575 m_pwndLeft->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6577 if (IsViewGood(m_pwndRight) && m_pwndRight != this)
6579 m_pwndRight->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6581 if (IsViewGood(m_pwndBottom) && m_pwndBottom != this)
6583 m_pwndBottom->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6585 SaveUndoStep();
6587 UpdateViewLineNumbers();
6588 CUndo::GetInstance().EndGrouping();
6590 ptCaretViewPos = SetupPoint(lines[nLinesToPaste - 1].GetLength(), nInsertLine);
6592 else
6594 // single line text - just insert it
6595 sLine = GetViewLineChars(nViewLine);
6596 sLine.Insert(nLeft, sText);
6597 ptCaretViewPos = SetupPoint(nLeft + sText.GetLength(), nViewLine);
6598 SetViewLine(nViewLine, sLine);
6599 SetViewState(nViewLine, DIFFSTATE_EDITED);
6600 SetModified();
6601 SaveUndoStep();
6604 RefreshViews();
6605 BuildAllScreen2ViewVector();
6606 UpdateCaretViewPosition(ptCaretViewPos);
6609 ULONG CBaseView::GetGestureStatus(CPoint /*ptTouch*/)
6611 return 0;