OSDN Git Service

Update TranslationsStatus
[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 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;
46
47 /** 
48  * @brief Bars in location pane
49  */
50 enum LOCBAR_TYPE
51 {
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 */
57 };
58
59 /////////////////////////////////////////////////////////////////////////////
60 // CLocationView
61
62 IMPLEMENT_DYNCREATE(CLocationView, CView)
63
64
65 CLocationView::CLocationView()
66         : m_visibleTop(-1)
67         , m_visibleBottom(-1)
68 //      MOVEDLINE_LIST m_movedLines; //*< List of moved block connecting lines */
69         , m_hwndFrame(nullptr)
70         , m_pSavedBackgroundBitmap(nullptr)
71         , m_bDrawn(false)
72         , m_bRecalculateBlocks(true) // calculate for the first time
73 {
74         // NB: set m_bIgnoreTrivials to false to see trivial diffs in the LocationView
75         // There is no GUI to do this
76
77         SetConnectMovedBlocks(GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS));
78
79         std::fill_n(m_nSubLineCount, std::size(m_nSubLineCount), 0);
80 }
81
82 CLocationView::~CLocationView()
83 {
84 }
85
86 BEGIN_MESSAGE_MAP(CLocationView, CView)
87         //{{AFX_MSG_MAP(CLocationView)
88         ON_WM_LBUTTONDOWN()
89         ON_WM_LBUTTONUP()
90         ON_WM_MOUSEMOVE()
91         ON_WM_MOUSEACTIVATE()
92         ON_WM_MOUSEWHEEL()
93         ON_WM_LBUTTONDBLCLK()
94         ON_WM_CONTEXTMENU()
95         ON_WM_CLOSE()
96         ON_WM_SIZE()
97         ON_WM_VSCROLL()
98         ON_WM_ERASEBKGND()
99         //}}AFX_MSG_MAP
100 END_MESSAGE_MAP()
101
102
103 void CLocationView::SetConnectMovedBlocks(bool displayMovedBlocks) 
104 {
105         if (m_displayMovedBlocks == displayMovedBlocks)
106                 return;
107
108         m_displayMovedBlocks = displayMovedBlocks;
109         if (this->GetSafeHwnd() != nullptr)
110                 if (IsWindowVisible())
111                         Invalidate();
112 }
113
114 /////////////////////////////////////////////////////////////////////////////
115 // CLocationView diagnostics
116 #ifdef _DEBUG
117 CMergeDoc* CLocationView::GetDocument() // non-debug version is inline
118 {
119         ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
120         return (CMergeDoc*)m_pDocument;
121 }
122 #endif //_DEBUG
123
124 /////////////////////////////////////////////////////////////////////////////
125 // CLocationView message handlers
126
127 /**
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
131  * of the diff data.
132  */
133 void CLocationView::ForceRecalculate()
134 {
135         m_bRecalculateBlocks = true;
136         Invalidate();
137 }
138
139 /** 
140  * @brief Update view.
141  */
142 void CLocationView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )
143 {
144         UNREFERENCED_PARAMETER(pSender);
145         UNREFERENCED_PARAMETER(lHint);
146
147         m_bRecalculateBlocks = true;
148         Invalidate();
149 }
150
151 /** 
152  * @brief Override for CMemDC to work.
153  */
154 BOOL CLocationView::OnEraseBkgnd(CDC* pDC)
155 {
156         return FALSE;
157 }
158
159 static bool IsColorDark(COLORREF clrBackground)
160 {
161         return !(GetRValue(clrBackground) >= 0x80 || GetGValue(clrBackground) >= 0x80 || GetBValue(clrBackground) >= 0x80);
162
163 }
164
165 COLORREF CLocationView::GetBackgroundColor()
166 {
167         COLORREF clrBackground = GetDocument()->GetView(0, 0)->GetColor(COLORINDEX_WHITESPACE);
168         if (!IsColorDark(clrBackground))
169         {
170                 return RGB(
171                         (std::max)(GetRValue(clrBackground) - 24, 0),
172                         (std::max)(GetGValue(clrBackground) - 24, 0),
173                         (std::max)(GetBValue(clrBackground) - 12, 0));
174         }
175         return RGB(
176                 (std::min)(GetRValue(clrBackground) + 20, 255),
177                 (std::min)(GetGValue(clrBackground) + 20, 255),
178                 (std::min)(GetBValue(clrBackground) + 32, 255));
179 }
180
181 /**
182  * @brief Draw custom (non-white) background.
183  * @param [in] pDC Pointer to draw context.
184  */
185 void CLocationView::DrawBackground(CDC* pDC)
186 {
187         // Set brush to desired background color
188         CBrush backBrush(GetBackgroundColor());
189         
190         // Save old brush
191         CBrush* pOldBrush = pDC->SelectObject(&backBrush);
192         
193         CRect rect;
194         pDC->GetClipBox(&rect);     // Erase the area needed
195         
196         pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
197
198         pDC->SelectObject(pOldBrush);
199 }
200
201 /**
202  * @brief Calculate bar coordinates and scaling factors.
203  */
204 void CLocationView::CalculateBars()
205 {
206         CMergeDoc *pDoc = GetDocument();
207         CRect rc;
208         GetClientRect(rc);
209         const int w = rc.Width() / (pDoc->m_nBuffers * 2);
210         const int margin = (rc.Width() - w * pDoc->m_nBuffers) / (pDoc->m_nBuffers + 1);
211         int pane;
212         for (pane = 0; pane < pDoc->m_nBuffers; pane++)
213         {
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
217         int nbLines = 0;
218         pDoc->ForEachActiveGroupView([&](auto& pView) {
219                 nbLines = max(nbLines, pView->GetSubLineCount());
220         });
221
222         m_lineInPix = hTotal / nbLines;
223         m_pixInLines = nbLines / hTotal;
224         if (m_lineInPix > MAX_LINEPIX)
225         {
226                 m_lineInPix = MAX_LINEPIX;
227                 m_pixInLines = 1 / MAX_LINEPIX;
228         }
229
230         for (pane = 0; pane < pDoc->m_nBuffers; pane++)
231         {
232                 m_bar[pane].top = Y_OFFSET - 1;
233                 m_bar[pane].bottom = (LONG)(m_lineInPix * nbLines + Y_OFFSET + 1);
234         }
235 }
236
237 /**
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.
243  */
244 void CLocationView::CalculateBlocks()
245 {
246         // lineposition in pixels.
247         int nBeginY;
248         int nEndY;
249
250         m_diffBlocks.clear();
251
252         CMergeDoc *pDoc = GetDocument();
253         const int nDiffs = pDoc->m_diffList.GetSize();
254         if (nDiffs > 0)
255                 m_diffBlocks.reserve(nDiffs); // Pre-allocate space for the list.
256
257         int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
258         int nLineCount = pDoc->GetView(nGroup, 0)->GetLineCount();
259         int nDiff = pDoc->m_diffList.FirstSignificantDiff();
260         while (nDiff != -1)
261         {
262                 DIFFRANGE diff;
263                 VERIFY(pDoc->m_diffList.GetDiff(nDiff, diff));
264
265                 CMergeEditView *pView = pDoc->GetView(nGroup, 0);
266
267                 DiffBlock block;
268                 int i, nBlocks = 0;
269                 int bs[4] = {0};
270                 int minY = INT_MAX, maxY = -1;
271
272                 bs[nBlocks++] = diff.dbegin;
273                 for (i = 0; i < pDoc->m_nBuffers; i++)
274                 {
275                         if (diff.blank[i] >= 0)
276                         {
277                                 if (minY > diff.blank[i])
278                                         minY = diff.blank[i];
279                                 if (maxY < diff.blank[i])
280                                         maxY = diff.blank[i];
281                         }
282                 }
283                 if (minY == maxY)
284                 {
285                         bs[nBlocks++] = minY;
286                 }
287                 else if (maxY != -1)
288                 {
289                         bs[nBlocks++] = minY;
290                         bs[nBlocks++] = maxY;
291                 }
292                 bs[nBlocks] = diff.dend + 1;
293                 if (bs[nBlocks] >= nLineCount)
294                         bs[nBlocks] = nLineCount - 1;
295
296                 for (i = 0; i < nBlocks; i++)
297                 {
298                         CalculateBlocksPixel(
299                                 pView->GetSubLineIndex(bs[i]),
300                                 pView->GetSubLineIndex(bs[i + 1]),
301                                 pView->GetSubLines(bs[i + 1]), nBeginY, nEndY);
302
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;
308                         block.op = diff.op;
309                         m_diffBlocks.push_back(block);
310                 }
311
312                 nDiff = pDoc->m_diffList.NextSignificantDiff(nDiff);
313         }
314         m_bRecalculateBlocks = false;
315 }
316
317 /**
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
324
325  */
326 void CLocationView::CalculateBlocksPixel(int nBlockStart, int nBlockEnd,
327                 int nBlockLength, int &nBeginY, int &nEndY)
328 {
329         // Count how many line does the diff block have.
330         const int nBlockHeight = nBlockEnd - nBlockStart + nBlockLength;
331
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);
335 }
336
337 static COLORREF GetIntermediateColor(COLORREF a, COLORREF b, float ratio)
338 {
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);
342         return RGB(R, G, B);
343 }
344
345 static COLORREF GetDarkenColor(COLORREF a, double l)
346 {
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);
350         return RGB(R, G, B);
351 }
352
353 /** 
354  * @brief Draw maps of files.
355  *
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()
361  */
362 void CLocationView::OnDraw(CDC* pDC)
363 {
364         CMergeDoc *pDoc = GetDocument();
365         int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
366         if (pDoc->GetView(nGroup, 0) == nullptr)
367                 return;
368
369         if (!pDoc->GetView(nGroup, 0)->IsInitialized()) return;
370
371         bool bEditedAfterRescan = false;
372         int nPaneNotModified = -1;
373         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
374         {
375                 if (!pDoc->IsEditedAfterRescan(pane))
376                         nPaneNotModified = pane;
377                 else
378                         bEditedAfterRescan = true;
379         }
380
381         CRect rc;
382         GetClientRect(&rc);
383
384         CMyMemDC dc(pDC, &rc);
385
386         COLORREF cr[3] = {CLR_NONE, CLR_NONE, CLR_NONE};
387         COLORREF crt = CLR_NONE; // Text color
388         bool bwh = false;
389
390         m_movedLines.RemoveAll();
391
392         CalculateBars();
393         DrawBackground(&dc);
394
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);
401
402         // Draw bar outlines
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++)
407         {
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);
414
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);
420         }
421         dc.SelectObject(oldBrush);
422         dc.SelectObject(oldBrush);
423         dc.SelectObject(oldObj);
424
425         // Iterate the differences list and draw differences as colored blocks.
426
427         // Don't recalculate blocks if we earlier determined it is not needed
428         // This may save lots of processing
429         if (m_bRecalculateBlocks)
430                 CalculateBlocks();
431
432         unsigned nPrevEndY = static_cast<unsigned>(-1);
433         const unsigned nCurDiff = pDoc->GetCurrentDiff();
434         DIFFRANGE diCur;
435         if (nCurDiff != -1)
436                 pDoc->m_diffList.GetDiff(nCurDiff, diCur);
437
438         vector<DiffBlock>::const_iterator iter = m_diffBlocks.begin();
439         for (; iter != m_diffBlocks.end(); ++iter)
440         {
441                 if (nPaneNotModified == -1)
442                         continue;
443                 CMergeEditView *pView = pDoc->GetView(nGroup, nPaneNotModified);
444                 const bool bInsideDiff = (nCurDiff == (*iter).diff_index);
445
446                 if ((nPrevEndY != (*iter).bottom_coord) || bInsideDiff)
447                 {
448                         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
449                         {
450                                 if (pDoc->IsEditedAfterRescan(pane))
451                                         continue;
452                                 // Draw 3way-diff state
453                                 if (pDoc->m_nBuffers == 3 && pane < 2)
454                                 {
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);
462                                 }
463                                 // Draw block
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);
467                         }
468                 }
469                 nPrevEndY = (*iter).bottom_coord;
470
471                 if (bEditedAfterRescan)
472                         continue;
473
474                 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
475                 {
476                         if (m_displayMovedBlocks && pane < 2)
477                         {
478                                 int apparent0 = (*iter).top_line;
479                                 int apparent1 = pDoc->RightLineInMovedBlock(pane, apparent0);
480                                 const int nBlockHeight = (*iter).bottom_line - (*iter).top_line + 1;
481                                 if (apparent1 != -1)
482                                 {
483                                         MovedLine line;
484
485                                         line.currentDiff = bInsideDiff || (nCurDiff != -1 && diCur.dbegin <= apparent1 && apparent1 <= diCur.dend);
486
487                                         apparent0 = pView->GetSubLineIndex(apparent0);
488                                         apparent1 = pView->GetSubLineIndex(apparent1);
489
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())
504                                         {
505                                                 MovedLine& movedLineTail = m_movedLines.GetTail();
506                                                 if (line.apparent0 - movedLineTail.blockHeight - 1 == movedLineTail.apparent0 &&
507                                                     line.apparent1 - movedLineTail.blockHeight - 1 == movedLineTail.apparent1)
508                                                 {
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();
515                                                 }
516                                         }
517                                         m_movedLines.AddTail(line);
518                                 }
519                         }
520                 }
521         }
522
523         if (m_displayMovedBlocks)
524                 DrawConnectLines(&dc);
525
526         m_pSavedBackgroundBitmap.reset(CopyRectToBitmap(&dc, rc));
527
528         // Since we have invalidated locationbar there is no previous
529         // arearect to remove
530         m_visibleTop = -1;
531         m_visibleBottom = -1;
532         DrawVisibleAreaRect(&dc);
533
534         m_bDrawn = true;
535 }
536
537 /** 
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?
543  */
544 void CLocationView::DrawRect(CDC* pDC, const CRect& r, COLORREF cr, bool bSelected /*= false*/)
545 {
546         // Draw only colored blocks
547         if (cr != CLR_NONE && cr != GetSysColor(COLOR_WINDOW))
548         {
549                 CRect drawRect(r);
550                 drawRect.DeflateRect(1, 0);
551
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)
555                         ++drawRect.bottom;
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));
561
562                 if (bSelected)
563                 {
564                         DrawDiffMarker(pDC, r.top);
565                 }
566         }
567 }
568
569 /**
570  * @brief Capture the mouse target.
571  */
572 void CLocationView::OnLButtonDown(UINT nFlags, CPoint point) 
573 {
574         SetCapture();
575
576         bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
577         if (!GotoLocation(point, false, !bShift))
578                 CView::OnLButtonDown(nFlags, point);
579 }
580
581 /**
582  * @brief Release the mouse target.
583  */
584 void CLocationView::OnLButtonUp(UINT nFlags, CPoint point) 
585 {
586         ReleaseCapture();
587
588         CView::OnLButtonUp(nFlags, point);
589 }
590
591 /**
592  * @brief Process drag action on a captured mouse.
593  *
594  * Reposition on every dragged movement.
595  * The Screen update stress will be similar to a mouse wheeling.:-)
596  */
597 void CLocationView::OnMouseMove(UINT nFlags, CPoint point) 
598 {
599         if (GetCapture() == this)
600         {
601                 CMergeDoc *pDoc = GetDocument();
602                 int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
603
604                 // Don't go above bars.
605                 point.y = max(point.y, Y_OFFSET);
606
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;
611                 if (nSubLine < 0)
612                         nSubLine = 0;
613
614                 // Just a random choose as both view share the same scroll bar.
615                 CWnd *pView = pDoc->GetView(nGroup, 0);
616
617                 SCROLLINFO si = { sizeof SCROLLINFO };
618                 si.fMask = SIF_POS;
619                 si.nPos = nSubLine;
620                 pView->SetScrollInfo(SB_VERT, &si);
621
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.
625                 int pane;
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);
629         }
630
631         CView::OnMouseMove(nFlags, point);
632 }
633
634 int  CLocationView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message)
635 {
636         return MA_NOACTIVATE;
637 }
638
639 BOOL CLocationView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
640 {
641         CMergeEditView* pView = GetDocument()->GetActiveMergeView();
642         if (pView)
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);
646 }
647
648 /**
649  * User left double-clicked mouse
650  * @todo We can give alternative action to a double clicking. 
651  */
652 void CLocationView::OnLButtonDblClk(UINT nFlags, CPoint point) 
653 {
654         bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
655         if (!GotoLocation(point, false, !bShift))
656                 CView::OnLButtonDblClk(nFlags, point);
657 }
658
659 /**
660  * @brief Scroll both views to point given.
661  *
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.
667  *
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.
673  */
674 bool CLocationView::GotoLocation(const CPoint& point, bool bRealLine /*= true*/, bool bMoveAnchor)
675 {
676         CRect rc;
677         GetClientRect(rc);
678         CMergeDoc* pDoc = GetDocument();
679
680         if (!pDoc->GetActiveMergeView())
681                 return false;
682
683         int line = -1;
684         int bar = IsInsideBar(rc, point);
685         if (bar == BAR_0 || bar == BAR_1 || bar == BAR_2)
686         {
687                 line = GetLineFromYPos(point.y, bar, bRealLine);
688         }
689         else if (bar == BAR_YAREA)
690         {
691                 // Outside bars, use left bar
692                 bar = BAR_0;
693                 line = GetLineFromYPos(point.y, bar, false);
694         }
695         else
696                 return false;
697
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();
701
702         return true;
703 }
704
705 /**
706  * @brief Handle scroll events sent directly.
707  *
708  */
709 void CLocationView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
710 {
711         if (pScrollBar == nullptr)
712         {
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);
718                 return;
719         }
720         CView::OnVScroll (nSBCode, nPos, pScrollBar);
721 }
722
723 /**
724  * Show context menu and handle user selection.
725  */
726 void CLocationView::OnContextMenu(CWnd* pWnd, CPoint point) 
727 {
728         if (point.x == -1 && point.y == -1)
729         {
730                 //keystroke invocation
731                 CRect rect;
732                 GetClientRect(rect);
733                 ClientToScreen(rect);
734
735                 point = rect.TopLeft();
736                 point.Offset(5, 5);
737         }
738
739         CRect rc;
740         CPoint pt = point;
741         GetClientRect(rc);
742         ScreenToClient(&pt);
743         BCMenu menu;
744         VERIFY(menu.LoadMenu(IDR_POPUP_LOCATIONBAR));
745         theApp.TranslateMenu(menu.m_hMenu);
746
747         BCMenu* pPopup = static_cast<BCMenu *>(menu.GetSubMenu(0));
748         ASSERT(pPopup != nullptr);
749
750         CCmdUI cmdUI;
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)
754         {
755                 cmdUI.m_nID = cmdUI.m_pMenu->GetMenuItemID(cmdUI.m_nIndex);
756                 switch (cmdUI.m_nID)
757                 {
758                 case ID_DISPLAY_MOVED_NONE:
759                         cmdUI.SetRadio(!m_displayMovedBlocks);
760                         break;
761                 case ID_DISPLAY_MOVED_ALL:
762                         cmdUI.SetRadio(m_displayMovedBlocks);
763                         break;
764                 }
765         }
766
767         String strItem;
768         String strNum;
769         int nLine = -1;
770         int bar = IsInsideBar(rc, pt);
771
772         // If cursor over bar, format string with linenumber, else disable item
773         if (bar != BAR_NONE)
774         {
775                 // If outside bar area use left bar
776                 if (bar == BAR_YAREA)
777                         bar = BAR_0;
778                 nLine = GetLineFromYPos(pt.y, bar);
779                 strNum = strutils::to_str(nLine + 1); // Show linenumber not lineindex
780         }
781         else
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);
785
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());
790
791         CMergeDoc* pDoc = GetDocument();
792         switch (command)
793         {
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();
798                 break;
799         case ID_EDIT_WMGOTO:
800                 pDoc->GetActiveMergeGroupView(0)->WMGoto();
801                 break;
802         case ID_DISPLAY_MOVED_NONE:
803                 SetConnectMovedBlocks(false);
804                 pDoc->SetDetectMovedBlocks(false);
805                 break;
806         case ID_DISPLAY_MOVED_ALL:
807                 SetConnectMovedBlocks(true);
808                 pDoc->SetDetectMovedBlocks(true);
809                 break;
810         }
811 }
812
813 /** 
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]
819  */
820 int CLocationView::GetLineFromYPos(int nYCoord, int bar, bool bRealLine /*= true*/)
821 {
822         CMergeEditView *pView = GetDocument()->GetActiveMergeGroupView(bar);
823
824         int nSubLineIndex = (int) (m_pixInLines * (nYCoord - Y_OFFSET));
825
826         // Keep sub-line index in range.
827         if (nSubLineIndex < 0)
828         {
829                 nSubLineIndex = 0;
830         }
831         else if (nSubLineIndex >= pView->GetSubLineCount())
832         {
833                 nSubLineIndex = pView->GetSubLineCount() - 1;
834         }
835
836         // Find the real (not wrapped) line number from sub-line index.
837         int nLine = 0;
838         int nSubLine = 0;
839         pView->GetLineBySubLine(nSubLineIndex, nLine, nSubLine);
840
841         // Convert line number to line index.
842         if (nLine > 0)
843         {
844                 nLine -= 1;
845         }
846
847         // We've got a view line now
848         if (!bRealLine)
849                 return nLine;
850
851         // Get real line (exclude ghost lines)
852         CMergeDoc* pDoc = GetDocument();
853         const int nRealLine = pDoc->m_ptBuf[bar]->ComputeRealLine(nLine);
854         return nRealLine;
855 }
856
857 /** 
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.
862  */
863 int CLocationView::IsInsideBar(const CRect& rc, const POINT& pt)
864 {
865         CMergeDoc *pDoc = GetDocument();
866         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
867         {
868                 if (m_bar[pane].PtInRect(pt))
869                         return BAR_0 + pane;
870         }
871         if (pt.y > m_bar[0].top && pt.y <= m_bar[0].bottom)
872                 return BAR_YAREA;
873         return BAR_NONE;
874 }
875
876 /** 
877  * @brief Draws rect indicating visible area in file views.
878  *
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.
882  */
883 void CLocationView::DrawVisibleAreaRect(CDC *pClientDC, int nTopLine, int nBottomLine)
884 {
885         CMergeDoc* pDoc = GetDocument();
886         int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
887         
888         if (nTopLine == -1)
889                 nTopLine = pDoc->GetView(nGroup, 0)->GetTopSubLine();
890         
891         if (nBottomLine == -1)
892         {
893                 const int nScreenLines = pDoc->GetView(nGroup, 1)->GetScreenLines();
894                 nBottomLine = nTopLine + nScreenLines;
895         }
896
897         CRect rc;
898         GetClientRect(rc);
899         int nbLines = INT_MAX;
900         pDoc->ForEachActiveGroupView([&](auto& pView) {
901                 nbLines = min(nbLines, pView->GetSubLineCount());
902         });
903
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)));
908         
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);
913
914         // Ensure visible area is at least minimum height
915         if (nBottomCoord - nTopCoord < INDICATOR_MIN_HEIGHT)
916         {
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);
921                 else
922                 {
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;
927                 }
928         }
929
930         // Store current values for later use (to check if area changes)
931         m_visibleTop = nTopCoord;
932         m_visibleBottom = nBottomCoord;
933
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());
938 }
939
940 /**
941  * @brief Public function for updating visible area indicator.
942  *
943  * @param [in] nTopLine New topline for indicator
944  * @param [in] nBottomLine New bottomline for indicator
945  */
946 void CLocationView::UpdateVisiblePos(int nTopLine, int nBottomLine)
947 {
948         if (m_bDrawn)
949         {
950                 CMergeDoc *pDoc = GetDocument();
951                 int nGroup = pDoc->GetActiveMergeView()->m_nThisGroup;
952                 int pane = 0;
953                 if (std::all_of(m_nSubLineCount, m_nSubLineCount + pDoc->m_nBuffers,
954                         [&](int nSubLineCount) { return nSubLineCount == pDoc->GetView(nGroup, pane++)->GetSubLineCount(); }))
955                 {
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)
961                         {
962                                 // Visible area was changed
963                                 if (m_pSavedBackgroundBitmap != nullptr)
964                                 {
965                                         CClientDC dc(this);
966                                         CMyMemDC dcMem(&dc);
967                                         // Clear previous visible rect
968                                         DrawBitmap(&dcMem, 0, 0, m_pSavedBackgroundBitmap.get());
969
970                                         DrawVisibleAreaRect(&dcMem, nTopLine, nBottomLine);
971                                 }
972                         }
973                 }
974                 else
975                 {
976                         InvalidateRect(nullptr);
977                         for (pane = 0; pane < pDoc->m_nBuffers; pane++)
978                                 m_nSubLineCount[pane] = pDoc->GetView(nGroup, pane)->GetSubLineCount();
979                 }
980         }
981 }
982
983 /**
984  * @brief Unset pointers to MergeEditView when location pane is closed.
985  */
986 void CLocationView::OnClose()
987 {
988         CView::OnClose();
989 }
990
991 /** 
992  * @brief Draw lines connecting moved blocks.
993  */
994 void CLocationView::DrawConnectLines(CDC *pClientDC)
995 {
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);
1002
1003         POSITION pos = m_movedLines.GetHeadPosition();
1004         int oldMode = pClientDC->SetPolyFillMode(ALTERNATE);
1005
1006         while (pos != nullptr)
1007         {
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)
1011                 {
1012                         CPoint points[4] = {
1013                                 line.ptLeftUpper,
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);
1018                 }
1019                 else
1020                 {
1021                         BYTE types[9] = {
1022                                 PT_MOVETO, PT_BEZIERTO, PT_BEZIERTO, PT_BEZIERTO,
1023                                 PT_LINETO, PT_BEZIERTO, PT_BEZIERTO, PT_BEZIERTO, PT_LINETO };
1024                         CPoint points[9] = {
1025                                 line.ptLeftUpper,
1026                                 {(line.ptLeftUpper.x + line.ptRightUpper.x) / 2, line.ptLeftUpper.y},
1027                                 {(line.ptLeftUpper.x + line.ptRightUpper.x) / 2, line.ptRightUpper.y},
1028                                 line.ptRightUpper,
1029                                 line.ptRightLower,
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();
1036                         CRgn rgn;
1037                         if (rgn.CreateFromPath(pClientDC))
1038                                 pClientDC->FillRgn(&rgn, line.currentDiff ? &brushSelectedMovedBlock : &brushMovedBlock);
1039                         pClientDC->PolyDraw(points, types, 9);
1040                 }
1041                 pClientDC->SelectObject(pOldPen);
1042         }
1043
1044         pClientDC->SetPolyFillMode(oldMode);
1045 }
1046
1047 /** 
1048  * @brief Request frame window to store sizes.
1049  *
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.
1055  */
1056 void CLocationView::OnSize(UINT nType, int cx, int cy) 
1057 {
1058         CView::OnSize(nType, cx, cy);
1059
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;
1065
1066         if (cx != m_currentSize.cx)
1067         {
1068                 if (m_hwndFrame != nullptr)
1069                         ::PostMessage(m_hwndFrame, MSG_STORE_PANESIZES, 0, 0);
1070         }
1071
1072         m_currentSize.cx = cx;
1073         m_currentSize.cy = cy;
1074 }
1075
1076 /** 
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.
1084  */
1085 void CLocationView::DrawDiffMarker(CDC* pDC, int yCoord)
1086 {
1087         int nBuffers = GetDocument()->m_nBuffers;
1088
1089         CPoint points[3];
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;
1096
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);
1102
1103         pDC->SetPolyFillMode(WINDING);
1104         pDC->Polygon(points, 3);
1105
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);
1110
1111         pDC->SelectObject(pOldBrush);
1112         pDC->SelectObject(oldObj);
1113 }