OSDN Git Service

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