OSDN Git Service

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