OSDN Git Service

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