OSDN Git Service

Add /fileext command line option
[winmerge-jp/winmerge-jp.git] / Src / MainFrm.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  MainFrm.cpp
9  *
10  * @brief Implementation of the CMainFrame class
11  */
12
13 #include "StdAfx.h"
14 #include "MainFrm.h"
15 #include <vector>
16 #include <afxinet.h>
17 #include <boost/range/mfc.hpp>
18 #include "Constants.h"
19 #include "Merge.h"
20 #include "FileFilterHelper.h"
21 #include "UnicodeString.h"
22 #include "BCMenu.h"
23 #include "OpenFrm.h"
24 #include "DirFrame.h"           // Include type information
25 #include "MergeEditFrm.h"
26 #include "HexMergeFrm.h"
27 #include "DirView.h"
28 #include "DirDoc.h"
29 #include "OpenDoc.h"
30 #include "MergeDoc.h"
31 #include "MergeEditView.h"
32 #include "HexMergeDoc.h"
33 #include "HexMergeView.h"
34 #include "ImgMergeFrm.h"
35 #include "LineFiltersList.h"
36 #include "SubstitutionFiltersList.h"
37 #include "ConflictFileParser.h"
38 #include "LineFiltersDlg.h"
39 #include "SubstitutionFiltersDlg.h"
40 #include "paths.h"
41 #include "Environment.h"
42 #include "PatchTool.h"
43 #include "Plugins.h"
44 #include "ConfigLog.h"
45 #include "7zCommon.h"
46 #include "Merge7zFormatMergePluginImpl.h"
47 #include "FileFiltersDlg.h"
48 #include "OptionsMgr.h"
49 #include "OptionsDef.h"
50 #include "codepage_detect.h"
51 #include "unicoder.h"
52 #include "PreferencesDlg.h"
53 #include "FileOrFolderSelect.h"
54 #include "PluginsListDlg.h"
55 #include "SelectPluginDlg.h"
56 #include "stringdiffs.h"
57 #include "MergeCmdLineInfo.h"
58 #include "OptionsFont.h"
59 #include "JumpList.h"
60 #include "DropHandler.h"
61 #include "LanguageSelect.h"
62 #include "VersionInfo.h"
63 #include "Bitmap.h"
64 #include "CCrystalTextMarkers.h"
65 #include "utils/hqbitmap.h"
66 #include "UniFile.h"
67 #include "TFile.h"
68 #include "Shell.h"
69 #include "WindowsManagerDialog.h"
70
71 using std::vector;
72 using boost::begin;
73 using boost::end;
74
75 #ifdef _DEBUG
76 #define new DEBUG_NEW
77 #endif
78
79 static void LoadToolbarImageList(int orgImageWidth, int newImageHeight, UINT nIDResource, bool bGrayscale, CImageList& ImgList);
80 static CPtrList &GetDocList(CMultiDocTemplate *pTemplate);
81 template<class DocClass>
82 DocClass * GetMergeDocForDiff(CMultiDocTemplate *pTemplate, CDirDoc *pDirDoc, int nFiles, bool bMakeVisible = true);
83
84 /**
85  * @brief A table associating menuitem id, icon and menus to apply.
86  */
87 const CMainFrame::MENUITEM_ICON CMainFrame::m_MenuIcons[] = {
88         { ID_FILE_OPENCONFLICT,                 IDB_FILE_OPENCONFLICT,                  CMainFrame::MENU_ALL },
89         { ID_FILE_NEW_TABLE,                    IDB_FILE_NEW_TABLE,                             CMainFrame::MENU_ALL },
90         { ID_FILE_NEW_HEX,                              IDB_FILE_NEW_HEX,                               CMainFrame::MENU_ALL },
91         { ID_FILE_NEW_IMAGE,                    IDB_FILE_NEW_IMAGE,                             CMainFrame::MENU_ALL },
92         { ID_FILE_NEW3,                                 IDB_FILE_NEW3,                                  CMainFrame::MENU_ALL },
93         { ID_FILE_NEW3_TABLE,                   IDB_FILE_NEW3_TABLE,                    CMainFrame::MENU_ALL },
94         { ID_FILE_NEW3_HEX,                             IDB_FILE_NEW3_HEX,                              CMainFrame::MENU_ALL },
95         { ID_FILE_NEW3_IMAGE,                   IDB_FILE_NEW3_IMAGE,                    CMainFrame::MENU_ALL },
96         { ID_EDIT_COPY,                                 IDB_EDIT_COPY,                                  CMainFrame::MENU_ALL },
97         { ID_EDIT_CUT,                                  IDB_EDIT_CUT,                                   CMainFrame::MENU_ALL },
98         { ID_EDIT_PASTE,                                IDB_EDIT_PASTE,                                 CMainFrame::MENU_ALL },
99         { ID_EDIT_FIND,                                 IDB_EDIT_SEARCH,                                CMainFrame::MENU_ALL },
100         { ID_WINDOW_CASCADE,                    IDB_WINDOW_CASCADE,                             CMainFrame::MENU_ALL },
101         { ID_WINDOW_TILE_HORZ,                  IDB_WINDOW_HORIZONTAL,                  CMainFrame::MENU_ALL },
102         { ID_WINDOW_TILE_VERT,                  IDB_WINDOW_VERTICAL,                    CMainFrame::MENU_ALL },
103         { ID_FILE_CLOSE,                                IDB_WINDOW_CLOSE,                               CMainFrame::MENU_ALL },
104         { ID_WINDOW_CHANGE_PANE,                IDB_WINDOW_CHANGEPANE,                  CMainFrame::MENU_ALL },
105         { ID_EDIT_WMGOTO,                               IDB_EDIT_GOTO,                                  CMainFrame::MENU_ALL },
106         { ID_EDIT_REPLACE,                              IDB_EDIT_REPLACE,                               CMainFrame::MENU_ALL },
107         { ID_VIEW_SELECTFONT,                   IDB_VIEW_SELECTFONT,                    CMainFrame::MENU_ALL },
108         { ID_APP_EXIT,                                  IDB_FILE_EXIT,                                  CMainFrame::MENU_ALL },
109         { ID_HELP_CONTENTS,                             IDB_HELP_CONTENTS,                              CMainFrame::MENU_ALL },
110         { ID_EDIT_SELECT_ALL,                   IDB_EDIT_SELECTALL,                             CMainFrame::MENU_ALL },
111         { ID_TOOLS_FILTERS,                             IDB_TOOLS_FILTERS,                              CMainFrame::MENU_ALL },
112         { ID_TOOLS_CUSTOMIZECOLUMNS,    IDB_TOOLS_COLUMNS,                              CMainFrame::MENU_ALL },
113         { ID_TOOLS_GENERATEPATCH,               IDB_TOOLS_GENERATEPATCH,                CMainFrame::MENU_ALL },
114         { ID_PLUGINS_LIST,                              IDB_PLUGINS_LIST,                               CMainFrame::MENU_ALL },
115         { ID_FILE_PRINT,                                IDB_FILE_PRINT,                                 CMainFrame::MENU_FILECMP },
116         { ID_TOOLS_GENERATEREPORT,              IDB_TOOLS_GENERATEREPORT,               CMainFrame::MENU_FILECMP },
117         { ID_EDIT_TOGGLE_BOOKMARK,              IDB_EDIT_TOGGLE_BOOKMARK,               CMainFrame::MENU_FILECMP },
118         { ID_EDIT_GOTO_NEXT_BOOKMARK,   IDB_EDIT_GOTO_NEXT_BOOKMARK,    CMainFrame::MENU_FILECMP },
119         { ID_EDIT_GOTO_PREV_BOOKMARK,   IDB_EDIT_GOTO_PREV_BOOKMARK,    CMainFrame::MENU_FILECMP },
120         { ID_EDIT_CLEAR_ALL_BOOKMARKS,  IDB_EDIT_CLEAR_ALL_BOOKMARKS,   CMainFrame::MENU_FILECMP },
121         { ID_VIEW_ZOOMIN,                               IDB_VIEW_ZOOMIN,                                CMainFrame::MENU_FILECMP },
122         { ID_VIEW_ZOOMOUT,                              IDB_VIEW_ZOOMOUT,                               CMainFrame::MENU_FILECMP },
123         { ID_COPY_FROM_LEFT,                    IDB_COPY_FROM_LEFT,                             CMainFrame::MENU_FILECMP },
124         { ID_COPY_FROM_RIGHT,                   IDB_COPY_FROM_RIGHT,                    CMainFrame::MENU_FILECMP },
125         { ID_LINES_R2L,                                 IDB_COPY_SELECTED_LINES_TO_LEFT,        CMainFrame::MENU_FILECMP },
126         { ID_LINES_L2R,                                 IDB_COPY_SELECTED_LINES_TO_RIGHT,       CMainFrame::MENU_FILECMP },
127         { ID_COPY_LINES_FROM_LEFT,              IDB_COPY_SELECTED_LINES_FROM_LEFT,      CMainFrame::MENU_FILECMP },
128         { ID_COPY_LINES_FROM_RIGHT,             IDB_COPY_SELECTED_LINES_FROM_RIGHT,     CMainFrame::MENU_FILECMP },
129         { ID_MERGE_COMPARE,                             IDB_MERGE_COMPARE,                              CMainFrame::MENU_FOLDERCMP },
130         { ID_MERGE_COMPARE_LEFT1_LEFT2,         IDB_MERGE_COMPARE_LEFT1_LEFT2,  CMainFrame::MENU_FOLDERCMP },
131         { ID_MERGE_COMPARE_RIGHT1_RIGHT2,       IDB_MERGE_COMPARE_RIGHT1_RIGHT2,CMainFrame::MENU_FOLDERCMP },
132         { ID_MERGE_COMPARE_LEFT1_RIGHT2,        IDB_MERGE_COMPARE_LEFT1_RIGHT2, CMainFrame::MENU_FOLDERCMP },
133         { ID_MERGE_COMPARE_LEFT2_RIGHT1,        IDB_MERGE_COMPARE_LEFT2_RIGHT1, CMainFrame::MENU_FOLDERCMP },
134         { ID_MERGE_DELETE,                              IDB_MERGE_DELETE,                               CMainFrame::MENU_FOLDERCMP },
135         { ID_TOOLS_GENERATEREPORT,              IDB_TOOLS_GENERATEREPORT,               CMainFrame::MENU_FOLDERCMP },
136         { ID_DIR_COPY_LEFT_TO_RIGHT,    IDB_LEFT_TO_RIGHT,                              CMainFrame::MENU_FOLDERCMP },
137         { ID_DIR_COPY_LEFT_TO_MIDDLE,   IDB_LEFT_TO_MIDDLE,                             CMainFrame::MENU_FOLDERCMP },
138         { ID_DIR_COPY_RIGHT_TO_LEFT,    IDB_RIGHT_TO_LEFT,                              CMainFrame::MENU_FOLDERCMP },
139         { ID_DIR_COPY_RIGHT_TO_MIDDLE,  IDB_RIGHT_TO_MIDDLE,                    CMainFrame::MENU_FOLDERCMP },
140         { ID_DIR_COPY_MIDDLE_TO_LEFT,   IDB_MIDDLE_TO_LEFT,                             CMainFrame::MENU_FOLDERCMP },
141         { ID_DIR_COPY_MIDDLE_TO_RIGHT,  IDB_MIDDLE_TO_RIGHT,                    CMainFrame::MENU_FOLDERCMP },
142         { ID_DIR_COPY_LEFT_TO_BROWSE,   IDB_LEFT_TO_BROWSE,                             CMainFrame::MENU_FOLDERCMP },
143         { ID_DIR_COPY_MIDDLE_TO_BROWSE, IDB_MIDDLE_TO_BROWSE,                   CMainFrame::MENU_FOLDERCMP },
144         { ID_DIR_COPY_RIGHT_TO_BROWSE,  IDB_RIGHT_TO_BROWSE,                    CMainFrame::MENU_FOLDERCMP },
145         { ID_DIR_MOVE_LEFT_TO_BROWSE,   IDB_MOVE_LEFT_TO_BROWSE,                CMainFrame::MENU_FOLDERCMP },
146         { ID_DIR_MOVE_MIDDLE_TO_BROWSE, IDB_MOVE_MIDDLE_TO_BROWSE,              CMainFrame::MENU_FOLDERCMP },
147         { ID_DIR_MOVE_RIGHT_TO_BROWSE,  IDB_MOVE_RIGHT_TO_BROWSE,               CMainFrame::MENU_FOLDERCMP },
148         { ID_DIR_DEL_LEFT,                              IDB_LEFT,                                               CMainFrame::MENU_FOLDERCMP },
149         { ID_DIR_DEL_MIDDLE,                    IDB_MIDDLE,                                             CMainFrame::MENU_FOLDERCMP },
150         { ID_DIR_DEL_RIGHT,                             IDB_RIGHT,                                              CMainFrame::MENU_FOLDERCMP },
151         { ID_DIR_DEL_BOTH,                              IDB_BOTH,                                               CMainFrame::MENU_FOLDERCMP },
152         { ID_DIR_DEL_ALL,                               IDB_ALL,                                                CMainFrame::MENU_FOLDERCMP },
153         { ID_DIR_COPY_PATHNAMES_LEFT,   IDB_LEFT,                                               CMainFrame::MENU_FOLDERCMP },
154         { ID_DIR_COPY_PATHNAMES_MIDDLE, IDB_MIDDLE,                                             CMainFrame::MENU_FOLDERCMP },
155         { ID_DIR_COPY_PATHNAMES_RIGHT,  IDB_RIGHT,                                              CMainFrame::MENU_FOLDERCMP },
156         { ID_DIR_COPY_PATHNAMES_BOTH,   IDB_BOTH,                                               CMainFrame::MENU_FOLDERCMP },
157         { ID_DIR_COPY_PATHNAMES_ALL,    IDB_ALL,                                                CMainFrame::MENU_FOLDERCMP },
158         { ID_DIR_COPY_LEFT_TO_CLIPBOARD, IDB_LEFT,                                              CMainFrame::MENU_FOLDERCMP },
159         { ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, IDB_MIDDLE,                                  CMainFrame::MENU_FOLDERCMP },
160         { ID_DIR_COPY_RIGHT_TO_CLIPBOARD, IDB_RIGHT,                                    CMainFrame::MENU_FOLDERCMP },
161         { ID_DIR_COPY_BOTH_TO_CLIPBOARD, IDB_BOTH,                                              CMainFrame::MENU_FOLDERCMP },
162         { ID_DIR_COPY_ALL_TO_CLIPBOARD, IDB_ALL,                                                CMainFrame::MENU_FOLDERCMP },
163         { ID_DIR_ZIP_LEFT,                              IDB_LEFT,                                               CMainFrame::MENU_FOLDERCMP },
164         { ID_DIR_ZIP_MIDDLE,                    IDB_MIDDLE,                                             CMainFrame::MENU_FOLDERCMP },
165         { ID_DIR_ZIP_RIGHT,                             IDB_RIGHT,                                              CMainFrame::MENU_FOLDERCMP },
166         { ID_DIR_ZIP_BOTH,                              IDB_BOTH,                                               CMainFrame::MENU_FOLDERCMP },
167         { ID_DIR_ZIP_ALL,                               IDB_ALL,                                                CMainFrame::MENU_FOLDERCMP }
168 };
169
170
171 /////////////////////////////////////////////////////////////////////////////
172 // CMainFrame
173
174 IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd)
175
176 BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
177         //{{AFX_MSG_MAP(CMainFrame)
178         ON_WM_MENUCHAR()
179         ON_WM_MEASUREITEM()
180         ON_WM_INITMENUPOPUP()
181         ON_WM_INITMENU()
182         ON_WM_CREATE()
183         ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
184         ON_COMMAND(ID_HELP_GNULICENSE, OnHelpGnulicense)
185         ON_COMMAND(ID_OPTIONS, OnOptions)
186         ON_COMMAND(ID_VIEW_SELECTFONT, OnViewSelectfont)
187         ON_COMMAND(ID_VIEW_USEDEFAULTFONT, OnViewUsedefaultfont)
188         ON_COMMAND(ID_HELP_CONTENTS, OnHelpContents)
189         ON_WM_CLOSE()
190         ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
191         ON_WM_TIMER()
192         ON_WM_DESTROY()
193         ON_COMMAND_RANGE(ID_UNPACK_MANUAL, ID_UNPACK_AUTO, OnPluginUnpackMode)
194         ON_UPDATE_COMMAND_UI_RANGE(ID_UNPACK_MANUAL, ID_UNPACK_AUTO, OnUpdatePluginUnpackMode)
195         ON_COMMAND_RANGE(ID_PREDIFFER_MANUAL, ID_PREDIFFER_AUTO, OnPluginPrediffMode)
196         ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFER_MANUAL, ID_PREDIFFER_AUTO, OnUpdatePluginPrediffMode)
197         ON_UPDATE_COMMAND_UI(ID_OPEN_WITH_UNPACKER, OnUpdatePluginRelatedMenu)
198         ON_UPDATE_COMMAND_UI(ID_APPLY_PREDIFFER, OnUpdatePluginRelatedMenu)
199         ON_UPDATE_COMMAND_UI(ID_TRANSFORM_WITH_SCRIPT, OnUpdatePluginRelatedMenu)
200         ON_UPDATE_COMMAND_UI(ID_RELOAD_PLUGINS, OnUpdatePluginRelatedMenu)
201         ON_COMMAND(ID_RELOAD_PLUGINS, OnReloadPlugins)
202         ON_COMMAND(ID_HELP_GETCONFIG, OnSaveConfigData)
203         ON_COMMAND(ID_FILE_NEW, (OnFileNew<2, ID_MERGE_COMPARE_TEXT>))
204         ON_COMMAND(ID_FILE_NEW_TABLE, (OnFileNew<2, ID_MERGE_COMPARE_TABLE>))
205         ON_COMMAND(ID_FILE_NEW_HEX, (OnFileNew<2, ID_MERGE_COMPARE_HEX>))
206         ON_COMMAND(ID_FILE_NEW_IMAGE, (OnFileNew<2, ID_MERGE_COMPARE_IMAGE>))
207         ON_COMMAND(ID_FILE_NEW3, (OnFileNew<3, ID_MERGE_COMPARE_TEXT>))
208         ON_COMMAND(ID_FILE_NEW3_TABLE, (OnFileNew<2, ID_MERGE_COMPARE_TABLE>))
209         ON_COMMAND(ID_FILE_NEW3_HEX, (OnFileNew<3, ID_MERGE_COMPARE_HEX>))
210         ON_COMMAND(ID_FILE_NEW3_IMAGE, (OnFileNew<3, ID_MERGE_COMPARE_IMAGE>))
211         ON_COMMAND(ID_TOOLS_FILTERS, OnToolsFilters)
212         ON_COMMAND(ID_VIEW_STATUS_BAR, OnViewStatusBar)
213         ON_UPDATE_COMMAND_UI(ID_VIEW_TAB_BAR, OnUpdateViewTabBar)
214         ON_COMMAND(ID_VIEW_TAB_BAR, OnViewTabBar)
215         ON_UPDATE_COMMAND_UI(ID_VIEW_RESIZE_PANES, OnUpdateResizePanes)
216         ON_COMMAND(ID_VIEW_RESIZE_PANES, OnResizePanes)
217         ON_COMMAND(ID_FILE_OPENPROJECT, OnFileOpenProject)
218         ON_MESSAGE(WM_COPYDATA, OnCopyData)
219         ON_MESSAGE(WM_USER+1, OnUser1)
220         ON_COMMAND(ID_WINDOW_CLOSEALL, OnWindowCloseAll)
221         ON_UPDATE_COMMAND_UI(ID_WINDOW_CLOSEALL, OnUpdateWindowCloseAll)
222         ON_COMMAND(ID_FILE_SAVEPROJECT, OnSaveProject)
223         ON_WM_ACTIVATEAPP()
224         ON_COMMAND_RANGE(ID_TOOLBAR_NONE, ID_TOOLBAR_HUGE, OnToolbarSize)
225         ON_UPDATE_COMMAND_UI_RANGE(ID_TOOLBAR_NONE, ID_TOOLBAR_HUGE, OnUpdateToolbarSize)
226         ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
227         ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
228         ON_COMMAND(ID_HELP_RELEASENOTES, OnHelpReleasenotes)
229         ON_COMMAND(ID_HELP_TRANSLATIONS, OnHelpTranslations)
230         ON_COMMAND(ID_FILE_OPENCONFLICT, OnFileOpenConflict)
231         ON_COMMAND(ID_PLUGINS_LIST, OnPluginsList)
232         ON_UPDATE_COMMAND_UI(ID_STATUS_PLUGIN, OnUpdatePluginName)
233         ON_NOTIFY(TBN_DROPDOWN, AFX_IDW_TOOLBAR, OnToolbarButtonDropDown)
234         ON_COMMAND_RANGE(ID_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnDiffWhitespace)
235         ON_UPDATE_COMMAND_UI_RANGE(ID_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
236         ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_BLANKLINES, OnDiffIgnoreBlankLines)
237         ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_BLANKLINES, OnUpdateDiffIgnoreBlankLines)
238         ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_CASE, OnDiffIgnoreCase)
239         ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_CASE, OnUpdateDiffIgnoreCase)
240         ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_EOL, OnDiffIgnoreEOL)
241         ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_EOL, OnUpdateDiffIgnoreEOL)
242         ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_CODEPAGE, OnDiffIgnoreCP)
243         ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_CODEPAGE, OnUpdateDiffIgnoreCP)
244         ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_COMMENTS, OnDiffIgnoreComments)
245         ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_COMMENTS, OnUpdateDiffIgnoreComments)
246         ON_COMMAND(ID_DIFF_OPTIONS_INCLUDE_SUBFOLDERS, OnIncludeSubfolders)
247         ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_INCLUDE_SUBFOLDERS, OnUpdateIncludeSubfolders)
248         ON_COMMAND_RANGE(ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_DIFF_OPTIONS_COMPMETHOD_SIZE, OnCompareMethod)
249         ON_UPDATE_COMMAND_UI_RANGE(ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_DIFF_OPTIONS_COMPMETHOD_SIZE, OnUpdateCompareMethod)
250         ON_COMMAND_RANGE(ID_MRU_FIRST, ID_MRU_LAST, OnMRUs)
251         ON_UPDATE_COMMAND_UI(ID_MRU_FIRST, OnUpdateNoMRUs)
252         ON_UPDATE_COMMAND_UI(ID_NO_MRU, OnUpdateNoMRUs)
253         ON_COMMAND(ID_FIRSTFILE, OnFirstFile)
254         ON_UPDATE_COMMAND_UI(ID_FIRSTFILE, OnUpdateFirstFile)
255         ON_COMMAND(ID_PREVFILE, OnPrevFile)
256         ON_UPDATE_COMMAND_UI(ID_PREVFILE, OnUpdatePrevFile)
257         ON_COMMAND(ID_NEXTFILE, OnNextFile)
258         ON_UPDATE_COMMAND_UI(ID_NEXTFILE, OnUpdateNextFile)
259         ON_COMMAND(ID_LASTFILE, OnLastFile)
260         ON_UPDATE_COMMAND_UI(ID_LASTFILE, OnUpdateLastFile)
261         ON_COMMAND(ID_ACCEL_QUIT, &CMainFrame::OnAccelQuit)
262         ON_MESSAGE(WMU_CHILDFRAMEADDED, &CMainFrame::OnChildFrameAdded)
263         ON_MESSAGE(WMU_CHILDFRAMEREMOVED, &CMainFrame::OnChildFrameRemoved)
264         ON_MESSAGE(WMU_CHILDFRAMEACTIVATE, &CMainFrame::OnChildFrameActivate)
265         ON_MESSAGE(WMU_CHILDFRAMEACTIVATED, &CMainFrame::OnChildFrameActivated)
266         //}}AFX_MSG_MAP
267 END_MESSAGE_MAP()
268
269 /**
270  * @brief MainFrame statusbar panels/indicators
271  */
272 static UINT StatusbarIndicators[] =
273 {
274         ID_SEPARATOR,           // Plugin name
275         ID_SEPARATOR,           // status line indicator
276         ID_SEPARATOR,           // Merge mode
277         ID_SEPARATOR,           // Diff number
278         ID_INDICATOR_CAPS,      // Caps Lock
279         ID_INDICATOR_NUM,       // Num Lock
280         ID_INDICATOR_OVR,       // Insert
281 };
282
283 /**
284   * @brief Return a const reference to a CMultiDocTemplate's list of documents.
285   */
286 static CPtrList &GetDocList(CMultiDocTemplate *pTemplate)
287 {
288         struct Template : public CMultiDocTemplate
289         {
290         public:
291                 using CMultiDocTemplate::m_docList;
292         };
293         return static_cast<struct Template *>(pTemplate)->m_docList;
294 }
295
296 /////////////////////////////////////////////////////////////////////////////
297 // CMainFrame construction/destruction
298
299 /**
300  * @brief MainFrame constructor. Loads settings from registry.
301  * @todo Preference for logging?
302  */
303 CMainFrame::CMainFrame()
304 : m_bFirstTime(true)
305 , m_pDropHandler(nullptr)
306 , m_bShowErrors(false)
307 , m_lfDiff(Options::Font::Load(GetOptionsMgr(), OPT_FONT_FILECMP))
308 , m_lfDir(Options::Font::Load(GetOptionsMgr(), OPT_FONT_DIRCMP))
309 {
310 }
311
312 CMainFrame::~CMainFrame()
313 {
314         GetOptionsMgr()->SaveOption(OPT_TABBAR_AUTO_MAXWIDTH, m_wndTabBar.GetAutoMaxWidth());
315         strdiff::Close();
316
317         m_arrChild.RemoveAll();
318 }
319
320 const TCHAR CMainFrame::szClassName[] = _T("WinMergeWindowClassW");
321
322 /**
323  * @brief Change MainFrame window class name
324  *        see http://support.microsoft.com/kb/403825/ja
325  */
326 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
327 {
328         WNDCLASS wndcls;
329         BOOL bRes = CMDIFrameWnd::PreCreateWindow(cs);
330         HINSTANCE hInst = AfxGetInstanceHandle();
331         // see if the class already exists
332         if (!::GetClassInfo(hInst, szClassName, &wndcls))
333         {
334                 // get default stuff
335                 ::GetClassInfo(hInst, cs.lpszClass, &wndcls);
336                 // register a new class
337                 wndcls.lpszClassName = szClassName;
338                 wndcls.hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(IDR_MAINFRAME));
339                 ::RegisterClass(&wndcls);
340         }
341         cs.lpszClass = szClassName;
342         return bRes;
343 }
344
345 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
346 {
347         if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
348                 return -1;
349
350         m_wndMDIClient.SubclassWindow(m_hWndMDIClient);
351
352         if (!CreateToolbar())
353         {
354                 TRACE0("Failed to create toolbar\n");
355                 return -1;      // fail to create
356         }
357         
358         if (!m_wndTabBar.Create(this))
359         {
360                 TRACE0("Failed to create tab bar\n");
361                 return -1;      // fail to create
362         }
363         m_wndTabBar.SetAutoMaxWidth(GetOptionsMgr()->GetBool(OPT_TABBAR_AUTO_MAXWIDTH));
364
365         if (!GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR))
366                 CMDIFrameWnd::ShowControlBar(&m_wndTabBar, false, 0);
367
368         if (!m_wndStatusBar.Create(this))
369         {
370                 TRACE0("Failed to create status bar\n");
371                 return -1;      // fail to create
372         }
373         theApp.SetIndicators(m_wndStatusBar, StatusbarIndicators,
374                         static_cast<int>(std::size(StatusbarIndicators)));
375
376         const int lpx = CClientDC(this).GetDeviceCaps(LOGPIXELSX);
377         auto pointToPixel = [lpx](int point) { return MulDiv(point, lpx, 72); };
378         m_wndStatusBar.SetPaneInfo(0, 0, SBPS_STRETCH | SBPS_NOBORDERS, 0);
379         m_wndStatusBar.SetPaneInfo(1, ID_STATUS_PLUGIN, 0, pointToPixel(225));
380         m_wndStatusBar.SetPaneInfo(2, ID_STATUS_MERGINGMODE, 0, pointToPixel(75)); 
381         m_wndStatusBar.SetPaneInfo(3, ID_STATUS_DIFFNUM, 0, pointToPixel(112)); 
382
383         if (!GetOptionsMgr()->GetBool(OPT_SHOW_STATUSBAR))
384                 CMDIFrameWnd::ShowControlBar(&m_wndStatusBar, false, 0);
385
386         m_pDropHandler = new DropHandler(std::bind(&CMainFrame::OnDropFiles, this, std::placeholders::_1));
387         RegisterDragDrop(m_hWnd, m_pDropHandler);
388
389         m_wndMDIClient.ModifyStyleEx(WS_EX_CLIENTEDGE, 0);
390
391         return 0;
392 }
393
394 void CMainFrame::OnTimer(UINT_PTR nIDEvent)
395 {
396         CMDIFrameWnd::OnTimer(nIDEvent);
397
398         if (nIDEvent == IDT_UPDATEMAINMENU)
399         {
400                 KillTimer(nIDEvent);
401
402                 BOOL bMaximized;
403                 MDIGetActive(&bMaximized);
404
405                 // When MDI maximized the window icon is drawn on the menu bar, so we
406                 // need to notify it that our icon has changed.
407                 if (bMaximized)
408                         DrawMenuBar();
409
410                 OnUpdateFrameTitle(FALSE);
411         }
412 }
413
414 void CMainFrame::OnDestroy(void)
415 {
416         if (m_pDropHandler != nullptr)
417                 RevokeDragDrop(m_hWnd);
418 }
419
420 static HMENU GetSubmenu(HMENU menu, int nthSubmenu)
421 {
422         for (int nth = 0, i = 0; i < ::GetMenuItemCount(menu); i++)
423         {
424                 if (::GetSubMenu(menu, i) != nullptr)
425                 {
426                         if (nth == nthSubmenu)
427                                 return ::GetSubMenu(menu, i);
428                         nth++;
429                 }
430         }
431         // error, submenu not found
432         return nullptr;
433 }
434
435 static HMENU GetSubmenu(HMENU mainMenu, UINT nIDFirstMenuItem, int nthSubmenu)
436 {
437         int i;
438         for (i = 0 ; i < ::GetMenuItemCount(mainMenu) ; i++)
439                 if (::GetMenuItemID(::GetSubMenu(mainMenu, i), 0) == nIDFirstMenuItem)
440                         break;
441         HMENU menu = ::GetSubMenu(mainMenu, i);
442         if (!menu)
443                 return nullptr;
444         return GetSubmenu(menu, nthSubmenu);
445 }
446
447 /**
448  * @brief Find the scripts submenu from the main menu
449  * As now this is the first submenu in "Plugins" menu
450  * We find the "Plugins" menu by looking for a menu 
451  *  starting with ID_UNPACK_MANUAL.
452  */
453 HMENU CMainFrame::GetPrediffersSubmenu(HMENU mainMenu)
454 {
455         return GetSubmenu(mainMenu, ID_PLUGINS_LIST, 1);
456 }
457
458 /**
459  * @brief Create a new menu for the view..
460  * @param [in] view Menu view either MENU_DEFAULT, MENU_MERGEVIEW or MENU_DIRVIEW.
461  * @param [in] ID Menu's resource ID.
462  * @return Menu for the view.
463  */
464 HMENU CMainFrame::NewMenu(int view, int ID)
465 {
466         int menu_view;
467         if (m_pMenus[view] == nullptr)
468         {
469                 m_pMenus[view].reset(new BCMenu());
470                 if (m_pMenus[view] == nullptr)
471                         return nullptr;
472         }
473
474         switch (view)
475         {
476         case MENU_MERGEVIEW:
477                 menu_view = MENU_FILECMP;
478                 break;
479         case MENU_DIRVIEW:
480                 menu_view = MENU_FOLDERCMP;
481                 break;
482         case MENU_DEFAULT:
483         default:
484                 menu_view = MENU_MAINFRM;
485                 break;
486         };
487
488         if (!m_pMenus[view]->LoadMenu(ID))
489         {
490                 ASSERT(false);
491                 return nullptr;
492         }
493
494         if (view == MENU_IMGMERGEVIEW)
495         {
496                 m_pImageMenu.reset(new BCMenu);
497                 m_pImageMenu->LoadMenu(MAKEINTRESOURCE(IDR_POPUP_IMGMERGEVIEW));
498                 m_pMenus[view]->InsertMenu(4, MF_BYPOSITION | MF_POPUP, (UINT_PTR)m_pImageMenu->GetSubMenu(0)->m_hMenu, const_cast<TCHAR *>(LoadResString(IDS_IMAGE_MENU).c_str())); 
499         }
500
501         // Load bitmaps to menuitems
502         for (auto& menu_icon: m_MenuIcons)
503         {
504                 if (menu_view == (menu_icon.menusToApply & menu_view))
505                 {
506                         m_pMenus[view]->ModifyODMenu(nullptr, menu_icon.menuitemID, menu_icon.iconResID);
507                 }
508         }
509
510         m_pMenus[view]->LoadToolbar(IDR_MAINFRAME);
511
512         theApp.TranslateMenu(m_pMenus[view]->m_hMenu);
513
514         return (m_pMenus[view]->Detach());
515
516 }
517 /** 
518 * @brief Create new default (CMainFrame) menu.
519 */
520 HMENU CMainFrame::NewDefaultMenu(int ID /*=0*/)
521 {
522         if (ID == 0)
523                 ID = IDR_MAINFRAME;
524         return NewMenu( MENU_DEFAULT, ID );
525 }
526
527 /**
528  * @brief Create new File compare (CMergeEditView) menu.
529  */
530 HMENU CMainFrame::NewMergeViewMenu()
531 {
532         return NewMenu( MENU_MERGEVIEW, IDR_MERGEDOCTYPE);
533 }
534
535 /**
536  * @brief Create new Dir compare (CDirView) menu
537  */
538 HMENU CMainFrame::NewDirViewMenu()
539 {
540         return NewMenu(MENU_DIRVIEW, IDR_DIRDOCTYPE );
541 }
542
543 /**
544  * @brief Create new File compare (CHexMergeView) menu.
545  */
546 HMENU CMainFrame::NewHexMergeViewMenu()
547 {
548         return NewMenu( MENU_HEXMERGEVIEW, IDR_MERGEDOCTYPE);
549 }
550
551 /**
552  * @brief Create new Image compare (CImgMergeView) menu.
553  */
554 HMENU CMainFrame::NewImgMergeViewMenu()
555 {
556         return NewMenu( MENU_IMGMERGEVIEW, IDR_MERGEDOCTYPE);
557 }
558
559 /**
560  * @brief Create new File compare (COpenView) menu.
561  */
562 HMENU CMainFrame::NewOpenViewMenu()
563 {
564         return NewMenu( MENU_OPENVIEW, IDR_MAINFRAME);
565 }
566
567 /**
568  * @brief This handler ensures that the popup menu items are drawn correctly.
569  */
570 void CMainFrame::OnMeasureItem(int nIDCtl,
571         LPMEASUREITEMSTRUCT lpMeasureItemStruct)
572 {
573         bool setflag = false;
574         if (lpMeasureItemStruct->CtlType == ODT_MENU)
575         {
576                 if (IsMenu(reinterpret_cast<HMENU>(static_cast<uintptr_t>(lpMeasureItemStruct->itemID))))
577                 {
578                         CMenu* cmenu =
579                                 CMenu::FromHandle(reinterpret_cast<HMENU>(static_cast<uintptr_t>(lpMeasureItemStruct->itemID)));
580
581                         if (m_pMenus[MENU_DEFAULT]->IsMenu(cmenu))
582                         {
583                                 m_pMenus[MENU_DEFAULT]->MeasureItem(lpMeasureItemStruct);
584                                 setflag = true;
585                         }
586                 }
587         }
588
589         if (!setflag)
590                 CMDIFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
591 }
592
593 /**
594  * @brief This handler ensures that keyboard shortcuts work.
595  */
596 LRESULT CMainFrame::OnMenuChar(UINT nChar, UINT nFlags, 
597         CMenu* pMenu) 
598 {
599         LRESULT lresult;
600         if(m_pMenus[MENU_DEFAULT]->IsMenu(pMenu))
601                 lresult=BCMenu::FindKeyboardShortcut(nChar, nFlags, pMenu);
602         else
603                 lresult=CMDIFrameWnd::OnMenuChar(nChar, nFlags, pMenu);
604         return lresult;
605 }
606
607 /**
608  * @brief This handler updates the menus from time to time.
609  */
610 void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) 
611 {
612         if (!bSysMenu)
613         {
614                 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
615                 {
616                         PathContext paths;
617                         for (int i = 0; i < pMergeDoc->GetFileCount(); ++i)
618                                 paths.SetPath(i, pMergeDoc->GetPath(i));
619                         String filteredFilenames = strutils::join(paths.begin(), paths.end(), _T("|"));
620                         unsigned topMenuId = pPopupMenu->GetMenuItemID(0);
621                         if (topMenuId == ID_NO_PREDIFFER)
622                         {
623                                 UpdatePrediffersMenu();
624                         }
625                         else if (topMenuId == ID_MERGE_COMPARE_TEXT)
626                         {
627                                 CMenu* pMenu = pPopupMenu;
628                                 // empty the menu
629                                 for (int i = pMenu->GetMenuItemCount() - 1; i > (ID_MERGE_COMPARE_IMAGE - ID_MERGE_COMPARE_TEXT); --i)
630                                         pMenu->DeleteMenu(i, MF_BYPOSITION);
631
632                                 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
633                         }
634                         else if (topMenuId == ID_NO_EDIT_SCRIPTS)
635                         {
636                                 CMenu* pMenu = pPopupMenu;
637                                 ASSERT(pMenu != nullptr);
638
639                                 // empty the menu
640                                 int i = pMenu->GetMenuItemCount();
641                                 while (i--)
642                                         pMenu->DeleteMenu(0, MF_BYPOSITION);
643
644                                 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::EditorScriptEventNames, false, ID_SCRIPT_FIRST);
645                         }
646                         else if (topMenuId == ID_PLUGINS_LIST)
647                         {
648                                 for (int j = 0; j < 2; j++)
649                                 {
650                                         CMenu* pMenu = pPopupMenu->GetSubMenu((j == 0) ? 8 : (pPopupMenu->GetMenuItemCount() - 4));
651                                         ASSERT(pMenu != nullptr);
652
653                                         // empty the menu
654                                         int i = pMenu->GetMenuItemCount();
655                                         while (i--)
656                                                 pMenu->DeleteMenu(0, MF_BYPOSITION);
657
658                                         if (j == 0)
659                                                 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::UnpackerEventNames, false, ID_UNPACKERS_FIRST);
660                                         else
661                                                 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::EditorScriptEventNames, false, ID_SCRIPT_FIRST);
662                                 }
663                         }
664                 }
665
666                 CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
667                 if (BCMenu::IsMenu(pPopupMenu))
668                 {
669                         BCMenu::UpdateMenu(pPopupMenu);
670                 }
671         }
672         else
673         {
674                 CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
675         }
676 }
677
678 /////////////////////////////////////////////////////////////////////////////
679 // CMainFrame message handlers
680
681 void CMainFrame::OnFileOpen() 
682 {
683         DoFileOrFolderOpen();
684 }
685
686 /**
687  * @brief Check for BOM, and also, if bGuessEncoding, try to deduce codepage
688  *
689  * Unpacks info from FileLocation & delegates all work to codepage_detect module
690  */
691 static void
692 FileLocationGuessEncodings(FileLocation & fileloc, int iGuessEncoding)
693 {
694         fileloc.encoding = codepage_detect::Guess(fileloc.filepath, iGuessEncoding);
695 }
696
697 bool CMainFrame::ShowAutoMergeDoc(CDirDoc * pDirDoc,
698         int nFiles, const FileLocation ifileloc[],
699         const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
700         const PackingInfo * infoUnpacker /*= nullptr*/, OpenTextFileParams* pOpenParams /*= nullptr*/)
701 {
702         ASSERT(pDirDoc != nullptr);
703
704         if (sReportFile.empty() && pDirDoc->CompareFilesIfFilesAreLarge(nFiles, ifileloc))
705                 return false;
706
707         String unpackedFileExtension;
708         if (infoUnpacker && GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
709         {
710                 std::vector<String> filepaths(nFiles);
711                 std::transform(ifileloc, ifileloc + nFiles, filepaths.begin(),
712                         [](auto& file) { return file.filepath; });
713                 String filteredFilenames = strutils::join(filepaths.begin(), filepaths.end(), _T("|"));
714                 unpackedFileExtension = infoUnpacker->GetUnpackedFileExtension(filteredFilenames);
715         }
716         FileFilterHelper filterImg, filterBin;
717         filterImg.UseMask(true);
718         filterImg.SetMask(GetOptionsMgr()->GetString(OPT_CMP_IMG_FILEPATTERNS));
719         filterBin.UseMask(true);
720         filterBin.SetMask(GetOptionsMgr()->GetString(OPT_CMP_BIN_FILEPATTERNS));
721         for (int pane = 0; pane < nFiles; ++pane)
722         {
723                 String filepath = ifileloc[pane].filepath + unpackedFileExtension;
724                 if (filterImg.includeFile(filepath) && CImgMergeFrame::IsLoadable())
725                         return ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
726                 else if (filterBin.includeFile(filepath) && CHexMergeView::IsLoadable())
727                         return ShowHexMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
728         }
729         return ShowTextOrTableMergeDoc({}, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams);
730 }
731
732 bool CMainFrame::ShowMergeDoc(UINT nID, CDirDoc* pDirDoc,
733         int nFiles, const FileLocation ifileloc[],
734         const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
735         const PackingInfo* infoUnpacker /*= nullptr*/, OpenTextFileParams* pOpenParams /*= nullptr*/)
736 {
737         switch (nID)
738         {
739         case ID_MERGE_COMPARE_TEXT:
740                 return ShowTextMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
741                         strDesc, sReportFile, infoUnpacker, pOpenParams);
742         case ID_MERGE_COMPARE_TABLE:
743                 return ShowTableMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
744                         strDesc, sReportFile, infoUnpacker, pOpenParams);
745         case ID_MERGE_COMPARE_HEX:
746                 return ShowHexMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
747                         strDesc, sReportFile, infoUnpacker);
748         case ID_MERGE_COMPARE_IMAGE:
749                 return ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
750                         strDesc, sReportFile, infoUnpacker);
751         default:
752                 return ShowAutoMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
753                         strDesc, sReportFile, infoUnpacker, pOpenParams);
754         }
755 }
756
757 std::array<bool, 3> GetROFromFlags(int nFiles, const DWORD dwFlags[])
758 {
759         std::array<bool, 3> bRO = { false, false, false };
760         for (int pane = 0; pane < nFiles; pane++)
761         {
762                 if (dwFlags)
763                         bRO[pane] = ((dwFlags[pane] & FFILEOPEN_READONLY) > 0);
764         }
765         return bRO;
766 }
767
768 int GetActivePaneFromFlags(int nFiles, const DWORD dwFlags[])
769 {
770         int nActivePane = -1;
771         for (int pane = 0; pane < nFiles; ++pane)
772         {
773                 if (dwFlags && (dwFlags[pane] & FFILEOPEN_SETFOCUS))
774                         nActivePane = pane;
775         }
776         return nActivePane;
777 }
778
779 /**
780  * @brief Creates new MergeDoc instance and shows documents.
781  * @param [in] pDirDoc Dir compare document to create a new Merge document for.
782  * @param [in] ifilelocLeft Left side file location info.
783  * @param [in] ifilelocRight Right side file location info.
784  * @param [in] dwLeftFlags Left side flags.
785  * @param [in] dwRightFlags Right side flags.
786  * @param [in] infoUnpacker Plugin info.
787  * @return success/failure
788  */
789 bool CMainFrame::ShowTextOrTableMergeDoc(std::optional<bool> table, CDirDoc * pDirDoc,
790         int nFiles, const FileLocation ifileloc[],
791         const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
792         const PackingInfo * infoUnpacker /*= nullptr*/, OpenTextFileParams* pOpenParams /*= nullptr*/)
793 {
794         if (m_pMenus[MENU_MERGEVIEW] == nullptr)
795                 theApp.m_pDiffTemplate->m_hMenuShared = NewMergeViewMenu();
796         CMergeDoc * pMergeDoc = GetMergeDocForDiff<CMergeDoc>(theApp.m_pDiffTemplate, pDirDoc, nFiles, false);
797
798         // Make local copies, so we can change encoding if we guess it below
799         FileLocation fileloc[3];
800         std::copy_n(ifileloc, nFiles, fileloc);
801
802         ASSERT(pMergeDoc != nullptr);           // must ASSERT to get an answer to the question below ;-)
803         if (pMergeDoc == nullptr)
804                 return false; // when does this happen ?
805
806         // if an unpacker is selected, it must be used during LoadFromFile
807         // MergeDoc must memorize it for SaveToFile
808         // Warning : this unpacker may differ from the pDirDoc one
809         // (through menu : "Plugins"->"Open with unpacker")
810         pMergeDoc->SetUnpacker(infoUnpacker);
811
812         // detect codepage
813         int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
814         for (int pane = 0; pane < nFiles; pane++)
815         {
816                 if (fileloc[pane].encoding.m_unicoding == -1)
817                         fileloc[pane].encoding.m_unicoding = ucr::NONE;
818                 if (fileloc[pane].encoding.m_unicoding == ucr::NONE && fileloc[pane].encoding.m_codepage == -1)
819                 {
820                         FileLocationGuessEncodings(fileloc[pane], iGuessEncodingType);
821                 }
822         }
823
824         pMergeDoc->SetEnableTableEditing(table);
825         if (pOpenParams && table.value_or(false))
826         {
827                 CMergeDoc::TableProps props = CMergeDoc::GetTablePropertiesByFileName(
828                         pOpenParams->m_fileExt.empty() ? fileloc[0].filepath : pOpenParams->m_fileExt, true, false);
829                 props.delimiter = pOpenParams->m_tableDelimiter.value_or(props.delimiter);
830                 props.quote = pOpenParams->m_tableQuote.value_or(props.quote);
831                 props.allowNewlinesInQuotes = pOpenParams->m_tableAllowNewlinesInQuotes.value_or(props.allowNewlinesInQuotes);
832                 pMergeDoc->SetTableProperties(props);
833         }
834
835         // Note that OpenDocs() takes care of closing compare window when needed.
836         bool bResult = pMergeDoc->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc);
837         if (bResult)
838         {
839                 if (CMergeEditFrame *pFrame = pMergeDoc->GetParentFrame())
840                         if (!pFrame->IsActivated())
841                                 pFrame->InitialUpdateFrame(pMergeDoc, true);
842         }
843         else
844         {
845                 return false;
846         }
847
848         if (pOpenParams && !pOpenParams->m_fileExt.empty())
849                 pMergeDoc->SetTextType(pOpenParams->m_fileExt);
850
851         for (int pane = 0; pane < nFiles; pane++)
852         {
853                 if (dwFlags)
854                 {
855                         bool bModified = (dwFlags[pane] & FFILEOPEN_MODIFIED) > 0;
856                         if (bModified)
857                         {
858                                 pMergeDoc->m_ptBuf[pane]->SetModified(true);
859                                 pMergeDoc->UpdateHeaderPath(pane);
860                         }
861                         if (dwFlags[pane] & FFILEOPEN_AUTOMERGE)
862                         {
863                                 pMergeDoc->DoAutoMerge(pane);
864                         }
865                 }
866         }
867
868         pMergeDoc->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags), pOpenParams ? pOpenParams->m_line : -1, true);
869
870         if (!sReportFile.empty())
871                 pMergeDoc->GenerateReport(sReportFile);
872
873         return true;
874 }
875
876 bool CMainFrame::ShowTextMergeDoc(CDirDoc* pDirDoc,
877         int nFiles, const FileLocation ifileloc[],
878         const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
879         const PackingInfo* infoUnpacker /*= nullptr*/, OpenTextFileParams* pOpenParams /*= nullptr*/)
880 {
881         return ShowTextOrTableMergeDoc(false, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams); 
882 }
883
884 bool CMainFrame::ShowTableMergeDoc(CDirDoc* pDirDoc,
885         int nFiles, const FileLocation ifileloc[],
886         const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
887         const PackingInfo* infoUnpacker /*= nullptr*/, OpenTextFileParams* pOpenParams /*= nullptr*/)
888 {
889         return ShowTextOrTableMergeDoc(true, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams);
890 }
891
892 bool CMainFrame::ShowHexMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
893         const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
894         const PackingInfo * infoUnpacker /*= nullptr*/)
895 {
896         if (m_pMenus[MENU_HEXMERGEVIEW] == nullptr)
897                 theApp.m_pHexMergeTemplate->m_hMenuShared = NewHexMergeViewMenu();
898         CHexMergeDoc *pHexMergeDoc = GetMergeDocForDiff<CHexMergeDoc>(theApp.m_pHexMergeTemplate, pDirDoc, nFiles);
899         if (pHexMergeDoc == nullptr)
900                 return false;
901
902         pHexMergeDoc->SetUnpacker(infoUnpacker);
903
904         if (!pHexMergeDoc->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc))
905                 return false;
906
907         pHexMergeDoc->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags));
908         
909         if (!sReportFile.empty())
910                 pHexMergeDoc->GenerateReport(sReportFile);
911
912         return true;
913 }
914
915 bool CMainFrame::ShowImgMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
916         const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
917         const PackingInfo * infoUnpacker /*= nullptr*/)
918 {
919         CImgMergeFrame *pImgMergeFrame = new CImgMergeFrame();
920         if (!CImgMergeFrame::menu.m_hMenu)
921                 CImgMergeFrame::menu.m_hMenu = NewImgMergeViewMenu();
922         pImgMergeFrame->SetSharedMenu(CImgMergeFrame::menu.m_hMenu);
923         pImgMergeFrame->SetUnpacker(infoUnpacker);
924         pImgMergeFrame->SetDirDoc(pDirDoc);
925         pDirDoc->AddMergeDoc(pImgMergeFrame);
926                 
927         if (!pImgMergeFrame->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc, this))
928                 return false;
929
930         for (int pane = 0; pane < nFiles; pane++)
931         {
932                 if (dwFlags && (dwFlags[pane] & FFILEOPEN_AUTOMERGE))
933                         pImgMergeFrame->DoAutoMerge(pane);
934         }
935
936         pImgMergeFrame->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags));
937
938         if (!sReportFile.empty())
939                 pImgMergeFrame->GenerateReport(sReportFile);
940
941         return true;
942 }
943
944 bool CMainFrame::ShowTextMergeDoc(CDirDoc* pDirDoc, int nBuffers, const String text[],
945                 const String strDesc[], const String& strFileExt, OpenTextFileParams* pOpenParams /*= nullptr*/)
946 {
947         FileLocation fileloc[3];
948         DWORD dwFlags[3] = {};
949         CDirDoc* pDirDoc2 = pDirDoc->GetMainView() ? pDirDoc :
950                 static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
951         for (int nBuffer = 0; nBuffer < nBuffers; ++nBuffer)
952         {
953                 TempFilePtr wTemp(new TempFile());
954                 String workFile = wTemp->Create(_T("text_"), strFileExt);
955                 m_tempFiles.push_back(wTemp);
956                 wTemp->Create(_T(""), strFileExt);
957                 UniStdioFile file;
958                 if (file.OpenCreateUtf8(workFile))
959                 {
960                         file.WriteString(text[nBuffer]);
961                 }
962                 fileloc[nBuffer].setPath(workFile);
963         }
964         return ShowTextMergeDoc(pDirDoc2, nBuffers, fileloc, dwFlags, strDesc, _T(""), nullptr, pOpenParams);
965 }
966
967 /**
968  * @brief Show GNU licence information in notepad (local file) or in Web Browser
969  */
970 void CMainFrame::OnHelpGnulicense() 
971 {
972         const String spath = paths::ConcatPath(env::GetProgPath(), LicenseFile);
973         shell::OpenFileOrUrl(spath.c_str(), LicenceUrl);
974 }
975
976 /**
977  * @brief Opens Options-dialog and saves changed options
978  */
979 void CMainFrame::OnOptions() 
980 {
981         // Using singleton shared syntax colors
982         CPreferencesDlg dlg(GetOptionsMgr(), theApp.GetMainSyntaxColors());
983         INT_PTR rv = dlg.DoModal();
984
985         if (rv == IDOK)
986         {
987                 LANGID lang = static_cast<LANGID>(GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE));
988                 if (lang != theApp.m_pLangDlg->GetLangId())
989                 {
990                         theApp.m_pLangDlg->SetLanguage(lang, true);
991         
992                         // Update status bar inicator texts
993                         theApp.SetIndicators(m_wndStatusBar, 0, 0);
994         
995                         // Update the current menu
996                         ReloadMenu();
997         
998                         // update the title text of the document
999                         UpdateDocTitle();
1000
1001                         UpdateResources();
1002                 }
1003
1004                 // Set new temporary path
1005                 theApp.SetupTempPath();
1006
1007                 // Set new filterpath
1008                 String filterPath = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
1009                 theApp.m_pGlobalFileFilter->SetUserFilterPath(filterPath);
1010
1011                 CCrystalTextView::RENDERING_MODE nRenderingMode = static_cast<CCrystalTextView::RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
1012                 CCrystalTextView::SetRenderingModeDefault(nRenderingMode);
1013
1014                 theApp.UpdateCodepageModule();
1015
1016                 strdiff::SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
1017
1018                 // make an attempt at rescanning any open diff sessions
1019                 ApplyDiffOptions();
1020
1021                 // Update all dirdoc settings
1022                 for (auto pDirDoc : GetAllDirDocs())
1023                         pDirDoc->RefreshOptions();
1024                 for (auto pOpenDoc : GetAllOpenDocs())
1025                         pOpenDoc->RefreshOptions();
1026                 for (auto pMergeDoc : GetAllHexMergeDocs())
1027                         pMergeDoc->RefreshOptions();
1028         }
1029 }
1030
1031 static bool AddToRecentDocs(const PathContext& paths, const unsigned flags[], bool recurse, const String& filter)
1032 {
1033         String params, title;
1034         for (int nIndex = 0; nIndex < paths.GetSize(); ++nIndex)
1035         {
1036                 if (flags && (flags[nIndex] & FFILEOPEN_READONLY))
1037                 {
1038                         switch (nIndex)
1039                         {
1040                         case 0: params += _T("/wl "); break;
1041                         case 1: params += paths.GetSize() == 2 ? _T("/wr ") : _T("/wm "); break;
1042                         case 2: params += _T("/wr "); break;
1043                         }
1044                 }
1045                 params += _T("\"") + paths[nIndex] + _T("\" ");
1046
1047                 String path = paths[nIndex];
1048                 paths::normalize(path);
1049                 title += paths::FindFileName(path);
1050                 if (nIndex < paths.GetSize() - 1)
1051                         title += _T(" - ");
1052         }
1053         if (recurse)
1054                 params += _T("/r ");
1055         if (!filter.empty())
1056                 params += _T("/f \"") + filter + _T("\" ");
1057
1058         Concurrent::CreateTask([params, title](){
1059                         CoInitialize(nullptr);
1060                         JumpList::AddToRecentDocs(_T(""), params, title, params, 0);
1061                         CoUninitialize();
1062                         return 0;
1063                 });
1064         return true;
1065 }
1066 /**
1067  * @brief Begin a diff: open dirdoc if it is directories, else open a mergedoc for editing.
1068  * @param [in] pszLeft Left-side path.
1069  * @param [in] pszRight Right-side path.
1070  * @param [in] dwLeftFlags Left-side flags.
1071  * @param [in] dwRightFlags Right-side flags.
1072  * @param [in] bRecurse Do we run recursive (folder) compare?
1073  * @param [in] pDirDoc Dir compare document to use.
1074  * @param [in] infoUnpacker Unpacker plugin name.
1075  * @param [in] infoPrediffer Prediffer plugin name.
1076  * @return `true` if opening files and compare succeeded, `false` otherwise.
1077  */
1078 bool CMainFrame::DoFileOrFolderOpen(const PathContext * pFiles /*= nullptr*/,
1079         const DWORD dwFlags[] /*= nullptr*/, const String strDesc[] /*= nullptr*/, const String& sReportFile /*= T("")*/,
1080         bool bRecurse /*= false*/, CDirDoc* pDirDoc/*= nullptr*/,
1081         const PackingInfo *infoUnpacker /*= nullptr*/, const PrediffingInfo *infoPrediffer /*= nullptr*/,
1082         UINT nID /*= 0*/, OpenTextFileParams *pOpenParams /*= nullptr*/)
1083 {
1084         if (pDirDoc != nullptr && !pDirDoc->CloseMergeDocs())
1085                 return false;
1086
1087         FileTransform::AutoUnpacking = GetOptionsMgr()->GetBool(OPT_PLUGINS_UNPACKER_MODE);
1088         FileTransform::AutoPrediffing = GetOptionsMgr()->GetBool(OPT_PLUGINS_PREDIFFER_MODE);
1089
1090         Merge7zFormatMergePluginScope scope(infoUnpacker);
1091
1092         PathContext tFiles;
1093         if (pFiles != nullptr)
1094                 tFiles = *pFiles;
1095         bool bRO[3] = {0};
1096         if (dwFlags)
1097         {
1098                 bRO[0] = (dwFlags[0] & FFILEOPEN_READONLY) != 0;
1099                 bRO[1] = (dwFlags[1] & FFILEOPEN_READONLY) != 0;
1100                 bRO[2] = (dwFlags[2] & FFILEOPEN_READONLY) != 0;
1101         };
1102
1103         // pop up dialog unless arguments exist (and are compatible)
1104         paths::PATH_EXISTENCE pathsType = paths::GetPairComparability(tFiles, IsArchiveFile);
1105         if (pathsType == paths::DOES_NOT_EXIST)
1106         {
1107                 if (m_pMenus[MENU_OPENVIEW] == nullptr)
1108                         theApp.m_pOpenTemplate->m_hMenuShared = NewOpenViewMenu();
1109                 COpenDoc *pOpenDoc = static_cast<COpenDoc *>(theApp.m_pOpenTemplate->CreateNewDocument());
1110                 if (dwFlags)
1111                 {
1112                         pOpenDoc->m_dwFlags[0] = dwFlags[0];
1113                         pOpenDoc->m_dwFlags[1] = dwFlags[1];
1114                         pOpenDoc->m_dwFlags[2] = dwFlags[2];
1115                 }
1116                 pOpenDoc->m_files = tFiles;
1117                 pOpenDoc->m_bRecurse = bRecurse;
1118                 if (infoUnpacker)
1119                         pOpenDoc->m_strUnpackerPipeline = infoUnpacker->GetPluginPipeline();
1120                 CFrameWnd *pFrame = theApp.m_pOpenTemplate->CreateNewFrame(pOpenDoc, nullptr);
1121                 theApp.m_pOpenTemplate->InitialUpdateFrame(pFrame, pOpenDoc);
1122                 return true;
1123         }
1124         else
1125         {
1126                 // Add trailing '\' for directories if its missing
1127                 if (pathsType == paths::IS_EXISTING_DIR)
1128                 {
1129                         if (!paths::EndsWithSlash(tFiles[0]) && !IsArchiveFile(tFiles[0]))
1130                                 tFiles[0] = paths::AddTrailingSlash(tFiles[0]);
1131                         if (!paths::EndsWithSlash(tFiles[1]) && !IsArchiveFile(tFiles[1]))
1132                                 tFiles[1] = paths::AddTrailingSlash(tFiles[1]);
1133                         if (tFiles.GetSize() == 3 && !paths::EndsWithSlash(tFiles[2]) && !IsArchiveFile(tFiles[1]))
1134                                 tFiles[2] = paths::AddTrailingSlash(tFiles[2]);
1135                 }
1136
1137                 //save the MRU left and right files.
1138                 if (dwFlags)
1139                 {
1140                         if (!(dwFlags[0] & FFILEOPEN_NOMRU))
1141                                 addToMru(tFiles[0].c_str(), _T("Files\\Left"));
1142                         if (!(dwFlags[1] & FFILEOPEN_NOMRU))
1143                                 addToMru(tFiles[1].c_str(), _T("Files\\Right"));
1144                         if (tFiles.GetSize() == 3 && !(dwFlags[2] & FFILEOPEN_NOMRU))
1145                                 addToMru(tFiles[2].c_str(), _T("Files\\Option"));
1146                 }
1147         }
1148
1149         CTempPathContext *pTempPathContext = nullptr;
1150         if (nID == 0 && pathsType == paths::IS_EXISTING_DIR)
1151         {
1152                 DecompressResult res= DecompressArchive(m_hWnd, tFiles);
1153                 if (res.pTempPathContext)
1154                 {
1155                         pathsType = res.pathsType;
1156                         tFiles = res.files;
1157                         pTempPathContext = res.pTempPathContext;
1158                 }
1159         }
1160
1161         // Determine if we want a new dirview open, now that we know if it was
1162         // an archive. Don't open a new dirview if we are comparing files.
1163         if (pDirDoc == nullptr)
1164         {
1165                 if (nID == 0 && pathsType == paths::IS_EXISTING_DIR)
1166                 {
1167                         CDirDoc::m_nDirsTemp = tFiles.GetSize();
1168                         if (m_pMenus[MENU_DIRVIEW] == nullptr)
1169                                 theApp.m_pDirTemplate->m_hMenuShared = NewDirViewMenu();
1170                         pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->OpenDocumentFile(nullptr));
1171                 }
1172                 else
1173                 {
1174                         pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
1175                 }
1176         }
1177
1178         // open the diff
1179         if (nID == 0 && pathsType == paths::IS_EXISTING_DIR)
1180         {
1181                 if (pDirDoc != nullptr)
1182                 {
1183                         // Anything that can go wrong inside InitCompare() will yield an
1184                         // exception. There is no point in checking return value.
1185                         pDirDoc->InitCompare(tFiles, bRecurse, pTempPathContext);
1186
1187                         pDirDoc->SetReportFile(sReportFile);
1188                         pDirDoc->SetDescriptions(strDesc);
1189                         pDirDoc->SetTitle(nullptr);
1190                         for (int nIndex = 0; nIndex < tFiles.GetSize(); nIndex++)
1191                                 pDirDoc->SetReadOnly(nIndex, bRO[nIndex]);
1192
1193                         pDirDoc->Rescan();
1194                 }
1195         }
1196         else
1197         {               
1198                 FileLocation fileloc[3];
1199
1200                 for (int nPane = 0; nPane < tFiles.GetSize(); nPane++)
1201                         fileloc[nPane].setPath(tFiles[nPane]);
1202
1203                 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
1204                 {
1205                         String strBothFilenames = strutils::join(tFiles.begin(), tFiles.end(), _T("|"));
1206                         pDirDoc->GetPluginManager().SetPrediffer(strBothFilenames, infoPrediffer->GetPluginPipeline());
1207                 }
1208
1209                 ShowMergeDoc(nID, pDirDoc, tFiles.GetSize(), fileloc, dwFlags, strDesc, sReportFile,
1210                                 infoUnpacker, pOpenParams);
1211         }
1212
1213         if (pFiles != nullptr && (!dwFlags || !(dwFlags[0] & FFILEOPEN_NOMRU)))
1214         {
1215                 String filter = GetOptionsMgr()->GetString(OPT_FILEFILTER_CURRENT);
1216                 AddToRecentDocs(*pFiles, (unsigned *)dwFlags, bRecurse, filter);
1217         }
1218
1219         return true;
1220 }
1221
1222 bool CMainFrame::DoFileOpen(UINT nID, const PathContext* pFiles /*= nullptr*/,
1223         const DWORD dwFlags[] /*= nullptr*/, const String strDesc[] /*= nullptr*/,
1224         const String& sReportFile /*= _T("")*/,
1225         const PackingInfo *infoUnpacker /*= nullptr*/, const PrediffingInfo *infoPrediffer /*= nullptr*/,
1226         OpenTextFileParams *pOpenParams /*= nullptr*/)
1227 {
1228         CDirDoc* pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
1229         FileLocation fileloc[3];
1230         for (int pane = 0; pane < pFiles->GetSize(); pane++)
1231                 fileloc[pane].setPath((*pFiles)[pane]);
1232         if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
1233                 pDirDoc->GetPluginManager().SetPrediffer(_T("|"), infoPrediffer->GetPluginPipeline());
1234         return ShowMergeDoc(nID, pDirDoc, pFiles->GetSize(), fileloc,
1235                 dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams);
1236 }
1237
1238 void CMainFrame::UpdateFont(FRAMETYPE frame)
1239 {
1240         if (frame == FRAME_FOLDER)
1241         {
1242                 for (auto pDoc : GetAllDirDocs())
1243                 {
1244                         if (pDoc != nullptr)
1245                         {
1246                                 CDirView *pView = pDoc->GetMainView();
1247                                 if (pView != nullptr)
1248                                         pView->SetFont(m_lfDir);
1249                         }
1250                 }
1251         }
1252         else
1253         {
1254                 for (auto pDoc : GetAllMergeDocs())
1255                 {
1256                         CMergeDoc *pMergeDoc = dynamic_cast<CMergeDoc *>(pDoc);
1257                         if (pMergeDoc != nullptr)
1258                                 for (auto& pView: pMergeDoc->GetViewList())
1259                                         pView->SetFont(m_lfDiff);
1260                 }
1261         }
1262 }
1263
1264 /**
1265  * @brief Select font for Merge/Dir view
1266  * 
1267  * Shows font selection dialog to user, sets current font and saves
1268  * selected font properties to registry. Selects fon type to active
1269  * view (Merge/Dir compare). If there is no open views, then font
1270  * is selected for Merge view (for example user may want to change to
1271  * unicode font before comparing files).
1272  */
1273 void CMainFrame::OnViewSelectfont() 
1274 {
1275         FRAMETYPE frame = GetFrameType(GetActiveFrame());
1276         CHOOSEFONT cf = { sizeof CHOOSEFONT };
1277         LOGFONT *lf = nullptr;
1278         cf.Flags = CF_INITTOLOGFONTSTRUCT|CF_FORCEFONTEXIST|CF_SCREENFONTS;
1279         if (frame == FRAME_FILE)
1280                 cf.Flags |= CF_FIXEDPITCHONLY; // Only fixed-width fonts for merge view
1281
1282         // CF_FIXEDPITCHONLY = 0x00004000L
1283         // in case you are a developer and want to disable it to test with, eg, a Chinese capable font
1284         if (frame == FRAME_FOLDER)
1285                 lf = &m_lfDir;
1286         else
1287                 lf = &m_lfDiff;
1288
1289         cf.lpLogFont = lf;
1290
1291         if (ChooseFont(&cf))
1292         {
1293                 Options::Font::Save(GetOptionsMgr(), frame == FRAME_FOLDER ? OPT_FONT_DIRCMP : OPT_FONT_FILECMP, lf, true);
1294                 UpdateFont(frame);
1295         }
1296 }
1297
1298 /**
1299  * @brief Use default font for active view type
1300  *
1301  * Disable user-selected font for active view type (Merge/Dir compare).
1302  * If there is no open views, then Merge view font is changed.
1303  */
1304 void CMainFrame::OnViewUsedefaultfont() 
1305 {
1306         FRAMETYPE frame = GetFrameType(GetActiveFrame());
1307
1308         if (frame == FRAME_FOLDER)
1309         {
1310                 Options::Font::Reset(GetOptionsMgr(), OPT_FONT_DIRCMP);
1311                 m_lfDir = Options::Font::Load(GetOptionsMgr(), OPT_FONT_DIRCMP);
1312                 Options::Font::Save(GetOptionsMgr(), OPT_FONT_DIRCMP, &m_lfDir, false);
1313         }
1314         else
1315         {
1316                 Options::Font::Reset(GetOptionsMgr(), OPT_FONT_FILECMP);
1317                 m_lfDiff = Options::Font::Load(GetOptionsMgr(), OPT_FONT_FILECMP);
1318                 Options::Font::Save(GetOptionsMgr(), OPT_FONT_FILECMP, &m_lfDiff, false);
1319         }
1320
1321         UpdateFont(frame);
1322 }
1323
1324 /**
1325  * @brief Update any resources necessary after a GUI language change
1326  */
1327 void CMainFrame::UpdateResources()
1328 {
1329         m_wndStatusBar.SetPaneText(0, theApp.LoadString(AFX_IDS_IDLEMESSAGE).c_str());
1330
1331         for (auto pDoc : GetAllDirDocs())
1332                 pDoc->UpdateResources();
1333         for (auto pDoc : GetAllMergeDocs())
1334                 pDoc->UpdateResources();
1335         for (auto pDoc : GetAllOpenDocs())
1336                 pDoc->UpdateResources();
1337         for (auto pFrame: GetAllImgMergeFrames())
1338                 pFrame->UpdateResources();
1339 }
1340
1341 /**
1342  * @brief Open WinMerge help.
1343  *
1344  * If local HTMLhelp file is found, open it, otherwise open HTML page from web.
1345  */
1346 void CMainFrame::OnHelpContents()
1347 {
1348         theApp.ShowHelp();
1349 }
1350
1351 /**
1352  * @brief Handle translation of default messages on the status bar
1353  */
1354 void CMainFrame::GetMessageString(UINT nID, CString& rMessage) const
1355 {
1356         // load appropriate string
1357         const String s = theApp.LoadString(nID);
1358         if (s.length() > 0)
1359                 AfxExtractSubString(rMessage, s.c_str(), 0);
1360 }
1361
1362 void CMainFrame::ActivateFrame(int nCmdShow) 
1363 {
1364         if (!m_bFirstTime)
1365         {
1366                 CMDIFrameWnd::ActivateFrame(nCmdShow);
1367                 return;
1368         }
1369
1370         m_bFirstTime = false;
1371
1372         WINDOWPLACEMENT wp = {};
1373         wp.length = sizeof(WINDOWPLACEMENT);
1374         GetWindowPlacement(&wp);
1375         wp.rcNormalPosition.left=theApp.GetProfileInt(_T("Settings"), _T("MainLeft"),0);
1376         wp.rcNormalPosition.top=theApp.GetProfileInt(_T("Settings"), _T("MainTop"),0);
1377         wp.rcNormalPosition.right=theApp.GetProfileInt(_T("Settings"), _T("MainRight"),0);
1378         wp.rcNormalPosition.bottom=theApp.GetProfileInt(_T("Settings"), _T("MainBottom"),0);
1379         if (nCmdShow != SW_MINIMIZE && theApp.GetProfileInt(_T("Settings"), _T("MainMax"), FALSE))
1380                 wp.showCmd = SW_MAXIMIZE;
1381         else
1382                 wp.showCmd = nCmdShow;
1383
1384         CRect dsk_rc,rc(wp.rcNormalPosition);
1385
1386         dsk_rc.left = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
1387         dsk_rc.top = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
1388         dsk_rc.right = dsk_rc.left + ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
1389         dsk_rc.bottom = dsk_rc.top + ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
1390         if (rc.Width() != 0 && rc.Height() != 0)
1391         {
1392                 // Ensure top-left corner is on visible area,
1393                 // 20 points margin is added to prevent "lost" window
1394                 CPoint ptTopLeft(rc.TopLeft());
1395                 ptTopLeft += CPoint(20, 20);
1396
1397                 if (dsk_rc.PtInRect(ptTopLeft))
1398                         SetWindowPlacement(&wp);
1399                 else
1400                         CMDIFrameWnd::ActivateFrame(nCmdShow);
1401         }
1402         else
1403                 CMDIFrameWnd::ActivateFrame(nCmdShow);
1404 }
1405
1406 /**
1407  * @brief Called when mainframe is about to be closed.
1408  * This function is called when mainframe is to be closed (not for
1409  * file/compare windows.
1410  */
1411 void CMainFrame::OnClose()
1412 {
1413         if (theApp.GetActiveOperations())
1414                 return;
1415
1416         // Check if there are multiple windows open and ask for closing them
1417         bool bAskClosing = GetOptionsMgr()->GetBool(OPT_ASK_MULTIWINDOW_CLOSE);
1418         if (bAskClosing)
1419         {
1420                 bool quit = AskCloseConfirmation();
1421                 if (!quit)
1422                         return;
1423         }
1424
1425         // Save last selected filter
1426         String filter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1427         GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
1428
1429         // save main window position
1430         WINDOWPLACEMENT wp = {};
1431         wp.length = sizeof(WINDOWPLACEMENT);
1432         GetWindowPlacement(&wp);
1433         theApp.WriteProfileInt(_T("Settings"), _T("MainLeft"),wp.rcNormalPosition.left);
1434         theApp.WriteProfileInt(_T("Settings"), _T("MainTop"),wp.rcNormalPosition.top);
1435         theApp.WriteProfileInt(_T("Settings"), _T("MainRight"),wp.rcNormalPosition.right);
1436         theApp.WriteProfileInt(_T("Settings"), _T("MainBottom"),wp.rcNormalPosition.bottom);
1437         theApp.WriteProfileInt(_T("Settings"), _T("MainMax"), (wp.showCmd == SW_MAXIMIZE));
1438
1439         for (auto pFrame: GetAllImgMergeFrames())
1440         {
1441                 if (!pFrame->CloseNow())
1442                         return;
1443         }
1444
1445         CMDIFrameWnd::OnClose();
1446 }
1447
1448 /**
1449  * @brief Utility function to update CSuperComboBox format MRU
1450  */
1451 void CMainFrame::addToMru(LPCTSTR szItem, LPCTSTR szRegSubKey, UINT nMaxItems)
1452 {
1453         std::vector<CString> list;
1454         CString s;
1455         UINT cnt = AfxGetApp()->GetProfileInt(szRegSubKey, _T("Count"), 0);
1456         list.push_back(szItem);
1457         for (UINT i=0 ; i<cnt; ++i)
1458         {
1459                 s = AfxGetApp()->GetProfileString(szRegSubKey, strutils::format(_T("Item_%d"), i).c_str());
1460                 if (s != szItem)
1461                         list.push_back(s);
1462         }
1463         cnt = list.size() > nMaxItems ? nMaxItems : static_cast<UINT>(list.size());
1464         for (UINT i=0 ; i<cnt; ++i)
1465                 AfxGetApp()->WriteProfileString(szRegSubKey, strutils::format(_T("Item_%d"), i).c_str(), list[i]);
1466         // update count
1467         AfxGetApp()->WriteProfileInt(szRegSubKey, _T("Count"), cnt);
1468 }
1469
1470 void CMainFrame::ApplyDiffOptions() 
1471 {
1472         for (auto pMergeDoc : GetAllMergeDocs())
1473         {
1474                 // Re-read MergeDoc settings (also updates view settings)
1475                 // and rescan using new options
1476                 pMergeDoc->RefreshOptions();
1477                 pMergeDoc->FlushAndRescan(true);
1478         }
1479 }
1480
1481 /// Get list of OpenDocs (documents underlying edit sessions)
1482 OpenDocList &CMainFrame::GetAllOpenDocs()
1483 {
1484         return static_cast<OpenDocList &>(GetDocList(theApp.m_pOpenTemplate));
1485 }
1486
1487 /// Get list of MergeDocs (documents underlying edit sessions)
1488 MergeDocList &CMainFrame::GetAllMergeDocs()
1489 {
1490         return static_cast<MergeDocList &>(GetDocList(theApp.m_pDiffTemplate));
1491 }
1492
1493 /// Get list of DirDocs (documents underlying a scan)
1494 DirDocList &CMainFrame::GetAllDirDocs()
1495 {
1496         return static_cast<DirDocList &>(GetDocList(theApp.m_pDirTemplate));
1497 }
1498
1499 /// Get list of HexMergeDocs (documents underlying edit sessions)
1500 HexMergeDocList &CMainFrame::GetAllHexMergeDocs()
1501 {
1502         return static_cast<HexMergeDocList &>(GetDocList(theApp.m_pHexMergeTemplate));
1503 }
1504
1505 std::list<CImgMergeFrame *> CMainFrame::GetAllImgMergeFrames()
1506 {
1507         std::list<CImgMergeFrame *> list;
1508         // Close Non-Document/View frame with confirmation
1509         CMDIChildWnd *pChild = static_cast<CMDIChildWnd *>(CWnd::FromHandle(m_hWndMDIClient)->GetWindow(GW_CHILD));
1510         while (pChild != nullptr)
1511         {
1512                 CMDIChildWnd *pNextChild = static_cast<CMDIChildWnd *>(pChild->GetWindow(GW_HWNDNEXT));
1513                 if (GetFrameType(pChild) == FRAME_IMGFILE)
1514                         list.push_back(static_cast<CImgMergeFrame *>(pChild));
1515                 pChild = pNextChild;
1516         }
1517         return list;
1518 }
1519
1520 /**
1521  * @brief Obtain a merge doc to display a difference in files.
1522  * @return Pointer to CMergeDoc to use. 
1523  */
1524 template<class DocClass>
1525 DocClass * GetMergeDocForDiff(CMultiDocTemplate *pTemplate, CDirDoc *pDirDoc, int nFiles, bool bMakeVisible)
1526 {
1527         // Create a new merge doc
1528         DocClass::m_nBuffersTemp = nFiles;
1529         DocClass *pMergeDoc = static_cast<DocClass*>(pTemplate->OpenDocumentFile(nullptr, bMakeVisible));
1530         if (pMergeDoc != nullptr)
1531         {
1532                 pDirDoc->AddMergeDoc(pMergeDoc);
1533                 pMergeDoc->SetDirDoc(pDirDoc);
1534         }
1535         return pMergeDoc;
1536 }
1537
1538 // Clear the item count in the main status pane
1539 void CMainFrame::ClearStatusbarItemCount()
1540 {
1541         m_wndStatusBar.SetPaneText(2, _T(""));
1542 }
1543
1544 /**
1545  * @brief Generate patch from files selected.
1546  *
1547  * Creates a patch from selected files in active directory compare, or
1548  * active file compare. Files in file compare must be saved before
1549  * creating a patch.
1550  */
1551 void CMainFrame::OnToolsGeneratePatch()
1552 {
1553         CPatchTool patcher;
1554         patcher.CreatePatch();
1555 }
1556
1557 void CMainFrame::OnDropFiles(const std::vector<String>& dropped_files)
1558 {
1559         PathContext tFiles(dropped_files);
1560         const size_t fileCount = tFiles.GetSize();
1561
1562         bool recurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
1563         // Do a reverse comparison with the current 'Include Subfolders' settings when pressing Control key
1564         if (::GetAsyncKeyState(VK_CONTROL) & 0x8000)
1565                 recurse = !recurse;
1566
1567         // If user has <Shift> pressed with one file selected,
1568         // assume it is an archive and set filenames to same
1569         if (::GetAsyncKeyState(VK_SHIFT) < 0 && fileCount == 1)
1570         {
1571                 tFiles.SetRight(tFiles[0]);
1572         }
1573
1574         // Check if they dropped a project file
1575         DWORD dwFlags[3] = {FFILEOPEN_NONE, FFILEOPEN_NONE, FFILEOPEN_NONE};
1576         if (fileCount == 1)
1577         {
1578                 if (theApp.IsProjectFile(tFiles[0]))
1579                 {
1580                         theApp.LoadAndOpenProjectFile(tFiles[0]);
1581                         return;
1582                 }
1583                 if (IsConflictFile(tFiles[0]))
1584                 {
1585                         DoOpenConflict(tFiles[0], nullptr, true);
1586                         return;
1587                 }
1588         }
1589
1590         DoFileOrFolderOpen(&tFiles, dwFlags, nullptr, _T(""), recurse);
1591 }
1592
1593 void CMainFrame::OnPluginUnpackMode(UINT nID )
1594 {
1595         switch (nID)
1596         {
1597         case ID_UNPACK_MANUAL:
1598                 FileTransform::AutoUnpacking = false;
1599                 break;
1600         case ID_UNPACK_AUTO:
1601                 FileTransform::AutoUnpacking = true;
1602                 break;
1603         }
1604         for (auto pDirDoc : GetAllDirDocs())
1605         {
1606                 pDirDoc->GetPluginManager().SetUnpackerSettingAll(FileTransform::AutoUnpacking);
1607                 pDirDoc->UpdateAllViews(nullptr);
1608         }
1609         GetOptionsMgr()->SaveOption(OPT_PLUGINS_UNPACKER_MODE, static_cast<int>(FileTransform::AutoUnpacking));
1610 }
1611
1612 void CMainFrame::OnUpdatePluginUnpackMode(CCmdUI* pCmdUI) 
1613 {
1614         pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
1615
1616         if (pCmdUI->m_nID == ID_UNPACK_MANUAL)
1617                 pCmdUI->SetRadio(!FileTransform::AutoUnpacking);
1618         if (pCmdUI->m_nID == ID_UNPACK_AUTO)
1619                 pCmdUI->SetRadio(FileTransform::AutoUnpacking);
1620 }
1621
1622 void CMainFrame::OnPluginPrediffMode(UINT nID )
1623 {
1624         switch (nID)
1625         {
1626         case ID_PREDIFFER_MANUAL:
1627                 FileTransform::AutoPrediffing = false;
1628                 break;
1629         case ID_PREDIFFER_AUTO:
1630                 FileTransform::AutoPrediffing = true;
1631                 break;
1632         }
1633         PrediffingInfo infoPrediffer;
1634         for (auto pMergeDoc : GetAllMergeDocs())
1635                 pMergeDoc->SetPrediffer(&infoPrediffer);
1636         for (auto pDirDoc : GetAllDirDocs())
1637         {
1638                 pDirDoc->GetPluginManager().SetPrediffSettingAll(FileTransform::AutoPrediffing);
1639                 pDirDoc->UpdateAllViews(nullptr);
1640         }
1641         GetOptionsMgr()->SaveOption(OPT_PLUGINS_PREDIFFER_MODE, FileTransform::AutoPrediffing);
1642 }
1643
1644 void CMainFrame::OnUpdatePluginPrediffMode(CCmdUI* pCmdUI) 
1645 {
1646         pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
1647
1648         if (pCmdUI->m_nID == ID_PREDIFFER_MANUAL)
1649                 pCmdUI->SetRadio(!FileTransform::AutoPrediffing);
1650         if (pCmdUI->m_nID == ID_PREDIFFER_AUTO)
1651                 pCmdUI->SetRadio(FileTransform::AutoPrediffing);
1652 }
1653 /**
1654  * @brief Called when "Reload Plugins" item is updated
1655  */
1656 void CMainFrame::OnUpdatePluginRelatedMenu(CCmdUI* pCmdUI)
1657 {
1658         bool enabled = GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED);
1659         if (enabled && (pCmdUI->m_nID == ID_APPLY_PREDIFFER || pCmdUI->m_nID == ID_TRANSFORM_WITH_SCRIPT))
1660                 enabled = GetFrameType(GetActiveFrame()) == FRAME_FILE;
1661         pCmdUI->Enable(enabled);
1662 }
1663
1664 void CMainFrame::OnReloadPlugins()
1665 {
1666         UpdatePrediffersMenu();
1667 }
1668
1669 /** @brief Return active merge edit view, if can figure it out/is available */
1670 CMergeEditView * CMainFrame::GetActiveMergeEditView()
1671 {
1672         // NB: GetActiveDocument does not return the Merge Doc 
1673         //     even when the merge edit view is in front
1674         // NB: CMergeEditFrame::GetActiveView returns `nullptr` when location view active
1675         // So we have this rather complicated logic to try to get a merge edit view
1676         // We look at the front child window, which should be a frame
1677         // and we can get a MergeEditView from it, if it is a CMergeEditFrame
1678         // (DirViews use a different frame type)
1679         CMergeEditFrame * pFrame = dynamic_cast<CMergeEditFrame *>(GetActiveFrame());
1680         if (pFrame == nullptr) return nullptr;
1681         // Try to get the active MergeEditView (ie, left or right)
1682         if (pFrame->GetActiveView() != nullptr && pFrame->GetActiveView()->IsKindOf(RUNTIME_CLASS(CMergeEditView)))
1683         {
1684                 return dynamic_cast<CMergeEditView *>(pFrame->GetActiveView());
1685         }
1686         return pFrame->GetMergeDoc()->GetActiveMergeView();
1687 }
1688
1689 void CMainFrame::UpdatePrediffersMenu()
1690 {
1691         CMenu* menu = GetMenu();
1692         if (menu == nullptr)
1693         {
1694                 return;
1695         }
1696
1697         HMENU hMainMenu = menu->m_hMenu;
1698         HMENU prediffersSubmenu = GetPrediffersSubmenu(hMainMenu);
1699         if (prediffersSubmenu != nullptr)
1700         {
1701                 CMergeEditView * pEditView = GetActiveMergeEditView();
1702                 if (pEditView != nullptr)
1703                         pEditView->GetDocument()->createPrediffersSubmenu(prediffersSubmenu);
1704                 else
1705                 {
1706                         // no view or dir view : display an empty submenu
1707                         int i = GetMenuItemCount(prediffersSubmenu);
1708                         while (i --)
1709                                 ::DeleteMenu(prediffersSubmenu, 0, MF_BYPOSITION);
1710                         ::AppendMenu(prediffersSubmenu, MF_SEPARATOR, 0, nullptr);
1711                 }
1712         }
1713 }
1714
1715 /**
1716  * @brief Save WinMerge configuration and info to file
1717  */
1718 void CMainFrame::OnSaveConfigData()
1719 {
1720         CConfigLog configLog;
1721         String sError;
1722
1723         if (configLog.WriteLogFile(sError))
1724         {
1725                 String sFileName = configLog.GetFileName();
1726                 CMergeApp::OpenFileToExternalEditor(sFileName);
1727         }
1728         else
1729         {
1730                 String sFileName = configLog.GetFileName();
1731                 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sFileName, sError);
1732                 AfxMessageBox(msg.c_str(), MB_OK | MB_ICONSTOP);
1733         }
1734 }
1735
1736 /**
1737  * @brief Open two new empty docs, 'Scratchpads'
1738  * 
1739  * Allows user to open two empty docs, to paste text to
1740  * compare from clipboard.
1741  * @note File filenames are set emptys and filedescriptors
1742  * are loaded from resource.
1743  * @sa CMergeDoc::OpenDocs()
1744  * @sa CMergeDoc::TrySaveAs()
1745  */
1746 bool CMainFrame::DoFileNew(UINT nID, int nPanes, const String strDesc[],
1747         const PrediffingInfo *infoPrediffer /*= nullptr*/,
1748         OpenTextFileParams *pOpenParams)
1749 {
1750         CDirDoc *pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
1751         
1752         // Load emptyfile descriptors and open empty docs
1753         // Use default codepage
1754         DWORD dwFlags[3] = {0, 0, 0};
1755         FileLocation fileloc[3];
1756         String strDesc2[3];
1757         if (nPanes == 2)
1758         {
1759                 strDesc2[0] = _("Untitled left");
1760                 strDesc2[1] = _("Untitled right");
1761         }
1762         else
1763         {
1764                 strDesc2[0] = _("Untitled left");
1765                 strDesc2[1] = _("Untitled middle");
1766                 strDesc2[2] = _("Untitled right");
1767         }
1768         for (int i = 0; i < nPanes; ++i)
1769         {
1770                 if (strDesc && !strDesc[i].empty())
1771                         strDesc2[i] = strDesc[i];
1772                 fileloc[i].encoding.SetCodepage(ucr::getDefaultCodepage());
1773         }
1774         if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
1775                 pDirDoc->GetPluginManager().SetPrediffer(_T("|"), infoPrediffer->GetPluginPipeline());
1776         return ShowMergeDoc(nID, pDirDoc, nPanes, fileloc, dwFlags, strDesc2, _T(""), nullptr, pOpenParams);
1777 }
1778
1779 /**
1780  * @brief Open two new empty docs, 'Scratchpads'
1781  * 
1782  * Allows user to open two empty docs, to paste text to
1783  * compare from clipboard.
1784  * @note File filenames are set emptys and filedescriptors
1785  * are loaded from resource.
1786  * @sa CMergeDoc::OpenDocs()
1787  * @sa CMergeDoc::TrySaveAs()
1788  */
1789 template <int nFiles, unsigned nID>
1790 void CMainFrame::OnFileNew() 
1791 {
1792         DoFileNew(nID, nFiles);
1793 }
1794
1795 /**
1796  * @brief Open Filters dialog
1797  */
1798 void CMainFrame::OnToolsFilters()
1799 {
1800         String title = _("Filters");
1801         CPropertySheet sht(title.c_str());
1802         LineFiltersDlg lineFiltersDlg;
1803         SubstitutionFiltersDlg substitutionFiltersDlg;
1804         FileFiltersDlg fileFiltersDlg;
1805         std::unique_ptr<LineFiltersList> lineFilters(new LineFiltersList());
1806         std::unique_ptr<SubstitutionFiltersList> SubstitutionFilters(new SubstitutionFiltersList());
1807         String selectedFilter;
1808         const String origFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1809         sht.AddPage(&fileFiltersDlg);
1810         sht.AddPage(&lineFiltersDlg);
1811         sht.AddPage(&substitutionFiltersDlg);
1812         sht.m_psh.dwFlags |= PSH_NOAPPLYNOW; // Hide 'Apply' button since we don't need it
1813
1814         // Make sure all filters are up-to-date
1815         theApp.m_pGlobalFileFilter->ReloadUpdatedFilters();
1816
1817         fileFiltersDlg.SetFilterArray(theApp.m_pGlobalFileFilter->GetFileFilters(selectedFilter));
1818         fileFiltersDlg.SetSelected(selectedFilter);
1819         const bool lineFiltersEnabledOrig = GetOptionsMgr()->GetBool(OPT_LINEFILTER_ENABLED);
1820         lineFiltersDlg.m_bIgnoreRegExp = lineFiltersEnabledOrig;
1821
1822         lineFilters->CloneFrom(theApp.m_pLineFilters.get());
1823         lineFiltersDlg.SetList(lineFilters.get());
1824
1825         SubstitutionFilters->CloneFrom(theApp.m_pSubstitutionFiltersList.get());
1826         substitutionFiltersDlg.SetList(SubstitutionFilters.get());
1827
1828         sht.SetActivePage(AfxGetApp()->GetProfileInt(_T("Settings"), _T("FilterStartPage"), 0));
1829
1830         if (sht.DoModal() == IDOK)
1831         {
1832                 String strNone = _("<None>");
1833                 String path = fileFiltersDlg.GetSelected();
1834                 if (path.find(strNone) != String::npos)
1835                 {
1836                         // Don't overwrite mask we already have
1837                         if (!theApp.m_pGlobalFileFilter->IsUsingMask())
1838                         {
1839                                 String sFilter(_T("*.*"));
1840                                 theApp.m_pGlobalFileFilter->SetFilter(sFilter);
1841                                 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, sFilter);
1842                         }
1843                 }
1844                 else
1845                 {
1846                         theApp.m_pGlobalFileFilter->SetFileFilterPath(path);
1847                         theApp.m_pGlobalFileFilter->UseMask(false);
1848                         String sFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1849                         GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, sFilter);
1850                 }
1851                 bool linefiltersEnabled = lineFiltersDlg.m_bIgnoreRegExp;
1852                 GetOptionsMgr()->SaveOption(OPT_LINEFILTER_ENABLED, linefiltersEnabled);
1853
1854                 // Check if compare documents need rescanning
1855                 bool bFileCompareRescan = false;
1856                 bool bFolderCompareRescan = false;
1857                 CFrameWnd * pFrame = GetActiveFrame();
1858                 FRAMETYPE frame = GetFrameType(pFrame);
1859                 if (frame == FRAME_FILE)
1860                 {
1861                         if
1862                         (
1863                                    linefiltersEnabled != lineFiltersEnabledOrig
1864                                 || !lineFilters->Compare(theApp.m_pLineFilters.get())
1865                                 || !SubstitutionFilters->Compare(theApp.m_pSubstitutionFiltersList.get())
1866                         )
1867                         {
1868                                 bFileCompareRescan = true;
1869                         }
1870                 }
1871                 else if (frame == FRAME_FOLDER)
1872                 {
1873                         const String newFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1874                         if (lineFiltersEnabledOrig != linefiltersEnabled || 
1875                                         !theApp.m_pLineFilters->Compare(lineFilters.get()) || origFilter != newFilter)
1876                         {
1877                                 int res = LangMessageBox(IDS_FILTERCHANGED, MB_ICONWARNING | MB_YESNO);
1878                                 if (res == IDYES)
1879                                         bFolderCompareRescan = true;
1880                         }
1881                 }
1882
1883                 // Save new filters before (possibly) rescanning
1884                 theApp.m_pLineFilters->CloneFrom(lineFilters.get());
1885                 theApp.m_pLineFilters->SaveFilters();
1886
1887                 theApp.m_pSubstitutionFiltersList->CloneFrom(SubstitutionFilters.get());
1888                 theApp.m_pSubstitutionFiltersList->SaveFilters();
1889
1890                 if (bFileCompareRescan)
1891                 {
1892                         for (auto pMergeDoc : GetAllMergeDocs())
1893                                 pMergeDoc->FlushAndRescan(true);
1894                 }
1895                 else if (bFolderCompareRescan)
1896                 {
1897                         for (auto pDirDoc : GetAllDirDocs())
1898                                 pDirDoc->Rescan();
1899                 }
1900         }
1901 }
1902
1903 /**
1904  * @brief Open Filters dialog.
1905  */
1906 void CMainFrame::SelectFilter()
1907 {
1908         OnToolsFilters();
1909 }
1910
1911 /**
1912  * @brief Closes application with ESC.
1913  *
1914  * Application is closed if:
1915  * - 'Close Windows with ESC' option is enabled and
1916  *    there is no open document windows
1917  * - '-e' commandline switch is given
1918  */
1919 BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
1920 {
1921         // Check if we got 'ESC pressed' -message
1922         if ((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
1923         {
1924                 if (theApp.m_bEscShutdown && m_wndTabBar.GetItemCount() <= 1)
1925                 {
1926                         AfxGetMainWnd()->SendMessage(WM_COMMAND, ID_APP_EXIT);
1927                         return TRUE;
1928                 }
1929                 else if (GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC) == 1 && m_wndTabBar.GetItemCount() == 0)
1930                 {
1931                         AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_APP_EXIT);
1932                         return FALSE;
1933                 }
1934         }
1935
1936         if (WM_KEYDOWN == pMsg->message && VK_TAB == pMsg->wParam && GetAsyncKeyState(VK_CONTROL) < 0 && m_arrChild.GetSize() > 1)
1937         {
1938                 CWindowsManagerDialog* pDlg = new CWindowsManagerDialog;
1939                 pDlg->Create(CWindowsManagerDialog::IDD, this);
1940                 pDlg->ShowWindow(SW_SHOW);
1941                 return TRUE;
1942         }
1943
1944         return CMDIFrameWnd::PreTranslateMessage(pMsg);
1945 }
1946
1947 /**
1948  * @brief Show/hide statusbar.
1949  */
1950 void CMainFrame::OnViewStatusBar()
1951 {
1952         bool bShow = !GetOptionsMgr()->GetBool(OPT_SHOW_STATUSBAR);
1953         GetOptionsMgr()->SaveOption(OPT_SHOW_STATUSBAR, bShow);
1954
1955         CMDIFrameWnd::ShowControlBar(&m_wndStatusBar, bShow, 0);
1956 }
1957
1958 /**
1959  * @brief Updates "Show Tabbar" menuitem.
1960  */
1961 void CMainFrame::OnUpdateViewTabBar(CCmdUI* pCmdUI) 
1962 {
1963         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR));
1964 }
1965
1966 /**
1967  * @brief Show/hide tabbar.
1968  */
1969 void CMainFrame::OnViewTabBar()
1970 {
1971         bool bShow = !GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR);
1972         GetOptionsMgr()->SaveOption(OPT_SHOW_TABBAR, bShow);
1973
1974         CMDIFrameWnd::ShowControlBar(&m_wndTabBar, bShow, 0);
1975 }
1976
1977 /**
1978  * @brief Updates "Automatically Resize Panes" menuitem.
1979  */
1980 void CMainFrame::OnUpdateResizePanes(CCmdUI* pCmdUI)
1981 {
1982         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_RESIZE_PANES));
1983 }
1984
1985
1986 /**
1987  * @brief Enable/disable automatic pane resizing.
1988  */
1989 void CMainFrame::OnResizePanes()
1990 {
1991         bool bResize = !GetOptionsMgr()->GetBool(OPT_RESIZE_PANES);
1992         GetOptionsMgr()->SaveOption(OPT_RESIZE_PANES, bResize);
1993         // TODO: Introduce a common merge frame superclass?
1994         CFrameWnd *pActiveFrame = GetActiveFrame();
1995         if (CMergeEditFrame *pFrame = DYNAMIC_DOWNCAST(CMergeEditFrame, pActiveFrame))
1996         {
1997                 pFrame->UpdateAutoPaneResize();
1998                 if (bResize)
1999                         pFrame->UpdateSplitter();
2000         }
2001         else if (CHexMergeFrame *pFrame1 = DYNAMIC_DOWNCAST(CHexMergeFrame, pActiveFrame))
2002         {
2003                 pFrame1->UpdateAutoPaneResize();
2004                 if (bResize)
2005                         pFrame1->UpdateSplitter();
2006         }
2007 }
2008
2009 /**
2010  * @brief Open project-file.
2011  */
2012 void CMainFrame::OnFileOpenProject()
2013 {
2014         String sFilepath;
2015         
2016         // get the default projects path
2017         String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
2018         if (!SelectFile(GetSafeHwnd(), sFilepath, true, strProjectPath.c_str(), _T(""),
2019                         _("WinMerge Project Files (*.WinMerge)|*.WinMerge||")))
2020                 return;
2021         
2022         strProjectPath = paths::GetParentPath(sFilepath);
2023         // store this as the new project path
2024         GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
2025
2026         theApp.LoadAndOpenProjectFile(sFilepath);
2027 }
2028
2029 /**
2030  * @brief Receive command line from another instance.
2031  *
2032  * This function receives command line when only single-instance
2033  * is allowed. New instance tried to start sends its command line
2034  * to here so we can open paths it was meant to.
2035  */
2036 LRESULT CMainFrame::OnCopyData(WPARAM wParam, LPARAM lParam)
2037 {
2038         COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;
2039         LPCTSTR pchData = (LPCTSTR)pCopyData->lpData;
2040         // Bail out if data isn't zero-terminated
2041         DWORD cchData = pCopyData->cbData / sizeof(TCHAR);
2042         if (cchData == 0 || pchData[cchData - 1] != _T('\0'))
2043                 return FALSE;
2044         ReplyMessage(TRUE);
2045         MergeCmdLineInfo cmdInfo(pchData);
2046         theApp.ApplyCommandLineConfigOptions(cmdInfo);
2047         theApp.ParseArgsAndDoOpen(cmdInfo, this);
2048         return TRUE;
2049 }
2050
2051 LRESULT CMainFrame::OnUser1(WPARAM wParam, LPARAM lParam)
2052 {
2053         if (IMergeDoc *pMergeDoc = GetActiveIMergeDoc())
2054                 pMergeDoc->CheckFileChanged();
2055         return 0;
2056 }
2057
2058 /**
2059  * @brief Close all open windows.
2060  * 
2061  * Asks about saving unsaved files and then closes all open windows.
2062  */
2063 void CMainFrame::OnWindowCloseAll()
2064 {
2065         CMDIChildWnd *pChild = MDIGetActive();
2066         while (pChild != nullptr)
2067         {
2068                 CDocument* pDoc;
2069                 if ((pDoc = pChild->GetActiveDocument()) != nullptr)
2070                 {
2071                         if (!pDoc->SaveModified())
2072                                 return;
2073                         pDoc->OnCloseDocument();
2074                 }
2075                 else if (GetFrameType(pChild) == FRAME_IMGFILE)
2076                 {
2077                         if (!static_cast<CImgMergeFrame *>(pChild)->CloseNow())
2078                                 return;
2079                 }
2080                 else
2081                 {
2082                         pChild->DestroyWindow();
2083                 }
2084                 pChild = MDIGetActive();
2085         }
2086         return;
2087 }
2088
2089 /**
2090  * @brief Enables Window/Close All item if there are open windows.
2091  */ 
2092 void CMainFrame::OnUpdateWindowCloseAll(CCmdUI* pCmdUI)
2093 {
2094         pCmdUI->Enable(MDIGetActive() != nullptr);
2095 }
2096
2097 /**
2098  * @brief Access to the singleton main frame (where we have some globals)
2099  */
2100 CMainFrame * GetMainFrame()
2101 {
2102         CWnd * mainwnd = AfxGetMainWnd();
2103         ASSERT(mainwnd != nullptr);
2104         CMainFrame *pMainframe = dynamic_cast<CMainFrame*>(mainwnd);
2105         ASSERT(pMainframe != nullptr);
2106         return pMainframe;
2107 }
2108
2109 /**
2110  * @brief Opens dialog for user to Load, edit and save project files.
2111  * This dialog gets current compare paths and filter (+other properties
2112  * possible in project files) and initializes the dialog with them.
2113  */
2114 void CMainFrame::OnSaveProject()
2115 {
2116         if (m_pMenus[MENU_OPENVIEW] == nullptr)
2117                 theApp.m_pOpenTemplate->m_hMenuShared = NewOpenViewMenu();
2118         COpenDoc *pOpenDoc = static_cast<COpenDoc *>(theApp.m_pOpenTemplate->CreateNewDocument());
2119
2120         CFrameWnd * pFrame = GetActiveFrame();
2121         FRAMETYPE frame = pFrame ? GetFrameType(pFrame) : FRAME_OTHER;
2122
2123         if (frame == FRAME_FILE || frame == FRAME_HEXFILE || frame == FRAME_IMGFILE)
2124         {
2125                 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2126                 {
2127                         PathContext paths;
2128                         for (int pane = 0; pane < pMergeDoc->GetFileCount(); ++pane)
2129                         {
2130                                 pOpenDoc->m_dwFlags[pane] = FFILEOPEN_PROJECT | (pMergeDoc->GetReadOnly(pane) ? FFILEOPEN_READONLY : 0);
2131                                 paths.SetPath(pane, pMergeDoc->GetPath(pane));
2132                         }
2133                         pOpenDoc->m_files = paths;
2134                         pOpenDoc->m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
2135                         pOpenDoc->m_strExt = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
2136                         pOpenDoc->m_strUnpackerPipeline = pMergeDoc->GetUnpacker()->GetPluginPipeline();
2137                 }
2138         }
2139         else if (frame == FRAME_FOLDER)
2140         {
2141                 // Get paths currently in compare
2142                 if (const CDirDoc* pDoc = static_cast<const CDirDoc*>(pFrame->GetActiveDocument()))
2143                 {
2144                         const CDiffContext& ctxt = pDoc->GetDiffContext();
2145
2146                         // Set-up the dialog
2147                         for (int pane = 0; pane < ctxt.GetCompareDirs(); ++pane)
2148                         {
2149                                 pOpenDoc->m_dwFlags[pane] = FFILEOPEN_PROJECT | (pDoc->GetReadOnly(pane) ? FFILEOPEN_READONLY : 0);
2150                                 pOpenDoc->m_files.SetPath(pane, paths::AddTrailingSlash(ctxt.GetNormalizedPath(pane)));
2151                         }
2152                         pOpenDoc->m_bRecurse = ctxt.m_bRecursive;
2153                         pOpenDoc->m_strExt = static_cast<FileFilterHelper*>(ctxt.m_piFilterGlobal)->GetFilterNameOrMask();
2154                 }
2155         }
2156
2157         CFrameWnd *pOpenFrame = theApp.m_pOpenTemplate->CreateNewFrame(pOpenDoc, nullptr);
2158         theApp.m_pOpenTemplate->InitialUpdateFrame(pOpenFrame, pOpenDoc);
2159 }
2160
2161 /** 
2162  * @brief Start flashing window if window is inactive.
2163  */
2164 void CMainFrame::StartFlashing()
2165 {
2166         CWnd * activeWindow = GetActiveWindow();
2167         if (activeWindow != this)
2168                 FlashWindowEx(FLASHW_ALL | FLASHW_TIMERNOFG, 3, 0);
2169 }
2170
2171 #if _MFC_VER > 0x0600
2172 void CMainFrame::OnActivateApp(BOOL bActive, DWORD dwThreadID)
2173 #else
2174 void CMainFrame::OnActivateApp(BOOL bActive, HTASK hTask)
2175 #endif
2176 {
2177 #if _MFC_VER > 0x0600
2178         CMDIFrameWnd::OnActivateApp(bActive, dwThreadID);
2179 #else
2180         CMDIFrameWnd::OnActivateApp(bActive, hTask);
2181 #endif
2182
2183         if (IMergeDoc *pMergeDoc = GetActiveIMergeDoc())
2184                 PostMessage(WM_USER+1);
2185 }
2186
2187 BOOL CMainFrame::CreateToolbar()
2188 {
2189         if (!m_wndToolBar.CreateEx(this) ||
2190                 !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
2191         {
2192                 return FALSE;
2193         }
2194
2195         if (!m_wndReBar.Create(this, RBS_BANDBORDERS,
2196                 WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CBRS_ALIGN_TOP))
2197         {
2198                 return FALSE;
2199         }
2200
2201         VERIFY(m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT|TBSTYLE_TRANSPARENT));
2202
2203         // Remove this if you don't want tool tips or a resizable toolbar
2204         m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
2205                 CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
2206         m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS);
2207
2208         m_wndReBar.AddBar(&m_wndToolBar);
2209
2210         LoadToolbarImages();
2211
2212         UINT nID, nStyle;
2213         for (auto cmd : { ID_OPTIONS, ID_FILE_NEW, ID_FILE_OPEN, ID_FILE_SAVE })
2214         {
2215                 int iImage;
2216                 int index = m_wndToolBar.GetToolBarCtrl().CommandToIndex(cmd);
2217                 m_wndToolBar.GetButtonInfo(index, nID, nStyle, iImage);
2218                 nStyle |= TBSTYLE_DROPDOWN;
2219                 m_wndToolBar.SetButtonInfo(index, nID, nStyle, iImage);
2220         }
2221
2222         if (!GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR))
2223         {
2224                 CMDIFrameWnd::ShowControlBar(&m_wndToolBar, false, 0);
2225         }
2226
2227         return TRUE;
2228 }
2229
2230 /** @brief Load toolbar images from the resource. */
2231 void CMainFrame::LoadToolbarImages()
2232 {
2233         const int toolbarNewImgSize = MulDiv(16, GetSystemMetrics(SM_CXSMICON), 16) * (1 + GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE));
2234         const int toolbarOrgImgSize = toolbarNewImgSize <= 20 ? 16 : 32;
2235         CToolBarCtrl& BarCtrl = m_wndToolBar.GetToolBarCtrl();
2236
2237         m_ToolbarImages[TOOLBAR_IMAGES_ENABLED].Detach();
2238         m_ToolbarImages[TOOLBAR_IMAGES_DISABLED].Detach();
2239         CSize sizeButton(0, 0);
2240
2241         LoadToolbarImageList(toolbarOrgImgSize, toolbarNewImgSize,
2242                 toolbarOrgImgSize <= 16 ? IDB_TOOLBAR_ENABLED : IDB_TOOLBAR_ENABLED32,
2243                 false, m_ToolbarImages[TOOLBAR_IMAGES_ENABLED]);
2244         LoadToolbarImageList(toolbarOrgImgSize, toolbarNewImgSize,
2245                 toolbarOrgImgSize <= 16 ? IDB_TOOLBAR_ENABLED : IDB_TOOLBAR_ENABLED32,
2246                 true, m_ToolbarImages[TOOLBAR_IMAGES_DISABLED]);
2247
2248         sizeButton = CSize(toolbarNewImgSize + 8, toolbarNewImgSize + 8);
2249
2250         BarCtrl.SetButtonSize(sizeButton);
2251         if (CImageList *pImgList = BarCtrl.SetImageList(&m_ToolbarImages[TOOLBAR_IMAGES_ENABLED]))
2252                 pImgList->DeleteImageList();
2253         if (CImageList *pImgList = BarCtrl.SetDisabledImageList(&m_ToolbarImages[TOOLBAR_IMAGES_DISABLED]))
2254                 pImgList->DeleteImageList();
2255
2256         // resize the rebar.
2257         REBARBANDINFO rbbi = { sizeof REBARBANDINFO };
2258         rbbi.fMask = RBBIM_CHILDSIZE;
2259         rbbi.cyMinChild = sizeButton.cy;
2260         m_wndReBar.GetReBarCtrl().SetBandInfo(0, &rbbi);
2261 }
2262
2263
2264 /**
2265  * @brief Load a transparent 32-bit color image list.
2266  */
2267 static void LoadHiColImageList(UINT nIDResource, int nWidth, int nHeight, int nNewWidth, int nNewHeight, int nCount, bool bGrayscale, CImageList& ImgList)
2268 {
2269         CBitmap bm;
2270         bm.Attach(LoadBitmapAndConvertTo32bit(AfxGetInstanceHandle(), nIDResource, nNewWidth * nCount, nNewHeight, bGrayscale, RGB(0xff, 0, 0xff)));
2271
2272         VERIFY(ImgList.Create(nNewWidth, nNewHeight, ILC_COLOR32, nCount, 0));
2273         int nIndex = ImgList.Add(&bm, nullptr);
2274         ASSERT(-1 != nIndex);
2275 }
2276
2277 /**
2278  * @brief Load toolbar image list.
2279  */
2280 static void LoadToolbarImageList(int orgImageWidth, int newImageWidth, UINT nIDResource, bool bGrayscale, CImageList& ImgList)
2281 {
2282         const int ImageCount = 26;
2283         const int orgImageHeight = orgImageWidth - 1;
2284         const int newImageHeight = newImageWidth - 1;
2285         LoadHiColImageList(nIDResource, orgImageWidth, orgImageHeight, newImageWidth, newImageHeight, ImageCount, bGrayscale, ImgList);
2286 }
2287
2288 /**
2289  * @brief Called when the document title is modified.
2290  */
2291 void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
2292 {
2293         CFrameWnd::OnUpdateFrameTitle(bAddToTitle);
2294         
2295         if (m_wndTabBar.m_hWnd != nullptr)
2296                 m_wndTabBar.UpdateTabs();
2297 }
2298
2299 /** @brief Show none/small/big/huge toolbar. */
2300 void CMainFrame::OnToolbarSize(UINT id)
2301 {
2302         if (id == ID_TOOLBAR_NONE)
2303         {
2304                 GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, false);
2305                 CMDIFrameWnd::ShowControlBar(&m_wndToolBar, false, 0);
2306         }
2307         else
2308         {
2309                 GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, true);
2310                 GetOptionsMgr()->SaveOption(OPT_TOOLBAR_SIZE, id - ID_TOOLBAR_SMALL);
2311
2312                 LoadToolbarImages();
2313
2314                 CMDIFrameWnd::ShowControlBar(&m_wndToolBar, true, 0);
2315         }
2316 }
2317
2318 /** @brief Show none/small/big/huge toolbar. */
2319 void CMainFrame::OnUpdateToolbarSize(CCmdUI *pCmdUI)
2320 {
2321         if (!GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR))
2322                 pCmdUI->SetRadio(pCmdUI->m_nID == ID_TOOLBAR_NONE);
2323         else
2324                 pCmdUI->SetRadio((pCmdUI->m_nID - ID_TOOLBAR_SMALL) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE)));
2325 }
2326
2327 /** @brief Lang aware version of CFrameWnd::OnToolTipText() */
2328 BOOL CMainFrame::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
2329 {
2330         ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);
2331
2332         // need to handle both ANSI and UNICODE versions of the message
2333         TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
2334         TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
2335         String strFullText;
2336         CString strTipText;
2337         UINT_PTR nID = pNMHDR->idFrom;
2338         if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
2339                 pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
2340         {
2341                 // idFrom is actually the HWND of the tool
2342                 nID = ::GetDlgCtrlID((HWND)nID);
2343         }
2344
2345         if (nID != 0) // will be zero on a separator
2346         {
2347                 strFullText = theApp.LoadString(static_cast<UINT>(nID));
2348                 // don't handle the message if no string resource found
2349                 if (strFullText.empty())
2350                         return FALSE;
2351
2352                 // this is the command id, not the button index
2353                 AfxExtractSubString(strTipText, strFullText.c_str(), 1, '\n');
2354         }
2355         if (pNMHDR->code == TTN_NEEDTEXTA)
2356                 _wcstombsz(pTTTA->szText, strTipText, static_cast<ULONG>(std::size(pTTTA->szText)));
2357         else
2358                 lstrcpyn(pTTTW->szText, strTipText, static_cast<int>(std::size(pTTTW->szText)));
2359         *pResult = 0;
2360
2361         // bring the tooltip window above other popup windows
2362         ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
2363                 SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
2364
2365         return TRUE;    // message was handled
2366 }
2367
2368 /**
2369  * @brief Ask user for close confirmation when closing the mainframe.
2370  * This function asks if user wants to close multiple open windows when user
2371  * selects (perhaps accidentally) to close WinMerge (application).
2372  * @return true if user agreeds to close all windows.
2373  */
2374 bool CMainFrame::AskCloseConfirmation()
2375 {
2376         const DirDocList &dirdocs = GetAllDirDocs();
2377         const MergeDocList &mergedocs = GetAllMergeDocs();
2378
2379         int ret = IDYES;
2380         const size_t count = dirdocs.GetCount() + mergedocs.GetCount();
2381         if (count > 1)
2382         {
2383                 // Check that we don't have one empty dirdoc + mergedoc situation.
2384                 // That happens since we open "hidden" dirdoc for every file compare.
2385                 if (dirdocs.GetCount() == 1)
2386                 {
2387                         CDirDoc *pDoc = dirdocs.GetHead();
2388                         if (!pDoc->HasDiffs())
2389                                 return true;
2390                 }
2391                 ret = LangMessageBox(IDS_CLOSEALL_WINDOWS, MB_YESNO | MB_ICONWARNING);
2392         }
2393         return (ret == IDYES);
2394 }
2395
2396 /**
2397  * @brief Shows the release notes for user.
2398  * This function opens release notes HTML document into browser.
2399  */
2400 void CMainFrame::OnHelpReleasenotes()
2401 {
2402         const String sPath = paths::ConcatPath(env::GetProgPath(), RelNotes);
2403         shell::Open(sPath.c_str());
2404 }
2405
2406 /**
2407  * @brief Shows the translations page.
2408  * This function opens translations page URL into browser.
2409  */
2410 void CMainFrame::OnHelpTranslations()
2411 {
2412         shell::Open(TranslationsUrl);
2413 }
2414
2415 /**
2416  * @brief Called when user selects File/Open Conflict...
2417  */
2418 void CMainFrame::OnFileOpenConflict()
2419 {
2420         String conflictFile;
2421         if (SelectFile(GetSafeHwnd(), conflictFile))
2422         {
2423                 DoOpenConflict(conflictFile);
2424         }
2425 }
2426
2427 /**
2428  * @brief Select and open conflict file for resolving.
2429  * This function lets user to select conflict file to resolve.
2430  * Then we parse conflict file to two files to "merge" and
2431  * save resulting file over original file.
2432  *
2433  * Set left-side file read-only as it is the repository file which cannot
2434  * be modified anyway. Right-side file is user's file which is set as
2435  * modified by default so user can just save it and accept workspace
2436  * file as resolved file.
2437  * @param [in] conflictFile Full path to conflict file to open.
2438  * @param [in] checked If true, do not check if it really is project file.
2439  * @return `true` if conflict file was opened for resolving.
2440  */
2441 bool CMainFrame::DoOpenConflict(const String& conflictFile, const String strDesc[] /*= nullptr*/, bool checked /*= false*/)
2442 {
2443         bool conflictCompared = false;
2444
2445         if (!checked)
2446         {
2447                 bool confFile = IsConflictFile(conflictFile);
2448                 if (!confFile)
2449                 {
2450                         String message = strutils::format_string1(_("The file\n%1\nis not a conflict file."), conflictFile);
2451                         AfxMessageBox(message.c_str(), MB_ICONSTOP);
2452                         return false;
2453                 }
2454         }
2455
2456         // Create temp files and put them into the list,
2457         // from where they get deleted when MainFrame is deleted.
2458         String ext = paths::FindExtension(conflictFile);
2459         TempFilePtr wTemp(new TempFile());
2460         String workFile = wTemp->Create(_T("confw_"), ext);
2461         m_tempFiles.push_back(wTemp);
2462         TempFilePtr vTemp(new TempFile());
2463         String revFile = vTemp->Create(_T("confv_"), ext);
2464         m_tempFiles.push_back(vTemp);
2465         TempFilePtr bTemp(new TempFile());
2466         String baseFile = vTemp->Create(_T("confb_"), ext);
2467         m_tempFiles.push_back(bTemp);
2468
2469         // Parse conflict file into two files.
2470         bool inners, threeWay;
2471         int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
2472         bool success = ParseConflictFile(conflictFile, workFile, revFile, baseFile, iGuessEncodingType, inners, threeWay);
2473
2474         if (success)
2475         {
2476                 // Open two parsed files to WinMerge, telling WinMerge to
2477                 // save over original file (given as third filename).
2478                 theApp.m_strSaveAsPath = conflictFile;
2479                 if (!threeWay)
2480                 {
2481                         String strDesc2[2] = { 
2482                                 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Theirs File"),
2483                                 (strDesc && !strDesc[2].empty()) ? strDesc[2] : _("Mine File") };
2484                         DWORD dwFlags[2] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU | FFILEOPEN_MODIFIED};
2485                         PathContext tmpPathContext(revFile, workFile);
2486                         conflictCompared = DoFileOrFolderOpen(&tmpPathContext, dwFlags, strDesc2);
2487                 }
2488                 else
2489                 {
2490                         String strDesc3[3] = {
2491                                 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Base File"),
2492                                 (strDesc && !strDesc[1].empty()) ? strDesc[1] : _("Theirs File"),
2493                                 (strDesc && !strDesc[2].empty()) ? strDesc[2] : _("Mine File") };
2494                         PathContext tmpPathContext(baseFile, revFile, workFile);
2495                         DWORD dwFlags[3] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU | FFILEOPEN_MODIFIED};
2496                         conflictCompared = DoFileOrFolderOpen(&tmpPathContext, dwFlags, strDesc3);
2497                 }
2498         }
2499         else
2500         {
2501                 LangMessageBox(IDS_ERROR_CONF_RESOLVE, MB_ICONSTOP);
2502         }
2503         return conflictCompared;
2504 }
2505
2506 bool CMainFrame::DoSelfCompare(UINT nID, const String& file, const String strDesc[] /*= nullptr*/,
2507         const PackingInfo *infoUnpacker /*= nullptr*/, const PrediffingInfo *infoPrediffer /*= nullptr*/,
2508         OpenTextFileParams *pOpenParams /*= nullptr*/)
2509 {
2510         String ext = paths::FindExtension(file);
2511         TempFilePtr wTemp(new TempFile());
2512         String copiedFile = wTemp->Create(_T("self-compare_"), ext);
2513         m_tempFiles.push_back(wTemp);
2514
2515         TFile(file).copyTo(copiedFile);
2516
2517         String strDesc2[2] = { 
2518                 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Original File"),
2519                 (strDesc && !strDesc[1].empty()) ? strDesc[1] : _("") };
2520         DWORD dwFlags[2] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU};
2521         PathContext tmpPathContext(copiedFile, file);
2522         return DoFileOpen(nID, &tmpPathContext, dwFlags, strDesc2, _T(""), infoUnpacker, infoPrediffer, pOpenParams);
2523 }
2524
2525 /**
2526  * @brief Get type of frame (File/Folder compare).
2527  * @param [in] pFrame Pointer to frame to check.
2528  * @return FRAMETYPE of the given frame.
2529 */
2530 CMainFrame::FRAMETYPE CMainFrame::GetFrameType(const CFrameWnd * pFrame)
2531 {
2532         bool bMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame));
2533         bool bHexMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame));
2534         bool bImgMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame));
2535         bool bDirFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame));
2536
2537         if (bMergeFrame)
2538                 return FRAME_FILE;
2539         else if (bHexMergeFrame)
2540                 return FRAME_HEXFILE;
2541         else if (bImgMergeFrame)
2542                 return FRAME_IMGFILE;
2543         else if (bDirFrame)
2544                 return FRAME_FOLDER;
2545         else
2546                 return FRAME_OTHER;
2547 }
2548
2549 /**
2550  * @brief Show the plugins list dialog.
2551  */
2552 void CMainFrame::OnPluginsList()
2553 {
2554         PluginsListDlg dlg;
2555         dlg.DoModal();
2556 }
2557
2558 void CMainFrame::OnToolbarButtonDropDown(NMHDR* pNMHDR, LRESULT* pResult)
2559 {
2560         LPNMTOOLBAR pToolBar = reinterpret_cast<LPNMTOOLBAR>(pNMHDR);
2561         ClientToScreen(&(pToolBar->rcButton));
2562         BCMenu menu;
2563         int id;
2564         switch (pToolBar->iItem)
2565         {
2566         case ID_FILE_NEW:
2567                 id = IDR_POPUP_NEW;
2568                 break;
2569         case ID_FILE_OPEN:
2570                 id = IDR_POPUP_OPEN;
2571                 break;
2572         case ID_FILE_SAVE:
2573                 id = IDR_POPUP_SAVE;
2574                 break;
2575         default:
2576                 id = IDR_POPUP_DIFF_OPTIONS;
2577                 break;
2578         }
2579         VERIFY(menu.LoadMenu(id));
2580         theApp.TranslateMenu(menu.m_hMenu);
2581         CMenu* pPopup = menu.GetSubMenu(0);
2582         if (pPopup != nullptr)
2583         {
2584                 pPopup->TrackPopupMenu(TPM_RIGHTALIGN | TPM_RIGHTBUTTON, 
2585                         pToolBar->rcButton.right, pToolBar->rcButton.bottom, this);
2586         }
2587         *pResult = 0;
2588 }
2589 void CMainFrame::OnDiffWhitespace(UINT nID)
2590 {
2591         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, nID - ID_DIFF_OPTIONS_WHITESPACE_COMPARE);
2592         ApplyDiffOptions();
2593 }
2594
2595 void CMainFrame::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
2596 {
2597         pCmdUI->SetRadio((pCmdUI->m_nID - ID_DIFF_OPTIONS_WHITESPACE_COMPARE) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE)));
2598         pCmdUI->Enable();
2599 }
2600
2601 void CMainFrame::OnDiffIgnoreBlankLines()
2602 {
2603         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES));
2604         ApplyDiffOptions();
2605 }
2606
2607 void CMainFrame::OnUpdateDiffIgnoreBlankLines(CCmdUI* pCmdUI)
2608 {
2609         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES));
2610         pCmdUI->Enable();
2611 }
2612
2613 void CMainFrame::OnDiffIgnoreCase()
2614 {
2615         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE));
2616         ApplyDiffOptions();
2617 }
2618
2619 void CMainFrame::OnUpdateDiffIgnoreCase(CCmdUI* pCmdUI)
2620 {
2621         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE));
2622         pCmdUI->Enable();
2623 }
2624
2625 void CMainFrame::OnDiffIgnoreEOL()
2626 {
2627         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL));
2628         ApplyDiffOptions();
2629 }
2630
2631 void CMainFrame::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
2632 {
2633         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL));
2634         pCmdUI->Enable();
2635 }
2636
2637 void CMainFrame::OnDiffIgnoreCP()
2638 {
2639         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE));
2640         ApplyDiffOptions();
2641 }
2642
2643 void CMainFrame::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
2644 {
2645         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE));
2646         pCmdUI->Enable();
2647 }
2648
2649 void CMainFrame::OnDiffIgnoreComments()
2650 {
2651         GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, !GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES));
2652         ApplyDiffOptions();
2653 }
2654
2655 void CMainFrame::OnUpdateDiffIgnoreComments(CCmdUI* pCmdUI)
2656 {
2657         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES));
2658         pCmdUI->Enable();
2659 }
2660
2661 void CMainFrame::OnIncludeSubfolders()
2662 {
2663         GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, !GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS));
2664         // Update all dirdoc settings
2665         for (auto pDirDoc : GetAllDirDocs())
2666                 pDirDoc->RefreshOptions();
2667         for (auto pOpenDoc : GetAllOpenDocs())
2668                 pOpenDoc->RefreshOptions();
2669 }
2670
2671 void CMainFrame::OnUpdateIncludeSubfolders(CCmdUI* pCmdUI)
2672 {
2673         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS));
2674         pCmdUI->Enable();
2675 }
2676
2677 void CMainFrame::OnCompareMethod(UINT nID)
2678
2679         GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, nID - ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS);
2680 }
2681
2682 void CMainFrame::OnUpdateCompareMethod(CCmdUI* pCmdUI)
2683 {
2684         pCmdUI->SetRadio((pCmdUI->m_nID - ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_CMP_METHOD)));
2685         pCmdUI->Enable();
2686 }
2687
2688 void CMainFrame::OnMRUs(UINT nID)
2689 {
2690         std::vector<JumpList::Item> mrus = JumpList::GetRecentDocs(GetOptionsMgr()->GetInt(OPT_MRU_MAX));
2691         const size_t idx = static_cast<size_t>(nID) - ID_MRU_FIRST;
2692         if (idx < mrus.size())
2693         {
2694                 MergeCmdLineInfo cmdInfo((_T("\"") + mrus[idx].path + _T("\" ") + mrus[idx].params).c_str());
2695                 theApp.ParseArgsAndDoOpen(cmdInfo, this);
2696         }
2697 }
2698
2699 void CMainFrame::OnUpdateNoMRUs(CCmdUI* pCmdUI)
2700 {
2701         // append the MRU submenu
2702         CMenu *pMenu = pCmdUI->m_pSubMenu ? pCmdUI->m_pSubMenu : pCmdUI->m_pMenu;
2703         if (pMenu == nullptr)
2704                 return;
2705         HMENU hMenu = pMenu->m_hMenu;
2706         
2707         // empty the menu
2708         size_t i = ::GetMenuItemCount(hMenu);
2709         while (i --)
2710                 ::DeleteMenu(hMenu, 0, MF_BYPOSITION);
2711
2712         std::vector<JumpList::Item> mrus = JumpList::GetRecentDocs(GetOptionsMgr()->GetInt(OPT_MRU_MAX));
2713
2714         if (mrus.size() == 0)
2715         {
2716                 // no script : create a <empty> entry
2717                 ::AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, theApp.LoadString(IDS_NO_EDIT_SCRIPTS).c_str());
2718         }
2719         else
2720         {
2721                 // or fill in the submenu with the scripts names
2722                 int ID = ID_MRU_FIRST;  // first ID in menu
2723                 for (i = 0 ; i < mrus.size() ; i++, ID++)
2724                         ::AppendMenu(hMenu, MF_STRING, ID, 
2725                                 ((i < 9 ? strutils::format(_T("&%d "), i+1) : strutils::format(_T("&%c "), 'a' + i - 9)) 
2726                                         + mrus[i].title).c_str());
2727         }
2728
2729         pCmdUI->Enable(true);
2730 }
2731
2732 /**
2733  * @brief Update plugin name
2734  * @param [in] pCmdUI UI component to update.
2735  */
2736 void CMainFrame::OnUpdatePluginName(CCmdUI* pCmdUI)
2737 {
2738         if (auto pMergeDoc = GetActiveIMergeDoc())
2739         {
2740                 String pluginNames;
2741                 const PackingInfo* infoUnpacker = pMergeDoc->GetUnpacker();
2742                 if (infoUnpacker && !infoUnpacker->GetPluginPipeline().empty())
2743                         pluginNames += infoUnpacker->GetPluginPipeline() + _T("&");
2744                 const PrediffingInfo* infoPrediffer = pMergeDoc->GetPrediffer();
2745                 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
2746                         pluginNames += infoPrediffer->GetPluginPipeline() + _T("&");
2747                 pCmdUI->SetText(pluginNames.substr(0, pluginNames.length() - 1).c_str());
2748         }
2749         else
2750                 pCmdUI->SetText(_T(""));
2751 }
2752
2753 /**
2754  * @brief Move to next file
2755  */
2756 void CMainFrame::OnNextFile()
2757 {
2758         if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2759                 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2760                         pDirDoc->MoveToNextFile(pMergeDoc);
2761 }
2762
2763 /**
2764  * @brief Called when Move to next file is updated
2765  */
2766 void CMainFrame::OnUpdateNextFile(CCmdUI* pCmdUI)
2767 {
2768         bool enabled = false;
2769         if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2770                 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2771                         enabled = !pDirDoc->IsLastFile();
2772         pCmdUI->Enable(enabled);
2773 }
2774
2775 /**
2776  * @brief Move to previous file
2777  */
2778 void CMainFrame::OnPrevFile()
2779 {
2780         if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2781                 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2782                         pDirDoc->MoveToPrevFile(pMergeDoc);
2783 }
2784
2785 /**
2786  * @brief Called when Move to previous file is updated
2787  */
2788 void CMainFrame::OnUpdatePrevFile(CCmdUI* pCmdUI)
2789 {
2790         bool enabled = false;
2791         if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2792                 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2793                         enabled = !pDirDoc->IsFirstFile();
2794         pCmdUI->Enable(enabled);
2795 }
2796
2797 /**
2798  * @brief Move to first file
2799  */
2800 void CMainFrame::OnFirstFile()
2801 {
2802         if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2803                 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2804                         pDirDoc->MoveToFirstFile(pMergeDoc);
2805 }
2806
2807 /**
2808  * @brief Called when Move to first file is updated
2809  */
2810 void CMainFrame::OnUpdateFirstFile(CCmdUI* pCmdUI)
2811 {
2812         bool enabled = false;
2813         if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2814                 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2815                         enabled = !pDirDoc->IsFirstFile();
2816         pCmdUI->Enable(enabled);
2817 }
2818
2819 /**
2820  * @brief Move to last file
2821  */
2822 void CMainFrame::OnLastFile()
2823 {
2824         if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2825                 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2826                         pDirDoc->MoveToLastFile(pMergeDoc);
2827 }
2828
2829 /**
2830  * @brief Called when Move to last file item is updated
2831  */
2832 void CMainFrame::OnUpdateLastFile(CCmdUI* pCmdUI)
2833 {
2834         bool enabled = false;
2835         if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2836                 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2837                         enabled = !pDirDoc->IsLastFile();
2838         pCmdUI->Enable(enabled);
2839 }
2840
2841 void CMainFrame::ReloadMenu()
2842 {
2843         // set the menu of the main frame window
2844         UINT idMenu = IDR_MAINFRAME;
2845         CMergeApp *pApp = dynamic_cast<CMergeApp *> (AfxGetApp());
2846         CMainFrame * pMainFrame = dynamic_cast<CMainFrame *> ((CFrameWnd*)pApp->m_pMainWnd);
2847         HMENU hNewDefaultMenu = pMainFrame->NewDefaultMenu(idMenu);
2848         HMENU hNewMergeMenu = pMainFrame->NewMergeViewMenu();
2849         HMENU hNewImgMergeMenu = pMainFrame->NewImgMergeViewMenu();
2850         HMENU hNewDirMenu = pMainFrame->NewDirViewMenu();
2851         if (hNewDefaultMenu != nullptr && hNewMergeMenu != nullptr && hNewDirMenu != nullptr)
2852         {
2853                 // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
2854                 CMenu * pNewDefaultMenu = CMenu::FromHandle(hNewDefaultMenu);
2855                 CMenu * pNewMergeMenu = CMenu::FromHandle(hNewMergeMenu);
2856                 CMenu * pNewImgMergeMenu = CMenu::FromHandle(hNewImgMergeMenu);
2857                 CMenu * pNewDirMenu = CMenu::FromHandle(hNewDirMenu);
2858
2859                 CWnd *pFrame = CWnd::FromHandle(::GetWindow(pMainFrame->m_hWndMDIClient, GW_CHILD));
2860                 while (pFrame != nullptr)
2861                 {
2862                         if (pFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame)))
2863                                 static_cast<CMergeEditFrame *>(pFrame)->SetSharedMenu(hNewMergeMenu);
2864                         if (pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame)))
2865                                 static_cast<CHexMergeFrame *>(pFrame)->SetSharedMenu(hNewMergeMenu);
2866                         if (pFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame)))
2867                                 static_cast<CImgMergeFrame *>(pFrame)->SetSharedMenu(hNewImgMergeMenu);
2868                         else if (pFrame->IsKindOf(RUNTIME_CLASS(COpenFrame)))
2869                                 static_cast<COpenFrame *>(pFrame)->SetSharedMenu(hNewDefaultMenu);
2870                         else if (pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
2871                                 static_cast<CDirFrame *>(pFrame)->SetSharedMenu(hNewDirMenu);
2872                         pFrame = pFrame->GetNextWindow();
2873                 }
2874
2875                 CFrameWnd *pActiveFrame = pMainFrame->GetActiveFrame();
2876                 if (pActiveFrame != nullptr)
2877                 {
2878                         if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame)))
2879                                 pMainFrame->MDISetMenu(pNewMergeMenu, nullptr);
2880                         else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame)))
2881                                 pMainFrame->MDISetMenu(pNewMergeMenu, nullptr);
2882                         else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame)))
2883                                 pMainFrame->MDISetMenu(pNewImgMergeMenu, nullptr);
2884                         else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
2885                                 pMainFrame->MDISetMenu(pNewDirMenu, nullptr);
2886                         else
2887                                 pMainFrame->MDISetMenu(pNewDefaultMenu, nullptr);
2888                 }
2889                 else
2890                         pMainFrame->MDISetMenu(pNewDefaultMenu, nullptr);
2891
2892                 // Don't delete the old menu
2893                 // There is a bug in BCMenu or in Windows98 : the new menu does not
2894                 // appear correctly if we destroy the old one
2895                 //                      if (pOldDefaultMenu != nullptr)
2896                 //                              pOldDefaultMenu->DestroyMenu();
2897                 //                      if (pOldMergeMenu != nullptr)
2898                 //                              pOldMergeMenu->DestroyMenu();
2899                 //                      if (pOldDirMenu = nullptr)
2900                 //                              pOldDirMenu->DestroyMenu();
2901
2902                 // m_hMenuDefault is used to redraw the main menu when we close a child frame
2903                 // if this child frame had a different menu
2904                 pMainFrame->m_hMenuDefault = hNewDefaultMenu;
2905                 pApp->m_pOpenTemplate->m_hMenuShared = hNewDefaultMenu;
2906                 pApp->m_pDiffTemplate->m_hMenuShared = hNewMergeMenu;
2907                 pApp->m_pDirTemplate->m_hMenuShared = hNewDirMenu;
2908
2909                 // force redrawing the menu bar
2910                 pMainFrame->DrawMenuBar();
2911         }
2912 }
2913
2914 void CMainFrame::AppendPluginMenus(CMenu *pMenu, const String& filteredFilenames,
2915         const std::vector<std::wstring>& events, bool addAllMenu, unsigned baseId)
2916 {
2917         if (!GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
2918                 return;
2919
2920         auto [suggestedPlugins, allPlugins] = FileTransform::CreatePluginMenuInfos(filteredFilenames, events, baseId);
2921
2922         if (!addAllMenu)
2923         {
2924                 pMenu->AppendMenu(MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2925         }
2926
2927         for (const auto& [caption, name, id, plugin] : suggestedPlugins)
2928                 pMenu->AppendMenu(MF_STRING, id, caption.c_str());
2929
2930         CMenu* pMenu2 = pMenu;
2931         CMenu popupAll;
2932         if (addAllMenu)
2933         {
2934                 popupAll.CreatePopupMenu();
2935                 pMenu->AppendMenu(MF_POPUP, reinterpret_cast<UINT_PTR>(popupAll.m_hMenu), _("Al&l").c_str());
2936                 pMenu2 = &popupAll;
2937         }
2938         else
2939         {
2940                 pMenu->AppendMenu(MF_SEPARATOR, 0);
2941                 pMenu->AppendMenu(MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2942         }
2943
2944         std::vector<String> processTypes;
2945         for (const auto& [processType, pluginList] : allPlugins)
2946                 processTypes.push_back(processType);
2947         auto it = std::find(processTypes.begin(), processTypes.end(), _("&Others"));
2948         if (it != processTypes.end())
2949         {
2950                 processTypes.erase(it);
2951                 processTypes.push_back(_("&Others"));
2952         }
2953
2954         for (const auto& processType: processTypes)
2955         {
2956                 CMenu popup;
2957                 popup.CreatePopupMenu();
2958                 if (processType.empty())
2959                 {
2960                         for (const auto& [caption, name, id, plugin] : allPlugins[processType])
2961                                 pMenu2->AppendMenu(MF_STRING, id, caption.c_str());
2962                 }
2963                 else
2964                 {
2965                         for (const auto& [caption, name, id, plugin] : allPlugins[processType])
2966                                 popup.AppendMenu(MF_STRING, id, caption.c_str());
2967                         pMenu2->AppendMenu(MF_POPUP, reinterpret_cast<UINT_PTR>(popup.m_hMenu), processType.c_str());
2968                 }
2969                 popup.Detach();
2970         }
2971
2972         if (addAllMenu)
2973         {
2974                 if (baseId == ID_UNPACKERS_FIRST)
2975                         pMenu2->AppendMenu(MF_STRING, ID_OPEN_WITH_UNPACKER, _("&Select...").c_str());
2976                 else if (baseId == ID_PREDIFFERS_FIRST)
2977                         pMenu2->AppendMenu(MF_STRING, ID_APPLY_PREDIFFER, _("&Select...").c_str());
2978         }
2979         popupAll.Detach();
2980 }
2981
2982 String CMainFrame::GetPluginPipelineByMenuId(unsigned idSearch, const std::vector<std::wstring>& events, unsigned baseId)
2983 {
2984         PluginInfo* pluginFound = nullptr;
2985         String pluginName;
2986         auto [suggestedPlugins, allPlugins] = FileTransform::CreatePluginMenuInfos(_T(""), events, baseId);
2987         for (const auto& [processType, pluginList] : allPlugins)
2988         {
2989                 for (const auto& [caption, name, id, plugin] : pluginList)
2990                 {
2991                         if (id == idSearch)
2992                         {
2993                                 pluginName = name;
2994                                 pluginFound = plugin;
2995                                 break;
2996                         }
2997                 }
2998         }
2999         if (pluginFound)
3000         {
3001                 if (!pluginFound->GetExtendedPropertyValue(_T("ArgumentsRequired")).has_value() && 
3002                     !pluginFound->GetExtendedPropertyValue(pluginName + _T(".ArgumentsRequired")).has_value())
3003                         return pluginName;
3004                 CSelectPluginDlg dlg(pluginName, _T(""), 
3005                         (baseId == ID_UNPACKERS_FIRST)  ? CSelectPluginDlg::PluginType::Unpacker    : (
3006                         (baseId == ID_PREDIFFERS_FIRST) ? CSelectPluginDlg::PluginType::Prediffer   : 
3007                                                           CSelectPluginDlg::PluginType::EditorScript), true);
3008                 if (dlg.DoModal() != IDOK)
3009                         return {};
3010                 return dlg.GetPluginPipeline();
3011         }
3012         return {};
3013 }
3014
3015 IMergeDoc* CMainFrame::GetActiveIMergeDoc()
3016 {
3017         CFrameWnd* pFrame = GetActiveFrame();
3018         if (!pFrame)
3019                 return nullptr;
3020         IMergeDoc* pMergeDoc = dynamic_cast<IMergeDoc*>(pFrame->GetActiveDocument());
3021         if (!pMergeDoc)
3022                 pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
3023         return pMergeDoc;
3024 }
3025
3026 void CMainFrame::UpdateDocTitle()
3027 {
3028         CDocManager* pDocManager = AfxGetApp()->m_pDocManager;
3029         POSITION posTemplate = pDocManager->GetFirstDocTemplatePosition();
3030         ASSERT(posTemplate != nullptr);
3031
3032         while (posTemplate != nullptr)
3033         {
3034                 CDocTemplate* pTemplate = pDocManager->GetNextDocTemplate(posTemplate);
3035
3036                 ASSERT(pTemplate != nullptr);
3037
3038                 for (auto pDoc : GetDocList(static_cast<CMultiDocTemplate *>(pTemplate)))
3039                 {
3040                         static_cast<CDocument *>(const_cast<void *>(pDoc))->SetTitle(nullptr);
3041                         ((CFrameWnd*)AfxGetApp()->m_pMainWnd)->OnUpdateFrameTitle(TRUE);
3042                 }
3043         }
3044 }
3045
3046 void CMainFrame::OnAccelQuit()
3047 {
3048         // TODO: Add your command handler code here
3049
3050         SendMessage(WM_CLOSE);
3051 }
3052
3053 LRESULT CMainFrame::OnChildFrameAdded(WPARAM wParam, LPARAM lParam)
3054 {
3055         for (int i = 0; i < m_arrChild.GetSize(); ++i)
3056         {
3057                 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3058                 {
3059                         return 0;
3060                 }
3061         }
3062
3063         m_arrChild.InsertAt(0, (CMDIChildWnd*)lParam);
3064
3065         return 1;
3066 }
3067
3068 LRESULT CMainFrame::OnChildFrameRemoved(WPARAM wParam, LPARAM lParam)
3069 {
3070         for (int i = 0; i < m_arrChild.GetSize(); ++i)
3071         {
3072                 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3073                 {
3074                         m_arrChild.RemoveAt(i);
3075                         break;
3076                 }
3077         }
3078
3079         return 1;
3080 }
3081
3082 LRESULT CMainFrame::OnChildFrameActivate(WPARAM wParam, LPARAM lParam)
3083 {
3084         for (int i = 0; i < m_arrChild.GetSize(); ++i)
3085         {
3086                 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3087                 {
3088                         CMDIChildWnd* pMDIChild = m_arrChild.GetAt(i);
3089                         if (pMDIChild->IsIconic())
3090                                 pMDIChild->ShowWindow(SW_RESTORE);
3091                         MDIActivate(pMDIChild);
3092                         break;
3093                 }
3094         }
3095
3096         return 1;
3097 }
3098 // put lParam as index 0 in m_arrChild
3099 LRESULT CMainFrame::OnChildFrameActivated(WPARAM wParam, LPARAM lParam)
3100 {
3101         for (int i = 0; i < m_arrChild.GetSize(); ++i)
3102         {
3103                 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3104                 {
3105                         m_arrChild.RemoveAt(i);
3106                         break;
3107                 }
3108         }
3109
3110         m_arrChild.InsertAt(0, (CMDIChildWnd*)lParam);
3111
3112         return 1;
3113 }