OSDN Git Service

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