OSDN Git Service

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