1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 /////////////////////////////////////////////////////////////////////////////
22 * @file MergeEditView.cpp
24 * @brief Implementation of the CMergeEditView class
28 #include "MergeEditView.h"
32 #include "LocationView.h"
35 #include "OptionsMgr.h"
36 #include "OptionsDiffColors.h"
37 #include "FileTransform.h"
39 #include "WMGotoDlg.h"
40 #include "OptionsDef.h"
41 #include "SyntaxColors.h"
43 #include "MergeLineFlags.h"
45 #include "DropHandler.h"
54 /** @brief Timer ID for delayed rescan. */
55 const UINT IDT_RESCAN = 2;
56 /** @brief Timer timeout for delayed rescan. */
57 const UINT RESCAN_TIMEOUT = 1000;
59 /** @brief Location for file compare specific help to open. */
60 static TCHAR MergeViewHelpLocation[] = _T("::/htmlhelp/Compare_files.html");
62 /////////////////////////////////////////////////////////////////////////////
65 IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)
67 CMergeEditView::CMergeEditView()
68 : m_bCurrentLineIsDiff(false)
71 , m_bDetailView(false)
72 , m_piMergeEditStatus(nullptr)
73 , m_bAutomaticRescan(false)
74 , fTimerWaitingForIdle(0)
77 , m_CurrentPredifferID(0)
79 SetParser(&m_xParser);
81 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
84 CMergeEditView::~CMergeEditView()
89 BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
90 //{{AFX_MSG_MAP(CMergeEditView)
91 ON_COMMAND(ID_CURDIFF, OnCurdiff)
92 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
93 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
94 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
95 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
96 ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
97 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
98 ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
99 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
100 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
101 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
102 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
103 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
104 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
105 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
106 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
107 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
108 ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
109 ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
110 ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
111 ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
112 ON_COMMAND(ID_NEXTDIFFLM, OnNextdiffLM)
113 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLM, OnUpdateNextdiffLM)
114 ON_COMMAND(ID_PREVDIFFLM, OnPrevdiffLM)
115 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLM, OnUpdatePrevdiffLM)
116 ON_COMMAND(ID_NEXTDIFFLR, OnNextdiffLR)
117 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLR, OnUpdateNextdiffLR)
118 ON_COMMAND(ID_PREVDIFFLR, OnPrevdiffLR)
119 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLR, OnUpdatePrevdiffLR)
120 ON_COMMAND(ID_NEXTDIFFMR, OnNextdiffMR)
121 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMR, OnUpdateNextdiffMR)
122 ON_COMMAND(ID_PREVDIFFMR, OnPrevdiffMR)
123 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMR, OnUpdatePrevdiffMR)
124 ON_COMMAND(ID_NEXTDIFFLO, OnNextdiffLO)
125 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLO, OnUpdateNextdiffLO)
126 ON_COMMAND(ID_PREVDIFFLO, OnPrevdiffLO)
127 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLO, OnUpdatePrevdiffLO)
128 ON_COMMAND(ID_NEXTDIFFMO, OnNextdiffMO)
129 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMO, OnUpdateNextdiffMO)
130 ON_COMMAND(ID_PREVDIFFMO, OnPrevdiffMO)
131 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMO, OnUpdatePrevdiffMO)
132 ON_COMMAND(ID_NEXTDIFFRO, OnNextdiffRO)
133 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFRO, OnUpdateNextdiffRO)
134 ON_COMMAND(ID_PREVDIFFRO, OnPrevdiffRO)
135 ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
136 ON_WM_LBUTTONDBLCLK()
138 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
139 ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
140 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
141 ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
142 ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
143 ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
144 ON_COMMAND(ID_L2R, OnL2r)
145 ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
146 ON_COMMAND(ID_R2L, OnR2l)
147 ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
148 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
149 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
150 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
151 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
152 ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
153 ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
154 ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
155 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
156 ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
157 ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
159 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
160 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_MIDDLE, OnUpdateFileSaveMiddle)
161 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
162 ON_COMMAND(ID_REFRESH, OnRefresh)
163 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
164 ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
165 ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
166 ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
167 ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
169 ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
170 ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
171 ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateLeftReadOnly)
172 ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnMiddleReadOnly)
173 ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateMiddleReadOnly)
174 ON_COMMAND(ID_FILE_RIGHT_READONLY, OnRightReadOnly)
175 ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateRightReadOnly)
176 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_RO, OnUpdateStatusRO)
177 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_RO, OnUpdateStatusRO)
178 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_RO, OnUpdateStatusRO)
179 ON_COMMAND_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnConvertEolTo)
180 ON_UPDATE_COMMAND_UI_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnUpdateConvertEolTo)
181 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_EOL, OnUpdateStatusEOL)
182 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_EOL, OnUpdateStatusEOL)
183 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_EOL, OnUpdateStatusEOL)
184 ON_COMMAND(ID_L2RNEXT, OnL2RNext)
185 ON_UPDATE_COMMAND_UI(ID_L2RNEXT, OnUpdateL2RNext)
186 ON_COMMAND(ID_R2LNEXT, OnR2LNext)
187 ON_UPDATE_COMMAND_UI(ID_R2LNEXT, OnUpdateR2LNext)
188 ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnChangePane)
189 ON_COMMAND(ID_NEXT_PANE, OnChangePane)
190 ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
191 ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
192 ON_COMMAND(ID_NO_PREDIFFER, OnNoPrediffer)
193 ON_UPDATE_COMMAND_UI(ID_NO_PREDIFFER, OnUpdateNoPrediffer)
194 ON_COMMAND_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnPrediffer)
195 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnUpdatePrediffer)
198 ON_COMMAND(ID_EDIT_COPY_LINENUMBERS, OnEditCopyLineNumbers)
199 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY_LINENUMBERS, OnUpdateEditCopyLinenumbers)
200 ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
201 ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
202 ON_COMMAND(ID_VIEW_WORDWRAP, OnViewWordWrap)
203 ON_UPDATE_COMMAND_UI(ID_VIEW_WORDWRAP, OnUpdateViewWordWrap)
204 ON_COMMAND(ID_VIEW_LINENUMBERS, OnViewLineNumbers)
205 ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
206 ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
207 ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
208 ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
209 ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
210 ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
211 ON_COMMAND(ID_VIEW_SWAPPANES, OnViewSwapPanes)
212 ON_UPDATE_COMMAND_UI(ID_NO_EDIT_SCRIPTS, OnUpdateNoEditScripts)
215 ON_COMMAND(ID_HELP, OnHelp)
216 ON_COMMAND(ID_VIEW_FILEMARGIN, OnViewMargin)
217 ON_UPDATE_COMMAND_UI(ID_VIEW_FILEMARGIN, OnUpdateViewMargin)
218 ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
219 ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
220 ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
222 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
223 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
224 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
225 ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
226 ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
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)
348 void CMergeEditView::OnInitialUpdate()
350 CCrystalEditViewEx::OnInitialUpdate();
351 SetFont(dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff);
352 SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
358 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
360 CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
362 CMergeDoc* pDoc = GetDocument();
363 pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
366 std::vector<CCrystalTextView::TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
368 static const std::vector<TEXTBLOCK> emptyBlocks;
371 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
375 DWORD dwLineFlags = GetLineFlags(nLineIndex);
376 if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
379 if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
382 CMergeDoc *pDoc = GetDocument();
383 if (pDoc->IsEditedAfterRescan(m_nThisPane))
386 vector<WordDiff> worddiffs;
387 int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
392 pDoc->m_diffList.GetDiff(nDiff, cd);
393 int unemptyLineCount = 0;
394 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
396 if (cd.begin[nPane] != cd.end[nPane] + 1)
399 if (unemptyLineCount < 2)
402 pDoc->GetWordDiffArray(nLineIndex, &worddiffs);
403 size_t nWordDiffs = worddiffs.size();
405 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
407 std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
408 blocks[0].m_nCharPos = 0;
409 blocks[0].m_nColorIndex = COLORINDEX_NONE;
410 blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
412 for (i = 0, j = 1; i < nWordDiffs; i++)
414 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
416 if (pDoc->m_nBuffers > 2)
418 if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
420 else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
423 int begin[3], end[3];
424 bool deleted = false;
425 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
427 begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
428 end[pane] = (worddiffs[i].endline[pane] > nLineIndex) ? GetGroupView(pane)->GetLineLength(nLineIndex) : worddiffs[i].end[pane];
429 if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
430 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
433 blocks[j].m_nCharPos = begin[m_nThisPane];
434 if (lineInCurrentDiff)
436 if (m_cachedColors.clrSelDiffText != CLR_NONE)
437 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT1 | COLORINDEX_APPLYFORCE;
439 blocks[j].m_nColorIndex = COLORINDEX_NONE;
440 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
441 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
445 if (m_cachedColors.clrDiffText != CLR_NONE)
446 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT2 | COLORINDEX_APPLYFORCE;
448 blocks[j].m_nColorIndex = COLORINDEX_NONE;
449 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
450 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
453 blocks[j].m_nCharPos = end[m_nThisPane];
454 blocks[j].m_nColorIndex = COLORINDEX_NONE;
455 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
464 COLORREF CMergeEditView::GetColor(int nColorIndex)
466 switch (nColorIndex & ~COLORINDEX_APPLYFORCE)
468 case COLORINDEX_HIGHLIGHTBKGND1:
469 return m_cachedColors.clrSelWordDiff;
470 case COLORINDEX_HIGHLIGHTTEXT1:
471 return m_cachedColors.clrSelWordDiffText;
472 case COLORINDEX_HIGHLIGHTBKGND2:
473 return m_cachedColors.clrWordDiff;
474 case COLORINDEX_HIGHLIGHTTEXT2:
475 return m_cachedColors.clrWordDiffText;
476 case COLORINDEX_HIGHLIGHTBKGND3:
477 return m_cachedColors.clrWordDiffDeleted;
478 case COLORINDEX_HIGHLIGHTBKGND4:
479 return m_cachedColors.clrSelWordDiffDeleted;
482 return CCrystalTextView::GetColor(nColorIndex);
487 * @brief Determine text and background color for line
488 * @param [in] nLineIndex Index of line in view (NOT line in file)
489 * @param [out] crBkgnd Backround color for line
490 * @param [out] crText Text color for line
492 void CMergeEditView::GetLineColors(int nLineIndex, COLORREF & crBkgnd,
493 COLORREF & crText, bool & bDrawWhitespace)
495 DWORD ignoreFlags = 0;
496 GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
500 * @brief Determine text and background color for line
501 * @param [in] nLineIndex Index of line in view (NOT line in file)
502 * @param [in] ignoreFlags Flags that caller wishes ignored
503 * @param [out] crBkgnd Backround color for line
504 * @param [out] crText Text color for line
506 * This version allows caller to suppress particular flags
508 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF & crBkgnd,
509 COLORREF & crText, bool & bDrawWhitespace)
511 if (GetLineCount() <= nLineIndex)
514 DWORD dwLineFlags = GetLineFlags(nLineIndex);
516 if (dwLineFlags & ignoreFlags)
517 dwLineFlags &= (~ignoreFlags);
521 // Line with WinMerge flag,
522 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
523 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
525 crText = m_cachedColors.clrDiffText;
526 bDrawWhitespace = true;
528 if (dwLineFlags & LF_GHOST)
530 crBkgnd = m_cachedColors.clrDiffDeleted;
535 // If no syntax hilighting
536 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
538 crBkgnd = GetColor (COLORINDEX_BKGND);
539 crText = GetColor (COLORINDEX_NORMALTEXT);
540 bDrawWhitespace = false;
543 // Line not inside diff, get colors from CrystalEditor
544 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
545 crText, bDrawWhitespace);
547 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
549 crBkgnd = GetColor (COLORINDEX_WHITESPACE);
550 crText = GetColor (COLORINDEX_WHITESPACE);
551 bDrawWhitespace = false;
557 if (dwLineFlags & LF_WINMERGE_FLAGS)
559 crText = m_cachedColors.clrDiffText;
560 bDrawWhitespace = true;
561 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
563 if (dwLineFlags & LF_SNP)
565 if (lineInCurrentDiff)
567 if (dwLineFlags & LF_GHOST)
568 crBkgnd = m_cachedColors.clrSelSNPDeleted;
570 crBkgnd = m_cachedColors.clrSelSNP;
571 crText = m_cachedColors.clrSelSNPText;
575 if (dwLineFlags & LF_GHOST)
576 crBkgnd = m_cachedColors.clrSNPDeleted;
578 crBkgnd = m_cachedColors.clrSNP;
579 crText = m_cachedColors.clrSNPText;
583 else if (dwLineFlags & LF_DIFF)
585 if (lineInCurrentDiff)
587 if (dwLineFlags & LF_MOVED)
589 if (dwLineFlags & LF_GHOST)
590 crBkgnd = m_cachedColors.clrSelMovedDeleted;
592 crBkgnd = m_cachedColors.clrSelMoved;
593 crText = m_cachedColors.clrSelMovedText;
597 crBkgnd = m_cachedColors.clrSelDiff;
598 crText = m_cachedColors.clrSelDiffText;
604 if (dwLineFlags & LF_MOVED)
606 if (dwLineFlags & LF_GHOST)
607 crBkgnd = m_cachedColors.clrMovedDeleted;
609 crBkgnd = m_cachedColors.clrMoved;
610 crText = m_cachedColors.clrMovedText;
614 crBkgnd = m_cachedColors.clrDiff;
615 crText = m_cachedColors.clrDiffText;
620 else if (dwLineFlags & LF_TRIVIAL)
622 // trivial diff can not be selected
623 if (dwLineFlags & LF_GHOST)
624 // ghost lines in trivial diff has their own color
625 crBkgnd = m_cachedColors.clrTrivialDeleted;
627 crBkgnd = m_cachedColors.clrTrivial;
628 crText = m_cachedColors.clrTrivialText;
631 else if (dwLineFlags & LF_GHOST)
633 if (lineInCurrentDiff)
634 crBkgnd = m_cachedColors.clrSelDiffDeleted;
636 crBkgnd = m_cachedColors.clrDiffDeleted;
642 // Line not inside diff,
643 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
645 // If no syntax hilighting, get windows default colors
646 crBkgnd = GetColor (COLORINDEX_BKGND);
647 crText = GetColor (COLORINDEX_NORMALTEXT);
648 bDrawWhitespace = false;
651 // Syntax highlighting, get colors from CrystalEditor
652 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
653 crText, bDrawWhitespace);
658 * @brief Sync other pane position
660 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
662 CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
663 if (pSplitterWnd != nullptr)
665 // See CSplitterWnd::IdFromRowCol() implementation for details
666 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
667 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
668 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
669 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
671 // limit the TopLine : must be smaller than GetLineCount for all the panels
672 int newTopSubLine = m_nTopSubLine;
673 int nRows = pSplitterWnd->GetRowCount ();
674 int nCols = pSplitterWnd->GetColumnCount ();
676 // for (nRow = 0; nRow < nRows; nRow++)
678 // for (int nCol = 0; nCol < nCols; nCol++)
680 // CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
681 // if (pSiblingView != nullptr)
682 // if (pSiblingView->GetSubLineCount() <= newTopSubLine)
683 // newTopSubLine = pSiblingView->GetSubLineCount()-1;
686 if (m_nTopSubLine != newTopSubLine)
687 ScrollToSubLine(newTopSubLine);
689 for (nRow = 0; nRow < nRows; nRow++)
691 for (int nCol = 0; nCol < nCols; nCol++)
693 if (!(nRow == nCurrentRow && nCol == nCurrentCol)) // We don't need to update ourselves
695 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
696 if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
697 pSiblingView->OnUpdateSibling (this, bHorz);
705 * @brief Update other panes
707 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
709 if (pUpdateSource != this)
711 ASSERT (pUpdateSource != nullptr);
712 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
713 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
714 if (!bHorz) // changed this so bHorz works right
716 ASSERT (pSrcView->m_nTopSubLine >= 0);
718 // This ASSERT is wrong: panes have different files and
719 // different linecounts
720 // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
721 if (pSrcView->m_nTopSubLine != m_nTopSubLine)
723 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
725 RecalcVertScrollBar(true);
730 ASSERT (pSrcView->m_nOffsetChar >= 0);
732 // This ASSERT is wrong: panes have different files and
733 // different linelengths
734 // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
735 if (pSrcView->m_nOffsetChar != m_nOffsetChar)
737 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
739 RecalcHorzScrollBar(true);
745 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
747 int newlineBegin, newlineEnd;
748 CMergeDoc *pd = GetDocument();
749 if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
757 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
759 newlineBegin = curDiff.dbegin;
760 ASSERT (newlineBegin >= 0);
761 newlineEnd = curDiff.dend;
764 if (newlineBegin == m_lineBegin && newlineEnd == m_lineEnd)
766 m_lineBegin = newlineBegin;
767 m_lineEnd = newlineEnd;
769 // scroll to the first line of the diff
770 ScrollToLine(m_lineBegin);
772 // tell the others views about this diff (no need to call UpdateSiblingScrollPos)
773 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
775 // pSplitterWnd is `nullptr` if WinMerge started minimized.
776 if (pSplitterWnd != nullptr)
778 int nRows = pSplitterWnd->GetRowCount ();
779 int nCols = pSplitterWnd->GetColumnCount ();
780 for (int nRow = 0; nRow < nRows; nRow++)
782 for (int nCol = 0; nCol < nCols; nCol++)
784 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
785 if (pSiblingView != nullptr)
786 pSiblingView->OnDisplayDiff(nDiff);
791 // update the width of the horizontal scrollbar
792 RecalcHorzScrollBar();
796 * @brief Selects diff by number and syncs other file
797 * @param [in] nDiff Diff to select, must be >= 0
798 * @param [in] bScroll Scroll diff to view
799 * @param [in] bSelectText Select diff text
800 * @sa CMergeEditView::ShowDiff()
801 * @sa CMergeDoc::SetCurrentDiff()
802 * @todo Parameter bSelectText is never used?
804 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
806 CMergeDoc *pd = GetDocument();
808 // Check that nDiff is valid
810 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
811 if (nDiff >= pd->m_diffList.GetSize())
812 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
813 nDiff, pd->m_diffList.GetSize());
816 pd->SetCurrentDiff(nDiff);
817 ShowDiff(bScroll, bSelectText);
818 pd->UpdateAllViews(this);
819 UpdateSiblingScrollPos(false);
821 // notify either side, as it will notify the other one
822 pd->ForEachView (0, [&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
826 * @brief Called when user selects "Current Difference".
827 * Goes to active diff. If no active diff, selects diff under cursor
828 * @sa CMergeEditView::SelectDiff()
829 * @sa CMergeDoc::GetCurrentDiff()
830 * @sa CMergeDoc::LineToDiff()
832 void CMergeEditView::OnCurdiff()
834 CMergeDoc *pd = GetDocument();
836 // If no diffs, nothing to select
837 if (!pd->m_diffList.HasSignificantDiffs())
840 // GetCurrentDiff() returns -1 if no diff selected
841 int nDiff = pd->GetCurrentDiff();
844 // Scroll to the first line of the currently selected diff
845 SelectDiff(nDiff, true, false);
849 // If cursor is inside diff, select that diff
850 CPoint pos = GetCursorPos();
851 nDiff = pd->m_diffList.LineToDiff(pos.y);
852 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
853 SelectDiff(nDiff, true, false);
858 * @brief Called when "Current diff" item is updated
860 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
862 CMergeDoc *pd = GetDocument();
863 CPoint pos = GetCursorPos();
864 int nCurrentDiff = pd->GetCurrentDiff();
865 if (nCurrentDiff == -1)
867 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
868 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
871 pCmdUI->Enable(true);
875 * @brief Copy selected text to clipboard
877 void CMergeEditView::OnEditCopy()
879 CMergeDoc * pDoc = GetDocument();
880 CPoint ptSelStart, ptSelEnd;
881 GetSelection(ptSelStart, ptSelEnd);
884 if (ptSelStart == ptSelEnd)
889 if (!m_bColumnSelection)
891 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
893 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
894 ptSelEnd.y, ptSelEnd.x, text);
897 GetTextWithoutEmptysInColumnSelection(text);
899 PutToClipboard(text, text.GetLength(), m_bColumnSelection);
903 * @brief Called when "Copy" item is updated
905 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
907 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
911 * @brief Cut current selection to clipboard
913 void CMergeEditView::OnEditCut()
915 if (IsReadOnly(m_nThisPane))
918 CPoint ptSelStart, ptSelEnd;
919 CMergeDoc * pDoc = GetDocument();
920 GetSelection(ptSelStart, ptSelEnd);
923 if (ptSelStart == ptSelEnd)
927 if (!m_bColumnSelection)
928 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
929 ptSelEnd.y, ptSelEnd.x, text);
931 GetTextWithoutEmptysInColumnSelection(text);
933 PutToClipboard(text, text.GetLength(), m_bColumnSelection);
935 if (!m_bColumnSelection)
937 CPoint ptCursorPos = ptSelStart;
938 ASSERT_VALIDTEXTPOS(ptCursorPos);
939 SetAnchor(ptCursorPos);
940 SetSelection(ptCursorPos, ptCursorPos);
941 SetCursorPos(ptCursorPos);
942 EnsureVisible(ptCursorPos);
944 pDoc->m_ptBuf[m_nThisPane]->DeleteText(this, ptSelStart.y, ptSelStart.x, ptSelEnd.y,
945 ptSelEnd.x, CE_ACTION_CUT);
948 DeleteCurrentColumnSelection (CE_ACTION_CUT);
950 m_pTextBuffer->SetModified(true);
954 * @brief Called when "Cut" item is updated
956 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
958 if (!IsReadOnly(m_nThisPane))
959 CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
961 pCmdUI->Enable(false);
965 * @brief Paste text from clipboard
967 void CMergeEditView::OnEditPaste()
969 if (IsReadOnly(m_nThisPane))
972 CCrystalEditViewEx::Paste();
973 m_pTextBuffer->SetModified(true);
977 * @brief Called when "Paste" item is updated
979 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
981 if (!IsReadOnly(m_nThisPane))
982 CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
984 pCmdUI->Enable(false);
988 * @brief Undo last action
990 void CMergeEditView::OnEditUndo()
992 CWaitCursor waitstatus;
993 CMergeDoc* pDoc = GetDocument();
994 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
997 if (IsReadOnly(m_nThisPane))
1000 GetParentFrame()->SetActiveView(this, true);
1001 if(CCrystalEditViewEx::DoEditUndo())
1004 pDoc->UpdateHeaderPath(m_nThisPane);
1005 pDoc->FlushAndRescan();
1008 m_pTextBuffer->GetRedoActionCode(nAction);
1009 if (nAction == CE_ACTION_MERGE)
1010 // select the diff so we may just merge it again
1016 tgt->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
1018 if (!pDoc->CanUndo())
1019 pDoc->SetAutoMerged(false);
1023 * @brief Called when "Undo" item is updated
1025 void CMergeEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
1027 CMergeDoc* pDoc = GetDocument();
1028 if (pDoc->curUndo!=pDoc->undoTgt.begin())
1030 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1031 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
1034 pCmdUI->Enable(false);
1038 * @brief Go to first diff
1040 * Called when user selects "First Difference"
1041 * @sa CMergeEditView::SelectDiff()
1043 void CMergeEditView::OnFirstdiff()
1045 CMergeDoc *pd = GetDocument();
1046 if (pd->m_diffList.HasSignificantDiffs())
1048 int nDiff = pd->m_diffList.FirstSignificantDiff();
1049 SelectDiff(nDiff, true, false);
1054 * @brief Update "First diff" UI items
1056 void CMergeEditView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1058 OnUpdatePrevdiff(pCmdUI);
1062 * @brief Go to last diff
1064 void CMergeEditView::OnLastdiff()
1066 CMergeDoc *pd = GetDocument();
1067 if (pd->m_diffList.HasSignificantDiffs())
1069 int nDiff = pd->m_diffList.LastSignificantDiff();
1070 SelectDiff(nDiff, true, false);
1075 * @brief Update "Last diff" UI items
1077 void CMergeEditView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1079 OnUpdateNextdiff(pCmdUI);
1083 * @brief Go to next diff and select it.
1085 * Finds and selects next difference. There are several cases:
1086 * - if there is selected difference, and that difference is visible
1087 * on screen, next found difference is selected.
1088 * - if there is selected difference but it is not visible, next
1089 * difference from cursor position is selected. This is what user
1090 * expects to happen and is natural thing to do. Also reduces
1091 * needless scrolling.
1092 * - if there is no selected difference, next difference from cursor
1093 * position is selected.
1095 void CMergeEditView::OnNextdiff()
1097 CMergeDoc *pd = GetDocument();
1098 int cnt = pd->m_ptBuf[0]->GetLineCount();
1102 // Returns -1 if no diff selected
1104 int curDiff = pd->GetCurrentDiff();
1108 if (!IsDiffVisible(curDiff))
1110 // Selected difference not visible, select next from cursor
1111 int line = GetCursorPos().y;
1112 // Make sure we aren't in the first line of the diff
1114 if (!IsValidTextPosY(CPoint(0, line)))
1116 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1120 // Find out if there is a following significant diff
1121 if (curDiff < pd->m_diffList.GetSize() - 1)
1123 nextDiff = pd->m_diffList.NextSignificantDiff(curDiff);
1129 // We don't have a selected difference,
1130 // but cursor can be inside inactive diff
1131 int line = GetCursorPos().y;
1132 if (!IsValidTextPosY(CPoint(0, line)))
1134 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1137 int lastDiff = pd->m_diffList.LastSignificantDiff();
1138 if (nextDiff >= 0 && nextDiff <= lastDiff)
1139 SelectDiff(nextDiff, true, false);
1140 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1142 if (pDirDoc->MoveableToNextDiff())
1143 pDirDoc->MoveToNextDiff(pd);
1148 * @brief Update "Next diff" UI items
1150 void CMergeEditView::OnUpdateNextdiff(CCmdUI* pCmdUI)
1152 CMergeDoc *pd = GetDocument();
1153 const DIFFRANGE * dfi = pd->m_diffList.LastSignificantDiffRange();
1158 // There aren't any significant differences
1163 // Enable if the beginning of the last significant difference is after caret
1164 enabled = (GetCursorPos().y < (long)dfi->dbegin);
1167 if (!enabled && pd->GetDirDoc())
1168 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1170 pCmdUI->Enable(enabled);
1174 * @brief Go to previous diff and select it.
1176 * Finds and selects previous difference. There are several cases:
1177 * - if there is selected difference, and that difference is visible
1178 * on screen, previous found difference is selected.
1179 * - if there is selected difference but it is not visible, previous
1180 * difference from cursor position is selected. This is what user
1181 * expects to happen and is natural thing to do. Also reduces
1182 * needless scrolling.
1183 * - if there is no selected difference, previous difference from cursor
1184 * position is selected.
1186 void CMergeEditView::OnPrevdiff()
1188 CMergeDoc *pd = GetDocument();
1189 int cnt = pd->m_ptBuf[0]->GetLineCount();
1193 // GetCurrentDiff() returns -1 if no diff selected
1195 int curDiff = pd->GetCurrentDiff();
1199 if (!IsDiffVisible(curDiff))
1201 // Selected difference not visible, select previous from cursor
1202 int line = GetCursorPos().y;
1203 // Make sure we aren't in the last line of the diff
1205 if (!IsValidTextPosY(CPoint(0, line)))
1207 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1211 // Find out if there is a preceding significant diff
1214 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1220 // We don't have a selected difference,
1221 // but cursor can be inside inactive diff
1222 int line = GetCursorPos().y;
1223 if (!IsValidTextPosY(CPoint(0, line)))
1225 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1228 int firstDiff = pd->m_diffList.FirstSignificantDiff();
1229 if (prevDiff >= 0 && prevDiff >= firstDiff)
1230 SelectDiff(prevDiff, true, false);
1231 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1233 if (pDirDoc->MoveableToPrevDiff())
1234 pDirDoc->MoveToPrevDiff(pd);
1239 * @brief Update "Previous diff" UI items
1241 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1243 CMergeDoc *pd = GetDocument();
1244 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1249 // There aren't any significant differences
1254 // Enable if the end of the first significant difference is before caret
1255 enabled = (GetCursorPos().y > (long)dfi->dend);
1258 if (!enabled && pd->GetDirDoc())
1259 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1261 pCmdUI->Enable(enabled);
1264 void CMergeEditView::OnNextConflict()
1266 OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1270 * @brief Update "Next Conflict" UI items
1272 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1274 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1277 void CMergeEditView::OnPrevConflict()
1279 OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1283 * @brief Update "Prev Conflict" UI items
1285 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1287 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1291 * @brief Go to next 3-way diff and select it.
1293 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1295 CMergeDoc *pd = GetDocument();
1296 int cnt = pd->m_ptBuf[0]->GetLineCount();
1300 // Returns -1 if no diff selected
1301 int curDiff = pd->GetCurrentDiff();
1305 int nextDiff = curDiff;
1306 if (!IsDiffVisible(curDiff))
1308 // Selected difference not visible, select next from cursor
1309 int line = GetCursorPos().y;
1310 // Make sure we aren't in the first line of the diff
1312 if (!IsValidTextPosY(CPoint(0, line)))
1314 nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1318 // Find out if there is a following significant diff
1319 if (curDiff < pd->m_diffList.GetSize() - 1)
1321 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1327 // nextDiff is the next one if there is one, else it is the one we're on
1328 SelectDiff(nextDiff, true, false);
1332 // We don't have a selected difference,
1333 // but cursor can be inside inactive diff
1334 int line = GetCursorPos().y;
1335 if (!IsValidTextPosY(CPoint(0, line)))
1337 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1339 SelectDiff(curDiff, true, false);
1344 * @brief Update "Next 3-way diff" UI items
1346 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1348 CMergeDoc *pd = GetDocument();
1350 if (pd->m_nBuffers < 3)
1352 pCmdUI->Enable(false);
1356 const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1360 // There aren't any significant differences
1361 pCmdUI->Enable(false);
1365 // Enable if the beginning of the last significant difference is after caret
1366 CPoint pos = GetCursorPos();
1367 pCmdUI->Enable(pos.y < (long)dfi->dbegin);
1372 * @brief Go to previous 3-way diff and select it.
1374 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1376 CMergeDoc *pd = GetDocument();
1378 int cnt = pd->m_ptBuf[0]->GetLineCount();
1382 // GetCurrentDiff() returns -1 if no diff selected
1383 int curDiff = pd->GetCurrentDiff();
1387 int prevDiff = curDiff;
1388 if (!IsDiffVisible(curDiff))
1390 // Selected difference not visible, select previous from cursor
1391 int line = GetCursorPos().y;
1392 // Make sure we aren't in the last line of the diff
1394 if (!IsValidTextPosY(CPoint(0, line)))
1396 prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1400 // Find out if there is a preceding significant diff
1403 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1409 // prevDiff is the preceding one if there is one, else it is the one we're on
1410 SelectDiff(prevDiff, true, false);
1414 // We don't have a selected difference,
1415 // but cursor can be inside inactive diff
1416 int line = GetCursorPos().y;
1417 if (!IsValidTextPosY(CPoint(0, line)))
1419 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1421 SelectDiff(curDiff, true, false);
1426 * @brief Update "Previous diff X and Y" UI items
1428 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1430 CMergeDoc *pd = GetDocument();
1432 if (pd->m_nBuffers < 3)
1434 pCmdUI->Enable(false);
1438 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1442 // There aren't any significant differences
1443 pCmdUI->Enable(false);
1447 // Enable if the end of the first significant difference is before caret
1448 CPoint pos = GetCursorPos();
1449 pCmdUI->Enable(pos.y > (long)dfi->dend);
1453 void CMergeEditView::OnNextdiffLM()
1455 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1458 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1460 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1463 void CMergeEditView::OnNextdiffLR()
1465 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1468 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1470 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1473 void CMergeEditView::OnNextdiffMR()
1475 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1478 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1480 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1483 void CMergeEditView::OnNextdiffLO()
1485 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1488 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1490 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1493 void CMergeEditView::OnNextdiffMO()
1495 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1498 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1500 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1503 void CMergeEditView::OnNextdiffRO()
1505 OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1508 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1510 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1513 void CMergeEditView::OnPrevdiffLM()
1515 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1518 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1520 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1523 void CMergeEditView::OnPrevdiffLR()
1525 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1528 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1530 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1533 void CMergeEditView::OnPrevdiffMR()
1535 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1538 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1540 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1543 void CMergeEditView::OnPrevdiffLO()
1545 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1548 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1550 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1553 void CMergeEditView::OnPrevdiffMO()
1555 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1558 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1560 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1563 void CMergeEditView::OnPrevdiffRO()
1565 OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1568 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1570 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1574 * @brief Clear selection
1576 void CMergeEditView::SelectNone()
1578 SetSelection (GetCursorPos(), GetCursorPos());
1583 * @brief Check if line is inside currently selected diff
1584 * @param [in] nLine 0-based linenumber in view
1585 * @sa CMergeDoc::GetCurrentDiff()
1586 * @sa CMergeDoc::LineInDiff()
1588 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1590 // Check validity of nLine
1593 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1594 int nLineCount = LocateTextBuffer()->GetLineCount();
1595 if (nLine >= nLineCount)
1596 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1599 const CMergeDoc *pd = GetDocument();
1600 int curDiff = pd->GetCurrentDiff();
1603 return pd->m_diffList.LineInDiff(nLine, curDiff);
1607 * @brief Called when mouse left-button double-clicked
1609 * Double-clicking mouse inside diff selects that diff
1611 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1613 CMergeDoc *pd = GetDocument();
1614 CPoint pos = GetCursorPos();
1616 int diff = pd->m_diffList.LineToDiff(pos.y);
1617 if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1618 SelectDiff(diff, false, false);
1620 CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1624 * @brief Called when mouse left button is released.
1626 * If button is released outside diffs, current diff
1629 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1631 CMergeDoc *pd = GetDocument();
1632 CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1634 // If we have a selected diff, deselect it
1635 int nCurrentDiff = pd->GetCurrentDiff();
1636 if (nCurrentDiff != -1)
1638 CPoint pos = GetCursorPos();
1639 if (!IsLineInCurrentDiff(pos.y))
1641 pd->SetCurrentDiff(-1);
1643 pd->UpdateAllViews(this);
1648 void CMergeEditView::OnX2Y(int srcPane, int dstPane)
1650 // Check that right side is not readonly
1651 if (IsReadOnly(dstPane))
1654 CMergeDoc *pDoc = GetDocument();
1655 int currentDiff = pDoc->GetCurrentDiff();
1657 if (currentDiff == -1)
1660 // If cursor is inside diff get number of that diff
1661 if (m_bCurrentLineIsDiff)
1663 CPoint pt = GetCursorPos();
1664 currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1668 int firstDiff, lastDiff;
1669 GetFullySelectedDiffs(firstDiff, lastDiff);
1671 if (firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff))
1673 CWaitCursor waitstatus;
1674 if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff) && !IsSelection())
1675 pDoc->ListCopy(srcPane, dstPane, currentDiff);
1677 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
1679 else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
1681 CWaitCursor waitstatus;
1682 pDoc->ListCopy(srcPane, dstPane, currentDiff);
1686 void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
1688 // Check that right side is not readonly
1689 if (!IsReadOnly(dstPane))
1691 int firstDiff, lastDiff;
1692 GetFullySelectedDiffs(firstDiff, lastDiff);
1694 // If one or more diffs inside selection OR
1695 // there is an active diff OR
1696 // cursor is inside diff
1697 if (firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff))
1698 pCmdUI->Enable(true);
1701 const int currDiff = GetDocument()->GetCurrentDiff();
1702 if (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff))
1703 pCmdUI->Enable(true);
1705 pCmdUI->Enable(m_bCurrentLineIsDiff);
1709 pCmdUI->Enable(false);
1713 * @brief Copy diff from left pane to right pane
1715 * Difference is copied from left to right when
1716 * - difference is selected
1717 * - difference is inside selection (allows merging multiple differences).
1718 * - cursor is inside diff
1720 * If there is selected diff outside selection, we copy selected
1723 void CMergeEditView::OnL2r()
1725 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1726 int srcPane = dstPane - 1;
1727 OnX2Y(srcPane, dstPane);
1731 * @brief Called when "Copy to left" item is updated
1733 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
1735 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
1739 * @brief Copy diff from right pane to left pane
1741 * Difference is copied from left to right when
1742 * - difference is selected
1743 * - difference is inside selection (allows merging multiple differences).
1744 * - cursor is inside diff
1746 * If there is selected diff outside selection, we copy selected
1749 void CMergeEditView::OnR2l()
1751 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
1752 int srcPane = dstPane + 1;
1753 OnX2Y(srcPane, dstPane);
1757 * @brief Called when "Copy to right" item is updated
1759 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
1761 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
1764 void CMergeEditView::OnCopyFromLeft()
1766 int dstPane = m_nThisPane;
1767 int srcPane = dstPane - 1;
1770 OnX2Y(srcPane, dstPane);
1773 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
1775 int dstPane = m_nThisPane;
1776 int srcPane = dstPane - 1;
1778 pCmdUI->Enable(false);
1780 OnUpdateX2Y(dstPane, pCmdUI);
1783 void CMergeEditView::OnCopyFromRight()
1785 int dstPane = m_nThisPane;
1786 int srcPane = dstPane + 1;
1787 if (srcPane >= GetDocument()->m_nBuffers)
1789 OnX2Y(srcPane, dstPane);
1792 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
1794 int dstPane = m_nThisPane;
1795 int srcPane = dstPane + 1;
1796 if (srcPane >= GetDocument()->m_nBuffers)
1797 pCmdUI->Enable(false);
1799 OnUpdateX2Y(dstPane, pCmdUI);
1803 * @brief Copy all diffs from right pane to left pane
1805 void CMergeEditView::OnAllLeft()
1807 // Check that left side is not readonly
1808 int srcPane = m_nThisPane > 0 ? m_nThisPane : 1;
1809 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
1810 if (IsReadOnly(dstPane))
1812 CWaitCursor waitstatus;
1814 GetDocument()->CopyAllList(srcPane, dstPane);
1818 * @brief Called when "Copy all to left" item is updated
1820 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
1822 // Check that left side is not readonly
1823 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
1824 if (!IsReadOnly(dstPane))
1825 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
1827 pCmdUI->Enable(false);
1831 * @brief Copy all diffs from left pane to right pane
1833 void CMergeEditView::OnAllRight()
1835 // Check that right side is not readonly
1836 int srcPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane : m_nThisPane - 1;
1837 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1838 if (IsReadOnly(dstPane))
1841 CWaitCursor waitstatus;
1843 GetDocument()->CopyAllList(srcPane, dstPane);
1847 * @brief Called when "Copy all to right" item is updated
1849 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
1851 // Check that right side is not readonly
1852 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1853 if (!IsReadOnly(dstPane))
1854 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
1856 pCmdUI->Enable(false);
1860 * @brief Do Auto merge
1862 void CMergeEditView::OnAutoMerge()
1864 // Check current pane is not readonly
1865 if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || IsReadOnly(m_nThisPane))
1868 CWaitCursor waitstatus;
1870 GetDocument()->DoAutoMerge(m_nThisPane);
1874 * @brief Called when "Auto Merge" item is updated
1876 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
1878 pCmdUI->Enable(GetDocument()->m_nBuffers == 3 &&
1879 !GetDocument()->IsModified() &&
1880 !GetDocument()->GetAutoMerged() &&
1881 !IsReadOnly(m_nThisPane));
1885 * @brief Add synchronization point
1887 void CMergeEditView::OnAddSyncPoint()
1889 GetDocument()->AddSyncPoint();
1893 * @brief Clear synchronization points
1895 void CMergeEditView::OnClearSyncPoints()
1897 GetDocument()->ClearSyncPoints();
1901 * @brief Called when "Clear Synchronization Points" item is updated
1903 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
1905 pCmdUI->Enable(GetDocument()->HasSyncPoints());
1909 * @brief This function is called before other edit events.
1910 * @param [in] nAction Edit operation to do
1911 * @param [in] pszText Text to insert, delete etc
1912 * @sa CCrystalEditView::OnEditOperation()
1913 * @todo More edit-events for rescan delaying?
1915 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
1917 if (IsReadOnly(m_nThisPane))
1919 // We must not arrive here, and assert helps detect troubles
1924 CMergeDoc* pDoc = GetDocument();
1925 pDoc->SetEditedAfterRescan(m_nThisPane);
1927 // simple hook for multiplex undo operations
1928 // deleted by jtuc 2003-06-28
1929 // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
1930 /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
1932 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
1933 pDoc->undoTgt.push_back(this);
1934 pDoc->curUndo = pDoc->undoTgt.end();
1937 // perform original function
1938 CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
1940 // augment with additional operations
1942 // Change header to inform about changed doc
1943 pDoc->UpdateHeaderPath(m_nThisPane);
1945 // If automatic rescan enabled, rescan after edit events
1946 if (m_bAutomaticRescan)
1948 // keep document up to date
1949 // (Re)start timer to rescan only when user edits text
1950 // If timer starting fails, rescan immediately
1951 if (nAction == CE_ACTION_TYPING ||
1952 nAction == CE_ACTION_REPLACE ||
1953 nAction == CE_ACTION_BACKSPACE ||
1954 nAction == CE_ACTION_INDENT ||
1955 nAction == CE_ACTION_PASTE ||
1956 nAction == CE_ACTION_DELSEL ||
1957 nAction == CE_ACTION_DELETE ||
1958 nAction == CE_ACTION_CUT)
1960 if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
1961 pDoc->FlushAndRescan();
1964 pDoc->FlushAndRescan();
1970 // Update other pane for sync line.
1971 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
1973 if (nPane == m_nThisPane)
1975 CCrystalEditView *pView = GetGroupView(nPane);
1976 if (pView != nullptr)
1977 pView->Invalidate();
1984 * @brief Redo last action
1986 void CMergeEditView::OnEditRedo()
1988 CWaitCursor waitstatus;
1989 CMergeDoc* pDoc = GetDocument();
1990 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
1993 if (IsReadOnly(m_nThisPane))
1996 GetParentFrame()->SetActiveView(this, true);
1997 if(CCrystalEditViewEx::DoEditRedo())
2000 pDoc->UpdateHeaderPath(m_nThisPane);
2001 pDoc->FlushAndRescan();
2006 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2011 * @brief Called when "Redo" item is updated
2013 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2015 CMergeDoc* pDoc = GetDocument();
2016 if (pDoc->curUndo!=pDoc->undoTgt.end())
2018 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2019 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2022 pCmdUI->Enable(false);
2025 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2027 CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2031 * @brief Scrolls to current diff and/or selects diff text
2032 * @param [in] bScroll If true scroll diff to view
2033 * @param [in] bSelectText If true select diff text
2034 * @note If bScroll and bSelectText are false, this does nothing!
2035 * @todo This shouldn't be called when no diff is selected, so
2036 * somebody could try to ASSERT(nDiff > -1)...
2038 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2040 CMergeDoc *pd = GetDocument();
2041 const int nDiff = pd->GetCurrentDiff();
2043 // Try to trap some errors
2044 if (nDiff >= pd->m_diffList.GetSize())
2045 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2046 nDiff, pd->m_diffList.GetSize());
2048 if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2050 CPoint ptStart, ptEnd;
2052 pd->m_diffList.GetDiff(nDiff, curDiff);
2055 ptStart.y = curDiff.dbegin;
2057 ptEnd.y = curDiff.dend;
2061 if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2063 // Difference is not visible, scroll it so that max amount of
2064 // scrolling is done while keeping the diff in screen. So if
2065 // scrolling is downwards, scroll the diff to as up in screen
2066 // as possible. This usually brings next diff to the screen
2067 // and we don't need to scroll into it.
2068 int nLine = GetSubLineIndex(ptStart.y);
2069 if (nLine > CONTEXT_LINES_ABOVE)
2071 nLine -= CONTEXT_LINES_ABOVE;
2073 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2074 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2076 if (nPane != m_nThisPane)
2077 GetGroupView(nPane)->ScrollToSubLine(nLine);
2080 GetGroupView(m_nThisPane)->SetCursorPos(ptStart);
2081 GetGroupView(m_nThisPane)->SetAnchor(ptStart);
2082 GetGroupView(m_nThisPane)->SetSelection(ptStart, ptStart);
2083 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2085 if (nPane != m_nThisPane)
2087 GetGroupView(nPane)->SetCursorPos(ptStart);
2088 GetGroupView(nPane)->SetAnchor(ptStart);
2089 GetGroupView(nPane)->SetSelection(ptStart, ptStart);
2096 ptEnd.x = GetLineLength(ptEnd.y);
2097 SetSelection(ptStart, ptEnd);
2106 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2108 // Maybe we want theApp::OnIdle to proceed before processing a timer message
2109 // ...but for this the queue must be empty
2110 // The timer message is a low priority message but the queue is maybe not yet empty
2111 // So we set a flag, wait for OnIdle to proceed, then come back here...
2112 // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2113 // not with SetTimer so there is no delay)
2115 // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2117 // IDLE_TIMER is the false timer used to come back here after OnIdle
2118 // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2119 // (one normal timer = one flag = one command)
2121 if (nIDEvent == IDT_RESCAN)
2123 KillTimer(IDT_RESCAN);
2124 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2125 // notify the app to come back after OnIdle
2126 theApp.SetNeedIdleTimer();
2129 if (nIDEvent == IDLE_TIMER)
2131 // not a real timer, just come back after OnIdle
2132 // look to flags to know what to do
2133 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2134 GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2135 fTimerWaitingForIdle = 0;
2138 CCrystalEditViewEx::OnTimer(nIDEvent);
2142 * @brief Returns if buffer is read-only
2143 * @note This has no any relation to file being read-only!
2145 bool CMergeEditView::IsReadOnly(int pane) const
2147 return GetDocument()->m_ptBuf[pane]->GetReadOnly() != false;
2151 * @brief Called when "Save left (as...)" item is updated
2153 void CMergeEditView::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
2155 CMergeDoc *pd = GetDocument();
2156 pCmdUI->Enable(!IsReadOnly(0) && pd->m_ptBuf[0]->IsModified());
2160 * @brief Called when "Save middle (as...)" item is updated
2162 void CMergeEditView::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
2164 CMergeDoc *pd = GetDocument();
2165 pCmdUI->Enable(pd->m_nBuffers == 3 && !IsReadOnly(1) && pd->m_ptBuf[1]->IsModified());
2169 * @brief Called when "Save right (as...)" item is updated
2171 void CMergeEditView::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
2173 CMergeDoc *pd = GetDocument();
2174 pCmdUI->Enable(!IsReadOnly(pd->m_nBuffers - 1) && pd->m_ptBuf[pd->m_nBuffers - 1]->IsModified());
2178 * @brief Refresh display using text-buffers
2179 * @note This DOES NOT reload files!
2181 void CMergeEditView::OnRefresh()
2183 CMergeDoc *pd = GetDocument();
2184 ASSERT(pd != nullptr);
2185 pd->FlushAndRescan(true);
2189 * @brief Enable/Disable automatic rescanning
2191 bool CMergeEditView::EnableRescan(bool bEnable)
2193 bool bOldValue = m_bAutomaticRescan;
2194 m_bAutomaticRescan = bEnable;
2199 * @brief Handle some keys when in merging mode
2201 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2203 bool bHandled = false;
2205 // Allow default text selection when SHIFT pressed
2206 if (::GetAsyncKeyState(VK_SHIFT))
2209 // Allow default editor functions when CTRL pressed
2210 if (::GetAsyncKeyState(VK_CONTROL))
2213 // If we are in merging mode (merge with cursor keys)
2214 // handle some keys here
2215 switch (pMsg->wParam)
2242 * @brief Called before messages are translated.
2244 * Checks if ESC key was pressed, saves and closes doc.
2245 * Also if in merge mode traps cursor keys.
2247 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2249 if (pMsg->message == WM_KEYDOWN)
2251 // If we are in merging mode (merge with cursor keys)
2252 // handle some keys here
2253 if (theApp.GetMergingMode())
2255 bool bHandled = MergeModeKeyDown(pMsg);
2260 // Close window if user has allowed it from options
2261 if (pMsg->wParam == VK_ESCAPE)
2263 bool bCloseWithEsc = GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_ESC);
2265 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2270 return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2274 * @brief Called when "Save" item is updated
2276 void CMergeEditView::OnUpdateFileSave(CCmdUI* pCmdUI)
2278 CMergeDoc *pd = GetDocument();
2280 bool bModified = false;
2281 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2283 if (pd->m_ptBuf[nPane]->IsModified())
2286 pCmdUI->Enable(bModified);
2290 * @brief Enable/disable left buffer read-only
2292 void CMergeEditView::OnLeftReadOnly()
2294 CMergeDoc *pd = GetDocument();
2295 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2296 pd->m_ptBuf[0]->SetReadOnly(!bReadOnly);
2300 * @brief Called when "Left read-only" item is updated
2302 void CMergeEditView::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
2304 CMergeDoc *pd = GetDocument();
2305 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2306 pCmdUI->Enable(true);
2307 pCmdUI->SetCheck(bReadOnly);
2311 * @brief Enable/disable middle buffer read-only
2313 void CMergeEditView::OnMiddleReadOnly()
2315 CMergeDoc *pd = GetDocument();
2316 if (pd->m_nBuffers == 3)
2318 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2319 pd->m_ptBuf[1]->SetReadOnly(!bReadOnly);
2324 * @brief Called when "Middle read-only" item is updated
2326 void CMergeEditView::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
2328 CMergeDoc *pd = GetDocument();
2329 if (pd->m_nBuffers < 3)
2331 pCmdUI->Enable(false);
2335 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2336 pCmdUI->Enable(true);
2337 pCmdUI->SetCheck(bReadOnly);
2342 * @brief Enable/disable right buffer read-only
2344 void CMergeEditView::OnRightReadOnly()
2346 CMergeDoc *pd = GetDocument();
2347 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2348 pd->m_ptBuf[pd->m_nBuffers - 1]->SetReadOnly(!bReadOnly);
2352 * @brief Called when "Left read-only" item is updated
2354 void CMergeEditView::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
2356 CMergeDoc *pd = GetDocument();
2357 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2358 pCmdUI->Enable(true);
2359 pCmdUI->SetCheck(bReadOnly);
2362 /// Store interface we use to display status line info
2363 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2365 ASSERT(m_piMergeEditStatus == nullptr);
2366 m_piMergeEditStatus = piMergeEditStatus;
2370 * @brief Update status bar contents.
2372 void CMergeEditView::UpdateStatusbar()
2378 * @brief Update statusbar info, Override from CCrystalTextView
2379 * @note we tab-expand column, but we don't tab-expand char count,
2380 * since we want to show how many chars there are and tab is just one
2381 * character although it expands to several spaces.
2383 void CMergeEditView::OnUpdateCaret()
2385 if (m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2388 CPoint cursorPos = GetCursorPos();
2389 int nScreenLine = cursorPos.y;
2390 const int nRealLine = ComputeRealLine(nScreenLine);
2397 DWORD dwLineFlags = 0;
2399 dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2400 // Is this a ghost line ?
2401 if (dwLineFlags & LF_GHOST)
2403 // Ghost lines display eg "Line 12-13"
2404 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2405 sEol = _T("hidden");
2409 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2410 sLine.Format(_T("%d"), nRealLine+1);
2411 curChar = cursorPos.x + 1;
2412 chars = GetLineLength(nScreenLine);
2413 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2414 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2416 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2417 GetDocument()->IsMixedEOL(m_nThisPane))
2419 sEol = GetTextBufferEol(nScreenLine);
2422 sEol = _T("hidden");
2424 m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2425 curChar, chars, sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2427 // Is cursor inside difference?
2428 if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2429 m_bCurrentLineIsDiff = true;
2431 m_bCurrentLineIsDiff = false;
2433 UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2436 * @brief Select linedifference in the current line.
2438 * Select line difference in current line. Selection type
2439 * is choosed by highlight type.
2441 template<bool reversed>
2442 void CMergeEditView::OnSelectLineDiff()
2444 // Pass this to the document, to compare this file to other
2445 GetDocument()->Showlinediff(this, reversed);
2448 /// Enable select difference menuitem if current line is inside difference.
2449 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2451 int line = GetCursorPos().y;
2452 bool enable = ((GetLineFlags(line) & (LF_DIFF | LF_GHOST)) != 0);
2453 if (GetDocument()->IsEditedAfterRescan(m_nThisPane))
2455 pCmdUI->Enable(enable);
2459 * @brief Enable/disable Replace-menuitem
2461 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2463 CMergeDoc *pd = GetDocument();
2464 bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2466 pCmdUI->Enable(!bReadOnly);
2470 * @brief Update readonly statusbaritem
2472 void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
2474 bool bRO = GetDocument()->m_ptBuf[pCmdUI->m_nID - ID_STATUS_PANE0FILE_RO]->GetReadOnly();
2475 pCmdUI->Enable(bRO);
2479 * @brief Create the dynamic submenu for scripts
2481 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
2484 std::vector<String> functionNamesList;
2485 FileTransform::GetFreeFunctionsInScripts(functionNamesList, L"EDITOR_SCRIPT");
2488 size_t i = GetMenuItemCount(hMenu);
2490 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2492 if (functionNamesList.size() == 0)
2494 // no script : create a <empty> entry
2495 AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, _("< Empty >").c_str());
2499 // or fill in the submenu with the scripts names
2500 int ID = ID_SCRIPT_FIRST; // first ID in menu
2501 for (i = 0 ; i < functionNamesList.size() ; i++, ID++)
2502 AppendMenu(hMenu, MF_STRING, ID, functionNamesList[i].c_str());
2504 functionNamesList.clear();
2507 if (!plugin::IsWindowsScriptThere())
2508 AppendMenu(hMenu, MF_STRING, ID_NO_SCT_SCRIPTS, _("WSH not found - .sct scripts disabled").c_str());
2514 * @brief Create the dynamic submenu for prediffers
2516 * @note The plugins are grouped in (suggested) and (not suggested)
2517 * The IDs follow the order of GetAvailableScripts
2519 * suggested 0 ID_1ST + 0
2520 * suggested 1 ID_1ST + 2
2521 * suggested 2 ID_1ST + 5
2522 * not suggested 0 ID_1ST + 1
2523 * not suggested 1 ID_1ST + 3
2524 * not suggested 2 ID_1ST + 4
2526 HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
2529 int i = GetMenuItemCount(hMenu);
2531 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2533 CMergeDoc *pd = GetDocument();
2534 ASSERT(pd != nullptr);
2537 AppendMenu(hMenu, MF_STRING, ID_NO_PREDIFFER, _("No prediffer (normal)").c_str());
2539 // get the scriptlet files
2540 PluginArray * piScriptArray =
2541 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
2542 PluginArray * piScriptArray2 =
2543 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
2545 // build the menu : first part, suggested plugins
2547 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2548 AppendMenu(hMenu, MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2550 int ID = ID_PREDIFFERS_FIRST; // first ID in menu
2552 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2554 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2555 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2558 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2560 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2562 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2563 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2566 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2569 // build the menu : second part, others plugins
2571 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2572 AppendMenu(hMenu, MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2574 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2575 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2577 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2578 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2581 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2583 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2585 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2586 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2589 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2592 // compute the m_CurrentPredifferID (to set the radio button)
2593 PrediffingInfo prediffer;
2594 pd->GetPrediffer(&prediffer);
2596 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
2597 m_CurrentPredifferID = 0;
2598 else if (prediffer.m_PluginName.empty())
2599 m_CurrentPredifferID = ID_NO_PREDIFFER;
2602 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2603 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2605 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2606 if (prediffer.m_PluginName == plugin->m_name)
2607 m_CurrentPredifferID = ID;
2610 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2612 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2613 if (prediffer.m_PluginName == plugin->m_name)
2614 m_CurrentPredifferID = ID;
2622 * @brief Offer a context menu built with scriptlet/ActiveX functions
2624 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
2626 // Create the menu and populate it with the available functions
2628 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
2630 // Remove copying item copying from active side
2631 if (m_nThisPane == 0) // left?
2633 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2634 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2636 if (m_nThisPane == GetDocument()->m_nBuffers - 1)
2638 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2639 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2642 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
2643 theApp.TranslateMenu(menu.m_hMenu);
2645 BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
2646 ASSERT(pSub != nullptr);
2648 // Context menu opened using keyboard has no coordinates
2649 if (point.x == -1 && point.y == -1)
2652 GetClientRect(rect);
2653 ClientToScreen(rect);
2655 point = rect.TopLeft();
2659 pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
2660 point.x, point.y, AfxGetMainWnd());
2665 * @brief Update EOL mode in status bar
2667 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
2669 GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
2673 * @brief Change EOL mode and unify all the lines EOL to this new mode
2675 void CMergeEditView::OnConvertEolTo(UINT nID )
2677 CRLFSTYLE nStyle = CRLF_STYLE_AUTOMATIC;;
2681 nStyle = CRLF_STYLE_DOS;
2683 case ID_EOL_TO_UNIX:
2684 nStyle = CRLF_STYLE_UNIX;
2687 nStyle = CRLF_STYLE_MAC;
2691 _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
2694 m_pTextBuffer->SetCRLFMode(nStyle);
2696 // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
2697 if (m_pTextBuffer->applyEOLMode())
2699 CMergeDoc *pd = GetDocument();
2700 ASSERT(pd != nullptr);
2701 pd->UpdateHeaderPath(m_nThisPane);
2702 pd->FlushAndRescan(true);
2707 * @brief allow convert to entries in file submenu
2709 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
2711 int nStyle = CRLF_STYLE_AUTOMATIC;
2712 switch (pCmdUI->m_nID)
2715 nStyle = CRLF_STYLE_DOS;
2717 case ID_EOL_TO_UNIX:
2718 nStyle = CRLF_STYLE_UNIX;
2721 nStyle = CRLF_STYLE_MAC;
2725 _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
2729 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2730 GetDocument()->IsMixedEOL(m_nThisPane) ||
2731 nStyle != m_pTextBuffer->GetCRLFMode())
2733 pCmdUI->SetRadio(false);
2735 // Don't allow selecting other EOL style for protected pane
2736 if (IsReadOnly(m_nThisPane))
2737 pCmdUI->Enable(false);
2740 pCmdUI->SetRadio(true);
2744 * @brief Copy diff from left to right and advance to next diff
2746 void CMergeEditView::OnL2RNext()
2753 * @brief Update "Copy right and advance" UI item
2755 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
2757 OnUpdateL2r(pCmdUI);
2761 * @brief Copy diff from right to left and advance to next diff
2763 void CMergeEditView::OnR2LNext()
2770 * @brief Update "Copy left and advance" UI item
2772 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
2774 OnUpdateR2l(pCmdUI);
2778 * @brief Change active pane in MergeView.
2779 * Changes active pane and makes sure cursor position is kept in
2780 * screen. Currently we put cursor in same line than in original
2781 * active pane but we could be smarter too? Maybe update cursor
2782 * only when it is not visible in new pane?
2784 void CMergeEditView::OnChangePane()
2786 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
2787 CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
2788 CMergeDoc *pDoc = GetDocument();
2789 bool bFound = false;
2790 CMergeEditView *pNextActiveView = nullptr;
2791 std::vector<CMergeEditView *> list = pDoc->GetViewList();
2792 list.insert(list.end(), list.begin(), list.end());
2793 for (auto& pView : list)
2795 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
2797 pNextActiveView = pView;
2803 GetParentFrame()->SetActiveView(pNextActiveView);
2804 CPoint ptCursor = pWnd->GetCursorPos();
2806 if (ptCursor.y >= pNextActiveView->GetLineCount())
2807 ptCursor.y = pNextActiveView->GetLineCount() - 1;
2808 pNextActiveView->SetCursorPos(ptCursor);
2809 pNextActiveView->SetAnchor(ptCursor);
2810 pNextActiveView->SetSelection(ptCursor, ptCursor);
2814 * @brief Show "Go To" dialog and scroll views to line or diff.
2816 * Before dialog is opened, current line and file is determined
2818 * @note Conversions needed between apparent and real lines
2820 void CMergeEditView::OnWMGoto()
2823 CMergeDoc *pDoc = GetDocument();
2824 CPoint pos = GetCursorPos();
2828 nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
2829 int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
2830 nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
2832 // Set active file and current line selected in dialog
2833 dlg.m_strParam = strutils::to_str(nRealLine + 1);
2834 dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
2835 dlg.m_nGotoWhat = 0;
2837 if (dlg.DoModal() == IDOK)
2839 CMergeDoc * pDoc1 = GetDocument();
2840 CMergeEditView * pCurrentView = nullptr;
2843 pCurrentView = GetGroupView(m_nThisPane);
2846 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
2848 if (dlg.m_nGotoWhat == 0)
2850 int nRealLine1 = num;
2853 if (nRealLine1 > nLastLine)
2854 nRealLine1 = nLastLine;
2856 GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile);
2863 if (diff >= pDoc1->m_diffList.GetSize())
2864 diff = pDoc1->m_diffList.GetSize();
2866 pCurrentView->SelectDiff(diff, true, false);
2872 * @brief Reload options.
2874 void CMergeEditView::RefreshOptions()
2876 m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
2878 if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
2879 SetInsertTabs(true);
2881 SetInsertTabs(false);
2883 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
2885 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
2886 SetTextType(CCrystalTextView::SRC_PLAIN);
2888 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
2889 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
2891 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
2892 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE),
2893 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2894 GetDocument()->IsMixedEOL(m_nThisPane));
2896 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
2899 void CMergeEditView::OnScripts(UINT nID )
2901 // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
2902 String text = GetSelectedText();
2904 // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
2905 bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
2907 // now replace the text
2908 ReplaceSelection(text.c_str(), text.length(), 0);
2912 * @brief Called when an editor script item is updated
2914 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
2916 // append the scripts submenu
2917 HMENU scriptsSubmenu = dynamic_cast<CMainFrame*>(AfxGetMainWnd())->GetScriptsSubmenu(AfxGetMainWnd()->GetMenu()->m_hMenu);
2918 if (scriptsSubmenu != nullptr)
2919 createScriptsSubmenu(scriptsSubmenu);
2921 pCmdUI->Enable(true);
2925 * @brief Called when an editor script item is updated
2927 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
2929 pCmdUI->Enable(true);
2931 CMergeDoc *pd = GetDocument();
2932 ASSERT(pd != nullptr);
2933 PrediffingInfo prediffer;
2934 pd->GetPrediffer(&prediffer);
2936 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
2938 pCmdUI->SetRadio(false);
2942 // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
2943 if (prediffer.m_PluginName.empty())
2944 m_CurrentPredifferID = ID_NO_PREDIFFER;
2946 pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
2950 * @brief Update "Prediffer" menuitem
2952 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
2954 // recreate the sub menu (to fill the "selected prediffers")
2955 GetMainFrame()->UpdatePrediffersMenu();
2959 void CMergeEditView::OnNoPrediffer()
2961 OnPrediffer(ID_NO_PREDIFFER);
2964 * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
2966 void CMergeEditView::OnPrediffer(UINT nID )
2968 CMergeDoc *pd = GetDocument();
2969 ASSERT(pd != nullptr);
2971 SetPredifferByMenu(nID);
2972 pd->FlushAndRescan(true);
2976 * @brief Handler for all prediffer choices.
2977 * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
2978 * ID_NO_PREDIFFER, & specific prediffers.
2980 void CMergeEditView::SetPredifferByMenu(UINT nID )
2982 CMergeDoc *pd = GetDocument();
2983 ASSERT(pd != nullptr);
2985 if (nID == ID_NO_PREDIFFER)
2987 m_CurrentPredifferID = nID;
2988 // All flags are set correctly during the construction
2989 PrediffingInfo *infoPrediffer = new PrediffingInfo;
2990 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MANUAL;
2991 infoPrediffer->m_PluginName.clear();
2992 pd->SetPrediffer(infoPrediffer);
2993 pd->FlushAndRescan(true);
2997 // get the scriptlet files
2998 PluginArray * piScriptArray =
2999 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3000 PluginArray * piScriptArray2 =
3001 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3003 // build a PrediffingInfo structure fom the ID
3004 PrediffingInfo prediffer;
3005 prediffer.m_PluginOrPredifferMode = PLUGIN_MANUAL;
3007 size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3008 if (pluginNumber < piScriptArray->size())
3010 prediffer.m_bWithFile = true;
3011 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3012 prediffer.m_PluginName = plugin->m_name;
3016 pluginNumber -= piScriptArray->size();
3017 if (pluginNumber >= piScriptArray2->size())
3019 prediffer.m_bWithFile = false;
3020 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3021 prediffer.m_PluginName = plugin->m_name;
3024 // update data for the radio button
3025 m_CurrentPredifferID = nID;
3027 // update the prediffer and rescan
3028 pd->SetPrediffer(&prediffer);
3032 * @brief Look through available prediffers, and return ID of requested one, if found
3034 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3037 int ID = ID_PREDIFFERS_FIRST;
3039 // Search file prediffers
3040 PluginArray * piScriptArray =
3041 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3042 for (i=0; i<piScriptArray->size(); ++i, ++ID)
3044 const PluginInfoPtr & plugin = piScriptArray->at(i);
3045 if (plugin->m_name == prediffer)
3049 // Search buffer prediffers
3050 PluginArray * piScriptArray2 =
3051 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3052 for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3054 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3055 if (plugin->m_name == prediffer)
3063 * @brief Look through available prediffers, and return ID of requested one, if found
3065 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3067 int id = FindPrediffer(prediffer);
3068 if (id<0) return false;
3069 SetPredifferByMenu(id);
3074 * @brief Goto given line.
3075 * @param [in] nLine Destination linenumber
3076 * @param [in] bRealLine if true linenumber is real line, otherwise
3077 * it is apparent line (including deleted lines)
3078 * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3080 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane)
3082 CMergeDoc *pDoc = GetDocument();
3083 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3084 CMergeEditView *pCurrentView = nullptr;
3085 if (pSplitterWnd != nullptr)
3086 pCurrentView = static_cast<CMergeEditView*>
3087 (pSplitterWnd->GetActivePane());
3089 int nRealLine = nLine;
3090 int nApparentLine = nLine;
3092 // Compute apparent (shown linenumber) line
3095 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3096 nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3098 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3102 ptPos.y = nApparentLine;
3104 // Scroll line to center of view
3105 int nScrollLine = GetSubLineIndex(nApparentLine);
3106 nScrollLine -= GetScreenLines() / 2;
3107 if (nScrollLine < 0)
3110 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3112 CMergeEditView *pView = GetGroupView(nPane);
3113 pView->ScrollToSubLine(nScrollLine);
3114 if (ptPos.y < pView->GetLineCount())
3116 pView->SetCursorPos(ptPos);
3117 pView->SetAnchor(ptPos);
3121 CPoint ptPos1(0, pView->GetLineCount() - 1);
3122 pView->SetCursorPos(ptPos1);
3123 pView->SetAnchor(ptPos1);
3127 // If goto target is another view - activate another view.
3128 // This is done for user convenience as user probably wants to
3129 // work with goto target file.
3130 if (GetGroupView(pane) != pCurrentView)
3132 if (pSplitterWnd != nullptr)
3134 if (pSplitterWnd->GetColumnCount() > 1)
3135 pSplitterWnd->SetActivePane(0, pane);
3137 pSplitterWnd->SetActivePane(pane, 0);
3143 * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3146 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3148 if (pScrollBar == nullptr)
3150 // Scroll did not come frome a scroll bar
3151 // Find the appropriate scroll bar
3152 // and send the message to the splitter window instead
3153 // The event should eventually come back here but with a valid scrollbar
3154 // Along the way it will be propagated to other windows that need it
3155 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3156 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3157 pSplitterWnd->SendMessage(WM_HSCROLL,
3158 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3161 CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3165 * @brief When view is scrolled using scrollbars update location pane.
3167 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3169 if (pScrollBar == nullptr)
3171 // Scroll did not come frome a scroll bar
3172 // Find the appropriate scroll bar
3173 // and send the message to the splitter window instead
3174 // The event should eventually come back here but with a valid scrollbar
3175 // Along the way it will be propagated to other windows that need it
3176 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3177 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3178 pSplitterWnd->SendMessage(WM_VSCROLL,
3179 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3182 CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3184 if (nSBCode == SB_ENDSCROLL)
3187 // Note we cannot use nPos because of its 16-bit nature
3188 SCROLLINFO si = {0};
3189 si.cbSize = sizeof (si);
3190 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3191 VERIFY (GetScrollInfo (SB_VERT, &si));
3193 // Get the current position of scroll box.
3194 int nCurPos = si.nPos;
3196 UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3200 * @brief Copy selected lines adding linenumbers.
3202 void CMergeEditView::OnEditCopyLineNumbers()
3210 CMergeDoc *pDoc = GetDocument();
3211 GetSelection(ptStart, ptEnd);
3213 // Get last selected line (having widest linenumber)
3214 int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3215 size_t nNumWidth = strutils::to_str(line + 1).length();
3217 for (int i = ptStart.y; i <= ptEnd.y; i++)
3219 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3222 // We need to convert to real linenumbers
3223 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3225 // Insert spaces to align different width linenumbers (99, 100)
3226 strLine = GetLineText(i);
3227 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3230 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3231 strText += strNumLine;
3233 PutToClipboard(strText, strText.GetLength(), m_bColumnSelection);
3236 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3238 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3242 * @brief Open active file with associated application.
3244 * First tries to open file using shell 'Edit' action, since that
3245 * action open scripts etc. to editor instead of running them. If
3246 * edit-action is not registered, 'Open' action is used.
3248 void CMergeEditView::OnOpenFile()
3250 CMergeDoc * pDoc = GetDocument();
3251 ASSERT(pDoc != nullptr);
3253 String sFileName = pDoc->m_filePaths[m_nThisPane];
3254 if (sFileName.empty())
3256 HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
3257 0, 0, SW_SHOWNORMAL);
3258 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3259 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
3260 0, 0, SW_SHOWNORMAL);
3261 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3266 * @brief Open active file with app selection dialog
3268 void CMergeEditView::OnOpenFileWith()
3270 CMergeDoc * pDoc = GetDocument();
3271 ASSERT(pDoc != nullptr);
3273 String sFileName = pDoc->m_filePaths[m_nThisPane];
3274 if (sFileName.empty())
3278 if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
3280 sysdir.ReleaseBuffer();
3281 CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
3282 ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
3283 sysdir, SW_SHOWNORMAL);
3287 * @brief Open active file with external editor
3289 void CMergeEditView::OnOpenFileWithEditor()
3291 CMergeDoc * pDoc = GetDocument();
3292 ASSERT(pDoc != nullptr);
3294 String sFileName = pDoc->m_filePaths[m_nThisPane];
3295 if (sFileName.empty())
3298 int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3299 theApp.OpenFileToExternalEditor(sFileName.c_str(), nRealLine);
3303 * @brief Force repaint of the location pane.
3305 void CMergeEditView::RepaintLocationPane()
3307 // Must force recalculation due to caching of data in location pane.
3308 CLocationView *pLocationView = GetDocument()->GetLocationView();
3309 if (pLocationView != nullptr)
3310 pLocationView->ForceRecalculate();
3314 * @brief Enables/disables linediff (different color for diffs)
3316 void CMergeEditView::OnViewLineDiffs()
3318 bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3319 GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3321 // Call CMergeDoc RefreshOptions() to refresh *both* views
3322 CMergeDoc *pDoc = GetDocument();
3323 pDoc->RefreshOptions();
3324 pDoc->FlushAndRescan(true);
3327 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3329 pCmdUI->Enable(true);
3330 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3334 * @brief Enables/disables line number
3336 void CMergeEditView::OnViewLineNumbers()
3338 GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3340 // Call CMergeDoc RefreshOptions() to refresh *both* views
3341 CMergeDoc *pDoc = GetDocument();
3342 pDoc->RefreshOptions();
3345 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3347 pCmdUI->Enable(true);
3348 pCmdUI->SetCheck(GetViewLineNumbers());
3352 * @brief Enables/disables word wrap
3354 void CMergeEditView::OnViewWordWrap()
3356 GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
3358 // Call CMergeDoc RefreshOptions() to refresh *both* views
3359 CMergeDoc *pDoc = GetDocument();
3360 pDoc->RefreshOptions();
3361 pDoc->UpdateAllViews(this);
3366 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3368 pCmdUI->Enable(true);
3369 pCmdUI->SetCheck(m_bWordWrap);
3372 void CMergeEditView::OnViewWhitespace()
3374 GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3376 // Call CMergeDoc RefreshOptions() to refresh *both* views
3377 CMergeDoc *pDoc = GetDocument();
3378 pDoc->RefreshOptions();
3381 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI)
3383 pCmdUI->SetCheck(GetViewTabs());
3386 void CMergeEditView::OnSize(UINT nType, int cx, int cy)
3388 if (!IsInitialized())
3391 CMergeDoc * pDoc = GetDocument();
3392 if (m_nThisPane < pDoc->m_nBuffers - 1)
3394 // To calculate subline index correctly
3395 // we have to invalidate line cache in all pane before calling the function related the subline.
3396 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3398 CMergeEditView *pView = GetGroupView(nPane);
3399 if (pView != nullptr)
3400 pView->InvalidateScreenRect(false);
3405 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3407 CMergeEditView *pView = GetGroupView(nPane);
3408 if (pView != nullptr)
3409 pView->Invalidate();
3412 // recalculate m_nTopSubLine
3413 m_nTopSubLine = GetSubLineIndex(m_nTopLine);
3417 RecalcVertScrollBar (false, false);
3418 RecalcHorzScrollBar (false, false);
3422 * @brief allocates GDI resources for printing
3423 * @param pDC [in] points to the printer device context
3424 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3426 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
3428 GetParentFrame()->PostMessage(WM_TIMER);
3430 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3432 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
3433 pView->m_bPrintHeader = true;
3434 pView->m_bPrintFooter = true;
3435 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
3440 * @brief frees GDI resources for printing
3441 * @param pDC [in] points to the printer device context
3442 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3444 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
3446 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3447 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
3449 GetParentFrame()->PostMessage(WM_TIMER);
3453 * @brief Gets header text to print
3454 * @param [in] nPageNum the page number to print
3455 * @param [out] header text to print
3457 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
3459 text = GetDocument()->GetTitle();
3463 * @brief Prints header
3464 * @param [in] nPageNum the page number to print
3466 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
3468 if (m_nThisPane > 0)
3470 int oldRight = m_rcPrintArea.right;
3471 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3472 CGhostTextView::PrintHeader(pdc, nPageNum);
3473 m_rcPrintArea.right = oldRight;
3477 * @brief Prints footer
3478 * @param [in] nPageNum the page number to print
3480 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
3482 if (m_nThisPane > 0)
3484 int oldRight = m_rcPrintArea.right;
3485 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3486 CGhostTextView::PrintFooter(pdc, nPageNum);
3487 m_rcPrintArea.right = oldRight;
3490 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
3492 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3493 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
3497 * @brief Prints or previews both panes.
3498 * @param pDC [in] points to the printer device context
3499 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3501 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
3503 CRect rDraw = pInfo->m_rectDraw;
3504 CSize sz = rDraw.Size();
3505 CMergeDoc *pDoc = GetDocument();
3507 SIZE szLeftTop, szRightBottom;
3508 GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
3509 pDC->HIMETRICtoLP(&szLeftTop);
3510 pDC->HIMETRICtoLP(&szRightBottom);
3512 int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
3515 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
3517 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
3518 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
3519 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
3520 pPane->CGhostTextView::OnPrint(pDC, pInfo);
3524 bool CMergeEditView::IsInitialized() const
3526 CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
3527 CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
3528 return pBuffer->IsInitialized();
3532 * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
3534 int CMergeEditView::GetEmptySubLines( int nLineIndex )
3536 int nBreaks[3] = {0};
3537 int nMaxBreaks = -1;
3538 CMergeDoc * pDoc = GetDocument();
3539 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3541 CMergeEditView *pView = GetGroupView(nPane);
3542 if (pView != nullptr)
3544 if (nLineIndex >= pView->GetLineCount())
3546 pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
3548 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
3551 if (nBreaks[m_nThisPane] < nMaxBreaks)
3552 return nMaxBreaks - nBreaks[m_nThisPane];
3558 * @brief Invalidate sub line index cache from the specified index to the end of file.
3559 * @param [in] nLineIndex Index of the first line to invalidate
3561 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
3563 CMergeDoc * pDoc = GetDocument();
3564 ASSERT(pDoc != nullptr);
3566 // We have to invalidate sub line index cache on both panes.
3567 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3569 CMergeEditView *pView = GetGroupView(nPane);
3570 if (pView != nullptr)
3571 pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
3575 void CMergeEditView::SetWordWrapping( bool bWordWrap )
3577 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3578 GetGroupView(pane)->m_bWordWrap = bWordWrap;
3579 CCrystalTextView::SetWordWrapping(bWordWrap);
3583 * @brief Swap the positions of the two panes
3585 void CMergeEditView::OnViewSwapPanes()
3587 GetDocument()->SwapFiles();
3591 * @brief Check if cursor is inside difference.
3592 * @return true if cursor is inside difference.
3594 bool CMergeEditView::IsCursorInDiff() const
3596 return m_bCurrentLineIsDiff;
3600 * @brief Determine if difference is visible on screen.
3601 * @param [in] nDiff Number of diff to check.
3602 * @return true if difference is visible.
3604 bool CMergeEditView::IsDiffVisible(int nDiff)
3606 const CMergeDoc *pd = GetDocument();
3609 pd->m_diffList.GetDiff(nDiff, diff);
3611 return IsDiffVisible(diff);
3615 * @brief Determine if difference is visible on screen.
3616 * @param [in] diff diff to check.
3617 * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
3618 * @return true if difference is visible, false otherwise.
3620 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
3622 const int nDiffStart = GetSubLineIndex(diff.dbegin);
3623 const int nDiffEnd = GetSubLineIndex(diff.dend);
3624 // Diff's height is last line - first line + last line's line count
3625 const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
3627 // If diff first line outside current view - context OR
3628 // if diff last line outside current view - context OR
3629 // if diff is bigger than screen
3630 if ((nDiffStart < m_nTopSubLine) ||
3631 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
3632 (nDiffHeight >= GetScreenLines()))
3642 /** @brief Open help from mainframe when user presses F1*/
3643 void CMergeEditView::OnHelp()
3645 theApp.ShowHelp(MergeViewHelpLocation);
3649 * @brief Called after document is loaded.
3650 * This function is called from CMergeDoc::OpenDocs() after documents are
3651 * loaded. So this is good place to set View's options etc.
3653 void CMergeEditView::DocumentsLoaded()
3655 // Enable/disable automatic rescan (rescanning after edit)
3656 EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
3658 // SetTextType will revert to language dependent defaults for tab
3659 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
3660 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3661 const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3662 GetDocument()->IsMixedEOL(m_nThisPane);
3663 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE), mixedEOLs);
3664 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3665 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3666 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3668 // Enable Backspace at beginning of line
3669 SetDisableBSAtSOL(false);
3671 // Set tab type (tabs/spaces)
3672 bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
3673 SetInsertTabs(bInsertTabs);
3675 // Sometimes WinMerge doesn't update scrollbars correctly (they remain
3676 // disabled) after docs are open in screen. So lets make sure they are
3677 // really updated, even though this is unnecessary in most cases.
3678 RecalcHorzScrollBar();
3679 RecalcVertScrollBar();
3683 * @brief Update LocationView position.
3684 * This function updates LocationView position to given lines.
3685 * Usually we want to lines in file compare view and area in
3686 * LocationView to match. Be extra carefull to not call non-existing
3688 * @param [in] nTopLine Top line of current view.
3689 * @param [in] nBottomLine Bottom line of current view.
3691 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
3692 int nBottomLine /*= -1*/)
3694 CMergeDoc *pDoc = GetDocument();
3695 if (pDoc == nullptr)
3698 CLocationView *pLocationView = pDoc->GetLocationView();
3700 if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
3702 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
3707 * @brief Enable/Disable view's selection margins.
3708 * Selection margins show bookmarks and word-wrap symbols, so they are pretty
3709 * useful. But it appears many users don't use/need those features and for them
3710 * selection margins are just wasted screen estate.
3712 void CMergeEditView::OnViewMargin()
3714 bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
3715 GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
3717 SetSelectionMargin(!bViewMargin);
3718 CMergeDoc *pDoc = GetDocument();
3719 pDoc->RefreshOptions();
3720 pDoc->UpdateAllViews(this);
3724 * @brief Update GUI for Enable/Disable view's selection margin.
3725 * @param [in] pCmdUI Pointer to UI item to update.
3727 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
3729 pCmdUI->Enable(true);
3730 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3734 * @brief Create the "Change Scheme" sub menu.
3735 * @param [in] pCmdUI Pointer to UI item to update.
3737 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
3739 // Delete the place holder menu.
3740 pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
3742 const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
3744 String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
3745 AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
3746 AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
3748 for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
3750 name = theApp.LoadString(i);
3751 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
3754 pCmdUI->Enable(true);
3758 * @brief Change the editor's syntax highlighting scheme.
3759 * @param [in] nID Selected color scheme sub menu id.
3761 void CMergeEditView::OnChangeScheme(UINT nID)
3763 CMergeDoc *pDoc = GetDocument();
3764 ASSERT(pDoc != nullptr);
3766 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3768 CMergeEditView *pView = GetGroupView(nPane);
3769 ASSERT(pView != nullptr);
3771 if (pView != nullptr)
3773 pView->SetTextType(CCrystalTextView::TextType(nID - ID_COLORSCHEME_FIRST));
3774 pView->SetDisableBSAtSOL(false);
3778 pDoc->UpdateAllViews(nullptr);
3782 * @brief Enable all color schemes sub menu items.
3783 * @param [in] pCmdUI Pointer to UI item to update.
3785 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
3787 const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
3788 pCmdUI->SetRadio(bIsCurrentScheme);
3789 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
3793 * @brief Called when mouse's wheel is scrolled.
3795 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
3797 if ( nFlags == MK_CONTROL )
3799 short amount = zDelta < 0 ? -1: 1;
3802 // no default CCrystalTextView
3803 return CView::OnMouseWheel(nFlags, zDelta, pt);
3806 if (nFlags == MK_SHIFT)
3808 SCROLLINFO si = { sizeof SCROLLINFO };
3809 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
3811 VERIFY(GetScrollInfo(SB_HORZ, &si));
3814 si.nPos -= zDelta / 40;
3815 if (si.nPos > si.nMax) si.nPos = si.nMax;
3816 if (si.nPos < si.nMin) si.nPos = si.nMin;
3818 SetScrollInfo(SB_HORZ, &si);
3821 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
3823 // no default CCrystalTextView
3824 return CView::OnMouseWheel(nFlags, zDelta, pt);
3827 return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
3831 * @brief Change font size (zoom) in views.
3832 * @param [in] amount Amount of change/zoom, negative number makes
3833 * font smaller, positive number bigger and 0 reset the font size.
3835 void CMergeEditView::ZoomText(short amount)
3840 const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
3841 int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
3845 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
3848 nPointSize += amount;
3852 lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
3854 CMergeDoc *pDoc = GetDocument();
3855 ASSERT(pDoc != nullptr);
3857 if (pDoc != nullptr)
3859 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3861 CMergeEditView *pView = GetGroupView(nPane);
3862 ASSERT(pView != nullptr);
3864 if (pView != nullptr)
3873 * @brief Called when user selects View/Zoom In from menu.
3875 void CMergeEditView::OnViewZoomIn()
3881 * @brief Called when user selects View/Zoom Out from menu.
3883 void CMergeEditView::OnViewZoomOut()
3889 * @brief Called when user selects View/Zoom Normal from menu.
3891 void CMergeEditView::OnViewZoomNormal()
3896 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
3898 if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
3900 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
3904 GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
3907 void CMergeEditView::OnWindowSplit()
3910 auto& wndSplitter = dynamic_cast<CChildFrame *>(GetParentFrame())->GetSplitter();
3911 CMergeDoc *pDoc = GetDocument();
3912 CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
3913 auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
3914 int nBuffer = m_nThisPane;
3915 if (pDoc->m_nGroups <= 2)
3917 wndSplitter.SplitRow(1);
3918 wndSplitter.EqualizeRows();
3922 wndSplitter.SetActivePane(0, 0);
3923 wndSplitter.DeleteRow(1);
3924 if (pwndSplitterChild->GetColumnCount() > 1)
3925 pwndSplitterChild->SetActivePane(0, nBuffer);
3927 pwndSplitterChild->SetActivePane(nBuffer, 0);
3931 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
3933 pCmdUI->Enable(TRUE);
3934 pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);