1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: An interactive diff/merge utility
3 // Copyright (C) 1997 Dean P. Grimm
4 // SPDX-License-Identifier: GPL-2.0-or-later
5 /////////////////////////////////////////////////////////////////////////////
8 * @file LocationView.cpp
10 * @brief Implementation file for CLocationView
16 #include "LocationView.h"
19 #include "OptionsMgr.h"
20 #include "MergeEditView.h"
23 #include "OptionsDef.h"
26 #include "SyntaxColors.h"
34 /** @brief Size of empty frame above and below bars (in pixels). */
35 static const int Y_OFFSET = 5;
36 /** @brief Max pixels in view per line in file. */
37 static const double MAX_LINEPIX = 4.0;
38 /** @brief Top of difference marker, relative to difference start. */
39 static const int DIFFMARKER_TOP = 3;
40 /** @brief Bottom of difference marker, relative to difference start. */
41 static const int DIFFMARKER_BOTTOM = 3;
42 /** @brief Width of difference marker. */
43 static const int DIFFMARKER_WIDTH = 6;
44 /** @brief Minimum height of the visible area indicator */
45 static const int INDICATOR_MIN_HEIGHT = 2;
48 * @brief Bars in location pane
52 BAR_NONE = -1, /**< No bar in given coords */
53 BAR_0, /**< first bar in given coords */
54 BAR_1, /**< second bar in given coords */
55 BAR_2, /**< third side bar in given coords */
56 BAR_YAREA, /**< Y-Coord in bar area */
59 /////////////////////////////////////////////////////////////////////////////
62 IMPLEMENT_DYNCREATE(CLocationView, CView)
65 CLocationView::CLocationView()
68 // MOVEDLINE_LIST m_movedLines; //*< List of moved block connecting lines */
69 , m_hwndFrame(nullptr)
70 , m_pSavedBackgroundBitmap(nullptr)
72 , m_bRecalculateBlocks(true) // calculate for the first time
74 // NB: set m_bIgnoreTrivials to false to see trivial diffs in the LocationView
75 // There is no GUI to do this
77 SetConnectMovedBlocks(GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS));
79 std::fill_n(m_nSubLineCount, std::size(m_nSubLineCount), 0);
82 CLocationView::~CLocationView()
86 BEGIN_MESSAGE_MAP(CLocationView, CView)
87 //{{AFX_MSG_MAP(CLocationView)
103 void CLocationView::SetConnectMovedBlocks(bool displayMovedBlocks)
105 if (m_displayMovedBlocks == displayMovedBlocks)
108 m_displayMovedBlocks = displayMovedBlocks;
109 if (this->GetSafeHwnd() != nullptr)
110 if (IsWindowVisible())
114 /////////////////////////////////////////////////////////////////////////////
115 // CLocationView diagnostics
117 CMergeDoc* CLocationView::GetDocument() // non-debug version is inline
119 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
120 return (CMergeDoc*)m_pDocument;
124 /////////////////////////////////////////////////////////////////////////////
125 // CLocationView message handlers
128 * @brief Force recalculation and update of location pane.
129 * This method forces location pane to first recalculate its data and
130 * then repaint itself. This method bypasses location pane's caching
133 void CLocationView::ForceRecalculate()
135 m_bRecalculateBlocks = true;
140 * @brief Update view.
142 void CLocationView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )
144 UNREFERENCED_PARAMETER(pSender);
145 UNREFERENCED_PARAMETER(lHint);
147 m_bRecalculateBlocks = true;
152 * @brief Override for CMemDC to work.
154 BOOL CLocationView::OnEraseBkgnd(CDC* pDC)
159 static bool IsColorDark(COLORREF clrBackground)
161 return !(GetRValue(clrBackground) >= 0x80 || GetGValue(clrBackground) >= 0x80 || GetBValue(clrBackground) >= 0x80);
165 COLORREF CLocationView::GetBackgroundColor()
167 COLORREF clrBackground = GetDocument()->GetView(0, 0)->GetColor(COLORINDEX_WHITESPACE);
168 if (!IsColorDark(clrBackground))
171 (std::max)(GetRValue(clrBackground) - 24, 0),
172 (std::max)(GetGValue(clrBackground) - 24, 0),
173 (std::max)(GetBValue(clrBackground) - 12, 0));
176 (std::min)(GetRValue(clrBackground) + 20, 255),
177 (std::min)(GetGValue(clrBackground) + 20, 255),
178 (std::min)(GetBValue(clrBackground) + 32, 255));
182 * @brief Draw custom (non-white) background.
183 * @param [in] pDC Pointer to draw context.
185 void CLocationView::DrawBackground(CDC* pDC)
187 // Set brush to desired background color
188 CBrush backBrush(GetBackgroundColor());
191 CBrush* pOldBrush = pDC->SelectObject(&backBrush);
194 pDC->GetClipBox(&rect); // Erase the area needed
196 pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
198 pDC->SelectObject(pOldBrush);
202 * @brief Calculate bar coordinates and scaling factors.
204 void CLocationView::CalculateBars()
206 CMergeDoc *pDoc = GetDocument();
209 const int w = rc.Width() / (pDoc->m_nBuffers * 2);
210 const int margin = (rc.Width() - w * pDoc->m_nBuffers) / (pDoc->m_nBuffers + 1);
212 for (pane = 0; pane < pDoc->m_nBuffers; pane++)
214 m_bar[pane].left = pane * (w + margin) + margin;
215 m_bar[pane].right = m_bar[pane].left + w;
216 } const double hTotal = rc.Height() - (2 * Y_OFFSET); // Height of draw area
218 pDoc->ForEachActiveGroupView([&](auto& pView) {
219 nbLines = max(nbLines, pView->GetSubLineCount());
222 m_lineInPix = hTotal / nbLines;
223 m_pixInLines = nbLines / hTotal;
224 if (m_lineInPix > MAX_LINEPIX)
226 m_lineInPix = MAX_LINEPIX;
227 m_pixInLines = 1 / MAX_LINEPIX;
230 for (pane = 0; pane < pDoc->m_nBuffers; pane++)
232 m_bar[pane].top = Y_OFFSET - 1;
233 m_bar[pane].bottom = (LONG)(m_lineInPix * nbLines + Y_OFFSET + 1);
238 * @brief Calculate difference lines and coordinates.
239 * This function calculates begin- and end-lines of differences when word-wrap
240 * is enabled. Otherwise the value from original difflist is used. Line
241 * numbers are also converted to coordinates in the window. All calculated
242 * (and not ignored) differences are added to the new list.
244 void CLocationView::CalculateBlocks()
246 // lineposition in pixels.
250 m_diffBlocks.clear();
252 CMergeDoc *pDoc = GetDocument();
253 const int nDiffs = pDoc->m_diffList.GetSize();
255 m_diffBlocks.reserve(nDiffs); // Pre-allocate space for the list.
257 int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
258 int nLineCount = pDoc->GetView(nGroup, 0)->GetLineCount();
259 int nDiff = pDoc->m_diffList.FirstSignificantDiff();
263 VERIFY(pDoc->m_diffList.GetDiff(nDiff, diff));
265 CMergeEditView *pView = pDoc->GetView(nGroup, 0);
270 int minY = INT_MAX, maxY = -1;
272 bs[nBlocks++] = diff.dbegin;
273 for (i = 0; i < pDoc->m_nBuffers; i++)
275 if (diff.blank[i] >= 0)
277 if (minY > diff.blank[i])
278 minY = diff.blank[i];
279 if (maxY < diff.blank[i])
280 maxY = diff.blank[i];
285 bs[nBlocks++] = minY;
289 bs[nBlocks++] = minY;
290 bs[nBlocks++] = maxY;
292 bs[nBlocks] = diff.dend + 1;
293 if (bs[nBlocks] >= nLineCount)
294 bs[nBlocks] = nLineCount - 1;
296 for (i = 0; i < nBlocks; i++)
298 CalculateBlocksPixel(
299 pView->GetSubLineIndex(bs[i]),
300 pView->GetSubLineIndex(bs[i + 1]),
301 pView->GetSubLines(bs[i + 1]), nBeginY, nEndY);
303 block.top_line = bs[i];
304 block.bottom_line = bs[i + 1];
305 block.top_coord = nBeginY;
306 block.bottom_coord = nEndY;
307 block.diff_index = nDiff;
309 m_diffBlocks.push_back(block);
312 nDiff = pDoc->m_diffList.NextSignificantDiff(nDiff);
314 m_bRecalculateBlocks = false;
318 * @brief Calculate Blocksize to pixel.
319 * @param [in] nBlockStart line where block starts
320 * @param [in] nBlockEnd line where block ends
321 * @param [in] nBlockLength length of the block
322 * @param [in,out] nBeginY pixel in y where block starts
323 * @param [in,out] nEndY pixel in y where block ends
326 void CLocationView::CalculateBlocksPixel(int nBlockStart, int nBlockEnd,
327 int nBlockLength, int &nBeginY, int &nEndY)
329 // Count how many line does the diff block have.
330 const int nBlockHeight = nBlockEnd - nBlockStart + nBlockLength;
332 // Convert diff block size from lines to pixels.
333 nBeginY = (int)(nBlockStart * m_lineInPix + Y_OFFSET);
334 nEndY = (int)((nBlockStart + nBlockHeight) * m_lineInPix + Y_OFFSET);
337 static COLORREF GetIntermediateColor(COLORREF a, COLORREF b, float ratio)
339 const int R = static_cast<int>((GetRValue(a) - GetRValue(b)) * ratio) + GetRValue(b);
340 const int G = static_cast<int>((GetGValue(a) - GetGValue(b)) * ratio) + GetGValue(b);
341 const int B = static_cast<int>((GetBValue(a) - GetBValue(b)) * ratio) + GetBValue(b);
345 static COLORREF GetDarkenColor(COLORREF a, double l)
347 const int R = static_cast<int>(GetRValue(a) * l);
348 const int G = static_cast<int>(GetGValue(a) * l);
349 const int B = static_cast<int>(GetBValue(a) * l);
354 * @brief Draw maps of files.
356 * Draws maps of differences in files. Difference list is walked and
357 * every difference is drawn with same colors as in editview.
358 * @note We MUST use doubles when calculating coords to avoid rounding
359 * to integers. Rounding causes miscalculation of coords.
360 * @sa CLocationView::DrawRect()
362 void CLocationView::OnDraw(CDC* pDC)
364 CMergeDoc *pDoc = GetDocument();
365 int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
366 if (pDoc->GetView(nGroup, 0) == nullptr)
369 if (!pDoc->GetView(nGroup, 0)->IsInitialized()) return;
371 bool bEditedAfterRescan = false;
372 int nPaneNotModified = -1;
373 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
375 if (!pDoc->IsEditedAfterRescan(pane))
376 nPaneNotModified = pane;
378 bEditedAfterRescan = true;
384 CMyMemDC dc(pDC, &rc);
386 COLORREF cr[3] = {CLR_NONE, CLR_NONE, CLR_NONE};
387 COLORREF crt = CLR_NONE; // Text color
390 m_movedLines.RemoveAll();
395 COLORREF clrFace = GetBackgroundColor();
396 COLORREF clrShadow = GetSysColor(COLOR_BTNSHADOW);
397 COLORREF clrShadow2 = GetIntermediateColor(clrFace, clrShadow, 0.9f);
398 COLORREF clrShadow3 = GetIntermediateColor(clrFace, clrShadow2, 0.5f);
399 COLORREF clrShadow4 = GetIntermediateColor(clrFace, clrShadow3, 0.5f);
400 COLORREF clrShadow5 = GetIntermediateColor(clrFace, clrShadow4, 0.5f);
403 CPen* oldObj = (CPen*)dc.SelectStockObject(NULL_PEN);
404 CBrush brush(pDoc->GetView(nGroup, 0)->GetColor(COLORINDEX_WHITESPACE));
405 CBrush* oldBrush = (CBrush*)dc.SelectObject(&brush);
406 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
408 CBrush *pOldBrush = nullptr;
409 if (pDoc->IsEditedAfterRescan(pane))
410 pOldBrush = (CBrush *)dc.SelectStockObject(HOLLOW_BRUSH);
411 dc.Rectangle(m_bar[pane]);
412 if (pOldBrush != nullptr)
413 dc.SelectObject(pOldBrush);
415 CRect rect = m_bar[pane];
416 rect.InflateRect(1, 1);
417 dc.Draw3dRect(rect, clrShadow5, clrShadow4);
418 rect.InflateRect(-1, -1);
419 dc.Draw3dRect(rect, clrShadow3, clrShadow2);
421 dc.SelectObject(oldBrush);
422 dc.SelectObject(oldBrush);
423 dc.SelectObject(oldObj);
425 // Iterate the differences list and draw differences as colored blocks.
427 // Don't recalculate blocks if we earlier determined it is not needed
428 // This may save lots of processing
429 if (m_bRecalculateBlocks)
432 unsigned nPrevEndY = static_cast<unsigned>(-1);
433 const unsigned nCurDiff = pDoc->GetCurrentDiff();
436 pDoc->m_diffList.GetDiff(nCurDiff, diCur);
438 vector<DiffBlock>::const_iterator iter = m_diffBlocks.begin();
439 for (; iter != m_diffBlocks.end(); ++iter)
441 if (nPaneNotModified == -1)
443 CMergeEditView *pView = pDoc->GetView(nGroup, nPaneNotModified);
444 const bool bInsideDiff = (nCurDiff == (*iter).diff_index);
446 if ((nPrevEndY != (*iter).bottom_coord) || bInsideDiff)
448 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
450 if (pDoc->IsEditedAfterRescan(pane))
452 // Draw 3way-diff state
453 if (pDoc->m_nBuffers == 3 && pane < 2)
455 CRect r(m_bar[pane].right - 1, (*iter).top_coord, m_bar[pane + 1].left + 1, (*iter).bottom_coord);
456 if ((pane == 0 && (*iter).op == OP_3RDONLY) || (pane == 1 && (*iter).op == OP_1STONLY))
457 DrawRect(&dc, r, RGB(255, 255, 127), false);
458 else if ((*iter).op == OP_2NDONLY)
459 DrawRect(&dc, r, RGB(127, 255, 255), false);
460 else if ((*iter).op == OP_DIFF)
461 DrawRect(&dc, r, RGB(255, 0, 0), false);
464 pDoc->GetView(0, pane)->GetLineColors2((*iter).top_line, 0, cr[pane], crt, bwh);
465 CRect r(m_bar[pane].left, (*iter).top_coord, m_bar[pane].right, (*iter).bottom_coord);
466 DrawRect(&dc, r, cr[pane], bInsideDiff);
469 nPrevEndY = (*iter).bottom_coord;
471 if (bEditedAfterRescan)
474 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
476 if (m_displayMovedBlocks && pane < 2)
478 int apparent0 = (*iter).top_line;
479 int apparent1 = pDoc->RightLineInMovedBlock(pane, apparent0);
480 const int nBlockHeight = (*iter).bottom_line - (*iter).top_line + 1;
485 line.currentDiff = bInsideDiff || (nCurDiff != -1 && diCur.dbegin <= apparent1 && apparent1 <= diCur.dend);
487 apparent0 = pView->GetSubLineIndex(apparent0);
488 apparent1 = pView->GetSubLineIndex(apparent1);
490 int leftUpper = (int) (apparent0 * m_lineInPix + Y_OFFSET);
491 int leftLower = (int) ((nBlockHeight + apparent0) * m_lineInPix + Y_OFFSET - 1);
492 int rightUpper = (int) (apparent1 * m_lineInPix + Y_OFFSET);
493 int rightLower = (int) ((nBlockHeight + apparent1) * m_lineInPix + Y_OFFSET - 1);
494 line.ptLeftUpper.x = line.ptLeftLower.x = m_bar[pane].right - 1;
495 line.ptLeftUpper.y = leftUpper;
496 line.ptLeftLower.y = leftLower;
497 line.ptRightUpper.x = line.ptRightLower.x = m_bar[pane + 1].left;
498 line.ptRightUpper.y = rightUpper;
499 line.ptRightLower.y = rightLower;
500 line.apparent0 = apparent0;
501 line.apparent1 = apparent1;
502 line.blockHeight = nBlockHeight;
503 if (!m_movedLines.IsEmpty())
505 MovedLine& movedLineTail = m_movedLines.GetTail();
506 if (line.apparent0 - movedLineTail.blockHeight - 1 == movedLineTail.apparent0 &&
507 line.apparent1 - movedLineTail.blockHeight - 1 == movedLineTail.apparent1)
509 line.ptLeftUpper = movedLineTail.ptLeftUpper;
510 line.ptRightUpper = movedLineTail.ptRightUpper;
511 line.apparent0 = movedLineTail.apparent0;
512 line.apparent1 = movedLineTail.apparent1;
513 line.blockHeight += movedLineTail.blockHeight;
514 m_movedLines.RemoveTail();
517 m_movedLines.AddTail(line);
523 if (m_displayMovedBlocks)
524 DrawConnectLines(&dc);
526 m_pSavedBackgroundBitmap.reset(CopyRectToBitmap(&dc, rc));
528 // Since we have invalidated locationbar there is no previous
529 // arearect to remove
531 m_visibleBottom = -1;
532 DrawVisibleAreaRect(&dc);
538 * @brief Draw one block of map.
539 * @param [in] pDC Draw context.
540 * @param [in] r Rectangle to draw.
541 * @param [in] cr Color for rectangle.
542 * @param [in] bSelected Is rectangle for selected difference?
544 void CLocationView::DrawRect(CDC* pDC, const CRect& r, COLORREF cr, bool bSelected /*= false*/)
546 // Draw only colored blocks
547 if (cr != CLR_NONE && cr != GetSysColor(COLOR_WINDOW))
550 drawRect.DeflateRect(1, 0);
552 // With long files and small difference areas rect may become 0-height.
553 // Make sure all diffs are drawn at least one pixel height.
554 if (drawRect.Height() < 1)
556 pDC->FillSolidRect(drawRect, cr);
557 CRect drawRect2(drawRect.left, drawRect.top, drawRect.right, drawRect.top + 1);
558 pDC->FillSolidRect(drawRect2, GetDarkenColor(cr, 0.96));
559 CRect drawRect3(drawRect.left, drawRect.bottom - 1, drawRect.right, drawRect.bottom);
560 pDC->FillSolidRect(drawRect3, GetDarkenColor(cr, 0.91));
564 DrawDiffMarker(pDC, r.top);
570 * @brief Capture the mouse target.
572 void CLocationView::OnLButtonDown(UINT nFlags, CPoint point)
576 bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
577 if (!GotoLocation(point, false, !bShift))
578 CView::OnLButtonDown(nFlags, point);
582 * @brief Release the mouse target.
584 void CLocationView::OnLButtonUp(UINT nFlags, CPoint point)
588 CView::OnLButtonUp(nFlags, point);
592 * @brief Process drag action on a captured mouse.
594 * Reposition on every dragged movement.
595 * The Screen update stress will be similar to a mouse wheeling.:-)
597 void CLocationView::OnMouseMove(UINT nFlags, CPoint point)
599 if (GetCapture() == this)
601 CMergeDoc *pDoc = GetDocument();
602 int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
604 // Don't go above bars.
605 point.y = max(point.y, Y_OFFSET);
607 // Vertical scroll handlers are range safe, so there is no need to
608 // make sure value is valid and in range.
609 int nSubLine = (int) (m_pixInLines * (point.y - Y_OFFSET));
610 nSubLine -= pDoc->GetView(nGroup, 0)->GetScreenLines() / 2;
614 // Just a random choose as both view share the same scroll bar.
615 CWnd *pView = pDoc->GetView(nGroup, 0);
617 SCROLLINFO si = { sizeof SCROLLINFO };
620 pView->SetScrollInfo(SB_VERT, &si);
622 // The views are child windows of a splitter windows. Splitter window
623 // doesn't accept scroll bar updates not send from scroll bar control.
624 // So we need to update both views.
626 int nBuffers = pDoc->m_nBuffers;
627 for (pane = 0; pane < nBuffers; pane++)
628 pDoc->GetView(nGroup, pane)->SendMessage(WM_VSCROLL, MAKEWPARAM(SB_THUMBPOSITION, 0), NULL);
631 CView::OnMouseMove(nFlags, point);
634 int CLocationView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message)
636 return MA_NOACTIVATE;
639 BOOL CLocationView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
641 CMergeEditView* pView = GetDocument()->GetActiveMergeView();
643 return static_cast<BOOL>(pView->SendMessage(WM_MOUSEWHEEL,
644 MAKEWPARAM(nFlags, zDelta), MAKELPARAM(pt.x, pt.y)));
645 return CView::OnMouseWheel(nFlags, zDelta, pt);
649 * User left double-clicked mouse
650 * @todo We can give alternative action to a double clicking.
652 void CLocationView::OnLButtonDblClk(UINT nFlags, CPoint point)
654 bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
655 if (!GotoLocation(point, false, !bShift))
656 CView::OnLButtonDblClk(nFlags, point);
660 * @brief Scroll both views to point given.
662 * Scroll views to given line. There is two ways to scroll, based on
663 * view lines (ghost lines counted in) or on real lines (no ghost lines).
664 * In most cases view lines should be used as it avoids real line number
665 * calculation and is able to scroll to all lines - real line numbers
666 * cannot be used to scroll to ghost lines.
668 * @param [in] point Point to move to
669 * @param [in] bRealLine `true` if we want to scroll using real line num,
670 * `false` if view linenumbers are OK.
671 * @param [in] bMoveAnchor if true the anchor is moved to the point
672 * @return `true` if succeeds, `false` if point not inside bars.
674 bool CLocationView::GotoLocation(const CPoint& point, bool bRealLine /*= true*/, bool bMoveAnchor)
678 CMergeDoc* pDoc = GetDocument();
680 if (!pDoc->GetActiveMergeView())
684 int bar = IsInsideBar(rc, point);
685 if (bar == BAR_0 || bar == BAR_1 || bar == BAR_2)
687 line = GetLineFromYPos(point.y, bar, bRealLine);
689 else if (bar == BAR_YAREA)
691 // Outside bars, use left bar
693 line = GetLineFromYPos(point.y, bar, false);
698 pDoc->GetActiveMergeGroupView(0)->GotoLine(line, bRealLine, bar, bMoveAnchor);
699 if (bar == BAR_0 || bar == BAR_1 || bar == BAR_2)
700 pDoc->GetActiveMergeGroupView(bar)->SetFocus();
706 * @brief Handle scroll events sent directly.
709 void CLocationView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
711 if (pScrollBar == nullptr)
713 // Scroll did not come frome a scroll bar
714 // Send it to the right view instead
715 CMergeDoc *pDoc = GetDocument();
716 pDoc->GetActiveMergeGroupView(pDoc->m_nBuffers - 1)->SendMessage(WM_VSCROLL,
717 MAKELONG(nSBCode, nPos), (LPARAM)NULL);
720 CView::OnVScroll (nSBCode, nPos, pScrollBar);
724 * Show context menu and handle user selection.
726 void CLocationView::OnContextMenu(CWnd* pWnd, CPoint point)
728 if (point.x == -1 && point.y == -1)
730 //keystroke invocation
733 ClientToScreen(rect);
735 point = rect.TopLeft();
744 VERIFY(menu.LoadMenu(IDR_POPUP_LOCATIONBAR));
745 theApp.TranslateMenu(menu.m_hMenu);
747 BCMenu* pPopup = static_cast<BCMenu *>(menu.GetSubMenu(0));
748 ASSERT(pPopup != nullptr);
751 cmdUI.m_pMenu = pPopup;
752 cmdUI.m_nIndexMax = cmdUI.m_pMenu->GetMenuItemCount();
753 for (cmdUI.m_nIndex = 0 ; cmdUI.m_nIndex < cmdUI.m_nIndexMax ; ++cmdUI.m_nIndex)
755 cmdUI.m_nID = cmdUI.m_pMenu->GetMenuItemID(cmdUI.m_nIndex);
758 case ID_DISPLAY_MOVED_NONE:
759 cmdUI.SetRadio(!m_displayMovedBlocks);
761 case ID_DISPLAY_MOVED_ALL:
762 cmdUI.SetRadio(m_displayMovedBlocks);
770 int bar = IsInsideBar(rc, pt);
772 // If cursor over bar, format string with linenumber, else disable item
775 // If outside bar area use left bar
776 if (bar == BAR_YAREA)
778 nLine = GetLineFromYPos(pt.y, bar);
779 strNum = strutils::to_str(nLine + 1); // Show linenumber not lineindex
782 pPopup->EnableMenuItem(ID_LOCBAR_GOTODIFF, MF_GRAYED);
783 strItem = strutils::format_string1(_("G&o to Line %1"), strNum);
784 pPopup->SetMenuText(ID_LOCBAR_GOTODIFF, strItem.c_str(), MF_BYCOMMAND);
786 // invoke context menu
787 // we don't want to use the main application handlers, so we use flags TPM_NONOTIFY | TPM_RETURNCMD
788 // and handle the command after TrackPopupMenu
789 int command = pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, point.x, point.y, AfxGetMainWnd());
791 CMergeDoc* pDoc = GetDocument();
794 case ID_LOCBAR_GOTODIFF:
795 pDoc->GetActiveMergeGroupView(0)->GotoLine(nLine, true, bar);
796 if (bar == BAR_0 || bar == BAR_1 || bar == BAR_2)
797 pDoc->GetActiveMergeGroupView(bar)->SetFocus();
800 pDoc->GetActiveMergeGroupView(0)->WMGoto();
802 case ID_DISPLAY_MOVED_NONE:
803 SetConnectMovedBlocks(false);
804 pDoc->SetDetectMovedBlocks(false);
806 case ID_DISPLAY_MOVED_ALL:
807 SetConnectMovedBlocks(true);
808 pDoc->SetDetectMovedBlocks(true);
814 * @brief Calculates view/real line in file from given YCoord in bar.
815 * @param [in] nYCoord ycoord in pane
816 * @param [in] bar bar/file
817 * @param [in] bRealLine `true` if real line is returned, `false` for view line
818 * @return 0-based index of view/real line in file [0...lines-1]
820 int CLocationView::GetLineFromYPos(int nYCoord, int bar, bool bRealLine /*= true*/)
822 CMergeEditView *pView = GetDocument()->GetActiveMergeGroupView(bar);
824 int nSubLineIndex = (int) (m_pixInLines * (nYCoord - Y_OFFSET));
826 // Keep sub-line index in range.
827 if (nSubLineIndex < 0)
831 else if (nSubLineIndex >= pView->GetSubLineCount())
833 nSubLineIndex = pView->GetSubLineCount() - 1;
836 // Find the real (not wrapped) line number from sub-line index.
839 pView->GetLineBySubLine(nSubLineIndex, nLine, nSubLine);
841 // Convert line number to line index.
847 // We've got a view line now
851 // Get real line (exclude ghost lines)
852 CMergeDoc* pDoc = GetDocument();
853 const int nRealLine = pDoc->m_ptBuf[bar]->ComputeRealLine(nLine);
858 * @brief Determines if given coords are inside left/right bar.
859 * @param rc [in] size of locationpane client area
860 * @param pt [in] point we want to check, in client coordinates.
861 * @return LOCBAR_TYPE area where point is.
863 int CLocationView::IsInsideBar(const CRect& rc, const POINT& pt)
865 CMergeDoc *pDoc = GetDocument();
866 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
868 if (m_bar[pane].PtInRect(pt))
871 if (pt.y > m_bar[0].top && pt.y <= m_bar[0].bottom)
877 * @brief Draws rect indicating visible area in file views.
879 * @param [in] nTopLine New topline for indicator
880 * @param [in] nBottomLine New bottomline for indicator
881 * @todo This function dublicates too much DrawRect() code.
883 void CLocationView::DrawVisibleAreaRect(CDC *pClientDC, int nTopLine, int nBottomLine)
885 CMergeDoc* pDoc = GetDocument();
886 int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
889 nTopLine = pDoc->GetView(nGroup, 0)->GetTopSubLine();
891 if (nBottomLine == -1)
893 const int nScreenLines = pDoc->GetView(nGroup, 1)->GetScreenLines();
894 nBottomLine = nTopLine + nScreenLines;
899 int nbLines = INT_MAX;
900 pDoc->ForEachActiveGroupView([&](auto& pView) {
901 nbLines = min(nbLines, pView->GetSubLineCount());
904 int nTopCoord = static_cast<int>(Y_OFFSET +
905 (static_cast<double>(nTopLine * m_lineInPix)));
906 int nBottomCoord = static_cast<int>(Y_OFFSET +
907 (static_cast<double>(nBottomLine * m_lineInPix)));
909 double xbarBottom = min(nbLines / m_pixInLines + Y_OFFSET, rc.Height() - Y_OFFSET);
910 int barBottom = (int)xbarBottom;
911 // Make sure bottom coord is in bar range
912 nBottomCoord = min(nBottomCoord, barBottom);
914 // Ensure visible area is at least minimum height
915 if (nBottomCoord - nTopCoord < INDICATOR_MIN_HEIGHT)
917 // If area is near top of file, add additional area to bottom
918 // of the bar and vice versa.
919 if (nTopCoord < Y_OFFSET + 20)
920 nBottomCoord += INDICATOR_MIN_HEIGHT - (nBottomCoord - nTopCoord);
923 // If we have a high number of lines, it may be better
924 // to keep the topline, otherwise the cursor can
925 // jump up and down unexpected
926 nBottomCoord = nTopCoord + INDICATOR_MIN_HEIGHT;
930 // Store current values for later use (to check if area changes)
931 m_visibleTop = nTopCoord;
932 m_visibleBottom = nBottomCoord;
934 CRect rcVisibleArea(2, m_visibleTop, rc.right - 2, m_visibleBottom);
935 std::unique_ptr<CBitmap> pBitmap(CopyRectToBitmap(pClientDC, rcVisibleArea));
936 std::unique_ptr<CBitmap> pDarkenedBitmap(GetDarkenedBitmap(pClientDC, pBitmap.get(), IsColorDark(GetBackgroundColor())));
937 DrawBitmap(pClientDC, rcVisibleArea.left, rcVisibleArea.top, pDarkenedBitmap.get());
941 * @brief Public function for updating visible area indicator.
943 * @param [in] nTopLine New topline for indicator
944 * @param [in] nBottomLine New bottomline for indicator
946 void CLocationView::UpdateVisiblePos(int nTopLine, int nBottomLine)
950 CMergeDoc *pDoc = GetDocument();
951 int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
953 if (std::all_of(m_nSubLineCount, m_nSubLineCount + pDoc->m_nBuffers,
954 [&](int nSubLineCount) { return nSubLineCount == pDoc->GetView(nGroup, pane++)->GetSubLineCount(); }))
956 int nTopCoord = static_cast<int>(Y_OFFSET +
957 (static_cast<double>(nTopLine * m_lineInPix)));
958 int nBottomCoord = static_cast<int>(Y_OFFSET +
959 (static_cast<double>(nBottomLine * m_lineInPix)));
960 if (m_visibleTop != nTopCoord || m_visibleBottom != nBottomCoord)
962 // Visible area was changed
963 if (m_pSavedBackgroundBitmap != nullptr)
967 // Clear previous visible rect
968 DrawBitmap(&dcMem, 0, 0, m_pSavedBackgroundBitmap.get());
970 DrawVisibleAreaRect(&dcMem, nTopLine, nBottomLine);
976 InvalidateRect(nullptr);
977 for (pane = 0; pane < pDoc->m_nBuffers; pane++)
978 m_nSubLineCount[pane] = pDoc->GetView(nGroup, pane)->GetSubLineCount();
984 * @brief Unset pointers to MergeEditView when location pane is closed.
986 void CLocationView::OnClose()
992 * @brief Draw lines connecting moved blocks.
994 void CLocationView::DrawConnectLines(CDC *pClientDC)
996 COLORREF clrMovedBlock = GetOptionsMgr()->GetInt(OPT_CLR_MOVEDBLOCK);
997 COLORREF clrSelectedMovedBlock = GetOptionsMgr()->GetInt(OPT_CLR_SELECTED_MOVEDBLOCK);
998 CBrush brushMovedBlock(clrMovedBlock);
999 CBrush brushSelectedMovedBlock(clrSelectedMovedBlock);
1000 CPen penMovedBlock(PS_SOLID, 0, clrMovedBlock);
1001 CPen penSelectedMovedBlock(PS_SOLID, 0, clrSelectedMovedBlock);
1003 POSITION pos = m_movedLines.GetHeadPosition();
1004 int oldMode = pClientDC->SetPolyFillMode(ALTERNATE);
1006 while (pos != nullptr)
1008 MovedLine line = m_movedLines.GetNext(pos);
1009 CPen *pOldPen = (CPen *)pClientDC->SelectObject(line.currentDiff ? &penSelectedMovedBlock : &penMovedBlock);
1010 if (line.ptLeftLower.y - line.ptLeftUpper.y <= 1)
1012 CPoint points[4] = {
1014 {(line.ptLeftUpper.x + line.ptRightUpper.x) / 2, line.ptLeftUpper.y},
1015 {(line.ptLeftUpper.x + line.ptRightUpper.x) / 2, line.ptRightLower.y},
1016 line.ptRightUpper };
1017 pClientDC->PolyBezier(points, 4);
1022 PT_MOVETO, PT_BEZIERTO, PT_BEZIERTO, PT_BEZIERTO,
1023 PT_LINETO, PT_BEZIERTO, PT_BEZIERTO, PT_BEZIERTO, PT_LINETO };
1024 CPoint points[9] = {
1026 {(line.ptLeftUpper.x + line.ptRightUpper.x) / 2, line.ptLeftUpper.y},
1027 {(line.ptLeftUpper.x + line.ptRightUpper.x) / 2, line.ptRightUpper.y},
1030 {(line.ptLeftLower.x + line.ptRightLower.x) / 2, line.ptRightLower.y},
1031 {(line.ptLeftLower.x + line.ptRightLower.x) / 2, line.ptLeftLower.y},
1032 line.ptLeftLower, line.ptLeftUpper };
1033 pClientDC->BeginPath();
1034 pClientDC->PolyDraw(points, types, 8);
1035 pClientDC->EndPath();
1037 if (rgn.CreateFromPath(pClientDC))
1038 pClientDC->FillRgn(&rgn, line.currentDiff ? &brushSelectedMovedBlock : &brushMovedBlock);
1039 pClientDC->PolyDraw(points, types, 9);
1041 pClientDC->SelectObject(pOldPen);
1044 pClientDC->SetPolyFillMode(oldMode);
1048 * @brief Request frame window to store sizes.
1050 * When locationview size changes we want to save new size
1051 * for new windows. But we must do it through frame window.
1052 * @param [in] nType Type of resizing, SIZE_MAXIMIZED etc.
1053 * @param [in] cx New panel width.
1054 * @param [in] cy New panel height.
1056 void CLocationView::OnSize(UINT nType, int cx, int cy)
1058 CView::OnSize(nType, cx, cy);
1060 // Height change needs block recalculation
1061 // TODO: Perhaps this should be determined from need to change bar size?
1062 // And we could change bar sizes more lazily, not from every one pixel change in size?
1063 if (cy != m_currentSize.cy)
1064 m_bRecalculateBlocks = true;
1066 if (cx != m_currentSize.cx)
1068 if (m_hwndFrame != nullptr)
1069 ::PostMessage(m_hwndFrame, MSG_STORE_PANESIZES, 0, 0);
1072 m_currentSize.cx = cx;
1073 m_currentSize.cy = cy;
1077 * @brief Draw marker for top of currently selected difference.
1078 * This function draws marker for top of currently selected difference.
1079 * This marker makes it a lot easier to see where currently selected
1080 * difference is in location bar. Especially when selected diffence is
1081 * small and it is not easy to find it otherwise.
1082 * @param [in] pDC Pointer to draw context.
1083 * @param [in] yCoord Y-coord of top of difference, -1 if no difference.
1085 void CLocationView::DrawDiffMarker(CDC* pDC, int yCoord)
1087 int nBuffers = GetDocument()->m_nBuffers;
1090 points[0].x = m_bar[0].left - DIFFMARKER_WIDTH - 1;
1091 points[0].y = yCoord - DIFFMARKER_TOP;
1092 points[1].x = m_bar[0].left - 1;
1093 points[1].y = yCoord;
1094 points[2].x = m_bar[0].left - DIFFMARKER_WIDTH - 1;
1095 points[2].y = yCoord + DIFFMARKER_BOTTOM;
1097 COLORREF clrBlue = GetSysColor(COLOR_ACTIVECAPTION);
1098 CPen penDarkBlue(PS_SOLID, 0, GetDarkenColor(clrBlue, 0.9));
1099 CPen* oldObj = (CPen*)pDC->SelectObject(&penDarkBlue);
1100 CBrush brushBlue(clrBlue);
1101 CBrush* pOldBrush = pDC->SelectObject(&brushBlue);
1103 pDC->SetPolyFillMode(WINDING);
1104 pDC->Polygon(points, 3);
1106 points[0].x = m_bar[nBuffers - 1].right + 1 + DIFFMARKER_WIDTH;
1107 points[1].x = m_bar[nBuffers - 1].right + 1;
1108 points[2].x = m_bar[nBuffers - 1].right + 1 + DIFFMARKER_WIDTH;
1109 pDC->Polygon(points, 3);
1111 pDC->SelectObject(pOldBrush);
1112 pDC->SelectObject(oldObj);