1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
8 * @file MergeEditView.cpp
10 * @brief Implementation of the CMergeEditView class
14 #include "MergeEditView.h"
18 #include "LocationView.h"
21 #include "OptionsMgr.h"
22 #include "OptionsDiffColors.h"
23 #include "FileTransform.h"
25 #include "WMGotoDlg.h"
26 #include "OptionsDef.h"
27 #include "SyntaxColors.h"
28 #include "MergeEditFrm.h"
29 #include "MergeLineFlags.h"
31 #include "DropHandler.h"
33 #include "ShellContextMenu.h"
40 #ifndef WM_MOUSEHWHEEL
41 # define WM_MOUSEHWHEEL 0x20e
45 using CrystalLineParser::TEXTBLOCK;
47 /** @brief Timer ID for delayed rescan. */
48 const UINT IDT_RESCAN = 2;
49 /** @brief Timer timeout for delayed rescan. */
50 const UINT RESCAN_TIMEOUT = 1000;
52 /** @brief Location for file compare specific help to open. */
53 static TCHAR MergeViewHelpLocation[] = _T("::/htmlhelp/Compare_files.html");
55 /////////////////////////////////////////////////////////////////////////////
58 IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)
60 CMergeEditView::CMergeEditView()
61 : m_bCurrentLineIsDiff(false)
64 , m_bDetailView(false)
65 , m_piMergeEditStatus(nullptr)
66 , m_bAutomaticRescan(false)
67 , fTimerWaitingForIdle(0)
70 , m_CurrentPredifferID(0)
72 SetParser(&m_xParser);
74 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
77 CMergeEditView::~CMergeEditView()
82 BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
83 //{{AFX_MSG_MAP(CMergeEditView)
84 ON_COMMAND(ID_CURDIFF, OnCurdiff)
85 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
86 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
87 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
88 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
89 ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
90 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
91 ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
92 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
93 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
94 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
95 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
96 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
97 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
98 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
99 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
100 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
101 ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
102 ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
103 ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
104 ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
105 ON_COMMAND(ID_NEXTDIFFLM, OnNextdiffLM)
106 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLM, OnUpdateNextdiffLM)
107 ON_COMMAND(ID_PREVDIFFLM, OnPrevdiffLM)
108 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLM, OnUpdatePrevdiffLM)
109 ON_COMMAND(ID_NEXTDIFFLR, OnNextdiffLR)
110 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLR, OnUpdateNextdiffLR)
111 ON_COMMAND(ID_PREVDIFFLR, OnPrevdiffLR)
112 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLR, OnUpdatePrevdiffLR)
113 ON_COMMAND(ID_NEXTDIFFMR, OnNextdiffMR)
114 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMR, OnUpdateNextdiffMR)
115 ON_COMMAND(ID_PREVDIFFMR, OnPrevdiffMR)
116 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMR, OnUpdatePrevdiffMR)
117 ON_COMMAND(ID_NEXTDIFFLO, OnNextdiffLO)
118 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLO, OnUpdateNextdiffLO)
119 ON_COMMAND(ID_PREVDIFFLO, OnPrevdiffLO)
120 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLO, OnUpdatePrevdiffLO)
121 ON_COMMAND(ID_NEXTDIFFMO, OnNextdiffMO)
122 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMO, OnUpdateNextdiffMO)
123 ON_COMMAND(ID_PREVDIFFMO, OnPrevdiffMO)
124 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMO, OnUpdatePrevdiffMO)
125 ON_COMMAND(ID_NEXTDIFFRO, OnNextdiffRO)
126 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFRO, OnUpdateNextdiffRO)
127 ON_COMMAND(ID_PREVDIFFRO, OnPrevdiffRO)
128 ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
129 ON_WM_LBUTTONDBLCLK()
132 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
133 ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
134 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
135 ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
136 ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
137 ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
138 ON_COMMAND(ID_L2R, OnL2r)
139 ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
140 ON_COMMAND(ID_R2L, OnR2l)
141 ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
142 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
143 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
144 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
145 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
146 ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
147 ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
148 ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
149 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
150 ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
151 ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
153 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
154 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_MIDDLE, OnUpdateFileSaveMiddle)
155 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
156 ON_COMMAND(ID_REFRESH, OnRefresh)
157 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
158 ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
159 ON_UPDATE_COMMAND_UI(ID_SELECTLINEDIFF, OnUpdateSelectLineDiff)
160 ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
161 ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
163 ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
164 ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
165 ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateLeftReadOnly)
166 ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnMiddleReadOnly)
167 ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateMiddleReadOnly)
168 ON_COMMAND(ID_FILE_RIGHT_READONLY, OnRightReadOnly)
169 ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateRightReadOnly)
170 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_RO, OnUpdateStatusRO)
171 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_RO, OnUpdateStatusRO)
172 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_RO, OnUpdateStatusRO)
173 ON_COMMAND_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnConvertEolTo)
174 ON_UPDATE_COMMAND_UI_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnUpdateConvertEolTo)
175 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_EOL, OnUpdateStatusEOL)
176 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_EOL, OnUpdateStatusEOL)
177 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_EOL, OnUpdateStatusEOL)
178 ON_COMMAND(ID_L2RNEXT, OnL2RNext)
179 ON_UPDATE_COMMAND_UI(ID_L2RNEXT, OnUpdateL2RNext)
180 ON_COMMAND(ID_R2LNEXT, OnR2LNext)
181 ON_UPDATE_COMMAND_UI(ID_R2LNEXT, OnUpdateR2LNext)
182 ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnChangePane)
183 ON_COMMAND(ID_NEXT_PANE, OnChangePane)
184 ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
185 ON_COMMAND(ID_FILE_SHELLMENU, OnShellMenu)
186 ON_UPDATE_COMMAND_UI(ID_FILE_SHELLMENU, OnUpdateShellMenu)
187 ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
188 ON_COMMAND(ID_NO_PREDIFFER, OnNoPrediffer)
189 ON_UPDATE_COMMAND_UI(ID_NO_PREDIFFER, OnUpdateNoPrediffer)
190 ON_COMMAND_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnPrediffer)
191 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnUpdatePrediffer)
194 ON_COMMAND(ID_EDIT_COPY_LINENUMBERS, OnEditCopyLineNumbers)
195 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY_LINENUMBERS, OnUpdateEditCopyLinenumbers)
196 ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
197 ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
198 ON_COMMAND(ID_VIEW_WORDWRAP, OnViewWordWrap)
199 ON_UPDATE_COMMAND_UI(ID_VIEW_WORDWRAP, OnUpdateViewWordWrap)
200 ON_COMMAND(ID_VIEW_LINENUMBERS, OnViewLineNumbers)
201 ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
202 ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
203 ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
204 ON_COMMAND(ID_VIEW_EOL, OnViewEOL)
205 ON_UPDATE_COMMAND_UI(ID_VIEW_EOL, OnUpdateViewEOL)
206 ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
207 ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
208 ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
209 ON_COMMAND(ID_VIEW_SWAPPANES, OnViewSwapPanes)
210 ON_UPDATE_COMMAND_UI(ID_NO_EDIT_SCRIPTS, OnUpdateNoEditScripts)
213 ON_COMMAND(ID_HELP, OnHelp)
214 ON_COMMAND(ID_VIEW_SELMARGIN, OnViewMargin)
215 ON_UPDATE_COMMAND_UI(ID_VIEW_SELMARGIN, OnUpdateViewMargin)
216 ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
217 ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
218 ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
221 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
222 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
223 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
224 ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
225 ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
226 ON_NOTIFY(NM_DBLCLK, AFX_IDW_STATUS_BAR, OnStatusBarDblClick)
231 /////////////////////////////////////////////////////////////////////////////
232 // CMergeEditView diagnostics
235 CMergeDoc* CMergeEditView::GetDocument() // non-debug version is inline
237 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
238 return (CMergeDoc*)m_pDocument;
243 /////////////////////////////////////////////////////////////////////////////
244 // CMergeEditView message handlers
247 * @brief Return text buffer for file in view
249 CCrystalTextBuffer *CMergeEditView::LocateTextBuffer()
251 return GetDocument()->m_ptBuf[m_nThisPane].get();
255 * @brief Update any resources necessary after a GUI language change
257 void CMergeEditView::UpdateResources()
261 CMergeEditView *CMergeEditView::GetGroupView(int nBuffer) const
263 return GetDocument()->GetView(m_nThisGroup, nBuffer);
266 void CMergeEditView::PrimeListWithFile()
268 // Set the tab size now, just in case the options change...
269 // We don't update it at the end of OnOptions,
270 // we can update it safely now
271 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
274 * @brief Return text from line given
276 CString CMergeEditView::GetLineText(int idx)
278 return GetLineChars(idx);
282 * @brief Return text from selection
284 CString CMergeEditView::GetSelectedText()
286 CPoint ptStart, ptEnd;
288 GetSelection(ptStart, ptEnd);
289 if (ptStart != ptEnd)
290 GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
295 * @brief Get diffs inside selection.
296 * @param [out] firstDiff First diff inside selection
297 * @param [out] lastDiff Last diff inside selection
298 * @note -1 is returned in parameters if diffs cannot be determined
299 * @todo This shouldn't be called when there is no diffs, so replace
300 * first 'if' with ASSERT()?
302 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff)
307 CMergeDoc *pd = GetDocument();
308 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
312 int firstLine, lastLine;
313 GetFullySelectedLines(firstLine, lastLine);
314 if (lastLine < firstLine)
317 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
318 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
319 if (firstDiff != -1 && lastDiff != -1)
323 // Check that first selected line is first diff's first line or above it
324 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
325 if ((int)di.dbegin < firstLine)
327 if (firstDiff < lastDiff)
331 // Check that last selected line is last diff's last line or below it
332 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
333 if ((int)di.dend > lastLine)
335 if (firstDiff < lastDiff)
339 // Special case: one-line diff is not selected if cursor is in it
340 if (firstLine == lastLine)
349 * @brief Get diffs inside selection.
350 * @param [out] firstDiff First diff inside selection
351 * @param [out] lastDiff Last diff inside selection
352 * @param [out] firstWordDiff First word level diff inside selection
353 * @param [out] lastWordDiff Last word level diff inside selection
354 * @note -1 is returned in parameters if diffs cannot be determined
355 * @todo This shouldn't be called when there is no diffs, so replace
356 * first 'if' with ASSERT()?
358 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CPoint *pptStart, const CPoint *pptEnd)
365 CMergeDoc *pd = GetDocument();
366 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
370 int firstLine, lastLine;
371 CPoint ptStart, ptEnd;
372 GetSelection(ptStart, ptEnd);
373 if (pptStart != nullptr)
375 if (pptEnd != nullptr)
377 firstLine = ptStart.y;
380 firstDiff = pd->m_diffList.LineToDiff(firstLine);
381 bool firstLineIsNotInDiff = firstDiff == -1;
384 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
389 lastDiff = pd->m_diffList.LineToDiff(lastLine);
390 bool lastLineIsNotInDiff = lastDiff == -1;
392 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
393 if (lastDiff < firstDiff)
400 if (firstDiff != -1 && lastDiff != -1)
404 if (pd->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
406 firstWordDiff = lastWordDiff = static_cast<int>(pd->GetCurrentWordDiff().nWordDiff);
408 else if (ptStart != ptEnd)
410 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
411 if (lastLineIsNotInDiff && (firstLineIsNotInDiff || (di.dbegin == firstLine && ptStart.x == 0)))
417 if (firstWordDiff == -1)
419 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff);
420 for (size_t i = 0; i < worddiffs.size(); ++i)
422 int worddiffLen = worddiffs[i].end[m_nThisPane] - worddiffs[i].begin[m_nThisPane];
423 if (worddiffs[i].endline[m_nThisPane] > firstLine ||
424 (firstLine == worddiffs[i].endline[m_nThisPane] &&
425 worddiffs[i].end[m_nThisPane] - (worddiffLen == 0 ? 0 : 1) >= ptStart.x))
427 firstWordDiff = static_cast<int>(i);
432 if (firstLine >= di.dbegin && firstWordDiff == -1)
439 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
440 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(lastDiff);
441 for (size_t i = worddiffs.size() - 1; i != (size_t)-1; --i)
443 if (worddiffs[i].beginline[m_nThisPane] < lastLine ||
444 (lastLine == worddiffs[i].beginline[m_nThisPane] && worddiffs[i].begin[m_nThisPane] + 1 <= ptEnd.x))
446 lastWordDiff = static_cast<int>(i);
451 if (lastLine <= di.dend && lastWordDiff == -1)
454 if (firstDiff == lastDiff && (lastWordDiff != -1 && lastWordDiff < firstWordDiff))
461 else if (lastDiff < firstDiff || (firstDiff == lastDiff && firstWordDiff == -1 && lastWordDiff == -1))
478 ASSERT(firstDiff == -1 ? (lastDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
479 ASSERT(lastDiff == -1 ? (firstDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
480 ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true);
483 std::map<int, std::vector<int>> CMergeEditView::GetColumnSelectedWordDiffIndice()
485 CMergeDoc *pDoc = GetDocument();
486 std::map<int, std::vector<int>> ret;
487 std::map<int, std::vector<int> *> list;
488 CPoint ptStart, ptEnd;
489 GetSelection(ptStart, ptEnd);
490 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
492 if (pDoc->m_diffList.LineToDiff(nLine) != -1)
494 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
496 GetColumnSelection(nLine, nLeft, nRight);
497 CPoint ptStart2, ptEnd2;
500 ptStart2.y = ptEnd2.y = nLine;
501 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff, &ptStart2, &ptEnd2);
502 if (firstDiff != -1 && lastDiff != -1)
504 std::vector<int> *pWordDiffs;
505 if (list.find(firstDiff) == list.end())
506 list.insert(std::pair<int, std::vector<int> *>(firstDiff, new std::vector<int>()));
507 pWordDiffs = list[firstDiff];
508 for (int i = firstWordDiff; i <= lastWordDiff; ++i)
510 if (pWordDiffs->empty() || i != (*pWordDiffs)[pWordDiffs->size() - 1])
511 pWordDiffs->push_back(i);
516 for (auto& it : list)
517 ret.insert(std::pair<int, std::vector<int>>(it.first, *it.second));
521 void CMergeEditView::OnInitialUpdate()
524 CCrystalEditViewEx::OnInitialUpdate();
526 SetFont(dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff);
527 SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
533 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
535 CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
537 CMergeDoc* pDoc = GetDocument();
538 pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
541 std::vector<CrystalLineParser::TEXTBLOCK> CMergeEditView::GetMarkerTextBlocks(int nLineIndex) const
545 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
546 return std::vector<CrystalLineParser::TEXTBLOCK>();
548 return CCrystalTextView::GetMarkerTextBlocks(nLineIndex);
551 std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
553 static const std::vector<TEXTBLOCK> emptyBlocks;
556 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
560 DWORD dwLineFlags = GetLineFlags(nLineIndex);
561 if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
564 if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
567 CMergeDoc *pDoc = GetDocument();
568 if (pDoc->IsEditedAfterRescan(m_nThisPane))
571 int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
576 pDoc->m_diffList.GetDiff(nDiff, cd);
577 int unemptyLineCount = 0;
578 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
580 if (cd.begin[nPane] != cd.end[nPane] + 1)
583 if (unemptyLineCount < 2)
586 vector<WordDiff> worddiffs = pDoc->GetWordDiffArray(nLineIndex);
587 size_t nWordDiffs = worddiffs.size();
589 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
591 std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
592 blocks[0].m_nCharPos = 0;
593 blocks[0].m_nColorIndex = COLORINDEX_NONE;
594 blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
596 for (i = 0, j = 1; i < nWordDiffs; i++)
598 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
600 if (pDoc->m_nBuffers > 2)
602 if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
604 else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
607 int begin[3], end[3];
608 bool deleted = false;
609 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
611 begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
612 end[pane] = (worddiffs[i].endline[pane] > nLineIndex) ? GetGroupView(pane)->GetLineLength(nLineIndex) : worddiffs[i].end[pane];
613 if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
614 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
617 blocks[j].m_nCharPos = begin[m_nThisPane];
618 if (lineInCurrentDiff)
620 if (m_cachedColors.clrSelDiffText != CLR_NONE)
621 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT1 | COLORINDEX_APPLYFORCE;
623 blocks[j].m_nColorIndex = COLORINDEX_NONE;
624 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
625 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
629 if (m_cachedColors.clrDiffText != CLR_NONE)
630 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT2 | COLORINDEX_APPLYFORCE;
632 blocks[j].m_nColorIndex = COLORINDEX_NONE;
633 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
634 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
637 blocks[j].m_nCharPos = end[m_nThisPane];
638 blocks[j].m_nColorIndex = COLORINDEX_NONE;
639 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
648 COLORREF CMergeEditView::GetColor(int nColorIndex)
650 switch (nColorIndex & ~COLORINDEX_APPLYFORCE)
652 case COLORINDEX_HIGHLIGHTBKGND1:
653 return m_cachedColors.clrSelWordDiff;
654 case COLORINDEX_HIGHLIGHTTEXT1:
655 return m_cachedColors.clrSelWordDiffText;
656 case COLORINDEX_HIGHLIGHTBKGND2:
657 return m_cachedColors.clrWordDiff;
658 case COLORINDEX_HIGHLIGHTTEXT2:
659 return m_cachedColors.clrWordDiffText;
660 case COLORINDEX_HIGHLIGHTBKGND3:
661 return m_cachedColors.clrWordDiffDeleted;
662 case COLORINDEX_HIGHLIGHTBKGND4:
663 return m_cachedColors.clrSelWordDiffDeleted;
666 return CCrystalTextView::GetColor(nColorIndex);
671 * @brief Determine text and background color for line
672 * @param [in] nLineIndex Index of line in view (NOT line in file)
673 * @param [out] crBkgnd Backround color for line
674 * @param [out] crText Text color for line
676 void CMergeEditView::GetLineColors(int nLineIndex, COLORREF & crBkgnd,
677 COLORREF & crText, bool & bDrawWhitespace)
679 DWORD ignoreFlags = 0;
680 GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
684 * @brief Determine text and background color for line
685 * @param [in] nLineIndex Index of line in view (NOT line in file)
686 * @param [in] ignoreFlags Flags that caller wishes ignored
687 * @param [out] crBkgnd Backround color for line
688 * @param [out] crText Text color for line
690 * This version allows caller to suppress particular flags
692 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF & crBkgnd,
693 COLORREF & crText, bool & bDrawWhitespace)
695 if (GetLineCount() <= nLineIndex)
698 DWORD dwLineFlags = GetLineFlags(nLineIndex);
700 if (dwLineFlags & ignoreFlags)
701 dwLineFlags &= (~ignoreFlags);
705 // Line with WinMerge flag,
706 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
707 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
709 crText = m_cachedColors.clrDiffText;
710 bDrawWhitespace = true;
712 if (dwLineFlags & LF_GHOST)
714 crBkgnd = m_cachedColors.clrDiffDeleted;
719 // If no syntax hilighting
720 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
722 crBkgnd = GetColor (COLORINDEX_BKGND);
723 crText = GetColor (COLORINDEX_NORMALTEXT);
724 bDrawWhitespace = false;
727 // Line not inside diff, get colors from CrystalEditor
728 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
729 crText, bDrawWhitespace);
731 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
733 crBkgnd = GetColor (COLORINDEX_WHITESPACE);
734 crText = GetColor (COLORINDEX_WHITESPACE);
735 bDrawWhitespace = false;
741 if (dwLineFlags & LF_WINMERGE_FLAGS)
743 crText = m_cachedColors.clrDiffText;
744 bDrawWhitespace = true;
745 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
747 if (dwLineFlags & LF_SNP)
749 if (lineInCurrentDiff)
751 if (dwLineFlags & LF_GHOST)
752 crBkgnd = m_cachedColors.clrSelSNPDeleted;
754 crBkgnd = m_cachedColors.clrSelSNP;
755 crText = m_cachedColors.clrSelSNPText;
759 if (dwLineFlags & LF_GHOST)
760 crBkgnd = m_cachedColors.clrSNPDeleted;
762 crBkgnd = m_cachedColors.clrSNP;
763 crText = m_cachedColors.clrSNPText;
767 else if (dwLineFlags & LF_DIFF)
769 if (lineInCurrentDiff)
771 if (dwLineFlags & LF_MOVED)
773 crBkgnd = m_cachedColors.clrSelMoved;
774 crText = m_cachedColors.clrSelMovedText;
778 crBkgnd = m_cachedColors.clrSelDiff;
779 crText = m_cachedColors.clrSelDiffText;
785 if (dwLineFlags & LF_MOVED)
787 crBkgnd = m_cachedColors.clrMoved;
788 crText = m_cachedColors.clrMovedText;
792 crBkgnd = m_cachedColors.clrDiff;
793 crText = m_cachedColors.clrDiffText;
798 else if (dwLineFlags & LF_TRIVIAL)
800 // trivial diff can not be selected
801 if (dwLineFlags & LF_GHOST)
802 // ghost lines in trivial diff has their own color
803 crBkgnd = m_cachedColors.clrTrivialDeleted;
805 crBkgnd = m_cachedColors.clrTrivial;
806 crText = m_cachedColors.clrTrivialText;
809 else if (dwLineFlags & LF_GHOST)
811 if (lineInCurrentDiff)
813 if (dwLineFlags & LF_MOVED)
814 crBkgnd = m_cachedColors.clrSelMovedDeleted;
816 crBkgnd = m_cachedColors.clrSelDiffDeleted;
820 if (dwLineFlags & LF_MOVED)
821 crBkgnd = m_cachedColors.clrMovedDeleted;
823 crBkgnd = m_cachedColors.clrDiffDeleted;
830 // Line not inside diff,
831 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
833 // If no syntax hilighting, get windows default colors
834 crBkgnd = GetColor (COLORINDEX_BKGND);
835 crText = GetColor (COLORINDEX_NORMALTEXT);
836 bDrawWhitespace = false;
839 // Syntax highlighting, get colors from CrystalEditor
840 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
841 crText, bDrawWhitespace);
846 * @brief Sync other pane position
848 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
850 CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
851 if (pSplitterWnd != nullptr)
853 // See CSplitterWnd::IdFromRowCol() implementation for details
854 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
855 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
856 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
857 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
859 // limit the TopLine : must be smaller than GetLineCount for all the panels
860 int newTopSubLine = m_nTopSubLine;
861 int nRows = pSplitterWnd->GetRowCount ();
862 int nCols = pSplitterWnd->GetColumnCount ();
864 // for (nRow = 0; nRow < nRows; nRow++)
866 // for (int nCol = 0; nCol < nCols; nCol++)
868 // CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
869 // if (pSiblingView != nullptr)
870 // if (pSiblingView->GetSubLineCount() <= newTopSubLine)
871 // newTopSubLine = pSiblingView->GetSubLineCount()-1;
874 if (m_nTopSubLine != newTopSubLine)
875 ScrollToSubLine(newTopSubLine);
877 for (nRow = 0; nRow < nRows; nRow++)
879 for (int nCol = 0; nCol < nCols; nCol++)
881 if (!(nRow == nCurrentRow && nCol == nCurrentCol)) // We don't need to update ourselves
883 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
884 if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
885 pSiblingView->OnUpdateSibling (this, bHorz);
893 * @brief Update other panes
895 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
897 if (pUpdateSource != this)
899 ASSERT (pUpdateSource != nullptr);
900 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
901 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
902 if (!bHorz) // changed this so bHorz works right
904 ASSERT (pSrcView->m_nTopSubLine >= 0);
906 // This ASSERT is wrong: panes have different files and
907 // different linecounts
908 // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
909 if (pSrcView->m_nTopSubLine != m_nTopSubLine)
911 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
913 RecalcVertScrollBar(true);
914 RecalcHorzScrollBar();
919 ASSERT (pSrcView->m_nOffsetChar >= 0);
921 // This ASSERT is wrong: panes have different files and
922 // different linelengths
923 // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
924 if (pSrcView->m_nOffsetChar != m_nOffsetChar)
926 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
928 RecalcHorzScrollBar(true);
929 RecalcHorzScrollBar();
935 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
937 int newlineBegin, newlineEnd;
938 CMergeDoc *pd = GetDocument();
939 if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
947 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
949 newlineBegin = curDiff.dbegin;
950 ASSERT (newlineBegin >= 0);
951 newlineEnd = curDiff.dend;
954 m_lineBegin = newlineBegin;
955 m_lineEnd = newlineEnd;
957 int nLineCount = GetLineCount();
958 if (m_lineBegin > nLineCount)
959 m_lineBegin = nLineCount - 1;
960 if (m_lineEnd > nLineCount)
961 m_lineEnd = nLineCount - 1;
963 if (m_nTopLine == newlineBegin)
966 // scroll to the first line of the diff
967 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
968 CPoint pt = worddiffs.size() > 0 ?
969 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
970 CPoint{ 0, m_lineBegin };
973 // update the width of the horizontal scrollbar
974 RecalcHorzScrollBar();
978 * @brief Selects diff by number and syncs other file
979 * @param [in] nDiff Diff to select, must be >= 0
980 * @param [in] bScroll Scroll diff to view
981 * @param [in] bSelectText Select diff text
982 * @sa CMergeEditView::ShowDiff()
983 * @sa CMergeDoc::SetCurrentDiff()
984 * @todo Parameter bSelectText is never used?
986 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
988 CMergeDoc *pd = GetDocument();
990 // Check that nDiff is valid
992 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
993 if (nDiff >= pd->m_diffList.GetSize())
994 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
995 nDiff, pd->m_diffList.GetSize());
998 pd->SetCurrentDiff(nDiff);
999 ShowDiff(bScroll, bSelectText);
1000 pd->UpdateAllViews(this);
1001 UpdateSiblingScrollPos(false);
1003 // notify either side, as it will notify the other one
1004 pd->ForEachView ([&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
1007 void CMergeEditView::DeselectDiffIfCursorNotInCurrentDiff()
1009 CMergeDoc *pd = GetDocument();
1010 // If we have a selected diff, deselect it
1011 int nCurrentDiff = pd->GetCurrentDiff();
1012 if (nCurrentDiff != -1)
1014 CPoint pos = GetCursorPos();
1015 if (!IsLineInCurrentDiff(pos.y))
1017 pd->SetCurrentDiff(-1);
1019 pd->UpdateAllViews(this);
1025 * @brief Called when user selects "Current Difference".
1026 * Goes to active diff. If no active diff, selects diff under cursor
1027 * @sa CMergeEditView::SelectDiff()
1028 * @sa CMergeDoc::GetCurrentDiff()
1029 * @sa CMergeDoc::LineToDiff()
1031 void CMergeEditView::OnCurdiff()
1033 CMergeDoc *pd = GetDocument();
1035 // If no diffs, nothing to select
1036 if (!pd->m_diffList.HasSignificantDiffs())
1039 // GetCurrentDiff() returns -1 if no diff selected
1040 int nDiff = pd->GetCurrentDiff();
1043 // Scroll to the first line of the currently selected diff
1044 SelectDiff(nDiff, true, false);
1048 // If cursor is inside diff, select that diff
1049 CPoint pos = GetCursorPos();
1050 nDiff = pd->m_diffList.LineToDiff(pos.y);
1051 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
1052 SelectDiff(nDiff, true, false);
1057 * @brief Called when "Current diff" item is updated
1059 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
1061 CMergeDoc *pd = GetDocument();
1062 CPoint pos = GetCursorPos();
1063 int nCurrentDiff = pd->GetCurrentDiff();
1064 if (nCurrentDiff == -1)
1066 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
1067 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
1070 pCmdUI->Enable(true);
1074 * @brief Copy selected text to clipboard
1076 void CMergeEditView::OnEditCopy()
1078 CMergeDoc * pDoc = GetDocument();
1079 CPoint ptSelStart, ptSelEnd;
1080 GetSelection(ptSelStart, ptSelEnd);
1083 if (ptSelStart == ptSelEnd)
1088 if (!m_bRectangularSelection)
1090 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
1092 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1093 ptSelEnd.y, ptSelEnd.x, text);
1096 GetTextWithoutEmptysInColumnSelection(text);
1098 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1102 * @brief Called when "Copy" item is updated
1104 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
1106 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
1110 * @brief Cut current selection to clipboard
1112 void CMergeEditView::OnEditCut()
1114 if (!QueryEditable())
1117 CPoint ptSelStart, ptSelEnd;
1118 CMergeDoc * pDoc = GetDocument();
1119 GetSelection(ptSelStart, ptSelEnd);
1122 if (ptSelStart == ptSelEnd)
1126 if (!m_bRectangularSelection)
1127 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1128 ptSelEnd.y, ptSelEnd.x, text);
1130 GetTextWithoutEmptysInColumnSelection(text);
1132 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1134 if (!m_bRectangularSelection)
1136 CPoint ptCursorPos = ptSelStart;
1137 ASSERT_VALIDTEXTPOS(ptCursorPos);
1138 SetAnchor(ptCursorPos);
1139 SetSelection(ptCursorPos, ptCursorPos);
1140 SetCursorPos(ptCursorPos);
1141 EnsureVisible(ptCursorPos);
1143 pDoc->m_ptBuf[m_nThisPane]->DeleteText(this, ptSelStart.y, ptSelStart.x, ptSelEnd.y,
1144 ptSelEnd.x, CE_ACTION_CUT);
1147 DeleteCurrentColumnSelection (CE_ACTION_CUT);
1149 m_pTextBuffer->SetModified(true);
1153 * @brief Called when "Cut" item is updated
1155 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
1157 if (QueryEditable())
1158 CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
1160 pCmdUI->Enable(false);
1164 * @brief Paste text from clipboard
1166 void CMergeEditView::OnEditPaste()
1168 if (!QueryEditable())
1171 CCrystalEditViewEx::Paste();
1172 m_pTextBuffer->SetModified(true);
1176 * @brief Called when "Paste" item is updated
1178 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
1180 if (QueryEditable())
1181 CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
1183 pCmdUI->Enable(false);
1187 * @brief Undo last action
1189 void CMergeEditView::OnEditUndo()
1191 CWaitCursor waitstatus;
1192 CMergeDoc* pDoc = GetDocument();
1193 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1196 if (!QueryEditable())
1199 GetParentFrame()->SetActiveView(this, true);
1200 if(CCrystalEditViewEx::DoEditUndo())
1203 pDoc->UpdateHeaderPath(m_nThisPane);
1204 pDoc->FlushAndRescan();
1207 m_pTextBuffer->GetRedoActionCode(nAction);
1208 if (nAction == CE_ACTION_MERGE)
1209 // select the diff so we may just merge it again
1215 tgt->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
1217 if (!pDoc->CanUndo())
1218 pDoc->SetAutoMerged(false);
1222 * @brief Called when "Undo" item is updated
1224 void CMergeEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
1226 CMergeDoc* pDoc = GetDocument();
1227 if (pDoc->curUndo!=pDoc->undoTgt.begin())
1229 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1230 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
1233 pCmdUI->Enable(false);
1237 * @brief Go to first diff
1239 * Called when user selects "First Difference"
1240 * @sa CMergeEditView::SelectDiff()
1242 void CMergeEditView::OnFirstdiff()
1244 CMergeDoc *pd = GetDocument();
1245 if (pd->m_diffList.HasSignificantDiffs())
1247 int nDiff = pd->m_diffList.FirstSignificantDiff();
1248 SelectDiff(nDiff, true, false);
1253 * @brief Update "First diff" UI items
1255 void CMergeEditView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1257 OnUpdatePrevdiff(pCmdUI);
1261 * @brief Go to last diff
1263 void CMergeEditView::OnLastdiff()
1265 CMergeDoc *pd = GetDocument();
1266 if (pd->m_diffList.HasSignificantDiffs())
1268 int nDiff = pd->m_diffList.LastSignificantDiff();
1269 SelectDiff(nDiff, true, false);
1274 * @brief Update "Last diff" UI items
1276 void CMergeEditView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1278 OnUpdateNextdiff(pCmdUI);
1282 * @brief Go to next diff and select it.
1284 * Finds and selects next difference. There are several cases:
1285 * - if there is selected difference, and that difference is visible
1286 * on screen, next found difference is selected.
1287 * - if there is selected difference but it is not visible, next
1288 * difference from cursor position is selected. This is what user
1289 * expects to happen and is natural thing to do. Also reduces
1290 * needless scrolling.
1291 * - if there is no selected difference, next difference from cursor
1292 * position is selected.
1294 void CMergeEditView::OnNextdiff()
1296 CMergeDoc *pd = GetDocument();
1297 int cnt = pd->m_ptBuf[0]->GetLineCount();
1301 // Returns -1 if no diff selected
1303 int curDiff = pd->GetCurrentDiff();
1307 if (!IsDiffVisible(curDiff))
1309 // Selected difference not visible, select next from cursor
1310 int line = GetCursorPos().y;
1311 // Make sure we aren't in the first line of the diff
1313 if (!IsValidTextPosY(CPoint(0, line)))
1315 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1319 // Find out if there is a following significant diff
1320 if (curDiff < pd->m_diffList.GetSize() - 1)
1322 nextDiff = pd->m_diffList.NextSignificantDiff(curDiff);
1328 // We don't have a selected difference,
1329 // but cursor can be inside inactive diff
1330 int line = GetCursorPos().y;
1331 if (!IsValidTextPosY(CPoint(0, line)))
1333 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1336 int lastDiff = pd->m_diffList.LastSignificantDiff();
1337 if (nextDiff >= 0 && nextDiff <= lastDiff)
1338 SelectDiff(nextDiff, true, false);
1339 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1341 if (pDirDoc->MoveableToNextDiff())
1342 pDirDoc->MoveToNextDiff(pd);
1347 * @brief Update "Next diff" UI items
1349 void CMergeEditView::OnUpdateNextdiff(CCmdUI* pCmdUI)
1351 CMergeDoc *pd = GetDocument();
1352 const DIFFRANGE * dfi = pd->m_diffList.LastSignificantDiffRange();
1357 // There aren't any significant differences
1362 // Enable if the beginning of the last significant difference is after caret
1363 enabled = (GetCursorPos().y < (long)dfi->dbegin);
1366 if (!enabled && pd->GetDirDoc())
1367 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1369 pCmdUI->Enable(enabled);
1373 * @brief Go to previous diff and select it.
1375 * Finds and selects previous difference. There are several cases:
1376 * - if there is selected difference, and that difference is visible
1377 * on screen, previous found difference is selected.
1378 * - if there is selected difference but it is not visible, previous
1379 * difference from cursor position is selected. This is what user
1380 * expects to happen and is natural thing to do. Also reduces
1381 * needless scrolling.
1382 * - if there is no selected difference, previous difference from cursor
1383 * position is selected.
1385 void CMergeEditView::OnPrevdiff()
1387 CMergeDoc *pd = GetDocument();
1388 int cnt = pd->m_ptBuf[0]->GetLineCount();
1392 // GetCurrentDiff() returns -1 if no diff selected
1394 int curDiff = pd->GetCurrentDiff();
1398 if (!IsDiffVisible(curDiff))
1400 // Selected difference not visible, select previous from cursor
1401 int line = GetCursorPos().y;
1402 // Make sure we aren't in the last line of the diff
1404 if (!IsValidTextPosY(CPoint(0, line)))
1406 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1410 // Find out if there is a preceding significant diff
1413 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1419 // We don't have a selected difference,
1420 // but cursor can be inside inactive diff
1421 int line = GetCursorPos().y;
1422 if (!IsValidTextPosY(CPoint(0, line)))
1424 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1427 int firstDiff = pd->m_diffList.FirstSignificantDiff();
1428 if (prevDiff >= 0 && prevDiff >= firstDiff)
1429 SelectDiff(prevDiff, true, false);
1430 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1432 if (pDirDoc->MoveableToPrevDiff())
1433 pDirDoc->MoveToPrevDiff(pd);
1438 * @brief Update "Previous diff" UI items
1440 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1442 CMergeDoc *pd = GetDocument();
1443 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1448 // There aren't any significant differences
1453 // Enable if the end of the first significant difference is before caret
1454 enabled = (GetCursorPos().y > (long)dfi->dend);
1457 if (!enabled && pd->GetDirDoc())
1458 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1460 pCmdUI->Enable(enabled);
1463 void CMergeEditView::OnNextConflict()
1465 OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1469 * @brief Update "Next Conflict" UI items
1471 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1473 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1476 void CMergeEditView::OnPrevConflict()
1478 OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1482 * @brief Update "Prev Conflict" UI items
1484 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1486 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1490 * @brief Go to next 3-way diff and select it.
1492 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1494 CMergeDoc *pd = GetDocument();
1495 int cnt = pd->m_ptBuf[0]->GetLineCount();
1499 // Returns -1 if no diff selected
1500 int curDiff = pd->GetCurrentDiff();
1504 int nextDiff = curDiff;
1505 if (!IsDiffVisible(curDiff))
1507 // Selected difference not visible, select next from cursor
1508 int line = GetCursorPos().y;
1509 // Make sure we aren't in the first line of the diff
1511 if (!IsValidTextPosY(CPoint(0, line)))
1513 nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1517 // Find out if there is a following significant diff
1518 if (curDiff < pd->m_diffList.GetSize() - 1)
1520 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1526 // nextDiff is the next one if there is one, else it is the one we're on
1527 SelectDiff(nextDiff, true, false);
1531 // We don't have a selected difference,
1532 // but cursor can be inside inactive diff
1533 int line = GetCursorPos().y;
1534 if (!IsValidTextPosY(CPoint(0, line)))
1536 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1538 SelectDiff(curDiff, true, false);
1543 * @brief Update "Next 3-way diff" UI items
1545 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1547 CMergeDoc *pd = GetDocument();
1549 if (pd->m_nBuffers < 3)
1551 pCmdUI->Enable(false);
1555 const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1559 // There aren't any significant differences
1560 pCmdUI->Enable(false);
1564 // Enable if the beginning of the last significant difference is after caret
1565 CPoint pos = GetCursorPos();
1566 pCmdUI->Enable(pos.y < (long)dfi->dbegin);
1571 * @brief Go to previous 3-way diff and select it.
1573 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1575 CMergeDoc *pd = GetDocument();
1577 int cnt = pd->m_ptBuf[0]->GetLineCount();
1581 // GetCurrentDiff() returns -1 if no diff selected
1582 int curDiff = pd->GetCurrentDiff();
1586 int prevDiff = curDiff;
1587 if (!IsDiffVisible(curDiff))
1589 // Selected difference not visible, select previous from cursor
1590 int line = GetCursorPos().y;
1591 // Make sure we aren't in the last line of the diff
1593 if (!IsValidTextPosY(CPoint(0, line)))
1595 prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1599 // Find out if there is a preceding significant diff
1602 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1608 // prevDiff is the preceding one if there is one, else it is the one we're on
1609 SelectDiff(prevDiff, true, false);
1613 // We don't have a selected difference,
1614 // but cursor can be inside inactive diff
1615 int line = GetCursorPos().y;
1616 if (!IsValidTextPosY(CPoint(0, line)))
1618 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1620 SelectDiff(curDiff, true, false);
1625 * @brief Update "Previous diff X and Y" UI items
1627 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1629 CMergeDoc *pd = GetDocument();
1631 if (pd->m_nBuffers < 3)
1633 pCmdUI->Enable(false);
1637 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1641 // There aren't any significant differences
1642 pCmdUI->Enable(false);
1646 // Enable if the end of the first significant difference is before caret
1647 CPoint pos = GetCursorPos();
1648 pCmdUI->Enable(pos.y > (long)dfi->dend);
1652 void CMergeEditView::OnNextdiffLM()
1654 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1657 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1659 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1662 void CMergeEditView::OnNextdiffLR()
1664 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1667 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1669 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1672 void CMergeEditView::OnNextdiffMR()
1674 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1677 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1679 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1682 void CMergeEditView::OnNextdiffLO()
1684 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1687 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1689 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1692 void CMergeEditView::OnNextdiffMO()
1694 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1697 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1699 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1702 void CMergeEditView::OnNextdiffRO()
1704 OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1707 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1709 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1712 void CMergeEditView::OnPrevdiffLM()
1714 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1717 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1719 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1722 void CMergeEditView::OnPrevdiffLR()
1724 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1727 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1729 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1732 void CMergeEditView::OnPrevdiffMR()
1734 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1737 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1739 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1742 void CMergeEditView::OnPrevdiffLO()
1744 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1747 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1749 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1752 void CMergeEditView::OnPrevdiffMO()
1754 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1757 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1759 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1762 void CMergeEditView::OnPrevdiffRO()
1764 OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1767 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1769 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1773 * @brief Clear selection
1775 void CMergeEditView::SelectNone()
1777 SetSelection (GetCursorPos(), GetCursorPos());
1782 * @brief Check if line is inside currently selected diff
1783 * @param [in] nLine 0-based linenumber in view
1784 * @sa CMergeDoc::GetCurrentDiff()
1785 * @sa CMergeDoc::LineInDiff()
1787 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1789 // Check validity of nLine
1792 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1793 int nLineCount = LocateTextBuffer()->GetLineCount();
1794 if (nLine >= nLineCount)
1795 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1798 const CMergeDoc *pd = GetDocument();
1799 int curDiff = pd->GetCurrentDiff();
1802 return pd->m_diffList.LineInDiff(nLine, curDiff);
1806 * @brief Called when mouse left-button double-clicked
1808 * Double-clicking mouse inside diff selects that diff
1810 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1812 CMergeDoc *pd = GetDocument();
1813 CPoint pos = GetCursorPos();
1815 int diff = pd->m_diffList.LineToDiff(pos.y);
1816 if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1817 SelectDiff(diff, false, false);
1819 CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1823 * @brief Called when mouse left button is released.
1825 * If button is released outside diffs, current diff
1828 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1830 CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1831 DeselectDiffIfCursorNotInCurrentDiff();
1835 * @brief Called when mouse right button is pressed.
1837 * If right button is pressed outside diffs, current diff
1840 void CMergeEditView::OnRButtonDown(UINT nFlags, CPoint point)
1842 CCrystalEditViewEx::OnRButtonDown(nFlags, point);
1843 DeselectDiffIfCursorNotInCurrentDiff();
1846 void CMergeEditView::OnX2Y(int srcPane, int dstPane)
1848 // Check that right side is not readonly
1849 if (IsReadOnly(dstPane))
1852 CMergeDoc *pDoc = GetDocument();
1853 int currentDiff = pDoc->GetCurrentDiff();
1855 if (currentDiff == -1)
1858 // If cursor is inside diff get number of that diff
1859 if (m_bCurrentLineIsDiff)
1861 CPoint pt = GetCursorPos();
1862 currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1866 CPoint ptStart, ptEnd;
1867 GetSelection(ptStart, ptEnd);
1868 if (IsSelection() || pDoc->EqualCurrentWordDiff(srcPane, ptStart, ptEnd))
1870 if (!m_bRectangularSelection)
1872 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
1873 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1874 if (firstDiff != -1 && lastDiff != -1)
1876 CWaitCursor waitstatus;
1878 // Setting CopyFullLine (OPT_COPY_FULL_LINE)
1879 // restore old copy behaviour (always copy "full line" instead of "selected text only"), with a hidden option
1880 if (GetOptionsMgr()->GetBool(OPT_COPY_FULL_LINE))
1882 // old behaviour: copy full line
1883 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
1887 // new behaviour: copy selected text only
1888 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1894 CWaitCursor waitstatus;
1895 auto wordDiffs = GetColumnSelectedWordDiffIndice();
1897 std::for_each(wordDiffs.rbegin(), wordDiffs.rend(), [&](auto& it) {
1898 pDoc->WordListCopy(srcPane, dstPane, it.first, it.second[0], it.second[it.second.size() - 1], &it.second, i != 0, i == 0);
1903 else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
1905 CWaitCursor waitstatus;
1906 pDoc->ListCopy(srcPane, dstPane, currentDiff);
1910 void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
1912 // Check that right side is not readonly
1913 if (!IsReadOnly(dstPane))
1915 // If one or more diffs inside selection OR
1916 // there is an active diff OR
1917 // cursor is inside diff
1918 CPoint ptStart, ptEnd;
1919 GetSelection(ptStart, ptEnd);
1920 if (IsSelection() || GetDocument()->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
1922 if (m_bCurrentLineIsDiff || (m_pTextBuffer->GetLineFlags(m_ptSelStart.y) & LF_NONTRIVIAL_DIFF) != 0)
1924 pCmdUI->Enable(true);
1928 int firstDiff, lastDiff;
1929 GetFullySelectedDiffs(firstDiff, lastDiff);
1931 pCmdUI->Enable(firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff));
1936 const int currDiff = GetDocument()->GetCurrentDiff();
1937 pCmdUI->Enable(m_bCurrentLineIsDiff || (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff)));
1941 pCmdUI->Enable(false);
1945 * @brief Copy diff from left pane to right pane
1947 * Difference is copied from left to right when
1948 * - difference is selected
1949 * - difference is inside selection (allows merging multiple differences).
1950 * - cursor is inside diff
1952 * If there is selected diff outside selection, we copy selected
1955 void CMergeEditView::OnL2r()
1957 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1958 int srcPane = dstPane - 1;
1959 OnX2Y(srcPane, dstPane);
1963 * @brief Called when "Copy to left" item is updated
1965 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
1967 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
1971 * @brief Copy diff from right pane to left pane
1973 * Difference is copied from left to right when
1974 * - difference is selected
1975 * - difference is inside selection (allows merging multiple differences).
1976 * - cursor is inside diff
1978 * If there is selected diff outside selection, we copy selected
1981 void CMergeEditView::OnR2l()
1983 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
1984 int srcPane = dstPane + 1;
1985 OnX2Y(srcPane, dstPane);
1989 * @brief Called when "Copy to right" item is updated
1991 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
1993 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
1996 void CMergeEditView::OnCopyFromLeft()
1998 int dstPane = m_nThisPane;
1999 int srcPane = dstPane - 1;
2002 OnX2Y(srcPane, dstPane);
2005 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
2007 int dstPane = m_nThisPane;
2008 int srcPane = dstPane - 1;
2010 pCmdUI->Enable(false);
2012 OnUpdateX2Y(dstPane, pCmdUI);
2015 void CMergeEditView::OnCopyFromRight()
2017 int dstPane = m_nThisPane;
2018 int srcPane = dstPane + 1;
2019 if (srcPane >= GetDocument()->m_nBuffers)
2021 OnX2Y(srcPane, dstPane);
2024 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
2026 int dstPane = m_nThisPane;
2027 int srcPane = dstPane + 1;
2028 if (srcPane >= GetDocument()->m_nBuffers)
2029 pCmdUI->Enable(false);
2031 OnUpdateX2Y(dstPane, pCmdUI);
2035 * @brief Copy all diffs from right pane to left pane
2037 void CMergeEditView::OnAllLeft()
2039 // Check that left side is not readonly
2040 int srcPane = m_nThisPane > 0 ? m_nThisPane : 1;
2041 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2042 if (IsReadOnly(dstPane))
2044 CWaitCursor waitstatus;
2046 GetDocument()->CopyAllList(srcPane, dstPane);
2050 * @brief Called when "Copy all to left" item is updated
2052 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
2054 // Check that left side is not readonly
2055 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2056 if (!IsReadOnly(dstPane))
2057 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2059 pCmdUI->Enable(false);
2063 * @brief Copy all diffs from left pane to right pane
2065 void CMergeEditView::OnAllRight()
2067 // Check that right side is not readonly
2068 int srcPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane : m_nThisPane - 1;
2069 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2070 if (IsReadOnly(dstPane))
2073 CWaitCursor waitstatus;
2075 GetDocument()->CopyAllList(srcPane, dstPane);
2079 * @brief Called when "Copy all to right" item is updated
2081 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
2083 // Check that right side is not readonly
2084 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2085 if (!IsReadOnly(dstPane))
2086 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2088 pCmdUI->Enable(false);
2092 * @brief Do Auto merge
2094 void CMergeEditView::OnAutoMerge()
2096 // Check current pane is not readonly
2097 if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || !QueryEditable())
2100 CWaitCursor waitstatus;
2102 GetDocument()->DoAutoMerge(m_nThisPane);
2106 * @brief Called when "Auto Merge" item is updated
2108 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
2110 pCmdUI->Enable(GetDocument()->m_nBuffers == 3 &&
2111 !GetDocument()->IsModified() &&
2112 !GetDocument()->GetAutoMerged() &&
2117 * @brief Add synchronization point
2119 void CMergeEditView::OnAddSyncPoint()
2121 GetDocument()->AddSyncPoint();
2125 * @brief Clear synchronization points
2127 void CMergeEditView::OnClearSyncPoints()
2129 GetDocument()->ClearSyncPoints();
2133 * @brief Called when "Clear Synchronization Points" item is updated
2135 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
2137 pCmdUI->Enable(GetDocument()->HasSyncPoints());
2141 * @brief This function is called before other edit events.
2142 * @param [in] nAction Edit operation to do
2143 * @param [in] pszText Text to insert, delete etc
2144 * @sa CCrystalEditView::OnEditOperation()
2145 * @todo More edit-events for rescan delaying?
2147 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
2149 if (!QueryEditable())
2151 // We must not arrive here, and assert helps detect troubles
2156 CMergeDoc* pDoc = GetDocument();
2157 pDoc->SetEditedAfterRescan(m_nThisPane);
2159 // simple hook for multiplex undo operations
2160 // deleted by jtuc 2003-06-28
2161 // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
2162 /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
2164 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
2165 pDoc->undoTgt.push_back(this);
2166 pDoc->curUndo = pDoc->undoTgt.end();
2169 // perform original function
2170 CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
2172 // augment with additional operations
2174 // Change header to inform about changed doc
2175 pDoc->UpdateHeaderPath(m_nThisPane);
2177 // If automatic rescan enabled, rescan after edit events
2178 if (m_bAutomaticRescan)
2180 // keep document up to date
2181 // (Re)start timer to rescan only when user edits text
2182 // If timer starting fails, rescan immediately
2183 if (nAction == CE_ACTION_TYPING ||
2184 nAction == CE_ACTION_REPLACE ||
2185 nAction == CE_ACTION_BACKSPACE ||
2186 nAction == CE_ACTION_INDENT ||
2187 nAction == CE_ACTION_PASTE ||
2188 nAction == CE_ACTION_DELSEL ||
2189 nAction == CE_ACTION_DELETE ||
2190 nAction == CE_ACTION_CUT)
2192 if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
2193 pDoc->FlushAndRescan();
2196 pDoc->FlushAndRescan();
2202 // Update other pane for sync line.
2203 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
2205 if (nPane == m_nThisPane)
2207 CCrystalEditView *pView = GetGroupView(nPane);
2208 if (pView != nullptr)
2209 pView->Invalidate();
2216 * @brief Redo last action
2218 void CMergeEditView::OnEditRedo()
2220 CWaitCursor waitstatus;
2221 CMergeDoc* pDoc = GetDocument();
2222 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2225 if (!QueryEditable())
2228 GetParentFrame()->SetActiveView(this, true);
2229 if(CCrystalEditViewEx::DoEditRedo())
2232 pDoc->UpdateHeaderPath(m_nThisPane);
2233 pDoc->FlushAndRescan();
2238 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2243 * @brief Called when "Redo" item is updated
2245 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2247 CMergeDoc* pDoc = GetDocument();
2248 if (pDoc->curUndo!=pDoc->undoTgt.end())
2250 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2251 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2254 pCmdUI->Enable(false);
2257 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2259 CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2263 * @brief Scrolls to current diff and/or selects diff text
2264 * @param [in] bScroll If true scroll diff to view
2265 * @param [in] bSelectText If true select diff text
2266 * @note If bScroll and bSelectText are false, this does nothing!
2267 * @todo This shouldn't be called when no diff is selected, so
2268 * somebody could try to ASSERT(nDiff > -1)...
2270 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2272 CMergeDoc *pd = GetDocument();
2273 const int nDiff = pd->GetCurrentDiff();
2275 // Try to trap some errors
2276 if (nDiff >= pd->m_diffList.GetSize())
2277 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2278 nDiff, pd->m_diffList.GetSize());
2280 if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2282 CPoint ptStart, ptEnd;
2284 pd->m_diffList.GetDiff(nDiff, curDiff);
2287 ptStart.y = curDiff.dbegin;
2289 ptEnd.y = curDiff.dend;
2291 if (bScroll && !m_bDetailView)
2293 if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2295 // Difference is not visible, scroll it so that max amount of
2296 // scrolling is done while keeping the diff in screen. So if
2297 // scrolling is downwards, scroll the diff to as up in screen
2298 // as possible. This usually brings next diff to the screen
2299 // and we don't need to scroll into it.
2300 int nLine = GetSubLineIndex(ptStart.y);
2301 if (nLine > CONTEXT_LINES_ABOVE)
2303 nLine -= CONTEXT_LINES_ABOVE;
2305 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2306 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2308 if (nPane != m_nThisPane)
2309 GetGroupView(nPane)->ScrollToSubLine(nLine);
2313 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
2314 CPoint pt = worddiffs.size() > 0 ?
2315 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
2317 GetGroupView(m_nThisPane)->SetCursorPos(pt);
2318 GetGroupView(m_nThisPane)->SetAnchor(pt);
2319 GetGroupView(m_nThisPane)->SetSelection(pt, pt);
2320 GetGroupView(m_nThisPane)->EnsureVisible(pt);
2321 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2323 if (nPane != m_nThisPane)
2325 if (worddiffs.size() > 0)
2327 pt.x = worddiffs[0].begin[nPane];
2328 pt.y = worddiffs[0].beginline[nPane];
2330 GetGroupView(nPane)->SetCursorPos(pt);
2331 GetGroupView(nPane)->SetAnchor(pt);
2332 GetGroupView(nPane)->SetSelection(pt, pt);
2339 ptEnd.x = GetLineLength(ptEnd.y);
2340 SetSelection(ptStart, ptEnd);
2349 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2351 // Maybe we want theApp::OnIdle to proceed before processing a timer message
2352 // ...but for this the queue must be empty
2353 // The timer message is a low priority message but the queue is maybe not yet empty
2354 // So we set a flag, wait for OnIdle to proceed, then come back here...
2355 // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2356 // not with SetTimer so there is no delay)
2358 // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2360 // IDLE_TIMER is the false timer used to come back here after OnIdle
2361 // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2362 // (one normal timer = one flag = one command)
2364 if (nIDEvent == IDT_RESCAN)
2366 KillTimer(IDT_RESCAN);
2367 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2368 // notify the app to come back after OnIdle
2369 theApp.SetNeedIdleTimer();
2372 if (nIDEvent == IDLE_TIMER)
2374 // not a real timer, just come back after OnIdle
2375 // look to flags to know what to do
2376 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2377 GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2378 fTimerWaitingForIdle = 0;
2381 CCrystalEditViewEx::OnTimer(nIDEvent);
2385 * @brief Returns if buffer is read-only
2386 * @note This has no any relation to file being read-only!
2388 bool CMergeEditView::IsReadOnly(int pane) const
2390 return m_bDetailView ? true : (GetDocument()->m_ptBuf[pane]->GetReadOnly() != false);
2394 * @brief Called when "Save left (as...)" item is updated
2396 void CMergeEditView::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
2398 CMergeDoc *pd = GetDocument();
2399 pCmdUI->Enable(!IsReadOnly(0) && pd->m_ptBuf[0]->IsModified());
2403 * @brief Called when "Save middle (as...)" item is updated
2405 void CMergeEditView::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
2407 CMergeDoc *pd = GetDocument();
2408 pCmdUI->Enable(pd->m_nBuffers == 3 && !IsReadOnly(1) && pd->m_ptBuf[1]->IsModified());
2412 * @brief Called when "Save right (as...)" item is updated
2414 void CMergeEditView::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
2416 CMergeDoc *pd = GetDocument();
2417 pCmdUI->Enable(!IsReadOnly(pd->m_nBuffers - 1) && pd->m_ptBuf[pd->m_nBuffers - 1]->IsModified());
2421 * @brief Refresh display using text-buffers
2422 * @note This DOES NOT reload files!
2424 void CMergeEditView::OnRefresh()
2426 CMergeDoc *pd = GetDocument();
2427 ASSERT(pd != nullptr);
2428 pd->FlushAndRescan(true);
2432 * @brief Handle some keys when in merging mode
2434 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2436 bool bHandled = false;
2438 // Allow default text selection when SHIFT pressed
2439 if (::GetAsyncKeyState(VK_SHIFT))
2442 // Allow default editor functions when CTRL pressed
2443 if (::GetAsyncKeyState(VK_CONTROL))
2446 // If we are in merging mode (merge with cursor keys)
2447 // handle some keys here
2448 switch (pMsg->wParam)
2475 * @brief Called before messages are translated.
2477 * Checks if ESC key was pressed, saves and closes doc.
2478 * Also if in merge mode traps cursor keys.
2480 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2482 if (pMsg->message == WM_KEYDOWN)
2484 // If we are in merging mode (merge with cursor keys)
2485 // handle some keys here
2486 if (theApp.GetMergingMode())
2488 bool bHandled = MergeModeKeyDown(pMsg);
2493 // Close window if user has allowed it from options
2494 if (pMsg->wParam == VK_ESCAPE)
2496 int nCloseWithEsc = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
2497 if (nCloseWithEsc != 0)
2498 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2503 return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2507 * @brief Called when "Save" item is updated
2509 void CMergeEditView::OnUpdateFileSave(CCmdUI* pCmdUI)
2511 CMergeDoc *pd = GetDocument();
2513 bool bModified = false;
2514 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2516 if (pd->m_ptBuf[nPane]->IsModified())
2519 pCmdUI->Enable(bModified);
2523 * @brief Enable/disable left buffer read-only
2525 void CMergeEditView::OnLeftReadOnly()
2527 CMergeDoc *pd = GetDocument();
2528 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2529 pd->m_ptBuf[0]->SetReadOnly(!bReadOnly);
2533 * @brief Called when "Left read-only" item is updated
2535 void CMergeEditView::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
2537 CMergeDoc *pd = GetDocument();
2538 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2539 pCmdUI->Enable(true);
2540 pCmdUI->SetCheck(bReadOnly);
2544 * @brief Enable/disable middle buffer read-only
2546 void CMergeEditView::OnMiddleReadOnly()
2548 CMergeDoc *pd = GetDocument();
2549 if (pd->m_nBuffers == 3)
2551 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2552 pd->m_ptBuf[1]->SetReadOnly(!bReadOnly);
2557 * @brief Called when "Middle read-only" item is updated
2559 void CMergeEditView::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
2561 CMergeDoc *pd = GetDocument();
2562 if (pd->m_nBuffers < 3)
2564 pCmdUI->Enable(false);
2568 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2569 pCmdUI->Enable(true);
2570 pCmdUI->SetCheck(bReadOnly);
2575 * @brief Enable/disable right buffer read-only
2577 void CMergeEditView::OnRightReadOnly()
2579 CMergeDoc *pd = GetDocument();
2580 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2581 pd->m_ptBuf[pd->m_nBuffers - 1]->SetReadOnly(!bReadOnly);
2585 * @brief Called when "Left read-only" item is updated
2587 void CMergeEditView::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
2589 CMergeDoc *pd = GetDocument();
2590 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2591 pCmdUI->Enable(true);
2592 pCmdUI->SetCheck(bReadOnly);
2595 /// Store interface we use to display status line info
2596 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2598 ASSERT(m_piMergeEditStatus == nullptr);
2599 m_piMergeEditStatus = piMergeEditStatus;
2603 * @brief Update status bar contents.
2605 void CMergeEditView::UpdateStatusbar()
2611 * @brief Update statusbar info, Override from CCrystalTextView
2612 * @note we tab-expand column, but we don't tab-expand char count,
2613 * since we want to show how many chars there are and tab is just one
2614 * character although it expands to several spaces.
2616 void CMergeEditView::OnUpdateCaret()
2618 if (m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2621 CPoint cursorPos = GetCursorPos();
2622 int nScreenLine = cursorPos.y;
2623 const int nRealLine = ComputeRealLine(nScreenLine);
2630 DWORD dwLineFlags = 0;
2632 dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2633 // Is this a ghost line ?
2634 if (dwLineFlags & LF_GHOST)
2636 // Ghost lines display eg "Line 12-13"
2637 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2638 sEol = _T("hidden");
2642 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2643 sLine.Format(_T("%d"), nRealLine+1);
2644 curChar = cursorPos.x + 1;
2645 chars = GetLineLength(nScreenLine);
2646 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2647 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2649 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2650 GetDocument()->IsMixedEOL(m_nThisPane))
2652 sEol = GetTextBufferEol(nScreenLine);
2655 sEol = _T("hidden");
2657 m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2658 curChar, chars, sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2660 // Is cursor inside difference?
2661 if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2662 m_bCurrentLineIsDiff = true;
2664 m_bCurrentLineIsDiff = false;
2666 CWnd* pWnd = GetFocus();
2667 if (!m_bDetailView || (pWnd && pWnd->m_hWnd == this->m_hWnd))
2668 UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2671 * @brief Select linedifference in the current line.
2673 * Select line difference in current line. Selection type
2674 * is choosed by highlight type.
2676 template<bool reversed>
2677 void CMergeEditView::OnSelectLineDiff()
2679 // Pass this to the document, to compare this file to other
2680 GetDocument()->Showlinediff(this, reversed);
2683 /// Enable select difference menuitem if current line is inside difference.
2684 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2686 pCmdUI->Enable(!GetDocument()->IsEditedAfterRescan());
2690 * @brief Enable/disable Replace-menuitem
2692 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2694 CMergeDoc *pd = GetDocument();
2695 bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2697 pCmdUI->Enable(!bReadOnly);
2701 * @brief Update readonly statusbaritem
2703 void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
2705 bool bRO = GetDocument()->m_ptBuf[pCmdUI->m_nID - ID_STATUS_PANE0FILE_RO]->GetReadOnly();
2706 pCmdUI->Enable(bRO);
2710 * @brief Create the dynamic submenu for scripts
2712 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
2715 std::vector<String> functionNamesList = FileTransform::GetFreeFunctionsInScripts(L"EDITOR_SCRIPT");
2718 size_t i = GetMenuItemCount(hMenu);
2720 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2722 if (functionNamesList.size() == 0)
2724 // no script : create a <empty> entry
2725 AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, _("< Empty >").c_str());
2729 // or fill in the submenu with the scripts names
2730 int ID = ID_SCRIPT_FIRST; // first ID in menu
2731 for (i = 0 ; i < functionNamesList.size() ; i++, ID++)
2732 AppendMenu(hMenu, MF_STRING, ID, functionNamesList[i].c_str());
2734 functionNamesList.clear();
2737 if (!plugin::IsWindowsScriptThere())
2738 AppendMenu(hMenu, MF_STRING, ID_NO_SCT_SCRIPTS, _("WSH not found - .sct scripts disabled").c_str());
2744 * @brief Create the dynamic submenu for prediffers
2746 * @note The plugins are grouped in (suggested) and (not suggested)
2747 * The IDs follow the order of GetAvailableScripts
2749 * suggested 0 ID_1ST + 0
2750 * suggested 1 ID_1ST + 2
2751 * suggested 2 ID_1ST + 5
2752 * not suggested 0 ID_1ST + 1
2753 * not suggested 1 ID_1ST + 3
2754 * not suggested 2 ID_1ST + 4
2756 HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
2759 int i = GetMenuItemCount(hMenu);
2761 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2763 CMergeDoc *pd = GetDocument();
2764 ASSERT(pd != nullptr);
2767 AppendMenu(hMenu, MF_STRING, ID_NO_PREDIFFER, _("No prediffer (normal)").c_str());
2769 // get the scriptlet files
2770 PluginArray * piScriptArray =
2771 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
2772 PluginArray * piScriptArray2 =
2773 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
2775 // build the menu : first part, suggested plugins
2777 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2778 AppendMenu(hMenu, MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2780 int ID = ID_PREDIFFERS_FIRST; // first ID in menu
2782 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2784 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2785 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2788 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2790 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2792 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2793 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2796 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2799 // build the menu : second part, others plugins
2801 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2802 AppendMenu(hMenu, MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2804 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2805 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2807 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2808 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2811 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2813 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2815 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2816 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2819 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2822 // compute the m_CurrentPredifferID (to set the radio button)
2823 PrediffingInfo prediffer;
2824 pd->GetPrediffer(&prediffer);
2826 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
2827 m_CurrentPredifferID = 0;
2828 else if (prediffer.m_PluginName.empty())
2829 m_CurrentPredifferID = ID_NO_PREDIFFER;
2832 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2833 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2835 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2836 if (prediffer.m_PluginName == plugin->m_name)
2837 m_CurrentPredifferID = ID;
2840 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2842 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2843 if (prediffer.m_PluginName == plugin->m_name)
2844 m_CurrentPredifferID = ID;
2852 * @brief Offer a context menu built with scriptlet/ActiveX functions
2854 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
2856 // Create the menu and populate it with the available functions
2858 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
2860 // Remove copying item copying from active side
2861 if (m_nThisPane == 0) // left?
2863 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2864 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2866 if (m_nThisPane == GetDocument()->m_nBuffers - 1)
2868 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2869 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2872 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
2873 theApp.TranslateMenu(menu.m_hMenu);
2875 BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
2876 ASSERT(pSub != nullptr);
2878 // Context menu opened using keyboard has no coordinates
2879 if (point.x == -1 && point.y == -1)
2882 GetClientRect(rect);
2883 ClientToScreen(rect);
2885 point = rect.TopLeft();
2889 pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
2890 point.x, point.y, AfxGetMainWnd());
2895 * @brief Update EOL mode in status bar
2897 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
2899 GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
2903 * @brief Change EOL mode and unify all the lines EOL to this new mode
2905 void CMergeEditView::OnConvertEolTo(UINT nID )
2907 CRLFSTYLE nStyle = CRLF_STYLE_AUTOMATIC;;
2911 nStyle = CRLF_STYLE_DOS;
2913 case ID_EOL_TO_UNIX:
2914 nStyle = CRLF_STYLE_UNIX;
2917 nStyle = CRLF_STYLE_MAC;
2921 _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
2924 m_pTextBuffer->SetCRLFMode(nStyle);
2926 // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
2927 if (m_pTextBuffer->applyEOLMode())
2929 CMergeDoc *pd = GetDocument();
2930 ASSERT(pd != nullptr);
2931 pd->UpdateHeaderPath(m_nThisPane);
2932 pd->FlushAndRescan(true);
2937 * @brief allow convert to entries in file submenu
2939 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
2941 int nStyle = CRLF_STYLE_AUTOMATIC;
2942 switch (pCmdUI->m_nID)
2945 nStyle = CRLF_STYLE_DOS;
2947 case ID_EOL_TO_UNIX:
2948 nStyle = CRLF_STYLE_UNIX;
2951 nStyle = CRLF_STYLE_MAC;
2955 _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
2959 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2960 GetDocument()->IsMixedEOL(m_nThisPane) ||
2961 nStyle != m_pTextBuffer->GetCRLFMode())
2963 pCmdUI->SetRadio(false);
2965 // Don't allow selecting other EOL style for protected pane
2966 if (!QueryEditable())
2967 pCmdUI->Enable(false);
2970 pCmdUI->SetRadio(true);
2974 * @brief Copy diff from left to right and advance to next diff
2976 void CMergeEditView::OnL2RNext()
2979 if (IsCursorInDiff()) // for 3-way file compare
2985 * @brief Update "Copy right and advance" UI item
2987 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
2989 OnUpdateL2r(pCmdUI);
2993 * @brief Copy diff from right to left and advance to next diff
2995 void CMergeEditView::OnR2LNext()
2998 if (IsCursorInDiff()) // for 3-way file compare
3004 * @brief Update "Copy left and advance" UI item
3006 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
3008 OnUpdateR2l(pCmdUI);
3012 * @brief Change active pane in MergeView.
3013 * Changes active pane and makes sure cursor position is kept in
3014 * screen. Currently we put cursor in same line than in original
3015 * active pane but we could be smarter too? Maybe update cursor
3016 * only when it is not visible in new pane?
3018 void CMergeEditView::OnChangePane()
3020 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3021 CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
3022 CMergeDoc *pDoc = GetDocument();
3023 bool bFound = false;
3024 CMergeEditView *pNextActiveView = nullptr;
3025 std::vector<CMergeEditView *> list = pDoc->GetViewList();
3026 list.insert(list.end(), list.begin(), list.end());
3027 for (auto& pView : list)
3029 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
3031 pNextActiveView = pView;
3037 GetParentFrame()->SetActiveView(pNextActiveView);
3038 CPoint ptCursor = pWnd->GetCursorPos();
3040 if (ptCursor.y >= pNextActiveView->GetLineCount())
3041 ptCursor.y = pNextActiveView->GetLineCount() - 1;
3042 pNextActiveView->SetCursorPos(ptCursor);
3043 pNextActiveView->SetAnchor(ptCursor);
3044 pNextActiveView->SetSelection(ptCursor, ptCursor);
3048 * @brief Show "Go To" dialog and scroll views to line or diff.
3050 * Before dialog is opened, current line and file is determined
3052 * @note Conversions needed between apparent and real lines
3054 void CMergeEditView::OnWMGoto()
3057 CMergeDoc *pDoc = GetDocument();
3058 CPoint pos = GetCursorPos();
3062 nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
3063 int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
3064 nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
3066 // Set active file and current line selected in dialog
3067 dlg.m_strParam = strutils::to_str(nRealLine + 1);
3068 dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
3069 dlg.m_nGotoWhat = 0;
3071 if (dlg.DoModal() == IDOK)
3073 CMergeDoc * pDoc1 = GetDocument();
3074 CMergeEditView * pCurrentView = nullptr;
3077 pCurrentView = GetGroupView(m_nThisPane);
3080 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
3082 if (dlg.m_nGotoWhat == 0)
3084 int nRealLine1 = num;
3087 if (nRealLine1 > nLastLine)
3088 nRealLine1 = nLastLine;
3090 GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile);
3097 if (diff >= pDoc1->m_diffList.GetSize())
3098 diff = pDoc1->m_diffList.GetSize();
3100 pCurrentView->SelectDiff(diff, true, false);
3105 void CMergeEditView::OnShellMenu()
3107 CFrameWnd *pFrame = GetTopLevelFrame();
3108 ASSERT(pFrame != nullptr);
3109 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3110 pFrame->m_bAutoMenuEnable = FALSE;
3112 String path = GetDocument()->m_filePaths[m_nThisPane];
3113 std::unique_ptr<CShellContextMenu> pContextMenu(new CShellContextMenu(0x9000, 0x9FFF));
3114 pContextMenu->Initialize();
3115 pContextMenu->AddItem(paths::GetParentPath(path), paths::FindFileName(path));
3116 pContextMenu->RequeryShellContextMenu();
3118 ::GetCursorPos(&point);
3119 HWND hWnd = GetSafeHwnd();
3120 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3122 pContextMenu->InvokeCommand(nCmd, hWnd);
3123 pContextMenu->ReleaseShellContextMenu();
3125 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3128 void CMergeEditView::OnUpdateShellMenu(CCmdUI* pCmdUI)
3130 pCmdUI->Enable(!GetDocument()->m_filePaths[m_nThisPane].empty());
3134 * @brief Reload options.
3136 void CMergeEditView::RefreshOptions()
3138 RENDERING_MODE nRenderingMode = static_cast<RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
3139 SetRenderingMode(nRenderingMode);
3141 m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
3143 if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
3144 SetInsertTabs(true);
3146 SetInsertTabs(false);
3148 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3150 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
3151 SetTextType(CrystalLineParser::SRC_PLAIN);
3153 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3154 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3156 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3157 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL),
3158 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3159 GetDocument()->IsMixedEOL(m_nThisPane));
3161 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
3164 void CMergeEditView::OnScripts(UINT nID )
3166 // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
3167 String text = GetSelectedText();
3169 // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
3170 bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
3172 // now replace the text
3173 ReplaceSelection(text.c_str(), text.length(), 0);
3177 * @brief Called when an editor script item is updated
3179 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
3181 // append the scripts submenu
3182 HMENU scriptsSubmenu = dynamic_cast<CMainFrame*>(AfxGetMainWnd())->GetScriptsSubmenu(AfxGetMainWnd()->GetMenu()->m_hMenu);
3183 if (scriptsSubmenu != nullptr)
3184 createScriptsSubmenu(scriptsSubmenu);
3186 pCmdUI->Enable(true);
3190 * @brief Called when an editor script item is updated
3192 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
3194 pCmdUI->Enable(true);
3196 CMergeDoc *pd = GetDocument();
3197 ASSERT(pd != nullptr);
3198 PrediffingInfo prediffer;
3199 pd->GetPrediffer(&prediffer);
3201 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
3203 pCmdUI->SetRadio(false);
3207 // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
3208 if (prediffer.m_PluginName.empty())
3209 m_CurrentPredifferID = ID_NO_PREDIFFER;
3211 pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
3215 * @brief Update "Prediffer" menuitem
3217 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
3219 // recreate the sub menu (to fill the "selected prediffers")
3220 GetMainFrame()->UpdatePrediffersMenu();
3224 void CMergeEditView::OnNoPrediffer()
3226 OnPrediffer(ID_NO_PREDIFFER);
3229 * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
3231 void CMergeEditView::OnPrediffer(UINT nID )
3233 CMergeDoc *pd = GetDocument();
3234 ASSERT(pd != nullptr);
3236 SetPredifferByMenu(nID);
3237 pd->FlushAndRescan(true);
3241 * @brief Handler for all prediffer choices.
3242 * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
3243 * ID_NO_PREDIFFER, & specific prediffers.
3245 void CMergeEditView::SetPredifferByMenu(UINT nID )
3247 CMergeDoc *pd = GetDocument();
3248 ASSERT(pd != nullptr);
3250 if (nID == ID_NO_PREDIFFER)
3252 m_CurrentPredifferID = nID;
3253 // All flags are set correctly during the construction
3254 PrediffingInfo *infoPrediffer = new PrediffingInfo;
3255 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MANUAL;
3256 infoPrediffer->m_PluginName.clear();
3257 pd->SetPrediffer(infoPrediffer);
3258 pd->FlushAndRescan(true);
3262 // get the scriptlet files
3263 PluginArray * piScriptArray =
3264 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3265 PluginArray * piScriptArray2 =
3266 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3268 // build a PrediffingInfo structure fom the ID
3269 PrediffingInfo prediffer;
3270 prediffer.m_PluginOrPredifferMode = PLUGIN_MANUAL;
3272 size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3273 if (pluginNumber < piScriptArray->size())
3275 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3276 prediffer.m_PluginName = plugin->m_name;
3280 pluginNumber -= piScriptArray->size();
3281 if (pluginNumber >= piScriptArray2->size())
3283 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3284 prediffer.m_PluginName = plugin->m_name;
3287 // update data for the radio button
3288 m_CurrentPredifferID = nID;
3290 // update the prediffer and rescan
3291 pd->SetPrediffer(&prediffer);
3295 * @brief Look through available prediffers, and return ID of requested one, if found
3297 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3300 int ID = ID_PREDIFFERS_FIRST;
3302 // Search file prediffers
3303 PluginArray * piScriptArray =
3304 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3305 for (i=0; i<piScriptArray->size(); ++i, ++ID)
3307 const PluginInfoPtr & plugin = piScriptArray->at(i);
3308 if (plugin->m_name == prediffer)
3312 // Search buffer prediffers
3313 PluginArray * piScriptArray2 =
3314 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3315 for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3317 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3318 if (plugin->m_name == prediffer)
3326 * @brief Look through available prediffers, and return ID of requested one, if found
3328 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3330 int id = FindPrediffer(prediffer);
3331 if (id<0) return false;
3332 SetPredifferByMenu(id);
3337 * @brief Goto given line.
3338 * @param [in] nLine Destination linenumber
3339 * @param [in] bRealLine if true linenumber is real line, otherwise
3340 * it is apparent line (including deleted lines)
3341 * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3343 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane)
3345 CMergeDoc *pDoc = GetDocument();
3346 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3347 CMergeEditView *pCurrentView = nullptr;
3348 if (pSplitterWnd != nullptr)
3349 pCurrentView = static_cast<CMergeEditView*>
3350 (pSplitterWnd->GetActivePane());
3352 int nRealLine = nLine;
3353 int nApparentLine = nLine;
3355 // Compute apparent (shown linenumber) line
3358 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3359 nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3361 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3365 ptPos.y = nApparentLine;
3367 // Scroll line to center of view
3368 int nScrollLine = GetSubLineIndex(nApparentLine);
3369 nScrollLine -= GetScreenLines() / 2;
3370 if (nScrollLine < 0)
3373 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3375 CMergeEditView *pView = GetGroupView(nPane);
3376 pView->ScrollToSubLine(nScrollLine);
3377 if (ptPos.y < pView->GetLineCount())
3379 pView->SetCursorPos(ptPos);
3380 pView->SetAnchor(ptPos);
3384 CPoint ptPos1(0, pView->GetLineCount() - 1);
3385 pView->SetCursorPos(ptPos1);
3386 pView->SetAnchor(ptPos1);
3390 // If goto target is another view - activate another view.
3391 // This is done for user convenience as user probably wants to
3392 // work with goto target file.
3393 if (GetGroupView(pane) != pCurrentView)
3394 GetGroupView(pane)->SetActivePane();
3398 * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3401 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3403 if (pScrollBar == nullptr)
3405 // Scroll did not come frome a scroll bar
3406 // Find the appropriate scroll bar
3407 // and send the message to the splitter window instead
3408 // The event should eventually come back here but with a valid scrollbar
3409 // Along the way it will be propagated to other windows that need it
3410 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3411 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3412 pSplitterWnd->SendMessage(WM_HSCROLL,
3413 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3416 CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3420 * @brief When view is scrolled using scrollbars update location pane.
3422 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3424 if (pScrollBar == nullptr)
3426 // Scroll did not come frome a scroll bar
3427 // Find the appropriate scroll bar
3428 // and send the message to the splitter window instead
3429 // The event should eventually come back here but with a valid scrollbar
3430 // Along the way it will be propagated to other windows that need it
3431 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3432 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3433 pSplitterWnd->SendMessage(WM_VSCROLL,
3434 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3437 CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3439 if (nSBCode == SB_ENDSCROLL)
3442 // Note we cannot use nPos because of its 16-bit nature
3443 SCROLLINFO si = {0};
3444 si.cbSize = sizeof (si);
3445 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3446 VERIFY (GetScrollInfo (SB_VERT, &si));
3448 // Get the current position of scroll box.
3449 int nCurPos = si.nPos;
3451 UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3455 * @brief Copy selected lines adding linenumbers.
3457 void CMergeEditView::OnEditCopyLineNumbers()
3465 CMergeDoc *pDoc = GetDocument();
3466 GetSelection(ptStart, ptEnd);
3468 // Get last selected line (having widest linenumber)
3469 int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3470 size_t nNumWidth = strutils::to_str(line + 1).length();
3472 for (int i = ptStart.y; i <= ptEnd.y; i++)
3474 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3477 // We need to convert to real linenumbers
3478 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3480 // Insert spaces to align different width linenumbers (99, 100)
3481 strLine = GetLineText(i);
3482 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3485 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3486 strText += strNumLine;
3488 PutToClipboard(strText, strText.GetLength(), m_bRectangularSelection);
3491 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3493 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3497 * @brief Open active file with associated application.
3499 * First tries to open file using shell 'Edit' action, since that
3500 * action open scripts etc. to editor instead of running them. If
3501 * edit-action is not registered, 'Open' action is used.
3503 void CMergeEditView::OnOpenFile()
3505 CMergeDoc * pDoc = GetDocument();
3506 ASSERT(pDoc != nullptr);
3508 String sFileName = pDoc->m_filePaths[m_nThisPane];
3509 if (sFileName.empty())
3511 HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
3512 0, 0, SW_SHOWNORMAL);
3513 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3514 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
3515 0, 0, SW_SHOWNORMAL);
3516 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3521 * @brief Open active file with app selection dialog
3523 void CMergeEditView::OnOpenFileWith()
3525 CMergeDoc * pDoc = GetDocument();
3526 ASSERT(pDoc != nullptr);
3528 String sFileName = pDoc->m_filePaths[m_nThisPane];
3529 if (sFileName.empty())
3533 if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
3535 sysdir.ReleaseBuffer();
3536 CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
3537 ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
3538 sysdir, SW_SHOWNORMAL);
3542 * @brief Open active file with external editor
3544 void CMergeEditView::OnOpenFileWithEditor()
3546 CMergeDoc * pDoc = GetDocument();
3547 ASSERT(pDoc != nullptr);
3549 String sFileName = pDoc->m_filePaths[m_nThisPane];
3550 if (sFileName.empty())
3553 int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3554 theApp.OpenFileToExternalEditor(sFileName, nRealLine);
3558 * @brief Force repaint of the location pane.
3560 void CMergeEditView::RepaintLocationPane()
3562 // Must force recalculation due to caching of data in location pane.
3563 CLocationView *pLocationView = GetDocument()->GetLocationView();
3564 if (pLocationView != nullptr)
3565 pLocationView->ForceRecalculate();
3569 * @brief Enables/disables linediff (different color for diffs)
3571 void CMergeEditView::OnViewLineDiffs()
3573 bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3574 GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3576 // Call CMergeDoc RefreshOptions() to refresh *both* views
3577 CMergeDoc *pDoc = GetDocument();
3578 pDoc->RefreshOptions();
3579 pDoc->FlushAndRescan(true);
3582 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3584 pCmdUI->Enable(true);
3585 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3589 * @brief Enables/disables line number
3591 void CMergeEditView::OnViewLineNumbers()
3593 GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3595 // Call CMergeDoc RefreshOptions() to refresh *both* views
3596 CMergeDoc *pDoc = GetDocument();
3597 pDoc->RefreshOptions();
3600 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3602 pCmdUI->Enable(true);
3603 pCmdUI->SetCheck(GetViewLineNumbers());
3607 * @brief Enables/disables word wrap
3609 void CMergeEditView::OnViewWordWrap()
3611 GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
3613 // Call CMergeDoc RefreshOptions() to refresh *both* views
3614 CMergeDoc *pDoc = GetDocument();
3615 pDoc->RefreshOptions();
3616 pDoc->UpdateAllViews(this);
3621 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3623 pCmdUI->Enable(true);
3624 pCmdUI->SetCheck(m_bWordWrap);
3627 void CMergeEditView::OnViewWhitespace()
3629 GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3631 // Call CMergeDoc RefreshOptions() to refresh *both* views
3632 CMergeDoc *pDoc = GetDocument();
3633 pDoc->RefreshOptions();
3636 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI)
3638 pCmdUI->SetCheck(GetViewTabs());
3641 void CMergeEditView::OnViewEOL()
3643 GetOptionsMgr()->SaveOption(OPT_VIEW_EOL, !GetViewEols());
3644 GetDocument()->RefreshOptions();
3647 void CMergeEditView::OnUpdateViewEOL(CCmdUI* pCmdUI)
3649 pCmdUI->SetCheck(GetViewEols());
3652 void CMergeEditView::OnSize(UINT nType, int cx, int cy)
3654 if (!IsInitialized())
3657 CMergeDoc * pDoc = GetDocument();
3658 if (m_nThisPane < pDoc->m_nBuffers - 1)
3660 // To calculate subline index correctly
3661 // we have to invalidate line cache in all pane before calling the function related the subline.
3662 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3664 CMergeEditView *pView = GetGroupView(nPane);
3665 if (pView != nullptr)
3666 pView->InvalidateScreenRect(false);
3671 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3673 CMergeEditView *pView = GetGroupView(nPane);
3674 if (pView != nullptr)
3675 pView->Invalidate();
3678 // recalculate m_nTopSubLine
3679 m_nTopSubLine = GetSubLineIndex(m_nTopLine);
3683 RecalcVertScrollBar (false, false);
3684 RecalcHorzScrollBar (false, false);
3688 * @brief allocates GDI resources for printing
3689 * @param pDC [in] points to the printer device context
3690 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3692 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
3694 GetParentFrame()->PostMessage(WM_TIMER);
3696 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3698 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
3699 pView->m_bPrintHeader = true;
3700 pView->m_bPrintFooter = true;
3701 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
3706 * @brief frees GDI resources for printing
3707 * @param pDC [in] points to the printer device context
3708 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3710 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
3712 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3713 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
3715 GetParentFrame()->PostMessage(WM_TIMER);
3719 * @brief Gets header text to print
3720 * @param [in] nPageNum the page number to print
3721 * @param [out] header text to print
3723 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
3725 text = GetDocument()->GetTitle();
3729 * @brief Prints header
3730 * @param [in] nPageNum the page number to print
3732 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
3734 if (m_nThisPane > 0)
3736 int oldRight = m_rcPrintArea.right;
3737 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3738 CGhostTextView::PrintHeader(pdc, nPageNum);
3739 m_rcPrintArea.right = oldRight;
3743 * @brief Prints footer
3744 * @param [in] nPageNum the page number to print
3746 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
3748 if (m_nThisPane > 0)
3750 int oldRight = m_rcPrintArea.right;
3751 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3752 CGhostTextView::PrintFooter(pdc, nPageNum);
3753 m_rcPrintArea.right = oldRight;
3756 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
3758 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3759 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
3763 * @brief Prints or previews both panes.
3764 * @param pDC [in] points to the printer device context
3765 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3767 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
3769 CRect rDraw = pInfo->m_rectDraw;
3770 CSize sz = rDraw.Size();
3771 CMergeDoc *pDoc = GetDocument();
3773 SIZE szLeftTop, szRightBottom;
3774 GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
3775 pDC->HIMETRICtoLP(&szLeftTop);
3776 pDC->HIMETRICtoLP(&szRightBottom);
3778 int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
3781 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
3783 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
3784 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
3785 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
3786 pPane->CGhostTextView::OnPrint(pDC, pInfo);
3790 bool CMergeEditView::IsInitialized() const
3792 CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
3793 CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
3794 return pBuffer->IsInitialized();
3798 * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
3800 int CMergeEditView::GetEmptySubLines( int nLineIndex )
3802 int nBreaks[3] = {0};
3803 int nMaxBreaks = -1;
3804 CMergeDoc * pDoc = GetDocument();
3805 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3807 CMergeEditView *pView = GetGroupView(nPane);
3808 if (pView != nullptr)
3810 if (nLineIndex >= pView->GetLineCount())
3812 pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
3814 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
3817 if (nBreaks[m_nThisPane] < nMaxBreaks)
3818 return nMaxBreaks - nBreaks[m_nThisPane];
3824 * @brief Invalidate sub line index cache from the specified index to the end of file.
3825 * @param [in] nLineIndex Index of the first line to invalidate
3827 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
3829 CMergeDoc * pDoc = GetDocument();
3830 ASSERT(pDoc != nullptr);
3832 // We have to invalidate sub line index cache on both panes.
3833 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3835 CMergeEditView *pView = GetGroupView(nPane);
3836 if (pView != nullptr)
3837 pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
3841 void CMergeEditView::SetWordWrapping( bool bWordWrap )
3843 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3844 GetGroupView(pane)->m_bWordWrap = bWordWrap;
3845 CCrystalTextView::SetWordWrapping(bWordWrap);
3849 * @brief Swap the positions of the two panes
3851 void CMergeEditView::OnViewSwapPanes()
3853 GetDocument()->SwapFiles();
3857 * @brief Determine if difference is visible on screen.
3858 * @param [in] nDiff Number of diff to check.
3859 * @return true if difference is visible.
3861 bool CMergeEditView::IsDiffVisible(int nDiff)
3863 const CMergeDoc *pd = GetDocument();
3866 pd->m_diffList.GetDiff(nDiff, diff);
3868 return IsDiffVisible(diff);
3872 * @brief Determine if difference is visible on screen.
3873 * @param [in] diff diff to check.
3874 * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
3875 * @return true if difference is visible, false otherwise.
3877 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
3879 const int nDiffStart = GetSubLineIndex(diff.dbegin);
3880 const int nDiffEnd = GetSubLineIndex(diff.dend);
3881 // Diff's height is last line - first line + last line's line count
3882 const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
3884 // If diff first line outside current view - context OR
3885 // if diff last line outside current view - context OR
3886 // if diff is bigger than screen
3887 if ((nDiffStart < m_nTopSubLine) ||
3888 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
3889 (nDiffHeight >= GetScreenLines()))
3899 /** @brief Open help from mainframe when user presses F1*/
3900 void CMergeEditView::OnHelp()
3902 theApp.ShowHelp(MergeViewHelpLocation);
3906 * @brief Called after document is loaded.
3907 * This function is called from CMergeDoc::OpenDocs() after documents are
3908 * loaded. So this is good place to set View's options etc.
3910 void CMergeEditView::DocumentsLoaded()
3912 if (GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing())
3915 if (m_nThisPane == GetDocument()->m_nBuffers - 1 && !m_bDetailView)
3920 SetTopMargin(false);
3923 // Enable/disable automatic rescan (rescanning after edit)
3924 EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
3926 // SetTextType will revert to language dependent defaults for tab
3927 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
3928 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3929 const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3930 GetDocument()->IsMixedEOL(m_nThisPane);
3931 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL), mixedEOLs);
3932 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3933 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3934 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3936 // Enable Backspace at beginning of line
3937 SetDisableBSAtSOL(false);
3939 // Set tab type (tabs/spaces)
3940 bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
3941 SetInsertTabs(bInsertTabs);
3943 // Sometimes WinMerge doesn't update scrollbars correctly (they remain
3944 // disabled) after docs are open in screen. So lets make sure they are
3945 // really updated, even though this is unnecessary in most cases.
3946 RecalcHorzScrollBar();
3947 RecalcVertScrollBar();
3951 * @brief Update LocationView position.
3952 * This function updates LocationView position to given lines.
3953 * Usually we want to lines in file compare view and area in
3954 * LocationView to match. Be extra carefull to not call non-existing
3956 * @param [in] nTopLine Top line of current view.
3957 * @param [in] nBottomLine Bottom line of current view.
3959 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
3960 int nBottomLine /*= -1*/)
3962 CMergeDoc *pDoc = GetDocument();
3963 if (pDoc == nullptr)
3966 CLocationView *pLocationView = pDoc->GetLocationView();
3968 if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
3970 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
3975 * @brief Enable/Disable view's selection margins.
3976 * Selection margins show bookmarks and word-wrap symbols, so they are pretty
3977 * useful. But it appears many users don't use/need those features and for them
3978 * selection margins are just wasted screen estate.
3980 void CMergeEditView::OnViewMargin()
3982 bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
3983 GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
3985 SetSelectionMargin(!bViewMargin);
3986 CMergeDoc *pDoc = GetDocument();
3987 pDoc->RefreshOptions();
3988 pDoc->UpdateAllViews(this);
3992 * @brief Update GUI for Enable/Disable view's selection margin.
3993 * @param [in] pCmdUI Pointer to UI item to update.
3995 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
3997 pCmdUI->Enable(true);
3998 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4002 * @brief Create the "Change Scheme" sub menu.
4003 * @param [in] pCmdUI Pointer to UI item to update.
4005 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
4007 // Delete the place holder menu.
4008 pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
4010 const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
4012 String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
4013 AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
4014 AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
4016 for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
4018 name = theApp.LoadString(i);
4019 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
4022 pCmdUI->Enable(true);
4026 * @brief Change the editor's syntax highlighting scheme.
4027 * @param [in] nID Selected color scheme sub menu id.
4029 void CMergeEditView::OnChangeScheme(UINT nID)
4031 CMergeDoc *pDoc = GetDocument();
4032 ASSERT(pDoc != nullptr);
4034 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4036 CMergeEditView *pView = GetGroupView(nPane);
4037 ASSERT(pView != nullptr);
4039 if (pView != nullptr)
4041 pView->SetTextType(CrystalLineParser::TextType(nID - ID_COLORSCHEME_FIRST));
4042 pView->SetDisableBSAtSOL(false);
4046 pDoc->UpdateAllViews(nullptr);
4050 * @brief Enable all color schemes sub menu items.
4051 * @param [in] pCmdUI Pointer to UI item to update.
4053 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
4055 const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
4056 pCmdUI->SetRadio(bIsCurrentScheme);
4057 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
4061 * @brief Called when mouse's wheel is scrolled.
4063 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
4065 if ( nFlags == MK_CONTROL )
4067 short amount = zDelta < 0 ? -1: 1;
4070 // no default CCrystalTextView
4071 return CView::OnMouseWheel(nFlags, zDelta, pt);
4074 if (nFlags == MK_SHIFT)
4076 SCROLLINFO si = { sizeof SCROLLINFO };
4077 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4079 VERIFY(GetScrollInfo(SB_HORZ, &si));
4082 si.nPos -= zDelta / 40;
4083 if (si.nPos > si.nMax) si.nPos = si.nMax;
4084 if (si.nPos < si.nMin) si.nPos = si.nMin;
4086 SetScrollInfo(SB_HORZ, &si);
4089 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4091 // no default CCrystalTextView
4092 return CView::OnMouseWheel(nFlags, zDelta, pt);
4095 return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
4099 * @brief Called when mouse's horizontal wheel is scrolled.
4101 void CMergeEditView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
4103 SCROLLINFO si = { sizeof SCROLLINFO };
4104 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4106 VERIFY(GetScrollInfo(SB_HORZ, &si));
4109 si.nPos += zDelta / 40;
4110 if (si.nPos > si.nMax) si.nPos = si.nMax;
4111 if (si.nPos < si.nMin) si.nPos = si.nMin;
4113 SetScrollInfo(SB_HORZ, &si);
4116 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4118 // no default CCrystalTextView
4119 CView::OnMouseHWheel(nFlags, zDelta, pt);
4123 * @brief Change font size (zoom) in views.
4124 * @param [in] amount Amount of change/zoom, negative number makes
4125 * font smaller, positive number bigger and 0 reset the font size.
4127 void CMergeEditView::ZoomText(short amount)
4132 const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
4133 int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
4137 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
4140 nPointSize += amount;
4144 lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
4146 CMergeDoc *pDoc = GetDocument();
4147 ASSERT(pDoc != nullptr);
4149 if (pDoc != nullptr)
4151 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4153 CMergeEditView *pView = GetGroupView(nPane);
4154 ASSERT(pView != nullptr);
4156 if (pView != nullptr)
4164 bool CMergeEditView::QueryEditable()
4166 return m_bDetailView ? false : !GetDocument()->m_ptBuf[m_nThisPane]->GetReadOnly();
4170 * @brief Adjust the point to remain in the displayed diff
4172 * @return Tells if the point has been changed
4174 bool CMergeEditView::EnsureInDiff(CPoint& pt)
4176 int nLineCount = GetLineCount();
4177 if (m_lineBegin >= nLineCount)
4178 m_lineBegin = nLineCount - 1;
4179 if (m_lineEnd >= nLineCount)
4180 m_lineEnd = nLineCount - 1;
4182 int diffLength = m_lineEnd - m_lineBegin + 1;
4183 // first get the degenerate case out of the way
4185 if (diffLength == 0)
4187 if (pt.y == m_lineBegin && pt.x == 0)
4195 if (pt.y < m_lineBegin)
4201 // diff is defined and not below diff
4202 if (m_lineEnd > -1 && pt.y > m_lineEnd)
4205 pt.x = GetLineLength(pt.y);
4211 void CMergeEditView::EnsureVisible(CPoint pt)
4216 // ensure we remain in diff
4217 if (EnsureInDiff(ptNew))
4218 SetCursorPos(ptNew);
4220 CCrystalTextView::EnsureVisible(ptNew);
4223 void CMergeEditView::EnsureVisible(CPoint ptStart, CPoint ptEnd)
4225 CCrystalTextView::EnsureVisible(ptStart, ptEnd);
4228 void CMergeEditView::SetSelection(const CPoint& ptStart, const CPoint& ptEnd, bool bUpdateView)
4230 CPoint ptStartNew = ptStart;
4231 CPoint ptEndNew = ptEnd;
4234 // ensure we remain in diff
4235 EnsureInDiff(ptStartNew);
4236 EnsureInDiff(ptEndNew);
4238 CCrystalTextView::SetSelection(ptStartNew, ptEndNew, bUpdateView);
4241 void CMergeEditView::ScrollToSubLine(int nNewTopLine, bool bNoSmoothScroll /*= FALSE*/, bool bTrackScrollBar /*= TRUE*/)
4245 int nLineCount = GetLineCount();
4246 if (m_lineBegin >= nLineCount)
4247 m_lineBegin = nLineCount - 1;
4248 if (m_lineEnd >= nLineCount)
4249 m_lineEnd = nLineCount - 1;
4251 // ensure we remain in diff
4252 int sublineBegin = GetSubLineIndex(m_lineBegin);
4253 int sublineEnd = m_lineEnd < 0 ? -1 : GetSubLineIndex(m_lineEnd) + GetSubLines(m_lineEnd) - 1;
4254 int diffLength = sublineEnd - sublineBegin + 1;
4255 int displayLength = GetScreenLines();
4256 if (diffLength <= displayLength)
4257 nNewTopLine = sublineBegin;
4260 if (nNewTopLine < sublineBegin)
4261 nNewTopLine = sublineBegin;
4262 if (nNewTopLine + displayLength - 1 > sublineEnd)
4263 nNewTopLine = GetSubLineIndex(sublineEnd - displayLength + 1);
4266 CPoint pt = GetCursorPos();
4267 if (EnsureInDiff(pt))
4270 CPoint ptSelStart, ptSelEnd;
4271 GetSelection(ptSelStart, ptSelEnd);
4272 if (EnsureInDiff(ptSelStart) || EnsureInDiff(ptSelEnd))
4273 SetSelection(ptSelStart, ptSelEnd);
4275 CCrystalTextView::ScrollToSubLine(nNewTopLine, bNoSmoothScroll, bTrackScrollBar);
4278 void CMergeEditView::SetActivePane()
4280 auto* pwndSplitterChild = GetParentSplitter(this, false);
4281 if (!pwndSplitterChild)
4283 if (pwndSplitterChild->GetColumnCount() > 1)
4284 pwndSplitterChild->SetActivePane(0, m_nThisPane);
4286 pwndSplitterChild->SetActivePane(m_nThisPane, 0);
4290 * @brief Called when user selects View/Zoom In from menu.
4292 void CMergeEditView::OnViewZoomIn()
4298 * @brief Called when user selects View/Zoom Out from menu.
4300 void CMergeEditView::OnViewZoomOut()
4306 * @brief Called when user selects View/Zoom Normal from menu.
4308 void CMergeEditView::OnViewZoomNormal()
4313 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
4315 if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
4317 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
4321 GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
4324 void CMergeEditView::OnWindowSplit()
4327 auto& wndSplitter = dynamic_cast<CMergeEditFrame *>(GetParentFrame())->GetSplitter();
4328 CMergeDoc *pDoc = GetDocument();
4329 CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
4330 auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
4331 int nBuffer = m_nThisPane;
4332 if (pDoc->m_nGroups <= 2)
4334 wndSplitter.SplitRow(1);
4335 wndSplitter.EqualizeRows();
4339 wndSplitter.SetActivePane(0, 0);
4340 wndSplitter.DeleteRow(1);
4341 pDoc->GetView(0, nBuffer)->SetActivePane();
4345 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
4347 pCmdUI->Enable(!m_bDetailView);
4348 pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
4351 void CMergeEditView::OnStatusBarDblClick(NMHDR* pNMHDR, LRESULT* pResult)
4354 LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
4355 const int pane = pNMItemActivate->iItem / 4;
4357 switch (pNMItemActivate->iItem % 4)
4360 GetDocument()->GetView(0, pane)->PostMessage(WM_COMMAND, ID_EDIT_WMGOTO);
4363 GetDocument()->GetView(0, pane)->PostMessage(WM_COMMAND, ID_FILE_ENCODING);
4368 ::GetCursorPos(&point);
4371 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEEDITFRAME_STATUSBAR_EOL));
4372 theApp.TranslateMenu(menu.m_hMenu);
4373 menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetDocument()->GetView(0, pane));
4377 GetDocument()->m_ptBuf[pane]->SetReadOnly(!GetDocument()->m_ptBuf[pane]->GetReadOnly());