OSDN Git Service

Allow NUL and \\.\NUL in paths specified as command line arguments (#2056)
[winmerge-jp/winmerge-jp.git] / Src / WebPageDiffFrm.cpp
1 /** 
2  * @file  WebPageDiffFrm.cpp
3  *
4  * @brief Implementation file for CWebPageDiffFrame
5  *
6  */
7
8 #include "stdafx.h"
9 #include "WebPageDiffFrm.h"
10 #include "Merge.h"
11 #include "MainFrm.h"
12 #include "BCMenu.h"
13 #include "IDirDoc.h"
14 #include "OptionsDef.h"
15 #include "OptionsMgr.h"
16 #include "OptionsDiffColors.h"
17 #include "OptionsDiffOptions.h"
18 #include "CompareOptions.h"
19 #include "paths.h"
20 #include "PathContext.h"
21 #include "unicoder.h"
22 #include "FileOrFolderSelect.h"
23 #include "SelectPluginDlg.h"
24 #include "FileLocation.h"
25 #include "Constants.h"
26 #include "Environment.h"
27 #include "UniFile.h"
28 #include <Poco/RegularExpression.h>
29
30 #ifdef _DEBUG
31 #define new DEBUG_NEW
32 #endif
33
34 #ifndef BCN_DROPDOWN
35 #define BCN_DROPDOWN (BCN_FIRST + 0x0002)
36 #endif
37
38 template <typename T, typename Func>
39 struct CallbackImpl : public T
40 {
41         CallbackImpl(Func&& callback) : m_callback(std::move(callback)) {}
42         HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { return E_NOTIMPL; }
43         ULONG STDMETHODCALLTYPE AddRef(void) override { return ++m_nRef; }
44         ULONG STDMETHODCALLTYPE Release(void) override { if (--m_nRef == 0) { delete this; return 0; } return m_nRef; }
45         HRESULT STDMETHODCALLTYPE Invoke(const WebDiffCallbackResult& result) { return m_callback(result); }
46         HRESULT STDMETHODCALLTYPE Invoke(const WebDiffEvent& event) { return m_callback(event); }
47         Func m_callback;
48         int m_nRef = 0;
49 };
50
51 template<typename T, typename Func>
52 CComPtr<T> Callback(Func&& callback)
53 {
54         return CComPtr<T>(new CallbackImpl<T, Func>(std::move(callback)));
55 }
56  
57 /////////////////////////////////////////////////////////////////////////////
58 // CWebPageDiffFrame
59
60 IMPLEMENT_DYNCREATE(CWebPageDiffFrame, CMergeFrameCommon)
61
62 BEGIN_MESSAGE_MAP(CWebPageDiffFrame, CMergeFrameCommon)
63         //{{AFX_MSG_MAP(CWebPageDiffFrame)
64         ON_WM_CREATE()
65         ON_WM_CLOSE()
66         ON_WM_DESTROY()
67         ON_WM_MDIACTIVATE()
68         ON_WM_SIZE()
69         ON_WM_SETFOCUS ()       
70         ON_MESSAGE_VOID(WM_IDLEUPDATECMDUI, OnIdleUpdateCmdUI)
71         ON_MESSAGE(MSG_STORE_PANESIZES, OnStorePaneSizes)
72         // [File] menu
73         ON_COMMAND(ID_FILE_CLOSE, OnFileClose)
74         ON_COMMAND(ID_RESCAN, OnFileReload)
75         ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnFileRecompareAs)
76         ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnUpdateFileRecompareAs)
77         // [Edit] menu
78         ON_COMMAND(ID_EDIT_CUT, OnEditCut)
79         ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
80         ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
81         ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
82         ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
83         ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
84         ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
85         ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll)
86         // [View] menu
87         ON_UPDATE_COMMAND_UI(ID_VIEW_LOCATION_BAR, OnUpdateControlBarMenu)
88         ON_COMMAND_EX(ID_VIEW_LOCATION_BAR, OnBarCheck)
89         ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
90         ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
91         ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
92         ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
93         ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
94         ON_COMMAND(ID_VIEW_SPLITVERTICALLY, OnViewSplitVertically)
95         ON_UPDATE_COMMAND_UI(ID_VIEW_SPLITVERTICALLY, OnUpdateViewSplitVertically)
96         ON_COMMAND(ID_REFRESH, OnRefresh)
97         // [Merge] menu
98         ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
99         ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
100         ON_COMMAND(ID_LASTDIFF, OnLastdiff)
101         ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
102         ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
103         ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
104         ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
105         ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
106         ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
107         ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
108         ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
109         ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
110         // [Web] menu
111         ON_COMMAND(ID_WEB_SIZE_FIT_TO_WINDOW, OnWebFitToWindow)
112         ON_UPDATE_COMMAND_UI(ID_WEB_SIZE_FIT_TO_WINDOW, OnUpdateWebFitToWindow)
113         ON_COMMAND_RANGE(ID_WEB_SIZE_320x512, ID_WEB_SIZE_1440x900, OnWebSize)
114         ON_COMMAND_RANGE(ID_WEB_COMPARE_SCREENSHOTS, ID_WEB_COMPARE_FULLSIZE_SCREENSHOTS, OnWebCompareScreenshots)
115         ON_COMMAND(ID_WEB_COMPARE_HTMLS, OnWebCompareHTMLs)
116         ON_COMMAND(ID_WEB_COMPARE_TEXTS, OnWebCompareTexts)
117         ON_COMMAND(ID_WEB_COMPARE_RESOURCETREES, OnWebCompareResourceTrees)
118         ON_COMMAND_RANGE(ID_WEB_CLEAR_DISK_CACHE, ID_WEB_CLEAR_ALL_PROFILE, OnWebClear)
119         // [Tools] menu
120         ON_COMMAND(ID_TOOLS_GENERATEREPORT, OnToolsGenerateReport)
121         // [Plugins] menu
122         ON_COMMAND_RANGE(ID_UNPACKERS_FIRST, ID_UNPACKERS_LAST, OnFileRecompareAs)
123         ON_COMMAND(ID_OPEN_WITH_UNPACKER, OnOpenWithUnpacker)
124         // [Window] menu
125         ON_COMMAND_RANGE(ID_NEXT_PANE, ID_PREV_PANE, OnWindowChangePane)
126         // [Help] menu
127         ON_COMMAND(ID_HELP, OnHelp)
128         // Dialog bar
129         ON_BN_CLICKED(IDC_FITTOWINDOW, OnBnClickedFitToWindow)
130         ON_BN_CLICKED(IDC_SHOWDIFFERENCES, OnBnClickedShowDifferences)
131         ON_BN_CLICKED(IDC_COMPARE, OnBnClickedCompare)
132         ON_EN_CHANGE(IDC_WIDTH, OnEnChangeWidth)
133         ON_EN_CHANGE(IDC_HEIGHT, OnEnChangeHeight)
134         ON_EN_CHANGE(IDC_ZOOM, OnEnChangeZoom)
135         ON_EN_CHANGE(IDC_USERAGENT, OnEnChangeUserAgent)
136         ON_EN_KILLFOCUS(IDC_WIDTH, OnKillFocusBarControls)
137         ON_EN_KILLFOCUS(IDC_HEIGHT, OnKillFocusBarControls)
138         ON_EN_KILLFOCUS(IDC_ZOOM, OnKillFocusBarControls)
139         ON_EN_KILLFOCUS(IDC_USERAGENT, OnKillFocusBarControls)
140         ON_NOTIFY(BCN_DROPDOWN, IDC_COMPARE, OnDropDownCompare)
141         // Status bar
142         ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
143         //}}AFX_MSG_MAP
144 END_MESSAGE_MAP()
145
146 CMenu CWebPageDiffFrame::menu;
147
148 /////////////////////////////////////////////////////////////////////////////
149 // CWebPageDiffFrame construction/destruction
150
151 CWebPageDiffFrame::CWebPageDiffFrame()
152 : CMergeFrameCommon(IDI_EQUALWEBPAGE, IDI_NOTEQUALWEBPAGE)
153 , m_pDirDoc(nullptr)
154 , m_bAutoMerged(false)
155 , m_pWebDiffWindow(nullptr)
156 //, m_pWebToolWindow(nullptr)
157 , m_nBufferType{BUFFERTYPE::NORMAL, BUFFERTYPE::NORMAL, BUFFERTYPE::NORMAL}
158 , m_bRO{}
159 , m_nActivePane(-1)
160 , m_bInUpdateWebPageDiffBar(false)
161 , m_bCompareCompleted(false)
162 {
163 }
164
165 CWebPageDiffFrame::~CWebPageDiffFrame()
166 {
167         GetMainFrame()->UnwatchDocuments(this);
168
169         if (m_pDirDoc != nullptr)
170         {
171                 m_pDirDoc->MergeDocClosing(this);
172                 m_pDirDoc = nullptr;
173         }
174
175         HMODULE hModule = GetModuleHandleW(L"WinWebDiffLib.dll");
176         if (hModule != nullptr)
177         {
178                 bool (*pfnWinWebDiff_DestroyWindow)(IWebDiffWindow *) = 
179                         (bool (*)(IWebDiffWindow *))GetProcAddress(hModule, "WinWebDiff_DestroyWindow");
180 //              bool (*pfnWinWebDiff_DestroyToolWindow)(IWebToolWindow *) = 
181 //                      (bool (*)(IWebToolWindow *))GetProcAddress(hModule, "WinWebDiff_DestroyToolWindow");
182                 if (pfnWinWebDiff_DestroyWindow != nullptr/* && pfnWinWebDiff_DestroyToolWindow != nullptr*/)
183                 {
184                         if (m_pWebDiffWindow != nullptr)
185                                 pfnWinWebDiff_DestroyWindow(m_pWebDiffWindow);
186 //                      if (m_pWebToolWindow != nullptr)
187 //                              pfnWinWebDiff_DestroyToolWindow(m_pWebToolWindow);
188                         m_pWebDiffWindow = nullptr;
189 //                      m_pWebToolWindow = nullptr;
190                 }
191         }
192 }
193
194 bool CWebPageDiffFrame::OpenDocs(int nFiles, const FileLocation fileloc[], const bool bRO[], const String strDesc[], CMDIFrameWnd *pParent, std::function<void ()> callback)
195 {
196         m_callbackOnOpenCompleted = callback;
197         m_bCompareCompleted = false;
198         
199         CWaitCursor waitstatus;
200         int nNormalBuffer = 0;
201         for (int pane = 0; pane < nFiles; ++pane)
202         {
203                 m_filePaths.SetPath(pane, fileloc[pane].filepath, false);
204                 m_bRO[pane] = bRO[pane];
205                 m_strDesc[pane] = strDesc ? strDesc[pane] : _T("");
206                 if (fileloc[pane].filepath.empty() || paths::IsNullDeviceName(fileloc[pane].filepath))
207                 {
208                         m_nBufferType[pane] = BUFFERTYPE::UNNAMED;
209                         if (m_strDesc[pane].empty())
210                                 m_strDesc[pane] = (pane == 0) ? _("Untitled left") : ((nFiles < 3 || pane == 2) ? _("Untitled right") : _("Untitled middle"));
211                         if (paths::IsNullDeviceName(fileloc[pane].filepath))
212                                 m_filePaths.SetPath(pane, _T("about:blank"), false);
213                 }
214                 else
215                 {
216                         m_nBufferType[pane] = (!strDesc || strDesc[pane].empty()) ? BUFFERTYPE::NORMAL : BUFFERTYPE::NORMAL_NAMED;
217                         ++nNormalBuffer;
218                 }
219         }
220         SetTitle(nullptr);
221
222         LPCTSTR lpszWndClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
223                         ::LoadCursor(nullptr, IDC_ARROW), (HBRUSH)(COLOR_WINDOW+1), nullptr);
224
225         if (!CMergeFrameCommon::Create(lpszWndClass, GetTitle(), WS_OVERLAPPEDWINDOW | WS_CHILD, rectDefault, pParent))
226                 return false;
227
228         int nCmdShow = SW_SHOW;
229         if (GetOptionsMgr()->GetBool(OPT_ACTIVE_FRAME_MAX))
230                 nCmdShow = SW_SHOWMAXIMIZED;
231         ShowWindow(nCmdShow);
232         BringToTop(nCmdShow);
233
234         GetParent()->ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_DRAWFRAME);
235
236         GetMainFrame()->WatchDocuments(this);
237
238         return true;
239 }
240
241 void CWebPageDiffFrame::MoveOnLoad(int nPane, int)
242 {
243         if (nPane < 0)
244         {
245                 nPane = (m_nBufferType[0] != BUFFERTYPE::UNNAMED) ? GetOptionsMgr()->GetInt(OPT_ACTIVE_PANE) : 0;
246                 if (nPane < 0 || nPane >= m_pWebDiffWindow->GetPaneCount())
247                         nPane = 0;
248         }
249
250         m_pWebDiffWindow->SetActivePane(nPane);
251
252         if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST) && m_pWebDiffWindow->GetDiffCount() > 0)
253                 m_pWebDiffWindow->SelectDiff(0);
254 }
255
256 /**
257  * @brief DirDoc gives us its identity just after it creates us
258  */
259 void CWebPageDiffFrame::SetDirDoc(IDirDoc * pDirDoc)
260 {
261         ASSERT(pDirDoc != nullptr && m_pDirDoc == nullptr);
262         m_pDirDoc = pDirDoc;
263 }
264
265 IMergeDoc::FileChange CWebPageDiffFrame::IsFileChangedOnDisk(int pane) const
266 {
267         DiffFileInfo dfi;
268         if (!dfi.Update(m_filePaths[pane]))
269                 return FileChange::Removed;
270         int tolerance = 0;
271         if (GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME))
272                 tolerance = SmallTimeDiff; // From MainFrm.h
273         int64_t timeDiff = dfi.mtime - m_fileInfo[pane].mtime;
274         if (timeDiff < 0) timeDiff = -timeDiff;
275         if ((timeDiff > tolerance * Poco::Timestamp::resolution()) || (dfi.size != m_fileInfo[pane].size))
276                 return FileChange::Changed;
277         return FileChange::NoChange;
278 }
279
280 void CWebPageDiffFrame::CheckFileChanged(void)
281 {
282         if (!m_pWebDiffWindow)
283                 return;
284         for (int pane = 0; pane < m_pWebDiffWindow->GetPaneCount(); ++pane)
285         {
286                 if (IsFileChangedOnDisk(pane) == FileChange::Changed)
287                 {
288                         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]);
289                         if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN, IDS_FILECHANGED_RESCAN) == IDYES)
290                         {
291                                 OnFileReload();
292                         }
293                         break;
294                 }
295         }
296 }
297
298 /**
299  * @brief Create a status bar to be associated with a heksedit control
300  */
301 void CWebPageDiffFrame::CreateWebWndStatusBar(CStatusBar &wndStatusBar, CWnd *pwndPane)
302 {
303         wndStatusBar.Create(pwndPane, WS_CHILD|WS_VISIBLE);
304         wndStatusBar.SetIndicators(0, 1);
305         wndStatusBar.SetPaneInfo(0, 0, SBPS_STRETCH, 0);
306         wndStatusBar.SetParent(this);
307         wndStatusBar.SetWindowPos(&wndBottom, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
308 }
309
310 static bool isTempFile(const String& path)
311 {
312         String tmpDir = env::GetTemporaryPath();
313         strutils::replace(tmpDir, _T("\\"), _T("/"));
314         tmpDir = _T("file:///") + tmpDir;
315         return (path.find(tmpDir) == 0);
316 }
317
318 void CWebPageDiffFrame::OnWebDiffEvent(const WebDiffEvent& event)
319 {
320         switch (event.type)
321         {
322         case WebDiffEvent::SourceChanged:
323         case WebDiffEvent::TabChanged:
324         {
325                 if (m_nBufferType[event.pane] == BUFFERTYPE::UNNAMED)
326                 {
327                         m_nBufferType[event.pane] = BUFFERTYPE::NORMAL;
328                         m_strDesc[event.pane].clear();
329                 }
330                 String curUrl = m_pWebDiffWindow->GetCurrentUrl(event.pane);
331                 if (!isTempFile(curUrl) && curUrl != _T("about:blank"))
332                 {
333                         m_filePaths[event.pane] = paths::isFileURL(curUrl) ? paths::FromURL(curUrl) : curUrl;
334                         UpdateHeaderPath(event.pane);
335                 }
336                 break;
337         }
338         case WebDiffEvent::ZoomFactorChanged:
339                 UpdateWebPageDiffBar();
340                 break;
341         }
342 }
343
344 /**
345  * @brief returns true if WinWebDiffLib.dll is loadable
346  */
347 bool CWebPageDiffFrame::IsLoadable()
348 {
349         static HMODULE hModule;
350         if (hModule == nullptr)
351         {
352                 hModule = LoadLibraryW(L"WinWebDiff\\WinWebDiffLib.dll");
353                 if (hModule == nullptr)
354                         return false;
355         }
356         return true;
357 }
358
359
360 /**
361  * @brief returns true if URL matches configured pattern
362  */
363 bool CWebPageDiffFrame::MatchURLPattern(const String& url)
364 {
365         const String& includePattern = GetOptionsMgr()->GetString(OPT_CMP_WEB_URL_PATTERN_TO_INCLUDE);
366         if (includePattern.empty())
367                 return false;
368         std::string textu8 = ucr::toUTF8(url);
369         try
370         {
371                 Poco::RegularExpression reInclude(ucr::toUTF8(includePattern));
372                 if (!reInclude.match(textu8))
373                         return false;
374                 const String& excludePattern = GetOptionsMgr()->GetString(OPT_CMP_WEB_URL_PATTERN_TO_EXCLUDE);
375                 if (excludePattern.empty())
376                         return true;
377                 Poco::RegularExpression reExclude(ucr::toUTF8(excludePattern));
378                 return !reExclude.match(textu8);
379         }
380         catch (...)
381         {
382                 return false;
383         }
384 }
385
386 /**
387  * @brief Create the splitter, the filename bar, the status bar, and the two views
388  */
389 BOOL CWebPageDiffFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,
390         CCreateContext* pContext)
391 {
392         if (!IsLoadable())
393                 return FALSE;
394
395         HMODULE hModule = GetModuleHandleW(L"WinWebDiffLib.dll");
396         if (hModule == nullptr)
397                 return FALSE;
398
399         IWebDiffWindow * (*pfnWinWebDiff_CreateWindow)(HINSTANCE hInstance, HWND hWndParent, int nID) =
400                 (IWebDiffWindow * (*)(HINSTANCE hInstance, HWND hWndParent, int nID))GetProcAddress(hModule, "WinWebDiff_CreateWindow");
401         if (pfnWinWebDiff_CreateWindow == nullptr ||
402                 (m_pWebDiffWindow = pfnWinWebDiff_CreateWindow(hModule, m_hWnd, AFX_IDW_PANE_FIRST)) == nullptr)
403         {
404                 FreeLibrary(hModule);
405                 return FALSE;
406         }
407
408         if (!m_pWebDiffWindow->IsWebView2Installed())
409         {
410                 if (IDYES == AfxMessageBox(_("WebView2 runtime is not installed. Do you want to download it?").c_str(), MB_ICONWARNING | MB_YESNO))
411                 {
412                         m_pWebDiffWindow->DownloadWebView2();
413                 }
414                 return FALSE;
415         }
416
417         m_pWebDiffWindow->AddEventListener(
418                 Callback<IWebDiffEventHandler>([this](const WebDiffEvent& event) -> HRESULT
419                 {
420                         OnWebDiffEvent(event);
421                         return S_OK;
422                 })
423         );
424
425         LoadOptions();
426
427         m_pWebDiffWindow->SetUserDataFolderType(
428                 static_cast<IWebDiffWindow::UserdataFolderType>(GetOptionsMgr()->GetInt(OPT_CMP_WEB_USERDATAFOLDER_TYPE)),
429                 GetOptionsMgr()->GetBool(OPT_CMP_WEB_USERDATAFOLDER_PERPANE));
430
431         auto callback = Callback<IWebDiffCallback>([this](const WebDiffCallbackResult& result) -> HRESULT
432                 {
433                         int nNormalBuffer = 0;
434                         for (int pane = 0; pane < m_pWebDiffWindow->GetPaneCount(); ++pane)
435                         {
436                                 if (m_nBufferType[pane] != BUFFERTYPE::UNNAMED)
437                                         ++nNormalBuffer;
438                         }
439                         if (nNormalBuffer == 0)
440                                 UpdateLastCompareResult();
441
442                         if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
443                                 m_pWebDiffWindow->FirstDiff();
444
445                         if (m_callbackOnOpenCompleted)
446                         {
447                                 m_callbackOnOpenCompleted();
448                                 m_callbackOnOpenCompleted = nullptr;
449                         }
450                         m_bCompareCompleted = true;
451                         return S_OK;
452                 });
453         bool bResult;
454         if (std::count(m_nBufferType, m_nBufferType + m_filePaths.GetSize(), BUFFERTYPE::UNNAMED) == m_filePaths.GetSize())
455         {
456                 for (int pane = 0; pane < m_filePaths.GetSize(); ++pane)
457                         m_filePaths[pane] = _T("about:blank");
458         }
459         bResult = OpenUrls(callback);
460
461         for (int pane = 0; pane < m_filePaths.GetSize(); ++pane)
462         {
463                 m_fileInfo[pane].Update(m_filePaths[pane]);
464         }
465
466         // Merge frame has also a dockable bar at the very left
467         // This is not the client area, but we create it now because we want
468         // to use the CCreateContext
469 //      String sCaption = theApp.LoadString(IDS_LOCBAR_CAPTION);
470 //      if (!m_wndLocationBar.Create(this, sCaption.c_str(), WS_CHILD | WS_VISIBLE, ID_VIEW_LOCATION_BAR))
471 //      {
472 //              TRACE0("Failed to create LocationBar\n");
473 //              return FALSE;
474 //      }
475 //
476 //      IWebToolWindow * (*pfnWinWebDiff_CreateToolWindow)(HINSTANCE hInstance, HWND hWndParent, IWebDiffWindow *) =
477 //              (IWebToolWindow * (*)(HINSTANCE hInstance, HWND hWndParent, IWebDiffWindow *pWebPageDiffWindow))GetProcAddress(hModule, "WinWebDiff_CreateToolWindow");
478 //      if (pfnWinWebDiff_CreateToolWindow == nullptr ||
479 //              (m_pWebToolWindow = pfnWinWebDiff_CreateToolWindow(hModule, m_wndLocationBar.m_hWnd, m_pWebDiffWindow)) == nullptr)
480 //      {
481 //              return FALSE;
482 //      }
483 //
484 //      m_pWebToolWindow->Translate(TranslateLocationPane);
485 //
486 //      m_wndLocationBar.SetFrameHwnd(GetSafeHwnd());
487
488         return TRUE;
489 }
490
491 //void CWebPageDiffFrame::TranslateLocationPane(int id, const wchar_t *org, size_t dstbufsize, wchar_t *dst)
492 //{
493 //      swprintf_s(dst, dstbufsize, L"%s", tr("WebPageDiffFrame|LocationPane", ucr::toUTF8(org)).c_str());
494 //}
495
496 /////////////////////////////////////////////////////////////////////////////
497 // CWebPageDiffFrame message handlers
498
499 int CWebPageDiffFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
500 {
501         if (CMergeFrameCommon::OnCreate(lpCreateStruct) == -1)
502                 return -1;
503
504         EnableDocking(CBRS_ALIGN_TOP | CBRS_ALIGN_BOTTOM | CBRS_ALIGN_LEFT | CBRS_ALIGN_RIGHT);
505
506         CMergeFrameCommon::RemoveBarBorder();
507
508         // Merge frame has a header bar at top
509         if (!m_wndWebPageDiffBar.Create(this) || !m_wndFilePathBar.Create(this))
510         {
511                 TRACE0("Failed to create dialog bar\n");
512                 return -1;      // fail to create
513         }
514
515         UpdateWebPageDiffBar();
516
517         m_wndFilePathBar.SetPaneCount(m_pWebDiffWindow->GetPaneCount());
518         m_wndFilePathBar.SetOnSetFocusCallback([&](int pane) {
519                 if (m_nActivePane != pane)
520                         m_pWebDiffWindow->SetActivePane(pane);
521         });
522         m_wndFilePathBar.SetOnCaptionChangedCallback([&](int pane, const String& sText) {
523                 m_strDesc[pane] = sText;
524                 UpdateHeaderPath(pane);
525         });
526
527         // Merge frame also has a dockable bar at the very left
528         // created in OnCreateClient 
529         //m_wndLocationBar.SetBarStyle(m_wndLocationBar.GetBarStyle() |
530         //      CBRS_SIZE_DYNAMIC | CBRS_ALIGN_LEFT);
531         //m_wndLocationBar.EnableDocking(CBRS_ALIGN_LEFT | CBRS_ALIGN_RIGHT);
532         //DockControlBar(&m_wndLocationBar, AFX_IDW_DOCKBAR_LEFT);
533
534         for (int nPane = 0; nPane < m_pWebDiffWindow->GetPaneCount(); nPane++)
535         {
536                 m_wndFilePathBar.SetActive(nPane, FALSE);
537 //              CreateWebWndStatusBar(m_wndStatusBar[nPane], CWnd::FromHandle(m_pWebDiffWindow->GetPaneHWND(nPane)));
538                 UpdateHeaderPath(nPane);
539         }
540
541 //      CSize size = m_wndStatusBar[0].CalcFixedLayout(TRUE, TRUE);
542 //      m_rectBorder.bottom = size.cy;
543
544         CDockState pDockState;
545         pDockState.LoadState(_T("Settings-WebPageDiffFrame"));
546         if (EnsureValidDockState(pDockState)) // checks for valid so won't ASSERT
547                 SetDockState(pDockState);
548         // for the dimensions of the diff and location pane, use the CSizingControlBar loader
549         //m_wndLocationBar.LoadState(_T("Settings-WebPageDiffFrame"));
550
551         return 0;
552 }
553
554 /**
555 * @brief We must use this function before a call to SetDockState
556 *
557 * @note Without this, SetDockState will assert or crash if a bar from the
558 * CDockState is missing in the current CMergeEditFrame.
559 * The bars are identified with their ID. This means the missing bar bug is triggered
560 * when we run WinMerge after changing the ID of a bar.
561 */
562 bool CWebPageDiffFrame::EnsureValidDockState(CDockState& state)
563 {
564         for (int i = (int)state.m_arrBarInfo.GetSize() - 1; i >= 0; i--)
565         {
566                 bool barIsCorrect = true;
567                 CControlBarInfo* pInfo = (CControlBarInfo*)state.m_arrBarInfo[i];
568                 if (pInfo == nullptr)
569                         barIsCorrect = false;
570                 else
571                 {
572                         if (!pInfo->m_bFloating)
573                         {
574                                 pInfo->m_pBar = GetControlBar(pInfo->m_nBarID);
575                                 if (pInfo->m_pBar == nullptr)
576                                         barIsCorrect = false; //toolbar id's probably changed   
577                         }
578                 }
579
580                 if (!barIsCorrect)
581                         state.m_arrBarInfo.RemoveAt(i);
582         }
583         return true;
584 }
585
586 /**
587  * @brief Save the window's position, free related resources, and destroy the window
588  */
589 BOOL CWebPageDiffFrame::DestroyWindow() 
590 {
591         SavePosition();
592         SaveActivePane();
593         SaveOptions();
594         SaveWindowState();
595         CFrameWnd* pParentFrame = GetParentFrame();
596         BOOL result = CMergeFrameCommon::DestroyWindow();
597         if (pParentFrame)
598                 pParentFrame->OnUpdateFrameTitle(FALSE);
599         return result;
600 }
601
602 void CWebPageDiffFrame::LoadOptions()
603 {
604         m_pWebDiffWindow->SetHorizontalSplit(GetOptionsMgr()->GetBool(OPT_SPLIT_HORIZONTALLY));
605         m_pWebDiffWindow->SetZoom(GetOptionsMgr()->GetInt(OPT_CMP_WEB_ZOOM) / 1000.0);
606         SIZE size{ GetOptionsMgr()->GetInt(OPT_CMP_WEB_VIEW_WIDTH), GetOptionsMgr()->GetInt(OPT_CMP_WEB_VIEW_HEIGHT) };
607         m_pWebDiffWindow->SetSize(size);
608         m_pWebDiffWindow->SetFitToWindow(GetOptionsMgr()->GetBool(OPT_CMP_WEB_FIT_TO_WINDOW));
609         m_pWebDiffWindow->SetShowDifferences(GetOptionsMgr()->GetBool(OPT_CMP_WEB_SHOWDIFFERENCES));
610         m_pWebDiffWindow->SetShowWordDifferences(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
611         m_pWebDiffWindow->SetUserAgent(GetOptionsMgr()->GetString(OPT_CMP_WEB_USER_AGENT).c_str());
612         COLORSETTINGS colors;
613         IWebDiffWindow::ColorSettings colorSettings;
614         Options::DiffColors::Load(GetOptionsMgr(), colors);
615         colorSettings.clrDiff = colors.clrDiff;
616         colorSettings.clrDiffText = colors.clrDiffText;
617         colorSettings.clrSelDiff = colors.clrSelDiff;
618         colorSettings.clrSelDiffText = colors.clrSelDiffText;
619         colorSettings.clrSNP = colors.clrSNP;
620         colorSettings.clrSNPText = colors.clrSNPText;
621         colorSettings.clrSelSNP = colors.clrSelSNP;
622         colorSettings.clrSelSNPText = colors.clrSelSNPText;
623         colorSettings.clrWordDiff = colors.clrWordDiff;
624         colorSettings.clrWordDiffDeleted = colors.clrWordDiffDeleted;
625         colorSettings.clrWordDiffText = colors.clrWordDiffText;
626         colorSettings.clrSelWordDiff = colors.clrSelWordDiff;
627         colorSettings.clrSelWordDiffDeleted = colors.clrSelWordDiffDeleted;
628         colorSettings.clrSelWordDiffText = colors.clrSelWordDiffText;
629         m_pWebDiffWindow->SetDiffColorSettings(colorSettings);
630         DIFFOPTIONS options;
631         IWebDiffWindow::DiffOptions diffOptions;
632         Options::DiffOptions::Load(GetOptionsMgr(), options);
633         diffOptions.bFilterCommentsLines = options.bFilterCommentsLines;
634         diffOptions.completelyBlankOutIgnoredChanges = options.bCompletelyBlankOutIgnoredChanges;
635         diffOptions.diffAlgorithm = options.nDiffAlgorithm;
636         diffOptions.ignoreBlankLines = options.bIgnoreBlankLines;
637         diffOptions.ignoreCase = options.bIgnoreCase;
638         diffOptions.ignoreEol = options.bIgnoreEol;
639         diffOptions.ignoreNumbers = options.bIgnoreNumbers;
640         diffOptions.ignoreWhitespace = options.nIgnoreWhitespace;
641         m_pWebDiffWindow->SetDiffOptions(diffOptions);
642 }
643
644 void CWebPageDiffFrame::SaveOptions()
645 {
646         //      GetOptionsMgr()->SaveOption(OPT_CMP_WEB_DIFFCOLORALPHA, static_cast<int>(m_pWebDiffWindow->GetDiffColorAlpha() * 100.0));
647         SIZE size = m_pWebDiffWindow->GetSize();
648         GetOptionsMgr()->SaveOption(OPT_CMP_WEB_VIEW_WIDTH, size.cx);
649         GetOptionsMgr()->SaveOption(OPT_CMP_WEB_VIEW_HEIGHT, size.cy);
650         GetOptionsMgr()->SaveOption(OPT_CMP_WEB_FIT_TO_WINDOW, m_pWebDiffWindow->GetFitToWindow());
651         GetOptionsMgr()->SaveOption(OPT_CMP_WEB_SHOWDIFFERENCES, m_pWebDiffWindow->GetShowDifferences());
652         GetOptionsMgr()->SaveOption(OPT_CMP_WEB_ZOOM, static_cast<int>(m_pWebDiffWindow->GetZoom() * 1000));
653         GetOptionsMgr()->SaveOption(OPT_CMP_WEB_USER_AGENT, m_pWebDiffWindow->GetUserAgent());
654 }
655
656 /**
657  * @brief Save coordinates of the frame, splitters, and bars
658  *
659  * @note Do not save the maximized/restored state here. We are interested
660  * in the state of the active frame, and maybe this frame is not active
661  */
662 void CWebPageDiffFrame::SavePosition()
663 {
664         CRect rc;
665         GetWindowRect(&rc);
666
667         // save the bars layout
668         // save docking positions and sizes
669         CDockState m_pDockState;
670         GetDockState(m_pDockState);
671         m_pDockState.SaveState(_T("Settings-WebPageDiffFrame"));
672         // for the dimensions of the diff pane, use the CSizingControlBar save
673         //m_wndLocationBar.SaveState(_T("Settings-WebPageDiffFrame"));
674 }
675
676 void CWebPageDiffFrame::SaveActivePane()
677 {
678         GetOptionsMgr()->SaveOption(OPT_ACTIVE_PANE, m_pWebDiffWindow->GetActivePane());
679 }
680
681 void CWebPageDiffFrame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
682 {
683         CMergeFrameCommon::OnMDIActivate(bActivate, pActivateWnd, pDeactivateWnd);
684
685         if (bActivate)
686         {
687                 GetMainFrame()->PostMessage(WM_USER + 1);
688         }
689 }
690
691 void CWebPageDiffFrame::OnClose() 
692 {
693         // clean up pointers.
694         CMergeFrameCommon::OnClose();
695 }
696
697 void CWebPageDiffFrame::OnDestroy()
698 {
699         if (!m_pWebDiffWindow)
700                 return;
701 }
702
703 /**
704  * @brief Reloads the opened files
705  */
706 void CWebPageDiffFrame::OnFileReload()
707 {
708         int nActivePane = m_pWebDiffWindow->GetActivePane();
709         OpenUrls(
710                 Callback<IWebDiffCallback>([nActivePane, this](const WebDiffCallbackResult& result) -> HRESULT
711                         {
712                                 MoveOnLoad(nActivePane);
713                                 for (int pane = 0; pane < m_filePaths.GetSize(); ++pane)
714                                         m_fileInfo[pane].Update(m_filePaths[pane]);
715                                 return S_OK;
716                         }));
717 }
718
719 void CWebPageDiffFrame::OnFileClose() 
720 {
721         OnClose();
722 }
723
724 void CWebPageDiffFrame::OnFileRecompareAs(UINT nID)
725 {
726         PathContext paths = m_filePaths;
727         fileopenflags_t dwFlags[3];
728         String strDesc[3];
729         int nBuffers = m_filePaths.GetSize();
730         PackingInfo infoUnpacker(m_infoUnpacker.GetPluginPipeline());
731
732         for (int nBuffer = 0; nBuffer < nBuffers; ++nBuffer)
733         {
734                 dwFlags[nBuffer] = m_bRO[nBuffer] ? FFILEOPEN_READONLY : 0;
735                 strDesc[nBuffer] = m_strDesc[nBuffer];
736         }
737         if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
738         {
739                 infoUnpacker.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
740                 nID = GetOptionsMgr()->GetBool(OPT_PLUGINS_OPEN_IN_SAME_FRAME_TYPE) ? ID_MERGE_COMPARE_WEBPAGE : -ID_MERGE_COMPARE_WEBPAGE;
741         }
742
743         CloseNow();
744         GetMainFrame()->DoFileOrFolderOpen(&paths, dwFlags, strDesc, _T(""),
745                 GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS), nullptr, &infoUnpacker, nullptr, nID);
746 }
747
748 void CWebPageDiffFrame::OnUpdateFileRecompareAs(CCmdUI* pCmdUI)
749 {
750         pCmdUI->Enable(pCmdUI->m_nID != ID_MERGE_COMPARE_WEBPAGE);
751 }
752
753 void CWebPageDiffFrame::OnOpenWithUnpacker()
754 {
755         CSelectPluginDlg dlg(m_infoUnpacker.GetPluginPipeline(),
756                 strutils::join(m_filePaths.begin(), m_filePaths.end(), _T("|")),
757                 CSelectPluginDlg::PluginType::Unpacker, false);
758         if (dlg.DoModal() == IDOK)
759         {
760                 PackingInfo infoUnpacker(dlg.GetPluginPipeline());
761                 PathContext paths = m_filePaths;
762                 fileopenflags_t dwFlags[3] = { FFILEOPEN_NOMRU, FFILEOPEN_NOMRU, FFILEOPEN_NOMRU };
763                 String strDesc[3] = { m_strDesc[0], m_strDesc[1], m_strDesc[2] };
764                 CloseNow();
765                 GetMainFrame()->DoFileOpen(
766                         GetOptionsMgr()->GetBool(OPT_PLUGINS_OPEN_IN_SAME_FRAME_TYPE) ? ID_MERGE_COMPARE_WEBPAGE: -1,
767                         &paths, dwFlags, strDesc, _T(""), &infoUnpacker);
768         }
769 }
770
771 void  CWebPageDiffFrame::OnWindowChangePane(UINT nID) 
772 {
773         int npanes = m_pWebDiffWindow->GetPaneCount();
774         int pane = m_pWebDiffWindow->GetActivePane();
775         pane = (nID == ID_NEXT_PANE) ? ((pane + 1) % npanes) : ((pane + npanes - 1) % npanes);
776         m_pWebDiffWindow->SetActivePane(pane);
777 }
778
779 /**
780  * @brief Write path and filename to headerbar
781  * @note SetText() does not repaint unchanged text
782  */
783 void CWebPageDiffFrame::UpdateHeaderPath(int pane)
784 {
785         String sText;
786
787         if (m_nBufferType[pane] == BUFFERTYPE::UNNAMED ||
788                 m_nBufferType[pane] == BUFFERTYPE::NORMAL_NAMED)
789         {
790                 sText = m_strDesc[pane];
791         }
792         else
793         {
794                 sText = m_filePaths.GetPath(pane);
795                 if (m_pDirDoc != nullptr)
796                         m_pDirDoc->ApplyDisplayRoot(pane, sText);
797         }
798
799         m_wndFilePathBar.SetText(pane, sText.c_str());
800
801         SetTitle(nullptr);
802 }
803
804 /// update splitting position for panels 1/2 and headerbar and statusbar 
805 void CWebPageDiffFrame::UpdateHeaderSizes()
806 {
807         if (m_pWebDiffWindow != nullptr)
808         {
809                 int w[3];
810                 CRect rc, rcMergeWindow;
811                 int nPaneCount = m_pWebDiffWindow->GetPaneCount();
812                 GetClientRect(&rc);
813                 ::GetWindowRect(m_pWebDiffWindow->GetHWND(), &rcMergeWindow);
814                 ScreenToClient(rcMergeWindow);
815                 if (!m_pWebDiffWindow->GetHorizontalSplit())
816                 {
817                         for (int pane = 0; pane < nPaneCount; pane++)
818                         {
819                                 RECT rc1 = m_pWebDiffWindow->GetPaneWindowRect(pane);
820                                 w[pane] = rc1.right - rc1.left - 4;
821                                 if (w[pane]<1) w[pane]=1; // Perry 2003-01-22 (I don't know why this happens)
822                         }
823                 }
824                 else
825                 {
826                         for (int pane = 0; pane < nPaneCount; pane++)
827                                 w[pane] = rcMergeWindow.Width() / nPaneCount - 4;
828                 }
829
830                 if (!std::equal(m_nLastSplitPos, m_nLastSplitPos + nPaneCount - 1, w))
831                 {
832                         std::copy_n(w, nPaneCount - 1, m_nLastSplitPos);
833
834                         // resize controls in header dialog bar
835                         m_wndFilePathBar.Resize(w);
836
837                         rc.left = rcMergeWindow.left;
838                         rc.top = rc.bottom - m_rectBorder.bottom;
839                         rc.right = rc.left;
840                         for (int pane = 0; pane < nPaneCount; pane++)
841                         {
842                                 rc.right += w[pane] + 4 + 2;
843 //                              m_wndStatusBar[pane].MoveWindow(&rc);
844                                 rc.left = rc.right;
845                         }
846                 }
847         }
848 }
849
850 /**
851  * @brief Update document filenames to title
852  */
853 void CWebPageDiffFrame::SetTitle(LPCTSTR lpszTitle)
854 {
855         String sTitle = (lpszTitle != nullptr) ? lpszTitle : CMergeFrameCommon::GetTitleString(m_filePaths, m_strDesc, &m_infoUnpacker, nullptr);
856         CMergeFrameCommon::SetTitle(sTitle.c_str());
857         if (m_hWnd != nullptr)
858         {
859                 SetWindowText(sTitle.c_str());
860                 GetMainFrame()->OnUpdateFrameTitle(TRUE);
861         }
862 }
863
864 int CWebPageDiffFrame::UpdateLastCompareResult()
865 {
866         int result = -1;
867         if (m_bCompareCompleted)
868         {
869                 result = m_pWebDiffWindow->GetDiffCount() > 0 ? 1 : 0;
870                 SetLastCompareResult(result);
871         }
872         return result;
873 }
874
875 void CWebPageDiffFrame::UpdateAutoPaneResize()
876 {
877 }
878
879 void CWebPageDiffFrame::UpdateSplitter()
880 {
881 }
882
883 bool CWebPageDiffFrame::OpenUrls(IWebDiffCallback* callback)
884 {
885         bool bResult;
886         String filteredFilenames = strutils::join(m_filePaths.begin(), m_filePaths.end(), _T("|"));
887         String strTempFileName[3];
888         m_infoUnpacker.EnableWebBrowserMode();
889         for (int pane = 0; pane < m_filePaths.GetSize(); ++pane)
890         {
891                 strTempFileName[pane] = m_filePaths[pane];
892                 if (!m_infoUnpacker.Unpacking(&m_unpackerSubcodes[pane], strTempFileName[pane], filteredFilenames, {strTempFileName[pane]}))
893                 {
894 //                      return false;
895                 }
896         }
897         if (m_filePaths.GetSize() == 2)
898                 bResult = SUCCEEDED(m_pWebDiffWindow->Open(ucr::toUTF16(strTempFileName[0]).c_str(), ucr::toUTF16(strTempFileName[1]).c_str(), callback));
899         else
900                 bResult = SUCCEEDED(m_pWebDiffWindow->Open(ucr::toUTF16(strTempFileName[0]).c_str(), ucr::toUTF16(strTempFileName[1]).c_str(), ucr::toUTF16(strTempFileName[2]).c_str(), callback));
901         return bResult;
902 }
903
904 /// Document commanding us to close
905 bool CWebPageDiffFrame::CloseNow()
906 {
907         DestroyWindow();
908         return true;
909 }
910
911 /**
912  * @brief A string to display as a tooltip for MDITabbar
913  */
914 CString CWebPageDiffFrame::GetTooltipString() const
915 {
916         return CMergeFrameCommon::GetTooltipString(m_filePaths, m_strDesc, &m_infoUnpacker, nullptr).c_str();
917 }
918
919 /**
920  * @brief Update any resources necessary after a GUI language change
921  */
922 void CWebPageDiffFrame::UpdateResources()
923 {
924         //m_pWebToolWindow->Translate(TranslateLocationPane);
925 }
926
927 void CWebPageDiffFrame::RefreshOptions()
928 {
929         LoadOptions();
930 }
931
932 /**
933  * @brief Handle some keys when in merging mode
934  */
935 bool CWebPageDiffFrame::MergeModeKeyDown(MSG* pMsg)
936 {
937         bool bHandled = false;
938
939         // Allow default text selection when SHIFT pressed
940         if (::GetAsyncKeyState(VK_SHIFT))
941                 return false;
942
943         // Allow default editor functions when CTRL pressed
944         if (::GetAsyncKeyState(VK_CONTROL))
945                 return false;
946
947         // If we are in merging mode (merge with cursor keys)
948         // handle some keys here
949         switch (pMsg->wParam)
950         {
951         case VK_UP:
952                 OnPrevdiff();
953                 bHandled = true;
954                 break;
955         case VK_DOWN:
956                 OnNextdiff();
957                 bHandled = true;
958                 break;
959         }
960
961         return bHandled;
962 }
963 /**
964  * @brief Check for keyboard commands
965  */
966 BOOL CWebPageDiffFrame::PreTranslateMessage(MSG* pMsg)
967 {
968         if (pMsg->message == WM_KEYDOWN)
969         {
970                 // If we are in merging mode (merge with cursor keys)
971                 // handle some keys here
972                 if (theApp.GetMergingMode())
973                 {
974                         bool bHandled = MergeModeKeyDown(pMsg);
975                         if (bHandled)
976                                 return true;
977                 }
978
979                 // Close window in response to VK_ESCAPE if user has allowed it from options
980                 if (pMsg->wParam == VK_ESCAPE && GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC) != 0)
981                 {
982                         PostMessage(WM_CLOSE, 0, 0);
983                         return true;
984                 }
985         }
986         return CMergeFrameCommon::PreTranslateMessage(pMsg);
987 }
988
989 void CWebPageDiffFrame::OnSize(UINT nType, int cx, int cy) 
990 {
991         CMergeFrameCommon::OnSize(nType, cx, cy);
992         UpdateHeaderSizes();
993 }
994
995 /**
996  * @brief Synchronize control and status bar placements with splitter position,
997  * update mod indicators, synchronize scrollbars
998  */
999 void CWebPageDiffFrame::OnIdleUpdateCmdUI()
1000 {
1001         if (IsWindowVisible())
1002         {
1003                 int nActivePane = m_pWebDiffWindow->GetActivePane();
1004                 if (nActivePane != -1)
1005                         m_nActivePane = nActivePane;
1006
1007                 UpdateHeaderSizes();
1008                 for (int pane = 0; pane < m_filePaths.GetSize(); ++pane)
1009                 {
1010                         // Update mod indicators
1011                         m_wndFilePathBar.SetActive(pane, pane == nActivePane);
1012                 }
1013                 UpdateLastCompareResult();
1014         }
1015         CMergeFrameCommon::OnIdleUpdateCmdUI();
1016 }
1017
1018 /**
1019  * @brief Save pane sizes and positions when one of panes requests it.
1020  */
1021 LRESULT CWebPageDiffFrame::OnStorePaneSizes(WPARAM wParam, LPARAM lParam)
1022 {
1023         SavePosition();
1024         return 0;
1025 }
1026
1027 void CWebPageDiffFrame::UpdateWebPageDiffBar()
1028 {
1029         m_bInUpdateWebPageDiffBar = true;
1030         bool fitToWindow = m_pWebDiffWindow->GetFitToWindow();
1031         m_wndWebPageDiffBar.CheckDlgButton(IDC_FITTOWINDOW, fitToWindow);
1032         m_wndWebPageDiffBar.CheckDlgButton(IDC_SHOWDIFFERENCES, m_pWebDiffWindow->GetShowDifferences());
1033         m_wndWebPageDiffBar.EnableDlgItem(IDC_WIDTH, !fitToWindow);
1034         m_wndWebPageDiffBar.EnableDlgItem(IDC_HEIGHT, !fitToWindow);
1035         SIZE size = m_pWebDiffWindow->GetSize();
1036         m_wndWebPageDiffBar.SetDlgItemText(IDC_WIDTH, strutils::format(_T("%d"), size.cx));
1037         m_wndWebPageDiffBar.SetDlgItemText(IDC_HEIGHT, strutils::format(_T("%d"), size.cy));
1038         m_wndWebPageDiffBar.SetDlgItemText(IDC_ZOOM, strutils::format(_T("%.1f%%"), 
1039                 m_pWebDiffWindow->GetZoom() * 100.0));
1040         m_wndWebPageDiffBar.SetDlgItemText(IDC_USERAGENT, m_pWebDiffWindow->GetUserAgent());
1041         m_bInUpdateWebPageDiffBar = false;
1042 }
1043
1044 void CWebPageDiffFrame::OnBnClickedFitToWindow()
1045 {
1046         m_pWebDiffWindow->SetFitToWindow(m_wndWebPageDiffBar.IsDlgButtonChecked(IDC_FITTOWINDOW));
1047         UpdateWebPageDiffBar();
1048 }
1049
1050 void CWebPageDiffFrame::OnBnClickedShowDifferences()
1051 {
1052         m_pWebDiffWindow->SetShowDifferences(m_wndWebPageDiffBar.IsDlgButtonChecked(IDC_SHOWDIFFERENCES));
1053         UpdateWebPageDiffBar();
1054 }
1055
1056 void CWebPageDiffFrame::OnBnClickedCompare()
1057 {
1058         OnRefresh();
1059 }
1060
1061 void CWebPageDiffFrame::OnDropDownCompare(NMHDR *pNMHDR, LRESULT *pResult)
1062 {
1063         CRect rc;
1064         m_wndWebPageDiffBar.GetDlgItem(IDC_COMPARE)->GetWindowRect(&rc);
1065         CPoint point { rc.left, rc.bottom };
1066         BCMenu menuPopup;
1067         menuPopup.LoadMenu(MAKEINTRESOURCE(IDR_POPUP_WEBPAGE_COMPARE));
1068         theApp.TranslateMenu(menuPopup.m_hMenu);
1069         BCMenu* pPopup = (BCMenu*)menuPopup.GetSubMenu(0);
1070         pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
1071                 point.x, point.y, AfxGetMainWnd());
1072 }
1073
1074 void CWebPageDiffFrame::OnEnChangeWidth()
1075 {
1076         if (m_bInUpdateWebPageDiffBar)
1077                 return;
1078         String text;
1079         m_wndWebPageDiffBar.GetDlgItemText(IDC_WIDTH, text);
1080         int v = tc::ttoi(text.c_str());
1081         if (v > 0 && v < 32678)
1082         {
1083                 SIZE size = m_pWebDiffWindow->GetSize();
1084                 size.cx = v;
1085                 m_pWebDiffWindow->SetSize(size);
1086         }
1087 }
1088
1089 void CWebPageDiffFrame::OnEnChangeHeight()
1090 {
1091         if (m_bInUpdateWebPageDiffBar)
1092                 return;
1093         String text;
1094         m_wndWebPageDiffBar.GetDlgItemText(IDC_HEIGHT, text);
1095         int v = tc::ttoi(text.c_str());
1096         if (v > 0 && v < 32678)
1097         {
1098                 SIZE size = m_pWebDiffWindow->GetSize();
1099                 size.cy = v;
1100                 m_pWebDiffWindow->SetSize(size);
1101         }
1102 }
1103
1104 void CWebPageDiffFrame::OnEnChangeZoom()
1105 {
1106         if (m_bInUpdateWebPageDiffBar)
1107                 return;
1108         String text;
1109         m_wndWebPageDiffBar.GetDlgItemText(IDC_ZOOM, text);
1110         tchar_t* endptr = nullptr;
1111         double v = tc::tcstod(text.c_str(), &endptr);
1112         if ((* endptr == '\0' || *endptr == '%') && v >= 25.0 && v <= 500.0)
1113                 m_pWebDiffWindow->SetZoom(v / 100.0);
1114 }
1115
1116 void CWebPageDiffFrame::OnEnChangeUserAgent()
1117 {
1118         if (m_bInUpdateWebPageDiffBar)
1119                 return;
1120         String text;
1121         m_wndWebPageDiffBar.GetDlgItemText(IDC_USERAGENT, text);
1122         m_pWebDiffWindow->SetUserAgent(text.c_str());
1123 }
1124
1125 void CWebPageDiffFrame::OnKillFocusBarControls()
1126 {
1127         UpdateWebPageDiffBar();
1128         SaveOptions();
1129         CString text;
1130         m_wndFilePathBar.GetDlgItemTextW(IDC_USERAGENT, text);
1131 }
1132
1133 void CWebPageDiffFrame::OnUpdateStatusNum(CCmdUI* pCmdUI) 
1134 {
1135         tchar_t sIdx[32] = { 0 };
1136         tchar_t sCnt[32] = { 0 };
1137         String s;
1138         const int nDiffs = m_pWebDiffWindow->GetDiffCount();
1139         
1140         // Files are identical - show text "Identical"
1141         if (nDiffs <= 0)
1142                 s = theApp.LoadString(IDS_IDENTICAL);
1143         
1144         // There are differences, but no selected diff
1145         // - show amount of diffs
1146         else if (m_pWebDiffWindow->GetCurrentDiffIndex() < 0)
1147         {
1148                 s = theApp.LoadString(nDiffs == 1 ? IDS_1_DIFF_FOUND : IDS_NO_DIFF_SEL_FMT);
1149                 _itot_s(nDiffs, sCnt, 10);
1150                 strutils::replace(s, _T("%1"), sCnt);
1151         }
1152         
1153         // There are differences and diff selected
1154         // - show diff number and amount of diffs
1155         else
1156         {
1157                 s = theApp.LoadString(IDS_DIFF_NUMBER_STATUS_FMT);
1158                 const int signInd = m_pWebDiffWindow->GetCurrentDiffIndex();
1159                 _itot_s(signInd + 1, sIdx, 10);
1160                 strutils::replace(s, _T("%1"), sIdx);
1161                 _itot_s(nDiffs, sCnt, 10);
1162                 strutils::replace(s, _T("%2"), sCnt);
1163         }
1164         pCmdUI->SetText(s.c_str());
1165 }
1166         
1167 /**
1168  * @brief Cut current selection to clipboard
1169  */
1170 void CWebPageDiffFrame::OnEditCut()
1171 {
1172         m_pWebDiffWindow->Cut();
1173 }
1174
1175 /**
1176  * @brief Copy current selection to clipboard
1177  */
1178 void CWebPageDiffFrame::OnEditCopy()
1179 {
1180         m_pWebDiffWindow->Copy();
1181 }
1182
1183 /**
1184  * @brief Paste clipboard content over selected content
1185  */
1186 void CWebPageDiffFrame::OnEditPaste()
1187 {
1188         m_pWebDiffWindow->Paste();
1189 }
1190
1191 /**
1192  * @brief Undo last action
1193  */
1194 void CWebPageDiffFrame::OnEditUndo()
1195 {
1196         m_pWebDiffWindow->Undo();
1197 }
1198
1199 /**
1200  * @brief Redo last action
1201  */
1202 void CWebPageDiffFrame::OnEditRedo()
1203 {
1204         m_pWebDiffWindow->Redo();
1205 }
1206
1207 /**
1208  * @brief Update the tool bar's "Undo" icon. It should be enabled when
1209  * renaming an item and undo is possible.
1210  */
1211 void CWebPageDiffFrame::OnUpdateEditUndo(CCmdUI* pCmdUI)
1212 {
1213         pCmdUI->Enable(m_pWebDiffWindow->CanUndo());
1214 }
1215
1216 /**
1217  * @brief Update the tool bar's "Redo" icon. It should be enabled when
1218  * renaming an item and undo is possible.
1219  */
1220 void CWebPageDiffFrame::OnUpdateEditRedo(CCmdUI* pCmdUI)
1221 {
1222         pCmdUI->Enable(m_pWebDiffWindow->CanRedo());
1223 }
1224
1225 /**
1226  * @brief Select entire image
1227  */
1228 void CWebPageDiffFrame::OnEditSelectAll()
1229 {
1230         m_pWebDiffWindow->SelectAll();
1231 }
1232
1233 /**
1234  * @brief Called when user selects View/Zoom In from menu.
1235  */
1236 void CWebPageDiffFrame::OnViewZoomIn()
1237 {
1238         m_pWebDiffWindow->SetZoom(m_pWebDiffWindow->GetZoom() + 0.1);
1239 }
1240
1241 /**
1242  * @brief Called when user selects View/Zoom Out from menu.
1243  */
1244 void CWebPageDiffFrame::OnViewZoomOut()
1245 {
1246         m_pWebDiffWindow->SetZoom(m_pWebDiffWindow->GetZoom() - 0.1);
1247 }
1248
1249 /**
1250  * @brief Called when user selects View/Zoom Normal from menu.
1251  */
1252 void CWebPageDiffFrame::OnViewZoomNormal()
1253 {
1254         m_pWebDiffWindow->SetZoom(1.0);
1255 }
1256
1257 /**
1258  * @brief Enables/disables linediff (different color for diffs)
1259  */
1260 void CWebPageDiffFrame::OnViewLineDiffs() 
1261 {
1262         bool bWordDiffHighlight = !GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
1263         GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, bWordDiffHighlight);
1264         m_pWebDiffWindow->SetShowWordDifferences(bWordDiffHighlight);
1265 }
1266
1267 void CWebPageDiffFrame::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
1268 {
1269         pCmdUI->Enable(true);
1270         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
1271 }
1272
1273 /**
1274  * @brief Split panes vertically
1275  */
1276 void CWebPageDiffFrame::OnViewSplitVertically() 
1277 {
1278         bool bSplitVertically = !m_pWebDiffWindow->GetHorizontalSplit();
1279         bSplitVertically = !bSplitVertically; // toggle
1280         GetOptionsMgr()->SaveOption(OPT_SPLIT_HORIZONTALLY, !bSplitVertically);
1281         m_pWebDiffWindow->SetHorizontalSplit(!bSplitVertically);
1282 }
1283
1284 /**
1285  * @brief Update "Split Vertically" UI items
1286  */
1287 void CWebPageDiffFrame::OnUpdateViewSplitVertically(CCmdUI* pCmdUI) 
1288 {
1289         pCmdUI->Enable(TRUE);
1290         pCmdUI->SetCheck(!m_pWebDiffWindow->GetHorizontalSplit());
1291 }
1292
1293 /**
1294  * @brief Go to first diff
1295  *
1296  * Called when user selects "First Difference"
1297  * @sa CWebPageDiffFrame::SelectDiff()
1298  */
1299 void CWebPageDiffFrame::OnFirstdiff()
1300 {
1301         m_pWebDiffWindow->FirstDiff();
1302 }
1303
1304 /**
1305  * @brief Update "First diff" UI items
1306  */
1307 void CWebPageDiffFrame::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1308 {
1309         OnUpdatePrevdiff(pCmdUI);
1310 }
1311
1312 /**
1313  * @brief Go to last diff
1314  */
1315 void CWebPageDiffFrame::OnLastdiff()
1316 {
1317         m_pWebDiffWindow->LastDiff();
1318 }
1319
1320 /**
1321  * @brief Update "Last diff" UI items
1322  */
1323 void CWebPageDiffFrame::OnUpdateLastdiff(CCmdUI* pCmdUI)
1324 {
1325         OnUpdateNextdiff(pCmdUI);
1326 }
1327
1328 /**
1329  * @brief Go to next diff and select it.
1330  */
1331 void CWebPageDiffFrame::OnNextdiff()
1332 {
1333         if (m_pWebDiffWindow->GetCurrentDiffIndex() != m_pWebDiffWindow->GetDiffCount() - 1)
1334                 m_pWebDiffWindow->NextDiff();
1335         else if (m_pDirDoc != nullptr)
1336                 m_pDirDoc->MoveToNextDiff(this);
1337 }
1338
1339 /**
1340  * @brief Update "Next diff" UI items
1341  */
1342 void CWebPageDiffFrame::OnUpdateNextdiff(CCmdUI* pCmdUI)
1343 {
1344         bool enabled = m_bCompareCompleted && (
1345                 m_pWebDiffWindow->GetNextDiffIndex() >= 0 ||
1346                 (m_pWebDiffWindow->GetDiffCount() > 0 && m_pWebDiffWindow->GetCurrentDiffIndex() == -1));
1347
1348         if (!enabled && m_pDirDoc != nullptr)
1349                 enabled = m_pDirDoc->MoveableToNextDiff();
1350         pCmdUI->Enable(enabled);
1351 }
1352
1353 /**
1354  * @brief Go to previous diff and select it.
1355  */
1356 void CWebPageDiffFrame::OnPrevdiff()
1357 {
1358         if (m_pWebDiffWindow->GetCurrentDiffIndex() > 0)
1359         {
1360                 m_pWebDiffWindow->PrevDiff();
1361         }
1362         else if (m_pDirDoc != nullptr)
1363                 m_pDirDoc->MoveToPrevDiff(this);
1364 }
1365
1366 /**
1367  * @brief Update "Previous diff" UI items
1368  */
1369 void CWebPageDiffFrame::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1370 {
1371         bool enabled = m_bCompareCompleted && (
1372                 m_pWebDiffWindow->GetPrevDiffIndex() >= 0 ||
1373                 (m_pWebDiffWindow->GetDiffCount() > 0 && m_pWebDiffWindow->GetCurrentDiffIndex() == -1));
1374
1375         if (!enabled && m_pDirDoc != nullptr)
1376                 enabled = m_pDirDoc->MoveableToPrevDiff();
1377         pCmdUI->Enable(enabled);
1378 }
1379
1380 /**
1381  * @brief Go to next conflict and select it.
1382  */
1383 void CWebPageDiffFrame::OnNextConflict()
1384 {
1385         m_pWebDiffWindow->NextConflict();
1386 }
1387
1388 /**
1389  * @brief Update "Next Conflict" UI items
1390  */
1391 void CWebPageDiffFrame::OnUpdateNextConflict(CCmdUI* pCmdUI)
1392 {
1393         pCmdUI->Enable(m_bCompareCompleted && 
1394                 m_pWebDiffWindow->GetPaneCount() > 2 && (
1395                         m_pWebDiffWindow->GetNextConflictIndex() >= 0 ||
1396                         (m_pWebDiffWindow->GetConflictCount() > 0 && m_pWebDiffWindow->GetCurrentDiffIndex() == -1)
1397                 )
1398         );
1399 }
1400
1401 /**
1402  * @brief Go to previous diff and select it.
1403  */
1404 void CWebPageDiffFrame::OnPrevConflict()
1405 {
1406         m_pWebDiffWindow->PrevConflict();
1407 }
1408
1409 /**
1410  * @brief Update "Previous diff" UI items
1411  */
1412 void CWebPageDiffFrame::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1413 {
1414         pCmdUI->Enable(m_bCompareCompleted &&
1415                 m_pWebDiffWindow->GetPaneCount() > 2 && (
1416                         m_pWebDiffWindow->GetPrevConflictIndex() >= 0 ||
1417                         (m_pWebDiffWindow->GetConflictCount() > 0 && m_pWebDiffWindow->GetCurrentDiffIndex() == -1)
1418                 )
1419         );
1420 }
1421
1422 void CWebPageDiffFrame::OnWebFitToWindow()
1423 {
1424         m_pWebDiffWindow->SetFitToWindow(true);
1425         SaveOptions();
1426         UpdateWebPageDiffBar();
1427 }
1428
1429 void CWebPageDiffFrame::OnUpdateWebFitToWindow(CCmdUI* pCmdUI)
1430 {
1431         pCmdUI->SetCheck(m_pWebDiffWindow->GetFitToWindow());
1432 }
1433
1434 void CWebPageDiffFrame::OnWebSize(UINT nID)
1435 {
1436         switch (nID)
1437         {
1438         case ID_WEB_SIZE_320x512:
1439                 m_pWebDiffWindow->SetFitToWindow(false);
1440                 m_pWebDiffWindow->SetSize({ 320, 512 });
1441                 UpdateWebPageDiffBar();
1442                 break;
1443         case ID_WEB_SIZE_375x600:
1444                 m_pWebDiffWindow->SetFitToWindow(false);
1445                 m_pWebDiffWindow->SetSize({ 375, 600 });
1446                 UpdateWebPageDiffBar();
1447                 break;
1448         case ID_WEB_SIZE_1024x640:
1449                 m_pWebDiffWindow->SetFitToWindow(false);
1450                 m_pWebDiffWindow->SetSize({ 1024, 640 });
1451                 SaveOptions();
1452                 UpdateWebPageDiffBar();
1453                 break;
1454         case ID_WEB_SIZE_1280x800:
1455                 m_pWebDiffWindow->SetFitToWindow(false);
1456                 m_pWebDiffWindow->SetSize({ 1280, 800 });
1457                 SaveOptions();
1458                 UpdateWebPageDiffBar();
1459                 break;
1460         case ID_WEB_SIZE_1440x900:
1461                 m_pWebDiffWindow->SetFitToWindow(false);
1462                 m_pWebDiffWindow->SetSize({ 1440, 900 });
1463                 SaveOptions();
1464                 UpdateWebPageDiffBar();
1465                 break;
1466         }
1467 }
1468
1469 void CWebPageDiffFrame::OnWebCompareScreenshots(UINT nID)
1470 {
1471         std::shared_ptr<CWaitCursor> pWaitStatus{ new CWaitCursor() };
1472         PathContext paths;
1473         const wchar_t *spaths[3];
1474         std::vector<String> descs;
1475         const int nPanes = m_pWebDiffWindow->GetPaneCount();
1476         for (int pane = 0; pane < nPanes; ++pane)
1477         {
1478                 std::shared_ptr<TempFile> pTempFile(new TempFile());
1479                 pTempFile->Create(_T("SCR"), _T(".png"));
1480                 paths.SetPath(pane, pTempFile->GetPath());
1481                 spaths[pane] = paths[pane].c_str();
1482                 descs.push_back(m_filePaths[pane]);
1483                 m_tempFiles.push_back(pTempFile);
1484         }
1485         m_pWebDiffWindow->SaveFiles(
1486                 (nID == ID_WEB_COMPARE_FULLSIZE_SCREENSHOTS) ? IWebDiffWindow::FULLSIZE_SCREENSHOT : IWebDiffWindow::SCREENSHOT,
1487                 spaths,
1488                 Callback<IWebDiffCallback>([paths, descs, pWaitStatus](const WebDiffCallbackResult& result) -> HRESULT
1489                         {
1490                                 fileopenflags_t dwFlags[3] = { FFILEOPEN_NOMRU, FFILEOPEN_NOMRU, FFILEOPEN_NOMRU };
1491                                 GetMainFrame()->DoFileOpen(0, &paths, dwFlags, descs.data());
1492                                 return S_OK;
1493                         }));
1494 }
1495
1496 void CWebPageDiffFrame::OnWebCompareHTMLs()
1497 {
1498         std::shared_ptr<CWaitCursor> pWaitStatus{ new CWaitCursor() };
1499         PathContext paths;
1500         const wchar_t *spaths[3];
1501         std::vector<String> descs;
1502         const int nPanes = m_pWebDiffWindow->GetPaneCount();
1503         for (int pane = 0; pane < nPanes; ++pane)
1504         {
1505                 std::shared_ptr<TempFile> pTempFile(new TempFile());
1506                 pTempFile->Create(_T("HTM"), _T(".html"));
1507                 paths.SetPath(pane, pTempFile->GetPath());
1508                 spaths[pane] = paths[pane].c_str();
1509                 descs.push_back(m_filePaths[pane]);
1510                 m_tempFiles.push_back(pTempFile);
1511         }
1512         m_pWebDiffWindow->SaveFiles(IWebDiffWindow::HTML, spaths,
1513                 Callback<IWebDiffCallback>([paths, descs, pWaitStatus](const WebDiffCallbackResult& result) -> HRESULT
1514                         {
1515                                 PackingInfo infoUnpacker(String(_T("PrettifyHTML")));
1516                                 fileopenflags_t dwFlags[3] = { FFILEOPEN_NOMRU, FFILEOPEN_NOMRU, FFILEOPEN_NOMRU };
1517                                 GetMainFrame()->DoFileOpen(0, &paths, dwFlags, descs.data(), _T(""), &infoUnpacker);
1518                                 return S_OK;
1519                         }));
1520 }
1521
1522 void CWebPageDiffFrame::OnWebCompareTexts()
1523 {
1524         std::shared_ptr<CWaitCursor> pWaitStatus{ new CWaitCursor() };
1525         PathContext paths;
1526         const wchar_t *spaths[3];
1527         std::vector<String> descs;
1528         const int nPanes = m_pWebDiffWindow->GetPaneCount();
1529         for (int pane = 0; pane < nPanes; ++pane)
1530         {
1531                 std::shared_ptr<TempFile> pTempFile(new TempFile());
1532                 pTempFile->Create(_T("TXT"), _T(".txt"));
1533                 paths.SetPath(pane, pTempFile->GetPath());
1534                 spaths[pane] = paths[pane].c_str();
1535                 descs.push_back(m_filePaths[pane]);
1536                 m_tempFiles.push_back(pTempFile);
1537         }
1538         m_pWebDiffWindow->SaveFiles(IWebDiffWindow::TEXT, spaths,
1539                 Callback<IWebDiffCallback>([paths, descs, pWaitStatus](const WebDiffCallbackResult& result) -> HRESULT
1540                         {
1541                                 fileopenflags_t dwFlags[3] = { FFILEOPEN_NOMRU, FFILEOPEN_NOMRU, FFILEOPEN_NOMRU };
1542                                 GetMainFrame()->DoFileOpen(0, &paths, dwFlags, descs.data(), _T(""));
1543                                 return S_OK;
1544                         }));
1545 }
1546
1547 void CWebPageDiffFrame::OnWebCompareResourceTrees()
1548 {
1549         std::shared_ptr<CWaitCursor> pWaitStatus{ new CWaitCursor() };
1550         PathContext paths;
1551         const wchar_t *spaths[3];
1552         std::vector<String> descs;
1553         const int nPanes = m_pWebDiffWindow->GetPaneCount();
1554         for (int pane = 0; pane < nPanes; ++pane)
1555         {
1556                 std::shared_ptr<TempFolder> pTempFolder(new TempFolder());
1557                 pTempFolder->Create();
1558                 paths.SetPath(pane, pTempFolder->GetPath());
1559                 spaths[pane] = paths[pane].c_str();
1560                 descs.push_back(m_filePaths[pane]);
1561                 m_tempFolders.push_back(pTempFolder);
1562         }
1563         m_pWebDiffWindow->SaveFiles(IWebDiffWindow::RESOURCETREE, spaths,
1564                 Callback<IWebDiffCallback>([paths, descs, pWaitStatus](const WebDiffCallbackResult& result) -> HRESULT
1565                         {
1566                                 fileopenflags_t dwFlags[3]{};
1567                                 for (int pane = 0; pane < paths.GetSize(); ++pane)
1568                                         dwFlags[pane] = FFILEOPEN_NOMRU;
1569                                 GetMainFrame()->DoFileOrFolderOpen(&paths, dwFlags, descs.data(), _T(""), true);
1570                                 return S_OK;
1571                         }));
1572 }
1573
1574 void CWebPageDiffFrame::OnWebClear(UINT nID)
1575 {
1576         IWebDiffWindow::BrowsingDataType dataKinds;
1577         switch (nID)
1578         {
1579         case ID_WEB_CLEAR_DISK_CACHE:       dataKinds = IWebDiffWindow::BrowsingDataType::DISK_CACHE; break;
1580         case ID_WEB_CLEAR_COOKIES:          dataKinds = IWebDiffWindow::BrowsingDataType::COOKIES; break;
1581         case ID_WEB_CLEAR_BROWSING_HISTORY: dataKinds = IWebDiffWindow::BrowsingDataType::BROWSING_HISTORY; break;
1582         case ID_WEB_CLEAR_ALL_PROFILE:      dataKinds = IWebDiffWindow::BrowsingDataType::ALL_PROFILE; break;
1583         default:
1584                 return;
1585         }
1586         m_pWebDiffWindow->ClearBrowsingData(-1, dataKinds);
1587 }
1588
1589 bool CWebPageDiffFrame::GenerateReport(const String& sFileName) const
1590 {
1591         bool result = false;
1592         bool completed = false;
1593         if (!GenerateReport(sFileName, [&completed, &result](bool res) { result = res; completed = true; }))
1594                 return false;
1595         CMainFrame::WaitAndDoMessageLoop(completed, 0);
1596         return result;
1597 }
1598
1599 bool CWebPageDiffFrame::GenerateReport(const String& sFileName, std::function<void(bool)> callback) const
1600 {
1601         String rptdir_full, rptdir, path, name, ext;
1602         String url[3];
1603         String diffrpt_filename[3];
1604         String diffrpt_filename_full[3];
1605         const wchar_t* pfilenames[3]{};
1606         paths::SplitFilename(sFileName, &path, &name, &ext);
1607         rptdir_full = paths::ConcatPath(path, name) + _T(".files");
1608         rptdir = paths::FindFileName(rptdir_full);
1609         paths::CreateIfNeeded(rptdir_full);
1610
1611         for (int pane = 0; pane < m_pWebDiffWindow->GetPaneCount(); ++pane)
1612         {
1613                 url[pane] = ucr::toTString(m_pWebDiffWindow->GetCurrentUrl(pane));
1614                 diffrpt_filename[pane] = strutils::format(_T("%s/%d.pdf"), rptdir, pane + 1);
1615                 diffrpt_filename_full[pane] = strutils::format(_T("%s/%d.pdf"), rptdir_full, pane + 1);
1616                 pfilenames[pane] = diffrpt_filename_full[pane].c_str();
1617         }
1618
1619         UniStdioFile file;
1620         if (!file.Open(sFileName, _T("wt")))
1621         {
1622                 String errMsg = GetSysError(GetLastError());
1623                 String msg = strutils::format_string1(
1624                         _("Error creating the report:\n%1"), errMsg);
1625                 AfxMessageBox(msg.c_str(), MB_OK | MB_ICONSTOP);
1626                 return false;
1627         }
1628
1629         file.SetCodepage(ucr::CP_UTF_8);
1630
1631         file.WriteString(
1632                 _T("<!DOCTYPE html>\n")
1633                 _T("<html>\n")
1634                 _T("<head>\n")
1635                 _T("<meta charset=\"UTF-8\">\n")
1636                 _T("<title>WinMerge Webpage Compare Report</title>\n")
1637                 _T("<style>\n")
1638                 _T("table { table-layout: fixed; width: 100%; border-collapse: collapse; }\n")
1639                 _T("th {position: sticky; top: 0;}\n")
1640                 _T("td,th { border: solid 1px black; }\n")
1641                 _T("embed { width: 100%; height: calc(100vh - 56px) }\n")
1642                 _T(".title { color: white; background-color: blue; vertical-align: top; padding: 4px 4px; background: linear-gradient(mediumblue, darkblue);}\n")
1643                 _T("</style>\n")
1644                 _T("</head>\n")
1645                 _T("<body>\n")
1646                 _T("<table>\n")
1647                 _T("<tr>\n"));
1648         for (int pane = 0; pane < m_pWebDiffWindow->GetPaneCount(); ++pane)
1649                 file.WriteString(strutils::format(_T("<th class=\"title\">%s</th>\n"), url[pane]));
1650         file.WriteString(_T("</tr>\n"));
1651         file.WriteString(
1652                 _T("<tr>\n"));
1653         for (int pane = 0; pane < m_pWebDiffWindow->GetPaneCount(); ++pane)
1654                 file.WriteString(
1655                         strutils::format(_T("<td><embed type=\"application/pdf\" src=\"%s\" title=\"%s\"></td>\n"),
1656                                 diffrpt_filename[pane], diffrpt_filename[pane]));
1657         file.WriteString(
1658                 _T("</tr>\n"));
1659         file.WriteString(
1660                 _T("</table>\n")
1661                 _T("</body>\n")
1662                 _T("</html>\n"));
1663
1664         return SUCCEEDED(m_pWebDiffWindow->SaveDiffFiles(IWebDiffWindow::PDF, pfilenames, 
1665                 Callback<IWebDiffCallback>([this, callback](const WebDiffCallbackResult& result) -> HRESULT
1666                         {
1667                                 callback(SUCCEEDED(result.errorCode));
1668                                 return S_OK;
1669                         })));
1670 }
1671
1672 /**
1673  * @brief Generate report from file compare results.
1674  */
1675 void CWebPageDiffFrame::OnToolsGenerateReport()
1676 {
1677         String s;
1678         CString folder;
1679         if (!SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, false, folder, _T(""), _("HTML Files (*.htm,*.html)|*.htm;*.html|All Files (*.*)|*.*||"), _T("htm")))
1680                 return;
1681
1682         CWaitCursor waitstatus;
1683         if (GenerateReport(s))
1684                 LangMessageBox(IDS_REPORT_SUCCESS, MB_OK | MB_ICONINFORMATION | MB_MODELESS);
1685 }
1686
1687 void CWebPageDiffFrame::OnRefresh()
1688 {
1689         if (!m_bCompareCompleted)
1690                 return;
1691         m_bCompareCompleted = false;
1692         m_pWebDiffWindow->Recompare(
1693                 Callback<IWebDiffCallback>([this](const WebDiffCallbackResult& result) -> HRESULT
1694                         {
1695                                 m_bCompareCompleted = true;
1696                                 if (UpdateLastCompareResult() == 0 &&
1697                                     std::count(m_filePaths.begin(), m_filePaths.end(), L"about:blank") != m_filePaths.GetSize())
1698                                 {
1699                                         CMergeFrameCommon::ShowIdenticalMessage(m_filePaths, true,
1700                                                 [](const tchar_t* msg, UINT flags, UINT id) -> int { return AfxMessageBox(msg, flags, id); });
1701                                 }
1702                                 return S_OK;
1703                         })
1704                 );
1705 }
1706
1707 void CWebPageDiffFrame::OnSetFocus(CWnd* pNewWnd)
1708 {
1709         if (m_nActivePane != -1)
1710                 m_pWebDiffWindow->SetActivePane(m_nActivePane);
1711 }
1712
1713 /**
1714  * @brief Open help from mainframe when user presses F1
1715  */
1716 void CWebPageDiffFrame::OnHelp()
1717 {
1718         theApp.ShowHelp(WebPageDiffFrameHelpLocation);
1719 }