OSDN Git Service

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