OSDN Git Service

Merge pull request #93 from GreyMerlin/master
[winmerge-jp/winmerge-jp.git] / Src / ImgMergeFrm.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean 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  * @file  ImgMergeFrm.cpp
23  *
24  * @brief Implementation file for CImgMergeFrame
25  *
26  */
27
28 #include "stdafx.h"
29 #include "ImgMergeFrm.h"
30 #include "Merge.h"
31 #include "MainFrm.h"
32 #include "BCMenu.h"
33 #include "DirDoc.h"
34 #include "OptionsDef.h"
35 #include "OptionsMgr.h"
36 #include "OptionsDiffColors.h"
37 #include "OptionsCustomColors.h"
38 #include "paths.h"
39 #include "PathContext.h"
40 #include "unicoder.h"
41 #include "FileOrFolderSelect.h"
42 #include "UniFile.h"
43 #include "SaveClosingDlg.h"
44 #include "FileLocation.h"
45 #include "Constants.h"
46 #include "DropHandler.h"
47 #include <cmath>
48
49 #ifdef _DEBUG
50 #define new DEBUG_NEW
51 #endif
52
53 /////////////////////////////////////////////////////////////////////////////
54 // CImgMergeFrame
55
56 IMPLEMENT_DYNCREATE(CImgMergeFrame, CMDIChildWnd)
57
58 BEGIN_MESSAGE_MAP(CImgMergeFrame, CMDIChildWnd)
59         //{{AFX_MSG_MAP(CImgMergeFrame)
60         ON_WM_CREATE()
61         ON_WM_CLOSE()
62         ON_WM_MDIACTIVATE()
63         ON_WM_SIZE()
64         ON_WM_GETMINMAXINFO()
65         ON_COMMAND(ID_FILE_SAVE, OnFileSave)
66         ON_UPDATE_COMMAND_UI(ID_VIEW_LOCATION_BAR, OnUpdateControlBarMenu)
67         ON_COMMAND_EX(ID_VIEW_LOCATION_BAR, OnBarCheck)
68         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
69         ON_COMMAND(ID_FILE_SAVE_LEFT, OnFileSaveLeft)
70         ON_COMMAND(ID_FILE_SAVE_MIDDLE, OnFileSaveMiddle)
71         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_MIDDLE, OnUpdateFileSaveMiddle)
72         ON_COMMAND(ID_FILE_SAVE_RIGHT, OnFileSaveRight)
73         ON_COMMAND(ID_FILE_SAVEAS_LEFT, OnFileSaveAsLeft)
74         ON_UPDATE_COMMAND_UI(ID_FILE_SAVEAS_MIDDLE, OnUpdateFileSaveAsMiddle)
75         ON_COMMAND(ID_FILE_SAVEAS_MIDDLE, OnFileSaveAsMiddle)
76         ON_COMMAND(ID_FILE_SAVEAS_RIGHT, OnFileSaveAsRight)
77         ON_COMMAND(ID_FILE_CLOSE, OnFileClose)
78         ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
79         ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateLeftReadOnly)
80         ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnMiddleReadOnly)
81         ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateMiddleReadOnly)
82         ON_COMMAND(ID_FILE_RIGHT_READONLY, OnRightReadOnly)
83         ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateRightReadOnly)
84         ON_COMMAND(ID_RESCAN, OnFileReload)
85         ON_COMMAND(ID_MERGE_COMPARE_HEX, OnFileRecompareAsBinary)
86         ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnWindowChangePane)
87         ON_MESSAGE_VOID(WM_IDLEUPDATECMDUI, OnIdleUpdateCmdUI)
88         ON_MESSAGE(MSG_STORE_PANESIZES, OnStorePaneSizes)
89         ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
90         ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
91         ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
92         ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
93         ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
94         ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
95         ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
96         ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
97         ON_COMMAND(ID_VIEW_SPLITVERTICALLY, OnViewSplitVertically)
98         ON_UPDATE_COMMAND_UI(ID_VIEW_SPLITVERTICALLY, OnUpdateViewSplitVertically)
99         ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
100         ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
101         ON_COMMAND(ID_LASTDIFF, OnLastdiff)
102         ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
103         ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
104         ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
105         ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
106         ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
107         ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
108         ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
109         ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
110         ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
111         ON_COMMAND(ID_L2R, OnL2r)
112         ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
113         ON_COMMAND(ID_R2L, OnR2l)
114         ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
115         ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
116         ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
117         ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
118         ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
119         ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
120         ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
121         ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
122         ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
123         ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
124         ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
125         ON_COMMAND(ID_IMG_VIEWDIFFERENCES, OnImgViewDifferences)
126         ON_UPDATE_COMMAND_UI(ID_IMG_VIEWDIFFERENCES, OnUpdateImgViewDifferences)
127         ON_COMMAND_RANGE(ID_IMG_ZOOM_25, ID_IMG_ZOOM_800, OnImgZoom)
128         ON_UPDATE_COMMAND_UI_RANGE(ID_IMG_ZOOM_25, ID_IMG_ZOOM_800, OnUpdateImgZoom)
129         ON_COMMAND_RANGE(ID_IMG_OVERLAY_NONE, ID_IMG_OVERLAY_ALPHABLEND_ANIM, OnImgOverlayMode)
130         ON_UPDATE_COMMAND_UI_RANGE(ID_IMG_OVERLAY_NONE, ID_IMG_OVERLAY_ALPHABLEND_ANIM, OnUpdateImgOverlayMode)
131         ON_COMMAND_RANGE(ID_IMG_DRAGGINGMODE_NONE, ID_IMG_DRAGGINGMODE_ADJUST_OFFSET, OnImgDraggingMode)
132         ON_UPDATE_COMMAND_UI_RANGE(ID_IMG_DRAGGINGMODE_NONE, ID_IMG_DRAGGINGMODE_ADJUST_OFFSET, OnUpdateImgDraggingMode)
133         ON_COMMAND_RANGE(ID_IMG_DIFFBLOCKSIZE_1, ID_IMG_DIFFBLOCKSIZE_32, OnImgDiffBlockSize)
134         ON_UPDATE_COMMAND_UI_RANGE(ID_IMG_DIFFBLOCKSIZE_1, ID_IMG_DIFFBLOCKSIZE_32, OnUpdateImgDiffBlockSize)
135         ON_COMMAND_RANGE(ID_IMG_THRESHOLD_0, ID_IMG_THRESHOLD_64, OnImgThreshold)
136         ON_UPDATE_COMMAND_UI_RANGE(ID_IMG_THRESHOLD_0, ID_IMG_THRESHOLD_64, OnUpdateImgThreshold)
137         ON_COMMAND(ID_IMG_PREVPAGE, OnImgPrevPage)
138         ON_UPDATE_COMMAND_UI(ID_IMG_PREVPAGE, OnUpdateImgPrevPage)
139         ON_COMMAND(ID_IMG_NEXTPAGE, OnImgNextPage)
140         ON_UPDATE_COMMAND_UI(ID_IMG_NEXTPAGE, OnUpdateImgNextPage)
141         ON_COMMAND(ID_IMG_CURPANE_PREVPAGE, OnImgCurPanePrevPage)
142         ON_UPDATE_COMMAND_UI(ID_IMG_CURPANE_PREVPAGE, OnUpdateImgCurPanePrevPage)
143         ON_COMMAND(ID_IMG_CURPANE_NEXTPAGE, OnImgCurPaneNextPage)
144         ON_UPDATE_COMMAND_UI(ID_IMG_CURPANE_NEXTPAGE, OnUpdateImgCurPaneNextPage)
145         ON_COMMAND(ID_IMG_USEBACKCOLOR, OnImgUseBackColor)
146         ON_UPDATE_COMMAND_UI(ID_IMG_USEBACKCOLOR, OnUpdateImgUseBackColor)
147         ON_COMMAND(ID_TOOLS_GENERATEREPORT, OnToolsGenerateReport)
148         ON_COMMAND(ID_REFRESH, OnRefresh)
149         //}}AFX_MSG_MAP
150 END_MESSAGE_MAP()
151
152 CMenu CImgMergeFrame::menu;
153
154 /////////////////////////////////////////////////////////////////////////////
155 // CImgMergeFrame construction/destruction
156
157 CImgMergeFrame::CImgMergeFrame()
158 : m_hIdentical(NULL)
159 , m_hDifferent(NULL)
160 , m_pDirDoc(NULL)
161 , m_bAutoMerged(false)
162 , m_pImgMergeWindow(NULL)
163 , m_pImgToolWindow(NULL)
164 {
165         std::fill_n(m_nLastSplitPos, 2, 0);
166         std::fill_n(m_nBufferType, 3, BUFFER_NORMAL);
167         std::fill_n(m_bRO, 3, false);
168 }
169
170 CImgMergeFrame::~CImgMergeFrame()
171 {
172         if (m_pDirDoc)
173         {
174                 m_pDirDoc->MergeDocClosing(this);
175                 m_pDirDoc = NULL;
176         }
177
178         HMODULE hModule = GetModuleHandleW(L"WinIMergeLib.dll");
179         if (hModule)
180         {
181                 bool (*WinIMerge_DestroyWindow)(IImgMergeWindow *) = 
182                         (bool (*)(IImgMergeWindow *))GetProcAddress(hModule, "WinIMerge_DestroyWindow");
183                 bool (*WinIMerge_DestroyToolWindow)(IImgToolWindow *) = 
184                         (bool (*)(IImgToolWindow *))GetProcAddress(hModule, "WinIMerge_DestroyToolWindow");
185                 if (WinIMerge_DestroyWindow && WinIMerge_DestroyToolWindow)
186                 {
187                         if (m_pImgMergeWindow)
188                         {
189                                 for (int pane = 0; pane < m_pImgMergeWindow->GetPaneCount(); ++pane)
190                                         RevokeDragDrop(m_pImgMergeWindow->GetPaneHWND(pane));
191                                 WinIMerge_DestroyWindow(m_pImgMergeWindow);
192                         }
193                         if (m_pImgToolWindow)
194                                 WinIMerge_DestroyToolWindow(m_pImgToolWindow);
195                         m_pImgMergeWindow = NULL;
196                         m_pImgToolWindow = NULL;
197                 }
198         }
199 }
200
201 bool CImgMergeFrame::OpenDocs(int nFiles, const FileLocation fileloc[], const bool bRO[], const String strDesc[], int nPane, CMDIFrameWnd *pParent)
202 {
203
204         for (int pane = 0; pane < nFiles; ++pane)
205         {
206                 m_filePaths.SetPath(pane, fileloc[pane].filepath);
207                 m_bRO[pane] = bRO[pane];
208                 m_strDesc[pane] = strDesc ? strDesc[pane] : _T("");
209                 m_nBufferType[pane] = (!strDesc || strDesc[pane].empty()) ? BUFFER_NORMAL : BUFFER_NORMAL_NAMED;
210         }
211         SetTitle(NULL);
212
213         LPCTSTR lpszWndClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
214                         LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW+1), NULL);
215
216         if (!CMDIChildWnd::Create(lpszWndClass, GetTitle(), WS_OVERLAPPEDWINDOW | WS_CHILD, rectDefault, pParent))
217                 return false;
218
219         int nCmdShow = SW_SHOW;
220         if (theApp.GetProfileInt(_T("Settings"), _T("ActiveFrameMax"), FALSE))
221                 nCmdShow = SW_SHOWMAXIMIZED;
222         ShowWindow(nCmdShow);
223         BringToTop(nCmdShow);
224
225         OnRefresh();
226
227         if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
228                 m_pImgMergeWindow->FirstDiff();
229
230         if (nPane < 0)
231         {
232                 nPane = theApp.GetProfileInt(_T("Settings"), _T("ActivePane"), 0);
233                 if (nPane < 0 || nPane >= m_pImgMergeWindow->GetPaneCount())
234                         nPane = 0;
235         }
236
237         m_pImgMergeWindow->SetActivePane(nPane);
238
239         return true;
240 }
241
242 void CImgMergeFrame::ChangeFile(int nBuffer, const String& path)
243 {
244         for (int pane = 0; pane < m_pImgMergeWindow->GetPaneCount(); ++pane)
245                 RevokeDragDrop(m_pImgMergeWindow->GetPaneHWND(pane));
246
247         m_filePaths[nBuffer] = path;
248         m_nBufferType[nBuffer] = BUFFER_NORMAL;
249         m_strDesc[nBuffer] = _T("");
250
251         if (m_filePaths.GetSize() == 2)
252                 m_pImgMergeWindow->OpenImages(ucr::toUTF16(m_filePaths[0]).c_str(), ucr::toUTF16(m_filePaths[1]).c_str());
253         else
254                 m_pImgMergeWindow->OpenImages(ucr::toUTF16(m_filePaths[0]).c_str(), ucr::toUTF16(m_filePaths[1]).c_str(), ucr::toUTF16(m_filePaths[2]).c_str());
255
256         for (int pane = 0; pane < m_filePaths.GetSize(); ++pane)
257         {
258                 m_fileInfo[pane].Update(m_filePaths[pane]);
259
260                 RegisterDragDrop(m_pImgMergeWindow->GetPaneHWND(pane),
261                         new DropHandler(std::bind(&CImgMergeFrame::OnDropFiles, this, pane, std::placeholders::_1)));
262         }
263
264         UpdateHeaderPath(nBuffer);
265         UpdateLastCompareResult();
266 }
267
268 bool CImgMergeFrame::IsModified() const
269 {
270         for (int pane = 0; pane < m_pImgMergeWindow->GetPaneCount(); ++pane)
271                 if (m_pImgMergeWindow->IsModified(pane))
272                         return true;
273         return false;
274 }
275
276 void CImgMergeFrame::DoAutoMerge(int dstPane)
277 {
278         int autoMergedCount = m_pImgMergeWindow->CopyDiff3Way(dstPane);
279         if (autoMergedCount > 0)
280                 m_bAutoMerged = true;
281
282         // move to first conflict 
283         m_pImgMergeWindow->FirstConflict();
284
285         AfxMessageBox(
286                 strutils::format_string2(
287                         _T("The number of automatically merged changes: %1\nThe number of unresolved conflicts: %2"), 
288                         strutils::format(_T("%d"), autoMergedCount),
289                         strutils::format(_T("%d"), m_pImgMergeWindow->GetConflictCount())).c_str(),
290                 MB_ICONINFORMATION);
291 }
292
293 /**
294  * @brief DirDoc gives us its identity just after it creates us
295  */
296 void CImgMergeFrame::SetDirDoc(CDirDoc * pDirDoc)
297 {
298         ASSERT(pDirDoc && !m_pDirDoc);
299         m_pDirDoc = pDirDoc;
300 }
301
302 bool CImgMergeFrame::IsFileChangedOnDisk(int pane) const
303 {
304         DiffFileInfo dfi;
305         dfi.Update(m_filePaths[pane]);
306         int tolerance = 0;
307         if (GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME))
308                 tolerance = SmallTimeDiff; // From MainFrm.h
309         int64_t timeDiff = dfi.mtime - m_fileInfo[pane].mtime;
310         if (timeDiff < 0) timeDiff = -timeDiff;
311         if ((timeDiff > tolerance * Poco::Timestamp::resolution()) || (dfi.size != m_fileInfo[pane].size))
312                 return true;
313         return false;
314 }
315
316 void CImgMergeFrame::CheckFileChanged(void)
317 {
318         for (int pane = 0; pane < m_pImgMergeWindow->GetPaneCount(); ++pane)
319         {
320                 if (IsFileChangedOnDisk(pane))
321                 {
322                         String msg = strutils::format_string1(_("Another application has updated file\n%1\nsince WinMerge scanned it last time.\n\nDo you want to reload the file?"), m_filePaths[pane]);
323                         if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING) == IDYES)
324                         {
325                                 OnFileReload();
326                         }
327                         break;
328                 }
329         }
330 }
331
332 BOOL CImgMergeFrame::PreCreateWindow(CREATESTRUCT& cs)
333 {
334         CMDIChildWnd::PreCreateWindow(cs);
335         cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
336         return TRUE;
337 }
338         
339 /**
340  * @brief Create a status bar to be associated with a heksedit control
341  */
342 void CImgMergeFrame::CreateImgWndStatusBar(CStatusBar &wndStatusBar, CWnd *pwndPane)
343 {
344         wndStatusBar.Create(pwndPane, WS_CHILD|WS_VISIBLE);
345         wndStatusBar.SetIndicators(0, 1);
346         wndStatusBar.SetPaneInfo(0, 0, SBPS_STRETCH, 0);
347         wndStatusBar.SetParent(this);
348         wndStatusBar.SetWindowPos(&wndBottom, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
349 }
350
351 void CImgMergeFrame::OnChildPaneEvent(const IImgMergeWindow::Event& evt)
352 {
353         if (evt.eventType == IImgMergeWindow::KEYDOWN)
354         {
355                 CImgMergeFrame *pFrame = reinterpret_cast<CImgMergeFrame *>(evt.userdata);
356                 switch (evt.keycode)
357                 {
358                 case VK_PRIOR:
359                 case VK_NEXT:
360                         ::SendMessage(pFrame->m_pImgMergeWindow->GetPaneHWND(evt.pane), WM_VSCROLL, evt.keycode == VK_PRIOR ? SB_PAGEUP : SB_PAGEDOWN, 0);
361                         break;
362                 case VK_LEFT:
363                 case VK_RIGHT:
364                 case VK_UP:
365                 case VK_DOWN:
366                         if (GetAsyncKeyState(VK_SHIFT))
367                         {
368                                 int nActivePane = pFrame->m_pImgMergeWindow->GetActivePane();
369                                 int m = GetAsyncKeyState(VK_CONTROL) ? 8 : 1;
370                                 int dx = (-(evt.keycode == VK_LEFT) + (evt.keycode == VK_RIGHT)) * m;
371                                 int dy = (-(evt.keycode == VK_UP) + (evt.keycode == VK_DOWN)) * m;
372                                 pFrame->m_pImgMergeWindow->AddImageOffset(nActivePane, dx, dy);
373                         }
374                         break;
375                 }
376         }
377
378 /*      if (evt.eventType == IImgMergeWindow::CONTEXTMENU)
379         {
380                 CImgMergeFrame *pFrame = reinterpret_cast<CImgMergeFrame *>(evt.userdata);
381                 BCMenu menu;
382                 menu.LoadMenu(MAKEINTRESOURCE(IDR_POPUP_IMGMERGEVIEW));
383                 BCMenu* pPopup = (BCMenu *)menu.GetSubMenu(0);
384                 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
385                         evt.x, evt.y, AfxGetMainWnd());
386         }
387         */
388 }
389
390 /**
391  * @brief returns true if WinIMergeLib.dll is loadable
392  */
393 bool CImgMergeFrame::IsLoadable()
394 {
395         static HMODULE hModule;
396         if (!hModule)
397         {
398                 hModule = LoadLibraryW(L"WinIMerge\\WinIMergeLib.dll");
399                 if (!hModule)
400                         return false;
401         }
402         return true;
403 }
404
405 /**
406  * @brief Create the splitter, the filename bar, the status bar, and the two views
407  */
408 BOOL CImgMergeFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/,
409         CCreateContext* pContext)
410 {
411         if (!IsLoadable())
412                 return FALSE;
413
414         HMODULE hModule = GetModuleHandleW(L"WinIMergeLib.dll");
415         if (!hModule)
416                 return FALSE;
417
418         IImgMergeWindow * (*WinIMerge_CreateWindow)(HINSTANCE hInstance, HWND hWndParent, int nID) = 
419                         (IImgMergeWindow * (*)(HINSTANCE hInstance, HWND hWndParent, int nID))GetProcAddress(hModule, "WinIMerge_CreateWindow");
420         if (!WinIMerge_CreateWindow || 
421                 (m_pImgMergeWindow = WinIMerge_CreateWindow(hModule, m_hWnd, AFX_IDW_PANE_FIRST)) == NULL)
422         {
423                 FreeLibrary(hModule);
424                 return FALSE;
425         }
426
427         COLORSETTINGS colors;
428         Options::DiffColors::Load(GetOptionsMgr(), colors);
429         m_pImgMergeWindow->SetDiffColor(colors.clrDiff);
430         m_pImgMergeWindow->SetSelDiffColor(colors.clrSelDiff);
431         m_pImgMergeWindow->AddEventListener(OnChildPaneEvent, this);
432         LoadOptions();
433         
434         bool bResult;
435         if (m_filePaths.GetSize() == 2)
436                 bResult = m_pImgMergeWindow->OpenImages(ucr::toUTF16(m_filePaths[0]).c_str(), ucr::toUTF16(m_filePaths[1]).c_str());
437         else
438                 bResult = m_pImgMergeWindow->OpenImages(ucr::toUTF16(m_filePaths[0]).c_str(), ucr::toUTF16(m_filePaths[1]).c_str(), ucr::toUTF16(m_filePaths[2]).c_str());
439
440         for (int pane = 0; pane < m_filePaths.GetSize(); ++pane)
441         {
442                 m_fileInfo[pane].Update(m_filePaths[pane]);
443                 
444                 RegisterDragDrop(m_pImgMergeWindow->GetPaneHWND(pane), 
445                         new DropHandler(std::bind(&CImgMergeFrame::OnDropFiles, this, pane, std::placeholders::_1)));
446         }
447
448         // Merge frame has also a dockable bar at the very left
449         // This is not the client area, but we create it now because we want
450         // to use the CCreateContext
451         String sCaption = theApp.LoadString(IDS_LOCBAR_CAPTION);
452         if (!m_wndLocationBar.Create(this, sCaption.c_str(), WS_CHILD | WS_VISIBLE, ID_VIEW_LOCATION_BAR))
453         {
454                 TRACE0("Failed to create LocationBar\n");
455                 return FALSE;
456         }
457
458         IImgToolWindow * (*WinIMerge_CreateToolWindow)(HINSTANCE hInstance, HWND hWndParent, IImgMergeWindow *) = 
459                         (IImgToolWindow * (*)(HINSTANCE hInstance, HWND hWndParent, IImgMergeWindow *pImgMergeWindow))GetProcAddress(hModule, "WinIMerge_CreateToolWindow");
460         if (!WinIMerge_CreateToolWindow ||
461                 (m_pImgToolWindow = WinIMerge_CreateToolWindow(hModule, m_wndLocationBar.m_hWnd, m_pImgMergeWindow)) == NULL)
462         {
463                 return FALSE;
464         }
465
466         m_wndLocationBar.SetFrameHwnd(GetSafeHwnd());
467
468         return TRUE;
469 }
470
471 /////////////////////////////////////////////////////////////////////////////
472 // CImgMergeFrame message handlers
473
474 int CImgMergeFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
475 {
476
477         if (CMDIChildWnd::OnCreate(lpCreateStruct) == -1)
478                 return -1;
479
480         EnableDocking(CBRS_ALIGN_TOP | CBRS_ALIGN_BOTTOM | CBRS_ALIGN_LEFT | CBRS_ALIGN_RIGHT);
481
482         // Merge frame has a header bar at top
483         if (!m_wndFilePathBar.Create(this))
484         {
485                 TRACE0("Failed to create dialog bar\n");
486                 return FALSE;      // fail to create
487         }
488
489         m_wndFilePathBar.SetPaneCount(m_pImgMergeWindow->GetPaneCount());
490         m_wndFilePathBar.SetOnSetFocusCallback([&](int pane) { m_pImgMergeWindow->SetActivePane(pane); });
491
492         // Merge frame also has a dockable bar at the very left
493         // created in OnCreateClient 
494         m_wndLocationBar.SetBarStyle(m_wndLocationBar.GetBarStyle() |
495                 CBRS_SIZE_DYNAMIC | CBRS_ALIGN_LEFT);
496         m_wndLocationBar.EnableDocking(CBRS_ALIGN_LEFT | CBRS_ALIGN_RIGHT);
497         DockControlBar(&m_wndLocationBar, AFX_IDW_DOCKBAR_LEFT);
498
499         for (int nPane = 0; nPane < m_pImgMergeWindow->GetPaneCount(); nPane++)
500         {
501                 m_pImgMergeWindow->SetReadOnly(nPane, m_bRO[nPane]);
502
503                 m_wndFilePathBar.SetActive(nPane, FALSE);
504                 CreateImgWndStatusBar(m_wndStatusBar[nPane], CWnd::FromHandle(m_pImgMergeWindow->GetPaneHWND(nPane)));
505                 UpdateHeaderPath(nPane);
506         }
507
508         CSize size = m_wndStatusBar[0].CalcFixedLayout(TRUE, TRUE);
509         m_rectBorder.bottom = size.cy;
510
511         m_hIdentical = AfxGetApp()->LoadIcon(IDI_EQUALIMAGE);
512         m_hDifferent = AfxGetApp()->LoadIcon(IDI_NOTEQUALIMAGE);
513
514         return 0;
515 }
516
517 /**
518 * @brief We must use this function before a call to SetDockState
519 *
520 * @note Without this, SetDockState will assert or crash if a bar from the
521 * CDockState is missing in the current CChildFrame.
522 * The bars are identified with their ID. This means the missing bar bug is triggered
523 * when we run WinMerge after changing the ID of a bar.
524 */
525 BOOL CImgMergeFrame::EnsureValidDockState(CDockState& state)
526 {
527         for (int i = (int)state.m_arrBarInfo.GetSize() - 1; i >= 0; i--)
528         {
529                 BOOL barIsCorrect = TRUE;
530                 CControlBarInfo* pInfo = (CControlBarInfo*)state.m_arrBarInfo[i];
531                 if (!pInfo)
532                         barIsCorrect = FALSE;
533                 else
534                 {
535                         if (!pInfo->m_bFloating)
536                         {
537                                 pInfo->m_pBar = GetControlBar(pInfo->m_nBarID);
538                                 if (!pInfo->m_pBar)
539                                         barIsCorrect = FALSE; //toolbar id's probably changed   
540                         }
541                 }
542
543                 if (!barIsCorrect)
544                         state.m_arrBarInfo.RemoveAt(i);
545         }
546         return TRUE;
547 }
548
549 /**
550  * @brief Save the window's position, free related resources, and destroy the window
551  */
552 BOOL CImgMergeFrame::DestroyWindow() 
553 {
554         SavePosition();
555         SaveOptions();
556         // If we are active, save the restored/maximized state
557         // If we are not, do nothing and let the active frame do the job.
558         if (GetParentFrame()->GetActiveFrame() == this)
559         {
560                 WINDOWPLACEMENT wp;
561                 wp.length = sizeof(WINDOWPLACEMENT);
562                 GetWindowPlacement(&wp);
563                 theApp.WriteProfileInt(_T("Settings"), _T("ActiveFrameMax"), (wp.showCmd == SW_MAXIMIZE));
564         }
565
566         return CMDIChildWnd::DestroyWindow();
567 }
568
569 void CImgMergeFrame::LoadOptions()
570 {
571         m_pImgMergeWindow->SetShowDifferences(GetOptionsMgr()->GetBool(OPT_CMP_IMG_SHOWDIFFERENCES));
572         m_pImgMergeWindow->SetOverlayMode(static_cast<IImgMergeWindow::OVERLAY_MODE>(GetOptionsMgr()->GetInt(OPT_CMP_IMG_OVERLAYMOVE)));
573         m_pImgMergeWindow->SetOverlayAlpha(GetOptionsMgr()->GetInt(OPT_CMP_IMG_OVERLAYALPHA) / 100.0);
574         m_pImgMergeWindow->SetDraggingMode(static_cast<IImgMergeWindow::DRAGGING_MODE>(GetOptionsMgr()->GetInt(OPT_CMP_IMG_DRAGGING_MODE)));
575         m_pImgMergeWindow->SetZoom(GetOptionsMgr()->GetInt(OPT_CMP_IMG_ZOOM) / 1000.0);
576         m_pImgMergeWindow->SetUseBackColor(GetOptionsMgr()->GetBool(OPT_CMP_IMG_USEBACKCOLOR));
577         COLORREF clrBackColor = GetOptionsMgr()->GetInt(OPT_CMP_IMG_BACKCOLOR);
578         RGBQUAD backColor = {GetBValue(clrBackColor), GetGValue(clrBackColor), GetRValue(clrBackColor)};
579         m_pImgMergeWindow->SetBackColor(backColor);
580         m_pImgMergeWindow->SetDiffBlockSize(GetOptionsMgr()->GetInt(OPT_CMP_IMG_DIFFBLOCKSIZE));
581         m_pImgMergeWindow->SetDiffColorAlpha(GetOptionsMgr()->GetInt(OPT_CMP_IMG_DIFFCOLORALPHA) / 100.0);
582         m_pImgMergeWindow->SetColorDistanceThreshold(GetOptionsMgr()->GetInt(OPT_CMP_IMG_THRESHOLD) / 1000.0);
583 }
584
585 void CImgMergeFrame::SaveOptions()
586 {
587         GetOptionsMgr()->SaveOption(OPT_CMP_IMG_SHOWDIFFERENCES, m_pImgMergeWindow->GetShowDifferences());
588         GetOptionsMgr()->SaveOption(OPT_CMP_IMG_OVERLAYMOVE, m_pImgMergeWindow->GetOverlayMode());
589         GetOptionsMgr()->SaveOption(OPT_CMP_IMG_OVERLAYALPHA, static_cast<int>(m_pImgMergeWindow->GetOverlayAlpha() * 100));
590         GetOptionsMgr()->SaveOption(OPT_CMP_IMG_DRAGGING_MODE, static_cast<int>(m_pImgMergeWindow->GetDraggingMode()));
591         GetOptionsMgr()->SaveOption(OPT_CMP_IMG_ZOOM, static_cast<int>(m_pImgMergeWindow->GetZoom() * 1000));
592         GetOptionsMgr()->SaveOption(OPT_CMP_IMG_USEBACKCOLOR, m_pImgMergeWindow->GetUseBackColor());
593         RGBQUAD backColor = m_pImgMergeWindow->GetBackColor();
594         GetOptionsMgr()->SaveOption(OPT_CMP_IMG_BACKCOLOR, static_cast<int>(RGB(backColor.rgbRed, backColor.rgbGreen, backColor.rgbBlue)));
595         GetOptionsMgr()->SaveOption(OPT_CMP_IMG_DIFFBLOCKSIZE, m_pImgMergeWindow->GetDiffBlockSize());
596         GetOptionsMgr()->SaveOption(OPT_CMP_IMG_DIFFCOLORALPHA, static_cast<int>(m_pImgMergeWindow->GetDiffColorAlpha() * 100.0));
597         GetOptionsMgr()->SaveOption(OPT_CMP_IMG_THRESHOLD, static_cast<int>(m_pImgMergeWindow->GetColorDistanceThreshold() * 1000));
598 }
599 /**
600  * @brief Save coordinates of the frame, splitters, and bars
601  *
602  * @note Do not save the maximized/restored state here. We are interested
603  * in the state of the active frame, and maybe this frame is not active
604  */
605 void CImgMergeFrame::SavePosition()
606 {
607         CRect rc;
608         GetWindowRect(&rc);
609         theApp.WriteProfileInt(_T("Settings"), _T("ActivePane"), m_pImgMergeWindow->GetActivePane());
610
611         // save the bars layout
612         // save docking positions and sizes
613         CDockState m_pDockState;
614         GetDockState(m_pDockState);
615         m_pDockState.SaveState(_T("Settings-ImgMergeFrame"));
616         // for the dimensions of the diff pane, use the CSizingControlBar save
617         m_wndLocationBar.SaveState(_T("Settings-ImgMergeFrame"));
618 }
619
620 void CImgMergeFrame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
621 {
622         CMDIChildWnd::OnMDIActivate(bActivate, pActivateWnd, pDeactivateWnd);
623         if (bActivate)
624         {
625                 GetMainFrame()->PostMessage(WM_USER + 1);
626
627                 CDockState pDockState;
628                 pDockState.LoadState(_T("Settings-ImgMergeFrame"));
629                 if (EnsureValidDockState(pDockState)) // checks for valid so won't ASSERT
630                         SetDockState(pDockState);
631                 // for the dimensions of the diff and location pane, use the CSizingControlBar loader
632                 m_wndLocationBar.LoadState(_T("Settings-ImgMergeFrame"));
633         }
634 }
635
636 void CImgMergeFrame::OnClose() 
637 {
638         // Allow user to cancel closing
639         if (!PromptAndSaveIfNeeded(true))
640                 return;
641
642         // clean up pointers.
643         CMDIChildWnd::OnClose();
644
645         GetMainFrame()->ClearStatusbarItemCount();
646 }
647
648 bool CImgMergeFrame::DoFileSave(int pane)
649 {
650         if (m_pImgMergeWindow->IsModified(pane))
651         {
652                 if (m_nBufferType[pane] == BUFFER_UNNAMED)
653                         DoFileSaveAs(pane);
654                 else
655                 {
656                         String filename = ucr::toTString(m_pImgMergeWindow->GetFileName(pane));
657                         BOOL bApplyToAll = FALSE;
658                         if (theApp.HandleReadonlySave(filename, FALSE, bApplyToAll) == IDCANCEL)
659                                 return false;
660                         theApp.CreateBackup(false, filename);
661                         if (!m_pImgMergeWindow->SaveImage(pane))
662                         {
663                                 return false;
664                         }
665                 }
666                 UpdateDiffItem(m_pDirDoc);
667         }
668         return true;
669 }
670
671 bool CImgMergeFrame::DoFileSaveAs(int pane)
672 {
673         const String &path = m_filePaths.GetPath(pane);
674         String strPath;
675         String title;
676         if (pane == 0)
677                 title = _("Save Left File As");
678         else if (pane == m_pImgMergeWindow->GetPaneCount() - 1)
679                 title = _("Save Right File As");
680         else
681                 title = _("Save Middle File As");
682         if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), strPath, FALSE, path.c_str(), title))
683         {
684                 std::wstring filename = ucr::toUTF16(strPath).c_str();
685                 BOOL bApplyToAll = FALSE;
686                 if (m_pImgMergeWindow->SaveImageAs(pane, filename.c_str()))
687                         return false;
688                 if (path.empty())
689                 {
690                         // We are saving scratchpad (unnamed file)
691                         m_nBufferType[pane] = BUFFER_UNNAMED_SAVED;
692                         m_strDesc[pane].erase();
693                 }
694
695                 m_filePaths.SetPath(pane, strPath);
696                 UpdateDiffItem(m_pDirDoc);
697                 UpdateHeaderPath(pane);
698         }
699         return true;
700 }
701
702 /**
703  * @brief Saves both files
704  */
705 void CImgMergeFrame::OnFileSave() 
706 {
707         for (int pane = 0; pane < m_pImgMergeWindow->GetPaneCount(); ++pane)
708                 DoFileSave(pane);
709 }
710
711 /**
712  * @brief Called when "Save" item is updated
713  */
714 void CImgMergeFrame::OnUpdateFileSave(CCmdUI *pCmdUI)
715 {
716         pCmdUI->Enable(IsModified());
717 }
718
719 /**
720  * @brief Saves left-side file
721  */
722 void CImgMergeFrame::OnFileSaveLeft() 
723 {
724         DoFileSave(0);
725 }
726
727 /**
728  * @brief Called when "Save middle (...)" item is updated
729  */
730 void CImgMergeFrame::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
731 {
732         pCmdUI->Enable(m_pImgMergeWindow->GetPaneCount() == 3 ? true : false);
733 }
734
735 /**
736  * @brief Saves middle-side file
737  */
738 void CImgMergeFrame::OnFileSaveMiddle()
739 {
740         DoFileSave(1);
741 }
742
743 /**
744  * @brief Saves right-side file
745  */
746 void CImgMergeFrame::OnFileSaveRight()
747 {
748         DoFileSave(m_pImgMergeWindow->GetPaneCount() - 1);
749 }
750
751 /**
752  * @brief Called when "Save middle (as...)" item is updated
753  */
754 void CImgMergeFrame::OnUpdateFileSaveAsMiddle(CCmdUI* pCmdUI)
755 {
756         pCmdUI->Enable(m_pImgMergeWindow->GetPaneCount() == 3 ? true : false);
757 }
758
759 /**
760  * @brief Saves left-side file with name asked
761  */
762 void CImgMergeFrame::OnFileSaveAsLeft()
763 {
764         DoFileSaveAs(0);
765 }
766
767 /**
768  * @brief Saves middle-side file with name asked
769  */
770 void CImgMergeFrame::OnFileSaveAsMiddle()
771 {
772         DoFileSaveAs(1);
773 }
774
775 /**
776  * @brief Saves right-side file with name asked
777  */
778 void CImgMergeFrame::OnFileSaveAsRight()
779 {
780         DoFileSaveAs(m_pImgMergeWindow->GetPaneCount() - 1);
781 }
782
783 /**
784  * @brief Reloads the opened files
785  */
786 void CImgMergeFrame::OnFileReload()
787 {
788         if (!PromptAndSaveIfNeeded(true))
789                 return;
790         m_pImgMergeWindow->ReloadImages();
791         for (int pane = 0; pane < m_filePaths.GetSize(); ++pane)
792                 m_fileInfo[pane].Update(m_filePaths[pane]);
793 }
794
795 void CImgMergeFrame::OnFileClose() 
796 {
797         OnClose();
798 }
799
800 /**
801  * @brief Enable/disable left buffer read-only
802  */
803 void CImgMergeFrame::OnLeftReadOnly()
804 {
805         m_bRO[0] = !m_bRO[0];
806         m_pImgMergeWindow->SetReadOnly(0, m_bRO[0]);
807 }
808
809 /**
810  * @brief Called when "Left read-only" item is updated
811  */
812 void CImgMergeFrame::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
813 {
814         pCmdUI->Enable(true);
815         pCmdUI->SetCheck(m_bRO[0]);
816 }
817
818 /**
819  * @brief Enable/disable middle buffer read-only
820  */
821 void CImgMergeFrame::OnMiddleReadOnly()
822 {
823         if (m_pImgMergeWindow->GetPaneCount() == 3)
824         {
825                 m_bRO[1] = !m_bRO[1];
826                 m_pImgMergeWindow->SetReadOnly(1, m_bRO[1]);
827         }
828 }
829
830 /**
831  * @brief Called when "Middle read-only" item is updated
832  */
833 void CImgMergeFrame::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
834 {
835         if (m_pImgMergeWindow->GetPaneCount() < 3)
836         {
837                 pCmdUI->Enable(false);
838         }
839         else
840         {
841                 pCmdUI->Enable(true);
842                 pCmdUI->SetCheck(m_bRO[1]);
843         }
844 }
845
846 /**
847  * @brief Enable/disable right buffer read-only
848  */
849 void CImgMergeFrame::OnRightReadOnly()
850 {
851         int pane = m_pImgMergeWindow->GetPaneCount() - 1;
852         m_bRO[pane] = !m_bRO[pane];
853         m_pImgMergeWindow->SetReadOnly(pane, m_bRO[pane]);
854 }
855
856 /**
857  * @brief Called when "Right read-only" item is updated
858  */
859 void CImgMergeFrame::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
860 {
861         pCmdUI->Enable(true);
862         pCmdUI->SetCheck(m_pImgMergeWindow->GetReadOnly(m_pImgMergeWindow->GetPaneCount() - 1));
863 }
864
865 void CImgMergeFrame::OnFileRecompareAsBinary()
866 {
867         DWORD dwFlags[3] = { 0 };
868         FileLocation fileloc[3];
869         for (int pane = 0; pane < m_filePaths.GetSize(); pane++)
870         {
871                 fileloc[pane].setPath(m_filePaths[pane]);
872                 dwFlags[pane] |= FFILEOPEN_NOMRU | (m_bRO[pane] ? FFILEOPEN_READONLY : 0);
873         }
874         GetMainFrame()->ShowHexMergeDoc(m_pDirDoc, m_filePaths.GetSize(), fileloc, dwFlags, m_strDesc);
875         ShowWindow(SW_RESTORE);
876         DestroyWindow();
877 }
878
879 void  CImgMergeFrame::OnWindowChangePane() 
880 {
881         m_pImgMergeWindow->SetActivePane((m_pImgMergeWindow->GetActivePane() + 1) % m_pImgMergeWindow->GetPaneCount());
882 }
883
884 /**
885  * @brief Write path and filename to headerbar
886  * @note SetText() does not repaint unchanged text
887  */
888 void CImgMergeFrame::UpdateHeaderPath(int pane)
889 {
890         String sText;
891
892         if (m_nBufferType[pane] == BUFFER_UNNAMED ||
893                 m_nBufferType[pane] == BUFFER_NORMAL_NAMED)
894         {
895                 sText = m_strDesc[pane];
896         }
897         else
898         {
899                 sText = m_filePaths.GetPath(pane);
900                 if (m_pDirDoc)
901                         m_pDirDoc->ApplyDisplayRoot(pane, sText);
902         }
903         if (m_pImgMergeWindow->IsModified(pane))
904                 sText.insert(0, _T("* "));
905
906         m_wndFilePathBar.SetText(pane, sText.c_str());
907
908         SetTitle(NULL);
909 }
910
911 /// update splitting position for panels 1/2 and headerbar and statusbar 
912 void CImgMergeFrame::UpdateHeaderSizes()
913 {
914         if (IsWindowVisible() && m_pImgMergeWindow)
915         {
916                 int w[3];
917                 CRect rc, rcMergeWindow;
918                 int nPaneCount = m_pImgMergeWindow->GetPaneCount();
919                 GetClientRect(&rc);
920                 ::GetWindowRect(m_pImgMergeWindow->GetHWND(), &rcMergeWindow);
921                 ScreenToClient(rcMergeWindow);
922                 if (!m_pImgMergeWindow->GetHorizontalSplit())
923                 {
924                         for (int pane = 0; pane < nPaneCount; pane++)
925                         {
926                                 RECT rc1 = m_pImgMergeWindow->GetPaneWindowRect(pane);
927                                 w[pane] = rc1.right - rc1.left - 4;
928                                 if (w[pane]<1) w[pane]=1; // Perry 2003-01-22 (I don't know why this happens)
929                         }
930                 }
931                 else
932                 {
933                         for (int pane = 0; pane < nPaneCount; pane++)
934                                 w[pane] = rc.Width() / nPaneCount - 4;
935                 }
936
937                 if (!std::equal(m_nLastSplitPos, m_nLastSplitPos + nPaneCount - 1, w))
938                 {
939                         std::copy_n(w, nPaneCount - 1, m_nLastSplitPos);
940
941                         // resize controls in header dialog bar
942                         m_wndFilePathBar.Resize(w);
943
944                         rc.left = rcMergeWindow.left;
945                         rc.top = rc.bottom - m_rectBorder.bottom;
946                         rc.right = rc.left;
947                         for (int pane = 0; pane < nPaneCount; pane++)
948                         {
949                                 rc.right += w[pane] + 4 + 2;
950                                 m_wndStatusBar[pane].MoveWindow(&rc);
951                                 rc.left = rc.right;
952                         }
953                 }
954         }
955 }
956
957 /**
958  * @brief Update document filenames to title
959  */
960 void CImgMergeFrame::SetTitle(LPCTSTR lpszTitle)
961 {
962         String sTitle;
963         String sFileName[3];
964
965         if (lpszTitle)
966                 sTitle = lpszTitle;
967         else
968         {
969                 for (int nBuffer = 0; nBuffer < m_filePaths.GetSize(); nBuffer++)
970                 {
971                         if (!m_strDesc[nBuffer].empty())
972                                 sFileName[nBuffer] = m_strDesc[nBuffer];
973                         else
974                         {
975                                 String file;
976                                 String ext;
977                                 paths::SplitFilename(m_filePaths[nBuffer], NULL, &file, &ext);
978                                 sFileName[nBuffer] += file;
979                                 if (!ext.empty())
980                                 {
981                                         sFileName[nBuffer] += _T(".");
982                                         sFileName[nBuffer] += ext;
983                                 }
984                         }
985                 }
986                 const int nBuffers = m_filePaths.GetSize();
987                 if (std::count(&sFileName[0], &sFileName[0] + nBuffers, sFileName[0]) == nBuffers)
988                         sTitle = sFileName[0] + strutils::format(_T(" x %d"), nBuffers);
989                 else
990                         sTitle = strutils::join(&sFileName[0], &sFileName[0] + nBuffers, _T(" - "));
991         }
992         CMDIChildWnd::SetTitle(sTitle.c_str());
993         if (m_hWnd)
994                 SetWindowText(sTitle.c_str());
995 }
996
997 /**
998  * @brief Reflect comparison result in window's icon.
999  * @param nResult [in] Last comparison result which the application returns.
1000  */
1001 void CImgMergeFrame::SetLastCompareResult(int nResult)
1002 {
1003         HICON hCurrent = GetIcon(FALSE);
1004         HICON hReplace = (nResult == 0) ? m_hIdentical : m_hDifferent;
1005
1006         if (hCurrent != hReplace)
1007         {
1008                 SetIcon(hReplace, TRUE);
1009
1010                 BOOL bMaximized;
1011                 GetMDIFrame()->MDIGetActive(&bMaximized);
1012
1013                 // When MDI maximized the window icon is drawn on the menu bar, so we
1014                 // need to notify it that our icon has changed.
1015                 if (bMaximized)
1016                 {
1017                         GetMDIFrame()->DrawMenuBar();
1018                 }
1019                 GetMDIFrame()->OnUpdateFrameTitle(FALSE);
1020         }
1021
1022         theApp.SetLastCompareResult(nResult);
1023 }
1024
1025 void CImgMergeFrame::UpdateLastCompareResult()
1026 {
1027         SetLastCompareResult(m_pImgMergeWindow->GetDiffCount() > 0 ? 1 : 0);
1028 }
1029
1030 void CImgMergeFrame::UpdateAutoPaneResize()
1031 {
1032 }
1033
1034 void CImgMergeFrame::UpdateSplitter()
1035 {
1036 }
1037
1038 /**
1039  * @brief Update associated diff item
1040  */
1041 int CImgMergeFrame::UpdateDiffItem(CDirDoc *pDirDoc)
1042 {
1043         // If directory compare has results
1044         if (pDirDoc && pDirDoc->HasDiffs())
1045         {
1046                 const String &pathLeft = m_filePaths.GetLeft();
1047                 const String &pathRight = m_filePaths.GetRight();
1048                 CDiffContext &ctxt = const_cast<CDiffContext &>(pDirDoc->GetDiffContext());
1049 // FIXME:
1050 //              if (UINT_PTR pos = pDirDoc->FindItemFromPaths(pathLeft, pathRight))
1051 //              {
1052 //                      DIFFITEM &di = pDirDoc->GetDiffRefByKey(pos);
1053 //                      ::UpdateDiffItem(m_nBuffers, di, &ctxt);
1054 //              }
1055         }
1056         int result = m_pImgMergeWindow->GetDiffCount() > 0 ? 1 : 0;
1057         SetLastCompareResult(result != 0);
1058         return result;
1059 }
1060
1061 /**
1062  * @brief Asks and then saves modified files.
1063  *
1064  * This function saves modified files. Dialog is shown for user to select
1065  * modified file(s) one wants to save or discard changed. Cancelling of
1066  * save operation is allowed unless denied by parameter. After successfully
1067  * save operation file statuses are updated to directory compare.
1068  * @param [in] bAllowCancel If false "Cancel" button is disabled.
1069  * @return true if user selected "OK" so next operation can be
1070  * executed. If false user choosed "Cancel".
1071  * @note If filename is empty, we assume scratchpads are saved,
1072  * so instead of filename, description is shown.
1073  * @todo If we have filename and description for file, what should
1074  * we do after saving to different filename? Empty description?
1075  * @todo Parameter @p bAllowCancel is always true in callers - can be removed.
1076  */
1077 bool CImgMergeFrame::PromptAndSaveIfNeeded(bool bAllowCancel)
1078 {
1079         bool bLModified = false, bMModified = false, bRModified = false;
1080         bool result = true;
1081         bool bLSaveSuccess = false, bMSaveSuccess = false, bRSaveSuccess = false;
1082
1083         if (m_pImgMergeWindow->GetPaneCount() == 3)
1084         {
1085                 bLModified = m_pImgMergeWindow->IsModified(0);
1086                 bMModified = m_pImgMergeWindow->IsModified(1);
1087                 bRModified = m_pImgMergeWindow->IsModified(2);
1088         }
1089         else
1090         {
1091                 bLModified = m_pImgMergeWindow->IsModified(0);
1092                 bRModified = m_pImgMergeWindow->IsModified(1);
1093         }
1094         if (!bLModified && !bMModified && !bRModified)
1095                  return true;
1096
1097         SaveClosingDlg dlg;
1098         dlg.DoAskFor(bLModified, bMModified, bRModified);
1099         if (!bAllowCancel)
1100                 dlg.m_bDisableCancel = true;
1101         if (!m_filePaths.GetLeft().empty())
1102         {
1103                 if (theApp.m_strSaveAsPath.empty())
1104                         dlg.m_sLeftFile = m_filePaths.GetLeft();
1105                 else
1106                         dlg.m_sLeftFile = theApp.m_strSaveAsPath;
1107         }
1108         else
1109                 dlg.m_sLeftFile = m_strDesc[0];
1110         if (m_pImgMergeWindow->GetPaneCount() == 3)
1111         {
1112                 if (!m_filePaths.GetMiddle().empty())
1113                 {
1114                         if (theApp.m_strSaveAsPath.empty())
1115                                 dlg.m_sMiddleFile = m_filePaths.GetMiddle();
1116                         else
1117                                 dlg.m_sMiddleFile = theApp.m_strSaveAsPath;
1118                 }
1119                 else
1120                         dlg.m_sMiddleFile = m_strDesc[1];
1121         }
1122         if (!m_filePaths.GetRight().empty())
1123         {
1124                 if (theApp.m_strSaveAsPath.empty())
1125                         dlg.m_sRightFile = m_filePaths.GetRight();
1126                 else
1127                         dlg.m_sRightFile = theApp.m_strSaveAsPath;
1128         }
1129         else
1130                 dlg.m_sRightFile = m_strDesc[m_pImgMergeWindow->GetPaneCount() - 1];
1131
1132         if (dlg.DoModal() == IDOK)
1133         {
1134                 if (bLModified && dlg.m_leftSave == SaveClosingDlg::SAVECLOSING_SAVE)
1135                 {
1136                         bLSaveSuccess = DoFileSave(0);
1137                         if (!bLSaveSuccess)
1138                                 result = false;
1139                 }
1140
1141                 if (bMModified && dlg.m_middleSave == SaveClosingDlg::SAVECLOSING_SAVE)
1142                 {
1143                         bMSaveSuccess = DoFileSave(1);
1144                         if (!bMSaveSuccess)
1145                                 result = false;
1146                 }
1147
1148                 if (bRModified && dlg.m_rightSave == SaveClosingDlg::SAVECLOSING_SAVE)
1149                 {
1150                         bRSaveSuccess = DoFileSave(m_pImgMergeWindow->GetPaneCount() - 1);
1151                         if (!bRSaveSuccess)
1152                                 result = false;
1153                 }
1154         }
1155         else
1156         {       
1157                 result = false;
1158         }
1159
1160         // If file were modified and saving was successfull,
1161         // update status on dir view
1162         if ((bLModified && bLSaveSuccess) || 
1163              (bMModified && bMSaveSuccess) ||
1164                  (bRModified && bRSaveSuccess))
1165         {
1166                 // If directory compare has results
1167                 if (m_pDirDoc && m_pDirDoc->HasDiffs())
1168                 {
1169                         // FIXME:
1170                 }
1171         }
1172
1173         return result;
1174 }
1175
1176 /// Document commanding us to close
1177 bool CImgMergeFrame::CloseNow()
1178 {
1179         // Allow user to cancel closing
1180         if (!PromptAndSaveIfNeeded(true))
1181                 return false;
1182
1183         SavePosition(); // Save settings before closing!
1184         SaveOptions();
1185         MDIActivate();
1186         MDIDestroy();
1187         return true;
1188 }
1189
1190 /**
1191  * @brief Update any resources necessary after a GUI language change
1192  */
1193 void CImgMergeFrame::UpdateResources()
1194 {
1195 }
1196
1197 /**
1198  * @brief Handle some keys when in merging mode
1199  */
1200 bool CImgMergeFrame::MergeModeKeyDown(MSG* pMsg)
1201 {
1202         bool bHandled = false;
1203
1204         // Allow default text selection when SHIFT pressed
1205         if (::GetAsyncKeyState(VK_SHIFT))
1206                 return false;
1207
1208         // Allow default editor functions when CTRL pressed
1209         if (::GetAsyncKeyState(VK_CONTROL))
1210                 return false;
1211
1212         // If we are in merging mode (merge with cursor keys)
1213         // handle some keys here
1214         switch (pMsg->wParam)
1215         {
1216         case VK_LEFT:
1217                 OnR2l();
1218                 bHandled = true;
1219                 break;
1220
1221         case VK_RIGHT:
1222                 OnL2r();
1223                 bHandled = true;
1224                 break;
1225
1226         case VK_UP:
1227                 OnPrevdiff();
1228                 bHandled = true;
1229                 break;
1230         case VK_DOWN:
1231                 OnNextdiff();
1232                 bHandled = true;
1233                 break;
1234         }
1235
1236         return bHandled;
1237 }
1238 /**
1239  * @brief Check for keyboard commands
1240  */
1241 BOOL CImgMergeFrame::PreTranslateMessage(MSG* pMsg)
1242 {
1243         if (pMsg->message == WM_KEYDOWN)
1244         {
1245                 // If we are in merging mode (merge with cursor keys)
1246                 // handle some keys here
1247                 if (theApp.GetMergingMode())
1248                 {
1249                         bool bHandled = MergeModeKeyDown(pMsg);
1250                         if (bHandled)
1251                                 return true;
1252                 }
1253
1254                 // Close window in response to VK_ESCAPE if user has allowed it from options
1255                 if (pMsg->wParam == VK_ESCAPE && GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_ESC))
1256                 {
1257                         PostMessage(WM_CLOSE, 0, 0);
1258                         return true;
1259                 }
1260         }
1261         return CMDIChildWnd::PreTranslateMessage(pMsg);
1262 }
1263
1264 void CImgMergeFrame::OnSize(UINT nType, int cx, int cy) 
1265 {
1266         CMDIChildWnd::OnSize(nType, cx, cy);
1267         UpdateHeaderSizes();
1268 }
1269
1270 void CImgMergeFrame::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
1271 {
1272         CMDIChildWnd::OnGetMinMaxInfo(lpMMI);
1273         // [Fix for MFC 8.0 MDI Maximizing Child Window bug on Vista]
1274         // https://groups.google.com/forum/#!topic/microsoft.public.vc.mfc/iajCdW5DzTM
1275 #if _MFC_VER >= 0x0800
1276         lpMMI->ptMaxTrackSize.x = max(lpMMI->ptMaxTrackSize.x, lpMMI->ptMaxSize.x);
1277         lpMMI->ptMaxTrackSize.y = max(lpMMI->ptMaxTrackSize.y, lpMMI->ptMaxSize.y);
1278 #endif
1279 }
1280
1281 /**
1282  * @brief Synchronize control and status bar placements with splitter position,
1283  * update mod indicators, synchronize scrollbars
1284  */
1285 void CImgMergeFrame::OnIdleUpdateCmdUI()
1286 {
1287         if (IsWindowVisible())
1288         {
1289                 POINT pt = {-1, -1}, ptCursor;
1290                 GetCursorPos(&ptCursor);
1291                 for (int pane = 0; pane < m_pImgMergeWindow->GetPaneCount(); ++pane)
1292                 {
1293                         RECT rc;
1294                         ::GetWindowRect(m_pImgMergeWindow->GetPaneHWND(pane), &rc);
1295                         if (PtInRect(&rc, ptCursor))
1296                                 pt = m_pImgMergeWindow->GetCursorPos(pane);
1297                 }
1298                 
1299                 RGBQUAD color[3];
1300                 for (int pane = 0; pane < m_pImgMergeWindow->GetPaneCount(); ++pane)
1301                         color[pane] = m_pImgMergeWindow->GetPixelColor(pane, pt.x, pt.y);
1302                 double colorDistance01 = m_pImgMergeWindow->GetColorDistance(0, 1, pt.x, pt.y);
1303                 double colorDistance12 = 0;
1304                 if (m_pImgMergeWindow->GetPaneCount() == 3)
1305                         colorDistance12 = m_pImgMergeWindow->GetColorDistance(1, 2, pt.x, pt.y);
1306
1307                 UpdateHeaderSizes();
1308                 for (int pane = 0; pane < m_filePaths.GetSize(); ++pane)
1309                 {
1310                         // Update mod indicators
1311                         String ind = m_wndFilePathBar.GetText(pane);
1312                         if (m_pImgMergeWindow->IsModified(pane) ? ind[0] != _T('*') : ind[0] == _T('*'))
1313                                 UpdateHeaderPath(pane);
1314
1315                         m_wndFilePathBar.SetActive(pane, pane == m_pImgMergeWindow->GetActivePane());
1316                         String text;
1317                         if (pt.x >= 0 && pt.y >= 0 &&
1318                                 pt.x < m_pImgMergeWindow->GetImageWidth(pane) &&
1319                                 pt.y < m_pImgMergeWindow->GetImageHeight(pane))
1320                         {
1321                                 POINT ptOffset = m_pImgMergeWindow->GetImageOffset(pane);
1322                                 text += strutils::format(_T("Pt:(%d,%d) RGBA:(%d,%d,%d,%d) "), pt.x - ptOffset.x, pt.y - ptOffset.y,
1323                                         color[pane].rgbRed, color[pane].rgbGreen, color[pane].rgbBlue, color[pane].rgbReserved);
1324                                 if (pane == 1 && m_pImgMergeWindow->GetPaneCount() == 3)
1325                                         text += strutils::format(_T("Dist:%g,%g "), colorDistance01, colorDistance12);
1326                                 else
1327                                         text += strutils::format(_T("Dist:%g "), colorDistance01);
1328                         }
1329
1330                         text += strutils::format(_T("Page:%d/%d Zoom:%d%% %dx%dpx %dbpp"), 
1331                                         m_pImgMergeWindow->GetCurrentPage(pane) + 1,
1332                                         m_pImgMergeWindow->GetPageCount(pane),
1333                                         static_cast<int>(m_pImgMergeWindow->GetZoom() * 100),
1334                                         m_pImgMergeWindow->GetImageWidth(pane),
1335                                         m_pImgMergeWindow->GetImageHeight(pane),
1336                                         m_pImgMergeWindow->GetImageBitsPerPixel(pane)
1337                                         );
1338                         m_wndStatusBar[pane].SetPaneText(0, text.c_str());
1339                 }
1340         }
1341         CMDIChildWnd::OnIdleUpdateCmdUI();
1342 }
1343
1344 /**
1345  * @brief Save pane sizes and positions when one of panes requests it.
1346  */
1347 LRESULT CImgMergeFrame::OnStorePaneSizes(WPARAM wParam, LPARAM lParam)
1348 {
1349         SavePosition();
1350         return 0;
1351 }
1352
1353 void CImgMergeFrame::OnUpdateStatusNum(CCmdUI* pCmdUI) 
1354 {
1355         TCHAR sIdx[32] = { 0 };
1356         TCHAR sCnt[32] = { 0 };
1357         String s;
1358         const int nDiffs = m_pImgMergeWindow->GetDiffCount();
1359         
1360         // Files are identical - show text "Identical"
1361         if (nDiffs <= 0)
1362                 s = theApp.LoadString(IDS_IDENTICAL);
1363         
1364         // There are differences, but no selected diff
1365         // - show amount of diffs
1366         else if (m_pImgMergeWindow->GetCurrentDiffIndex() < 0)
1367         {
1368                 s = theApp.LoadString(nDiffs == 1 ? IDS_1_DIFF_FOUND : IDS_NO_DIFF_SEL_FMT);
1369                 _itot_s(nDiffs, sCnt, 10);
1370                 strutils::replace(s, _T("%1"), sCnt);
1371         }
1372         
1373         // There are differences and diff selected
1374         // - show diff number and amount of diffs
1375         else
1376         {
1377                 s = theApp.LoadString(IDS_DIFF_NUMBER_STATUS_FMT);
1378                 const int signInd = m_pImgMergeWindow->GetCurrentDiffIndex();
1379                 _itot_s(signInd + 1, sIdx, 10);
1380                 strutils::replace(s, _T("%1"), sIdx);
1381                 _itot_s(nDiffs, sCnt, 10);
1382                 strutils::replace(s, _T("%2"), sCnt);
1383         }
1384         pCmdUI->SetText(s.c_str());
1385 }
1386         
1387 /**
1388  * @brief Undo last action
1389  */
1390 void CImgMergeFrame::OnEditUndo()
1391 {
1392         m_pImgMergeWindow->Undo();
1393         if (!m_pImgMergeWindow->IsUndoable())
1394                 m_bAutoMerged = false;
1395         UpdateLastCompareResult();
1396 }
1397
1398 /**
1399  * @brief Called when "Undo" item is updated
1400  */
1401 void CImgMergeFrame::OnUpdateEditUndo(CCmdUI* pCmdUI)
1402 {
1403         pCmdUI->Enable(m_pImgMergeWindow->IsUndoable());
1404 }
1405
1406 /**
1407  * @brief Redo last action
1408  */
1409 void CImgMergeFrame::OnEditRedo()
1410 {
1411         m_pImgMergeWindow->Redo();
1412         UpdateLastCompareResult();
1413 }
1414
1415 /**
1416  * @brief Called when "Redo" item is updated
1417  */
1418 void CImgMergeFrame::OnUpdateEditRedo(CCmdUI* pCmdUI)
1419 {
1420         pCmdUI->Enable(m_pImgMergeWindow->IsRedoable());
1421 }
1422
1423 /**
1424  * @brief Called when user selects View/Zoom In from menu.
1425  */
1426 void CImgMergeFrame::OnViewZoomIn()
1427 {
1428         m_pImgMergeWindow->SetZoom(m_pImgMergeWindow->GetZoom() + 0.1);
1429 }
1430
1431 /**
1432  * @brief Called when user selects View/Zoom Out from menu.
1433  */
1434 void CImgMergeFrame::OnViewZoomOut()
1435 {
1436         m_pImgMergeWindow->SetZoom(m_pImgMergeWindow->GetZoom() - 0.1);
1437 }
1438
1439 /**
1440  * @brief Called when user selects View/Zoom Normal from menu.
1441  */
1442 void CImgMergeFrame::OnViewZoomNormal()
1443 {
1444         m_pImgMergeWindow->SetZoom(1.0);
1445 }
1446
1447 /**
1448  * @brief Split panes vertically
1449  */
1450 void CImgMergeFrame::OnViewSplitVertically() 
1451 {
1452         bool bSplitVertically = !m_pImgMergeWindow->GetHorizontalSplit();
1453         bSplitVertically = !bSplitVertically; // toggle
1454         GetOptionsMgr()->SaveOption(OPT_SPLIT_HORIZONTALLY, !bSplitVertically);
1455         m_pImgMergeWindow->SetHorizontalSplit(!bSplitVertically);
1456 }
1457
1458 /**
1459  * @brief Update "Split Vertically" UI items
1460  */
1461 void CImgMergeFrame::OnUpdateViewSplitVertically(CCmdUI* pCmdUI) 
1462 {
1463         pCmdUI->Enable(TRUE);
1464         pCmdUI->SetCheck(!m_pImgMergeWindow->GetHorizontalSplit());
1465 }
1466
1467 /**
1468  * @brief Go to first diff
1469  *
1470  * Called when user selects "First Difference"
1471  * @sa CImgMergeFrame::SelectDiff()
1472  */
1473 void CImgMergeFrame::OnFirstdiff()
1474 {
1475         m_pImgMergeWindow->FirstDiff();
1476 }
1477
1478 /**
1479  * @brief Update "First diff" UI items
1480  */
1481 void CImgMergeFrame::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1482 {
1483         OnUpdatePrevdiff(pCmdUI);
1484 }
1485
1486 /**
1487  * @brief Go to last diff
1488  */
1489 void CImgMergeFrame::OnLastdiff()
1490 {
1491         m_pImgMergeWindow->LastDiff();
1492 }
1493
1494 /**
1495  * @brief Update "Last diff" UI items
1496  */
1497 void CImgMergeFrame::OnUpdateLastdiff(CCmdUI* pCmdUI)
1498 {
1499         OnUpdateNextdiff(pCmdUI);
1500 }
1501
1502 /**
1503  * @brief Go to next diff and select it.
1504  */
1505 void CImgMergeFrame::OnNextdiff()
1506 {
1507         if (m_pImgMergeWindow->GetCurrentDiffIndex() != m_pImgMergeWindow->GetDiffCount() - 1)
1508                 m_pImgMergeWindow->NextDiff();
1509         else if (m_pImgMergeWindow->GetCurrentMaxPage() != m_pImgMergeWindow->GetMaxPageCount() - 1)
1510         {
1511                 if (AfxMessageBox(_("Do you want to move to the next page?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN) == IDYES)
1512                 {
1513                         m_pImgMergeWindow->SetCurrentPageAll(m_pImgMergeWindow->GetCurrentMaxPage() + 1);
1514                         UpdateLastCompareResult();
1515                 }
1516         }
1517         else if (m_pDirDoc)
1518                 m_pDirDoc->MoveToNextDiff(this);
1519 }
1520
1521 /**
1522  * @brief Update "Next diff" UI items
1523  */
1524 void CImgMergeFrame::OnUpdateNextdiff(CCmdUI* pCmdUI)
1525 {
1526         bool enabled =
1527                 m_pImgMergeWindow->GetCurrentMaxPage() < m_pImgMergeWindow->GetMaxPageCount() - 1 ||
1528                 m_pImgMergeWindow->GetNextDiffIndex() >= 0 ||
1529                 (m_pImgMergeWindow->GetDiffCount() > 0 && m_pImgMergeWindow->GetCurrentDiffIndex() == -1);
1530
1531         if (!enabled && m_pDirDoc)
1532                 enabled = m_pDirDoc->MoveableToNextDiff();
1533
1534         pCmdUI->Enable(enabled);
1535 }
1536
1537 /**
1538  * @brief Go to previous diff and select it.
1539  */
1540 void CImgMergeFrame::OnPrevdiff()
1541 {
1542         if (m_pImgMergeWindow->GetCurrentDiffIndex() > 0)
1543         {
1544                 m_pImgMergeWindow->PrevDiff();
1545         }
1546         else if (m_pImgMergeWindow->GetCurrentMaxPage() != 0)
1547         {
1548                 if (AfxMessageBox(_("Do you want to move to the previous page?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN) == IDYES)
1549                 {
1550                         m_pImgMergeWindow->SetCurrentPageAll(m_pImgMergeWindow->GetCurrentMaxPage() - 1);
1551                         UpdateLastCompareResult();
1552                 }
1553         }
1554         else if (m_pDirDoc)
1555                 m_pDirDoc->MoveToPrevDiff(this);
1556 }
1557
1558 /**
1559  * @brief Update "Previous diff" UI items
1560  */
1561 void CImgMergeFrame::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1562 {
1563         bool enabled =
1564                 m_pImgMergeWindow->GetCurrentMaxPage() > 0 ||
1565                 m_pImgMergeWindow->GetPrevDiffIndex() >= 0 ||
1566                 (m_pImgMergeWindow->GetDiffCount() > 0 && m_pImgMergeWindow->GetCurrentDiffIndex() == -1);
1567
1568         if (!enabled && m_pDirDoc)
1569                 enabled = m_pDirDoc->MoveableToPrevDiff();
1570
1571         pCmdUI->Enable(enabled);
1572 }
1573
1574 /**
1575  * @brief Go to next conflict and select it.
1576  */
1577 void CImgMergeFrame::OnNextConflict()
1578 {
1579         m_pImgMergeWindow->NextConflict();
1580 }
1581
1582 /**
1583  * @brief Update "Next Conflict" UI items
1584  */
1585 void CImgMergeFrame::OnUpdateNextConflict(CCmdUI* pCmdUI)
1586 {
1587         pCmdUI->Enable(
1588                 m_pImgMergeWindow->GetPaneCount() > 2 && (
1589                         m_pImgMergeWindow->GetNextConflictIndex() >= 0 ||
1590                         (m_pImgMergeWindow->GetConflictCount() > 0 && m_pImgMergeWindow->GetCurrentDiffIndex() == -1)
1591                 )
1592         );
1593 }
1594
1595 /**
1596  * @brief Go to previous diff and select it.
1597  */
1598 void CImgMergeFrame::OnPrevConflict()
1599 {
1600         m_pImgMergeWindow->PrevConflict();
1601 }
1602
1603 /**
1604  * @brief Update "Previous diff" UI items
1605  */
1606 void CImgMergeFrame::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1607 {
1608         pCmdUI->Enable(
1609                 m_pImgMergeWindow->GetPaneCount() > 2 && (
1610                         m_pImgMergeWindow->GetPrevConflictIndex() >= 0 ||
1611                         (m_pImgMergeWindow->GetConflictCount() > 0 && m_pImgMergeWindow->GetCurrentDiffIndex() == -1)
1612                 )
1613         );
1614 }
1615
1616 void CImgMergeFrame::OnUpdateX2Y(CCmdUI* pCmdUI, int srcPane, int dstPane)
1617 {
1618         pCmdUI->Enable(m_pImgMergeWindow->GetCurrentDiffIndex() >= 0 && 
1619                 srcPane >= 0 && srcPane <= m_pImgMergeWindow->GetPaneCount() &&
1620                 dstPane >= 0 && dstPane <= m_pImgMergeWindow->GetPaneCount() &&
1621                 !m_bRO[dstPane]
1622                 );
1623 }
1624
1625 void CImgMergeFrame::OnX2Y(int srcPane, int dstPane)
1626 {
1627         m_pImgMergeWindow->CopyDiff(m_pImgMergeWindow->GetCurrentDiffIndex(), srcPane, dstPane);
1628         UpdateLastCompareResult();
1629 }
1630
1631 /**
1632  * @brief Copy diff from left pane to right pane
1633  */
1634 void CImgMergeFrame::OnL2r()
1635 {
1636         int srcPane = m_pImgMergeWindow->GetActivePane();
1637         if (srcPane >= m_pImgMergeWindow->GetPaneCount() - 1)
1638                 srcPane = m_pImgMergeWindow->GetPaneCount() - 2;
1639         if (srcPane < 0)
1640                 srcPane = 0;
1641         int dstPane = srcPane + 1;
1642         OnX2Y(srcPane, dstPane);
1643 }
1644
1645 /**
1646  * @brief Called when "Copy to left" item is updated
1647  */
1648 void CImgMergeFrame::OnUpdateL2r(CCmdUI* pCmdUI)
1649 {
1650         int srcPane = m_pImgMergeWindow->GetActivePane();
1651         if (srcPane >= m_pImgMergeWindow->GetPaneCount() - 1)
1652                 srcPane = m_pImgMergeWindow->GetPaneCount() - 2;
1653         if (srcPane < 0)
1654                 srcPane = 0;
1655         int dstPane = srcPane + 1;
1656         OnUpdateX2Y(pCmdUI, srcPane, dstPane);
1657 }
1658
1659 /**
1660  * @brief Copy diff from right pane to left pane
1661  */
1662 void CImgMergeFrame::OnR2l()
1663 {
1664         int srcPane = m_pImgMergeWindow->GetActivePane();
1665         if (srcPane < 1)
1666                 srcPane = 1;
1667         int dstPane = srcPane - 1;
1668         OnX2Y(srcPane, dstPane);
1669 }
1670
1671 /**
1672  * @brief Called when "Copy to right" item is updated
1673  */
1674 void CImgMergeFrame::OnUpdateR2l(CCmdUI* pCmdUI)
1675 {
1676         int srcPane = m_pImgMergeWindow->GetActivePane();
1677         if (srcPane < 1)
1678                 srcPane = 1;
1679         int dstPane = srcPane - 1;
1680         OnUpdateX2Y(pCmdUI, srcPane, dstPane);
1681 }
1682
1683 void CImgMergeFrame::OnCopyFromLeft()
1684 {
1685         int srcPane = m_pImgMergeWindow->GetActivePane() - 1;
1686         if (srcPane < 0)
1687                 srcPane = 0;
1688         int dstPane = srcPane + 1;
1689         OnX2Y(srcPane, dstPane);
1690 }
1691
1692 /**
1693  * @brief Called when "Copy from left" item is updated
1694  */
1695 void CImgMergeFrame::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
1696 {
1697         int srcPane = m_pImgMergeWindow->GetActivePane() - 1;
1698         if (srcPane < 0)
1699                 srcPane = 0;
1700         int dstPane = srcPane + 1;
1701         OnUpdateX2Y(pCmdUI, srcPane, dstPane);
1702 }
1703
1704 void CImgMergeFrame::OnCopyFromRight()
1705 {
1706         int srcPane = m_pImgMergeWindow->GetActivePane() + 1;
1707         if (srcPane > m_pImgMergeWindow->GetPaneCount() - 1)
1708                 srcPane = m_pImgMergeWindow->GetPaneCount() - 1;
1709         int dstPane = srcPane - 1;
1710         OnX2Y(srcPane, dstPane);
1711 }
1712
1713 /**
1714  * @brief Called when "Copy from right" item is updated
1715  */
1716 void CImgMergeFrame::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
1717 {
1718         int srcPane = m_pImgMergeWindow->GetActivePane() + 1;
1719         if (srcPane > m_pImgMergeWindow->GetPaneCount() - 1)
1720                 srcPane = m_pImgMergeWindow->GetPaneCount() - 1;
1721         int dstPane = srcPane - 1;
1722         OnUpdateX2Y(pCmdUI, srcPane, dstPane);
1723 }
1724
1725 /**
1726  * @brief Copy all diffs from right pane to left pane
1727  */
1728 void CImgMergeFrame::OnAllLeft()
1729 {
1730         int srcPane = m_pImgMergeWindow->GetActivePane();
1731         if (srcPane < 1)
1732                 srcPane = 1;
1733         int dstPane = srcPane - 1;
1734
1735         CWaitCursor waitstatus;
1736
1737         m_pImgMergeWindow->CopyDiffAll(srcPane, dstPane);
1738         UpdateLastCompareResult();
1739 }
1740
1741 /**
1742  * @brief Called when "Copy all to left" item is updated
1743  */
1744 void CImgMergeFrame::OnUpdateAllLeft(CCmdUI* pCmdUI)
1745 {
1746         int srcPane = m_pImgMergeWindow->GetActivePane();
1747         if (srcPane < 1)
1748                 srcPane = 1;
1749         int dstPane = srcPane - 1;
1750
1751         pCmdUI->Enable(m_pImgMergeWindow->GetDiffCount() > 0 && !m_bRO[dstPane]);
1752 }
1753
1754 /**
1755  * @brief Copy all diffs from left pane to right pane
1756  */
1757 void CImgMergeFrame::OnAllRight()
1758 {
1759         int srcPane = m_pImgMergeWindow->GetActivePane();
1760         if (srcPane >= m_pImgMergeWindow->GetPaneCount() - 1)
1761                 srcPane = m_pImgMergeWindow->GetPaneCount() - 2;
1762         if (srcPane < 0)
1763                 srcPane = 0;
1764         int dstPane = srcPane + 1;
1765
1766         CWaitCursor waitstatus;
1767
1768         m_pImgMergeWindow->CopyDiffAll(srcPane, dstPane);
1769         UpdateLastCompareResult();
1770 }
1771
1772 /**
1773  * @brief Called when "Copy all to right" item is updated
1774  */
1775 void CImgMergeFrame::OnUpdateAllRight(CCmdUI* pCmdUI)
1776 {
1777         int srcPane = m_pImgMergeWindow->GetActivePane();
1778         if (srcPane >= m_pImgMergeWindow->GetPaneCount() - 1)
1779                 srcPane = m_pImgMergeWindow->GetPaneCount() - 2;
1780         if (srcPane < 0)
1781                 srcPane = 0;
1782         int dstPane = srcPane + 1;
1783
1784         pCmdUI->Enable(m_pImgMergeWindow->GetDiffCount() > 0 && !m_bRO[dstPane]);
1785 }
1786
1787 /**
1788  * @brief Do Auto merge
1789  */
1790 void CImgMergeFrame::OnAutoMerge()
1791 {
1792         int dstPane = m_pImgMergeWindow->GetActivePane();
1793         
1794         // Check current pane is not readonly
1795         if (dstPane < 0 || IsModified() || m_bAutoMerged || m_bRO[dstPane])
1796                 return;
1797
1798         CWaitCursor waitstatus;
1799
1800         DoAutoMerge(dstPane);
1801 }
1802
1803 /**
1804  * @brief Called when "Auto Merge" item is updated
1805  */
1806 void CImgMergeFrame::OnUpdateAutoMerge(CCmdUI* pCmdUI)
1807 {
1808         int dstPane = m_pImgMergeWindow->GetActivePane();
1809         
1810         pCmdUI->Enable(m_pImgMergeWindow->GetPaneCount() == 3 && 
1811                 dstPane >= 0 && !IsModified() && !m_bAutoMerged && !m_bRO[dstPane]);
1812 }
1813
1814 void CImgMergeFrame::OnImgViewDifferences()
1815 {
1816         m_pImgMergeWindow->SetShowDifferences(!m_pImgMergeWindow->GetShowDifferences());
1817         SaveOptions();
1818 }
1819
1820 void CImgMergeFrame::OnUpdateImgViewDifferences(CCmdUI* pCmdUI)
1821 {
1822         pCmdUI->SetCheck(m_pImgMergeWindow->GetShowDifferences() ? 1 : 0);
1823 }
1824
1825 void CImgMergeFrame::OnImgZoom(UINT nId)
1826 {
1827         m_pImgMergeWindow->SetZoom(pow(2.0, int(nId - ID_IMG_ZOOM_100)));
1828         SaveOptions();
1829 }
1830
1831 void CImgMergeFrame::OnUpdateImgZoom(CCmdUI* pCmdUI)
1832 {
1833         pCmdUI->SetRadio(pow(2.0, int(pCmdUI->m_nID - ID_IMG_ZOOM_100)) == m_pImgMergeWindow->GetZoom());
1834 }
1835
1836 void CImgMergeFrame::OnImgOverlayMode(UINT nId)
1837 {
1838         if (nId == ID_IMG_OVERLAY_NONE)
1839                 m_pImgMergeWindow->SetOverlayMode(IImgMergeWindow::OVERLAY_NONE);
1840         else if (nId == ID_IMG_OVERLAY_XOR)
1841                 m_pImgMergeWindow->SetOverlayMode(IImgMergeWindow::OVERLAY_XOR);
1842         else if (nId == ID_IMG_OVERLAY_ALPHABLEND)
1843                 m_pImgMergeWindow->SetOverlayMode(IImgMergeWindow::OVERLAY_ALPHABLEND);
1844         else if (nId == ID_IMG_OVERLAY_ALPHABLEND_ANIM)
1845                 m_pImgMergeWindow->SetOverlayMode(IImgMergeWindow::OVERLAY_ALPHABLEND_ANIM);
1846         SaveOptions();
1847 }
1848
1849 void CImgMergeFrame::OnUpdateImgOverlayMode(CCmdUI* pCmdUI)
1850 {
1851         pCmdUI->SetRadio(static_cast<IImgMergeWindow::OVERLAY_MODE>(pCmdUI->m_nID - ID_IMG_OVERLAY_NONE) == m_pImgMergeWindow->GetOverlayMode());
1852 }
1853
1854 void CImgMergeFrame::OnImgDraggingMode(UINT nId)
1855 {
1856         m_pImgMergeWindow->SetDraggingMode(static_cast<IImgMergeWindow::DRAGGING_MODE>(nId - ID_IMG_DRAGGINGMODE_NONE));
1857         SaveOptions();
1858 }
1859
1860 void CImgMergeFrame::OnUpdateImgDraggingMode(CCmdUI* pCmdUI)
1861 {
1862         pCmdUI->SetRadio(static_cast<IImgMergeWindow::DRAGGING_MODE>(pCmdUI->m_nID - ID_IMG_DRAGGINGMODE_NONE) == m_pImgMergeWindow->GetDraggingMode());
1863 }
1864
1865 void CImgMergeFrame::OnImgDiffBlockSize(UINT nId)
1866 {
1867         m_pImgMergeWindow->SetDiffBlockSize(1 << (nId - ID_IMG_DIFFBLOCKSIZE_1));
1868         SaveOptions();
1869 }
1870
1871 void CImgMergeFrame::OnUpdateImgDiffBlockSize(CCmdUI* pCmdUI)
1872 {
1873         pCmdUI->SetRadio(1 << (pCmdUI->m_nID - ID_IMG_DIFFBLOCKSIZE_1) == m_pImgMergeWindow->GetDiffBlockSize() );
1874 }
1875
1876 void CImgMergeFrame::OnImgThreshold(UINT nId)
1877 {
1878         if (nId == ID_IMG_THRESHOLD_0)
1879                 m_pImgMergeWindow->SetColorDistanceThreshold(0.0);
1880         else
1881                 m_pImgMergeWindow->SetColorDistanceThreshold((1 << (nId - ID_IMG_THRESHOLD_2)) * 2);
1882         SaveOptions();
1883 }
1884
1885 void CImgMergeFrame::OnUpdateImgThreshold(CCmdUI* pCmdUI)
1886 {
1887         if (pCmdUI->m_nID == ID_IMG_THRESHOLD_0)
1888                 pCmdUI->SetRadio(m_pImgMergeWindow->GetColorDistanceThreshold() == 0.0);
1889         else
1890                 pCmdUI->SetRadio((1 << (pCmdUI->m_nID - ID_IMG_THRESHOLD_2)) * 2 == m_pImgMergeWindow->GetColorDistanceThreshold() );
1891 }
1892
1893 void CImgMergeFrame::OnImgPrevPage()
1894 {
1895         m_pImgMergeWindow->SetCurrentPageAll(m_pImgMergeWindow->GetCurrentMaxPage() - 1);
1896         UpdateLastCompareResult();
1897 }
1898
1899 void CImgMergeFrame::OnUpdateImgPrevPage(CCmdUI* pCmdUI)
1900 {
1901         pCmdUI->Enable(m_pImgMergeWindow->GetCurrentMaxPage() > 0);
1902 }
1903
1904 void CImgMergeFrame::OnImgNextPage()
1905 {
1906         m_pImgMergeWindow->SetCurrentPageAll(m_pImgMergeWindow->GetCurrentMaxPage() + 1);
1907         UpdateLastCompareResult();
1908 }
1909
1910 void CImgMergeFrame::OnUpdateImgNextPage(CCmdUI* pCmdUI)
1911 {
1912         pCmdUI->Enable(
1913                 m_pImgMergeWindow->GetCurrentMaxPage() < m_pImgMergeWindow->GetMaxPageCount() - 1);
1914 }
1915
1916 void CImgMergeFrame::OnImgCurPanePrevPage()
1917 {
1918         m_pImgMergeWindow->SetCurrentPage(m_pImgMergeWindow->GetActivePane(), m_pImgMergeWindow->GetCurrentPage(m_pImgMergeWindow->GetActivePane()) - 1);
1919         UpdateLastCompareResult();
1920 }
1921
1922 void CImgMergeFrame::OnUpdateImgCurPanePrevPage(CCmdUI* pCmdUI)
1923 {
1924         pCmdUI->Enable(m_pImgMergeWindow->GetCurrentPage(m_pImgMergeWindow->GetActivePane()) > 0);
1925 }
1926
1927 void CImgMergeFrame::OnImgCurPaneNextPage()
1928 {
1929         m_pImgMergeWindow->SetCurrentPage(m_pImgMergeWindow->GetActivePane(), m_pImgMergeWindow->GetCurrentPage(m_pImgMergeWindow->GetActivePane()) + 1);
1930         UpdateLastCompareResult();
1931 }
1932
1933 void CImgMergeFrame::OnUpdateImgCurPaneNextPage(CCmdUI* pCmdUI)
1934 {
1935         pCmdUI->Enable(
1936                 m_pImgMergeWindow->GetCurrentPage(m_pImgMergeWindow->GetActivePane()) < 
1937                 m_pImgMergeWindow->GetPageCount(m_pImgMergeWindow->GetActivePane()) - 1);
1938 }
1939
1940 void CImgMergeFrame::OnImgUseBackColor()
1941 {
1942         bool bUseBackColor = !m_pImgMergeWindow->GetUseBackColor();
1943         if (bUseBackColor)
1944         {
1945                 RGBQUAD backColor = m_pImgMergeWindow->GetBackColor();
1946                 CColorDialog dialog(RGB(backColor.rgbRed, backColor.rgbGreen, backColor.rgbBlue));
1947                 static DWORD dwCustColors[16];
1948                 Options::CustomColors::Load(GetOptionsMgr(), dwCustColors);
1949                 dialog.m_cc.lpCustColors = dwCustColors;
1950                 if (dialog.DoModal() == IDOK)
1951                 {
1952                         COLORREF clrBackColor = dialog.GetColor();
1953                         RGBQUAD backColor1 = {GetBValue(clrBackColor), GetGValue(clrBackColor), GetRValue(clrBackColor)};
1954                         m_pImgMergeWindow->SetBackColor(backColor1);
1955                         m_pImgMergeWindow->SetUseBackColor(bUseBackColor);
1956                 }
1957         }
1958         else
1959         {
1960                 m_pImgMergeWindow->SetUseBackColor(bUseBackColor);
1961         }
1962         SaveOptions();
1963 }
1964
1965 void CImgMergeFrame::OnUpdateImgUseBackColor(CCmdUI* pCmdUI)
1966 {
1967         pCmdUI->SetCheck(m_pImgMergeWindow->GetUseBackColor() ? 1 : 0);
1968 }
1969
1970 /**
1971  * @brief Generate report from file compare results.
1972  */
1973 bool CImgMergeFrame::GenerateReport(const String& sFileName) const
1974 {
1975         String imgdir_full, imgdir, imgfilepath[3], diffimg_filename[3], path, name, ext;
1976         paths::SplitFilename(sFileName, &path, &name, &ext);
1977         imgdir_full = paths::ConcatPath(path, name) + _T(".files");
1978         imgdir = paths::FindFileName(imgdir_full);
1979         paths::CreateIfNeeded(imgdir_full);
1980         for (int i = 0; i < m_pImgMergeWindow->GetPaneCount(); ++i)
1981         {
1982                 imgfilepath[i] = ucr::toTString(m_pImgMergeWindow->GetFileName(i));
1983                 diffimg_filename[i] = strutils::format(_T("%s/%d.png"), imgdir.c_str(), i + 1);
1984                 m_pImgMergeWindow->SaveDiffImageAs(i, ucr::toUTF16(strutils::format(_T("%s\\%d.png"), imgdir_full.c_str(), i + 1)).c_str());
1985         }
1986
1987         UniStdioFile file;
1988         if (!file.Open(sFileName, _T("wt")))
1989         {
1990                 String errMsg = GetSysError(GetLastError());
1991                 String msg = strutils::format_string1(
1992                         _("Error creating the report:\n%1"), errMsg);
1993                 AfxMessageBox(msg.c_str(), MB_OK | MB_ICONSTOP);
1994                 return false;
1995         }
1996
1997         file.SetCodepage(ucr::CP_UTF_8);
1998
1999         file.WriteString(
2000                 _T("<!DOCTYPE html>\n")
2001                 _T("<html>\n")
2002                 _T("<head>\n")
2003                 _T("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n")
2004                 _T("<title>WinMerge Image Compare Report</title>\n")
2005                 _T("<style type=\"text/css\">\n")
2006                 _T("table { table-layout: fixed; width: 100%; height: 100%; border-collapse: collapse; }\n")
2007                 _T("td,th { border: solid 1px black; }\n")
2008                 _T(".border { border-radius: 6px; border: 1px #a0a0a0 solid; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.15); overflow: hidden; }\n")
2009                 _T(".title { color: white; background-color: blue; vertical-align: top; padding: 4px 4px; background: linear-gradient(mediumblue, darkblue);}\n")
2010                 _T(".img   { overflow: scroll; text-align: center; }\n")
2011                 _T("</style>\n")
2012                 _T("</head>\n")
2013                 _T("<body>\n")
2014                 _T("<div class=\"border\">\n")
2015                 _T("<table>\n")
2016                 _T("<tr>\n"));
2017         for (int i = 0; i < m_pImgMergeWindow->GetPaneCount(); ++i)
2018                 file.WriteString(strutils::format(_T("<th class=\"title\">%s</th>\n"), imgfilepath[i].c_str()));
2019         file.WriteString(
2020                 _T("</tr>\n")
2021                 _T("<tr>\n"));
2022         for (int i = 0; i < m_pImgMergeWindow->GetPaneCount(); ++i)
2023                 file.WriteString(
2024                         strutils::format(_T("<td><div class=\"img\"><img src=\"%s\" alt=\"%s\"></div></td>\n"),
2025                         diffimg_filename[i].c_str(), diffimg_filename[i].c_str()));
2026         file.WriteString(
2027                 _T("</tr>\n")
2028                 _T("</table>\n")
2029                 _T("</div>\n")
2030                 _T("</body>\n")
2031                 _T("</html>\n"));
2032         return true;
2033 }
2034
2035 /**
2036  * @brief Generate report from file compare results.
2037  */
2038 void CImgMergeFrame::OnToolsGenerateReport()
2039 {
2040         String s;
2041         CString folder;
2042
2043         if (!SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, false, folder, _T(""), _("HTML Files (*.htm,*.html)|*.htm;*.html|All Files (*.*)|*.*||"), _T("htm")))
2044                 return;
2045
2046         GenerateReport(s);
2047
2048         LangMessageBox(IDS_REPORT_SUCCESS, MB_OK | MB_ICONINFORMATION);
2049 }
2050
2051 void CImgMergeFrame::OnRefresh()
2052 {
2053         if (UpdateDiffItem(m_pDirDoc) == 0)
2054                 LangMessageBox(IDS_FILESSAME, MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN);
2055 }
2056
2057 void CImgMergeFrame::OnDropFiles(int pane, const std::vector<String>& files)
2058 {
2059         if (files.size() > 1 || paths::IsDirectory(files[0]))
2060         {
2061                 GetMainFrame()->GetDropHandler()->GetCallback()(files);
2062                 return;
2063         }
2064
2065         ChangeFile(pane, files[0]);
2066 }