OSDN Git Service

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