OSDN Git Service

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