Make sure CTRL+HOME scrolls to the beginning of the first line
[TortoiseGit.git] / src / TortoiseMerge / BaseView.cpp
blobd23f1f33b6b7b414d752d27ab3c89a937997e495
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2003-2018 - TortoiseSVN
4 // Copyright (C) 2011-2012, 2017 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 "DpiScale.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 CDC * pDC = GetDC();
501 if (pDC)
503 m_lfBaseFont.lfHeight = -MulDiv((DWORD)CRegDWORD(L"Software\\TortoiseGitMerge\\LogFontSize", 10), GetDeviceCaps(pDC->m_hDC, LOGPIXELSY), 72);
504 ReleaseDC(pDC);
506 wcsncpy_s(m_lfBaseFont.lfFaceName, (LPCTSTR)(CString)CRegString(L"Software\\TortoiseGitMerge\\LogFontName", L"Consolas"), _countof(m_lfBaseFont.lfFaceName) - 1);
507 if (!m_apFonts[nIndex]->CreateFontIndirect(&m_lfBaseFont))
509 delete m_apFonts[nIndex];
510 m_apFonts[nIndex] = nullptr;
511 return CView::GetFont();
514 return m_apFonts[nIndex];
517 void CBaseView::CalcLineCharDim()
519 CDC *pDC = GetDC();
520 if (!pDC)
521 return;
522 CFont *pOldFont = pDC->SelectObject(GetFont());
523 const CSize szCharExt = pDC->GetTextExtent(L"X");
524 pDC->SelectObject(pOldFont);
525 ReleaseDC(pDC);
527 m_nLineHeight = szCharExt.cy;
528 if (m_nLineHeight <= 0)
529 m_nLineHeight = -1;
530 m_nCharWidth = szCharExt.cx;
531 if (m_nCharWidth <= 0)
532 m_nCharWidth = -1;
535 int CBaseView::GetScreenChars()
537 if (m_nScreenChars == -1)
539 CRect rect;
540 GetClientRect(&rect);
541 m_nScreenChars = (rect.Width() - GetMarginWidth() - GetSystemMetrics(SM_CXVSCROLL)) / GetCharWidth();
542 if (m_nScreenChars < 0)
543 m_nScreenChars = 0;
545 return m_nScreenChars;
548 int CBaseView::GetAllMinScreenChars() const
550 int nChars = INT_MAX;
551 if (IsLeftViewGood())
552 nChars = std::min<int>(nChars, m_pwndLeft->GetScreenChars());
553 if (IsRightViewGood())
554 nChars = std::min<int>(nChars, m_pwndRight->GetScreenChars());
555 if (IsBottomViewGood())
556 nChars = std::min<int>(nChars, m_pwndBottom->GetScreenChars());
557 return (nChars==INT_MAX) ? 0 : nChars;
560 int CBaseView::GetAllMaxLineLength() const
562 int nLength = 0;
563 if (IsLeftViewGood())
564 nLength = std::max<int>(nLength, m_pwndLeft->GetMaxLineLength());
565 if (IsRightViewGood())
566 nLength = std::max<int>(nLength, m_pwndRight->GetMaxLineLength());
567 if (IsBottomViewGood())
568 nLength = std::max<int>(nLength, m_pwndBottom->GetMaxLineLength());
569 return nLength;
572 int CBaseView::GetLineHeight()
574 if (m_nLineHeight == -1)
575 CalcLineCharDim();
576 if (m_nLineHeight <= 0)
577 return 1;
578 return m_nLineHeight;
581 int CBaseView::GetCharWidth()
583 if (m_nCharWidth == -1)
584 CalcLineCharDim();
585 if (m_nCharWidth <= 0)
586 return 1;
587 return m_nCharWidth;
590 int CBaseView::GetMaxLineLength()
592 if (m_nMaxLineLength == -1)
594 m_nMaxLineLength = 0;
595 int nLineCount = GetLineCount();
596 if (nLineCount == 1)
598 m_nMaxLineLength = GetLineLengthWithTabsConverted(0);
599 return m_nMaxLineLength;
601 for (int i=0; i<nLineCount; i++)
603 int nActualLength = GetLineLengthWithTabsConverted(i);
604 if (m_nMaxLineLength < nActualLength)
605 m_nMaxLineLength = nActualLength;
608 return m_nMaxLineLength;
611 int CBaseView::GetLineLengthWithTabsConverted(int index)
613 if (!m_pViewData)
614 return 0;
615 if (m_pViewData->GetCount() == 0)
616 return 0;
617 if ((int)m_Screen2View.size() <= index)
618 return 0;
619 CString sLine;
620 if (m_pMainFrame->m_bWrapLines)
621 sLine = GetLineChars(index);
622 else
624 int viewLine = GetViewLineForScreen(index);
625 sLine = m_pViewData->GetLine(viewLine);
627 int tabCount = 0;
628 wchar_t* pChar = (LPWSTR)(LPCWSTR)sLine;
629 auto nLineLength = sLine.GetLength();
630 for (int i = 0; i < nLineLength; ++i)
632 if (*pChar == '\t')
633 ++tabCount;
634 ++pChar;
636 // GetTabSize() - 1 because the tabs are already counted
637 nLineLength = nLineLength + (tabCount * (GetTabSize() - 1));
638 ASSERT(nLineLength >= 0);
639 return nLineLength;
642 int CBaseView::GetLineLength(int index)
644 if (!m_pViewData)
645 return 0;
646 if (m_pViewData->GetCount() == 0)
647 return 0;
648 if ((int)m_Screen2View.size() <= index)
649 return 0;
650 int viewLine = GetViewLineForScreen(index);
651 if (m_pMainFrame->m_bWrapLines)
653 int nLineLength = GetLineChars(index).GetLength();
654 ASSERT(nLineLength >= 0);
655 return nLineLength;
657 int nLineLength = m_pViewData->GetLine(viewLine).GetLength();
658 ASSERT(nLineLength >= 0);
659 return nLineLength;
662 int CBaseView::GetViewLineLength(int nViewLine) const
664 if (!m_pViewData)
665 return 0;
666 if (m_pViewData->GetCount() <= nViewLine)
667 return 0;
668 int nLineLength = m_pViewData->GetLine(nViewLine).GetLength();
669 ASSERT(nLineLength >= 0);
670 return nLineLength;
673 int CBaseView::GetLineCount() const
675 if (!m_pViewData)
676 return 1;
677 int nLineCount = (int)m_Screen2View.size();
678 ASSERT(nLineCount >= 0);
679 return nLineCount;
682 int CBaseView::GetSubLineOffset(int index)
684 return m_Screen2View.GetSubLineOffset(index);
687 CString CBaseView::GetViewLineChars(int nViewLine) const
689 if (!m_pViewData)
690 return 0;
691 if (m_pViewData->GetCount() <= nViewLine)
692 return 0;
693 return m_pViewData->GetLine(nViewLine);
696 CString CBaseView::GetLineChars(int index)
698 if (!m_pViewData)
699 return 0;
700 if (m_pViewData->GetCount() == 0)
701 return 0;
702 if ((int)m_Screen2View.size() <= index)
703 return 0;
704 int viewLine = GetViewLineForScreen(index);
705 if (m_pMainFrame->m_bWrapLines)
707 int subLine = GetSubLineOffset(index);
708 if (subLine >= 0)
710 if (subLine < CountMultiLines(viewLine))
712 return m_ScreenedViewLine[viewLine].SubLines[subLine];
714 return L"";
717 return m_pViewData->GetLine(viewLine);
720 void CBaseView::CheckOtherView()
722 if (m_bOtherDiffChecked)
723 return;
724 // find out what the 'other' file is
725 m_pOtherViewData = nullptr;
726 m_pOtherView = nullptr;
727 if (this == m_pwndLeft && IsRightViewGood())
729 m_pOtherViewData = m_pwndRight->m_pViewData;
730 m_pOtherView = m_pwndRight;
733 if (this == m_pwndRight && IsLeftViewGood())
735 m_pOtherViewData = m_pwndLeft->m_pViewData;
736 m_pOtherView = m_pwndLeft;
739 m_bOtherDiffChecked = true;
743 void CBaseView::GetWhitespaceBlock(CViewData *viewData, int nLineIndex, int & nStartBlock, int & nEndBlock)
745 enum { MAX_WHITESPACEBLOCK_SIZE = 8 };
746 ASSERT(viewData);
748 DiffStates origstate = viewData->GetState(nLineIndex);
750 // Go back and forward at most MAX_WHITESPACEBLOCK_SIZE lines to see where this block ends
751 nStartBlock = nLineIndex;
752 nEndBlock = nLineIndex;
753 while ((nStartBlock > 0) && (nStartBlock > (nLineIndex - MAX_WHITESPACEBLOCK_SIZE)))
755 DiffStates state = viewData->GetState(nStartBlock - 1);
756 if ((origstate == DIFFSTATE_EMPTY) && (state != DIFFSTATE_NORMAL))
757 origstate = state;
758 if ((origstate == state) || (state == DIFFSTATE_EMPTY))
759 nStartBlock--;
760 else
761 break;
763 while ((nEndBlock < (viewData->GetCount() - 1)) && (nEndBlock < (nLineIndex + MAX_WHITESPACEBLOCK_SIZE)))
765 DiffStates state = viewData->GetState(nEndBlock + 1);
766 if ((origstate == DIFFSTATE_EMPTY) && (state != DIFFSTATE_NORMAL))
767 origstate = state;
768 if ((origstate == state) || (state == DIFFSTATE_EMPTY))
769 nEndBlock++;
770 else
771 break;
775 CString CBaseView::GetWhitespaceString(CViewData *viewData, int nStartBlock, int nEndBlock)
777 enum { MAX_WHITESPACEBLOCK_SIZE = 8 };
779 int len = 0;
780 for (int i = nStartBlock; i <= nEndBlock; ++i)
781 len += viewData->GetLine(i).GetLength()+2;
783 CString block;
784 // do not check for whitespace blocks if the line is too long, because
785 // reserving a lot of memory here takes too much time (performance hog)
786 if (len > MAX_WHITESPACEBLOCK_SIZE*256)
787 return block;
788 block.Preallocate(len+1);
789 for (int i = nStartBlock; i <= nEndBlock; ++i)
791 block += viewData->GetLine(i);
792 block += m_Eols[viewData->GetLineEnding(i)];
794 return block;
797 bool CBaseView::IsBlockWhitespaceOnly(int nLineIndex, bool& bIdentical, int& blockstart, int& blockend)
799 if (!m_pViewData)
800 return false;
801 bIdentical = false;
802 CheckOtherView();
803 if (!m_pOtherViewData)
804 return false;
805 int viewLine = GetViewLineForScreen(nLineIndex);
806 if (
807 (m_pViewData->GetState(viewLine) == DIFFSTATE_NORMAL) &&
808 (m_pOtherViewData->GetLine(viewLine) == m_pViewData->GetLine(viewLine))
811 bIdentical = true;
812 return false;
814 // first check whether the line itself only has whitespace changes
815 CString mine = m_pViewData->GetLine(viewLine);
816 CString other = m_pOtherViewData->GetLine(min(viewLine, m_pOtherViewData->GetCount() - 1));
817 if (mine.IsEmpty() && other.IsEmpty())
819 bIdentical = true;
820 return false;
823 if (mine == other)
825 bIdentical = true;
826 return true;
828 FilterWhitespaces(mine, other);
829 if (mine == other)
830 return true;
832 int nStartBlock2, nEndBlock2;
833 GetWhitespaceBlock(m_pViewData, viewLine, blockstart, blockend);
834 GetWhitespaceBlock(m_pOtherViewData, min(viewLine, m_pOtherViewData->GetCount() - 1), nStartBlock2, nEndBlock2);
835 mine = GetWhitespaceString(m_pViewData, blockstart, blockend);
836 if (mine.IsEmpty())
837 bIdentical = false;
838 else
840 other = GetWhitespaceString(m_pOtherViewData, nStartBlock2, nEndBlock2);
841 bIdentical = mine == other;
842 FilterWhitespaces(mine, other);
845 return (!mine.IsEmpty()) && (mine == other);
848 bool CBaseView::IsViewLineHidden(int nViewLine)
850 return IsViewLineHidden(m_pViewData, nViewLine);
853 bool CBaseView::IsViewLineHidden(CViewData * pViewData, int nViewLine)
855 return m_pMainFrame->m_bCollapsed && (pViewData->GetHideState(nViewLine)!=HIDESTATE_SHOWN);
858 int CBaseView::GetLineNumber(int index) const
860 if (!m_pViewData)
861 return -1;
862 int viewLine = GetViewLineForScreen(index);
863 if (m_pViewData->GetLineNumber(viewLine)==DIFF_EMPTYLINENUMBER)
864 return -1;
865 return m_pViewData->GetLineNumber(viewLine);
868 int CBaseView::GetScreenLines()
870 if (m_nScreenLines == -1)
872 CRect rect;
873 GetClientRect(&rect);
874 SCROLLBARINFO sbi = { sizeof(sbi) };
875 if (GetScrollBarInfo(OBJID_HSCROLL, &sbi))
877 // only use the scroll bar size if the info is correct and the scrollbar is visible
878 // if anything isn't proper, assume the scrollbar has a size of zero
879 // and calculate the screen lines without it.
880 if (!(sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE) && !(sbi.rgstate[0] & STATE_SYSTEM_UNAVAILABLE))
882 int scrollBarHeight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top;
883 m_nScreenLines = (rect.Height() - HEADERHEIGHT - scrollBarHeight) / GetLineHeight();
884 if (m_nScreenLines < 0)
885 m_nScreenLines = 0;
886 return m_nScreenLines;
889 // if the scroll bar is not visible, unavailable or there was an error,
890 // assume the scroll bar height is zero and return the screen lines here.
891 // Of course, that means the cache (using the member variable) won't work
892 // and we fetch the scroll bar info every time. But in this case the overhead is necessary.
893 return (rect.Height() - HEADERHEIGHT) / GetLineHeight();
895 return m_nScreenLines;
898 int CBaseView::GetAllMinScreenLines() const
900 int nLines = INT_MAX;
901 if (IsLeftViewGood())
902 nLines = m_pwndLeft->GetScreenLines();
903 if (IsRightViewGood())
904 nLines = std::min<int>(nLines, m_pwndRight->GetScreenLines());
905 if (IsBottomViewGood())
906 nLines = std::min<int>(nLines, m_pwndBottom->GetScreenLines());
907 return (nLines == INT_MAX) || (nLines < 0) ? 0 : nLines;
910 int CBaseView::GetAllLineCount() const
912 int nLines = 0;
913 if (IsLeftViewGood())
914 nLines = m_pwndLeft->GetLineCount();
915 if (IsRightViewGood())
916 nLines = std::max<int>(nLines, m_pwndRight->GetLineCount());
917 if (IsBottomViewGood())
918 nLines = std::max<int>(nLines, m_pwndBottom->GetLineCount());
919 return nLines;
922 void CBaseView::RecalcAllVertScrollBars(BOOL bPositionOnly /*= FALSE*/)
924 if (IsLeftViewGood())
925 m_pwndLeft->RecalcVertScrollBar(bPositionOnly);
926 if (IsRightViewGood())
927 m_pwndRight->RecalcVertScrollBar(bPositionOnly);
928 if (IsBottomViewGood())
929 m_pwndBottom->RecalcVertScrollBar(bPositionOnly);
932 void CBaseView::RecalcVertScrollBar(BOOL bPositionOnly /*= FALSE*/)
934 SCROLLINFO si;
935 si.cbSize = sizeof(si);
936 if (bPositionOnly)
938 si.fMask = SIF_POS;
939 si.nPos = m_nTopLine;
941 else
943 EnableScrollBarCtrl(SB_VERT, TRUE);
944 if (GetAllMinScreenLines() >= GetAllLineCount() && m_nTopLine > 0)
946 m_nTopLine = 0;
947 Invalidate();
949 si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_POS | SIF_RANGE;
950 si.nMin = 0;
951 si.nMax = GetAllLineCount();
952 si.nPage = GetAllMinScreenLines();
953 si.nPos = m_nTopLine;
955 VERIFY(SetScrollInfo(SB_VERT, &si));
958 void CBaseView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
960 CView::OnVScroll(nSBCode, nPos, pScrollBar);
961 if (m_pwndLeft)
962 m_pwndLeft->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
963 if (m_pwndRight)
964 m_pwndRight->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
965 if (m_pwndBottom)
966 m_pwndBottom->OnDoVScroll(nSBCode, nPos, pScrollBar, this);
967 if (m_pwndLocator)
968 m_pwndLocator->Invalidate();
971 void CBaseView::OnDoVScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/, CBaseView * master)
973 // Note we cannot use nPos because of its 16-bit nature
974 SCROLLINFO si;
975 si.cbSize = sizeof(si);
976 si.fMask = SIF_ALL;
977 VERIFY(master->GetScrollInfo(SB_VERT, &si));
979 int nPageLines = GetScreenLines();
980 int nLineCount = GetLineCount();
982 int nNewTopLine;
984 static LONG textwidth = 0;
985 static CString sFormat(MAKEINTRESOURCE(IDS_VIEWSCROLLTIPTEXT));
986 switch (nSBCode)
988 case SB_TOP:
989 nNewTopLine = 0;
990 break;
991 case SB_BOTTOM:
992 nNewTopLine = nLineCount - nPageLines + 1;
993 break;
994 case SB_LINEUP:
995 nNewTopLine = m_nTopLine - 1;
996 break;
997 case SB_LINEDOWN:
998 nNewTopLine = m_nTopLine + 1;
999 break;
1000 case SB_PAGEUP:
1001 nNewTopLine = m_nTopLine - si.nPage + 1;
1002 break;
1003 case SB_PAGEDOWN:
1004 nNewTopLine = m_nTopLine + si.nPage - 1;
1005 break;
1006 case SB_THUMBPOSITION:
1007 m_ScrollTool.Clear();
1008 nNewTopLine = si.nTrackPos;
1009 textwidth = 0;
1010 break;
1011 case SB_THUMBTRACK:
1012 nNewTopLine = si.nTrackPos;
1013 if (GetFocus() == this)
1015 RECT thumbrect;
1016 GetClientRect(&thumbrect);
1017 ClientToScreen(&thumbrect);
1019 POINT thumbpoint;
1020 thumbpoint.x = thumbrect.right;
1021 thumbpoint.y = thumbrect.top + ((thumbrect.bottom-thumbrect.top)*si.nTrackPos)/(si.nMax-si.nMin);
1022 m_ScrollTool.Init(&thumbpoint);
1023 if (textwidth == 0)
1025 CString sTemp = sFormat;
1026 sTemp.Format(sFormat, m_nDigits, 10*m_nDigits-1);
1027 textwidth = m_ScrollTool.GetTextWidth(sTemp);
1029 thumbpoint.x -= textwidth;
1030 int line = GetLineNumber(nNewTopLine);
1031 if (line >= 0)
1032 m_ScrollTool.SetText(&thumbpoint, sFormat, m_nDigits, GetLineNumber(nNewTopLine)+1);
1033 else
1034 m_ScrollTool.SetText(&thumbpoint, m_sNoLineNr);
1036 break;
1037 default:
1038 return;
1041 if (nNewTopLine < 0)
1042 nNewTopLine = 0;
1043 if (nNewTopLine >= nLineCount)
1044 nNewTopLine = nLineCount - 1;
1045 ScrollToLine(nNewTopLine);
1048 void CBaseView::RecalcAllHorzScrollBars(BOOL bPositionOnly /*= FALSE*/)
1050 if (IsLeftViewGood())
1051 m_pwndLeft->RecalcHorzScrollBar(bPositionOnly);
1052 if (IsRightViewGood())
1053 m_pwndRight->RecalcHorzScrollBar(bPositionOnly);
1054 if (IsBottomViewGood())
1055 m_pwndBottom->RecalcHorzScrollBar(bPositionOnly);
1058 void CBaseView::RecalcHorzScrollBar(BOOL bPositionOnly /*= FALSE*/)
1060 SCROLLINFO si;
1061 si.cbSize = sizeof(si);
1062 if (bPositionOnly)
1064 si.fMask = SIF_POS;
1065 si.nPos = m_nOffsetChar;
1067 else
1069 EnableScrollBarCtrl(SB_HORZ, !m_pMainFrame->m_bWrapLines);
1070 if (!m_pMainFrame->m_bWrapLines)
1072 int minScreenChars = GetAllMinScreenChars();
1073 int maxLineLength = GetAllMaxLineLength();
1074 if (minScreenChars >= maxLineLength && m_nOffsetChar > 0)
1076 m_nOffsetChar = 0;
1077 Invalidate();
1079 si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_POS | SIF_RANGE;
1080 si.nMin = 0;
1081 si.nMax = m_pMainFrame->m_bWrapLines ? minScreenChars : maxLineLength;
1082 si.nMax += GetMarginWidth()/GetCharWidth();
1083 si.nPage = GetScreenChars();
1084 si.nPos = m_nOffsetChar;
1087 VERIFY(SetScrollInfo(SB_HORZ, &si));
1090 void CBaseView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1092 CView::OnHScroll(nSBCode, nPos, pScrollBar);
1093 if (m_pwndLeft)
1094 m_pwndLeft->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1095 if (m_pwndRight)
1096 m_pwndRight->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1097 if (m_pwndBottom)
1098 m_pwndBottom->OnDoHScroll(nSBCode, nPos, pScrollBar, this);
1099 if (m_pwndLocator)
1100 m_pwndLocator->Invalidate();
1103 void CBaseView::OnDoHScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/, CBaseView * master)
1105 SCROLLINFO si;
1106 si.cbSize = sizeof(si);
1107 si.fMask = SIF_ALL;
1108 VERIFY(master->GetScrollInfo(SB_HORZ, &si));
1110 int nPageChars = GetScreenChars();
1111 int nMaxLineLength = GetMaxLineLength();
1113 int nNewOffset;
1114 switch (nSBCode)
1116 case SB_LEFT:
1117 nNewOffset = 0;
1118 break;
1119 case SB_BOTTOM:
1120 nNewOffset = nMaxLineLength - nPageChars + 1;
1121 break;
1122 case SB_LINEUP:
1123 nNewOffset = m_nOffsetChar - 1;
1124 break;
1125 case SB_LINEDOWN:
1126 nNewOffset = m_nOffsetChar + 1;
1127 break;
1128 case SB_PAGEUP:
1129 nNewOffset = m_nOffsetChar - si.nPage + 1;
1130 break;
1131 case SB_PAGEDOWN:
1132 nNewOffset = m_nOffsetChar + si.nPage - 1;
1133 break;
1134 case SB_THUMBPOSITION:
1135 case SB_THUMBTRACK:
1136 nNewOffset = si.nTrackPos;
1137 break;
1138 default:
1139 return;
1142 if (nNewOffset >= nMaxLineLength)
1143 nNewOffset = nMaxLineLength - 1;
1144 if (nNewOffset < 0)
1145 nNewOffset = 0;
1146 ScrollToChar(nNewOffset, TRUE);
1149 void CBaseView::ScrollToChar(int nNewOffsetChar, BOOL bTrackScrollBar /*= TRUE*/)
1151 if (m_nOffsetChar != nNewOffsetChar)
1153 int nScrollChars = m_nOffsetChar - nNewOffsetChar;
1154 m_nOffsetChar = nNewOffsetChar;
1155 CRect rcScroll;
1156 GetClientRect(&rcScroll);
1157 rcScroll.left += GetMarginWidth();
1158 rcScroll.top += GetLineHeight()+HEADERHEIGHT;
1159 ScrollWindow(nScrollChars * GetCharWidth(), 0, &rcScroll, &rcScroll);
1160 // update the view header
1161 rcScroll.left = 0;
1162 rcScroll.top = 0;
1163 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
1164 InvalidateRect(&rcScroll, FALSE);
1165 UpdateWindow();
1166 if (bTrackScrollBar)
1167 RecalcHorzScrollBar(TRUE);
1168 UpdateCaret();
1169 if (m_pwndLineDiffBar)
1170 m_pwndLineDiffBar->Invalidate();
1174 void CBaseView::ScrollAllToChar(int nNewOffsetChar, BOOL bTrackScrollBar /* = TRUE */)
1176 if (m_pwndLeft)
1177 m_pwndLeft->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1178 if (m_pwndRight)
1179 m_pwndRight->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1180 if (m_pwndBottom)
1181 m_pwndBottom->ScrollToChar(nNewOffsetChar, bTrackScrollBar);
1184 void CBaseView::ScrollAllSide(int delta)
1186 int nNewOffset = m_nOffsetChar;
1187 nNewOffset += delta;
1188 int nMaxLineLength = GetMaxLineLength();
1189 if (nNewOffset >= nMaxLineLength)
1190 nNewOffset = nMaxLineLength - 1;
1191 if (nNewOffset < 0)
1192 nNewOffset = 0;
1193 ScrollAllToChar(nNewOffset, TRUE);
1194 if (m_pwndLineDiffBar)
1195 m_pwndLineDiffBar->Invalidate();
1196 UpdateCaret();
1199 void CBaseView::ScrollSide(int delta)
1201 int nNewOffset = m_nOffsetChar;
1202 nNewOffset += delta;
1203 int nMaxLineLength = GetMaxLineLength();
1204 if (nNewOffset >= nMaxLineLength)
1205 nNewOffset = nMaxLineLength - 1;
1206 if (nNewOffset < 0)
1207 nNewOffset = 0;
1208 ScrollToChar(nNewOffset, TRUE);
1209 if (m_pwndLineDiffBar)
1210 m_pwndLineDiffBar->Invalidate();
1211 UpdateCaret();
1214 void CBaseView::ScrollVertical(short zDelta)
1216 const int nLineCount = GetLineCount();
1217 int nTopLine = m_nTopLine;
1218 nTopLine -= (zDelta/30);
1219 if (nTopLine < 0)
1220 nTopLine = 0;
1221 if (nTopLine >= nLineCount)
1222 nTopLine = nLineCount - 1;
1223 ScrollToLine(nTopLine, TRUE);
1226 void CBaseView::ScrollToLine(int nNewTopLine, BOOL bTrackScrollBar /*= TRUE*/)
1228 if (m_nTopLine != nNewTopLine)
1230 if (nNewTopLine < 0)
1231 nNewTopLine = 0;
1233 int nScrollLines = m_nTopLine - nNewTopLine;
1235 m_nTopLine = nNewTopLine;
1236 CRect rcScroll;
1237 GetClientRect(&rcScroll);
1238 rcScroll.top += GetLineHeight()+HEADERHEIGHT;
1239 ScrollWindow(0, nScrollLines * GetLineHeight(), &rcScroll, &rcScroll);
1240 UpdateWindow();
1241 if (bTrackScrollBar)
1242 RecalcVertScrollBar(TRUE);
1243 UpdateCaret();
1248 void CBaseView::DrawMargin(CDC *pdc, const CRect &rect, int nLineIndex)
1250 pdc->FillSolidRect(rect, ::GetSysColor(COLOR_SCROLLBAR));
1252 if ((nLineIndex >= 0)&&(m_pViewData)&&(m_pViewData->GetCount()))
1254 int nViewLine = GetViewLineForScreen(nLineIndex);
1255 HICON icon = nullptr;
1256 ASSERT(nViewLine<(int)m_ScreenedViewLine.size());
1257 TScreenedViewLine::EIcon eIcon = m_ScreenedViewLine[nViewLine].eIcon;
1258 if (eIcon==TScreenedViewLine::ICN_UNKNOWN)
1260 DiffStates state = m_pViewData->GetState(nViewLine);
1261 switch (state)
1263 case DIFFSTATE_ADDED:
1264 case DIFFSTATE_THEIRSADDED:
1265 case DIFFSTATE_YOURSADDED:
1266 case DIFFSTATE_IDENTICALADDED:
1267 case DIFFSTATE_CONFLICTADDED:
1268 eIcon = TScreenedViewLine::ICN_ADD;
1269 break;
1270 case DIFFSTATE_REMOVED:
1271 case DIFFSTATE_THEIRSREMOVED:
1272 case DIFFSTATE_YOURSREMOVED:
1273 case DIFFSTATE_IDENTICALREMOVED:
1274 eIcon = TScreenedViewLine::ICN_REMOVED;
1275 break;
1276 case DIFFSTATE_CONFLICTED:
1277 eIcon = TScreenedViewLine::ICN_CONFLICT;
1278 break;
1279 case DIFFSTATE_CONFLICTED_IGNORED:
1280 eIcon = TScreenedViewLine::ICN_CONFLICTIGNORED;
1281 break;
1282 case DIFFSTATE_EDITED:
1283 eIcon = TScreenedViewLine::ICN_EDIT;
1284 break;
1285 default:
1286 break;
1288 bool bIdentical = false;
1289 int blockstart = -1;
1290 int blockend = -1;
1291 if ((state != DIFFSTATE_EDITED)&&(IsBlockWhitespaceOnly(nLineIndex, bIdentical, blockstart, blockend)))
1293 if (bIdentical)
1294 eIcon = TScreenedViewLine::ICN_SAME;
1295 else
1296 eIcon = TScreenedViewLine::ICN_WHITESPACEDIFF;
1297 if (((blockstart >= 0) && (blockend >= 0)) && (blockstart < blockend))
1299 if (nViewLine > blockstart)
1300 Invalidate(); // redraw the upper icons since they're now changing
1301 while (blockstart <= blockend)
1302 m_ScreenedViewLine[blockstart++].eIcon = eIcon;
1305 if (m_pViewData->GetMovedIndex(nViewLine) >= 0)
1306 eIcon = TScreenedViewLine::ICN_MOVED;
1307 if (m_pViewData->GetMarked(nViewLine))
1308 eIcon = TScreenedViewLine::ICN_MARKED;
1309 m_ScreenedViewLine[nViewLine].eIcon = eIcon;
1311 switch (eIcon)
1313 case TScreenedViewLine::ICN_UNKNOWN:
1314 case TScreenedViewLine::ICN_NONE:
1315 break;
1316 case TScreenedViewLine::ICN_SAME:
1317 icon = m_hEqualIcon;
1318 break;
1319 case TScreenedViewLine::ICN_EDIT:
1320 icon = m_hEditedIcon;
1321 break;
1322 case TScreenedViewLine::ICN_WHITESPACEDIFF:
1323 icon = m_hWhitespaceBlockIcon;
1324 break;
1325 case TScreenedViewLine::ICN_ADD:
1326 icon = m_hAddedIcon;
1327 break;
1328 case TScreenedViewLine::ICN_CONFLICT:
1329 icon = m_hConflictedIcon;
1330 break;
1331 case TScreenedViewLine::ICN_CONFLICTIGNORED:
1332 icon = m_hConflictedIgnoredIcon;
1333 break;
1334 case TScreenedViewLine::ICN_REMOVED:
1335 icon = m_hRemovedIcon;
1336 break;
1337 case TScreenedViewLine::ICN_MOVED:
1338 icon = m_hMovedIcon;
1339 break;
1340 case TScreenedViewLine::ICN_MARKED:
1341 icon = m_hMarkedIcon;
1342 break;
1345 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1346 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1347 if (icon)
1349 ::DrawIconEx(pdc->m_hDC, rect.left + 2, rect.top + (rect.Height() - iconHeight) / 2, icon, iconWidth, iconHeight, 0, nullptr, DI_NORMAL);
1351 if ((m_bViewLinenumbers)&&(m_nDigits))
1353 int nSubLine = GetSubLineOffset(nLineIndex);
1354 bool bIsFirstSubline = (nSubLine == 0) || (nSubLine == -1);
1355 CString sLinenumber;
1356 if (bIsFirstSubline)
1358 CString sLinenumberFormat;
1359 int nLineNumber = GetLineNumber(nLineIndex);
1360 if (IsViewLineHidden(GetViewLineForScreen(nLineIndex)))
1362 // TODO: do not show if there is no number hidden
1363 // TODO: show number if there is only one
1364 sLinenumberFormat.Format(L"%%%ds", m_nDigits);
1365 sLinenumber.Format(sLinenumberFormat, (m_nDigits>1) ? L"↕⁞" : L"⁞"); // alternative …
1367 else if (nLineNumber >= 0)
1369 sLinenumberFormat.Format(L"%%%dd", m_nDigits);
1370 sLinenumber.Format(sLinenumberFormat, nLineNumber+1);
1372 else if (m_pMainFrame->m_bWrapLines)
1374 sLinenumberFormat.Format(L"%%%ds", m_nDigits);
1375 sLinenumber.Format(sLinenumberFormat, L"·");
1377 if (!sLinenumber.IsEmpty())
1379 pdc->SetBkColor(::GetSysColor(COLOR_SCROLLBAR));
1380 pdc->SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
1382 pdc->SelectObject(GetFont());
1383 pdc->ExtTextOut(rect.left + iconWidth + 2, rect.top, ETO_CLIPPED, &rect, sLinenumber, nullptr);
1390 int CBaseView::GetMarginWidth()
1392 int marginWidth = GetSystemMetrics(SM_CXSMICON) + 2 + 2;
1394 if ((m_bViewLinenumbers)&&(m_pViewData)&&(m_pViewData->GetCount()))
1396 if (m_nDigits <= 0)
1398 int nLength = (int)m_pViewData->GetCount();
1399 // find out how many digits are needed to show the highest line number
1400 CString sMax;
1401 sMax.Format(L"%d", nLength);
1402 m_nDigits = sMax.GetLength();
1404 int nWidth = GetCharWidth();
1405 marginWidth += (m_nDigits * nWidth) + 2;
1408 return marginWidth;
1411 void CBaseView::DrawHeader(CDC *pdc, const CRect &rect)
1413 CRect textrect(rect.left, rect.top, rect.Width(), GetLineHeight()+HEADERHEIGHT);
1414 COLORREF crBk, crFg;
1415 if (IsBottomViewGood())
1417 CDiffColors::GetInstance().GetColors(DIFFSTATE_NORMAL, crBk, crFg);
1418 crBk = ::GetSysColor(COLOR_SCROLLBAR);
1420 else
1422 DiffStates state = DIFFSTATE_REMOVED;
1423 if (this == m_pwndRight)
1425 state = DIFFSTATE_ADDED;
1427 CDiffColors::GetInstance().GetColors(state, crBk, crFg);
1429 pdc->SetBkColor(crBk);
1430 pdc->FillSolidRect(textrect, crBk);
1432 pdc->SetTextColor(crFg);
1434 pdc->SelectObject(GetFont(FALSE, TRUE));
1436 CString sViewTitle;
1437 if (IsModified())
1439 sViewTitle = L"* " + m_sWindowName;
1441 else
1443 sViewTitle = m_sWindowName;
1445 int nStringLength = (GetCharWidth()*m_sWindowName.GetLength());
1446 if (nStringLength > rect.Width())
1448 int offset = std::min<int>(m_nOffsetChar, (nStringLength-rect.Width())/GetCharWidth()+1);
1449 sViewTitle = m_sWindowName.Mid(offset);
1451 pdc->ExtTextOut(std::max<int>(rect.left + (rect.Width()-nStringLength)/2, 1),
1452 rect.top + (HEADERHEIGHT / 2), ETO_CLIPPED, textrect, sViewTitle, nullptr);
1453 if (this->GetFocus() == this)
1454 pdc->DrawEdge(textrect, EDGE_BUMP, BF_RECT);
1455 else
1456 pdc->DrawEdge(textrect, EDGE_ETCHED, BF_RECT);
1459 void CBaseView::OnDraw(CDC * pDC)
1461 CRect rcClient;
1462 GetClientRect(rcClient);
1464 int nLineCount = GetLineCount();
1465 int nLineHeight = GetLineHeight();
1467 CDC cacheDC;
1468 VERIFY(cacheDC.CreateCompatibleDC(pDC));
1469 if (!m_pCacheBitmap)
1471 m_pCacheBitmap = new CBitmap;
1472 VERIFY(m_pCacheBitmap->CreateCompatibleBitmap(pDC, rcClient.Width(), nLineHeight));
1474 CBitmap *pOldBitmap = cacheDC.SelectObject(m_pCacheBitmap);
1476 DrawHeader(pDC, rcClient);
1478 CRect rcLine;
1479 rcLine = rcClient;
1480 rcLine.top += nLineHeight+HEADERHEIGHT;
1481 rcLine.bottom = rcLine.top + nLineHeight;
1482 CRect rcCacheMargin(0, 0, GetMarginWidth(), nLineHeight);
1483 CRect rcCacheLine(GetMarginWidth(), 0, rcLine.Width(), nLineHeight);
1485 int nCurrentLine = m_nTopLine;
1486 bool bBeyondFileLineCached = false;
1487 while (rcLine.top < rcClient.bottom)
1489 if (nCurrentLine < nLineCount)
1491 DrawMargin(&cacheDC, rcCacheMargin, nCurrentLine);
1492 DrawSingleLine(&cacheDC, rcCacheLine, nCurrentLine);
1493 bBeyondFileLineCached = false;
1495 else if (!bBeyondFileLineCached)
1497 DrawMargin(&cacheDC, rcCacheMargin, -1);
1498 DrawSingleLine(&cacheDC, rcCacheLine, -1);
1499 bBeyondFileLineCached = true;
1502 VERIFY(pDC->BitBlt(rcLine.left, rcLine.top, rcLine.Width(), rcLine.Height(), &cacheDC, 0, 0, SRCCOPY));
1504 nCurrentLine ++;
1505 rcLine.OffsetRect(0, nLineHeight);
1508 cacheDC.SelectObject(pOldBitmap);
1509 cacheDC.DeleteDC();
1512 bool CBaseView::IsStateConflicted(DiffStates state)
1514 switch (state)
1516 case DIFFSTATE_CONFLICTED:
1517 case DIFFSTATE_CONFLICTED_IGNORED:
1518 case DIFFSTATE_CONFLICTEMPTY:
1519 case DIFFSTATE_CONFLICTADDED:
1520 return true;
1522 return false;
1525 bool CBaseView::IsStateEmpty(DiffStates state)
1527 switch (state)
1529 case DIFFSTATE_CONFLICTEMPTY:
1530 case DIFFSTATE_UNKNOWN:
1531 case DIFFSTATE_EMPTY:
1532 return true;
1534 return false;
1537 bool CBaseView::IsStateRemoved(DiffStates state)
1539 switch (state)
1541 case DIFFSTATE_REMOVED:
1542 case DIFFSTATE_THEIRSREMOVED:
1543 case DIFFSTATE_YOURSREMOVED:
1544 case DIFFSTATE_IDENTICALREMOVED:
1545 return true;
1547 return false;
1550 DiffStates CBaseView::ResolveState(DiffStates state)
1552 if (IsStateConflicted(state))
1554 if (state == DIFFSTATE_CONFLICTEMPTY)
1555 return DIFFSTATE_CONFLICTRESOLVEDEMPTY;
1556 else
1557 return DIFFSTATE_CONFLICTRESOLVED;
1559 return state;
1563 bool CBaseView::IsLineEmpty(int nLineIndex)
1565 if (m_pViewData == 0)
1566 return FALSE;
1567 int nViewLine = GetViewLineForScreen(nLineIndex);
1568 return IsViewLineEmpty(nViewLine);
1571 bool CBaseView::IsViewLineEmpty(int nViewLine)
1573 if (m_pViewData == 0)
1574 return FALSE;
1575 const DiffStates state = m_pViewData->GetState(nViewLine);
1576 return IsStateEmpty(state);
1579 bool CBaseView::IsLineRemoved(int nLineIndex)
1581 if (m_pViewData == 0)
1582 return FALSE;
1583 int nViewLine = GetViewLineForScreen(nLineIndex);
1584 return IsViewLineRemoved(nViewLine);
1587 bool CBaseView::IsViewLineRemoved(int nViewLine)
1589 if (m_pViewData == 0)
1590 return FALSE;
1591 const DiffStates state = m_pViewData->GetState(nViewLine);
1592 return IsStateRemoved(state);
1595 bool CBaseView::IsViewLineConflicted(int nLineIndex)
1597 if (m_pViewData == 0)
1598 return false;
1599 const DiffStates state = m_pViewData->GetState(nLineIndex);
1600 return IsStateConflicted(state);
1603 COLORREF CBaseView::InlineDiffColor(int nLineIndex)
1605 return IsLineRemoved(nLineIndex) ? m_InlineRemovedBk : m_InlineAddedBk;
1608 COLORREF CBaseView::InlineViewLineDiffColor(int nViewLine)
1610 return IsViewLineRemoved(nViewLine) ? m_InlineRemovedBk : m_InlineAddedBk;
1613 void CBaseView::DrawLineEnding(CDC *pDC, const CRect &rc, int nLineIndex, const CPoint& origin)
1615 if (origin.x < (rc.left - GetCharWidth() +1))
1616 return;
1617 if (!(m_bViewWhitespace && m_pViewData && (nLineIndex >= 0) && (nLineIndex < GetLineCount())))
1618 return;
1619 int viewLine = GetViewLineForScreen(nLineIndex);
1620 EOL ending = m_pViewData->GetLineEnding(viewLine);
1621 if (m_bIconLFs)
1623 HICON hEndingIcon = nullptr;
1624 switch (ending)
1626 case EOL_CR: hEndingIcon = m_hLineEndingCR; break;
1627 case EOL_CRLF: hEndingIcon = m_hLineEndingCRLF; break;
1628 case EOL_LF: hEndingIcon = m_hLineEndingLF; break;
1629 default: return;
1631 // If EOL style has changed, color end-of-line markers as inline differences.
1633 m_bShowInlineDiff && m_pOtherViewData &&
1634 (viewLine < m_pOtherViewData->GetCount()) &&
1635 (ending != EOL_NOENDING) &&
1636 (ending != m_pOtherViewData->GetLineEnding(viewLine) &&
1637 (m_pOtherViewData->GetLineEnding(viewLine) != EOL_NOENDING))
1640 pDC->FillSolidRect(origin.x, origin.y, rc.Height(), rc.Height(), InlineDiffColor(nLineIndex));
1643 DrawIconEx(pDC->GetSafeHdc(), origin.x, origin.y, hEndingIcon, rc.Height(), rc.Height(), 0, nullptr, DI_NORMAL);
1645 else
1647 CDpiScale dpi(pDC->GetSafeHdc());
1648 CPen pen(PS_SOLID, 0, m_WhiteSpaceFg);
1649 CPen * oldpen = pDC->SelectObject(&pen);
1650 int yMiddle = origin.y + rc.Height()/2;
1651 int xMiddle = origin.x+GetCharWidth()/2;
1652 bool bMultiline = false;
1653 if (((int)m_Screen2View.size() > nLineIndex+1) && (GetViewLineForScreen(nLineIndex+1) == viewLine))
1655 if (GetLineLength(nLineIndex+1))
1657 // multiline
1658 bMultiline = true;
1659 pDC->MoveTo(origin.x, yMiddle-dpi.ScaleY(2));
1660 pDC->LineTo(origin.x + GetCharWidth() - dpi.ScaleX(1), yMiddle - dpi.ScaleY(2));
1661 pDC->LineTo(origin.x + GetCharWidth() - dpi.ScaleX(1), yMiddle + dpi.ScaleY(2));
1662 pDC->LineTo(origin.x, yMiddle + dpi.ScaleY(2));
1664 else if (GetLineLength(nLineIndex) == 0)
1665 bMultiline = true;
1667 else if ((nLineIndex > 0) && (GetViewLineForScreen(nLineIndex-1) == viewLine) && (GetLineLength(nLineIndex) == 0))
1668 bMultiline = true;
1670 if (!bMultiline)
1672 switch (ending)
1674 case EOL_AUTOLINE:
1675 case EOL_CRLF:
1676 // arrow from top to middle+2, then left
1677 pDC->MoveTo(origin.x + GetCharWidth() - dpi.ScaleX(1), rc.top+dpi.ScaleY(1));
1678 pDC->LineTo(origin.x + GetCharWidth() - dpi.ScaleX(1), yMiddle);
1679 case EOL_CR:
1680 // arrow from right to left
1681 pDC->MoveTo(origin.x + GetCharWidth() - dpi.ScaleX(1), yMiddle);
1682 pDC->LineTo(origin.x, yMiddle);
1683 pDC->LineTo(origin.x + dpi.ScaleX(4), yMiddle + dpi.ScaleY(4));
1684 pDC->MoveTo(origin.x, yMiddle);
1685 pDC->LineTo(origin.x + dpi.ScaleX(4), yMiddle - dpi.ScaleY(4));
1686 break;
1687 case EOL_LFCR:
1688 // from right-upper to left then down
1689 pDC->MoveTo(origin.x + GetCharWidth() - dpi.ScaleX(1), yMiddle-dpi.ScaleY(2));
1690 pDC->LineTo(xMiddle, yMiddle - dpi.ScaleY(2));
1691 pDC->LineTo(xMiddle, rc.bottom - dpi.ScaleY(1));
1692 pDC->LineTo(xMiddle + dpi.ScaleX(4), rc.bottom - dpi.ScaleY(5));
1693 pDC->MoveTo(xMiddle, rc.bottom - dpi.ScaleY(1));
1694 pDC->LineTo(xMiddle - dpi.ScaleX(4), rc.bottom - dpi.ScaleY(5));
1695 break;
1696 case EOL_LF:
1697 // arrow from top to bottom
1698 pDC->MoveTo(xMiddle, rc.top);
1699 pDC->LineTo(xMiddle, rc.bottom - dpi.ScaleY(1));
1700 pDC->LineTo(xMiddle + dpi.ScaleX(4), rc.bottom - dpi.ScaleY(5));
1701 pDC->MoveTo(xMiddle, rc.bottom - dpi.ScaleY(1));
1702 pDC->LineTo(xMiddle - dpi.ScaleX(4), rc.bottom - dpi.ScaleY(5));
1703 break;
1704 case EOL_FF: // Form Feed, U+000C
1705 case EOL_NEL: // Next Line, U+0085
1706 case EOL_LS: // Line Separator, U+2028
1707 case EOL_PS: // Paragraph Separator, U+2029
1708 // draw a horizontal line at the bottom of this line
1709 pDC->FillSolidRect(rc.left, rc.bottom-1, rc.right, rc.bottom, GetSysColor(COLOR_WINDOWTEXT));
1710 pDC->MoveTo(origin.x+GetCharWidth()-1, rc.bottom-GetCharWidth()-2);
1711 pDC->LineTo(origin.x, rc.bottom-2);
1712 pDC->LineTo(origin.x+5, rc.bottom-2);
1713 pDC->MoveTo(origin.x, rc.bottom-2);
1714 pDC->LineTo(origin.x+1, rc.bottom-6);
1715 break;
1716 default: // other EOLs
1717 // arrow from top right to bottom left
1718 pDC->MoveTo(origin.x+GetCharWidth()-1, rc.bottom-GetCharWidth());
1719 pDC->LineTo(origin.x, rc.bottom-1);
1720 pDC->LineTo(origin.x+5, rc.bottom-2);
1721 pDC->MoveTo(origin.x, rc.bottom-1);
1722 pDC->LineTo(origin.x+1, rc.bottom-6);
1723 break;
1724 case EOL_NOENDING:
1725 break;
1728 pDC->SelectObject(oldpen);
1732 void CBaseView::DrawBlockLine(CDC *pDC, const CRect &rc, int nLineIndex)
1734 if (!m_bShowSelection)
1735 return;
1737 int nSelBlockStart;
1738 int nSelBlockEnd;
1739 if (!GetViewSelection(nSelBlockStart, nSelBlockEnd))
1740 return;
1742 const int THICKNESS = 2;
1743 COLORREF rectcol = GetSysColor(m_bFocused ? COLOR_WINDOWTEXT : COLOR_GRAYTEXT);
1745 int nViewLineIndex = GetViewLineForScreen(nLineIndex);
1746 int nSubLine = GetSubLineOffset(nLineIndex);
1747 bool bFirstLineOfViewLine = (nSubLine==0 || nSubLine==-1);
1748 if ((nViewLineIndex == nSelBlockStart) && bFirstLineOfViewLine)
1750 pDC->FillSolidRect(rc.left, rc.top, rc.Width(), THICKNESS, rectcol);
1753 bool bLastLineOfViewLine = (nLineIndex+1 == m_Screen2View.size()) || (GetViewLineForScreen(nLineIndex) != GetViewLineForScreen(nLineIndex+1));
1754 if ((nViewLineIndex == nSelBlockEnd) && bLastLineOfViewLine)
1756 pDC->FillSolidRect(rc.left, rc.bottom - THICKNESS, rc.Width(), THICKNESS, rectcol);
1760 void CBaseView::DrawTextLine(
1761 CDC * pDC, const CRect &rc, int nLineIndex, POINT& coords)
1763 ASSERT(nLineIndex < GetLineCount());
1764 int nViewLine = GetViewLineForScreen(nLineIndex);
1765 ASSERT(m_pViewData && (nViewLine < m_pViewData->GetCount()));
1767 LineColors lineCols = GetLineColors(nViewLine);
1769 CString sViewLine = GetViewLineChars(nViewLine);
1770 // mark selection
1771 if (m_bShowSelection && HasTextSelection())
1773 // has this line selection ?
1774 if ((m_ptSelectionViewPosStart.y <= nViewLine) && (nViewLine <= m_ptSelectionViewPosEnd.y))
1776 int nViewLineLength = sViewLine.GetLength();
1778 // first suppose the whole line is selected
1779 int selectedStart = 0;
1780 int selectedEnd = nViewLineLength;
1782 // the view line is partially selected
1783 if (m_ptSelectionViewPosStart.y == nViewLine)
1785 selectedStart = m_ptSelectionViewPosStart.x;
1788 if (m_ptSelectionViewPosEnd.y == nViewLine)
1790 selectedEnd = m_ptSelectionViewPosEnd.x;
1792 // apply selection coloring
1793 // First enforce start and end point
1794 lineCols.SplitBlock(selectedStart);
1795 lineCols.SplitBlock(selectedEnd);
1796 // change color of affected parts
1797 long intenseColorScale = m_bFocused ? 70 : 30;
1798 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(selectedStart);
1799 for ( ; it != lineCols.end() && it->first < selectedEnd; ++it)
1801 auto& second = it->second;
1802 COLORREF crBk = CAppUtils::IntenseColor(intenseColorScale, second.background);
1803 if (second.shot == second.background)
1804 second.shot = crBk;
1805 second.background = crBk;
1806 second.text = CAppUtils::IntenseColor(intenseColorScale, second.text);
1811 // TODO: remove duplicate from selection and mark
1812 if (!m_sMarkedWord.IsEmpty())
1814 int nMarkLength = m_sMarkedWord.GetLength();
1815 //int nViewLineLength = sViewLine.GetLength();
1816 const TCHAR * text = sViewLine;
1817 const TCHAR * findText = text;
1818 while ((findText = wcsstr(findText, (LPCTSTR)m_sMarkedWord))!=0)
1820 int nMarkStart = static_cast<int>(findText - text);
1821 int nMarkEnd = nMarkStart + nMarkLength;
1822 findText += nMarkLength;
1823 ECharGroup eLeft = GetCharGroup(sViewLine, nMarkStart - 1);
1824 ECharGroup eStart = GetCharGroup(sViewLine, nMarkStart);
1825 if (eLeft == eStart)
1826 continue;
1827 ECharGroup eRight = GetCharGroup(sViewLine, nMarkEnd);
1828 ECharGroup eEnd = GetCharGroup(sViewLine, nMarkEnd - 1);
1829 if (eRight == eEnd)
1830 continue;
1832 // First enforce start and end point
1833 lineCols.SplitBlock(nMarkStart);
1834 lineCols.SplitBlock(nMarkEnd);
1835 // change color of affected parts
1836 const long int nIntenseColorScale = 200;
1837 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(nMarkStart);
1838 for ( ; it != lineCols.end() && it->first < nMarkEnd; ++it)
1840 auto& second = it->second;
1841 COLORREF crBk = CAppUtils::IntenseColor(nIntenseColorScale, second.background);
1842 if (second.shot == second.background)
1843 second.shot = crBk;
1844 second.background = crBk;
1845 second.text = CAppUtils::IntenseColor(nIntenseColorScale, second.text);
1849 if (!m_sFindText.IsEmpty())
1851 int nMarkStart = 0;
1852 int nMarkEnd = 0;
1853 int nStringPos = nMarkStart;
1854 CString searchLine = sViewLine;
1855 if (!m_bMatchCase)
1856 searchLine.MakeLower();
1857 while (StringFound(searchLine, SearchNext, nMarkStart, nMarkEnd)!=0)
1859 // First enforce start and end point
1860 lineCols.SplitBlock(nMarkStart+nStringPos);
1861 lineCols.SplitBlock(nMarkEnd+nStringPos);
1862 // change color of affected parts
1863 const long int nIntenseColorScale = 30;
1864 std::map<int, linecolors_t>::iterator it = lineCols.lower_bound(nMarkStart+nStringPos);
1865 for ( ; it != lineCols.end() && it->first < nMarkEnd; ++it)
1867 auto& second = it->second;
1868 COLORREF crBk = CAppUtils::IntenseColor(nIntenseColorScale, second.background);
1869 if (second.shot == second.background)
1870 second.shot = crBk;
1871 second.background = crBk;
1872 second.text = CAppUtils::IntenseColor(nIntenseColorScale, second.text);
1874 searchLine = searchLine.Mid(nMarkEnd);
1875 nStringPos = nMarkEnd;
1876 nMarkStart = 0;
1877 nMarkEnd = 0;
1881 // @ this point we may cache data for next line which may be same in wrapped mode
1883 int nTextOffset = 0;
1884 int nSubline = GetSubLineOffset(nLineIndex);
1885 for (int n=0; n<nSubline; n++)
1887 CString sLine = m_ScreenedViewLine[nViewLine].SubLines[n];
1888 nTextOffset += sLine.GetLength();
1891 CString sLine = GetLineChars(nLineIndex);
1892 int nLineLength = sLine.GetLength();
1893 CString sLineExp = ExpandChars(sLine);
1894 LPCTSTR textExp = sLineExp;
1895 //int nLineLengthExp = sLineExp.GetLength();
1896 int nStartExp = 0;
1897 int nLeft = coords.x;
1898 for (std::map<int, linecolors_t>::const_iterator itStart = lineCols.begin(); itStart != lineCols.end(); ++itStart)
1900 std::map<int, linecolors_t>::const_iterator itEnd = itStart;
1901 ++itEnd;
1902 int nStart = std::max<int>(0, itStart->first - nTextOffset);
1903 int nEnd = nLineLength;
1904 if (itEnd != lineCols.end())
1906 nEnd = std::min<int>(nEnd, itEnd->first - nTextOffset);
1908 int nBlockLength = nEnd - nStart;
1909 if (nBlockLength > 0 && nEnd>=0)
1911 auto& second = itStart->second;
1912 pDC->SetBkColor(second.background);
1913 pDC->SetTextColor(second.text);
1914 int nEndExp = CountExpandedChars(sLine, nEnd);
1915 int nTextLength = nEndExp - nStartExp;
1916 LPCTSTR p_zBlockText = textExp + nStartExp;
1917 SIZE Size;
1918 GetTextExtentPoint32(pDC->GetSafeHdc(), p_zBlockText, nTextLength, &Size); // falls time-2-tme
1919 int nRight = nLeft + Size.cx;
1920 if ((nRight > rc.left) && (nLeft < rc.right))
1922 // note: ExtTextOut has a limit for the length of the string. That limit is supposed
1923 // to be 8192, but that's not really true: I found that the limit (at least on my machine and a few others)
1924 // is 4094 (4095 doesn't work anymore).
1925 // So we limit the length here to that 4094 chars.
1926 // In case we're scrolled to the right, there's no need to draw the string
1927 // from way outside our window, so we also offset the drawing to the start of the window.
1928 // This reduces the string length as well.
1929 int offset = 0;
1930 int leftcoord = nLeft;
1931 if (nLeft < 0)
1933 int fit = nTextLength;
1934 auto posBuffer = std::make_unique<int[]>(fit);
1935 GetTextExtentExPoint(pDC->GetSafeHdc(), p_zBlockText, nTextLength, INT_MAX, &fit, posBuffer.get(), &Size);
1936 int lower = 0, upper = fit - 1;
1939 int middle = (upper + lower + 1) / 2;
1940 int width = posBuffer[middle];
1941 if (rc.left - nLeft < width)
1942 upper = middle - 1;
1943 else
1944 lower = middle;
1945 } while (lower < upper);
1947 offset = lower;
1948 nTextLength -= offset;
1949 leftcoord += lower > 0 ? posBuffer[lower - 1] : 0;
1952 pDC->ExtTextOut(leftcoord, coords.y, ETO_CLIPPED, &rc, p_zBlockText + offset, min(nTextLength, 4094), nullptr);
1953 if ((second.shot != second.background) && (itStart->first == nStart + nTextOffset))
1954 pDC->FillSolidRect(nLeft - 1, rc.top, 1, rc.Height(), second.shot);
1956 nLeft = nRight;
1957 coords.x = nRight;
1958 nStartExp = nEndExp;
1963 void CBaseView::DrawSingleLine(CDC *pDC, const CRect &rc, int nLineIndex)
1965 if (nLineIndex >= GetLineCount())
1966 nLineIndex = -1;
1967 ASSERT(nLineIndex >= -1);
1969 if ((nLineIndex == -1) || !m_pViewData)
1971 // Draw line beyond the text
1972 COLORREF crBkgnd, crText;
1973 CDiffColors::GetInstance().GetColors(DIFFSTATE_UNKNOWN, crBkgnd, crText);
1974 pDC->FillSolidRect(rc, crBkgnd);
1975 return;
1978 int viewLine = GetViewLineForScreen(nLineIndex);
1979 if (m_pMainFrame->m_bCollapsed)
1981 if (m_pViewData->GetHideState(viewLine) == HIDESTATE_MARKER)
1983 COLORREF crBkgnd, crText;
1984 CDiffColors::GetInstance().GetColors(DIFFSTATE_UNKNOWN, crBkgnd, crText);
1985 pDC->FillSolidRect(rc, crBkgnd);
1987 const int THICKNESS = 2;
1988 COLORREF rectcol = GetSysColor(COLOR_WINDOWTEXT);
1989 pDC->FillSolidRect(rc.left, rc.top + (rc.Height()/2), rc.Width(), THICKNESS, rectcol);
1990 pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
1991 pDC->SetBkColor(crBkgnd);
1992 CRect rect = rc;
1993 pDC->DrawText(L"{...}", &rect, DT_NOPREFIX|DT_SINGLELINE|DT_CENTER);
1994 return;
1998 DiffStates diffState = m_pViewData->GetState(viewLine);
1999 COLORREF crBkgnd, crText;
2000 CDiffColors::GetInstance().GetColors(diffState, crBkgnd, crText);
2002 if (diffState == DIFFSTATE_CONFLICTED)
2004 // conflicted lines are shown without 'text' on them
2005 CRect rect = rc;
2006 pDC->FillSolidRect(rc, crBkgnd);
2007 // now draw some faint text patterns
2008 pDC->SetTextColor(CAppUtils::IntenseColor(130, crBkgnd));
2009 pDC->DrawText(m_sConflictedText, rect, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE);
2010 DrawBlockLine(pDC, rc, nLineIndex);
2011 return;
2014 CPoint origin(rc.left - m_nOffsetChar * GetCharWidth(), rc.top);
2015 CString sLine = GetLineChars(nLineIndex);
2016 if (sLine.IsEmpty())
2018 pDC->FillSolidRect(rc, crBkgnd);
2019 DrawBlockLine(pDC, rc, nLineIndex);
2020 DrawLineEnding(pDC, rc, nLineIndex, origin);
2021 return;
2024 CheckOtherView();
2026 // Draw the line
2028 pDC->SelectObject(GetFont(FALSE, FALSE));
2030 DrawTextLine(pDC, rc, nLineIndex, origin);
2032 // draw white space after the end of line
2033 CRect frect = rc;
2034 if (origin.x > frect.left)
2035 frect.left = origin.x;
2036 if (frect.right > frect.left)
2037 pDC->FillSolidRect(frect, crBkgnd);
2039 // draw the whitespace chars
2040 LPCTSTR pszChars = (LPCWSTR)sLine;
2041 if (m_bViewWhitespace)
2043 int xpos = 0;
2044 int nChars = 0;
2045 LPCTSTR pLastSpace = pszChars;
2046 int y = rc.top + (rc.bottom-rc.top)/2;
2047 xpos -= m_nOffsetChar * GetCharWidth();
2049 CDpiScale dpi(pDC->GetSafeHdc());
2050 CPen pen(PS_SOLID, 0, m_WhiteSpaceFg);
2051 while (*pszChars)
2053 switch (*pszChars)
2055 case '\t':
2057 xpos += pDC->GetTextExtent(pLastSpace, (int)(pszChars - pLastSpace)).cx;
2058 pLastSpace = pszChars + 1;
2059 // draw an arrow
2060 int nSpaces = GetTabSize() - nChars % GetTabSize();
2061 if (xpos + nSpaces * GetCharWidth() > 0)
2063 int xposreal = max(xpos, 0);
2064 if ((xposreal > 0) || (nSpaces > 0))
2066 CPen * oldPen = pDC->SelectObject(&pen);
2067 pDC->MoveTo(xposreal + rc.left + dpi.ScaleX(2), y);
2068 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - dpi.ScaleX(2), y);
2069 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - dpi.ScaleX(6), y - dpi.ScaleY(4));
2070 pDC->MoveTo((xpos + nSpaces * GetCharWidth()) + rc.left - dpi.ScaleX(2), y);
2071 pDC->LineTo((xpos + nSpaces * GetCharWidth()) + rc.left - dpi.ScaleX(6), y + dpi.ScaleY(4));
2072 pDC->SelectObject(oldPen);
2075 xpos += nSpaces * GetCharWidth();
2076 nChars += nSpaces;
2078 break;
2079 case ' ':
2081 xpos += pDC->GetTextExtent(pLastSpace, (int)(pszChars - pLastSpace)).cx;
2082 pLastSpace = pszChars + 1;
2083 if (xpos >= 0)
2085 const int cxWhitespace = dpi.ScaleX(2);
2086 const int cyWhitespace = dpi.ScaleY(2);
2087 // draw 2-logical pixel rectangle, like Scintilla editor.
2088 pDC->FillSolidRect(xpos + rc.left + GetCharWidth() / 2 - cxWhitespace/2, y, cxWhitespace, cyWhitespace, m_WhiteSpaceFg);
2090 xpos += GetCharWidth();
2091 nChars++;
2093 break;
2094 default:
2095 nChars++;
2096 break;
2098 pszChars++;
2101 DrawBlockLine(pDC, rc, nLineIndex);
2102 if (origin.x >= rc.left)
2103 DrawLineEnding(pDC, rc, nLineIndex, origin);
2106 void CBaseView::ExpandChars(const CString &sLine, int nOffset, int nCount, CString &line)
2108 if (nCount <= 0)
2110 line.Empty();
2111 return;
2114 int nTabSize = GetTabSize();
2116 int nActualOffset = CountExpandedChars(sLine, nOffset);
2118 LPCTSTR pszChars = (LPCWSTR)sLine;
2119 pszChars += nOffset;
2120 int nLength = nCount;
2122 int nTabCount = 0;
2123 for (int i=0; i<nLength; i++)
2125 if (pszChars[i] == L'\t')
2126 nTabCount ++;
2129 LPTSTR pszBuf = line.GetBuffer(nLength + nTabCount * (nTabSize - 1) + 1);
2130 int nCurPos = 0;
2131 if (nTabCount > 0 || m_bViewWhitespace)
2133 for (int i=0; i<nLength; i++)
2135 if (pszChars[i] == L'\t')
2137 int nSpaces = nTabSize - (nActualOffset + nCurPos) % nTabSize;
2138 while (nSpaces > 0)
2140 pszBuf[nCurPos ++] = L' ';
2141 nSpaces --;
2144 else
2146 pszBuf[nCurPos] = pszChars[i];
2147 nCurPos ++;
2151 else
2153 memcpy(pszBuf, pszChars, sizeof(TCHAR) * nLength);
2154 nCurPos = nLength;
2156 pszBuf[nCurPos] = 0;
2157 line.ReleaseBuffer();
2160 CString CBaseView::ExpandChars(const CString &sLine, int nOffset)
2162 CString sRet;
2163 int nLength = sLine.GetLength();
2164 ExpandChars(sLine, nOffset, nLength, sRet);
2165 return sRet;
2168 int CBaseView::CountExpandedChars(const CString &sLine, int nLength)
2170 int nTabSize = GetTabSize();
2172 int nActualOffset = 0;
2173 for (int i=0; i<nLength; i++)
2175 if (sLine[i] == L'\t')
2176 nActualOffset += (nTabSize - nActualOffset % nTabSize);
2177 else
2178 nActualOffset ++;
2180 return nActualOffset;
2183 void CBaseView::ScrollAllToLine(int nNewTopLine, BOOL bTrackScrollBar)
2185 if (m_pwndLeft)
2186 m_pwndLeft->ScrollToLine(nNewTopLine, bTrackScrollBar);
2187 if (m_pwndRight)
2188 m_pwndRight->ScrollToLine(nNewTopLine, bTrackScrollBar);
2189 if (m_pwndBottom)
2190 m_pwndBottom->ScrollToLine(nNewTopLine, bTrackScrollBar);
2191 if (m_pwndLocator)
2192 m_pwndLocator->Invalidate();
2195 void CBaseView::GoToLine(int nNewLine, BOOL bAll)
2197 //almost the same as ScrollAllToLine, but try to put the line in the
2198 //middle of the view, not on top
2199 int nNewTopLine = nNewLine - GetScreenLines()/2;
2200 if (nNewTopLine < 0)
2201 nNewTopLine = 0;
2202 if (nNewTopLine >= (int)m_Screen2View.size())
2203 nNewTopLine = (int)m_Screen2View.size()-1;
2204 if (bAll)
2205 ScrollAllToLine(nNewTopLine);
2206 else
2207 ScrollToLine(nNewTopLine);
2210 BOOL CBaseView::OnEraseBkgnd(CDC* /*pDC*/)
2212 return TRUE;
2215 int CBaseView::OnCreate(LPCREATESTRUCT lpCreateStruct)
2217 if (CView::OnCreate(lpCreateStruct) == -1)
2218 return -1;
2220 SecureZeroMemory(&m_lfBaseFont, sizeof(m_lfBaseFont));
2221 //lstrcpy(m_lfBaseFont.lfFaceName, L"Courier New");
2222 //lstrcpy(m_lfBaseFont.lfFaceName, L"FixedSys");
2223 m_lfBaseFont.lfHeight = 0;
2224 m_lfBaseFont.lfWeight = FW_NORMAL;
2225 m_lfBaseFont.lfItalic = FALSE;
2226 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
2227 m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
2228 m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
2229 m_lfBaseFont.lfQuality = DEFAULT_QUALITY;
2230 m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;
2232 return 0;
2235 void CBaseView::OnDestroy()
2237 if ((m_pFindDialog)&&(!m_pFindDialog->IsTerminating()))
2239 m_pFindDialog->SendMessage(WM_CLOSE);
2240 return;
2242 CView::OnDestroy();
2243 DeleteFonts();
2244 ReleaseBitmap();
2247 void CBaseView::OnSize(UINT nType, int cx, int cy)
2249 CView::OnSize(nType, cx, cy);
2250 ReleaseBitmap();
2252 m_nScreenLines = -1;
2253 m_nScreenChars = -1;
2254 if (m_nLastScreenChars != GetScreenChars())
2256 BuildAllScreen2ViewVector();
2257 m_nLastScreenChars = m_nScreenChars;
2258 if (m_pMainFrame && m_pMainFrame->m_bWrapLines)
2260 // if we're in wrap mode, the line wrapping most likely changed
2261 // and that means we have to redraw the whole window, not just the
2262 // scrolled part.
2263 Invalidate(FALSE);
2265 else
2267 // make sure the view header is redrawn
2268 CRect rcScroll;
2269 GetClientRect(&rcScroll);
2270 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
2271 InvalidateRect(&rcScroll, FALSE);
2274 else
2276 // make sure the view header is redrawn
2277 CRect rcScroll;
2278 GetClientRect(&rcScroll);
2279 rcScroll.bottom = GetLineHeight()+HEADERHEIGHT;
2280 InvalidateRect(&rcScroll, FALSE);
2282 UpdateLocator();
2283 RecalcVertScrollBar();
2284 RecalcHorzScrollBar();
2286 UpdateCaret();
2289 BOOL CBaseView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
2291 if (m_pwndLeft)
2292 m_pwndLeft->OnDoMouseWheel(nFlags, zDelta, pt);
2293 if (m_pwndRight)
2294 m_pwndRight->OnDoMouseWheel(nFlags, zDelta, pt);
2295 if (m_pwndBottom)
2296 m_pwndBottom->OnDoMouseWheel(nFlags, zDelta, pt);
2297 if (m_pwndLocator)
2298 m_pwndLocator->Invalidate();
2299 return CView::OnMouseWheel(nFlags, zDelta, pt);
2302 void CBaseView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
2304 if (m_pwndLeft)
2305 m_pwndLeft->OnDoMouseHWheel(nFlags, zDelta, pt);
2306 if (m_pwndRight)
2307 m_pwndRight->OnDoMouseHWheel(nFlags, zDelta, pt);
2308 if (m_pwndBottom)
2309 m_pwndBottom->OnDoMouseHWheel(nFlags, zDelta, pt);
2310 if (m_pwndLocator)
2311 m_pwndLocator->Invalidate();
2314 void CBaseView::OnDoMouseWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/)
2316 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
2317 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
2319 if (bControl || bShift)
2321 if (m_pMainFrame->m_bWrapLines)
2322 return;
2323 // Ctrl-Wheel scrolls sideways
2324 ScrollSide(-zDelta/30);
2326 else
2328 ScrollVertical(zDelta);
2332 void CBaseView::OnDoMouseHWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/)
2334 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
2335 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
2337 if (bControl || bShift)
2339 // Ctrl-H-Wheel scrolls vertical
2340 ScrollVertical(zDelta);
2342 else
2344 if (m_pMainFrame->m_bWrapLines)
2345 return;
2346 // Ctrl-Wheel scrolls sideways
2347 ScrollSide(zDelta/30);
2351 BOOL CBaseView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
2353 if (nHitTest == HTCLIENT)
2355 if ((m_pViewData)&&(m_pMainFrame->m_bCollapsed))
2357 if (m_nMouseLine < (int)m_Screen2View.size())
2359 if (m_nMouseLine >= 0)
2361 int viewLine = GetViewLineForScreen(m_nMouseLine);
2362 if (viewLine < m_pViewData->GetCount())
2364 if (m_pViewData->GetHideState(viewLine) == HIDESTATE_MARKER)
2366 ::SetCursor(::LoadCursor(nullptr, IDC_HAND));
2367 return TRUE;
2373 if (m_mouseInMargin)
2375 ::SetCursor(m_margincursor);
2376 return TRUE;
2378 if (m_nMouseLine >= 0)
2380 ::SetCursor(::LoadCursor(nullptr, IDC_IBEAM)); // Set To Edit Cursor
2381 return TRUE;
2384 ::SetCursor(::LoadCursor(nullptr, IDC_ARROW)); // Set To Arrow Cursor
2385 return TRUE;
2387 return CView::OnSetCursor(pWnd, nHitTest, message);
2390 void CBaseView::OnKillFocus(CWnd* pNewWnd)
2392 CView::OnKillFocus(pNewWnd);
2393 m_bFocused = FALSE;
2394 UpdateCaret();
2395 Invalidate();
2398 void CBaseView::OnSetFocus(CWnd* pOldWnd)
2400 CView::OnSetFocus(pOldWnd);
2401 m_bFocused = TRUE;
2402 UpdateCaret();
2403 Invalidate();
2406 int CBaseView::GetLineFromPoint(CPoint point)
2408 ScreenToClient(&point);
2409 return (((point.y - HEADERHEIGHT) / GetLineHeight()) + m_nTopLine);
2412 void CBaseView::OnContextMenu(CPoint point, DiffStates state)
2414 CRect rcClient;
2415 GetClientRect(rcClient);
2416 CRect textrect(rcClient.left, rcClient.top, rcClient.Width(), m_nLineHeight + HEADERHEIGHT);
2418 CRect borderrect(rcClient.left, rcClient.top + m_nLineHeight + HEADERHEIGHT, 0, rcClient.bottom);
2420 CPoint ptLocal = point;
2421 ScreenToClient(&ptLocal);
2423 if (textrect.PtInRect(ptLocal) || borderrect.PtInRect(ptLocal))
2425 // inside the header part of the view (showing the filename)
2426 if (IsViewGood(m_pwndBottom))
2428 CString temp;
2429 if (this == m_pwndLeft)
2431 CIconMenu popup;
2432 if (!popup.CreatePopupMenu())
2433 return;
2435 temp.LoadString(IDS_HEADER_DIFFLEFTTOBASE);
2436 popup.AppendMenu(MF_STRING | MF_ENABLED, 10, temp);
2437 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2438 if (cmd == 10)
2439 m_pMainFrame->DiffLeftToBase();
2441 if (this == m_pwndRight)
2443 CIconMenu popup;
2444 if (!popup.CreatePopupMenu())
2445 return;
2447 temp.LoadString(IDS_HEADER_DIFFRIGHTTOBASE);
2448 popup.AppendMenu(MF_STRING | MF_ENABLED, 10, temp);
2449 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2450 if (cmd == 10)
2451 m_pMainFrame->DiffRightToBase();
2454 return;
2457 if (!this->IsWindowVisible())
2458 return;
2460 CIconMenu popup;
2461 if (!popup.CreatePopupMenu())
2462 return;
2464 AddContextItems(popup, state);
2466 CMenu popupEols;
2467 CMenu popupUnicode;
2468 int nEncodingCommandBase = POPUPCOMMAND__LAST;
2469 int nEolCommandBase = nEncodingCommandBase+_countof(uctArray);
2470 if (IsWritable())
2472 CString temp;
2473 TWhitecharsProperties oWhites = GetWhitecharsProperties();
2474 temp.LoadString(IDS_EDIT_TAB2SPACE);
2475 popup.AppendMenu(MF_STRING | (oWhites.HasTabsToConvert ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_TABTOSPACES, temp);
2476 temp.LoadString(IDS_EDIT_SPACE2TAB);
2477 popup.AppendMenu(MF_STRING | (oWhites.HasSpacesToConvert ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_SPACESTOTABS, temp);
2478 temp.LoadString(IDS_EDIT_TRIM);
2479 popup.AppendMenu(MF_STRING | (oWhites.HasTrailWhiteChars ? MF_ENABLED : (MF_DISABLED|MF_GRAYED)), POPUPCOMMAND_REMOVETRAILWHITES, temp);
2481 // add eol submenu
2482 if (!popupEols.CreatePopupMenu())
2483 return;
2485 EOL eEolType = GetLineEndings(oWhites.HasMixedEols);
2486 for (int i = 1; i < _countof(eolArray); i++)
2488 temp = GetEolName(eolArray[i]);
2489 bool bChecked = (eEolType == eolArray[i]);
2490 popupEols.AppendMenu(MF_STRING | MF_ENABLED | (bChecked ? MF_CHECKED : 0), nEolCommandBase+i, temp);
2493 temp.LoadString(IDS_VIEWCONTEXTMENU_EOL);
2494 popup.AppendMenuW(MF_POPUP | MF_ENABLED, (UINT_PTR)popupEols.GetSafeHmenu(), temp);
2496 // add encoding submenu
2497 if (!popupUnicode.CreatePopupMenu())
2498 return;
2499 for (int i = 0; i < _countof(uctArray); i++)
2501 temp = CFileTextLines::GetEncodingName(uctArray[i]);
2502 bool bChecked = (m_texttype == uctArray[i]);
2503 popupUnicode.AppendMenu(MF_STRING | MF_ENABLED | (bChecked ? MF_CHECKED : 0), nEncodingCommandBase+i, temp);
2505 temp.LoadString(IDS_VIEWCONTEXTMENU_ENCODING);
2506 popup.AppendMenuW(MF_POPUP | MF_ENABLED, (UINT_PTR)popupUnicode.GetSafeHmenu(), temp);
2510 CompensateForKeyboard(point);
2512 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
2513 ResetUndoStep();
2514 if ((cmd>=nEncodingCommandBase) && (cmd<nEncodingCommandBase+(int)_countof(uctArray)))
2516 SetTextType(uctArray[cmd-nEncodingCommandBase]);
2518 if ((cmd>=nEolCommandBase) && (cmd<nEolCommandBase+(int)_countof(eolArray)))
2520 ReplaceLineEndings(eolArray[cmd-nEolCommandBase]);
2521 SaveUndoStep();
2523 switch (cmd)
2525 // 2-pane view commands; target is right view
2526 case POPUPCOMMAND_USELEFTBLOCK:
2527 m_pwndRight->UseLeftBlock();
2528 break;
2529 case POPUPCOMMAND_USELEFTFILE:
2530 m_pwndRight->UseLeftFile();
2531 break;
2532 case POPUPCOMMAND_USEBOTHLEFTFIRST:
2533 m_pwndRight->UseBothLeftFirst();
2534 break;
2535 case POPUPCOMMAND_USEBOTHRIGHTFIRST:
2536 m_pwndRight->UseBothRightFirst();
2537 break;
2538 case POPUPCOMMAND_MARKBLOCK:
2539 m_pwndRight->MarkBlock(true);
2540 break;
2541 case POPUPCOMMAND_UNMARKBLOCK:
2542 m_pwndRight->MarkBlock(false);
2543 break;
2544 case POPUPCOMMAND_LEAVEONLYMARKEDBLOCKS:
2545 m_pwndRight->LeaveOnlyMarkedBlocks();
2546 break;
2547 // 2-pane view multiedit commands; target is left view
2548 case POPUPCOMMAND_PREPENDFROMRIGHT:
2549 if (!m_pwndLeft->IsReadonly())
2550 m_pwndLeft->UseBothRightFirst();
2551 break;
2552 case POPUPCOMMAND_REPLACEBYRIGHT:
2553 if (!m_pwndLeft->IsReadonly())
2554 m_pwndLeft->UseRightBlock();
2555 break;
2556 case POPUPCOMMAND_APPENDFROMRIGHT:
2557 if (!m_pwndLeft->IsReadonly())
2558 m_pwndLeft->UseBothLeftFirst();
2559 break;
2560 case POPUPCOMMAND_USERIGHTFILE:
2561 m_pwndLeft->UseRightFile();
2562 break;
2563 // 3-pane view commands; target is bottom view
2564 case POPUPCOMMAND_USEYOURANDTHEIRBLOCK:
2565 m_pwndBottom->UseBothRightFirst();
2566 break;
2567 case POPUPCOMMAND_USETHEIRANDYOURBLOCK:
2568 m_pwndBottom->UseBothLeftFirst();
2569 break;
2570 case POPUPCOMMAND_USEYOURBLOCK:
2571 m_pwndBottom->UseRightBlock();
2572 break;
2573 case POPUPCOMMAND_USEYOURFILE:
2574 m_pwndBottom->UseRightFile();
2575 break;
2576 case POPUPCOMMAND_USETHEIRBLOCK:
2577 m_pwndBottom->UseLeftBlock();
2578 break;
2579 case POPUPCOMMAND_USETHEIRFILE:
2580 m_pwndBottom->UseLeftFile();
2581 break;
2582 // copy, cut and paste commands
2583 case ID_EDIT_COPY:
2584 OnEditCopy();
2585 break;
2586 case ID_EDIT_CUT:
2587 OnEditCut();
2588 break;
2589 case ID_EDIT_PASTE:
2590 OnEditPaste();
2591 break;
2592 // white chars manipulations
2593 case POPUPCOMMAND_TABTOSPACES:
2594 ConvertTabToSpaces();
2595 break;
2596 case POPUPCOMMAND_SPACESTOTABS:
2597 Tabularize();
2598 break;
2599 case POPUPCOMMAND_REMOVETRAILWHITES:
2600 RemoveTrailWhiteChars();
2601 break;
2602 default:
2603 return;
2604 } // switch (cmd)
2605 SaveUndoStep(); // all except copy, cut paste save undo step, but this should not be harmful as step is empty already and thus not saved
2606 return;
2609 void CBaseView::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
2611 if (!m_pViewData)
2612 return;
2614 int nViewBlockStart = -1;
2615 int nViewBlockEnd = -1;
2616 GetViewSelection(nViewBlockStart, nViewBlockEnd);
2617 if ((point.x >= 0) && (point.y >= 0))
2619 int nLine = GetLineFromPoint(point)-1;
2620 if ((nLine >= 0) && (nLine < m_Screen2View.size()))
2622 int nViewLine = GetViewLineForScreen(nLine);
2623 if (((nViewLine < nViewBlockStart) || (nViewBlockEnd < nViewLine)))
2625 ClearSelection(); // Clear text-copy selection
2627 nViewBlockStart = nViewLine;
2628 nViewBlockEnd = nViewLine;
2629 DiffStates state = m_pViewData->GetState(nViewLine);
2630 while (nViewBlockStart > 0)
2632 const DiffStates lineState = m_pViewData->GetState(nViewBlockStart-1);
2633 if (!LinesInOneChange(-1, state, lineState))
2634 break;
2635 nViewBlockStart--;
2638 while (nViewBlockEnd < (m_pViewData->GetCount()-1))
2640 const DiffStates lineState = m_pViewData->GetState(nViewBlockEnd+1);
2641 if (!LinesInOneChange(1, state, lineState))
2642 break;
2643 nViewBlockEnd++;
2646 SetupAllViewSelection(nViewBlockStart, nViewBlockEnd);
2647 UpdateCaretPosition(SetupPoint(0, nViewLine));
2652 // FixSelection(); fix selection range
2653 /*if (m_nSelBlockEnd >= m_pViewData->GetCount())
2654 m_nSelBlockEnd = m_pViewData->GetCount()-1;//*/
2656 DiffStates state = DIFFSTATE_UNKNOWN;
2657 if (GetViewSelection(nViewBlockStart, nViewBlockEnd))
2659 // find a more 'relevant' state in the selection
2660 for (int i=nViewBlockStart; i<=nViewBlockEnd; ++i)
2662 state = m_pViewData->GetState(i);
2663 if ((state != DIFFSTATE_NORMAL) && (state != DIFFSTATE_UNKNOWN))
2664 break;
2667 OnContextMenu(point, state);
2670 void CBaseView::RefreshViews()
2672 if (m_pwndLeft)
2674 m_pwndLeft->UpdateStatusBar();
2675 m_pwndLeft->Invalidate();
2677 if (m_pwndRight)
2679 m_pwndRight->UpdateStatusBar();
2680 m_pwndRight->Invalidate();
2682 if (m_pwndBottom)
2684 m_pwndBottom->UpdateStatusBar();
2685 m_pwndBottom->Invalidate();
2687 if (m_pwndLocator)
2688 m_pwndLocator->Invalidate();
2691 void CBaseView::GoToFirstDifference()
2693 SetCaretToFirstViewLine();
2694 SelectNextBlock(1, false, false);
2697 void CBaseView::GoToFirstConflict()
2699 SetCaretToFirstViewLine();
2700 SelectNextBlock(1, true, false);
2703 void CBaseView::HighlightLines(int nStart, int nEnd /* = -1 */)
2705 ClearSelection();
2706 SetupAllSelection(nStart, max(nStart, nEnd));
2708 UpdateCaretPosition(SetupPoint(0, nStart));
2709 Invalidate();
2712 void CBaseView::HighlightViewLines(int nStart, int nEnd /* = -1 */)
2714 ClearSelection();
2715 SetupAllViewSelection(nStart, max(nStart, nEnd));
2717 UpdateCaretViewPosition(SetupPoint(0, nStart));
2718 Invalidate();
2721 void CBaseView::SetupAllViewSelection(int start, int end)
2723 SetupViewSelection(m_pwndBottom, start, end);
2724 SetupViewSelection(m_pwndLeft, start, end);
2725 SetupViewSelection(m_pwndRight, start, end);
2728 void CBaseView::SetupAllSelection(int start, int end)
2730 SetupAllViewSelection(GetViewLineForScreen(start), GetViewLineForScreen(end));
2733 //void CBaseView::SetupSelection(CBaseView* view, int start, int end) { }
2735 void CBaseView::SetupSelection(int start, int end)
2737 SetupViewSelection(GetViewLineForScreen(start), GetViewLineForScreen(end));
2740 void CBaseView::SetupViewSelection(CBaseView* view, int start, int end)
2742 if (!IsViewGood(view))
2743 return;
2744 view->SetupViewSelection(start, end);
2747 void CBaseView::SetupViewSelection(int start, int end)
2749 // clear text selection before setting line selection ?
2750 m_nSelViewBlockStart = start;
2751 m_nSelViewBlockEnd = end;
2752 Invalidate();
2756 void CBaseView::OnMergePreviousconflict()
2758 SelectNextBlock(-1, true);
2761 void CBaseView::OnMergeNextconflict()
2763 SelectNextBlock(1, true);
2766 void CBaseView::OnMergeNextdifference()
2768 SelectNextBlock(1, false);
2771 void CBaseView::OnMergePreviousdifference()
2773 SelectNextBlock(-1, false);
2776 bool CBaseView::HasNextConflict()
2778 return SelectNextBlock(1, true, true, true);
2781 bool CBaseView::HasPrevConflict()
2783 return SelectNextBlock(-1, true, true, true);
2786 bool CBaseView::HasNextDiff()
2788 return SelectNextBlock(1, false, true, true);
2791 bool CBaseView::HasPrevDiff()
2793 return SelectNextBlock(-1, false, true, true);
2796 bool CBaseView::LinesInOneChange(int direction,
2797 DiffStates initialLineState, DiffStates currentLineState)
2799 // Checks whether all the adjacent lines starting from the initial line
2800 // and up to the current line form the single change
2802 // First of all, if the two lines have identical states, they surely
2803 // belong to one change.
2804 if (initialLineState == currentLineState)
2805 return true;
2807 // Either we move down and initial line state is "added" or "removed" and
2808 // current line state is "empty"...
2809 if (direction > 0)
2811 if (currentLineState == DIFFSTATE_EMPTY)
2813 if (initialLineState == DIFFSTATE_ADDED || initialLineState == DIFFSTATE_REMOVED)
2814 return true;
2816 if (initialLineState == DIFFSTATE_CONFLICTADDED && currentLineState == DIFFSTATE_CONFLICTEMPTY)
2817 return true;
2819 // ...or we move up and initial line state is "empty" and current line
2820 // state is "added" or "removed".
2821 if (direction < 0)
2823 if (initialLineState == DIFFSTATE_EMPTY)
2825 if (currentLineState == DIFFSTATE_ADDED || currentLineState == DIFFSTATE_REMOVED)
2826 return true;
2828 if (initialLineState == DIFFSTATE_CONFLICTEMPTY && currentLineState == DIFFSTATE_CONFLICTADDED)
2829 return true;
2831 return false;
2834 bool CBaseView::SelectNextBlock(int nDirection, bool bConflict, bool bSkipEndOfCurrentBlock /* = true */, bool dryrun /* = false */)
2836 if (! m_pViewData)
2837 return false;
2839 const int linesCount = (int)m_Screen2View.size();
2840 if(linesCount == 0)
2841 return false;
2843 int nCenterPos = GetCaretPosition().y;
2844 int nLimit = -1;
2845 if (nDirection > 0)
2846 nLimit = linesCount;
2848 if (nCenterPos >= linesCount)
2849 nCenterPos = linesCount-1;
2851 if (bSkipEndOfCurrentBlock)
2853 // Find end of current block
2854 const DiffStates state = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2855 while (nCenterPos != nLimit)
2857 const DiffStates lineState = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2858 if (!LinesInOneChange(nDirection, state, lineState))
2859 break;
2860 nCenterPos += nDirection;
2864 // Find next diff/conflict block
2865 while (nCenterPos != nLimit)
2867 DiffStates linestate = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2868 if (!bConflict &&
2869 (linestate != DIFFSTATE_NORMAL) &&
2870 (linestate != DIFFSTATE_UNKNOWN) &&
2871 (linestate != DIFFSTATE_FILTEREDDIFF))
2873 break;
2875 if (bConflict &&
2876 ((linestate == DIFFSTATE_CONFLICTADDED) ||
2877 (linestate == DIFFSTATE_CONFLICTED_IGNORED) ||
2878 (linestate == DIFFSTATE_CONFLICTED) ||
2879 (linestate == DIFFSTATE_CONFLICTEMPTY)))
2881 break;
2884 nCenterPos += nDirection;
2886 if (nCenterPos == nLimit)
2887 return false;
2888 if (dryrun)
2889 return (nCenterPos != nLimit);
2891 // Find end of new block
2892 DiffStates state = m_pViewData->GetState(GetViewLineForScreen(nCenterPos));
2893 int nBlockEnd = nCenterPos;
2894 const int maxAllowedLine = nLimit-nDirection;
2895 while (nBlockEnd != maxAllowedLine)
2897 const int lineIndex = nBlockEnd + nDirection;
2898 if (lineIndex >= linesCount)
2899 break;
2900 DiffStates lineState = m_pViewData->GetState(GetViewLineForScreen(lineIndex));
2901 if (!LinesInOneChange(nDirection, state, lineState))
2902 break;
2903 nBlockEnd += nDirection;
2906 int nTopPos = nCenterPos - (GetScreenLines()/2);
2907 if (nTopPos < 0)
2908 nTopPos = 0;
2910 POINT ptCaretPos = {0, nCenterPos};
2911 SetCaretPosition(ptCaretPos);
2912 ClearSelection();
2913 if (nDirection > 0)
2914 SetupAllSelection(nCenterPos, nBlockEnd);
2915 else
2916 SetupAllSelection(nBlockEnd, nCenterPos);
2918 ScrollAllToLine(nTopPos, FALSE);
2919 RecalcAllVertScrollBars(TRUE);
2920 SetCaretToLineStart();
2921 EnsureCaretVisible();
2922 OnNavigateNextinlinediff();
2924 UpdateViewsCaretPosition();
2925 UpdateCaret();
2926 ShowDiffLines(nCenterPos);
2927 return true;
2930 BOOL CBaseView::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
2932 if (pNMHDR->idFrom != (UINT_PTR)m_hWnd)
2933 return FALSE;
2935 CString strTipText;
2936 strTipText = m_sWindowName + L"\r\n" + m_sFullFilePath;
2938 DWORD pos = GetMessagePos();
2939 CPoint point(GET_X_LPARAM(pos), GET_Y_LPARAM(pos));
2940 ScreenToClient(&point);
2941 const int nLine = GetButtonEventLineIndex(point);
2943 if (nLine >= 0)
2945 int nViewLine = GetViewLineForScreen(nLine);
2946 if((m_pViewData)&&(nViewLine < m_pViewData->GetCount()))
2948 auto movedIndex = m_pViewData->GetMovedIndex(nViewLine);
2949 if (movedIndex >= 0)
2951 if (m_pViewData->IsMovedFrom(nViewLine))
2953 strTipText.Format(IDS_MOVED_TO_TT, movedIndex+1);
2955 else
2957 strTipText.Format(IDS_MOVED_FROM_TT, movedIndex+1);
2964 *pResult = 0;
2965 if (strTipText.IsEmpty())
2966 return TRUE;
2968 // need to handle both ANSI and UNICODE versions of the message
2969 if (pNMHDR->code == TTN_NEEDTEXTA)
2971 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
2972 pTTTA->lpszText = m_szTip;
2973 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
2975 else
2977 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
2978 lstrcpyn(m_wszTip, strTipText, min(strTipText.GetLength() + 1, _countof(m_wszTip) - 1));
2979 pTTTW->lpszText = m_wszTip;
2982 return TRUE; // message was handled
2985 INT_PTR CBaseView::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
2987 CRect rcClient;
2988 GetClientRect(rcClient);
2989 CRect textrect(rcClient.left, rcClient.top, rcClient.Width(), m_nLineHeight+HEADERHEIGHT);
2991 int marginwidth = GetSystemMetrics(SM_CXSMICON) + 2 + 2;
2992 if ((m_bViewLinenumbers)&&(m_pViewData)&&(m_pViewData->GetCount())&&(m_nDigits > 0))
2994 marginwidth += (m_nDigits * m_nCharWidth) + 2;
2996 CRect borderrect(rcClient.left, rcClient.top+m_nLineHeight+HEADERHEIGHT, marginwidth, rcClient.bottom);
2998 if (textrect.PtInRect(point) || borderrect.PtInRect(point))
3000 // inside the header part of the view (showing the filename)
3001 pTI->hwnd = this->m_hWnd;
3002 this->GetClientRect(&pTI->rect);
3003 pTI->uFlags |= TTF_ALWAYSTIP | TTF_IDISHWND;
3004 pTI->uId = (UINT_PTR)m_hWnd;
3005 pTI->lpszText = LPSTR_TEXTCALLBACK;
3007 // we want multi line tooltips
3008 CToolTipCtrl* pToolTip = AfxGetModuleThreadState()->m_pToolTip;
3009 if (pToolTip->GetSafeHwnd())
3010 pToolTip->SetMaxTipWidth(SHRT_MAX);
3012 return (textrect.PtInRect(point) ? 1 : 2);
3015 return -1;
3018 void CBaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
3020 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
3021 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
3023 switch (nChar)
3025 case VK_TAB:
3026 if (bControl)
3028 if (this==m_pwndLeft)
3030 if (IsViewGood(m_pwndRight))
3032 m_pwndRight->SetFocus();
3034 else if (IsViewGood(m_pwndBottom))
3036 m_pwndBottom->SetFocus();
3039 else if (this==m_pwndRight)
3041 if (IsViewGood(m_pwndBottom))
3043 m_pwndBottom->SetFocus();
3045 else if (IsViewGood(m_pwndLeft))
3047 m_pwndLeft->SetFocus();
3050 else if (this==m_pwndBottom)
3052 if (IsViewGood(m_pwndLeft))
3054 m_pwndLeft->SetFocus();
3056 else if (IsViewGood(m_pwndRight))
3058 m_pwndRight->SetFocus();
3062 break;
3063 case VK_PRIOR:
3065 POINT ptCaretPos = GetCaretPosition();
3066 ptCaretPos.y -= GetScreenLines();
3067 ptCaretPos.y = max(ptCaretPos.y, 0);
3068 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
3069 SetCaretPosition(ptCaretPos);
3070 OnCaretMove(MOVELEFT, bShift);
3071 ShowDiffLines(ptCaretPos.y);
3073 break;
3074 case VK_NEXT:
3076 POINT ptCaretPos = GetCaretPosition();
3077 ptCaretPos.y += GetScreenLines();
3078 if (ptCaretPos.y >= GetLineCount())
3079 ptCaretPos.y = GetLineCount()-1;
3080 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
3081 SetCaretPosition(ptCaretPos);
3082 OnCaretMove(MOVERIGHT, bShift);
3083 ShowDiffLines(ptCaretPos.y);
3085 break;
3086 case VK_HOME:
3088 if (bControl)
3090 ScrollAllToLine(0);
3091 ScrollAllToChar(0);
3092 SetCaretToViewStart();
3093 m_nCaretGoalPos = 0;
3094 if (bShift)
3095 AdjustSelection(MOVELEFT);
3096 else
3097 ClearSelection();
3098 UpdateCaret();
3100 else
3102 POINT ptCaretPos = GetCaretPosition();
3103 CString sLine = GetLineChars(ptCaretPos.y);
3104 int pos = 0;
3105 while (pos < sLine.GetLength())
3107 if (sLine[pos] != ' ' && sLine[pos] != '\t')
3108 break;
3109 ++pos;
3111 if (ptCaretPos.x == pos)
3113 SetCaretToLineStart();
3114 m_nCaretGoalPos = 0;
3115 OnCaretMove(MOVERIGHT, bShift);
3116 ScrollAllToChar(0);
3118 else
3120 ptCaretPos.x = pos;
3121 SetCaretAndGoalPosition(ptCaretPos);
3122 OnCaretMove(MOVELEFT, bShift);
3126 break;
3127 case VK_END:
3129 if (bControl)
3131 ScrollAllToLine(GetLineCount()-GetAllMinScreenLines());
3132 POINT ptCaretPos;
3133 ptCaretPos.y = GetLineCount()-1;
3134 ptCaretPos.x = GetLineLength(ptCaretPos.y);
3135 SetCaretAndGoalPosition(ptCaretPos);
3136 if (bShift)
3137 AdjustSelection(MOVERIGHT);
3138 else
3139 ClearSelection();
3141 else
3143 POINT ptCaretPos = GetCaretPosition();
3144 ptCaretPos.x = GetLineLength(ptCaretPos.y);
3145 if ((GetSubLineOffset(ptCaretPos.y) != -1) && (GetSubLineOffset(ptCaretPos.y) != CountMultiLines(GetViewLineForScreen(ptCaretPos.y))-1)) // not last screen line of view line
3147 ptCaretPos.x--;
3149 SetCaretAndGoalPosition(ptCaretPos);
3150 OnCaretMove(MOVERIGHT, bShift);
3153 break;
3154 case VK_BACK:
3155 if (IsWritable())
3157 if (! HasTextSelection())
3159 POINT ptCaretPos = GetCaretPosition();
3160 if (ptCaretPos.y == 0 && ptCaretPos.x == 0)
3161 break;
3162 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3163 if (bControl)
3164 MoveCaretWordLeft();
3165 else
3167 while (MoveCaretLeft() && IsViewLineEmpty(GetCaretViewPosition().y))
3171 m_ptSelectionViewPosStart = GetCaretViewPosition();
3173 RemoveSelectedText();
3175 break;
3176 case VK_DELETE:
3177 if (IsWritable())
3179 if (! HasTextSelection())
3181 if (bControl)
3183 m_ptSelectionViewPosStart = GetCaretViewPosition();
3184 MoveCaretWordRight();
3185 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3187 else
3189 if (! MoveCaretRight())
3190 break;
3191 m_ptSelectionViewPosEnd = GetCaretViewPosition();
3192 MoveCaretLeft();
3193 m_ptSelectionViewPosStart = GetCaretViewPosition();
3196 RemoveSelectedText();
3198 break;
3199 case VK_INSERT:
3200 m_bInsertMode = !m_bInsertMode;
3201 UpdateCaret();
3202 break;
3204 CView::OnKeyDown(nChar, nRepCnt, nFlags);
3207 void CBaseView::OnLButtonDown(UINT nFlags, CPoint point)
3209 const int nClickedLine = GetButtonEventLineIndex(point);
3210 if ((nClickedLine >= m_nTopLine)&&(nClickedLine < GetLineCount()))
3212 POINT ptCaretPos;
3213 ptCaretPos.y = nClickedLine;
3214 int xpos2 = CalcColFromPoint(point.x, nClickedLine);
3215 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, xpos2);
3216 SetCaretAndGoalPosition(ptCaretPos);
3218 if (nFlags & MK_SHIFT)
3219 AdjustSelection(MOVERIGHT);
3220 else
3222 ClearSelection();
3223 SetupAllSelection(ptCaretPos.y, ptCaretPos.y);
3224 if (point.x < GetMarginWidth())
3226 // select the whole line
3227 m_ptSelectionViewPosStart = m_ptSelectionViewPosEnd = GetCaretViewPosition();
3228 m_ptSelectionViewPosStart.x = 0;
3229 m_ptSelectionViewPosEnd.x = GetViewLineLength(m_ptSelectionViewPosEnd.y);
3233 UpdateViewsCaretPosition();
3234 Invalidate();
3237 CView::OnLButtonDown(nFlags, point);
3240 CBaseView::ECharGroup CBaseView::GetCharGroup(wchar_t zChar) const
3242 if (zChar == ' ' || zChar == '\t' )
3244 return CHG_WHITESPACE;
3246 if (zChar < 0x20)
3248 return CHG_CONTROL;
3250 if (m_sWordSeparators.Find(zChar) >= 0)
3252 return CHG_WORDSEPARATOR;
3254 return CHG_WORDLETTER;
3257 void CBaseView::OnLButtonDblClk(UINT nFlags, CPoint point)
3259 if (m_pViewData == 0) {
3260 CView::OnLButtonDblClk(nFlags, point);
3261 return;
3264 const int nClickedLine = GetButtonEventLineIndex(point);
3265 if ( nClickedLine < 0)
3266 return;
3267 int nViewLine = GetViewLineForScreen(nClickedLine);
3268 if (point.x < GetMarginWidth()) // only if double clicked on the margin
3270 if((nViewLine < m_pViewData->GetCount())) // a double click on moved line scrolls to corresponding line
3272 if (m_pViewData->GetMovedIndex(nViewLine)>=0)
3274 int movedindex = m_pViewData->GetMovedIndex(nViewLine);
3275 int screenLine = FindViewLineNumber(movedindex);
3276 int nTop = screenLine - GetScreenLines()/2;
3277 if (nTop < 0)
3278 nTop = 0;
3279 ScrollAllToLine(nTop);
3280 // find and select the whole moved block
3281 int startSel = movedindex;
3282 int endSel = movedindex;
3283 while ((startSel > 0) && (m_pOtherViewData->GetMovedIndex(startSel) >= 0))
3284 startSel--;
3285 startSel++;
3286 while ((endSel < GetLineCount()) && (m_pOtherViewData->GetMovedIndex(endSel) >= 0))
3287 endSel++;
3288 endSel--;
3289 m_pOtherView->SetupSelection(startSel, endSel);
3290 return CView::OnLButtonDblClk(nFlags, point);
3294 if ((m_pMainFrame->m_bCollapsed)&&(m_pViewData->GetHideState(nViewLine) == HIDESTATE_MARKER))
3296 // a double click on a marker expands the hidden text
3297 int i = nViewLine;
3298 while ((i < m_pViewData->GetCount())&&(m_pViewData->GetHideState(i) != HIDESTATE_SHOWN))
3300 if ((m_pwndLeft)&&(m_pwndLeft->m_pViewData))
3301 m_pwndLeft->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN);
3302 if ((m_pwndRight)&&(m_pwndRight->m_pViewData))
3303 m_pwndRight->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN);
3304 if ((m_pwndBottom)&&(m_pwndBottom->m_pViewData))
3305 m_pwndBottom->m_pViewData->SetLineHideState(i, HIDESTATE_SHOWN);
3306 i++;
3308 BuildAllScreen2ViewVector();
3309 if (m_pwndLeft)
3310 m_pwndLeft->Invalidate();
3311 if (m_pwndRight)
3312 m_pwndRight->Invalidate();
3313 if (m_pwndBottom)
3314 m_pwndBottom->Invalidate();
3316 else
3318 POINT ptCaretPos;
3319 ptCaretPos.y = nClickedLine;
3320 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth());
3321 SetCaretPosition(ptCaretPos);
3322 ClearSelection();
3324 POINT ptViewCarret = GetCaretViewPosition();
3325 nViewLine = ptViewCarret.y;
3326 if (nViewLine >= GetViewCount())
3327 return;
3328 const CString &sLine = GetViewLine(nViewLine);
3329 int nLineLength = sLine.GetLength();
3330 int nBasePos = ptViewCarret.x;
3331 // get target char group
3332 ECharGroup eLeft = CHG_UNKNOWN;
3333 if (nBasePos > 0)
3335 eLeft = GetCharGroup(sLine[nBasePos-1]);
3337 ECharGroup eRight = CHG_UNKNOWN;
3338 if (nBasePos < nLineLength)
3340 eRight = GetCharGroup(sLine[nBasePos]);
3342 ECharGroup eTarget = max(eRight, eLeft);
3343 // find left margin
3344 int nLeft = nBasePos;
3345 while (nLeft > 0 && GetCharGroup(sLine[nLeft-1]) == eTarget)
3347 nLeft--;
3349 // get right margin
3350 int nRight = nBasePos;
3351 while (nRight < nLineLength && GetCharGroup(sLine[nRight]) == eTarget)
3353 nRight++;
3355 // set selection
3356 m_ptSelectionViewPosStart.x = nLeft;
3357 m_ptSelectionViewPosStart.y = nViewLine;
3358 m_ptSelectionViewPosEnd.x = nRight;
3359 m_ptSelectionViewPosEnd.y = nViewLine;
3360 m_ptSelectionViewPosOrigin = m_ptSelectionViewPosStart;
3361 SetupAllViewSelection(nViewLine, nViewLine);
3362 // set caret
3363 ptCaretPos = ConvertViewPosToScreen(m_ptSelectionViewPosEnd);
3364 UpdateViewsCaretPosition();
3365 UpdateGoalPos();
3367 // set mark word
3368 m_sPreviousMarkedWord = m_sMarkedWord; // store marked word to recall in case of triple click
3369 int nMarkWidth = max(nRight - nLeft, 0);
3370 m_sMarkedWord = sLine.Mid(m_ptSelectionViewPosStart.x, nMarkWidth).Trim();
3371 if (m_sMarkedWord.Compare(m_sPreviousMarkedWord) == 0)
3373 m_sMarkedWord.Empty();
3376 if (m_pwndLeft)
3377 m_pwndLeft->SetMarkedWord(m_sMarkedWord);
3378 if (m_pwndRight)
3379 m_pwndRight->SetMarkedWord(m_sMarkedWord);
3380 if (m_pwndBottom)
3381 m_pwndBottom->SetMarkedWord(m_sMarkedWord);
3383 Invalidate();
3384 if (m_pwndLocator)
3385 m_pwndLocator->Invalidate();
3388 CView::OnLButtonDblClk(nFlags, point);
3391 void CBaseView::OnLButtonTrippleClick( UINT /*nFlags*/, CPoint point )
3393 const int nClickedLine = GetButtonEventLineIndex(point);
3394 if (((point.y - HEADERHEIGHT) / GetLineHeight()) <= 0)
3396 if (!m_sConvertedFilePath.IsEmpty() && (GetKeyState(VK_CONTROL)&0x8000))
3398 PCIDLIST_ABSOLUTE __unaligned pidl = ILCreateFromPath((LPCTSTR)m_sConvertedFilePath);
3399 if (pidl)
3401 SHOpenFolderAndSelectItems(pidl,0,0,0);
3402 CoTaskMemFree((LPVOID)pidl);
3405 return;
3407 POINT ptCaretPos;
3408 ptCaretPos.y = nClickedLine;
3409 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth());
3410 SetCaretAndGoalPosition(ptCaretPos);
3411 m_sMarkedWord = m_sPreviousMarkedWord; // recall previous Marked word
3412 if (m_pwndLeft)
3413 m_pwndLeft->SetMarkedWord(m_sMarkedWord);
3414 if (m_pwndRight)
3415 m_pwndRight->SetMarkedWord(m_sMarkedWord);
3416 if (m_pwndBottom)
3417 m_pwndBottom->SetMarkedWord(m_sMarkedWord);
3418 ClearSelection();
3419 m_ptSelectionViewPosStart.x = 0;
3420 m_ptSelectionViewPosStart.y = nClickedLine;
3421 m_ptSelectionViewPosEnd.x = GetLineLength(nClickedLine);
3422 m_ptSelectionViewPosEnd.y = nClickedLine;
3423 SetupSelection(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
3424 UpdateViewsCaretPosition();
3425 Invalidate();
3426 if (m_pwndLocator)
3427 m_pwndLocator->Invalidate();
3430 void CBaseView::OnEditCopy()
3432 CString sCopyData = GetSelectedText();
3434 if (!sCopyData.IsEmpty())
3436 CStringUtils::WriteAsciiStringToClipboard(sCopyData, m_hWnd);
3440 void CBaseView::OnMouseMove(UINT nFlags, CPoint point)
3442 if (m_pMainFrame->m_nMoveMovesToIgnore > 0)
3444 --m_pMainFrame->m_nMoveMovesToIgnore;
3445 CView::OnMouseMove(nFlags, point);
3446 return;
3448 int nMouseLine = GetButtonEventLineIndex(point);
3449 if (nMouseLine < -1)
3450 nMouseLine = -1;
3451 m_mouseInMargin = point.x < GetMarginWidth();
3453 ShowDiffLines(nMouseLine);
3455 KillTimer(IDT_SCROLLTIMER);
3456 if (nFlags & MK_LBUTTON)
3458 int saveMouseLine = nMouseLine >= 0 ? nMouseLine : 0;
3459 saveMouseLine = saveMouseLine < GetLineCount() ? saveMouseLine : GetLineCount() - 1;
3460 if (saveMouseLine < 0)
3461 return;
3462 int col = CalcColFromPoint(point.x, saveMouseLine);
3463 int charIndex = CalculateCharIndex(saveMouseLine, col);
3464 if (HasSelection() &&
3465 ((nMouseLine >= m_nTopLine)&&(nMouseLine < GetLineCount())))
3467 POINT ptCaretPos = {charIndex, nMouseLine};
3468 SetCaretAndGoalPosition(ptCaretPos);
3469 AdjustSelection(MOVERIGHT);
3470 Invalidate();
3471 UpdateWindow();
3473 if (nMouseLine < m_nTopLine)
3475 ScrollAllToLine(m_nTopLine-1, TRUE);
3476 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3478 if (nMouseLine >= m_nTopLine + GetScreenLines() - 2)
3480 ScrollAllToLine(m_nTopLine+1, TRUE);
3481 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3483 if (!m_pMainFrame->m_bWrapLines && ((m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()) <= m_nOffsetChar))
3485 ScrollAllSide(-1);
3486 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3488 if (!m_pMainFrame->m_bWrapLines && (charIndex >= (GetScreenChars()+m_nOffsetChar-4)))
3490 ScrollAllSide(1);
3491 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3493 SetCapture();
3497 CView::OnMouseMove(nFlags, point);
3500 void CBaseView::OnLButtonUp(UINT nFlags, CPoint point)
3502 ShowDiffLines(-1);
3503 ReleaseCapture();
3504 KillTimer(IDT_SCROLLTIMER);
3506 __super::OnLButtonUp(nFlags, point);
3509 void CBaseView::OnTimer(UINT_PTR nIDEvent)
3511 if (nIDEvent == IDT_SCROLLTIMER)
3513 POINT point;
3514 GetCursorPos(&point);
3515 ScreenToClient(&point);
3516 int nMouseLine = GetButtonEventLineIndex(point);
3517 if (nMouseLine < -1)
3519 nMouseLine = -1;
3521 if (GetKeyState(VK_LBUTTON)&0x8000)
3523 int saveMouseLine = nMouseLine >= 0 ? nMouseLine : 0;
3524 saveMouseLine = saveMouseLine < GetLineCount() ? saveMouseLine : GetLineCount() - 1;
3525 int charIndex = CalculateCharIndex(saveMouseLine, m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth());
3526 if (nMouseLine < m_nTopLine)
3528 ScrollAllToLine(m_nTopLine-1, TRUE);
3529 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3531 if (nMouseLine >= m_nTopLine + GetScreenLines() - 2)
3533 ScrollAllToLine(m_nTopLine+1, TRUE);
3534 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3536 if (!m_pMainFrame->m_bWrapLines && ((m_nOffsetChar + (point.x - GetMarginWidth()) / GetCharWidth()) <= m_nOffsetChar))
3538 ScrollAllSide(-1);
3539 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3541 if (!m_pMainFrame->m_bWrapLines && (charIndex >= (GetScreenChars()+m_nOffsetChar-4)))
3543 ScrollAllSide(1);
3544 SetTimer(IDT_SCROLLTIMER, 20, nullptr);
3550 CView::OnTimer(nIDEvent);
3553 void CBaseView::ShowDiffLines(int nLine)
3555 if ((nLine < m_nTopLine)||(nLine >= GetLineCount()))
3557 m_pwndLineDiffBar->ShowLines(nLine);
3558 nLine = -1;
3559 m_nMouseLine = nLine;
3560 return;
3563 if ((!m_pwndRight)||(!m_pwndLeft))
3564 return;
3565 if(m_pMainFrame->m_bOneWay)
3566 return;
3568 nLine = (nLine > (int)m_pwndRight->m_Screen2View.size() ? -1 : nLine);
3569 nLine = (nLine > (int)m_pwndLeft->m_Screen2View.size() ? -1 : nLine);
3571 if (nLine < 0)
3572 return;
3574 if (nLine != m_nMouseLine)
3576 if (nLine >= GetLineCount())
3577 nLine = -1;
3578 m_nMouseLine = nLine;
3579 m_pwndLineDiffBar->ShowLines(nLine);
3581 m_pMainFrame->m_nMoveMovesToIgnore = MOVESTOIGNORE;
3584 const viewdata& CBaseView::GetEmptyLineData()
3586 static const viewdata emptyLine(L"", DIFFSTATE_EMPTY, -1, EOL_NOENDING, HIDESTATE_SHOWN);
3587 return emptyLine;
3590 void CBaseView::InsertViewEmptyLines(int nFirstView, int nCount)
3592 for (int i = 0; i < nCount; i++)
3594 InsertViewData(nFirstView, GetEmptyLineData());
3599 void CBaseView::UpdateCaret()
3601 POINT ptCaretPos = GetCaretPosition();
3602 ptCaretPos.y = std::max<int>(std::min<int>(ptCaretPos.y, GetLineCount()-1), 0);
3603 ptCaretPos.x = std::max<int>(std::min<int>(ptCaretPos.x, GetLineLength(ptCaretPos.y)), 0);
3604 SetCaretPosition(ptCaretPos);
3606 int nCaretOffset = CalculateActualOffset(ptCaretPos);
3608 if (m_bFocused &&
3609 ptCaretPos.y >= m_nTopLine &&
3610 ptCaretPos.y < (m_nTopLine+GetScreenLines()) &&
3611 nCaretOffset >= m_nOffsetChar &&
3612 nCaretOffset < (m_nOffsetChar+GetScreenChars()))
3614 POINT pt1 = TextToClient(ptCaretPos);
3615 if (m_bInsertMode)
3616 CreateSolidCaret(2, GetLineHeight());
3617 else
3619 POINT pt = { ptCaretPos.x + 1, ptCaretPos.y };
3620 POINT pt2 = TextToClient(pt);
3621 int width = max(GetCharWidth(), pt2.x - pt1.x);
3622 CreateSolidCaret(width, GetLineHeight());
3624 SetCaretPos(pt1);
3625 ShowCaret();
3627 else
3629 HideCaret();
3633 POINT CBaseView::ConvertScreenPosToView(const POINT& pt)
3635 POINT ptViewPos;
3636 ptViewPos.x = pt.x;
3638 int nSubLine = GetSubLineOffset(pt.y);
3639 if (nSubLine > 0)
3641 for (int nScreenLine = pt.y-1; nScreenLine >= pt.y-nSubLine; nScreenLine--)
3643 ptViewPos.x += GetLineChars(nScreenLine).GetLength();
3647 ptViewPos.y = GetViewLineForScreen(pt.y);
3648 return ptViewPos;
3651 POINT CBaseView::ConvertViewPosToScreen(const POINT& pt)
3653 POINT ptPos;
3654 int nViewLineLenLeft = GetViewLineLength(pt.y);
3655 ptPos.x = min(nViewLineLenLeft, pt.x);
3656 ptPos.y = FindScreenLineForViewLine(pt.y);
3657 if (GetViewLineForScreen(ptPos.y) != pt.y )
3659 ptPos.x = 0;
3661 else if (GetSubLineOffset(ptPos.y) >= 0) // sublined
3663 int nSubLineLength = GetLineChars(ptPos.y).GetLength();
3664 while (nSubLineLength < ptPos.x)
3666 ptPos.x -= nSubLineLength;
3667 nViewLineLenLeft -= nSubLineLength;
3668 ptPos.y++;
3669 nSubLineLength = GetLineChars(ptPos.y).GetLength();
3671 // last pos of non last sub-line go to start of next screen line
3672 // Note: while this works correctly, it's not what a user might expect:
3673 // cursor-right when the caret is before the last char of a wrapped line
3674 // now moves the caret to the next line. But users expect the caret to
3675 // move to the right of the last char instead, and with another cursor-right
3676 // keystroke to move the caret to the next line.
3677 // Basically, this would require to handle two caret positions for the same
3678 // logical position in the line string (one on the last position of the first line,
3679 // one on the first position of the new line. For non-wrapped lines this works
3680 // because there's an 'invisible' newline char at the end of the first line.
3681 if (nSubLineLength == ptPos.x && nViewLineLenLeft > nSubLineLength)
3683 ptPos.x = 0;
3684 ptPos.y++;
3688 return ptPos;
3692 void CBaseView::EnsureCaretVisible()
3694 POINT ptCaretPos = GetCaretPosition();
3695 int nCaretOffset = CalculateActualOffset(ptCaretPos);
3697 if (ptCaretPos.y < m_nTopLine)
3698 ScrollAllToLine(ptCaretPos.y);
3699 int screnLines = GetScreenLines();
3700 if (screnLines)
3702 if (ptCaretPos.y >= (m_nTopLine+screnLines)-1)
3703 ScrollAllToLine(ptCaretPos.y-screnLines+2);
3704 if (nCaretOffset < m_nOffsetChar)
3705 ScrollAllToChar(nCaretOffset);
3706 if (nCaretOffset > (m_nOffsetChar+GetScreenChars()-1))
3707 ScrollAllToChar(nCaretOffset-GetScreenChars()+1);
3711 int CBaseView::CalculateActualOffset(const POINT& point)
3713 int nLineIndex = point.y;
3714 int nCharIndex = point.x;
3715 ASSERT(nCharIndex >= 0);
3716 CString sLine = GetLineChars(nLineIndex);
3717 int nLineLength = sLine.GetLength();
3718 return CountExpandedChars(sLine, min(nCharIndex, nLineLength));
3721 int CBaseView::CalculateCharIndex(int nLineIndex, int nActualOffset)
3723 int nLength = GetLineLength(nLineIndex);
3724 int nSubLine = GetSubLineOffset(nLineIndex);
3725 if (nSubLine>=0)
3727 int nViewLine = GetViewLineForScreen(nLineIndex);
3728 if ((nViewLine>=0)&&(nViewLine < (int)m_ScreenedViewLine.size()))
3730 int nMultilineCount = CountMultiLines(nViewLine);
3731 if ((nMultilineCount>0) && (nSubLine<nMultilineCount-1))
3733 nLength--;
3737 CString Line = GetLineChars(nLineIndex);
3738 int nIndex = 0;
3739 int nOffset = 0;
3740 int nTabSize = GetTabSize();
3741 while (nOffset < nActualOffset && nIndex < nLength)
3743 if (Line.GetAt(nIndex) == L'\t')
3744 nOffset += (nTabSize - nOffset % nTabSize);
3745 else
3746 ++nOffset;
3747 ++nIndex;
3749 return nIndex;
3753 * @param xpos X coordinate in CBaseView
3754 * @param lineIndex logical line index (e.g. wrap/collapse)
3756 int CBaseView::CalcColFromPoint(int xpos, int lineIndex)
3758 int xpos2;
3759 CDC *pDC = GetDC();
3760 if (pDC)
3762 CString text = ExpandChars(GetLineChars(lineIndex), 0);
3763 int fit = text.GetLength();
3764 auto posBuffer = std::make_unique<int[]>(fit);
3765 pDC->SelectObject(GetFont()); // is this right font ?
3766 SIZE size;
3767 GetTextExtentExPoint(pDC->GetSafeHdc(), text, fit, INT_MAX, &fit, posBuffer.get(), &size);
3768 ReleaseDC(pDC);
3769 int lower = -1, upper = fit - 1;
3770 int xcheck = xpos - GetMarginWidth() + m_nOffsetChar * GetCharWidth();
3773 int middle = (upper + lower + 1) / 2;
3774 int width = posBuffer[middle];
3775 if (xcheck < width)
3776 upper = middle - 1;
3777 else
3778 lower = middle;
3779 } while (lower < upper);
3780 lower++;
3781 xpos2 = lower;
3782 if (lower < fit - 1)
3784 int charWidth = posBuffer[lower] - (lower > 0 ? posBuffer[lower - 1] : 0);
3785 if (posBuffer[lower] - xcheck <= charWidth / 2)
3786 xpos2++;
3789 else
3791 xpos2 = (xpos - GetMarginWidth()) / GetCharWidth() + m_nOffsetChar;
3792 if ((xpos % GetCharWidth()) >= (GetCharWidth()/2))
3793 xpos2++;
3795 return xpos2;
3798 POINT CBaseView::TextToClient(const POINT& point)
3800 POINT pt;
3801 int nOffsetScreenLine = max(0, (point.y - m_nTopLine));
3802 pt.y = nOffsetScreenLine * GetLineHeight();
3803 pt.x = CalculateActualOffset(point);
3805 int nLeft = GetMarginWidth() - m_nOffsetChar * GetCharWidth();
3806 CDC * pDC = GetDC();
3807 if (pDC)
3809 pDC->SelectObject(GetFont()); // is this right font ?
3810 int nScreenLine = nOffsetScreenLine + m_nTopLine;
3811 CString sLine = GetLineChars(nScreenLine);
3812 ExpandChars(sLine, 0, std::min<int>(pt.x, sLine.GetLength()), sLine);
3813 nLeft += pDC->GetTextExtent(sLine, pt.x).cx;
3814 ReleaseDC(pDC);
3815 } else {
3816 nLeft += pt.x * GetCharWidth();
3819 pt.x = nLeft;
3820 pt.y = (pt.y + GetLineHeight() + HEADERHEIGHT);
3821 return pt;
3824 void CBaseView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
3826 CView::OnChar(nChar, nRepCnt, nFlags);
3828 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
3829 bool bSkipSelectionClear = false;
3831 if (IsReadonly())
3832 return;
3834 if ((::GetKeyState(VK_LBUTTON) & 0x8000) != 0 ||
3835 (::GetKeyState(VK_RBUTTON) & 0x8000) != 0)
3837 return;
3840 if (!m_pViewData) // no data - nothing to do
3841 return;
3843 if (nChar == VK_F16)
3845 // generated by a ctrl+backspace - ignore.
3847 else if (nChar==VK_TAB && HasTextLineSelection())
3849 // change indentation for selected lines
3850 if (bShift)
3852 RemoveIndentationForSelectedBlock();
3854 else
3856 AddIndentationForSelectedBlock();
3858 bSkipSelectionClear = true;
3860 else if ((nChar > 31)||(nChar == VK_TAB))
3862 ResetUndoStep();
3863 RemoveSelectedText();
3864 POINT ptCaretViewPos = GetCaretViewPosition();
3865 int nViewLine = ptCaretViewPos.y;
3866 if ((nViewLine==0)&&(GetViewCount()==0))
3867 OnChar(VK_RETURN, 0, 0);
3868 int charCount = 1;
3869 viewdata lineData = GetViewData(nViewLine);
3870 if (nChar == VK_TAB)
3872 int indentChars = GetIndentCharsForLine(ptCaretViewPos.x, nViewLine);
3873 if (indentChars > 0)
3875 lineData.sLine.Insert(ptCaretViewPos.x, CString(L' ', indentChars));
3876 charCount = indentChars;
3878 else
3879 lineData.sLine.Insert(ptCaretViewPos.x, L'\t');
3881 else
3883 if (m_bInsertMode)
3884 lineData.sLine.Insert(ptCaretViewPos.x, (wchar_t)nChar);
3885 else
3887 if (lineData.sLine.GetLength() > ptCaretViewPos.x)
3888 lineData.sLine.SetAt(ptCaretViewPos.x, (wchar_t)nChar);
3889 else
3890 lineData.sLine.Insert(ptCaretViewPos.x, (wchar_t)nChar);
3893 if (IsStateEmpty(lineData.state))
3895 // if not last line set EOL
3896 for (int nCheckViewLine = nViewLine+1; nCheckViewLine < GetViewCount(); nCheckViewLine++)
3898 if (!IsViewLineEmpty(nCheckViewLine))
3900 lineData.ending = m_lineendings;
3901 break;
3904 // make sure previous (non empty) line have EOL set
3905 for (int nCheckViewLine = nViewLine-1; nCheckViewLine > 0; nCheckViewLine--)
3907 if (!IsViewLineEmpty(nCheckViewLine))
3909 if (GetViewLineEnding(nCheckViewLine) == EOL_NOENDING)
3911 SetViewLineEnding(nCheckViewLine, m_lineendings);
3913 break;
3917 lineData.state = DIFFSTATE_EDITED;
3918 bool bNeedRenumber = false;
3919 if (lineData.linenumber == -1)
3921 lineData.linenumber = 0;
3922 bNeedRenumber = true;
3924 SetViewData(nViewLine, lineData);
3925 SetModified();
3926 SaveUndoStep();
3927 BuildAllScreen2ViewVector(nViewLine);
3928 if (bNeedRenumber)
3930 UpdateViewLineNumbers();
3932 for (int i = 0; i < charCount; ++i)
3933 MoveCaretRight();
3934 UpdateGoalPos();
3936 else if (nChar == 10)
3938 int nViewLine = GetViewLineForScreen(GetCaretPosition().y);
3939 EOL eol = m_pViewData->GetLineEnding(nViewLine);
3940 EOL newEOL = EOL_CRLF;
3941 switch (eol)
3943 case EOL_CRLF:
3944 newEOL = EOL_CR;
3945 break;
3946 case EOL_CR:
3947 newEOL = EOL_LF;
3948 break;
3949 case EOL_LF:
3950 newEOL = EOL_CRLF;
3951 break;
3953 if (eol==EOL_NOENDING || eol==newEOL)
3954 // don't allow to change enter on empty line, or last text line (lines with EOL_NOENDING)
3955 // to add EOL on newly edited empty line hit enter
3956 // don't store into UNDO if no change happened
3957 // and don't mark file as modified
3958 return;
3959 AddUndoViewLine(nViewLine);
3960 m_pViewData->SetLineEnding(nViewLine, newEOL);
3961 m_pViewData->SetState(nViewLine, DIFFSTATE_EDITED);
3962 UpdateGoalPos();
3964 else if (nChar == VK_RETURN)
3966 // insert a new, fresh and empty line below the cursor
3967 RemoveSelectedText();
3969 CUndo::GetInstance().BeginGrouping();
3971 POINT ptCaretViewPos = GetCaretViewPosition();
3972 int nViewLine = ptCaretViewPos.y;
3973 int nLeft = ptCaretViewPos.x;
3974 CString sLine = GetViewLineChars(nViewLine);
3975 CString sLineLeft = sLine.Left(nLeft);
3976 CString sLineRight = sLine.Right(sLine.GetLength() - nLeft);
3977 EOL eOriginalEnding = EOL_AUTOLINE;
3978 if (m_pViewData->GetCount() > nViewLine)
3979 eOriginalEnding = GetViewLineEnding(nViewLine);
3981 if (!sLineRight.IsEmpty() || (eOriginalEnding!=m_lineendings))
3983 viewdata newFirstLine(sLineLeft, DIFFSTATE_EDITED, 1, m_lineendings, HIDESTATE_SHOWN);
3984 SetViewData(nViewLine, newFirstLine);
3987 int nInsertLine = (m_pViewData->GetCount()==0) ? 0 : nViewLine + 1;
3988 viewdata newLastLine(sLineRight, DIFFSTATE_EDITED, 1, eOriginalEnding, HIDESTATE_SHOWN);
3989 InsertViewData(nInsertLine, newLastLine);
3990 SetModified();
3991 SaveUndoStep();
3993 // adds new line everywhere except me
3994 if (IsViewGood(m_pwndLeft) && m_pwndLeft!=this)
3996 m_pwndLeft->InsertViewEmptyLines(nInsertLine, 1);
3998 if (IsViewGood(m_pwndRight) && m_pwndRight!=this)
4000 m_pwndRight->InsertViewEmptyLines(nInsertLine, 1);
4002 if (IsViewGood(m_pwndBottom) && m_pwndBottom!=this)
4004 m_pwndBottom->InsertViewEmptyLines(nInsertLine, 1);
4006 SaveUndoStep();
4008 UpdateViewLineNumbers();
4009 SaveUndoStep();
4010 CUndo::GetInstance().EndGrouping();
4012 BuildAllScreen2ViewVector();
4013 // move the cursor to the new line
4014 ptCaretViewPos = SetupPoint(0, nViewLine+1);
4015 SetCaretAndGoalViewPosition(ptCaretViewPos);
4017 else
4018 return; // Unknown control character -- ignore it.
4019 if (!bSkipSelectionClear)
4020 ClearSelection();
4021 EnsureCaretVisible();
4022 UpdateCaret();
4023 Invalidate(FALSE);
4026 void CBaseView::AddUndoViewLine(int nViewLine, bool bAddEmptyLine)
4028 ResetUndoStep();
4029 m_AllState.left.AddViewLineFromView(m_pwndLeft, nViewLine, bAddEmptyLine);
4030 m_AllState.right.AddViewLineFromView(m_pwndRight, nViewLine, bAddEmptyLine);
4031 m_AllState.bottom.AddViewLineFromView(m_pwndBottom, nViewLine, bAddEmptyLine);
4032 SetModified();
4033 SaveUndoStep();
4034 RecalcAllVertScrollBars();
4035 Invalidate(FALSE);
4038 void CBaseView::AddEmptyViewLine(int nViewLineIndex)
4040 if (!m_pViewData)
4041 return;
4042 int viewLine = nViewLineIndex;
4043 EOL ending = m_pViewData->GetLineEnding(viewLine);
4044 if (ending == EOL_NOENDING)
4046 ending = m_lineendings;
4048 viewdata newLine(L"", DIFFSTATE_EDITED, -1, ending, HIDESTATE_SHOWN);
4049 if (IsTarget()) // TODO: once more wievs will writable this is not correct anymore
4051 CString sPartLine = GetViewLineChars(nViewLineIndex);
4052 int nPosx = GetCaretPosition().x; // should be view pos ?
4053 m_pViewData->SetLine(viewLine, sPartLine.Left(nPosx));
4054 sPartLine = sPartLine.Mid(nPosx);
4055 newLine.sLine = sPartLine;
4057 m_pViewData->InsertData(viewLine+1, newLine);
4058 BuildAllScreen2ViewVector();
4061 void CBaseView::RemoveSelectedText()
4063 if (!m_pViewData)
4064 return;
4065 if (!HasTextSelection())
4066 return;
4068 // fix selection if starts or ends on empty line
4069 SetCaretViewPosition(m_ptSelectionViewPosEnd);
4070 while (IsViewLineEmpty(GetCaretViewPosition().y) && MoveCaretRight())
4073 m_ptSelectionViewPosEnd = GetCaretViewPosition();
4074 SetCaretViewPosition(m_ptSelectionViewPosStart);
4075 while (IsViewLineEmpty(GetCaretViewPosition().y) && MoveCaretRight())
4078 m_ptSelectionViewPosStart = GetCaretViewPosition();
4079 if (!HasTextSelection())
4081 ClearSelection();
4082 return;
4085 // We want to undo the insertion in a single step.
4086 ResetUndoStep();
4087 CUndo::GetInstance().BeginGrouping();
4089 // combine first and last line
4090 viewdata oFirstLine = GetViewData(m_ptSelectionViewPosStart.y);
4091 viewdata oLastLine = GetViewData(m_ptSelectionViewPosEnd.y);
4092 oFirstLine.sLine = oFirstLine.sLine.Left(m_ptSelectionViewPosStart.x) + oLastLine.sLine.Mid(m_ptSelectionViewPosEnd.x);
4093 oFirstLine.ending = oLastLine.ending;
4094 oFirstLine.state = DIFFSTATE_EDITED;
4095 SetViewData(m_ptSelectionViewPosStart.y, oFirstLine);
4097 // clean up middle lines if any
4098 if (m_ptSelectionViewPosStart.y != m_ptSelectionViewPosEnd.y)
4100 viewdata oEmptyLine = GetEmptyLineData();
4101 for (int nViewLine = m_ptSelectionViewPosStart.y+1; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
4103 SetViewData(nViewLine, oEmptyLine);
4105 SaveUndoStep();
4107 if (CleanEmptyLines())
4109 BuildAllScreen2ViewVector(); // schedule full rebuild
4111 SaveUndoStep();
4112 UpdateViewLineNumbers();
4115 SetModified(); //TODO set modified only if real data was changed
4116 SaveUndoStep();
4117 CUndo::GetInstance().EndGrouping();
4119 BuildAllScreen2ViewVector(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
4120 SetCaretViewPosition(m_ptSelectionViewPosStart);
4121 UpdateGoalPos();
4122 ClearSelection();
4123 UpdateCaret();
4124 EnsureCaretVisible();
4125 Invalidate(FALSE);
4128 void CBaseView::PasteText()
4130 if (!OpenClipboard())
4131 return;
4133 CString sClipboardText;
4134 HGLOBAL hglb = GetClipboardData(CF_TEXT);
4135 if (hglb)
4137 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
4138 sClipboardText = CString(lpstr);
4139 GlobalUnlock(hglb);
4141 hglb = GetClipboardData(CF_UNICODETEXT);
4142 if (hglb)
4144 LPCTSTR lpstr = (LPCTSTR)GlobalLock(hglb);
4145 sClipboardText = lpstr;
4146 GlobalUnlock(hglb);
4148 CloseClipboard();
4150 if (sClipboardText.IsEmpty())
4151 return;
4153 sClipboardText.Replace(L"\r\n", L"\r");
4154 sClipboardText.Replace('\n', '\r');
4156 InsertText(sClipboardText);
4159 void CBaseView::OnCaretDown()
4161 POINT ptCaretPos = GetCaretPosition();
4162 int nLine = ptCaretPos.y;
4163 int nNextLine = nLine + 1;
4164 if (nNextLine >= GetLineCount()) // already at last line
4166 return;
4169 POINT ptCaretViewPos = GetCaretViewPosition();
4170 int nViewLine = ptCaretViewPos.y;
4171 int nNextViewLine = GetViewLineForScreen(nNextLine);
4172 if (!((nNextViewLine == nViewLine) && (GetSubLineOffset(nNextLine)<CountMultiLines(nNextViewLine)))) // not on same view line
4174 // find next suitable screen line
4175 while ((nNextViewLine == nViewLine) || IsViewLineHidden(nNextViewLine))
4177 nNextLine++;
4178 if (nNextLine >= GetLineCount())
4180 return;
4182 nNextViewLine = GetViewLineForScreen(nNextLine);
4185 ptCaretPos.y = nNextLine;
4186 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
4187 SetCaretPosition(ptCaretPos);
4188 OnCaretMove(MOVELEFT);
4189 ShowDiffLines(ptCaretPos.y);
4192 bool CBaseView::MoveCaretLeft()
4194 POINT ptCaretViewPos = GetCaretViewPosition();
4196 //int nViewLine = ptCaretViewPos.y;
4197 if (ptCaretViewPos.x == 0)
4199 int nPrevLine = GetCaretPosition().y;
4200 int nPrevViewLine;
4201 do {
4202 nPrevLine--;
4203 if (nPrevLine < 0)
4205 return false;
4207 nPrevViewLine = GetViewLineForScreen(nPrevLine);
4208 } while ((GetSubLineOffset(nPrevLine) >= CountMultiLines(nPrevViewLine)) || IsViewLineHidden(nPrevViewLine));
4209 ptCaretViewPos = ConvertScreenPosToView(SetupPoint(GetLineLength(nPrevLine), nPrevLine));
4210 ShowDiffLines(nPrevLine);
4212 else
4213 --ptCaretViewPos.x;
4215 SetCaretAndGoalViewPosition(ptCaretViewPos);
4216 return true;
4219 bool CBaseView::MoveCaretRight()
4221 POINT ptCaretViewPos = GetCaretViewPosition();
4223 int nViewLine = ptCaretViewPos.y;
4224 int nViewLineLen = GetViewLineLength(nViewLine);
4225 if (ptCaretViewPos.x >= nViewLineLen)
4227 int nNextLine = GetCaretPosition().y;
4228 int nNextViewLine;
4229 do {
4230 nNextLine++;
4231 if (nNextLine >= GetLineCount())
4233 return false;
4235 nNextViewLine = GetViewLineForScreen(nNextLine);
4236 } while (nNextViewLine == nViewLine || IsViewLineHidden(nNextViewLine));
4237 ptCaretViewPos.y = nNextViewLine;
4238 ptCaretViewPos.x = 0;
4239 ShowDiffLines(nNextLine);
4241 else
4242 ++ptCaretViewPos.x;
4244 SetCaretAndGoalViewPosition(ptCaretViewPos);
4245 return true;
4248 void CBaseView::UpdateGoalPos()
4250 m_nCaretGoalPos = CalculateActualOffset(GetCaretPosition());
4253 void CBaseView::OnCaretLeft()
4255 MoveCaretLeft();
4256 OnCaretMove(MOVELEFT);
4259 void CBaseView::OnCaretRight()
4261 MoveCaretRight();
4262 OnCaretMove(MOVERIGHT);
4265 void CBaseView::OnCaretUp()
4267 POINT ptCaretPos = GetCaretPosition();
4268 int nLine = ptCaretPos.y;
4269 if (nLine <= 0) // already at first line
4271 return;
4273 int nPrevLine = nLine - 1;
4275 POINT ptCaretViewPos = GetCaretViewPosition();
4276 int nViewLine = ptCaretViewPos.y;
4277 int nPrevViewLine = GetViewLineForScreen(nPrevLine);
4278 if (nPrevViewLine != nViewLine) // not on same view line
4280 // find previous suitable screen line
4281 while ((GetSubLineOffset(nPrevLine) >= CountMultiLines(nPrevViewLine)) || IsViewLineHidden(nPrevViewLine))
4283 if (nPrevLine <= 0)
4285 return;
4287 nPrevLine--;
4288 nPrevViewLine = GetViewLineForScreen(nPrevLine);
4291 ptCaretPos.y = nPrevLine;
4292 ptCaretPos.x = CalculateCharIndex(ptCaretPos.y, m_nCaretGoalPos);
4293 SetCaretPosition(ptCaretPos);
4294 OnCaretMove(MOVELEFT);
4295 ShowDiffLines(ptCaretPos.y);
4298 bool CBaseView::IsWordSeparator(const wchar_t ch) const
4300 switch (GetCharGroup(ch))
4302 case CHG_CONTROL:
4303 case CHG_WHITESPACE:
4304 case CHG_WORDSEPARATOR:
4305 return true;
4307 return false;
4310 bool CBaseView::IsCaretAtWordBoundary()
4312 POINT ptViewCaret = GetCaretViewPosition();
4313 CString line = GetViewLineChars(ptViewCaret.y);
4314 if (line.IsEmpty())
4315 return false; // no boundary at the empty lines
4316 if (ptViewCaret.x == 0)
4317 return !IsWordSeparator(line.GetAt(ptViewCaret.x));
4318 if (ptViewCaret.x >= GetViewLineLength(ptViewCaret.y))
4319 return !IsWordSeparator(line.GetAt(ptViewCaret.x - 1));
4320 return
4321 IsWordSeparator(line.GetAt(ptViewCaret.x)) !=
4322 IsWordSeparator(line.GetAt(ptViewCaret.x - 1));
4325 void CBaseView::UpdateViewsCaretPosition()
4327 POINT ptCaretPos = GetCaretPosition();
4328 if (m_pwndBottom && m_pwndBottom!=this)
4329 m_pwndBottom->UpdateCaretPosition(ptCaretPos);
4330 if (m_pwndLeft && m_pwndLeft!=this)
4331 m_pwndLeft->UpdateCaretPosition(ptCaretPos);
4332 if (m_pwndRight && m_pwndRight!=this)
4333 m_pwndRight->UpdateCaretPosition(ptCaretPos);
4336 void CBaseView::OnCaretWordleft()
4338 MoveCaretWordLeft();
4339 OnCaretMove(MOVELEFT);
4342 void CBaseView::OnCaretWordright()
4344 MoveCaretWordRight();
4345 OnCaretMove(MOVERIGHT);
4348 void CBaseView::MoveCaretWordLeft()
4350 while (MoveCaretLeft() && !IsCaretAtWordBoundary())
4355 void CBaseView::MoveCaretWordRight()
4357 while (MoveCaretRight() && !IsCaretAtWordBoundary())
4362 void CBaseView::ClearCurrentSelection()
4364 m_ptSelectionViewPosStart = GetCaretViewPosition();
4365 m_ptSelectionViewPosEnd = m_ptSelectionViewPosStart;
4366 m_ptSelectionViewPosOrigin = m_ptSelectionViewPosStart;
4367 m_nSelViewBlockStart = -1;
4368 m_nSelViewBlockEnd = -1;
4369 Invalidate(FALSE);
4372 void CBaseView::ClearSelection()
4374 if (m_pwndLeft)
4375 m_pwndLeft->ClearCurrentSelection();
4376 if (m_pwndRight)
4377 m_pwndRight->ClearCurrentSelection();
4378 if (m_pwndBottom)
4379 m_pwndBottom->ClearCurrentSelection();
4382 void CBaseView::AdjustSelection(bool bMoveLeft)
4384 POINT ptCaretViewPos = GetCaretViewPosition();
4385 if (ArePointsSame(m_ptSelectionViewPosOrigin, SetupPoint(-1, -1)))
4387 // select all have been used recently update origin
4388 m_ptSelectionViewPosOrigin = bMoveLeft ? m_ptSelectionViewPosEnd : m_ptSelectionViewPosStart;
4390 if ((ptCaretViewPos.y < m_ptSelectionViewPosOrigin.y) ||
4391 (ptCaretViewPos.y == m_ptSelectionViewPosOrigin.y && ptCaretViewPos.x <= m_ptSelectionViewPosOrigin.x))
4393 m_ptSelectionViewPosStart = ptCaretViewPos;
4394 m_ptSelectionViewPosEnd = m_ptSelectionViewPosOrigin;
4396 else
4398 m_ptSelectionViewPosStart = m_ptSelectionViewPosOrigin;
4399 m_ptSelectionViewPosEnd = ptCaretViewPos;
4402 SetupAllViewSelection(m_ptSelectionViewPosStart.y, m_ptSelectionViewPosEnd.y);
4404 Invalidate(FALSE);
4407 void CBaseView::OnEditCut()
4409 if (IsWritable())
4411 OnEditCopy();
4412 RemoveSelectedText();
4416 void CBaseView::OnEditPaste()
4418 if (IsWritable())
4420 CUndo::GetInstance().BeginGrouping();
4421 RemoveSelectedText();
4422 PasteText();
4423 CUndo::GetInstance().EndGrouping();
4427 void CBaseView::DeleteFonts()
4429 for (int i=0; i<fontsCount; i++)
4431 if (m_apFonts[i])
4433 m_apFonts[i]->DeleteObject();
4434 delete m_apFonts[i];
4435 m_apFonts[i] = nullptr;
4440 void CBaseView::OnCaretMove(bool bMoveLeft)
4442 bool bShift = !!(GetKeyState(VK_SHIFT)&0x8000);
4443 OnCaretMove(bMoveLeft, bShift);
4446 void CBaseView::OnCaretMove(bool bMoveLeft, bool isShiftPressed)
4448 if(isShiftPressed)
4449 AdjustSelection(bMoveLeft);
4450 else
4451 ClearSelection();
4452 EnsureCaretVisible();
4453 UpdateCaret();
4456 void CBaseView::AddContextItems(CIconMenu& popup, DiffStates /*state*/)
4458 AddCutCopyAndPaste(popup);
4461 void CBaseView::AddCutCopyAndPaste(CIconMenu& popup)
4463 popup.AppendMenu(MF_SEPARATOR, NULL);
4464 CString temp;
4465 temp.LoadString(IDS_EDIT_COPY);
4466 popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_COPY, temp);
4467 if (IsWritable())
4469 temp.LoadString(IDS_EDIT_CUT);
4470 popup.AppendMenu(MF_STRING | (HasTextSelection() ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_CUT, temp);
4471 temp.LoadString(IDS_EDIT_PASTE);
4472 popup.AppendMenu(MF_STRING | (CAppUtils::HasClipboardFormat(CF_UNICODETEXT)||CAppUtils::HasClipboardFormat(CF_TEXT) ? MF_ENABLED : MF_DISABLED|MF_GRAYED), ID_EDIT_PASTE, temp);
4473 popup.AppendMenu(MF_SEPARATOR, NULL);
4477 void CBaseView::CompensateForKeyboard(CPoint& point)
4479 // if the context menu is invoked through the keyboard, we have to use
4480 // a calculated position on where to anchor the menu on
4481 if (ArePointsSame(point, SetupPoint(-1, -1)))
4483 CRect rect;
4484 GetWindowRect(&rect);
4485 point = rect.CenterPoint();
4489 HICON CBaseView::LoadIcon(WORD iconId)
4491 int iconWidth = GetSystemMetrics(SM_CXSMICON);
4492 int iconHeight = GetSystemMetrics(SM_CYSMICON);
4493 HANDLE icon = ::LoadImage( AfxGetResourceHandle(), MAKEINTRESOURCE(iconId),
4494 IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR);
4495 return (HICON)icon;
4498 void CBaseView::ReleaseBitmap()
4500 if (m_pCacheBitmap)
4502 m_pCacheBitmap->DeleteObject();
4503 delete m_pCacheBitmap;
4504 m_pCacheBitmap = nullptr;
4508 void CBaseView::BuildMarkedWordArray()
4510 int lineCount = GetLineCount();
4511 m_arMarkedWordLines.clear();
4512 m_arMarkedWordLines.reserve(lineCount);
4513 bool bDoit = !m_sMarkedWord.IsEmpty();
4514 for (int i = 0; i < lineCount; ++i)
4516 if (bDoit)
4518 CString line = GetLineChars(i);
4520 if (!line.IsEmpty())
4522 int found = 0;
4523 int nMarkStart = -1;
4524 while ((nMarkStart = line.Find(m_sMarkedWord, ++nMarkStart)) >= 0)
4526 int nMarkEnd = nMarkStart + m_sMarkedWord.GetLength();
4527 ECharGroup eLeft = GetCharGroup(line, nMarkStart - 1);
4528 ECharGroup eStart = GetCharGroup(line, nMarkStart);
4529 if (eLeft != eStart)
4531 ECharGroup eRight = GetCharGroup(line, nMarkEnd);
4532 ECharGroup eEnd = GetCharGroup(line, nMarkEnd - 1);
4533 if (eRight != eEnd)
4535 found = 1;
4536 break;
4540 m_arMarkedWordLines.push_back(found);
4542 else
4543 m_arMarkedWordLines.push_back(0);
4545 else
4546 m_arMarkedWordLines.push_back(0);
4550 void CBaseView::BuildFindStringArray()
4552 int lineCount = GetLineCount();
4553 m_arFindStringLines.clear();
4554 m_arFindStringLines.reserve(lineCount);
4555 bool bDoit = !m_sFindText.IsEmpty();
4556 int s = 0;
4557 int e = 0;
4558 for (int i = 0; i < lineCount; ++i)
4560 if (bDoit)
4562 CString line = GetLineChars(i);
4564 if (!line.IsEmpty())
4566 switch (m_pViewData->GetState(GetViewLineForScreen(i)))
4568 case DIFFSTATE_EMPTY:
4569 m_arFindStringLines.push_back(0);
4570 break;
4571 case DIFFSTATE_UNKNOWN:
4572 case DIFFSTATE_NORMAL:
4573 case DIFFSTATE_FILTEREDDIFF:
4574 if (m_bLimitToDiff)
4576 m_arFindStringLines.push_back(0);
4577 break;
4579 case DIFFSTATE_REMOVED:
4580 case DIFFSTATE_REMOVEDWHITESPACE:
4581 case DIFFSTATE_ADDED:
4582 case DIFFSTATE_ADDEDWHITESPACE:
4583 case DIFFSTATE_WHITESPACE:
4584 case DIFFSTATE_WHITESPACE_DIFF:
4585 case DIFFSTATE_CONFLICTED:
4586 case DIFFSTATE_CONFLICTED_IGNORED:
4587 case DIFFSTATE_CONFLICTADDED:
4588 case DIFFSTATE_CONFLICTEMPTY:
4589 case DIFFSTATE_CONFLICTRESOLVED:
4590 case DIFFSTATE_IDENTICALREMOVED:
4591 case DIFFSTATE_IDENTICALADDED:
4592 case DIFFSTATE_THEIRSREMOVED:
4593 case DIFFSTATE_THEIRSADDED:
4594 case DIFFSTATE_YOURSREMOVED:
4595 case DIFFSTATE_YOURSADDED:
4596 case DIFFSTATE_EDITED:
4598 if (!m_bMatchCase)
4599 line = line.MakeLower();
4600 s = 0;
4601 e = 0;
4602 int match = 0;
4603 while (StringFound(line, SearchNext, s, e))
4605 match++;
4606 s = e;
4607 e = 0;
4609 m_arFindStringLines.push_back(match);
4610 break;
4612 default:
4613 m_arFindStringLines.push_back(0);
4616 else
4617 m_arFindStringLines.push_back(0);
4619 else
4620 m_arFindStringLines.push_back(0);
4622 UpdateLocator();
4625 bool CBaseView::GetInlineDiffPositions(int nViewLine, std::vector<inlineDiffPos>& positions)
4627 if (!m_bShowInlineDiff)
4628 return false;
4629 if (m_pwndBottom && !(m_pwndBottom->IsHidden()))
4630 return false;
4632 if (!m_pViewData || m_pViewData->GetCount() <= nViewLine)
4633 return false;
4634 const CString &sLine = m_pViewData->GetLine(nViewLine);
4635 if (sLine.IsEmpty())
4636 return false;
4638 CheckOtherView();
4639 if (!m_pOtherViewData)
4640 return false;
4642 const CString &sDiffLine = m_pOtherViewData->GetLine(nViewLine);
4643 if (sDiffLine.IsEmpty())
4644 return false;
4646 svn_diff_t* diff = nullptr;
4647 auto pLine1 = (this == m_pwndLeft) ? &sLine : &sDiffLine;
4648 auto pLine2 = (this == m_pwndLeft) ? &sDiffLine : &sLine;
4649 m_svnlinediff.Diff(&diff, *pLine1, pLine1->GetLength(), *pLine2, pLine2->GetLength(), m_bInlineWordDiff);
4650 if (!diff || !SVNLineDiff::ShowInlineDiff(diff))
4651 return false;
4653 size_t lineoffset = 0;
4654 size_t position = 0;
4655 while (diff)
4657 if (this == m_pwndRight)
4659 apr_off_t nTmp = diff->modified_length;
4660 diff->modified_length = diff->original_length;
4661 diff->original_length = nTmp;
4663 nTmp = diff->modified_start;
4664 diff->modified_start = diff->original_start;
4665 diff->original_start = nTmp;
4667 apr_off_t len = diff->original_length;
4668 size_t oldpos = position;
4670 for (apr_off_t i = 0; i < len; ++i)
4672 position += (this == m_pwndRight) ? m_svnlinediff.m_line2tokens[lineoffset].size() : m_svnlinediff.m_line1tokens[lineoffset].size();
4673 lineoffset++;
4676 if (diff->type == svn_diff__type_diff_modified)
4678 inlineDiffPos p;
4679 p.start = oldpos;
4680 p.end = position;
4681 positions.push_back(p);
4684 diff = diff->next;
4687 return !positions.empty();
4690 void CBaseView::OnNavigateNextinlinediff()
4692 int nX;
4693 if (GetNextInlineDiff(nX))
4695 POINT ptCaretViewPos = GetCaretViewPosition();
4696 ptCaretViewPos.x = nX;
4697 SetCaretAndGoalViewPosition(ptCaretViewPos);
4698 m_ptSelectionViewPosOrigin = ptCaretViewPos;
4699 EnsureCaretVisible();
4703 void CBaseView::OnNavigatePrevinlinediff()
4705 int nX;
4706 if (GetPrevInlineDiff(nX))
4708 POINT ptCaretViewPos = GetCaretViewPosition();
4709 ptCaretViewPos.x = nX;
4710 SetCaretAndGoalViewPosition(ptCaretViewPos);
4711 m_ptSelectionViewPosOrigin = ptCaretViewPos;
4712 EnsureCaretVisible();
4716 bool CBaseView::HasNextInlineDiff()
4718 int nPos;
4719 return GetNextInlineDiff(nPos);
4722 bool CBaseView::GetNextInlineDiff(int & nPos)
4724 POINT ptCaretViewPos = GetCaretViewPosition();
4725 std::vector<inlineDiffPos> positions;
4726 if (GetInlineDiffPositions(ptCaretViewPos.y, positions))
4728 for (auto it = positions.cbegin(); it != positions.cend(); ++it)
4730 if (it->start > ptCaretViewPos.x)
4732 nPos = (LONG)it->start;
4733 return true;
4735 if (it->end > ptCaretViewPos.x)
4737 nPos = (LONG)it->end;
4738 return true;
4742 return false;
4745 bool CBaseView::HasPrevInlineDiff()
4747 int nPos;
4748 return GetPrevInlineDiff(nPos);
4751 bool CBaseView::GetPrevInlineDiff(int & nPos)
4753 POINT ptCaretViewPos = GetCaretViewPosition();
4754 std::vector<inlineDiffPos> positions;
4755 if (GetInlineDiffPositions(ptCaretViewPos.y, positions))
4757 for (auto it = positions.crbegin(); it != positions.crend(); ++it)
4759 if ( it->end < ptCaretViewPos.x)
4761 nPos = (LONG)it->end;
4762 return true;
4764 if ( it->start < ptCaretViewPos.x)
4766 nPos = (LONG)it->start;
4767 return true;
4771 return false;
4774 CBaseView * CBaseView::GetFirstGoodView()
4776 if (IsViewGood(m_pwndLeft))
4777 return m_pwndLeft;
4778 if (IsViewGood(m_pwndRight))
4779 return m_pwndRight;
4780 if (IsViewGood(m_pwndBottom))
4781 return m_pwndBottom;
4782 return nullptr;
4785 void CBaseView::BuildAllScreen2ViewVector()
4787 CBaseView * p_pwndView = GetFirstGoodView();
4788 if (p_pwndView)
4790 m_Screen2View.ScheduleFullRebuild(p_pwndView->m_pViewData);
4794 void CBaseView::BuildAllScreen2ViewVector(int nViewLine)
4796 BuildAllScreen2ViewVector(nViewLine, nViewLine);
4799 void CBaseView::BuildAllScreen2ViewVector(int nFirstViewLine, int nLastViewLine)
4801 CBaseView * p_pwndView = GetFirstGoodView();
4802 if (p_pwndView)
4804 m_Screen2View.ScheduleRangeRebuild(p_pwndView->m_pViewData, nFirstViewLine, nLastViewLine);
4808 void CBaseView::UpdateViewLineNumbers()
4810 int nLineNumber = 0;
4811 int nViewLineCount = GetViewCount();
4812 for (int nViewLine = 0; nViewLine < nViewLineCount; nViewLine++)
4814 int oldLine = (int)GetViewLineNumber(nViewLine);
4815 if (oldLine >= 0)
4816 SetViewLineNumber(nViewLine, nLineNumber++);
4818 m_nDigits = 0;
4821 int CBaseView::CleanEmptyLines()
4823 int nRemovedCount = 0;
4824 int nViewLineCount = GetViewCount();
4825 bool bCheckLeft = IsViewGood(m_pwndLeft);
4826 bool bCheckRight = IsViewGood(m_pwndRight);
4827 bool bCheckBottom = IsViewGood(m_pwndBottom);
4828 for (int nViewLine = 0; nViewLine < nViewLineCount; )
4830 bool bAllEmpty = true;
4831 bAllEmpty &= !bCheckLeft || IsStateEmpty(m_pwndLeft->GetViewState(nViewLine));
4832 bAllEmpty &= !bCheckRight || IsStateEmpty(m_pwndRight->GetViewState(nViewLine));
4833 bAllEmpty &= !bCheckBottom || IsStateEmpty(m_pwndBottom->GetViewState(nViewLine));
4834 if (bAllEmpty)
4836 if (bCheckLeft)
4838 m_pwndLeft->RemoveViewData(nViewLine);
4840 if (bCheckRight)
4842 m_pwndRight->RemoveViewData(nViewLine);
4844 if (bCheckBottom)
4846 m_pwndBottom->RemoveViewData(nViewLine);
4848 if (CUndo::GetInstance().IsGrouping()) // if use group undo -> ensure back adding goes in right (reversed) order
4850 SaveUndoStep();
4852 nViewLineCount--;
4853 nRemovedCount++;
4854 continue;
4856 nViewLine++;
4858 return nRemovedCount;
4861 int CBaseView::FindScreenLineForViewLine( int viewLine )
4863 return m_Screen2View.FindScreenLineForViewLine(viewLine);
4866 int CBaseView::CountMultiLines( int nViewLine )
4868 if (m_ScreenedViewLine.empty())
4869 return 0; // in case the view is completely empty
4871 ASSERT(nViewLine < (int)m_ScreenedViewLine.size());
4873 if (m_ScreenedViewLine[nViewLine].bSublinesSet)
4875 return (int)m_ScreenedViewLine[nViewLine].SubLines.size();
4878 CString multiline = CStringUtils::WordWrap(m_pViewData->GetLine(nViewLine), GetScreenChars()-1, false, true, GetTabSize()); // GetMultiLine(nLine);
4880 TScreenedViewLine oScreenedLine;
4881 // tokenize string
4882 int prevpos = 0;
4883 int pos = 0;
4884 while ((pos = multiline.Find('\n', pos)) >= 0)
4886 oScreenedLine.SubLines.push_back(multiline.Mid(prevpos, pos-prevpos)); // WordWrap could return vector/list of lines instead of string
4887 pos++;
4888 prevpos = pos;
4890 oScreenedLine.SubLines.push_back(multiline.Mid(prevpos));
4891 oScreenedLine.bSublinesSet = true;
4892 m_ScreenedViewLine[nViewLine] = oScreenedLine;
4894 return CountMultiLines(nViewLine);
4897 /// prepare inline diff cache
4898 LineColors & CBaseView::GetLineColors(int nViewLine)
4900 ASSERT(nViewLine < (int)m_ScreenedViewLine.size());
4902 if (m_bWhitespaceInlineDiffs)
4904 if (m_ScreenedViewLine[nViewLine].bLineColorsSetWhiteSpace)
4905 return m_ScreenedViewLine[nViewLine].lineColorsWhiteSpace;
4907 else
4909 if (m_ScreenedViewLine[nViewLine].bLineColorsSet)
4910 return m_ScreenedViewLine[nViewLine].lineColors;
4913 LineColors oLineColors;
4914 // set main line color
4915 COLORREF crBkgnd, crText;
4916 DiffStates diffState = m_pViewData->GetState(nViewLine);
4917 CDiffColors::GetInstance().GetColors(diffState, crBkgnd, crText);
4918 oLineColors.SetColor(0, crText, crBkgnd);
4920 do {
4921 if (!m_bShowInlineDiff)
4922 break;
4924 if (((diffState == DIFFSTATE_NORMAL) || (diffState == DIFFSTATE_FILTEREDDIFF)) && (!m_bWhitespaceInlineDiffs))
4925 break;
4927 CString sLine = GetViewLineChars(nViewLine);
4928 if (sLine.IsEmpty())
4929 break;
4930 CString sDiffLine;
4931 if (!m_pOtherView)
4933 switch (diffState)
4935 case DIFFSTATE_ADDED:
4937 if ((nViewLine > 0) && (m_pViewData->GetState(nViewLine - 1) == DIFFSTATE_REMOVED))
4938 sDiffLine = GetViewLineChars(nViewLine - 1);
4940 break;
4941 case DIFFSTATE_REMOVED:
4943 if (((nViewLine + 1) < m_pViewData->GetCount()) && (m_pViewData->GetState(nViewLine + 1) == DIFFSTATE_ADDED))
4944 sDiffLine = GetViewLineChars(nViewLine + 1);
4946 break;
4949 else
4950 sDiffLine = m_pOtherView->GetViewLineChars(nViewLine);
4951 if (sDiffLine.IsEmpty())
4952 break;
4954 svn_diff_t* diff = nullptr;
4955 if (sLine.GetLength() > (int)m_nInlineDiffMaxLineLength)
4956 break;
4957 auto pLine1 = (this == m_pwndLeft) ? &sLine : &sDiffLine;
4958 auto pLine2 = (this == m_pwndLeft) ? &sDiffLine : &sLine;
4959 m_svnlinediff.Diff(&diff, *pLine1, pLine1->GetLength(), *pLine2, pLine2->GetLength(), m_bInlineWordDiff);
4960 if (!diff || !SVNLineDiff::ShowInlineDiff(diff) || !diff->next)
4961 break;
4963 int lineoffset = 0;
4964 int nTextStartOffset = 0;
4965 std::map<int, COLORREF> removedPositions;
4966 while (diff)
4968 if (this == m_pwndRight)
4970 apr_off_t nTmp = diff->modified_length;
4971 diff->modified_length = diff->original_length;
4972 diff->original_length = nTmp;
4974 nTmp = diff->modified_start;
4975 diff->modified_start = diff->original_start;
4976 diff->original_start = nTmp;
4978 apr_off_t len = diff->original_length;
4980 size_t nTextLength = 0;
4981 for (int i = 0; i < len; ++i)
4983 nTextLength += (this == m_pwndRight) ? m_svnlinediff.m_line2tokens[lineoffset].size() : m_svnlinediff.m_line1tokens[lineoffset].size();
4984 lineoffset++;
4986 bool bInlineDiff = (diff->type == svn_diff__type_diff_modified);
4988 CDiffColors::GetInstance().GetColors(diffState, crBkgnd, crText);
4989 if ((m_bShowInlineDiff)&&(bInlineDiff))
4991 crBkgnd = InlineViewLineDiffColor(nViewLine);
4993 else if (m_pOtherView)
4995 crBkgnd = m_ModifiedBk;
4998 if (len < diff->modified_length)
5000 removedPositions[nTextStartOffset] = m_InlineRemovedBk;
5002 oLineColors.SetColor(nTextStartOffset, crText, crBkgnd);
5004 nTextStartOffset += (int)nTextLength;
5005 diff = diff->next;
5007 for (std::map<int, COLORREF>::const_iterator it = removedPositions.begin(); it != removedPositions.end(); ++it)
5009 oLineColors.AddShotColor(it->first, it->second);
5011 } while (false); // error catch
5013 if (!m_bWhitespaceInlineDiffs)
5015 m_ScreenedViewLine[nViewLine].lineColors = oLineColors;
5016 m_ScreenedViewLine[nViewLine].bLineColorsSet = true;
5018 else
5020 m_ScreenedViewLine[nViewLine].lineColorsWhiteSpace = oLineColors;
5021 m_ScreenedViewLine[nViewLine].bLineColorsSetWhiteSpace = true;
5024 return GetLineColors(nViewLine);
5027 void CBaseView::OnEditSelectall()
5029 if (!m_pViewData)
5030 return;
5031 int nLastViewLine = m_pViewData->GetCount()-1;
5032 if (nLastViewLine < 0)
5033 return;
5034 SetupAllViewSelection(0, nLastViewLine);
5036 CString sLine = GetViewLineChars(nLastViewLine);
5037 m_ptSelectionViewPosStart = SetupPoint(0, 0);
5038 m_ptSelectionViewPosEnd = SetupPoint(sLine.GetLength(), nLastViewLine);
5039 m_ptSelectionViewPosOrigin = SetupPoint(-1, -1);
5041 UpdateWindow();
5044 void CBaseView::FilterWhitespaces(CString& first, CString& second)
5046 FilterWhitespaces(first);
5047 FilterWhitespaces(second);
5050 void CBaseView::FilterWhitespaces(CString& line)
5052 line.Remove(' ');
5053 line.Remove('\t');
5054 line.Remove('\r');
5055 line.Remove('\n');
5058 int CBaseView::GetButtonEventLineIndex(const POINT& point)
5060 const int nLineFromTop = (point.y - HEADERHEIGHT) / GetLineHeight();
5061 int nEventLine = nLineFromTop + m_nTopLine;
5062 nEventLine--; //we need the index
5063 return nEventLine;
5067 BOOL CBaseView::PreTranslateMessage(MSG* pMsg)
5069 if (RelayTrippleClick(pMsg))
5070 return TRUE;
5071 return CView::PreTranslateMessage(pMsg);
5075 void CBaseView::ResetUndoStep()
5077 m_AllState.Clear();
5080 void CBaseView::SaveUndoStep()
5082 if (!m_AllState.IsEmpty())
5084 CUndo::GetInstance().AddState(m_AllState, GetCaretViewPosition());
5086 ResetUndoStep();
5089 void CBaseView::InsertViewData( int index, const CString& sLine, DiffStates state, int linenumber, EOL ending, HIDESTATE hide, int movedline )
5091 m_pState->addedlines.push_back(index);
5092 m_pViewData->InsertData(index, sLine, state, linenumber, ending, hide, movedline);
5095 void CBaseView::InsertViewData( int index, const viewdata& data )
5097 m_pState->addedlines.push_back(index);
5098 m_pViewData->InsertData(index, data);
5101 void CBaseView::RemoveViewData( int index )
5103 m_pState->removedlines[index] = m_pViewData->GetData(index);
5104 m_pViewData->RemoveData(index);
5107 void CBaseView::SetViewData( int index, const viewdata& data )
5109 m_pState->replacedlines[index] = m_pViewData->GetData(index);
5110 m_pViewData->SetData(index, data);
5113 void CBaseView::SetViewState( int index, DiffStates state )
5115 m_pState->linestates[index] = m_pViewData->GetState(index);
5116 m_pViewData->SetState(index, state);
5119 void CBaseView::SetViewLine( int index, const CString& sLine )
5121 m_pState->difflines[index] = m_pViewData->GetLine(index);
5122 m_pViewData->SetLine(index, sLine);
5125 void CBaseView::SetViewLineNumber( int index, int linenumber )
5127 int oldLineNumber = m_pViewData->GetLineNumber(index);
5128 if (oldLineNumber != linenumber) {
5129 m_pState->linelines[index] = oldLineNumber;
5130 m_pViewData->SetLineNumber(index, linenumber);
5134 void CBaseView::SetViewLineEnding( int index, EOL ending )
5136 m_pState->linesEOL[index] = m_pViewData->GetLineEnding(index);
5137 m_pViewData->SetLineEnding(index, ending);
5140 void CBaseView::SetViewMarked( int index, bool marked )
5142 m_pState->markedlines[index] = m_pViewData->GetMarked(index);
5143 m_pViewData->SetMarked(index, marked);
5147 BOOL CBaseView::GetViewSelection( int& start, int& end ) const
5149 if (HasSelection())
5151 start = m_nSelViewBlockStart;
5152 end = m_nSelViewBlockEnd;
5153 return true;
5155 return false;
5158 int CBaseView::Screen2View::GetViewLineForScreen( int screenLine )
5160 RebuildIfNecessary();
5161 if ((size() <= screenLine) || (screenLine < 0))
5162 return 0;
5163 return m_Screen2View[screenLine].nViewLine;
5166 int CBaseView::Screen2View::size()
5168 RebuildIfNecessary();
5169 return (int)m_Screen2View.size();
5172 int CBaseView::Screen2View::GetSubLineOffset( int screenLine )
5174 RebuildIfNecessary();
5175 if (size() <= screenLine)
5176 return 0;
5177 return m_Screen2View[screenLine].nViewSubLine;
5180 CBaseView::TScreenLineInfo CBaseView::Screen2View::GetScreenLineInfo( int screenLine )
5182 RebuildIfNecessary();
5183 return m_Screen2View[screenLine];
5187 doing partial rebuild, whole screen2view vector is built, but uses ScreenedViewLine cache to do it faster
5189 void CBaseView::Screen2View::RebuildIfNecessary()
5191 if (!m_pViewData)
5192 return; // rebuild not necessary
5194 FixScreenedCacheSize(m_pwndLeft);
5195 FixScreenedCacheSize(m_pwndRight);
5196 FixScreenedCacheSize(m_pwndBottom);
5197 if (!m_bFull)
5199 for (auto it = m_RebuildRanges.cbegin(); it != m_RebuildRanges.cend(); ++it)
5201 ResetScreenedViewLineCache(m_pwndLeft, *it);
5202 ResetScreenedViewLineCache(m_pwndRight, *it);
5203 ResetScreenedViewLineCache(m_pwndBottom, *it);
5206 else
5208 ResetScreenedViewLineCache(m_pwndLeft);
5209 ResetScreenedViewLineCache(m_pwndRight);
5210 ResetScreenedViewLineCache(m_pwndBottom);
5212 m_RebuildRanges.clear();
5213 m_bFull = false;
5215 size_t OldSize = m_Screen2View.size();
5216 m_Screen2View.clear();
5217 m_Screen2View.reserve(OldSize); // guess same size
5218 for (int i = 0; i < m_pViewData->GetCount(); ++i)
5220 if (m_pMainFrame->m_bCollapsed)
5222 while ((i < m_pViewData->GetCount())&&(m_pViewData->GetHideState(i) == HIDESTATE_HIDDEN))
5223 ++i;
5224 if (!(i < m_pViewData->GetCount()))
5225 break;
5227 TScreenLineInfo oLineInfo;
5228 oLineInfo.nViewLine = i;
5229 oLineInfo.nViewSubLine = -1; // no wrap
5230 if (m_pMainFrame->m_bWrapLines && !IsViewLineHidden(m_pViewData, i))
5232 int nMaxLines = 0;
5233 if (IsLeftViewGood())
5234 nMaxLines = std::max<int>(nMaxLines, m_pwndLeft->CountMultiLines(i));
5235 if (IsRightViewGood())
5236 nMaxLines = std::max<int>(nMaxLines, m_pwndRight->CountMultiLines(i));
5237 if (IsBottomViewGood())
5238 nMaxLines = std::max<int>(nMaxLines, m_pwndBottom->CountMultiLines(i));
5239 for (int l = 0; l < (nMaxLines-1); ++l)
5241 oLineInfo.nViewSubLine++;
5242 m_Screen2View.push_back(oLineInfo);
5244 oLineInfo.nViewSubLine++;
5246 m_Screen2View.push_back(oLineInfo);
5248 m_pViewData = nullptr;
5250 if (IsLeftViewGood())
5251 m_pwndLeft->BuildMarkedWordArray();
5252 if (IsRightViewGood())
5253 m_pwndRight->BuildMarkedWordArray();
5254 if (IsBottomViewGood())
5255 m_pwndBottom->BuildMarkedWordArray();
5256 UpdateLocator();
5257 RecalcAllVertScrollBars();
5258 RecalcAllHorzScrollBars();
5261 int CBaseView::Screen2View::FindScreenLineForViewLine( int viewLine )
5263 RebuildIfNecessary();
5265 int nScreenLineCount = (int)m_Screen2View.size();
5267 int nPos = 0;
5268 if (nScreenLineCount>16)
5270 // for enough long data search for last screen
5271 // with viewline less than one we are looking for
5272 // use approximate method (based on) binary search using asymmetric start point
5273 // in form 2**n (determined as MSB of length) to go around division and rounding;
5274 // this effectively looks for bit values from MSB to LSB
5276 int nTestBit;
5277 //GetMostSignificantBitValue
5278 // note _BitScanReverse(&nTestBit, nScreenLineCount); can be used instead
5279 nTestBit = nScreenLineCount;
5280 nTestBit |= nTestBit>>1;
5281 nTestBit |= nTestBit>>2;
5282 nTestBit |= nTestBit>>4;
5283 nTestBit |= nTestBit>>8;
5284 nTestBit |= nTestBit>>16;
5285 nTestBit ^= (nTestBit>>1);
5287 while (nTestBit)
5289 int nTestPos = nPos | nTestBit;
5290 if (nTestPos < nScreenLineCount && m_Screen2View[nTestPos].nViewLine < viewLine)
5292 nPos = nTestPos;
5294 nTestBit >>= 1;
5297 while (nPos < nScreenLineCount && m_Screen2View[nPos].nViewLine < viewLine)
5299 nPos++;
5302 return nPos;
5305 void CBaseView::Screen2View::ScheduleFullRebuild(CViewData * pViewData) {
5306 m_bFull = true;
5308 m_pViewData = pViewData;
5311 void CBaseView::Screen2View::ScheduleRangeRebuild(CViewData * pViewData, int nFirstViewLine, int nLastViewLine)
5313 if (m_bFull)
5314 return;
5316 m_pViewData = pViewData;
5318 TRebuildRange Range;
5319 Range.FirstViewLine=nFirstViewLine;
5320 Range.LastViewLine=nLastViewLine;
5321 m_RebuildRanges.push_back(Range);
5324 bool CBaseView::Screen2View::FixScreenedCacheSize(CBaseView* pwndView)
5326 if (!IsViewGood(pwndView))
5328 return false;
5330 const int nOldSize = (int)pwndView->m_ScreenedViewLine.size();
5331 const int nViewCount = std::max<int>(pwndView->GetViewCount(), 0);
5332 if (nOldSize == nViewCount)
5334 return false;
5336 pwndView->m_ScreenedViewLine.resize(nViewCount);
5337 return true;
5340 bool CBaseView::Screen2View::ResetScreenedViewLineCache(CBaseView* pwndView) const
5342 if (!IsViewGood(pwndView))
5344 return false;
5346 TRebuildRange Range={0, pwndView->GetViewCount()-1};
5347 ResetScreenedViewLineCache(pwndView, Range);
5348 return true;
5351 bool CBaseView::Screen2View::ResetScreenedViewLineCache(CBaseView* pwndView, const TRebuildRange& Range) const
5353 if (!IsViewGood(pwndView))
5355 return false;
5357 if (Range.LastViewLine == -1)
5359 return false;
5361 ASSERT(Range.FirstViewLine >= 0);
5362 ASSERT(Range.LastViewLine < pwndView->GetViewCount());
5363 for (int i = Range.FirstViewLine; i <= Range.LastViewLine; i++)
5365 pwndView->m_ScreenedViewLine[i].Clear();
5367 return false;
5370 void CBaseView::WrapChanged()
5372 m_nMaxLineLength = -1;
5373 m_nOffsetChar = 0;
5374 RecalcHorzScrollBar();
5377 void CBaseView::OnEditFind()
5379 if (m_pFindDialog)
5380 return;
5382 int id = 0;
5383 if (this == m_pwndLeft)
5384 id = 1;
5385 if (this == m_pwndRight)
5386 id = 2;
5387 if (this == m_pwndBottom)
5388 id = 3;
5390 m_pFindDialog = new CFindDlg(this);
5391 m_pFindDialog->Create(this, id);
5393 m_pFindDialog->SetFindString(HasTextSelection() ? GetSelectedText() : L"");
5394 m_pFindDialog->SetReadonly(m_bReadonly);
5397 LRESULT CBaseView::OnFindDialogMessage(WPARAM wParam, LPARAM /*lParam*/)
5399 ASSERT(m_pFindDialog != nullptr);
5401 if (m_pFindDialog->IsTerminating())
5403 // invalidate the handle identifying the dialog box.
5404 m_pFindDialog = nullptr;
5405 return 0;
5408 if(m_pFindDialog->FindNext())
5410 //read data from dialog
5411 m_sFindText = m_pFindDialog->GetFindString();
5412 m_bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
5413 m_bLimitToDiff = m_pFindDialog->LimitToDiffs();
5414 m_bWholeWord = m_pFindDialog->WholeWord();
5416 if (!m_bMatchCase)
5417 m_sFindText = m_sFindText.MakeLower();
5419 BuildFindStringArray();
5420 if ((CFindDlg::FindType)wParam == CFindDlg::FindType::Find)
5422 if (m_pFindDialog->SearchUp())
5423 OnEditFindprev();
5424 else
5425 OnEditFindnext();
5427 else if ((CFindDlg::FindType)wParam == CFindDlg::FindType::Count)
5429 size_t count = 0;
5430 for (size_t i = 0; i < m_arFindStringLines.size(); ++i)
5431 count += m_arFindStringLines[i];
5432 CString matches;
5433 matches.Format(IDS_FIND_COUNT, count);
5434 m_pFindDialog->SetStatusText(matches);
5436 else if ((CFindDlg::FindType)wParam == CFindDlg::FindType::Replace)
5438 if (!IsWritable())
5439 return 0;
5440 bool bFound = false;
5441 if (m_pFindDialog->SearchUp())
5442 bFound = Search(SearchPrevious, true, true, false);
5443 else
5444 bFound = Search(SearchNext, true, true, false);
5445 if (bFound)
5447 CString sReplaceText = m_pFindDialog->GetReplaceString();
5448 CUndo::GetInstance().BeginGrouping();
5449 RemoveSelectedText();
5450 InsertText(sReplaceText);
5451 CUndo::GetInstance().EndGrouping();
5455 else if ((CFindDlg::FindType)wParam == CFindDlg::FindType::ReplaceAll)
5457 if (!IsWritable())
5458 return 0;
5459 bool bFound = false;
5460 int replaceCount = 0;
5461 POINT lastPoint = m_ptSelectionViewPosStart;
5462 m_ptSelectionViewPosStart.x = m_ptSelectionViewPosStart.y = 0;
5463 CUndo::GetInstance().BeginGrouping();
5466 bFound = Search(SearchNext, true, false, true);
5467 if (bFound)
5469 CString sReplaceText = m_pFindDialog->GetReplaceString();
5470 RemoveSelectedText();
5471 InsertText(sReplaceText);
5472 ++replaceCount;
5474 } while (bFound);
5475 CUndo::GetInstance().EndGrouping();
5476 if (replaceCount == 0)
5477 m_ptSelectionViewPosStart = lastPoint;
5478 CString message;
5479 message.Format(IDS_FIND_REPLACED, replaceCount);
5480 m_pFindDialog->SetStatusText(message, RGB(0, 0, 0));
5484 return 0;
5487 void CBaseView::OnEditFindnextStart()
5489 if (!m_pViewData)
5490 return;
5491 if (HasTextSelection())
5493 m_sFindText = GetSelectedText();
5494 m_bMatchCase = false;
5495 m_bLimitToDiff = false;
5496 m_bWholeWord = false;
5497 m_sFindText = m_sFindText.MakeLower();
5499 BuildFindStringArray();
5500 OnEditFindnext();
5502 else
5504 m_sFindText.Empty();
5505 BuildFindStringArray();
5509 void CBaseView::OnEditFindprevStart()
5511 if (!m_pViewData)
5512 return;
5513 if (HasTextSelection())
5515 m_sFindText = GetSelectedText();
5516 m_bMatchCase = false;
5517 m_bLimitToDiff = false;
5518 m_bWholeWord = false;
5519 m_sFindText = m_sFindText.MakeLower();
5521 BuildFindStringArray();
5522 OnEditFindprev();
5524 else
5526 m_sFindText.Empty();
5527 BuildFindStringArray();
5531 bool CBaseView::StringFound(const CString& str, SearchDirection srchDir, int& start, int& end) const
5533 if (srchDir == SearchPrevious)
5535 int laststart = -1;
5536 int laststart2 = -1;
5539 laststart2 = laststart;
5540 laststart = str.Find(m_sFindText, laststart + 1);
5541 } while (laststart >= 0 && laststart < start);
5542 start = laststart2;
5544 else
5545 start = str.Find(m_sFindText, start);
5546 end = start + m_sFindText.GetLength();
5547 bool bStringFound = (start >= 0);
5548 if (bStringFound && m_bWholeWord)
5550 if (start)
5551 bStringFound = IsWordSeparator(str.Mid(start-1,1).GetAt(0));
5553 if (bStringFound)
5555 if (str.GetLength() > end)
5556 bStringFound = IsWordSeparator(str.Mid(end, 1).GetAt(0));
5559 return bStringFound;
5562 void CBaseView::OnEditFindprev()
5564 Search(SearchPrevious, false, true, false);
5567 void CBaseView::OnEditFindnext()
5569 Search(SearchNext, false, true, false);
5572 bool CBaseView::Search(SearchDirection srchDir, bool useStart, bool flashIfNotFound, bool stopEof)
5574 if (m_sFindText.IsEmpty())
5575 return false;
5576 if(!m_pViewData)
5577 return false;
5579 POINT start = useStart ? m_ptSelectionViewPosStart : m_ptSelectionViewPosEnd;
5580 POINT end;
5581 end.y = m_pViewData->GetCount()-1;
5582 if (end.y < 0)
5583 return false;
5585 if (srchDir==SearchNext)
5586 end.x = GetViewLineLength(end.y);
5587 else
5589 end.x = m_ptSelectionViewPosStart.x;
5590 start.x = 0;
5593 if (!HasTextSelection())
5595 start.y = m_ptCaretViewPos.y;
5596 if (srchDir==SearchNext)
5597 start.x = m_ptCaretViewPos.x;
5598 else
5600 start.x = 0;
5601 end.x = m_ptCaretViewPos.x;
5604 CString sSelectedText;
5605 int startline = -1;
5606 for (int nViewLine=start.y; ;srchDir==SearchNext ? nViewLine++ : nViewLine--)
5608 if (nViewLine < 0)
5610 if (stopEof)
5611 return false;
5612 nViewLine = m_pViewData->GetCount()-1;
5613 startline = start.y;
5614 if (flashIfNotFound)
5616 if (m_pFindDialog)
5617 m_pFindDialog->SetStatusText(CString(MAKEINTRESOURCE(IDS_FIND_TOPREACHED)), RGB(63, 127, 47));
5618 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 2, 100);
5621 if (nViewLine > end.y)
5623 if (stopEof)
5624 return false;
5625 nViewLine = 0;
5626 startline = start.y;
5627 if (flashIfNotFound)
5629 if (m_pFindDialog)
5630 m_pFindDialog->SetStatusText(CString(MAKEINTRESOURCE(IDS_FIND_BOTTOMREACHED)), RGB(63, 127, 47));
5631 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 2, 100);
5634 switch (m_pViewData->GetState(nViewLine))
5636 case DIFFSTATE_EMPTY:
5637 break;
5638 case DIFFSTATE_UNKNOWN:
5639 case DIFFSTATE_NORMAL:
5640 case DIFFSTATE_FILTEREDDIFF:
5641 if (m_bLimitToDiff)
5642 break;
5643 case DIFFSTATE_REMOVED:
5644 case DIFFSTATE_REMOVEDWHITESPACE:
5645 case DIFFSTATE_ADDED:
5646 case DIFFSTATE_ADDEDWHITESPACE:
5647 case DIFFSTATE_WHITESPACE:
5648 case DIFFSTATE_WHITESPACE_DIFF:
5649 case DIFFSTATE_CONFLICTED:
5650 case DIFFSTATE_CONFLICTED_IGNORED:
5651 case DIFFSTATE_CONFLICTADDED:
5652 case DIFFSTATE_CONFLICTEMPTY:
5653 case DIFFSTATE_CONFLICTRESOLVED:
5654 case DIFFSTATE_IDENTICALREMOVED:
5655 case DIFFSTATE_IDENTICALADDED:
5656 case DIFFSTATE_THEIRSREMOVED:
5657 case DIFFSTATE_THEIRSADDED:
5658 case DIFFSTATE_YOURSREMOVED:
5659 case DIFFSTATE_YOURSADDED:
5660 case DIFFSTATE_EDITED:
5662 sSelectedText = GetViewLineChars(nViewLine);
5663 if (nViewLine == start.y && startline < 0)
5664 sSelectedText = srchDir == SearchNext ? sSelectedText.Mid(start.x) : sSelectedText.Left(end.x);
5665 if (!m_bMatchCase)
5666 sSelectedText = sSelectedText.MakeLower();
5667 int startfound = srchDir == SearchNext ? 0 : sSelectedText.GetLength();
5668 int endfound = 0;
5669 if (StringFound(sSelectedText, srchDir, startfound, endfound))
5671 HighlightViewLines(nViewLine, nViewLine);
5672 m_ptSelectionViewPosStart.x = startfound;
5673 m_ptSelectionViewPosEnd.x = endfound;
5674 if (nViewLine == start.y && startline < 0)
5676 m_ptSelectionViewPosStart.x += start.x;
5677 m_ptSelectionViewPosEnd.x += start.x;
5679 m_ptSelectionViewPosEnd.x = m_ptSelectionViewPosStart.x + m_sFindText.GetLength();
5680 m_ptSelectionViewPosStart.y = nViewLine;
5681 m_ptSelectionViewPosEnd.y = nViewLine;
5682 m_ptCaretViewPos = m_ptSelectionViewPosStart;
5683 UpdateViewsCaretPosition();
5684 EnsureCaretVisible();
5685 Invalidate();
5686 return true;
5689 break;
5692 if (startline >= 0)
5694 if (nViewLine == startline)
5696 if (flashIfNotFound)
5698 CString message;
5699 message.Format(IDS_FIND_NOTFOUND, (LPCTSTR)m_sFindText);
5700 if (m_pFindDialog)
5701 m_pFindDialog->SetStatusText(message, RGB(255, 0, 0));
5702 ::MessageBeep(0xFFFFFFFF);
5703 m_pMainFrame->FlashWindowEx(FLASHW_ALL, 3, 100);
5705 break;
5709 m_pMainFrame->m_nMoveMovesToIgnore = MOVESTOIGNORE;
5710 return false;
5713 CString CBaseView::GetSelectedText() const
5715 CString sSelectedText;
5716 POINT start = m_ptSelectionViewPosStart;
5717 POINT end = m_ptSelectionViewPosEnd;
5718 if (!HasTextSelection())
5720 if (!HasSelection())
5721 return sSelectedText;
5722 start.y = m_nSelViewBlockStart;
5723 start.x = 0;
5724 end.y = m_nSelViewBlockEnd;
5725 end.x = GetViewLineLength(m_nSelViewBlockEnd);
5727 if (!m_pViewData)
5728 return sSelectedText;
5729 // first store the selected lines in one CString
5730 for (int nViewLine=start.y; nViewLine<=end.y; nViewLine++)
5732 switch (m_pViewData->GetState(nViewLine))
5734 case DIFFSTATE_EMPTY:
5735 break;
5736 case DIFFSTATE_UNKNOWN:
5737 case DIFFSTATE_NORMAL:
5738 case DIFFSTATE_REMOVED:
5739 case DIFFSTATE_REMOVEDWHITESPACE:
5740 case DIFFSTATE_ADDED:
5741 case DIFFSTATE_ADDEDWHITESPACE:
5742 case DIFFSTATE_WHITESPACE:
5743 case DIFFSTATE_WHITESPACE_DIFF:
5744 case DIFFSTATE_CONFLICTED:
5745 case DIFFSTATE_CONFLICTED_IGNORED:
5746 case DIFFSTATE_CONFLICTADDED:
5747 case DIFFSTATE_CONFLICTEMPTY:
5748 case DIFFSTATE_CONFLICTRESOLVED:
5749 case DIFFSTATE_IDENTICALREMOVED:
5750 case DIFFSTATE_IDENTICALADDED:
5751 case DIFFSTATE_THEIRSREMOVED:
5752 case DIFFSTATE_THEIRSADDED:
5753 case DIFFSTATE_YOURSREMOVED:
5754 case DIFFSTATE_YOURSADDED:
5755 case DIFFSTATE_EDITED:
5756 case DIFFSTATE_FILTEREDDIFF:
5757 sSelectedText += GetViewLineChars(nViewLine);
5758 sSelectedText += L"\r\n";
5759 break;
5762 // remove the non-selected chars from the first line, last line and last \r\n
5763 int nLeftCut = start.x;
5764 int nRightCut = GetViewLineChars(end.y).GetLength() - end.x + 2;
5765 sSelectedText = sSelectedText.Mid(nLeftCut, sSelectedText.GetLength()-nLeftCut-nRightCut);
5766 return sSelectedText;
5769 void CBaseView::CheckModifications(bool& hasMods, bool& hasConflicts, bool& hasWhitespaceMods, bool& hasFilteredMods)
5771 hasMods = false;
5772 hasConflicts = false;
5773 hasWhitespaceMods = false;
5774 hasFilteredMods = false;
5776 if (m_pViewData)
5778 for (int i=0; i<m_pViewData->GetCount(); i++)
5780 DiffStates state = m_pViewData->GetState(i);
5781 switch (state)
5783 case DIFFSTATE_ADDED:
5784 case DIFFSTATE_IDENTICALADDED:
5785 case DIFFSTATE_THEIRSADDED:
5786 case DIFFSTATE_YOURSADDED:
5787 case DIFFSTATE_CONFLICTADDED:
5788 case DIFFSTATE_IDENTICALREMOVED:
5789 case DIFFSTATE_REMOVED:
5790 case DIFFSTATE_THEIRSREMOVED:
5791 case DIFFSTATE_YOURSREMOVED:
5792 case DIFFSTATE_EMPTY:
5793 hasMods = true;
5794 break;
5795 case DIFFSTATE_CONFLICTED:
5796 case DIFFSTATE_CONFLICTED_IGNORED:
5797 hasConflicts = true;
5798 break;
5799 case DIFFSTATE_REMOVEDWHITESPACE:
5800 case DIFFSTATE_ADDEDWHITESPACE:
5801 case DIFFSTATE_WHITESPACE:
5802 case DIFFSTATE_WHITESPACE_DIFF:
5803 hasWhitespaceMods = true;
5804 break;
5805 case DIFFSTATE_FILTEREDDIFF:
5806 hasFilteredMods = true;
5807 break;
5813 void CBaseView::OnEditGotoline()
5815 if (!m_pViewData)
5816 return;
5817 // find the last and first line number
5818 int nViewLineCount = m_pViewData->GetCount();
5820 int nLastLineNumber = DIFF_EMPTYLINENUMBER;
5821 for (int nViewLine=nViewLineCount-1; nViewLine>=0; --nViewLine)
5823 nLastLineNumber = m_pViewData->GetLineNumber(nViewLine);
5824 if (nLastLineNumber!=DIFF_EMPTYLINENUMBER)
5826 break;
5829 if (nLastLineNumber==DIFF_EMPTYLINENUMBER || nLastLineNumber==0) // not numbered line foud or last one is first
5831 return;
5833 nLastLineNumber++;
5834 int nFirstLineNumber=1; // first is always 1
5836 CString sText;
5837 sText.Format(IDS_GOTOLINE, nFirstLineNumber, nLastLineNumber);
5839 CGotoLineDlg dlg(this);
5840 dlg.SetLabel(sText);
5841 dlg.SetLimits(nFirstLineNumber, nLastLineNumber);
5842 if (dlg.DoModal() == IDOK)
5844 for (int nViewLine = 0; nViewLine < nViewLineCount; ++nViewLine)
5846 if ((m_pViewData->GetLineNumber(nViewLine)+1) == dlg.GetLineNumber())
5848 HighlightViewLines(nViewLine, nViewLine);
5849 return;
5855 void CBaseView::OnToggleReadonly()
5857 if (IsReadonlyChangable()) {
5858 SetWritable(IsReadonly());
5862 int CBaseView::SaveFile(int nFlags)
5864 Invalidate();
5865 if (m_pViewData && m_pWorkingFile)
5867 CFileTextLines file;
5868 m_SaveParams.m_LineEndings = m_lineendings;
5869 m_SaveParams.m_UnicodeType = m_texttype;
5870 file.SetSaveParams(m_SaveParams);
5872 for (int i=0; i<m_pViewData->GetCount(); i++)
5874 //only copy non-removed lines
5875 DiffStates state = m_pViewData->GetState(i);
5876 switch (state)
5878 case DIFFSTATE_CONFLICTED:
5879 case DIFFSTATE_CONFLICTED_IGNORED:
5881 int first = i;
5882 int last = i;
5885 last++;
5886 } while((last<m_pViewData->GetCount()) && ((m_pViewData->GetState(last)==DIFFSTATE_CONFLICTED)||(m_pViewData->GetState(last)==DIFFSTATE_CONFLICTED_IGNORED)));
5887 file.Add(L"<<<<<<< .mine", EOL_NOENDING);
5888 for (int j=first; j<last; j++)
5890 file.Add(m_pwndRight->m_pViewData->GetLine(j), m_pwndRight->m_pViewData->GetLineEnding(j));
5892 file.Add(L"=======", EOL_NOENDING);
5893 for (int j=first; j<last; j++)
5895 file.Add(m_pwndLeft->m_pViewData->GetLine(j), m_pwndLeft->m_pViewData->GetLineEnding(j));
5897 file.Add(L">>>>>>> .theirs", EOL_NOENDING);
5898 i = last-1;
5900 break;
5901 case DIFFSTATE_EMPTY:
5902 break;
5903 case DIFFSTATE_CONFLICTEMPTY:
5904 case DIFFSTATE_IDENTICALREMOVED:
5905 case DIFFSTATE_REMOVED:
5906 case DIFFSTATE_THEIRSREMOVED:
5907 case DIFFSTATE_YOURSREMOVED:
5908 case DIFFSTATE_CONFLICTRESOLVEDEMPTY:
5909 if ((nFlags&SAVE_REMOVEDLINES) == 0)
5911 // do not save removed lines
5912 break;
5914 default:
5915 file.Add(m_pViewData->GetLine(i), m_pViewData->GetLineEnding(i));
5916 break;
5919 CString filename = m_pWorkingFile->GetFilename();
5920 if (m_pWorkingFile->IsReadonly())
5921 if (!CCommonAppUtils::FileOpenSave(filename, nullptr, IDS_SAVEASTITLE, IDS_COMMONFILEFILTER, false, m_hWnd))
5922 return -1;
5923 if (!file.Save(filename))
5925 ::MessageBox(m_hWnd, file.GetErrorString(), L"TortoiseGitMerge", MB_ICONERROR);
5926 return -1;
5928 m_pWorkingFile->SetFileName(filename);
5929 m_pWorkingFile->StoreFileAttributes();
5930 // m_dlgFilePatches.SetFileStatusAsPatched(sFilePath);
5931 SetModified(FALSE);
5932 CUndo::GetInstance().MarkAsOriginalState(
5933 this == m_pwndLeft,
5934 this == m_pwndRight,
5935 this == m_pwndBottom);
5936 if (file.GetCount() == 1 && file.GetAt(0).IsEmpty() && file.GetLineEnding(0) == EOL_NOENDING)
5937 return 0;
5938 return file.GetCount();
5940 return 1;
5944 int CBaseView::SaveFileTo(CString sFileName, int nFlags)
5946 if (m_pWorkingFile)
5948 m_pWorkingFile->SetFileName(sFileName);
5949 return SaveFile(nFlags);
5951 return -1;
5955 EOL CBaseView::GetLineEndings()
5957 return GetLineEndings(GetWhitecharsProperties().HasMixedEols);
5960 EOL CBaseView::GetLineEndings(bool bHasMixedEols)
5962 if (bHasMixedEols)
5964 return EOL_AUTOLINE; // mixed eols - hack value
5966 if (m_lineendings == EOL_AUTOLINE)
5968 return EOL_CRLF;
5970 return m_lineendings;
5973 void CBaseView::ReplaceLineEndings(EOL eEol)
5975 if (eEol == EOL_AUTOLINE)
5977 return;
5979 // set AUTOLINE
5980 m_lineendings = eEol;
5981 // replace all set EOLs
5982 // TODO store line endings and lineendings in undo
5983 //CUndo::BeginGrouping();
5984 for (int i = 0; i < GetViewCount(); ++i)
5986 if (IsLineEmpty(i))
5988 continue;
5990 EOL eLineEol = GetViewLineEnding(i);
5991 if (eLineEol == EOL_AUTOLINE || eLineEol == EOL_NOENDING || eLineEol == m_lineendings)
5993 continue;
5995 SetViewLineEnding(i, eEol);
5997 //CUndo::EndGrouping();
5998 //CUndo::saveundostep;
5999 DocumentUpdated();
6000 SetModified();
6003 void CBaseView::SetLineEndingStyle(EOL eEol)
6005 m_lineendings = eEol;
6008 void CBaseView::SetTextType(CFileTextLines::UnicodeType eTextType)
6010 if (m_texttype == eTextType)
6012 return;
6014 m_texttype = eTextType;
6015 DocumentUpdated();
6016 SetModified();
6019 void CBaseView::AskUserForNewLineEndingsAndTextType(int nTextId)
6021 if (IsReadonly())
6022 return; // nothing to be changed in read-only view
6023 CEncodingDlg dlg;
6024 dlg.view.LoadString(nTextId);
6025 dlg.texttype = m_texttype;
6026 dlg.lineendings = GetLineEndings();
6027 if (dlg.DoModal() != IDOK)
6028 return;
6029 SetTextType(dlg.texttype);
6030 ReplaceLineEndings(dlg.lineendings);
6034 Replaces lines from source view to this
6036 void CBaseView::UseViewBlock(CBaseView * pwndView, int nFirstViewLine, int nLastViewLine, std::function<bool(int)> fnSkip)
6038 if (!IsViewGood(pwndView))
6039 return;
6040 if (!IsWritable())
6041 return;
6042 CUndo::GetInstance().BeginGrouping();
6044 for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++)
6046 bool skip = fnSkip(viewLine);
6047 if (skip)
6049 if (GetViewMarked(viewLine))
6050 SetViewMarked(viewLine, false);
6051 continue;
6053 viewdata line = pwndView->GetViewData(viewLine);
6054 if (line.ending != EOL_NOENDING)
6055 line.ending = m_lineendings;
6056 switch (line.state)
6058 case DIFFSTATE_CONFLICTEMPTY:
6059 case DIFFSTATE_UNKNOWN:
6060 line.state = DIFFSTATE_EMPTY;
6061 case DIFFSTATE_EMPTY:
6062 break;
6063 case DIFFSTATE_ADDED:
6064 case DIFFSTATE_CONFLICTADDED:
6065 case DIFFSTATE_CONFLICTED:
6066 case DIFFSTATE_CONFLICTED_IGNORED:
6067 case DIFFSTATE_IDENTICALADDED:
6068 case DIFFSTATE_THEIRSADDED:
6069 case DIFFSTATE_YOURSADDED:
6070 case DIFFSTATE_IDENTICALREMOVED:
6071 case DIFFSTATE_REMOVED:
6072 case DIFFSTATE_THEIRSREMOVED:
6073 case DIFFSTATE_YOURSREMOVED:
6074 pwndView->SetViewState(viewLine, DIFFSTATE_NORMAL);
6075 line.state = DIFFSTATE_NORMAL;
6076 case DIFFSTATE_NORMAL:
6077 break;
6078 default:
6079 break;
6081 bool marked = GetViewMarked(viewLine);
6082 SetViewData(viewLine, line);
6083 if (marked)
6084 SetViewMarked(viewLine, false);
6085 if ((m_texttype == UnicodeType::ASCII) && (pwndView->GetTextType() != UnicodeType::ASCII))
6087 // if this view is in ASCII and the other is not, we have to make sure that
6088 // the text we copy from the other view can actually be saved in ASCII encoding.
6089 // if not, we have to change this views encoding to the same encoding as the other view
6090 BOOL useDefault = FALSE;
6091 WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, line.sLine, -1, nullptr, 0, 0, &useDefault);
6092 if (useDefault) // a default char is required, so the char can not be saved as ASCII
6093 SetTextType(pwndView->GetTextType());
6096 // normal lines is mostly same but may differ in EOL so any copied line change view state to modified
6097 // TODO: check if copied line is same as original one set modified only when differ
6098 SetModified();
6099 SaveUndoStep();
6101 int nRemovedLines = CleanEmptyLines();
6102 SaveUndoStep();
6103 //VerifyEols();
6104 // make sure all non empty line have EOL set but last
6105 // wrong can be last copied line(have eol, but no line under),
6106 // or old last line (line before copied block missing eol, but have line under)
6107 // we'll check all lines to be sure
6108 int nLine = GetViewCount();
6109 // check last line have no EOL set
6110 while (--nLine>=0)
6112 if (!IsViewLineEmpty(nLine))
6114 if (GetViewLineEnding(nLine) != EOL_NOENDING)
6116 // we added non last line into empty block on the end (or should we remove eol from this one ?)
6117 // so next line is empty
6118 ASSERT(IsViewLineEmpty(nLine+1));
6119 // and we can turn it to normal empty line
6120 SetViewData(nLine+1, viewdata(CString(), DIFFSTATE_ADDED, 1, EOL_NOENDING, HIDESTATE_SHOWN));
6122 break;
6125 // check all (nonlast) line have EOL set
6126 while (--nLine>=0)
6128 if (!IsViewLineEmpty(nLine))
6130 if (GetViewLineEnding(nLine) == EOL_NOENDING)
6132 SetViewLineEnding(nLine, m_lineendings);
6133 // in theory there should be only one line needing fix, but most of time we get over all anyway
6134 // break;
6138 SaveUndoStep();
6139 UpdateViewLineNumbers();
6140 SaveUndoStep();
6142 CUndo::GetInstance().EndGrouping();
6144 if (nRemovedLines!=0)
6146 // some lines are gone update selection
6147 ClearSelection();
6148 SetupAllViewSelection(nFirstViewLine, nLastViewLine - nRemovedLines);
6150 BuildAllScreen2ViewVector();
6151 pwndView->Invalidate();
6152 RefreshViews();
6155 void CBaseView::MarkBlock(bool marked, int nFirstViewLine, int nLastViewLine)
6157 if (!IsWritable())
6158 return;
6159 CUndo::GetInstance().BeginGrouping();
6161 for (int viewLine = nFirstViewLine; viewLine <= nLastViewLine; viewLine++)
6162 SetViewMarked(viewLine, marked);
6164 SetModified();
6165 SaveUndoStep();
6166 CUndo::GetInstance().EndGrouping();
6168 BuildAllScreen2ViewVector();
6169 Invalidate();
6170 RefreshViews();
6173 void CBaseView::LeaveOnlyMarkedBlocks(CBaseView *pwndView)
6175 auto fn = [this](int viewLine) -> bool { return GetViewMarked(viewLine) || GetViewState(viewLine) == DIFFSTATE_EDITED; };
6176 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6179 void CBaseView::UseViewFileOfMarked(CBaseView *pwndView)
6181 auto fn = [this](int viewLine) -> bool { return !GetViewMarked(viewLine) || GetViewState(viewLine) == DIFFSTATE_EDITED; };
6182 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6185 void CBaseView::UseViewFileExceptEdited(CBaseView *pwndView)
6187 auto fn = [this](int viewLine) -> bool { return GetViewState(viewLine) == DIFFSTATE_EDITED; };
6188 UseViewBlock(pwndView, 0, GetViewCount() - 1, fn);
6191 int CBaseView::GetIndentCharsForLine(int x, int y)
6193 const int maxGuessLine = 100;
6194 int nTabMode = -1;
6195 const CString& line = GetViewLine(y);
6196 if (m_nTabMode & TABMODE_SMARTINDENT)
6198 // if the line contains one tab, use tabs
6199 // we can not test for spaces, since even if tabs are used,
6200 // spaces are used in a tabified file for alignment.
6201 if (line.Find(L'\t') >= 0)
6202 nTabMode = 0; // use tabs
6203 else if (line.GetLength() > m_nTabSize)
6204 nTabMode = 1; // use spaces
6206 if (m_nTabMode & TABMODE_SMARTINDENT)
6208 // detect lines nearby
6209 for (int i = y - 1, j = y + 1; nTabMode == -1; --i, ++j)
6211 bool above = i > 0 && i >= y - maxGuessLine;
6212 bool below = j < GetViewCount() && j <= y + maxGuessLine;
6213 if (!(above || below))
6214 break;
6215 auto ac = GetViewLine(i);
6216 auto bc = GetViewLine(j);
6217 if ((ac.Find(L'\t') >= 0) || (bc.Find(L'\t') >= 0))
6219 nTabMode = 0;
6220 break;
6222 else if ((ac.GetLength() > m_nTabSize) && (bc.GetLength() > m_nTabSize))
6224 nTabMode = 1;
6225 break;
6230 else
6231 nTabMode = m_nTabMode & TABMODE_USESPACES;
6233 if (nTabMode > 0)
6235 // use spaces
6236 x = CountExpandedChars(line, x);
6237 return (m_nTabSize - (x % m_nTabSize));
6240 // use tab
6241 return 0;
6244 void CBaseView::AddIndentationForSelectedBlock()
6246 bool bModified = false;
6247 for (int nViewLine = m_ptSelectionViewPosStart.y; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
6249 // skip the line if no character is selected in the last selected line
6250 if (nViewLine == m_ptSelectionViewPosEnd.y && m_ptSelectionViewPosEnd.x == 0)
6252 continue;
6254 // skip empty lines
6255 if (IsLineEmpty(nViewLine))
6257 continue;
6259 const CString &sLine = GetViewLine(nViewLine);
6260 CString sTemp = sLine;
6261 if (sTemp.Trim().IsEmpty())
6263 // skip empty and whitechar only lines
6264 continue;
6266 // add tab to line start (alternatively m_nTabSize spaces can be used)
6267 CString tabStr;
6268 int indentChars = GetIndentCharsForLine(0, nViewLine);
6269 tabStr = indentChars > 0 ? CString(L' ', indentChars) : L"\t";
6270 SetViewLine(nViewLine, tabStr + sLine);
6271 bModified = true;
6273 if (bModified)
6275 SetModified();
6276 SaveUndoStep();
6277 BuildAllScreen2ViewVector();
6281 void CBaseView::RemoveIndentationForSelectedBlock()
6283 bool bModified = false;
6284 for (int nViewLine = m_ptSelectionViewPosStart.y; nViewLine <= m_ptSelectionViewPosEnd.y; nViewLine++)
6286 // skip the line if no character is selected in the last selected line
6287 if (nViewLine == m_ptSelectionViewPosEnd.y && m_ptSelectionViewPosEnd.x == 0)
6289 continue;
6291 // skip empty lines
6292 if (IsLineEmpty(nViewLine))
6294 continue;
6296 CString sLine = GetViewLine(nViewLine);
6297 // remove up to n spaces from line start
6298 // and one tab (if less then n spaces was removed)
6299 int nPos = 0;
6300 while (nPos<m_nTabSize)
6302 switch (sLine[nPos])
6304 case ' ':
6305 nPos++;
6306 continue;
6307 case '\t':
6308 nPos++;
6310 break;
6312 if (nPos>0)
6314 sLine.Delete(0, nPos);
6315 SetViewLine(nViewLine, sLine);
6316 bModified = true;
6319 if (bModified)
6321 SetModified();
6322 SaveUndoStep();
6323 BuildAllScreen2ViewVector();
6328 there are two possible versions
6329 - convert tabs to spaces only in front of text (implemented)
6330 - convert all tabs to spaces
6332 void CBaseView::ConvertTabToSpaces()
6334 bool bModified = false;
6335 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6337 if (IsLineEmpty(nViewLine))
6339 continue;
6341 const CString &sLine = GetViewLine(nViewLine);
6342 bool bTabToConvertFound = false;
6343 int nPosIn = 0;
6344 int nPosOut = 0;
6345 while (nPosIn<sLine.GetLength())
6347 switch (sLine[nPosIn])
6349 case ' ':
6350 nPosIn++;
6351 nPosOut++;
6352 continue;
6353 case '\t':
6354 nPosIn++;
6355 bTabToConvertFound = true;
6356 nPosOut = (nPosOut+m_nTabSize) - nPosOut%m_nTabSize;
6357 continue;
6359 break;
6361 if (bTabToConvertFound)
6363 CString sLineNew = sLine;
6364 sLineNew.Delete(0, nPosIn);
6365 sLineNew = CString(' ', nPosOut) + sLineNew;
6366 SetViewLine(nViewLine, sLineNew);
6367 bModified = true;
6370 if (bModified)
6372 SetModified();
6373 SaveUndoStep();
6374 BuildAllScreen2ViewVector();
6379 there are two possible version
6380 - convert spaces to tabs only in front of text (implemented)
6381 - convert all spaces to tabs
6383 void CBaseView::Tabularize()
6385 bool bModified = false;
6386 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6388 if (IsLineEmpty(nViewLine))
6390 continue;
6392 const CString &sLine = GetViewLine(nViewLine);
6393 int nDel = 0;
6394 int nTabCount = 0; // total tabs to be used
6395 int nSpaceCount = 0; // number of spaces in tab size run
6396 int nPos = 0;
6397 while (nPos<sLine.GetLength())
6399 switch (sLine[nPos++])
6401 case ' ':
6402 //bSpace = true;
6403 if (++nSpaceCount < m_nTabSize)
6405 continue;
6407 case '\t':
6408 nTabCount++;
6409 nSpaceCount = 0;
6410 nDel = nPos;
6411 continue;
6413 break;
6415 if (nDel > 0)
6417 CString sLineNew = sLine;
6418 sLineNew.Delete(0, nDel);
6419 sLineNew = CString('\t', nTabCount) + sLineNew;
6420 if (sLine!=sLineNew)
6422 SetViewLine(nViewLine, sLineNew);
6423 bModified = true;
6427 if (bModified)
6429 SetModified();
6430 SaveUndoStep();
6431 BuildAllScreen2ViewVector();
6435 void CBaseView::RemoveTrailWhiteChars()
6437 bool bModified = false;
6438 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6440 if (IsLineEmpty(nViewLine))
6442 continue;
6444 const CString &sLine = GetViewLine(nViewLine);
6445 CString sLineNew = sLine;
6446 sLineNew.TrimRight();
6447 if (sLine.GetLength()!=sLineNew.GetLength())
6449 SetViewLine(nViewLine, sLineNew);
6450 bModified = true;
6453 if (bModified)
6455 SetModified();
6456 SaveUndoStep();
6457 BuildAllScreen2ViewVector();
6461 CBaseView::TWhitecharsProperties CBaseView::GetWhitecharsProperties()
6463 if (GetViewCount()>10000)
6465 // 10k lines is enough to check
6466 TWhitecharsProperties oRet = {true, true, true, true};
6467 return oRet;
6469 TWhitecharsProperties oRet = {};
6470 for (int nViewLine = 0; nViewLine < GetViewCount(); nViewLine++)
6472 if (IsLineEmpty(nViewLine))
6474 continue;
6476 const CString &sLine = GetViewLine(nViewLine);
6477 if (sLine.IsEmpty())
6479 continue;
6481 // check leading whites for convertible tabs and spaces
6482 int nPos = 0;
6483 int nSpaceCount = 0; // number of spaces in tab size run
6484 while (nPos<sLine.GetLength() && (!oRet.HasSpacesToConvert || !oRet.HasTabsToConvert))
6486 switch (sLine[nPos++])
6488 case ' ':
6489 if (++nSpaceCount >= m_nTabSize)
6491 oRet.HasSpacesToConvert = true;
6493 continue;
6494 case '\t':
6495 oRet.HasTabsToConvert = true;
6496 if (nSpaceCount!=0)
6498 oRet.HasSpacesToConvert = true;
6500 continue;
6502 break;
6505 // check trailing whites for removable chars
6506 switch (sLine[sLine.GetLength()-1])
6508 case ' ':
6509 case '\t':
6510 oRet.HasTrailWhiteChars = true;
6513 // check EOLs
6514 EOL eLineEol = GetViewLineEnding(nViewLine);
6515 if (!oRet.HasMixedEols && (eLineEol != m_lineendings) && (eLineEol != EOL_AUTOLINE) && (eLineEol != EOL_NOENDING))
6517 oRet.HasMixedEols = true;
6520 return oRet;
6523 void CBaseView::InsertText(const CString& sText)
6525 ResetUndoStep();
6527 POINT ptCaretViewPos = GetCaretViewPosition();
6528 int nLeft = ptCaretViewPos.x;
6529 int nViewLine = ptCaretViewPos.y;
6531 if ((nViewLine == 0) && (GetViewCount() == 0))
6532 OnChar(VK_RETURN, 0, 0);
6534 std::vector<CString> lines;
6535 int nStart = 0;
6536 int nEolPos = 0;
6537 while ((nEolPos = sText.Find('\r', nEolPos)) >= 0)
6539 CString sLine = sText.Mid(nStart, nEolPos - nStart);
6540 lines.push_back(sLine);
6541 nEolPos++;
6542 nStart = nEolPos;
6544 CString sLine = sText.Mid(nStart);
6545 lines.push_back(sLine);
6547 int nLinesToPaste = (int)lines.size();
6548 if (nLinesToPaste > 1)
6550 // multiline text
6552 // We want to undo the multiline insertion in a single step.
6553 CUndo::GetInstance().BeginGrouping();
6555 sLine = GetViewLineChars(nViewLine);
6556 CString sLineLeft = sLine.Left(nLeft);
6557 CString sLineRight = sLine.Right(sLine.GetLength() - nLeft);
6558 EOL eOriginalEnding = GetViewLineEnding(nViewLine);
6559 viewdata newLine(L"", DIFFSTATE_EDITED, 1, m_lineendings, HIDESTATE_SHOWN);
6560 if (!lines[0].IsEmpty() || !sLineRight.IsEmpty() || (eOriginalEnding != m_lineendings))
6562 newLine.sLine = sLineLeft + lines[0];
6563 SetViewData(nViewLine, newLine);
6566 int nInsertLine = nViewLine;
6567 for (int i = 1; i < nLinesToPaste - 1; i++)
6569 newLine.sLine = lines[i];
6570 InsertViewData(++nInsertLine, newLine);
6572 newLine.sLine = lines[nLinesToPaste - 1] + sLineRight;
6573 newLine.ending = eOriginalEnding;
6574 InsertViewData(++nInsertLine, newLine);
6576 SetModified();
6577 SaveUndoStep();
6579 // adds new lines everywhere except me
6580 if (IsViewGood(m_pwndLeft) && m_pwndLeft != this)
6582 m_pwndLeft->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6584 if (IsViewGood(m_pwndRight) && m_pwndRight != this)
6586 m_pwndRight->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6588 if (IsViewGood(m_pwndBottom) && m_pwndBottom != this)
6590 m_pwndBottom->InsertViewEmptyLines(nViewLine + 1, nLinesToPaste - 1);
6592 SaveUndoStep();
6594 UpdateViewLineNumbers();
6595 CUndo::GetInstance().EndGrouping();
6597 ptCaretViewPos = SetupPoint(lines[nLinesToPaste - 1].GetLength(), nInsertLine);
6599 else
6601 // single line text - just insert it
6602 sLine = GetViewLineChars(nViewLine);
6603 sLine.Insert(nLeft, sText);
6604 ptCaretViewPos = SetupPoint(nLeft + sText.GetLength(), nViewLine);
6605 SetViewLine(nViewLine, sLine);
6606 SetViewState(nViewLine, DIFFSTATE_EDITED);
6607 SetModified();
6608 SaveUndoStep();
6611 RefreshViews();
6612 BuildAllScreen2ViewVector();
6613 UpdateCaretViewPosition(ptCaretViewPos);
6616 ULONG CBaseView::GetGestureStatus(CPoint /*ptTouch*/)
6618 return 0;