OSDN Git Service

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