OSDN Git Service

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