OSDN Git Service

Improve plugin system (#797) (6)
[winmerge-jp/winmerge-jp.git] / Src / LocationView.cpp
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 /////////////////////////////////////////////////////////////////////////////
6
7 /** 
8  * @file  LocationView.cpp
9  *
10  * @brief Implementation file for CLocationView
11  *
12  */
13
14
15 #include "StdAfx.h"
16 #include "LocationView.h"
17 #include <vector>
18 #include "Merge.h"
19 #include "OptionsMgr.h"
20 #include "MergeEditView.h"
21 #include "MergeDoc.h"
22 #include "BCMenu.h"
23 #include "OptionsDef.h"
24 #include "Bitmap.h"
25 #include "memdc.h"
26 #include "SyntaxColors.h"
27
28 #ifdef _DEBUG
29 #define new DEBUG_NEW
30 #endif
31
32 using std::vector;
33
34 /** @brief Size of empty frame above and below bars (in pixels). */
35 static const int Y_OFFSET = 5;
36 /** @brief Size of y-margin for visible area indicator (in pixels). */
37 static const long INDICATOR_MARGIN = 2;
38 /** @brief Max pixels in view per line in file. */
39 static const double MAX_LINEPIX = 4.0;
40 /** @brief Top of difference marker, relative to difference start. */
41 static const int DIFFMARKER_TOP = 3;
42 /** @brief Bottom of difference marker, relative to difference start. */
43 static const int DIFFMARKER_BOTTOM = 3;
44 /** @brief Width of difference marker. */
45 static const int DIFFMARKER_WIDTH = 6;
46 /** @brief Minimum height of the visible area indicator */
47 static const int INDICATOR_MIN_HEIGHT = 2;
48
49 /** 
50  * @brief Bars in location pane
51  */
52 enum LOCBAR_TYPE
53 {
54         BAR_NONE = -1,  /**< No bar in given coords */
55         BAR_0,                  /**< first bar in given coords */
56         BAR_1,                  /**< second bar in given coords */
57         BAR_2,                  /**< third side bar in given coords */
58         BAR_YAREA,              /**< Y-Coord in bar area */
59 };
60
61 /////////////////////////////////////////////////////////////////////////////
62 // CLocationView
63
64 IMPLEMENT_DYNCREATE(CLocationView, CView)
65
66
67 CLocationView::CLocationView()
68         : m_visibleTop(-1)
69         , m_visibleBottom(-1)
70 //      MOVEDLINE_LIST m_movedLines; //*< List of moved block connecting lines */
71         , m_hwndFrame(nullptr)
72         , m_pSavedBackgroundBitmap(nullptr)
73         , m_bDrawn(false)
74         , m_bRecalculateBlocks(true) // calculate for the first time
75 {
76         // NB: set m_bIgnoreTrivials to false to see trivial diffs in the LocationView
77         // There is no GUI to do this
78
79         SetConnectMovedBlocks(GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS));
80
81         std::fill_n(m_nSubLineCount, std::size(m_nSubLineCount), 0);
82 }
83
84 CLocationView::~CLocationView()
85 {
86 }
87
88 BEGIN_MESSAGE_MAP(CLocationView, CView)
89         //{{AFX_MSG_MAP(CLocationView)
90         ON_WM_LBUTTONDOWN()
91         ON_WM_LBUTTONUP()
92         ON_WM_MOUSEMOVE()
93         ON_WM_MOUSEACTIVATE()
94         ON_WM_LBUTTONDBLCLK()
95         ON_WM_CONTEXTMENU()
96         ON_WM_CLOSE()
97         ON_WM_SIZE()
98         ON_WM_VSCROLL()
99         ON_WM_ERASEBKGND()
100         //}}AFX_MSG_MAP
101 END_MESSAGE_MAP()
102
103
104 void CLocationView::SetConnectMovedBlocks(bool displayMovedBlocks) 
105 {
106         if (m_displayMovedBlocks == displayMovedBlocks)
107                 return;
108
109         m_displayMovedBlocks = displayMovedBlocks;
110         if (this->GetSafeHwnd() != nullptr)
111                 if (IsWindowVisible())
112                         Invalidate();
113 }
114
115 /////////////////////////////////////////////////////////////////////////////
116 // CLocationView diagnostics
117 #ifdef _DEBUG
118 CMergeDoc* CLocationView::GetDocument() // non-debug version is inline
119 {
120         ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
121         return (CMergeDoc*)m_pDocument;
122 }
123 #endif //_DEBUG
124
125 /////////////////////////////////////////////////////////////////////////////
126 // CLocationView message handlers
127
128 /**
129  * @brief Force recalculation and update of location pane.
130  * This method forces location pane to first recalculate its data and
131  * then repaint itself. This method bypasses location pane's caching
132  * of the diff data.
133  */
134 void CLocationView::ForceRecalculate()
135 {
136         m_bRecalculateBlocks = true;
137         Invalidate();
138 }
139
140 /** 
141  * @brief Update view.
142  */
143 void CLocationView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )
144 {
145         UNREFERENCED_PARAMETER(pSender);
146         UNREFERENCED_PARAMETER(lHint);
147
148         m_bRecalculateBlocks = true;
149         Invalidate();
150 }
151
152 /** 
153  * @brief Override for CMemDC to work.
154  */
155 BOOL CLocationView::OnEraseBkgnd(CDC* pDC)
156 {
157         return FALSE;
158 }
159
160 static bool IsColorDark(COLORREF clrBackground)
161 {
162         return !(GetRValue(clrBackground) >= 0x80 || GetGValue(clrBackground) >= 0x80 || GetBValue(clrBackground) >= 0x80);
163
164 }
165
166 COLORREF CLocationView::GetBackgroundColor()
167 {
168         COLORREF clrBackground = GetDocument()->GetView(0, 0)->GetColor(COLORINDEX_WHITESPACE);
169         if (!IsColorDark(clrBackground))
170         {
171                 return RGB(
172                         (std::max)(GetRValue(clrBackground) - 24, 0),
173                         (std::max)(GetGValue(clrBackground) - 24, 0),
174                         (std::max)(GetBValue(clrBackground) - 12, 0));
175         }
176         return RGB(
177                 (std::min)(GetRValue(clrBackground) + 20, 255),
178                 (std::min)(GetGValue(clrBackground) + 20, 255),
179                 (std::min)(GetBValue(clrBackground) + 32, 255));
180 }
181
182 /**
183  * @brief Draw custom (non-white) background.
184  * @param [in] pDC Pointer to draw context.
185  */
186 void CLocationView::DrawBackground(CDC* pDC)
187 {
188         // Set brush to desired background color
189         CBrush backBrush(GetBackgroundColor());
190         
191         // Save old brush
192         CBrush* pOldBrush = pDC->SelectObject(&backBrush);
193         
194         CRect rect;
195         pDC->GetClipBox(&rect);     // Erase the area needed
196         
197         pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
198
199         pDC->SelectObject(pOldBrush);
200 }
201
202 /**
203  * @brief Calculate bar coordinates and scaling factors.
204  */
205 void CLocationView::CalculateBars()
206 {
207         CMergeDoc *pDoc = GetDocument();
208         CRect rc;
209         GetClientRect(rc);
210         const int w = rc.Width() / (pDoc->m_nBuffers * 2);
211         const int margin = (rc.Width() - w * pDoc->m_nBuffers) / (pDoc->m_nBuffers + 1);
212         int pane;
213         for (pane = 0; pane < pDoc->m_nBuffers; pane++)
214         {
215                 m_bar[pane].left = pane * (w + margin) + margin;
216                 m_bar[pane].right = m_bar[pane].left + w;
217         }       const double hTotal = rc.Height() - (2 * Y_OFFSET); // Height of draw area
218         int nbLines = 0;
219         pDoc->ForEachActiveGroupView([&](auto& pView) {
220                 nbLines = max(nbLines, pView->GetSubLineCount());
221         });
222
223         m_lineInPix = hTotal / nbLines;
224         m_pixInLines = nbLines / hTotal;
225         if (m_lineInPix > MAX_LINEPIX)
226         {
227                 m_lineInPix = MAX_LINEPIX;
228                 m_pixInLines = 1 / MAX_LINEPIX;
229         }
230
231         for (pane = 0; pane < pDoc->m_nBuffers; pane++)
232         {
233                 m_bar[pane].top = Y_OFFSET - 1;
234                 m_bar[pane].bottom = (LONG)(m_lineInPix * nbLines + Y_OFFSET + 1);
235         }
236 }
237
238 /**
239  * @brief Calculate difference lines and coordinates.
240  * This function calculates begin- and end-lines of differences when word-wrap
241  * is enabled. Otherwise the value from original difflist is used. Line
242  * numbers are also converted to coordinates in the window. All calculated
243  * (and not ignored) differences are added to the new list.
244  */
245 void CLocationView::CalculateBlocks()
246 {
247         // lineposition in pixels.
248         int nBeginY;
249         int nEndY;
250
251         m_diffBlocks.clear();
252
253         CMergeDoc *pDoc = GetDocument();
254         const int nDiffs = pDoc->m_diffList.GetSize();
255         if (nDiffs > 0)
256                 m_diffBlocks.reserve(nDiffs); // Pre-allocate space for the list.
257
258         int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
259         int nLineCount = pDoc->GetView(nGroup, 0)->GetLineCount();
260         int nDiff = pDoc->m_diffList.FirstSignificantDiff();
261         while (nDiff != -1)
262         {
263                 DIFFRANGE diff;
264                 VERIFY(pDoc->m_diffList.GetDiff(nDiff, diff));
265
266                 CMergeEditView *pView = pDoc->GetView(nGroup, 0);
267
268                 DiffBlock block;
269                 int i, nBlocks = 0;
270                 int bs[4] = {0};
271                 int minY = INT_MAX, maxY = -1;
272
273                 bs[nBlocks++] = diff.dbegin;
274                 for (i = 0; i < pDoc->m_nBuffers; i++)
275                 {
276                         if (diff.blank[i] >= 0)
277                         {
278                                 if (minY > diff.blank[i])
279                                         minY = diff.blank[i];
280                                 if (maxY < diff.blank[i])
281                                         maxY = diff.blank[i];
282                         }
283                 }
284                 if (minY == maxY)
285                 {
286                         bs[nBlocks++] = minY;
287                 }
288                 else if (maxY != -1)
289                 {
290                         bs[nBlocks++] = minY;
291                         bs[nBlocks++] = maxY;
292                 }
293                 bs[nBlocks] = diff.dend + 1;
294                 if (bs[nBlocks] >= nLineCount)
295                         bs[nBlocks] = nLineCount - 1;
296
297                 for (i = 0; i < nBlocks; i++)
298                 {
299                         CalculateBlocksPixel(
300                                 pView->GetSubLineIndex(bs[i]),
301                                 pView->GetSubLineIndex(bs[i + 1]),
302                                 pView->GetSubLines(bs[i + 1]), nBeginY, nEndY);
303
304                         block.top_line = bs[i];
305                         block.bottom_line = bs[i + 1];
306                         block.top_coord = nBeginY;
307                         block.bottom_coord = nEndY;
308                         block.diff_index = nDiff;
309                         block.op = diff.op;
310                         m_diffBlocks.push_back(block);
311                 }
312
313                 nDiff = pDoc->m_diffList.NextSignificantDiff(nDiff);
314         }
315         m_bRecalculateBlocks = false;
316 }
317
318 /**
319  * @brief Calculate Blocksize to pixel.
320  * @param [in] nBlockStart line where block starts
321  * @param [in] nBlockEnd   line where block ends 
322  * @param [in] nBlockLength length of the block
323  * @param [in,out] nBeginY pixel in y  where block starts
324  * @param [in,out] nEndY   pixel in y  where block ends
325
326  */
327 void CLocationView::CalculateBlocksPixel(int nBlockStart, int nBlockEnd,
328                 int nBlockLength, int &nBeginY, int &nEndY)
329 {
330         // Count how many line does the diff block have.
331         const int nBlockHeight = nBlockEnd - nBlockStart + nBlockLength;
332
333         // Convert diff block size from lines to pixels.
334         nBeginY = (int)(nBlockStart * m_lineInPix + Y_OFFSET);
335         nEndY = (int)((nBlockStart + nBlockHeight) * m_lineInPix + Y_OFFSET);
336 }
337
338 static COLORREF GetIntermediateColor(COLORREF a, COLORREF b, float ratio)
339 {
340         const int R = static_cast<int>((GetRValue(a) - GetRValue(b)) * ratio) + GetRValue(b);
341         const int G = static_cast<int>((GetGValue(a) - GetGValue(b)) * ratio) + GetGValue(b);
342         const int B = static_cast<int>((GetBValue(a) - GetBValue(b)) * ratio) + GetBValue(b);
343         return RGB(R, G, B);
344 }
345
346 static COLORREF GetDarkenColor(COLORREF a, double l)
347 {
348         const int R = static_cast<int>(GetRValue(a) * l);
349         const int G = static_cast<int>(GetGValue(a) * l);
350         const int B = static_cast<int>(GetBValue(a) * l);
351         return RGB(R, G, B);
352 }
353
354 /** 
355  * @brief Draw maps of files.
356  *
357  * Draws maps of differences in files. Difference list is walked and
358  * every difference is drawn with same colors as in editview.
359  * @note We MUST use doubles when calculating coords to avoid rounding
360  * to integers. Rounding causes miscalculation of coords.
361  * @sa CLocationView::DrawRect()
362  */
363 void CLocationView::OnDraw(CDC* pDC)
364 {
365         CMergeDoc *pDoc = GetDocument();
366         int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
367         if (pDoc->GetView(nGroup, 0) == nullptr)
368                 return;
369
370         if (!pDoc->GetView(nGroup, 0)->IsInitialized()) return;
371
372         bool bEditedAfterRescan = false;
373         int nPaneNotModified = -1;
374         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
375         {
376                 if (!pDoc->IsEditedAfterRescan(pane))
377                         nPaneNotModified = pane;
378                 else
379                         bEditedAfterRescan = true;
380         }
381
382         CRect rc;
383         GetClientRect(&rc);
384
385         CMyMemDC dc(pDC, &rc);
386
387         COLORREF cr[3] = {CLR_NONE, CLR_NONE, CLR_NONE};
388         COLORREF crt = CLR_NONE; // Text color
389         bool bwh = false;
390
391         m_movedLines.RemoveAll();
392
393         CalculateBars();
394         DrawBackground(&dc);
395
396         COLORREF clrFace    = GetBackgroundColor();
397         COLORREF clrShadow  = GetSysColor(COLOR_BTNSHADOW);
398         COLORREF clrShadow2 = GetIntermediateColor(clrFace, clrShadow, 0.9f);
399         COLORREF clrShadow3 = GetIntermediateColor(clrFace, clrShadow2, 0.5f);
400         COLORREF clrShadow4 = GetIntermediateColor(clrFace, clrShadow3, 0.5f);
401         COLORREF clrShadow5 = GetIntermediateColor(clrFace, clrShadow4, 0.5f);
402
403         // Draw bar outlines
404         CPen* oldObj = (CPen*)dc.SelectStockObject(NULL_PEN);
405         CBrush brush(pDoc->GetView(nGroup, 0)->GetColor(COLORINDEX_WHITESPACE));
406         CBrush* oldBrush = (CBrush*)dc.SelectObject(&brush);
407         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
408         {
409                 int nBottom = (int)(m_lineInPix * pDoc->GetView(nGroup, pane)->GetSubLineCount() + Y_OFFSET + 1);
410                 CBrush *pOldBrush = nullptr;
411                 if (pDoc->IsEditedAfterRescan(pane))
412                         pOldBrush = (CBrush *)dc.SelectStockObject(HOLLOW_BRUSH);
413                 dc.Rectangle(m_bar[pane]);
414                 if (pOldBrush != nullptr)
415                         dc.SelectObject(pOldBrush);
416
417                 CRect rect = m_bar[pane];
418                 rect.InflateRect(1, 1);
419                 dc.Draw3dRect(rect, clrShadow5, clrShadow4);
420                 rect.InflateRect(-1, -1);
421                 dc.Draw3dRect(rect, clrShadow3, clrShadow2);
422         }
423         dc.SelectObject(oldBrush);
424         dc.SelectObject(oldBrush);
425         dc.SelectObject(oldObj);
426
427         // Iterate the differences list and draw differences as colored blocks.
428
429         // Don't recalculate blocks if we earlier determined it is not needed
430         // This may save lots of processing
431         if (m_bRecalculateBlocks)
432                 CalculateBlocks();
433
434         unsigned nPrevEndY = static_cast<unsigned>(-1);
435         const unsigned nCurDiff = pDoc->GetCurrentDiff();
436         DIFFRANGE diCur;
437         if (nCurDiff != -1)
438                 pDoc->m_diffList.GetDiff(nCurDiff, diCur);
439
440         vector<DiffBlock>::const_iterator iter = m_diffBlocks.begin();
441         for (; iter != m_diffBlocks.end(); ++iter)
442         {
443                 if (nPaneNotModified == -1)
444                         continue;
445                 CMergeEditView *pView = pDoc->GetView(nGroup, nPaneNotModified);
446                 const bool bInsideDiff = (nCurDiff == (*iter).diff_index);
447
448                 if ((nPrevEndY != (*iter).bottom_coord) || bInsideDiff)
449                 {
450                         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
451                         {
452                                 if (pDoc->IsEditedAfterRescan(pane))
453                                         continue;
454                                 // Draw 3way-diff state
455                                 if (pDoc->m_nBuffers == 3 && pane < 2)
456                                 {
457                                         CRect r(m_bar[pane].right - 1, (*iter).top_coord, m_bar[pane + 1].left + 1, (*iter).bottom_coord);
458                                         if ((pane == 0 && (*iter).op == OP_3RDONLY) || (pane == 1 && (*iter).op == OP_1STONLY))
459                                                 DrawRect(&dc, r, RGB(255, 255, 127), false);
460                                         else if ((*iter).op == OP_2NDONLY)
461                                                 DrawRect(&dc, r, RGB(127, 255, 255), false);
462                                         else if ((*iter).op == OP_DIFF)
463                                                 DrawRect(&dc, r, RGB(255, 0, 0), false);
464                                 }
465                                 // Draw block
466                                 pDoc->GetView(0, pane)->GetLineColors2((*iter).top_line, 0, cr[pane], crt, bwh);
467                                 CRect r(m_bar[pane].left, (*iter).top_coord, m_bar[pane].right, (*iter).bottom_coord);
468                                 DrawRect(&dc, r, cr[pane], bInsideDiff);
469                         }
470                 }
471                 nPrevEndY = (*iter).bottom_coord;
472
473                 if (bEditedAfterRescan)
474                         continue;
475
476                 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
477                 {
478                         if (m_displayMovedBlocks && pane < 2)
479                         {
480                                 int apparent0 = (*iter).top_line;
481                                 int apparent1 = pDoc->RightLineInMovedBlock(pane, apparent0);
482                                 const int nBlockHeight = (*iter).bottom_line - (*iter).top_line + 1;
483                                 if (apparent1 != -1)
484                                 {
485                                         MovedLine line;
486
487                                         line.currentDiff = bInsideDiff || (nCurDiff != -1 && diCur.dbegin <= apparent1 && apparent1 <= diCur.dend);
488
489                                         apparent0 = pView->GetSubLineIndex(apparent0);
490                                         apparent1 = pView->GetSubLineIndex(apparent1);
491
492                                         int leftUpper = (int) (apparent0 * m_lineInPix + Y_OFFSET);
493                                         int leftLower = (int) ((nBlockHeight + apparent0) * m_lineInPix + Y_OFFSET - 1);
494                                         int rightUpper = (int) (apparent1 * m_lineInPix + Y_OFFSET);
495                                         int rightLower = (int) ((nBlockHeight + apparent1) * m_lineInPix + Y_OFFSET - 1);
496                                         line.ptLeftUpper.x = line.ptLeftLower.x = m_bar[pane].right - 1;
497                                         line.ptLeftUpper.y = leftUpper;
498                                         line.ptLeftLower.y = leftLower;
499                                         line.ptRightUpper.x = line.ptRightLower.x = m_bar[pane + 1].left;
500                                         line.ptRightUpper.y = rightUpper;
501                                         line.ptRightLower.y = rightLower;
502                                         line.apparent0 = apparent0;
503                                         line.apparent1 = apparent1;
504                                         line.blockHeight = nBlockHeight;
505                                         if (!m_movedLines.IsEmpty())
506                                         {
507                                                 MovedLine& movedLineTail = m_movedLines.GetTail();
508                                                 if (line.apparent0 - movedLineTail.blockHeight - 1 == movedLineTail.apparent0 &&
509                                                     line.apparent1 - movedLineTail.blockHeight - 1 == movedLineTail.apparent1)
510                                                 {
511                                                         line.ptLeftUpper = movedLineTail.ptLeftUpper;
512                                                         line.ptRightUpper = movedLineTail.ptRightUpper;
513                                                         line.apparent0 = movedLineTail.apparent0;
514                                                         line.apparent1 = movedLineTail.apparent1;
515                                                         line.blockHeight += movedLineTail.blockHeight;
516                                                         m_movedLines.RemoveTail();
517                                                 }
518                                         }
519                                         m_movedLines.AddTail(line);
520                                 }
521                         }
522                 }
523         }
524
525         if (m_displayMovedBlocks)
526                 DrawConnectLines(&dc);
527
528         m_pSavedBackgroundBitmap.reset(CopyRectToBitmap(&dc, rc));
529
530         // Since we have invalidated locationbar there is no previous
531         // arearect to remove
532         m_visibleTop = -1;
533         m_visibleBottom = -1;
534         DrawVisibleAreaRect(&dc);
535
536         m_bDrawn = true;
537 }
538
539 /** 
540  * @brief Draw one block of map.
541  * @param [in] pDC Draw context.
542  * @param [in] r Rectangle to draw.
543  * @param [in] cr Color for rectangle.
544  * @param [in] bSelected Is rectangle for selected difference?
545  */
546 void CLocationView::DrawRect(CDC* pDC, const CRect& r, COLORREF cr, bool bSelected /*= false*/)
547 {
548         // Draw only colored blocks
549         if (cr != CLR_NONE && cr != GetSysColor(COLOR_WINDOW))
550         {
551                 CBrush brush(cr);
552                 CRect drawRect(r);
553                 drawRect.DeflateRect(1, 0);
554
555                 // With long files and small difference areas rect may become 0-height.
556                 // Make sure all diffs are drawn at least one pixel height.
557                 if (drawRect.Height() < 1)
558                         ++drawRect.bottom;
559                 pDC->FillSolidRect(drawRect, cr);
560                 CRect drawRect2(drawRect.left, drawRect.top, drawRect.right, drawRect.top + 1);
561                 pDC->FillSolidRect(drawRect2, GetDarkenColor(cr, 0.96));
562                 CRect drawRect3(drawRect.left, drawRect.bottom - 1, drawRect.right, drawRect.bottom);
563                 pDC->FillSolidRect(drawRect3, GetDarkenColor(cr, 0.91));
564
565                 if (bSelected)
566                 {
567                         DrawDiffMarker(pDC, r.top);
568                 }
569         }
570 }
571
572 /**
573  * @brief Capture the mouse target.
574  */
575 void CLocationView::OnLButtonDown(UINT nFlags, CPoint point) 
576 {
577         SetCapture();
578
579         bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
580         if (!GotoLocation(point, false, !bShift))
581                 CView::OnLButtonDown(nFlags, point);
582 }
583
584 /**
585  * @brief Release the mouse target.
586  */
587 void CLocationView::OnLButtonUp(UINT nFlags, CPoint point) 
588 {
589         ReleaseCapture();
590
591         CView::OnLButtonUp(nFlags, point);
592 }
593
594 /**
595  * @brief Process drag action on a captured mouse.
596  *
597  * Reposition on every dragged movement.
598  * The Screen update stress will be similar to a mouse wheeling.:-)
599  */
600 void CLocationView::OnMouseMove(UINT nFlags, CPoint point) 
601 {
602         if (GetCapture() == this)
603         {
604                 CMergeDoc *pDoc = GetDocument();
605                 int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
606
607                 // Don't go above bars.
608                 point.y = max(point.y, Y_OFFSET);
609
610                 // Vertical scroll handlers are range safe, so there is no need to
611                 // make sure value is valid and in range.
612                 int nSubLine = (int) (m_pixInLines * (point.y - Y_OFFSET));
613                 nSubLine -= pDoc->GetView(nGroup, 0)->GetScreenLines() / 2;
614                 if (nSubLine < 0)
615                         nSubLine = 0;
616
617                 // Just a random choose as both view share the same scroll bar.
618                 CWnd *pView = pDoc->GetView(nGroup, 0);
619
620                 SCROLLINFO si = { sizeof SCROLLINFO };
621                 si.fMask = SIF_POS;
622                 si.nPos = nSubLine;
623                 pView->SetScrollInfo(SB_VERT, &si);
624
625                 // The views are child windows of a splitter windows. Splitter window
626                 // doesn't accept scroll bar updates not send from scroll bar control.
627                 // So we need to update both views.
628                 int pane;
629                 int nBuffers = pDoc->m_nBuffers;
630                 for (pane = 0; pane < nBuffers; pane++)
631                         pDoc->GetView(nGroup, pane)->SendMessage(WM_VSCROLL, MAKEWPARAM(SB_THUMBPOSITION, 0), NULL);
632         }
633
634         CView::OnMouseMove(nFlags, point);
635 }
636
637 int  CLocationView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message)
638 {
639         return MA_NOACTIVATE;
640 }
641
642 /**
643  * User left double-clicked mouse
644  * @todo We can give alternative action to a double clicking. 
645  */
646 void CLocationView::OnLButtonDblClk(UINT nFlags, CPoint point) 
647 {
648         bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
649         if (!GotoLocation(point, false, !bShift))
650                 CView::OnLButtonDblClk(nFlags, point);
651 }
652
653 /**
654  * @brief Scroll both views to point given.
655  *
656  * Scroll views to given line. There is two ways to scroll, based on
657  * view lines (ghost lines counted in) or on real lines (no ghost lines).
658  * In most cases view lines should be used as it avoids real line number
659  * calculation and is able to scroll to all lines - real line numbers
660  * cannot be used to scroll to ghost lines.
661  *
662  * @param [in] point Point to move to
663  * @param [in] bRealLine `true` if we want to scroll using real line num,
664  *                       `false` if view linenumbers are OK.
665  * @param [in] bMoveAnchor if true the anchor is moved to the point
666  * @return `true` if succeeds, `false` if point not inside bars.
667  */
668 bool CLocationView::GotoLocation(const CPoint& point, bool bRealLine /*= true*/, bool bMoveAnchor)
669 {
670         CRect rc;
671         GetClientRect(rc);
672         CMergeDoc* pDoc = GetDocument();
673
674         if (!pDoc->GetActiveMergeView())
675                 return false;
676
677         int line = -1;
678         int bar = IsInsideBar(rc, point);
679         if (bar == BAR_0 || bar == BAR_1 || bar == BAR_2)
680         {
681                 line = GetLineFromYPos(point.y, bar, bRealLine);
682         }
683         else if (bar == BAR_YAREA)
684         {
685                 // Outside bars, use left bar
686                 bar = BAR_0;
687                 line = GetLineFromYPos(point.y, bar, false);
688         }
689         else
690                 return false;
691
692         pDoc->GetActiveMergeGroupView(0)->GotoLine(line, bRealLine, bar, bMoveAnchor);
693         if (bar == BAR_0 || bar == BAR_1 || bar == BAR_2)
694                 pDoc->GetActiveMergeGroupView(bar)->SetFocus();
695
696         return true;
697 }
698
699 /**
700  * @brief Handle scroll events sent directly.
701  *
702  */
703 void CLocationView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
704 {
705         if (pScrollBar == nullptr)
706         {
707                 // Scroll did not come frome a scroll bar
708                 // Send it to the right view instead
709           CMergeDoc *pDoc = GetDocument();
710                 pDoc->GetActiveMergeGroupView(pDoc->m_nBuffers - 1)->SendMessage(WM_VSCROLL,
711                         MAKELONG(nSBCode, nPos), (LPARAM)NULL);
712                 return;
713         }
714         CView::OnVScroll (nSBCode, nPos, pScrollBar);
715 }
716
717 /**
718  * Show context menu and handle user selection.
719  */
720 void CLocationView::OnContextMenu(CWnd* pWnd, CPoint point) 
721 {
722         if (point.x == -1 && point.y == -1)
723         {
724                 //keystroke invocation
725                 CRect rect;
726                 GetClientRect(rect);
727                 ClientToScreen(rect);
728
729                 point = rect.TopLeft();
730                 point.Offset(5, 5);
731         }
732
733         CRect rc;
734         CPoint pt = point;
735         GetClientRect(rc);
736         ScreenToClient(&pt);
737         BCMenu menu;
738         VERIFY(menu.LoadMenu(IDR_POPUP_LOCATIONBAR));
739         theApp.TranslateMenu(menu.m_hMenu);
740
741         BCMenu* pPopup = static_cast<BCMenu *>(menu.GetSubMenu(0));
742         ASSERT(pPopup != nullptr);
743
744         CCmdUI cmdUI;
745         cmdUI.m_pMenu = pPopup;
746         cmdUI.m_nIndexMax = cmdUI.m_pMenu->GetMenuItemCount();
747         for (cmdUI.m_nIndex = 0 ; cmdUI.m_nIndex < cmdUI.m_nIndexMax ; ++cmdUI.m_nIndex)
748         {
749                 cmdUI.m_nID = cmdUI.m_pMenu->GetMenuItemID(cmdUI.m_nIndex);
750                 switch (cmdUI.m_nID)
751                 {
752                 case ID_DISPLAY_MOVED_NONE:
753                         cmdUI.SetRadio(!m_displayMovedBlocks);
754                         break;
755                 case ID_DISPLAY_MOVED_ALL:
756                         cmdUI.SetRadio(m_displayMovedBlocks);
757                         break;
758                 }
759         }
760
761         String strItem;
762         String strNum;
763         int nLine = -1;
764         int bar = IsInsideBar(rc, pt);
765
766         // If cursor over bar, format string with linenumber, else disable item
767         if (bar != BAR_NONE)
768         {
769                 // If outside bar area use left bar
770                 if (bar == BAR_YAREA)
771                         bar = BAR_0;
772                 nLine = GetLineFromYPos(pt.y, bar);
773                 strNum = strutils::to_str(nLine + 1); // Show linenumber not lineindex
774         }
775         else
776                 pPopup->EnableMenuItem(ID_LOCBAR_GOTODIFF, MF_GRAYED);
777         strItem = strutils::format_string1(_("G&o to Line %1"), strNum);
778         pPopup->SetMenuText(ID_LOCBAR_GOTODIFF, strItem.c_str(), MF_BYCOMMAND);
779
780         // invoke context menu
781         // we don't want to use the main application handlers, so we use flags TPM_NONOTIFY | TPM_RETURNCMD
782         // and handle the command after TrackPopupMenu
783         int command = pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_NONOTIFY  | TPM_RETURNCMD, point.x, point.y, AfxGetMainWnd());
784
785         CMergeDoc* pDoc = GetDocument();
786         switch (command)
787         {
788         case ID_LOCBAR_GOTODIFF:
789                 pDoc->GetActiveMergeGroupView(0)->GotoLine(nLine, true, bar);
790                 if (bar == BAR_0 || bar == BAR_1 || bar == BAR_2)
791                         pDoc->GetActiveMergeGroupView(bar)->SetFocus();
792                 break;
793         case ID_EDIT_WMGOTO:
794                 pDoc->GetActiveMergeGroupView(0)->WMGoto();
795                 break;
796         case ID_DISPLAY_MOVED_NONE:
797                 SetConnectMovedBlocks(false);
798                 pDoc->SetDetectMovedBlocks(false);
799                 break;
800         case ID_DISPLAY_MOVED_ALL:
801                 SetConnectMovedBlocks(true);
802                 pDoc->SetDetectMovedBlocks(true);
803                 break;
804         }
805 }
806
807 /** 
808  * @brief Calculates view/real line in file from given YCoord in bar.
809  * @param [in] nYCoord ycoord in pane
810  * @param [in] bar bar/file
811  * @param [in] bRealLine `true` if real line is returned, `false` for view line
812  * @return 0-based index of view/real line in file [0...lines-1]
813  */
814 int CLocationView::GetLineFromYPos(int nYCoord, int bar, bool bRealLine /*= true*/)
815 {
816         CMergeEditView *pView = GetDocument()->GetActiveMergeGroupView(bar);
817
818         int nSubLineIndex = (int) (m_pixInLines * (nYCoord - Y_OFFSET));
819
820         // Keep sub-line index in range.
821         if (nSubLineIndex < 0)
822         {
823                 nSubLineIndex = 0;
824         }
825         else if (nSubLineIndex >= pView->GetSubLineCount())
826         {
827                 nSubLineIndex = pView->GetSubLineCount() - 1;
828         }
829
830         // Find the real (not wrapped) line number from sub-line index.
831         int nLine = 0;
832         int nSubLine = 0;
833         pView->GetLineBySubLine(nSubLineIndex, nLine, nSubLine);
834
835         // Convert line number to line index.
836         if (nLine > 0)
837         {
838                 nLine -= 1;
839         }
840
841         // We've got a view line now
842         if (!bRealLine)
843                 return nLine;
844
845         // Get real line (exclude ghost lines)
846         CMergeDoc* pDoc = GetDocument();
847         const int nRealLine = pDoc->m_ptBuf[bar]->ComputeRealLine(nLine);
848         return nRealLine;
849 }
850
851 /** 
852  * @brief Determines if given coords are inside left/right bar.
853  * @param rc [in] size of locationpane client area
854  * @param pt [in] point we want to check, in client coordinates.
855  * @return LOCBAR_TYPE area where point is.
856  */
857 int CLocationView::IsInsideBar(const CRect& rc, const POINT& pt)
858 {
859         CMergeDoc *pDoc = GetDocument();
860         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
861         {
862                 if (m_bar[pane].PtInRect(pt))
863                         return BAR_0 + pane;
864         }
865         if (pt.y > m_bar[0].top && pt.y <= m_bar[0].bottom)
866                 return BAR_YAREA;
867         return BAR_NONE;
868 }
869
870 /** 
871  * @brief Draws rect indicating visible area in file views.
872  *
873  * @param [in] nTopLine New topline for indicator
874  * @param [in] nBottomLine New bottomline for indicator
875  * @todo This function dublicates too much DrawRect() code.
876  */
877 void CLocationView::DrawVisibleAreaRect(CDC *pClientDC, int nTopLine, int nBottomLine)
878 {
879         CMergeDoc* pDoc = GetDocument();
880         int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
881         
882         if (nTopLine == -1)
883                 nTopLine = pDoc->GetView(nGroup, 0)->GetTopSubLine();
884         
885         if (nBottomLine == -1)
886         {
887                 const int nScreenLines = pDoc->GetView(nGroup, 1)->GetScreenLines();
888                 nBottomLine = nTopLine + nScreenLines;
889         }
890
891         CRect rc;
892         GetClientRect(rc);
893         int nbLines = INT_MAX;
894         pDoc->ForEachActiveGroupView([&](auto& pView) {
895                 nbLines = min(nbLines, pView->GetSubLineCount());
896         });
897
898         int nTopCoord = static_cast<int>(Y_OFFSET +
899                         (static_cast<double>(nTopLine * m_lineInPix)));
900         int nBottomCoord = static_cast<int>(Y_OFFSET +
901                         (static_cast<double>(nBottomLine * m_lineInPix)));
902         
903         double xbarBottom = min(nbLines / m_pixInLines + Y_OFFSET, rc.Height() - Y_OFFSET);
904         int barBottom = (int)xbarBottom;
905         // Make sure bottom coord is in bar range
906         nBottomCoord = min(nBottomCoord, barBottom);
907
908         // Ensure visible area is at least minimum height
909         if (nBottomCoord - nTopCoord < INDICATOR_MIN_HEIGHT)
910         {
911                 // If area is near top of file, add additional area to bottom
912                 // of the bar and vice versa.
913                 if (nTopCoord < Y_OFFSET + 20)
914                         nBottomCoord += INDICATOR_MIN_HEIGHT - (nBottomCoord - nTopCoord);
915                 else
916                 {
917                         // Make sure locationbox has min hight
918                         if ((nBottomCoord - nTopCoord) < INDICATOR_MIN_HEIGHT)
919                         {
920                                 // If we have a high number of lines, it may be better
921                                 // to keep the topline, otherwise the cursor can 
922                                 // jump up and down unexpected
923                                 nBottomCoord = nTopCoord + INDICATOR_MIN_HEIGHT;
924                         }
925                 }
926         }
927
928         // Store current values for later use (to check if area changes)
929         m_visibleTop = nTopCoord;
930         m_visibleBottom = nBottomCoord;
931
932         CRect rcVisibleArea(2, m_visibleTop, rc.right - 2, m_visibleBottom);
933         std::unique_ptr<CBitmap> pBitmap(CopyRectToBitmap(pClientDC, rcVisibleArea));
934         std::unique_ptr<CBitmap> pDarkenedBitmap(GetDarkenedBitmap(pClientDC, pBitmap.get(), IsColorDark(GetBackgroundColor())));
935         DrawBitmap(pClientDC, rcVisibleArea.left, rcVisibleArea.top, pDarkenedBitmap.get());
936 }
937
938 /**
939  * @brief Public function for updating visible area indicator.
940  *
941  * @param [in] nTopLine New topline for indicator
942  * @param [in] nBottomLine New bottomline for indicator
943  */
944 void CLocationView::UpdateVisiblePos(int nTopLine, int nBottomLine)
945 {
946         if (m_bDrawn)
947         {
948                 CMergeDoc *pDoc = GetDocument();
949                 int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
950                 int pane = 0;
951                 if (std::all_of(m_nSubLineCount, m_nSubLineCount + pDoc->m_nBuffers,
952                         [&](int nSubLineCount) { return nSubLineCount == pDoc->GetView(nGroup, pane++)->GetSubLineCount(); }))
953                 {
954                         int nTopCoord = static_cast<int>(Y_OFFSET +
955                                         (static_cast<double>(nTopLine * m_lineInPix)));
956                         int nBottomCoord = static_cast<int>(Y_OFFSET +
957                                         (static_cast<double>(nBottomLine * m_lineInPix)));
958                         if (m_visibleTop != nTopCoord || m_visibleBottom != nBottomCoord)
959                         {
960                                 // Visible area was changed
961                                 if (m_pSavedBackgroundBitmap != nullptr)
962                                 {
963                                         CClientDC dc(this);
964                                         CMyMemDC dcMem(&dc);
965                                         // Clear previous visible rect
966                                         DrawBitmap(&dcMem, 0, 0, m_pSavedBackgroundBitmap.get());
967
968                                         DrawVisibleAreaRect(&dcMem, nTopLine, nBottomLine);
969                                 }
970                         }
971                 }
972                 else
973                 {
974                         InvalidateRect(nullptr);
975                         for (pane = 0; pane < pDoc->m_nBuffers; pane++)
976                                 m_nSubLineCount[pane] = pDoc->GetView(nGroup, pane)->GetSubLineCount();
977                 }
978         }
979 }
980
981 /**
982  * @brief Unset pointers to MergeEditView when location pane is closed.
983  */
984 void CLocationView::OnClose()
985 {
986         CView::OnClose();
987 }
988
989 /** 
990  * @brief Draw lines connecting moved blocks.
991  */
992 void CLocationView::DrawConnectLines(CDC *pClientDC)
993 {
994         COLORREF clrMovedBlock = GetOptionsMgr()->GetInt(OPT_CLR_MOVEDBLOCK);
995         COLORREF clrSelectedMovedBlock = GetOptionsMgr()->GetInt(OPT_CLR_SELECTED_MOVEDBLOCK);
996         CBrush brushMovedBlock(clrMovedBlock);
997         CBrush brushSelectedMovedBlock(clrSelectedMovedBlock);
998         CPen penMovedBlock(PS_SOLID, 0, clrMovedBlock);
999         CPen penSelectedMovedBlock(PS_SOLID, 0, clrSelectedMovedBlock);
1000
1001         POSITION pos = m_movedLines.GetHeadPosition();
1002         int oldMode = pClientDC->SetPolyFillMode(ALTERNATE);
1003
1004         while (pos != nullptr)
1005         {
1006                 MovedLine line = m_movedLines.GetNext(pos);
1007                 CPen *pOldPen = (CPen *)pClientDC->SelectObject(line.currentDiff ? &penSelectedMovedBlock : &penMovedBlock);
1008                 if (line.ptLeftLower.y - line.ptLeftUpper.y <= 1)
1009                 {
1010                         CPoint points[4] = {
1011                                 line.ptLeftUpper,
1012                                 {(line.ptLeftUpper.x + line.ptRightUpper.x) / 2, line.ptLeftUpper.y},
1013                                 {(line.ptLeftUpper.x + line.ptRightUpper.x) / 2, line.ptRightLower.y},
1014                                 line.ptRightUpper };
1015                         pClientDC->PolyBezier(points, 4);
1016                 }
1017                 else
1018                 {
1019                         BYTE types[9] = {
1020                                 PT_MOVETO, PT_BEZIERTO, PT_BEZIERTO, PT_BEZIERTO,
1021                                 PT_LINETO, PT_BEZIERTO, PT_BEZIERTO, PT_BEZIERTO, PT_LINETO };
1022                         CPoint points[9] = {
1023                                 line.ptLeftUpper,
1024                                 {(line.ptLeftUpper.x + line.ptRightUpper.x) / 2, line.ptLeftUpper.y},
1025                                 {(line.ptLeftUpper.x + line.ptRightUpper.x) / 2, line.ptRightUpper.y},
1026                                 line.ptRightUpper,
1027                                 line.ptRightLower,
1028                                 {(line.ptLeftLower.x + line.ptRightLower.x) / 2, line.ptRightLower.y},
1029                                 {(line.ptLeftLower.x + line.ptRightLower.x) / 2, line.ptLeftLower.y},
1030                                 line.ptLeftLower, line.ptLeftUpper };
1031                         pClientDC->BeginPath();
1032                         pClientDC->PolyDraw(points, types, 8);
1033                         pClientDC->EndPath();
1034                         CRgn rgn;
1035                         if (rgn.CreateFromPath(pClientDC))
1036                                 pClientDC->FillRgn(&rgn, line.currentDiff ? &brushSelectedMovedBlock : &brushMovedBlock);
1037                         pClientDC->PolyDraw(points, types, 9);
1038                 }
1039                 pClientDC->SelectObject(pOldPen);
1040         }
1041
1042         pClientDC->SetPolyFillMode(oldMode);
1043 }
1044
1045 /** 
1046  * @brief Request frame window to store sizes.
1047  *
1048  * When locationview size changes we want to save new size
1049  * for new windows. But we must do it through frame window.
1050  * @param [in] nType Type of resizing, SIZE_MAXIMIZED etc.
1051  * @param [in] cx New panel width.
1052  * @param [in] cy New panel height.
1053  */
1054 void CLocationView::OnSize(UINT nType, int cx, int cy) 
1055 {
1056         CView::OnSize(nType, cx, cy);
1057
1058         // Height change needs block recalculation
1059         // TODO: Perhaps this should be determined from need to change bar size?
1060         // And we could change bar sizes more lazily, not from every one pixel change in size?
1061         if (cy != m_currentSize.cy)
1062                 m_bRecalculateBlocks = true;
1063
1064         if (cx != m_currentSize.cx)
1065         {
1066                 if (m_hwndFrame != nullptr)
1067                         ::PostMessage(m_hwndFrame, MSG_STORE_PANESIZES, 0, 0);
1068         }
1069
1070         m_currentSize.cx = cx;
1071         m_currentSize.cy = cy;
1072 }
1073
1074 /** 
1075  * @brief Draw marker for top of currently selected difference.
1076  * This function draws marker for top of currently selected difference.
1077  * This marker makes it a lot easier to see where currently selected
1078  * difference is in location bar. Especially when selected diffence is
1079  * small and it is not easy to find it otherwise.
1080  * @param [in] pDC Pointer to draw context.
1081  * @param [in] yCoord Y-coord of top of difference, -1 if no difference.
1082  */
1083 void CLocationView::DrawDiffMarker(CDC* pDC, int yCoord)
1084 {
1085         int nBuffers = GetDocument()->m_nBuffers;
1086
1087         CPoint points[3];
1088         points[0].x = m_bar[0].left - DIFFMARKER_WIDTH - 1;
1089         points[0].y = yCoord - DIFFMARKER_TOP;
1090         points[1].x = m_bar[0].left - 1;
1091         points[1].y = yCoord;
1092         points[2].x = m_bar[0].left - DIFFMARKER_WIDTH - 1;
1093         points[2].y = yCoord + DIFFMARKER_BOTTOM;
1094
1095         COLORREF clrBlue = GetSysColor(COLOR_ACTIVECAPTION);
1096         CPen penDarkBlue(PS_SOLID, 0, GetDarkenColor(clrBlue, 0.9));
1097         CPen* oldObj = (CPen*)pDC->SelectObject(&penDarkBlue);
1098         CBrush brushBlue(clrBlue);
1099         CBrush* pOldBrush = pDC->SelectObject(&brushBlue);
1100
1101         pDC->SetPolyFillMode(WINDING);
1102         pDC->Polygon(points, 3);
1103
1104         points[0].x = m_bar[nBuffers - 1].right + 1 + DIFFMARKER_WIDTH;
1105         points[1].x = m_bar[nBuffers - 1].right + 1;
1106         points[2].x = m_bar[nBuffers - 1].right + 1 + DIFFMARKER_WIDTH;
1107         pDC->Polygon(points, 3);
1108
1109         pDC->SelectObject(pOldBrush);
1110         pDC->SelectObject(oldObj);
1111 }