OSDN Git Service

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