OSDN Git Service

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