OSDN Git Service

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