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