OSDN Git Service

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