1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
10 * @brief Implementation of the CMainFrame class
17 #include <boost/range/mfc.hpp>
18 #include "Constants.h"
20 #include "FileFilterHelper.h"
21 #include "UnicodeString.h"
24 #include "DirFrame.h" // Include type information
25 #include "MergeEditFrm.h"
26 #include "HexMergeFrm.h"
31 #include "MergeEditView.h"
32 #include "HexMergeDoc.h"
33 #include "HexMergeView.h"
34 #include "ImgMergeFrm.h"
35 #include "LineFiltersList.h"
36 #include "ConflictFileParser.h"
37 #include "LineFiltersDlg.h"
39 #include "Environment.h"
40 #include "PatchTool.h"
42 #include "ConfigLog.h"
44 #include "Merge7zFormatMergePluginImpl.h"
45 #include "FileFiltersDlg.h"
46 #include "OptionsMgr.h"
47 #include "OptionsDef.h"
48 #include "codepage_detect.h"
50 #include "PreferencesDlg.h"
51 #include "FileOrFolderSelect.h"
52 #include "PluginsListDlg.h"
53 #include "stringdiffs.h"
54 #include "MergeCmdLineInfo.h"
55 #include "OptionsFont.h"
57 #include "DropHandler.h"
58 #include "LanguageSelect.h"
59 #include "VersionInfo.h"
61 #include "CCrystalTextMarkers.h"
63 #include "WindowsManagerDialog.h"
73 static void LoadToolbarImageList(int orgImageWidth, int newImageHeight, UINT nIDResource, bool bGrayscale, CImageList& ImgList);
74 static CPtrList &GetDocList(CMultiDocTemplate *pTemplate);
75 template<class DocClass>
76 DocClass * GetMergeDocForDiff(CMultiDocTemplate *pTemplate, CDirDoc *pDirDoc, int nFiles, bool bMakeVisible = true);
79 * @brief A table associating menuitem id, icon and menus to apply.
81 const CMainFrame::MENUITEM_ICON CMainFrame::m_MenuIcons[] = {
82 { ID_FILE_OPENCONFLICT, IDB_FILE_OPENCONFLICT, CMainFrame::MENU_ALL },
83 { ID_FILE_NEW3, IDB_FILE_NEW3, CMainFrame::MENU_ALL },
84 { ID_EDIT_COPY, IDB_EDIT_COPY, CMainFrame::MENU_ALL },
85 { ID_EDIT_CUT, IDB_EDIT_CUT, CMainFrame::MENU_ALL },
86 { ID_EDIT_PASTE, IDB_EDIT_PASTE, CMainFrame::MENU_ALL },
87 { ID_EDIT_FIND, IDB_EDIT_SEARCH, CMainFrame::MENU_ALL },
88 { ID_WINDOW_CASCADE, IDB_WINDOW_CASCADE, CMainFrame::MENU_ALL },
89 { ID_WINDOW_TILE_HORZ, IDB_WINDOW_HORIZONTAL, CMainFrame::MENU_ALL },
90 { ID_WINDOW_TILE_VERT, IDB_WINDOW_VERTICAL, CMainFrame::MENU_ALL },
91 { ID_FILE_CLOSE, IDB_WINDOW_CLOSE, CMainFrame::MENU_ALL },
92 { ID_WINDOW_CHANGE_PANE, IDB_WINDOW_CHANGEPANE, CMainFrame::MENU_ALL },
93 { ID_EDIT_WMGOTO, IDB_EDIT_GOTO, CMainFrame::MENU_ALL },
94 { ID_EDIT_REPLACE, IDB_EDIT_REPLACE, CMainFrame::MENU_ALL },
95 { ID_VIEW_SELECTFONT, IDB_VIEW_SELECTFONT, CMainFrame::MENU_ALL },
96 { ID_APP_EXIT, IDB_FILE_EXIT, CMainFrame::MENU_ALL },
97 { ID_HELP_CONTENTS, IDB_HELP_CONTENTS, CMainFrame::MENU_ALL },
98 { ID_EDIT_SELECT_ALL, IDB_EDIT_SELECTALL, CMainFrame::MENU_ALL },
99 { ID_TOOLS_FILTERS, IDB_TOOLS_FILTERS, CMainFrame::MENU_ALL },
100 { ID_TOOLS_CUSTOMIZECOLUMNS, IDB_TOOLS_COLUMNS, CMainFrame::MENU_ALL },
101 { ID_TOOLS_GENERATEPATCH, IDB_TOOLS_GENERATEPATCH, CMainFrame::MENU_ALL },
102 { ID_PLUGINS_LIST, IDB_PLUGINS_LIST, CMainFrame::MENU_ALL },
103 { ID_COPY_FROM_LEFT, IDB_COPY_FROM_LEFT, CMainFrame::MENU_ALL },
104 { ID_COPY_FROM_RIGHT, IDB_COPY_FROM_RIGHT, CMainFrame::MENU_ALL },
105 { ID_FILE_PRINT, IDB_FILE_PRINT, CMainFrame::MENU_FILECMP },
106 { ID_TOOLS_GENERATEREPORT, IDB_TOOLS_GENERATEREPORT, CMainFrame::MENU_FILECMP },
107 { ID_EDIT_TOGGLE_BOOKMARK, IDB_EDIT_TOGGLE_BOOKMARK, CMainFrame::MENU_FILECMP },
108 { ID_EDIT_GOTO_NEXT_BOOKMARK, IDB_EDIT_GOTO_NEXT_BOOKMARK, CMainFrame::MENU_FILECMP },
109 { ID_EDIT_GOTO_PREV_BOOKMARK, IDB_EDIT_GOTO_PREV_BOOKMARK, CMainFrame::MENU_FILECMP },
110 { ID_EDIT_CLEAR_ALL_BOOKMARKS, IDB_EDIT_CLEAR_ALL_BOOKMARKS, CMainFrame::MENU_FILECMP },
111 { ID_VIEW_ZOOMIN, IDB_VIEW_ZOOMIN, CMainFrame::MENU_FILECMP },
112 { ID_VIEW_ZOOMOUT, IDB_VIEW_ZOOMOUT, CMainFrame::MENU_FILECMP },
113 { ID_MERGE_COMPARE, IDB_MERGE_COMPARE, CMainFrame::MENU_FOLDERCMP },
114 { ID_MERGE_COMPARE_LEFT1_LEFT2, IDB_MERGE_COMPARE_LEFT1_LEFT2, CMainFrame::MENU_FOLDERCMP },
115 { ID_MERGE_COMPARE_RIGHT1_RIGHT2, IDB_MERGE_COMPARE_RIGHT1_RIGHT2,CMainFrame::MENU_FOLDERCMP },
116 { ID_MERGE_COMPARE_LEFT1_RIGHT2, IDB_MERGE_COMPARE_LEFT1_RIGHT2, CMainFrame::MENU_FOLDERCMP },
117 { ID_MERGE_COMPARE_LEFT2_RIGHT1, IDB_MERGE_COMPARE_LEFT2_RIGHT1, CMainFrame::MENU_FOLDERCMP },
118 { ID_MERGE_DELETE, IDB_MERGE_DELETE, CMainFrame::MENU_FOLDERCMP },
119 { ID_TOOLS_GENERATEREPORT, IDB_TOOLS_GENERATEREPORT, CMainFrame::MENU_FOLDERCMP },
120 { ID_DIR_COPY_LEFT_TO_RIGHT, IDB_LEFT_TO_RIGHT, CMainFrame::MENU_FOLDERCMP },
121 { ID_DIR_COPY_LEFT_TO_MIDDLE, IDB_LEFT_TO_MIDDLE, CMainFrame::MENU_FOLDERCMP },
122 { ID_DIR_COPY_RIGHT_TO_LEFT, IDB_RIGHT_TO_LEFT, CMainFrame::MENU_FOLDERCMP },
123 { ID_DIR_COPY_RIGHT_TO_MIDDLE, IDB_RIGHT_TO_MIDDLE, CMainFrame::MENU_FOLDERCMP },
124 { ID_DIR_COPY_MIDDLE_TO_LEFT, IDB_MIDDLE_TO_LEFT, CMainFrame::MENU_FOLDERCMP },
125 { ID_DIR_COPY_MIDDLE_TO_RIGHT, IDB_MIDDLE_TO_RIGHT, CMainFrame::MENU_FOLDERCMP },
126 { ID_DIR_COPY_LEFT_TO_BROWSE, IDB_LEFT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
127 { ID_DIR_COPY_MIDDLE_TO_BROWSE, IDB_MIDDLE_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
128 { ID_DIR_COPY_RIGHT_TO_BROWSE, IDB_RIGHT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
129 { ID_DIR_MOVE_LEFT_TO_BROWSE, IDB_MOVE_LEFT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
130 { ID_DIR_MOVE_MIDDLE_TO_BROWSE, IDB_MOVE_MIDDLE_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
131 { ID_DIR_MOVE_RIGHT_TO_BROWSE, IDB_MOVE_RIGHT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
132 { ID_DIR_DEL_LEFT, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
133 { ID_DIR_DEL_MIDDLE, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
134 { ID_DIR_DEL_RIGHT, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
135 { ID_DIR_DEL_BOTH, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
136 { ID_DIR_DEL_ALL, IDB_ALL, CMainFrame::MENU_FOLDERCMP },
137 { ID_DIR_COPY_PATHNAMES_LEFT, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
138 { ID_DIR_COPY_PATHNAMES_MIDDLE, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
139 { ID_DIR_COPY_PATHNAMES_RIGHT, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
140 { ID_DIR_COPY_PATHNAMES_BOTH, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
141 { ID_DIR_COPY_PATHNAMES_ALL, IDB_ALL, CMainFrame::MENU_FOLDERCMP },
142 { ID_DIR_COPY_LEFT_TO_CLIPBOARD, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
143 { ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
144 { ID_DIR_COPY_RIGHT_TO_CLIPBOARD, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
145 { ID_DIR_COPY_BOTH_TO_CLIPBOARD, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
146 { ID_DIR_COPY_ALL_TO_CLIPBOARD, IDB_ALL, CMainFrame::MENU_FOLDERCMP },
147 { ID_DIR_ZIP_LEFT, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
148 { ID_DIR_ZIP_MIDDLE, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
149 { ID_DIR_ZIP_RIGHT, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
150 { ID_DIR_ZIP_BOTH, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
151 { ID_DIR_ZIP_ALL, IDB_ALL, CMainFrame::MENU_FOLDERCMP }
155 /////////////////////////////////////////////////////////////////////////////
158 IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd)
160 BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
161 //{{AFX_MSG_MAP(CMainFrame)
164 ON_WM_INITMENUPOPUP()
167 ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
168 ON_COMMAND(ID_HELP_GNULICENSE, OnHelpGnulicense)
169 ON_COMMAND(ID_OPTIONS, OnOptions)
170 ON_COMMAND(ID_VIEW_SELECTFONT, OnViewSelectfont)
171 ON_COMMAND(ID_VIEW_USEDEFAULTFONT, OnViewUsedefaultfont)
172 ON_COMMAND(ID_HELP_CONTENTS, OnHelpContents)
174 ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
177 ON_COMMAND_RANGE(ID_UNPACK_MANUAL, ID_UNPACK_AUTO, OnPluginUnpackMode)
178 ON_UPDATE_COMMAND_UI_RANGE(ID_UNPACK_MANUAL, ID_UNPACK_AUTO, OnUpdatePluginUnpackMode)
179 ON_COMMAND_RANGE(ID_PREDIFFER_MANUAL, ID_PREDIFFER_AUTO, OnPluginPrediffMode)
180 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFER_MANUAL, ID_PREDIFFER_AUTO, OnUpdatePluginPrediffMode)
181 ON_UPDATE_COMMAND_UI(ID_RELOAD_PLUGINS, OnUpdateReloadPlugins)
182 ON_COMMAND(ID_RELOAD_PLUGINS, OnReloadPlugins)
183 ON_COMMAND(ID_HELP_GETCONFIG, OnSaveConfigData)
184 ON_COMMAND(ID_FILE_NEW, OnFileNew)
185 ON_COMMAND(ID_FILE_NEW3, OnFileNew3)
186 ON_COMMAND(ID_TOOLS_FILTERS, OnToolsFilters)
187 ON_COMMAND(ID_VIEW_STATUS_BAR, OnViewStatusBar)
188 ON_UPDATE_COMMAND_UI(ID_VIEW_TAB_BAR, OnUpdateViewTabBar)
189 ON_COMMAND(ID_VIEW_TAB_BAR, OnViewTabBar)
190 ON_UPDATE_COMMAND_UI(ID_VIEW_RESIZE_PANES, OnUpdateResizePanes)
191 ON_COMMAND(ID_VIEW_RESIZE_PANES, OnResizePanes)
192 ON_COMMAND(ID_FILE_OPENPROJECT, OnFileOpenProject)
193 ON_MESSAGE(WM_COPYDATA, OnCopyData)
194 ON_MESSAGE(WM_USER+1, OnUser1)
195 ON_COMMAND(ID_WINDOW_CLOSEALL, OnWindowCloseAll)
196 ON_UPDATE_COMMAND_UI(ID_WINDOW_CLOSEALL, OnUpdateWindowCloseAll)
197 ON_COMMAND(ID_FILE_SAVEPROJECT, OnSaveProject)
199 ON_COMMAND_RANGE(ID_TOOLBAR_NONE, ID_TOOLBAR_HUGE, OnToolbarSize)
200 ON_UPDATE_COMMAND_UI_RANGE(ID_TOOLBAR_NONE, ID_TOOLBAR_HUGE, OnUpdateToolbarSize)
201 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
202 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
203 ON_COMMAND(ID_HELP_RELEASENOTES, OnHelpReleasenotes)
204 ON_COMMAND(ID_HELP_TRANSLATIONS, OnHelpTranslations)
205 ON_COMMAND(ID_FILE_OPENCONFLICT, OnFileOpenConflict)
206 ON_COMMAND(ID_PLUGINS_LIST, OnPluginsList)
207 ON_UPDATE_COMMAND_UI(ID_STATUS_PLUGIN, OnUpdatePluginName)
208 ON_NOTIFY(TBN_DROPDOWN, AFX_IDW_TOOLBAR, OnDiffOptionsDropDown)
209 ON_COMMAND_RANGE(IDC_DIFF_WHITESPACE_COMPARE, IDC_DIFF_WHITESPACE_IGNOREALL, OnDiffWhitespace)
210 ON_UPDATE_COMMAND_UI_RANGE(IDC_DIFF_WHITESPACE_COMPARE, IDC_DIFF_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
211 ON_COMMAND(IDC_DIFF_CASESENSITIVE, OnDiffCaseSensitive)
212 ON_UPDATE_COMMAND_UI(IDC_DIFF_CASESENSITIVE, OnUpdateDiffCaseSensitive)
213 ON_COMMAND(IDC_DIFF_IGNOREEOL, OnDiffIgnoreEOL)
214 ON_UPDATE_COMMAND_UI(IDC_DIFF_IGNOREEOL, OnUpdateDiffIgnoreEOL)
215 ON_COMMAND(IDC_DIFF_IGNORECP, OnDiffIgnoreCP)
216 ON_UPDATE_COMMAND_UI(IDC_DIFF_IGNORECP, OnUpdateDiffIgnoreCP)
217 ON_COMMAND(IDC_RECURS_CHECK, OnIncludeSubfolders)
218 ON_UPDATE_COMMAND_UI(IDC_RECURS_CHECK, OnUpdateIncludeSubfolders)
219 ON_COMMAND_RANGE(ID_COMPMETHOD_FULL_CONTENTS, ID_COMPMETHOD_SIZE, OnCompareMethod)
220 ON_UPDATE_COMMAND_UI_RANGE(ID_COMPMETHOD_FULL_CONTENTS, ID_COMPMETHOD_SIZE, OnUpdateCompareMethod)
221 ON_COMMAND_RANGE(ID_MRU_FIRST, ID_MRU_LAST, OnMRUs)
222 ON_UPDATE_COMMAND_UI(ID_MRU_FIRST, OnUpdateNoMRUs)
223 ON_UPDATE_COMMAND_UI(ID_NO_MRU, OnUpdateNoMRUs)
224 ON_COMMAND(ID_ACCEL_QUIT, &CMainFrame::OnAccelQuit)
226 ON_MESSAGE(WMU_CHILDFRAMEADDED, &CMainFrame::OnChildFrameAdded)
227 ON_MESSAGE(WMU_CHILDFRAMEREMOVED, &CMainFrame::OnChildFrameRemoved)
228 ON_MESSAGE(WMU_CHILDFRAMEACTIVATE, &CMainFrame::OnChildFrameActivate)
229 ON_MESSAGE(WMU_CHILDFRAMEACTIVATED, &CMainFrame::OnChildFrameActivated)
233 * @brief MainFrame statusbar panels/indicators
235 static UINT StatusbarIndicators[] =
237 ID_SEPARATOR, // Plugin name
238 ID_SEPARATOR, // status line indicator
239 ID_SEPARATOR, // Merge mode
240 ID_SEPARATOR, // Diff number
241 ID_INDICATOR_CAPS, // Caps Lock
242 ID_INDICATOR_NUM, // Num Lock
243 ID_INDICATOR_OVR, // Insert
247 * @brief Return a const reference to a CMultiDocTemplate's list of documents.
249 static CPtrList &GetDocList(CMultiDocTemplate *pTemplate)
251 struct Template : public CMultiDocTemplate
254 using CMultiDocTemplate::m_docList;
256 return static_cast<struct Template *>(pTemplate)->m_docList;
259 /////////////////////////////////////////////////////////////////////////////
260 // CMainFrame construction/destruction
263 * @brief MainFrame constructor. Loads settings from registry.
264 * @todo Preference for logging?
266 CMainFrame::CMainFrame()
268 , m_pDropHandler(nullptr)
269 , m_bShowErrors(false)
270 , m_lfDiff(Options::Font::Load(GetOptionsMgr(), OPT_FONT_FILECMP))
271 , m_lfDir(Options::Font::Load(GetOptionsMgr(), OPT_FONT_DIRCMP))
275 CMainFrame::~CMainFrame()
277 GetOptionsMgr()->SaveOption(OPT_TABBAR_AUTO_MAXWIDTH, m_wndTabBar.GetAutoMaxWidth());
280 m_arrChild.RemoveAll();
283 const TCHAR CMainFrame::szClassName[] = _T("WinMergeWindowClassW");
286 * @brief Change MainFrame window class name
287 * see http://support.microsoft.com/kb/403825/ja
289 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
292 BOOL bRes = CMDIFrameWnd::PreCreateWindow(cs);
293 HINSTANCE hInst = AfxGetInstanceHandle();
294 // see if the class already exists
295 if (!::GetClassInfo(hInst, szClassName, &wndcls))
298 ::GetClassInfo(hInst, cs.lpszClass, &wndcls);
299 // register a new class
300 wndcls.lpszClassName = szClassName;
301 wndcls.hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(IDR_MAINFRAME));
302 ::RegisterClass(&wndcls);
304 cs.lpszClass = szClassName;
308 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
310 if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
313 m_wndMDIClient.SubclassWindow(m_hWndMDIClient);
315 if (!CreateToolbar())
317 TRACE0("Failed to create toolbar\n");
318 return -1; // fail to create
321 if (!m_wndTabBar.Create(this))
323 TRACE0("Failed to create tab bar\n");
324 return -1; // fail to create
326 m_wndTabBar.SetAutoMaxWidth(GetOptionsMgr()->GetBool(OPT_TABBAR_AUTO_MAXWIDTH));
328 if (!GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR))
329 CMDIFrameWnd::ShowControlBar(&m_wndTabBar, false, 0);
331 if (!m_wndStatusBar.Create(this))
333 TRACE0("Failed to create status bar\n");
334 return -1; // fail to create
336 theApp.SetIndicators(m_wndStatusBar, StatusbarIndicators,
337 static_cast<int>(std::size(StatusbarIndicators)));
339 const int lpx = CClientDC(this).GetDeviceCaps(LOGPIXELSX);
340 auto pointToPixel = [lpx](int point) { return MulDiv(point, lpx, 72); };
341 m_wndStatusBar.SetPaneInfo(0, 0, SBPS_STRETCH | SBPS_NOBORDERS, 0);
342 m_wndStatusBar.SetPaneInfo(1, ID_STATUS_PLUGIN, 0, pointToPixel(225));
343 m_wndStatusBar.SetPaneInfo(2, ID_STATUS_MERGINGMODE, 0, pointToPixel(75));
344 m_wndStatusBar.SetPaneInfo(3, ID_STATUS_DIFFNUM, 0, pointToPixel(112));
346 if (!GetOptionsMgr()->GetBool(OPT_SHOW_STATUSBAR))
347 CMDIFrameWnd::ShowControlBar(&m_wndStatusBar, false, 0);
349 m_pDropHandler = new DropHandler(std::bind(&CMainFrame::OnDropFiles, this, std::placeholders::_1));
350 RegisterDragDrop(m_hWnd, m_pDropHandler);
352 CWnd *pMDIChildWnd = FindWindowExW(m_hWnd, nullptr, _T("MDIClient"), nullptr);
354 pMDIChildWnd->ModifyStyleEx(WS_EX_CLIENTEDGE, 0);
359 void CMainFrame::OnTimer(UINT_PTR nIDEvent)
361 CMDIFrameWnd::OnTimer(nIDEvent);
363 if (nIDEvent == IDT_UPDATEMAINMENU)
368 MDIGetActive(&bMaximized);
370 // When MDI maximized the window icon is drawn on the menu bar, so we
371 // need to notify it that our icon has changed.
375 OnUpdateFrameTitle(FALSE);
379 void CMainFrame::OnDestroy(void)
381 if (m_pDropHandler != nullptr)
382 RevokeDragDrop(m_hWnd);
385 static HMENU GetSubmenu(HMENU mainMenu, UINT nIDFirstMenuItem, bool bFirstSubmenu)
388 for (i = 0 ; i < ::GetMenuItemCount(mainMenu) ; i++)
389 if (::GetMenuItemID(::GetSubMenu(mainMenu, i), 0) == nIDFirstMenuItem)
391 HMENU menu = ::GetSubMenu(mainMenu, i);
395 // look for last submenu
396 for (i = ::GetMenuItemCount(menu) ; i >= 0 ; i--)
397 if (::GetSubMenu(menu, i) != nullptr)
398 return ::GetSubMenu(menu, i);
402 // look for first submenu
403 for (i = 0 ; i < ::GetMenuItemCount(menu) ; i++)
404 if (::GetSubMenu(menu, i) != nullptr)
405 return ::GetSubMenu(menu, i);
408 // error, submenu not found
413 * @brief Find the scripts submenu from the main menu
414 * As now this is the first submenu in "Edit" menu
415 * We find the "Edit" menu by looking for a menu
416 * starting with ID_EDIT_UNDO.
418 HMENU CMainFrame::GetScriptsSubmenu(HMENU mainMenu)
420 return GetSubmenu(mainMenu, ID_PLUGINS_LIST, false);
424 * @brief Find the scripts submenu from the main menu
425 * As now this is the first submenu in "Plugins" menu
426 * We find the "Plugins" menu by looking for a menu
427 * starting with ID_UNPACK_MANUAL.
429 HMENU CMainFrame::GetPrediffersSubmenu(HMENU mainMenu)
431 return GetSubmenu(mainMenu, ID_PLUGINS_LIST, true);
435 * @brief Create a new menu for the view..
436 * @param [in] view Menu view either MENU_DEFAULT, MENU_MERGEVIEW or MENU_DIRVIEW.
437 * @param [in] ID Menu's resource ID.
438 * @return Menu for the view.
440 HMENU CMainFrame::NewMenu(int view, int ID)
443 if (m_pMenus[view] == nullptr)
445 m_pMenus[view].reset(new BCMenu());
446 if (m_pMenus[view] == nullptr)
453 menu_view = MENU_FILECMP;
456 menu_view = MENU_FOLDERCMP;
460 menu_view = MENU_MAINFRM;
464 if (!m_pMenus[view]->LoadMenu(ID))
470 if (view == MENU_IMGMERGEVIEW)
472 m_pImageMenu.reset(new BCMenu);
473 m_pImageMenu->LoadMenu(MAKEINTRESOURCE(IDR_POPUP_IMGMERGEVIEW));
474 m_pMenus[view]->InsertMenu(4, MF_BYPOSITION | MF_POPUP, (UINT_PTR)m_pImageMenu->GetSubMenu(0)->m_hMenu, const_cast<TCHAR *>(LoadResString(IDS_IMAGE_MENU).c_str()));
477 // Load bitmaps to menuitems
478 for (auto& menu_icon: m_MenuIcons)
480 if (menu_view == (menu_icon.menusToApply & menu_view))
482 m_pMenus[view]->ModifyODMenu(nullptr, menu_icon.menuitemID, menu_icon.iconResID);
486 m_pMenus[view]->LoadToolbar(IDR_MAINFRAME);
488 theApp.TranslateMenu(m_pMenus[view]->m_hMenu);
490 return (m_pMenus[view]->Detach());
494 * @brief Create new default (CMainFrame) menu.
496 HMENU CMainFrame::NewDefaultMenu(int ID /*=0*/)
500 return NewMenu( MENU_DEFAULT, ID );
504 * @brief Create new File compare (CMergeEditView) menu.
506 HMENU CMainFrame::NewMergeViewMenu()
508 return NewMenu( MENU_MERGEVIEW, IDR_MERGEDOCTYPE);
512 * @brief Create new Dir compare (CDirView) menu
514 HMENU CMainFrame::NewDirViewMenu()
516 return NewMenu(MENU_DIRVIEW, IDR_DIRDOCTYPE );
520 * @brief Create new File compare (CHexMergeView) menu.
522 HMENU CMainFrame::NewHexMergeViewMenu()
524 return NewMenu( MENU_HEXMERGEVIEW, IDR_MERGEDOCTYPE);
528 * @brief Create new Image compare (CImgMergeView) menu.
530 HMENU CMainFrame::NewImgMergeViewMenu()
532 return NewMenu( MENU_IMGMERGEVIEW, IDR_MERGEDOCTYPE);
536 * @brief Create new File compare (COpenView) menu.
538 HMENU CMainFrame::NewOpenViewMenu()
540 return NewMenu( MENU_OPENVIEW, IDR_MAINFRAME);
544 * @brief This handler ensures that the popup menu items are drawn correctly.
546 void CMainFrame::OnMeasureItem(int nIDCtl,
547 LPMEASUREITEMSTRUCT lpMeasureItemStruct)
549 bool setflag = false;
550 if (lpMeasureItemStruct->CtlType == ODT_MENU)
552 if (IsMenu(reinterpret_cast<HMENU>(static_cast<uintptr_t>(lpMeasureItemStruct->itemID))))
555 CMenu::FromHandle(reinterpret_cast<HMENU>(static_cast<uintptr_t>(lpMeasureItemStruct->itemID)));
557 if (m_pMenus[MENU_DEFAULT]->IsMenu(cmenu))
559 m_pMenus[MENU_DEFAULT]->MeasureItem(lpMeasureItemStruct);
566 CMDIFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
570 * @brief This handler ensures that keyboard shortcuts work.
572 LRESULT CMainFrame::OnMenuChar(UINT nChar, UINT nFlags,
576 if(m_pMenus[MENU_DEFAULT]->IsMenu(pMenu))
577 lresult=BCMenu::FindKeyboardShortcut(nChar, nFlags, pMenu);
579 lresult=CMDIFrameWnd::OnMenuChar(nChar, nFlags, pMenu);
584 * @brief This handler updates the menus from time to time.
586 void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
588 CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
592 if (BCMenu::IsMenu(pPopupMenu))
594 BCMenu::UpdateMenu(pPopupMenu);
599 /////////////////////////////////////////////////////////////////////////////
600 // CMainFrame message handlers
602 void CMainFrame::OnFileOpen()
608 * @brief Check for BOM, and also, if bGuessEncoding, try to deduce codepage
610 * Unpacks info from FileLocation & delegates all work to codepage_detect module
613 FileLocationGuessEncodings(FileLocation & fileloc, int iGuessEncoding)
615 fileloc.encoding = GuessCodepageEncoding(fileloc.filepath, iGuessEncoding);
618 bool CMainFrame::ShowAutoMergeDoc(CDirDoc * pDirDoc,
619 int nFiles, const FileLocation ifileloc[],
620 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
621 const PackingInfo * infoUnpacker /*= nullptr*/)
623 if (sReportFile.empty() && pDirDoc->CompareFilesIfFilesAreLarge(nFiles, ifileloc))
626 FileFilterHelper filterImg, filterBin;
627 filterImg.UseMask(true);
628 filterImg.SetMask(GetOptionsMgr()->GetString(OPT_CMP_IMG_FILEPATTERNS));
629 filterBin.UseMask(true);
630 filterBin.SetMask(GetOptionsMgr()->GetString(OPT_CMP_BIN_FILEPATTERNS));
631 for (int pane = 0; pane < nFiles; ++pane)
633 if (filterImg.includeFile(ifileloc[pane].filepath) && CImgMergeFrame::IsLoadable())
634 return ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
635 else if (filterBin.includeFile(ifileloc[pane].filepath) && CHexMergeView::IsLoadable())
636 return ShowHexMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
638 return ShowMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
641 std::array<bool, 3> GetROFromFlags(int nFiles, const DWORD dwFlags[])
643 std::array<bool, 3> bRO = { false, false, false };
644 for (int pane = 0; pane < nFiles; pane++)
647 bRO[pane] = ((dwFlags[pane] & FFILEOPEN_READONLY) > 0);
652 int GetActivePaneFromFlags(int nFiles, const DWORD dwFlags[])
654 int nActivePane = -1;
655 for (int pane = 0; pane < nFiles; ++pane)
657 if (dwFlags && (dwFlags[pane] & FFILEOPEN_SETFOCUS))
664 * @brief Creates new MergeDoc instance and shows documents.
665 * @param [in] pDirDoc Dir compare document to create a new Merge document for.
666 * @param [in] ifilelocLeft Left side file location info.
667 * @param [in] ifilelocRight Right side file location info.
668 * @param [in] dwLeftFlags Left side flags.
669 * @param [in] dwRightFlags Right side flags.
670 * @param [in] infoUnpacker Plugin info.
671 * @return success/failure
673 bool CMainFrame::ShowMergeDoc(CDirDoc * pDirDoc,
674 int nFiles, const FileLocation ifileloc[],
675 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
676 const PackingInfo * infoUnpacker /*= nullptr*/)
678 if (m_pMenus[MENU_MERGEVIEW] == nullptr)
679 theApp.m_pDiffTemplate->m_hMenuShared = NewMergeViewMenu();
680 CMergeDoc * pMergeDoc = GetMergeDocForDiff<CMergeDoc>(theApp.m_pDiffTemplate, pDirDoc, nFiles, false);
682 // Make local copies, so we can change encoding if we guess it below
683 FileLocation fileloc[3];
684 std::copy_n(ifileloc, nFiles, fileloc);
686 ASSERT(pMergeDoc != nullptr); // must ASSERT to get an answer to the question below ;-)
687 if (pMergeDoc == nullptr)
688 return false; // when does this happen ?
690 // if an unpacker is selected, it must be used during LoadFromFile
691 // MergeDoc must memorize it for SaveToFile
692 // Warning : this unpacker may differ from the pDirDoc one
693 // (through menu : "Plugins"->"Open with unpacker")
694 pMergeDoc->SetUnpacker(infoUnpacker);
697 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
698 for (int pane = 0; pane < nFiles; pane++)
700 if (fileloc[pane].encoding.m_unicoding == -1)
701 fileloc[pane].encoding.m_unicoding = ucr::NONE;
702 if (fileloc[pane].encoding.m_unicoding == ucr::NONE && fileloc[pane].encoding.m_codepage == -1)
704 FileLocationGuessEncodings(fileloc[pane], iGuessEncodingType);
708 // Note that OpenDocs() takes care of closing compare window when needed.
709 bool bResult = pMergeDoc->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc);
712 if (CMergeEditFrame *pFrame = pMergeDoc->GetParentFrame())
713 if (!pFrame->IsActivated())
714 pFrame->InitialUpdateFrame(pMergeDoc, true);
721 for (int pane = 0; pane < nFiles; pane++)
725 bool bModified = (dwFlags[pane] & FFILEOPEN_MODIFIED) > 0;
728 pMergeDoc->m_ptBuf[pane]->SetModified(true);
729 pMergeDoc->UpdateHeaderPath(pane);
731 if (dwFlags[pane] & FFILEOPEN_AUTOMERGE)
733 pMergeDoc->DoAutoMerge(pane);
738 pMergeDoc->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags));
740 if (!sReportFile.empty())
741 pMergeDoc->GenerateReport(sReportFile);
746 bool CMainFrame::ShowHexMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
747 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
748 const PackingInfo * infoUnpacker /*= nullptr*/)
750 if (m_pMenus[MENU_HEXMERGEVIEW] == nullptr)
751 theApp.m_pHexMergeTemplate->m_hMenuShared = NewHexMergeViewMenu();
752 CHexMergeDoc *pHexMergeDoc = GetMergeDocForDiff<CHexMergeDoc>(theApp.m_pHexMergeTemplate, pDirDoc, nFiles);
753 if (pHexMergeDoc == nullptr)
756 if (!pHexMergeDoc->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc))
759 pHexMergeDoc->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags));
761 if (!sReportFile.empty())
762 pHexMergeDoc->GenerateReport(sReportFile);
767 bool CMainFrame::ShowImgMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
768 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
769 const PackingInfo * infoUnpacker /*= nullptr*/)
771 CImgMergeFrame *pImgMergeFrame = new CImgMergeFrame();
772 if (!CImgMergeFrame::menu.m_hMenu)
773 CImgMergeFrame::menu.m_hMenu = NewImgMergeViewMenu();
774 pImgMergeFrame->SetSharedMenu(CImgMergeFrame::menu.m_hMenu);
776 pImgMergeFrame->SetDirDoc(pDirDoc);
777 pDirDoc->AddMergeDoc(pImgMergeFrame);
779 if (!pImgMergeFrame->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc, this))
780 return ShowMergeDoc(pDirDoc, nFiles, fileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
782 for (int pane = 0; pane < nFiles; pane++)
784 if (dwFlags && (dwFlags[pane] & FFILEOPEN_AUTOMERGE))
785 pImgMergeFrame->DoAutoMerge(pane);
788 pImgMergeFrame->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags));
790 if (!sReportFile.empty())
791 pImgMergeFrame->GenerateReport(sReportFile);
797 * @brief Show GNU licence information in notepad (local file) or in Web Browser
799 void CMainFrame::OnHelpGnulicense()
801 const String spath = paths::ConcatPath(env::GetProgPath(), LicenseFile);
802 theApp.OpenFileOrUrl(spath.c_str(), LicenceUrl);
806 * @brief Opens Options-dialog and saves changed options
808 void CMainFrame::OnOptions()
810 // Using singleton shared syntax colors
811 CPreferencesDlg dlg(GetOptionsMgr(), theApp.GetMainSyntaxColors());
812 INT_PTR rv = dlg.DoModal();
816 LANGID lang = static_cast<LANGID>(GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE));
817 if (lang != theApp.m_pLangDlg->GetLangId())
819 theApp.m_pLangDlg->SetLanguage(lang, true);
821 // Update status bar inicator texts
822 theApp.SetIndicators(m_wndStatusBar, 0, 0);
824 // Update the current menu
827 // update the title text of the document
833 // Set new temporary path
834 theApp.SetupTempPath();
836 // Set new filterpath
837 String filterPath = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
838 theApp.m_pGlobalFileFilter->SetUserFilterPath(filterPath);
840 CCrystalTextView::RENDERING_MODE nRenderingMode = static_cast<CCrystalTextView::RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
841 CCrystalTextView::SetRenderingModeDefault(nRenderingMode);
843 theApp.UpdateCodepageModule();
845 strdiff::SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
847 // make an attempt at rescanning any open diff sessions
850 // Update all dirdoc settings
851 for (auto pDirDoc : GetAllDirDocs())
852 pDirDoc->RefreshOptions();
853 for (auto pOpenDoc : GetAllOpenDocs())
854 pOpenDoc->RefreshOptions();
855 for (auto pMergeDoc : GetAllHexMergeDocs())
856 pMergeDoc->RefreshOptions();
860 static bool AddToRecentDocs(const PathContext& paths, const unsigned flags[], bool recurse, const String& filter)
862 String params, title;
863 for (int nIndex = 0; nIndex < paths.GetSize(); ++nIndex)
865 if (flags && (flags[nIndex] & FFILEOPEN_READONLY))
869 case 0: params += _T("/wl "); break;
870 case 1: params += paths.GetSize() == 2 ? _T("/wr ") : _T("/wm "); break;
871 case 2: params += _T("/wr "); break;
874 params += _T("\"") + paths[nIndex] + _T("\" ");
876 String path = paths[nIndex];
877 paths::normalize(path);
878 title += paths::FindFileName(path);
879 if (nIndex < paths.GetSize() - 1)
885 params += _T("/f \"") + filter + _T("\" ");
887 Concurrent::CreateTask([params, title](){
888 CoInitialize(nullptr);
889 JumpList::AddToRecentDocs(_T(""), params, title, params, 0);
896 * @brief Begin a diff: open dirdoc if it is directories, else open a mergedoc for editing.
897 * @param [in] pszLeft Left-side path.
898 * @param [in] pszRight Right-side path.
899 * @param [in] dwLeftFlags Left-side flags.
900 * @param [in] dwRightFlags Right-side flags.
901 * @param [in] bRecurse Do we run recursive (folder) compare?
902 * @param [in] pDirDoc Dir compare document to use.
903 * @param [in] prediffer Prediffer plugin name.
904 * @return `true` if opening files and compare succeeded, `false` otherwise.
906 bool CMainFrame::DoFileOpen(const PathContext * pFiles /*= nullptr*/,
907 const DWORD dwFlags[] /*= nullptr*/, const String strDesc[] /*= nullptr*/, const String& sReportFile /*= T("")*/, bool bRecurse /*= false*/, CDirDoc *pDirDoc/*= nullptr*/,
908 String prediffer /*= _T("")*/, const PackingInfo *infoUnpacker /*= nullptr*/)
910 if (pDirDoc != nullptr && !pDirDoc->CloseMergeDocs())
913 FileTransform::g_UnpackerMode = static_cast<PLUGIN_MODE>(GetOptionsMgr()->GetInt(OPT_PLUGINS_UNPACKER_MODE));
914 FileTransform::g_PredifferMode = static_cast<PLUGIN_MODE>(GetOptionsMgr()->GetInt(OPT_PLUGINS_PREDIFFER_MODE));
916 Merge7zFormatMergePluginScope scope(infoUnpacker);
919 if (pFiles != nullptr)
924 bRO[0] = (dwFlags[0] & FFILEOPEN_READONLY) != 0;
925 bRO[1] = (dwFlags[1] & FFILEOPEN_READONLY) != 0;
926 bRO[2] = (dwFlags[2] & FFILEOPEN_READONLY) != 0;
929 // pop up dialog unless arguments exist (and are compatible)
930 paths::PATH_EXISTENCE pathsType = paths::GetPairComparability(tFiles, IsArchiveFile);
931 if (pathsType == paths::DOES_NOT_EXIST)
933 if (m_pMenus[MENU_OPENVIEW] == nullptr)
934 theApp.m_pOpenTemplate->m_hMenuShared = NewOpenViewMenu();
935 COpenDoc *pOpenDoc = static_cast<COpenDoc *>(theApp.m_pOpenTemplate->CreateNewDocument());
938 pOpenDoc->m_dwFlags[0] = dwFlags[0];
939 pOpenDoc->m_dwFlags[1] = dwFlags[1];
940 pOpenDoc->m_dwFlags[2] = dwFlags[2];
942 pOpenDoc->m_files = tFiles;
943 pOpenDoc->m_bRecurse = bRecurse;
945 pOpenDoc->m_infoHandler = *infoUnpacker;
946 CFrameWnd *pFrame = theApp.m_pOpenTemplate->CreateNewFrame(pOpenDoc, nullptr);
947 theApp.m_pOpenTemplate->InitialUpdateFrame(pFrame, pOpenDoc);
952 // Add trailing '\' for directories if its missing
953 if (pathsType == paths::IS_EXISTING_DIR)
955 if (!paths::EndsWithSlash(tFiles[0]) && !IsArchiveFile(tFiles[0]))
956 tFiles[0] = paths::AddTrailingSlash(tFiles[0]);
957 if (!paths::EndsWithSlash(tFiles[1]) && !IsArchiveFile(tFiles[1]))
958 tFiles[1] = paths::AddTrailingSlash(tFiles[1]);
959 if (tFiles.GetSize() == 3 && !paths::EndsWithSlash(tFiles[2]) && !IsArchiveFile(tFiles[1]))
960 tFiles[2] = paths::AddTrailingSlash(tFiles[2]);
963 //save the MRU left and right files.
966 if (!(dwFlags[0] & FFILEOPEN_NOMRU))
967 addToMru(tFiles[0].c_str(), _T("Files\\Left"));
968 if (!(dwFlags[1] & FFILEOPEN_NOMRU))
969 addToMru(tFiles[1].c_str(), _T("Files\\Right"));
970 if (tFiles.GetSize() == 3 && !(dwFlags[2] & FFILEOPEN_NOMRU))
971 addToMru(tFiles[2].c_str(), _T("Files\\Option"));
975 CTempPathContext *pTempPathContext = nullptr;
976 if (pathsType == paths::IS_EXISTING_DIR)
978 DecompressResult res= DecompressArchive(m_hWnd, tFiles);
979 if (res.pTempPathContext)
981 pathsType = res.pathsType;
983 pTempPathContext = res.pTempPathContext;
987 // Determine if we want a new dirview open, now that we know if it was
988 // an archive. Don't open a new dirview if we are comparing files.
989 if (pDirDoc == nullptr)
991 if (pathsType == paths::IS_EXISTING_DIR)
993 CDirDoc::m_nDirsTemp = tFiles.GetSize();
994 if (m_pMenus[MENU_DIRVIEW] == nullptr)
995 theApp.m_pDirTemplate->m_hMenuShared = NewDirViewMenu();
996 pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->OpenDocumentFile(nullptr));
1000 pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
1005 if (pathsType == paths::IS_EXISTING_DIR)
1007 if (pDirDoc != nullptr)
1009 // Anything that can go wrong inside InitCompare() will yield an
1010 // exception. There is no point in checking return value.
1011 pDirDoc->InitCompare(tFiles, bRecurse, pTempPathContext);
1013 pDirDoc->SetReportFile(sReportFile);
1014 pDirDoc->SetDescriptions(strDesc);
1015 pDirDoc->SetTitle(nullptr);
1016 for (int nIndex = 0; nIndex < tFiles.GetSize(); nIndex++)
1017 pDirDoc->SetReadOnly(nIndex, bRO[nIndex]);
1024 FileLocation fileloc[3];
1026 for (int nPane = 0; nPane < tFiles.GetSize(); nPane++)
1027 fileloc[nPane].setPath(tFiles[nPane]);
1029 if (!prediffer.empty())
1031 String strBothFilenames = strutils::join(tFiles.begin(), tFiles.end(), _T("|"));
1032 pDirDoc->GetPluginManager().SetPrediffer(strBothFilenames, prediffer);
1035 ShowAutoMergeDoc(pDirDoc, tFiles.GetSize(), fileloc, dwFlags, strDesc, sReportFile,
1039 if (pFiles != nullptr && (!dwFlags || !(dwFlags[0] & FFILEOPEN_NOMRU)))
1041 String filter = GetOptionsMgr()->GetString(OPT_FILEFILTER_CURRENT);
1042 AddToRecentDocs(*pFiles, (unsigned *)dwFlags, bRecurse, filter);
1048 void CMainFrame::UpdateFont(FRAMETYPE frame)
1050 if (frame == FRAME_FOLDER)
1052 for (auto pDoc : GetAllDirDocs())
1054 if (pDoc != nullptr)
1056 CDirView *pView = pDoc->GetMainView();
1057 if (pView != nullptr)
1058 pView->SetFont(m_lfDir);
1064 for (auto pDoc : GetAllMergeDocs())
1066 CMergeDoc *pMergeDoc = dynamic_cast<CMergeDoc *>(pDoc);
1067 if (pMergeDoc != nullptr)
1068 for (auto& pView: pMergeDoc->GetViewList())
1069 pView->SetFont(m_lfDiff);
1075 * @brief Select font for Merge/Dir view
1077 * Shows font selection dialog to user, sets current font and saves
1078 * selected font properties to registry. Selects fon type to active
1079 * view (Merge/Dir compare). If there is no open views, then font
1080 * is selected for Merge view (for example user may want to change to
1081 * unicode font before comparing files).
1083 void CMainFrame::OnViewSelectfont()
1085 FRAMETYPE frame = GetFrameType(GetActiveFrame());
1086 CHOOSEFONT cf = { sizeof CHOOSEFONT };
1087 LOGFONT *lf = nullptr;
1088 cf.Flags = CF_INITTOLOGFONTSTRUCT|CF_FORCEFONTEXIST|CF_SCREENFONTS;
1089 if (frame == FRAME_FILE)
1090 cf.Flags |= CF_FIXEDPITCHONLY; // Only fixed-width fonts for merge view
1092 // CF_FIXEDPITCHONLY = 0x00004000L
1093 // in case you are a developer and want to disable it to test with, eg, a Chinese capable font
1094 if (frame == FRAME_FOLDER)
1101 if (ChooseFont(&cf))
1103 Options::Font::Save(GetOptionsMgr(), frame == FRAME_FOLDER ? OPT_FONT_DIRCMP : OPT_FONT_FILECMP, lf, true);
1109 * @brief Use default font for active view type
1111 * Disable user-selected font for active view type (Merge/Dir compare).
1112 * If there is no open views, then Merge view font is changed.
1114 void CMainFrame::OnViewUsedefaultfont()
1116 FRAMETYPE frame = GetFrameType(GetActiveFrame());
1118 if (frame == FRAME_FOLDER)
1120 Options::Font::Reset(GetOptionsMgr(), OPT_FONT_DIRCMP);
1121 m_lfDir = Options::Font::Load(GetOptionsMgr(), OPT_FONT_DIRCMP);
1122 Options::Font::Save(GetOptionsMgr(), OPT_FONT_DIRCMP, &m_lfDir, false);
1126 Options::Font::Reset(GetOptionsMgr(), OPT_FONT_FILECMP);
1127 m_lfDiff = Options::Font::Load(GetOptionsMgr(), OPT_FONT_FILECMP);
1128 Options::Font::Save(GetOptionsMgr(), OPT_FONT_FILECMP, &m_lfDiff, false);
1135 * @brief Update any resources necessary after a GUI language change
1137 void CMainFrame::UpdateResources()
1139 m_wndStatusBar.SetPaneText(0, theApp.LoadString(AFX_IDS_IDLEMESSAGE).c_str());
1141 for (auto pDoc : GetAllDirDocs())
1142 pDoc->UpdateResources();
1143 for (auto pDoc : GetAllMergeDocs())
1144 pDoc->UpdateResources();
1145 for (auto pDoc : GetAllOpenDocs())
1146 pDoc->UpdateResources();
1147 for (auto pFrame: GetAllImgMergeFrames())
1148 pFrame->UpdateResources();
1152 * @brief Open WinMerge help.
1154 * If local HTMLhelp file is found, open it, otherwise open HTML page from web.
1156 void CMainFrame::OnHelpContents()
1162 * @brief Handle translation of default messages on the status bar
1164 void CMainFrame::GetMessageString(UINT nID, CString& rMessage) const
1166 // load appropriate string
1167 const String s = theApp.LoadString(nID);
1169 AfxExtractSubString(rMessage, s.c_str(), 0);
1172 void CMainFrame::ActivateFrame(int nCmdShow)
1176 CMDIFrameWnd::ActivateFrame(nCmdShow);
1180 m_bFirstTime = false;
1183 wp.length = sizeof(WINDOWPLACEMENT);
1184 GetWindowPlacement(&wp);
1185 wp.rcNormalPosition.left=theApp.GetProfileInt(_T("Settings"), _T("MainLeft"),0);
1186 wp.rcNormalPosition.top=theApp.GetProfileInt(_T("Settings"), _T("MainTop"),0);
1187 wp.rcNormalPosition.right=theApp.GetProfileInt(_T("Settings"), _T("MainRight"),0);
1188 wp.rcNormalPosition.bottom=theApp.GetProfileInt(_T("Settings"), _T("MainBottom"),0);
1189 if (nCmdShow != SW_MINIMIZE && theApp.GetProfileInt(_T("Settings"), _T("MainMax"), FALSE))
1190 wp.showCmd = SW_MAXIMIZE;
1192 wp.showCmd = nCmdShow;
1194 CRect dsk_rc,rc(wp.rcNormalPosition);
1196 dsk_rc.left = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
1197 dsk_rc.top = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
1198 dsk_rc.right = dsk_rc.left + ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
1199 dsk_rc.bottom = dsk_rc.top + ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
1200 if (rc.Width() != 0 && rc.Height() != 0)
1202 // Ensure top-left corner is on visible area,
1203 // 20 points margin is added to prevent "lost" window
1204 CPoint ptTopLeft(rc.TopLeft());
1205 ptTopLeft += CPoint(20, 20);
1207 if (dsk_rc.PtInRect(ptTopLeft))
1208 SetWindowPlacement(&wp);
1210 CMDIFrameWnd::ActivateFrame(nCmdShow);
1213 CMDIFrameWnd::ActivateFrame(nCmdShow);
1217 * @brief Called when mainframe is about to be closed.
1218 * This function is called when mainframe is to be closed (not for
1219 * file/compare windows.
1221 void CMainFrame::OnClose()
1223 if (theApp.GetActiveOperations())
1226 // Check if there are multiple windows open and ask for closing them
1227 bool bAskClosing = GetOptionsMgr()->GetBool(OPT_ASK_MULTIWINDOW_CLOSE);
1230 bool quit = AskCloseConfirmation();
1235 // Save last selected filter
1236 String filter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1237 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
1239 // save main window position
1241 wp.length = sizeof(WINDOWPLACEMENT);
1242 GetWindowPlacement(&wp);
1243 theApp.WriteProfileInt(_T("Settings"), _T("MainLeft"),wp.rcNormalPosition.left);
1244 theApp.WriteProfileInt(_T("Settings"), _T("MainTop"),wp.rcNormalPosition.top);
1245 theApp.WriteProfileInt(_T("Settings"), _T("MainRight"),wp.rcNormalPosition.right);
1246 theApp.WriteProfileInt(_T("Settings"), _T("MainBottom"),wp.rcNormalPosition.bottom);
1247 theApp.WriteProfileInt(_T("Settings"), _T("MainMax"), (wp.showCmd == SW_MAXIMIZE));
1249 for (auto pFrame: GetAllImgMergeFrames())
1251 if (!pFrame->CloseNow())
1255 CMDIFrameWnd::OnClose();
1259 * @brief Utility function to update CSuperComboBox format MRU
1261 void CMainFrame::addToMru(LPCTSTR szItem, LPCTSTR szRegSubKey, UINT nMaxItems)
1263 std::vector<CString> list;
1265 UINT cnt = AfxGetApp()->GetProfileInt(szRegSubKey, _T("Count"), 0);
1266 list.push_back(szItem);
1267 for (UINT i=0 ; i<cnt; ++i)
1269 s = AfxGetApp()->GetProfileString(szRegSubKey, strutils::format(_T("Item_%d"), i).c_str());
1273 cnt = list.size() > nMaxItems ? nMaxItems : static_cast<UINT>(list.size());
1274 for (UINT i=0 ; i<cnt; ++i)
1275 AfxGetApp()->WriteProfileString(szRegSubKey, strutils::format(_T("Item_%d"), i).c_str(), list[i]);
1277 AfxGetApp()->WriteProfileInt(szRegSubKey, _T("Count"), cnt);
1280 void CMainFrame::ApplyDiffOptions()
1282 for (auto pMergeDoc : GetAllMergeDocs())
1284 // Re-read MergeDoc settings (also updates view settings)
1285 // and rescan using new options
1286 pMergeDoc->RefreshOptions();
1287 pMergeDoc->FlushAndRescan(true);
1291 /// Get list of OpenDocs (documents underlying edit sessions)
1292 OpenDocList &CMainFrame::GetAllOpenDocs()
1294 return static_cast<OpenDocList &>(GetDocList(theApp.m_pOpenTemplate));
1297 /// Get list of MergeDocs (documents underlying edit sessions)
1298 MergeDocList &CMainFrame::GetAllMergeDocs()
1300 return static_cast<MergeDocList &>(GetDocList(theApp.m_pDiffTemplate));
1303 /// Get list of DirDocs (documents underlying a scan)
1304 DirDocList &CMainFrame::GetAllDirDocs()
1306 return static_cast<DirDocList &>(GetDocList(theApp.m_pDirTemplate));
1309 /// Get list of HexMergeDocs (documents underlying edit sessions)
1310 HexMergeDocList &CMainFrame::GetAllHexMergeDocs()
1312 return static_cast<HexMergeDocList &>(GetDocList(theApp.m_pHexMergeTemplate));
1315 std::list<CImgMergeFrame *> CMainFrame::GetAllImgMergeFrames()
1317 std::list<CImgMergeFrame *> list;
1318 // Close Non-Document/View frame with confirmation
1319 CMDIChildWnd *pChild = static_cast<CMDIChildWnd *>(CWnd::FromHandle(m_hWndMDIClient)->GetWindow(GW_CHILD));
1320 while (pChild != nullptr)
1322 CMDIChildWnd *pNextChild = static_cast<CMDIChildWnd *>(pChild->GetWindow(GW_HWNDNEXT));
1323 if (GetFrameType(pChild) == FRAME_IMGFILE)
1324 list.push_back(static_cast<CImgMergeFrame *>(pChild));
1325 pChild = pNextChild;
1331 * @brief Obtain a merge doc to display a difference in files.
1332 * @return Pointer to CMergeDoc to use.
1334 template<class DocClass>
1335 DocClass * GetMergeDocForDiff(CMultiDocTemplate *pTemplate, CDirDoc *pDirDoc, int nFiles, bool bMakeVisible)
1337 // Create a new merge doc
1338 DocClass::m_nBuffersTemp = nFiles;
1339 DocClass *pMergeDoc = static_cast<DocClass*>(pTemplate->OpenDocumentFile(nullptr, bMakeVisible));
1340 if (pMergeDoc != nullptr)
1342 pDirDoc->AddMergeDoc(pMergeDoc);
1343 pMergeDoc->SetDirDoc(pDirDoc);
1348 // Clear the item count in the main status pane
1349 void CMainFrame::ClearStatusbarItemCount()
1351 m_wndStatusBar.SetPaneText(2, _T(""));
1355 * @brief Generate patch from files selected.
1357 * Creates a patch from selected files in active directory compare, or
1358 * active file compare. Files in file compare must be saved before
1361 void CMainFrame::OnToolsGeneratePatch()
1364 patcher.CreatePatch();
1367 void CMainFrame::OnDropFiles(const std::vector<String>& dropped_files)
1369 PathContext tFiles(dropped_files);
1370 const size_t fileCount = tFiles.GetSize();
1372 bool recurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
1373 // Do a reverse comparison with the current 'Include Subfolders' settings when pressing Control key
1374 if (!!::GetAsyncKeyState(VK_CONTROL))
1377 // If user has <Shift> pressed with one file selected,
1378 // assume it is an archive and set filenames to same
1379 if (::GetAsyncKeyState(VK_SHIFT) < 0 && fileCount == 1)
1381 tFiles.SetRight(tFiles[0]);
1384 // Check if they dropped a project file
1385 DWORD dwFlags[3] = {FFILEOPEN_NONE, FFILEOPEN_NONE, FFILEOPEN_NONE};
1388 if (theApp.IsProjectFile(tFiles[0]))
1390 theApp.LoadAndOpenProjectFile(tFiles[0]);
1393 if (IsConflictFile(tFiles[0]))
1395 DoOpenConflict(tFiles[0], nullptr, true);
1400 DoFileOpen(&tFiles, dwFlags, nullptr, _T(""), recurse);
1403 void CMainFrame::OnPluginUnpackMode(UINT nID )
1407 case ID_UNPACK_MANUAL:
1408 FileTransform::g_UnpackerMode = PLUGIN_MANUAL;
1410 case ID_UNPACK_AUTO:
1411 FileTransform::g_UnpackerMode = PLUGIN_AUTO;
1414 GetOptionsMgr()->SaveOption(OPT_PLUGINS_UNPACKER_MODE, FileTransform::g_UnpackerMode);
1417 void CMainFrame::OnUpdatePluginUnpackMode(CCmdUI* pCmdUI)
1419 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
1421 if (pCmdUI->m_nID == ID_UNPACK_MANUAL)
1422 pCmdUI->SetRadio(PLUGIN_MANUAL == FileTransform::g_UnpackerMode);
1423 if (pCmdUI->m_nID == ID_UNPACK_AUTO)
1424 pCmdUI->SetRadio(PLUGIN_AUTO == FileTransform::g_UnpackerMode);
1426 void CMainFrame::OnPluginPrediffMode(UINT nID )
1430 case ID_PREDIFFER_MANUAL:
1431 FileTransform::g_PredifferMode = PLUGIN_MANUAL;
1433 case ID_PREDIFFER_AUTO:
1434 FileTransform::g_PredifferMode = PLUGIN_AUTO;
1437 PrediffingInfo infoPrediffer;
1438 for (auto pMergeDoc : GetAllMergeDocs())
1439 pMergeDoc->SetPrediffer(&infoPrediffer);
1440 for (auto pDirDoc : GetAllDirDocs())
1441 pDirDoc->GetPluginManager().SetPrediffSettingAll(FileTransform::g_PredifferMode);
1442 GetOptionsMgr()->SaveOption(OPT_PLUGINS_PREDIFFER_MODE, FileTransform::g_PredifferMode);
1445 void CMainFrame::OnUpdatePluginPrediffMode(CCmdUI* pCmdUI)
1447 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
1449 if (pCmdUI->m_nID == ID_PREDIFFER_MANUAL)
1450 pCmdUI->SetRadio(PLUGIN_MANUAL == FileTransform::g_PredifferMode);
1451 if (pCmdUI->m_nID == ID_PREDIFFER_AUTO)
1452 pCmdUI->SetRadio(PLUGIN_AUTO == FileTransform::g_PredifferMode);
1455 * @brief Called when "Reload Plugins" item is updated
1457 void CMainFrame::OnUpdateReloadPlugins(CCmdUI* pCmdUI)
1459 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
1462 void CMainFrame::OnReloadPlugins()
1464 // delete all script interfaces
1465 // (interfaces will be created again automatically when WinMerge needs them)
1466 CAllThreadsScripts::GetActiveSet()->FreeAllScripts();
1468 // update the editor scripts submenu
1469 HMENU scriptsSubmenu = GetScriptsSubmenu(m_hMenuDefault);
1470 if (scriptsSubmenu != nullptr)
1471 CMergeEditView::createScriptsSubmenu(scriptsSubmenu);
1472 UpdatePrediffersMenu();
1475 /** @brief Return active merge edit view, if can figure it out/is available */
1476 CMergeEditView * CMainFrame::GetActiveMergeEditView()
1478 // NB: GetActiveDocument does not return the Merge Doc
1479 // even when the merge edit view is in front
1480 // NB: CMergeEditFrame::GetActiveView returns `nullptr` when location view active
1481 // So we have this rather complicated logic to try to get a merge edit view
1482 // We look at the front child window, which should be a frame
1483 // and we can get a MergeEditView from it, if it is a CMergeEditFrame
1484 // (DirViews use a different frame type)
1485 CMergeEditFrame * pFrame = dynamic_cast<CMergeEditFrame *>(GetActiveFrame());
1486 if (pFrame == nullptr) return nullptr;
1487 // Try to get the active MergeEditView (ie, left or right)
1488 if (pFrame->GetActiveView() != nullptr && pFrame->GetActiveView()->IsKindOf(RUNTIME_CLASS(CMergeEditView)))
1490 return dynamic_cast<CMergeEditView *>(pFrame->GetActiveView());
1492 return pFrame->GetMergeDoc()->GetActiveMergeView();
1495 void CMainFrame::UpdatePrediffersMenu()
1497 CMenu* menu = GetMenu();
1498 if (menu == nullptr)
1503 HMENU hMainMenu = menu->m_hMenu;
1504 HMENU prediffersSubmenu = GetPrediffersSubmenu(hMainMenu);
1505 if (prediffersSubmenu != nullptr)
1507 CMergeEditView * pEditView = GetActiveMergeEditView();
1508 if (pEditView != nullptr)
1509 pEditView->createPrediffersSubmenu(prediffersSubmenu);
1512 // no view or dir view : display an empty submenu
1513 int i = GetMenuItemCount(prediffersSubmenu);
1515 ::DeleteMenu(prediffersSubmenu, 0, MF_BYPOSITION);
1516 ::AppendMenu(prediffersSubmenu, MF_SEPARATOR, 0, nullptr);
1522 * @brief Save WinMerge configuration and info to file
1524 void CMainFrame::OnSaveConfigData()
1526 CConfigLog configLog;
1529 if (configLog.WriteLogFile(sError))
1531 String sFileName = configLog.GetFileName();
1532 theApp.OpenFileToExternalEditor(sFileName);
1536 String sFileName = configLog.GetFileName();
1537 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sFileName, sError);
1538 AfxMessageBox(msg.c_str(), MB_OK | MB_ICONSTOP);
1543 * @brief Open two new empty docs, 'Scratchpads'
1545 * Allows user to open two empty docs, to paste text to
1546 * compare from clipboard.
1547 * @note File filenames are set emptys and filedescriptors
1548 * are loaded from resource.
1549 * @sa CMergeDoc::OpenDocs()
1550 * @sa CMergeDoc::TrySaveAs()
1552 void CMainFrame::FileNew(int nPanes)
1554 CDirDoc *pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
1556 // Load emptyfile descriptors and open empty docs
1557 // Use default codepage
1558 DWORD dwFlags[3] = {0, 0, 0};
1560 FileLocation fileloc[3];
1563 strDesc[0] = _("Untitled left");
1564 strDesc[1] = _("Untitled right");
1565 fileloc[0].encoding.SetCodepage(ucr::getDefaultCodepage());
1566 fileloc[1].encoding.SetCodepage(ucr::getDefaultCodepage());
1567 ShowMergeDoc(pDirDoc, 2, fileloc, dwFlags, strDesc);
1571 strDesc[0] = _("Untitled left");
1572 strDesc[1] = _("Untitled middle");
1573 strDesc[2] = _("Untitled right");
1574 fileloc[0].encoding.SetCodepage(ucr::getDefaultCodepage());
1575 fileloc[1].encoding.SetCodepage(ucr::getDefaultCodepage());
1576 fileloc[2].encoding.SetCodepage(ucr::getDefaultCodepage());
1577 ShowMergeDoc(pDirDoc, 3, fileloc, dwFlags, strDesc);
1582 * @brief Open two new empty docs, 'Scratchpads'
1584 * Allows user to open two empty docs, to paste text to
1585 * compare from clipboard.
1586 * @note File filenames are set emptys and filedescriptors
1587 * are loaded from resource.
1588 * @sa CMergeDoc::OpenDocs()
1589 * @sa CMergeDoc::TrySaveAs()
1591 void CMainFrame::OnFileNew()
1596 void CMainFrame::OnFileNew3()
1602 * @brief Open Filters dialog
1604 void CMainFrame::OnToolsFilters()
1606 String title = _("Filters");
1607 CPropertySheet sht(title.c_str());
1608 LineFiltersDlg lineFiltersDlg;
1609 FileFiltersDlg fileFiltersDlg;
1610 std::unique_ptr<LineFiltersList> lineFilters(new LineFiltersList());
1611 String selectedFilter;
1612 const String origFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1613 sht.AddPage(&fileFiltersDlg);
1614 sht.AddPage(&lineFiltersDlg);
1615 sht.m_psh.dwFlags |= PSH_NOAPPLYNOW; // Hide 'Apply' button since we don't need it
1617 // Make sure all filters are up-to-date
1618 theApp.m_pGlobalFileFilter->ReloadUpdatedFilters();
1620 fileFiltersDlg.SetFilterArray(theApp.m_pGlobalFileFilter->GetFileFilters(selectedFilter));
1621 fileFiltersDlg.SetSelected(selectedFilter);
1622 const bool lineFiltersEnabledOrig = GetOptionsMgr()->GetBool(OPT_LINEFILTER_ENABLED);
1623 lineFiltersDlg.m_bIgnoreRegExp = lineFiltersEnabledOrig;
1625 lineFilters->CloneFrom(theApp.m_pLineFilters.get());
1626 lineFiltersDlg.SetList(lineFilters.get());
1628 if (sht.DoModal() == IDOK)
1630 String strNone = _("<None>");
1631 String path = fileFiltersDlg.GetSelected();
1632 if (path.find(strNone) != String::npos)
1634 // Don't overwrite mask we already have
1635 if (!theApp.m_pGlobalFileFilter->IsUsingMask())
1637 String sFilter(_T("*.*"));
1638 theApp.m_pGlobalFileFilter->SetFilter(sFilter);
1639 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, sFilter);
1644 theApp.m_pGlobalFileFilter->SetFileFilterPath(path);
1645 theApp.m_pGlobalFileFilter->UseMask(false);
1646 String sFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1647 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, sFilter);
1649 bool linefiltersEnabled = lineFiltersDlg.m_bIgnoreRegExp;
1650 GetOptionsMgr()->SaveOption(OPT_LINEFILTER_ENABLED, linefiltersEnabled);
1652 // Check if compare documents need rescanning
1653 bool bFileCompareRescan = false;
1654 bool bFolderCompareRescan = false;
1655 CFrameWnd * pFrame = GetActiveFrame();
1656 FRAMETYPE frame = GetFrameType(pFrame);
1657 if (frame == FRAME_FILE)
1659 if (lineFiltersEnabledOrig != linefiltersEnabled ||
1660 !theApp.m_pLineFilters->Compare(lineFilters.get()))
1662 bFileCompareRescan = true;
1665 else if (frame == FRAME_FOLDER)
1667 const String newFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1668 if (lineFiltersEnabledOrig != linefiltersEnabled ||
1669 !theApp.m_pLineFilters->Compare(lineFilters.get()) || origFilter != newFilter)
1671 int res = LangMessageBox(IDS_FILTERCHANGED, MB_ICONWARNING | MB_YESNO);
1673 bFolderCompareRescan = true;
1677 // Save new filters before (possibly) rescanning
1678 theApp.m_pLineFilters->CloneFrom(lineFilters.get());
1679 theApp.m_pLineFilters->SaveFilters();
1681 if (bFileCompareRescan)
1683 for (auto pMergeDoc : GetAllMergeDocs())
1684 pMergeDoc->FlushAndRescan(true);
1686 else if (bFolderCompareRescan)
1688 for (auto pDirDoc : GetAllDirDocs())
1695 * @brief Open Filters dialog.
1697 void CMainFrame::SelectFilter()
1703 * @brief Closes application with ESC.
1705 * Application is closed if:
1706 * - 'Close Windows with ESC' option is enabled and
1707 * there is no open document windows
1708 * - '-e' commandline switch is given
1710 BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
1712 // Check if we got 'ESC pressed' -message
1713 if ((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
1715 if (theApp.m_bEscShutdown && m_wndTabBar.GetItemCount() <= 1)
1717 AfxGetMainWnd()->SendMessage(WM_COMMAND, ID_APP_EXIT);
1720 else if (GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC) == 1 && m_wndTabBar.GetItemCount() == 0)
1722 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_APP_EXIT);
1727 if (WM_KEYDOWN == pMsg->message && VK_TAB == pMsg->wParam && GetAsyncKeyState(VK_CONTROL) < 0 && m_arrChild.GetSize() > 1)
1729 CWindowsManagerDialog* pDlg = new CWindowsManagerDialog;
1730 pDlg->Create(CWindowsManagerDialog::IDD, this);
1731 pDlg->ShowWindow(SW_SHOW);
1735 return CMDIFrameWnd::PreTranslateMessage(pMsg);
1739 * @brief Show/hide statusbar.
1741 void CMainFrame::OnViewStatusBar()
1743 bool bShow = !GetOptionsMgr()->GetBool(OPT_SHOW_STATUSBAR);
1744 GetOptionsMgr()->SaveOption(OPT_SHOW_STATUSBAR, bShow);
1746 CMDIFrameWnd::ShowControlBar(&m_wndStatusBar, bShow, 0);
1750 * @brief Updates "Show Tabbar" menuitem.
1752 void CMainFrame::OnUpdateViewTabBar(CCmdUI* pCmdUI)
1754 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR));
1758 * @brief Show/hide tabbar.
1760 void CMainFrame::OnViewTabBar()
1762 bool bShow = !GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR);
1763 GetOptionsMgr()->SaveOption(OPT_SHOW_TABBAR, bShow);
1765 CMDIFrameWnd::ShowControlBar(&m_wndTabBar, bShow, 0);
1769 * @brief Updates "Automatically Resize Panes" menuitem.
1771 void CMainFrame::OnUpdateResizePanes(CCmdUI* pCmdUI)
1773 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_RESIZE_PANES));
1778 * @brief Enable/disable automatic pane resizing.
1780 void CMainFrame::OnResizePanes()
1782 bool bResize = !GetOptionsMgr()->GetBool(OPT_RESIZE_PANES);
1783 GetOptionsMgr()->SaveOption(OPT_RESIZE_PANES, bResize);
1784 // TODO: Introduce a common merge frame superclass?
1785 CFrameWnd *pActiveFrame = GetActiveFrame();
1786 if (CMergeEditFrame *pFrame = DYNAMIC_DOWNCAST(CMergeEditFrame, pActiveFrame))
1788 pFrame->UpdateAutoPaneResize();
1790 pFrame->UpdateSplitter();
1792 else if (CHexMergeFrame *pFrame1 = DYNAMIC_DOWNCAST(CHexMergeFrame, pActiveFrame))
1794 pFrame1->UpdateAutoPaneResize();
1796 pFrame1->UpdateSplitter();
1801 * @brief Open project-file.
1803 void CMainFrame::OnFileOpenProject()
1807 // get the default projects path
1808 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
1809 if (!SelectFile(GetSafeHwnd(), sFilepath, true, strProjectPath.c_str(), _T(""),
1810 _("WinMerge Project Files (*.WinMerge)|*.WinMerge||")))
1813 strProjectPath = paths::GetParentPath(sFilepath);
1814 // store this as the new project path
1815 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
1817 theApp.LoadAndOpenProjectFile(sFilepath);
1821 * @brief Receive command line from another instance.
1823 * This function receives command line when only single-instance
1824 * is allowed. New instance tried to start sends its command line
1825 * to here so we can open paths it was meant to.
1827 LRESULT CMainFrame::OnCopyData(WPARAM wParam, LPARAM lParam)
1829 COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;
1830 LPCTSTR pchData = (LPCTSTR)pCopyData->lpData;
1831 // Bail out if data isn't zero-terminated
1832 DWORD cchData = pCopyData->cbData / sizeof(TCHAR);
1833 if (cchData == 0 || pchData[cchData - 1] != _T('\0'))
1836 MergeCmdLineInfo cmdInfo(pchData);
1837 theApp.ApplyCommandLineConfigOptions(cmdInfo);
1838 theApp.ParseArgsAndDoOpen(cmdInfo, this);
1842 LRESULT CMainFrame::OnUser1(WPARAM wParam, LPARAM lParam)
1844 CFrameWnd * pFrame = GetActiveFrame();
1845 if (pFrame != nullptr)
1847 IMergeDoc *pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame->GetActiveDocument());
1848 if (pMergeDoc == nullptr)
1849 pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
1850 if (pMergeDoc != nullptr)
1851 pMergeDoc->CheckFileChanged();
1857 * @brief Close all open windows.
1859 * Asks about saving unsaved files and then closes all open windows.
1861 void CMainFrame::OnWindowCloseAll()
1863 CMDIChildWnd *pChild = MDIGetActive();
1864 while (pChild != nullptr)
1867 if ((pDoc = pChild->GetActiveDocument()) != nullptr)
1869 if (!pDoc->SaveModified())
1871 pDoc->OnCloseDocument();
1873 else if (GetFrameType(pChild) == FRAME_IMGFILE)
1875 if (!static_cast<CImgMergeFrame *>(pChild)->CloseNow())
1880 pChild->DestroyWindow();
1882 pChild = MDIGetActive();
1888 * @brief Enables Window/Close All item if there are open windows.
1890 void CMainFrame::OnUpdateWindowCloseAll(CCmdUI* pCmdUI)
1892 pCmdUI->Enable(MDIGetActive() != nullptr);
1896 * @brief Access to the singleton main frame (where we have some globals)
1898 CMainFrame * GetMainFrame()
1900 CWnd * mainwnd = AfxGetMainWnd();
1901 ASSERT(mainwnd != nullptr);
1902 CMainFrame *pMainframe = dynamic_cast<CMainFrame*>(mainwnd);
1903 ASSERT(pMainframe != nullptr);
1908 * @brief Opens dialog for user to Load, edit and save project files.
1909 * This dialog gets current compare paths and filter (+other properties
1910 * possible in project files) and initializes the dialog with them.
1912 void CMainFrame::OnSaveProject()
1914 if (m_pMenus[MENU_OPENVIEW] == nullptr)
1915 theApp.m_pOpenTemplate->m_hMenuShared = NewOpenViewMenu();
1916 COpenDoc *pOpenDoc = static_cast<COpenDoc *>(theApp.m_pOpenTemplate->CreateNewDocument());
1919 CFrameWnd * pFrame = GetActiveFrame();
1920 FRAMETYPE frame = GetFrameType(pFrame);
1922 if (frame == FRAME_FILE)
1924 CMergeDoc * pMergeDoc = static_cast<CMergeDoc *>(pFrame->GetActiveDocument());
1925 pOpenDoc->m_files = pMergeDoc->m_filePaths;
1926 for (int pane = 0; pane < pOpenDoc->m_files.GetSize(); ++pane)
1927 pOpenDoc->m_dwFlags[pane] = FFILEOPEN_PROJECT | (pMergeDoc->m_ptBuf[pane]->GetReadOnly() ? FFILEOPEN_PROJECT : 0);
1928 pOpenDoc->m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
1929 pOpenDoc->m_strExt = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1931 else if (frame == FRAME_FOLDER)
1933 // Get paths currently in compare
1934 const CDirDoc * pDoc = static_cast<const CDirDoc*>(pFrame->GetActiveDocument());
1935 const CDiffContext& ctxt = pDoc->GetDiffContext();
1937 // Set-up the dialog
1938 for (int pane = 0; pane < ctxt.GetCompareDirs(); ++pane)
1940 pOpenDoc->m_dwFlags[pane] = FFILEOPEN_PROJECT | (pDoc->GetReadOnly(pane) ? FFILEOPEN_READONLY : 0);
1941 pOpenDoc->m_files.SetPath(pane, paths::AddTrailingSlash(ctxt.GetNormalizedPath(pane)));
1943 pOpenDoc->m_bRecurse = ctxt.m_bRecursive;
1944 pOpenDoc->m_strExt = static_cast<FileFilterHelper *>(ctxt.m_piFilterGlobal)->GetFilterNameOrMask();
1947 CFrameWnd *pOpenFrame = theApp.m_pOpenTemplate->CreateNewFrame(pOpenDoc, nullptr);
1948 theApp.m_pOpenTemplate->InitialUpdateFrame(pOpenFrame, pOpenDoc);
1952 * @brief Start flashing window if window is inactive.
1954 void CMainFrame::StartFlashing()
1956 CWnd * activeWindow = GetActiveWindow();
1957 if (activeWindow != this)
1958 FlashWindowEx(FLASHW_ALL | FLASHW_TIMERNOFG, 3, 0);
1961 #if _MFC_VER > 0x0600
1962 void CMainFrame::OnActivateApp(BOOL bActive, DWORD dwThreadID)
1964 void CMainFrame::OnActivateApp(BOOL bActive, HTASK hTask)
1967 #if _MFC_VER > 0x0600
1968 CMDIFrameWnd::OnActivateApp(bActive, dwThreadID);
1970 CMDIFrameWnd::OnActivateApp(bActive, hTask);
1973 CFrameWnd * pFrame = GetActiveFrame();
1974 if (pFrame != nullptr)
1976 IMergeDoc *pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame->GetActiveDocument());
1977 if (pMergeDoc == nullptr)
1978 pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
1979 if (pMergeDoc != nullptr)
1980 PostMessage(WM_USER+1);
1984 BOOL CMainFrame::CreateToolbar()
1986 if (!m_wndToolBar.CreateEx(this) ||
1987 !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
1992 if (!m_wndReBar.Create(this, RBS_BANDBORDERS,
1993 WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CBRS_ALIGN_TOP))
1998 VERIFY(m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT|TBSTYLE_TRANSPARENT));
2000 // Remove this if you don't want tool tips or a resizable toolbar
2001 m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
2002 CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
2003 m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS);
2005 m_wndReBar.AddBar(&m_wndToolBar);
2007 LoadToolbarImages();
2011 int index = m_wndToolBar.GetToolBarCtrl().CommandToIndex(ID_OPTIONS);
2012 m_wndToolBar.GetButtonInfo(index, nID, nStyle, iImage);
2013 nStyle |= TBSTYLE_DROPDOWN;
2014 m_wndToolBar.SetButtonInfo(index, nID, nStyle, iImage);
2016 if (!GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR))
2018 CMDIFrameWnd::ShowControlBar(&m_wndToolBar, false, 0);
2024 /** @brief Load toolbar images from the resource. */
2025 void CMainFrame::LoadToolbarImages()
2027 const int toolbarNewImgSize = MulDiv(16, GetSystemMetrics(SM_CXSMICON), 16) * (1 + GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE));
2028 const int toolbarOrgImgSize = toolbarNewImgSize <= 20 ? 16 : 32;
2029 CToolBarCtrl& BarCtrl = m_wndToolBar.GetToolBarCtrl();
2031 m_ToolbarImages[TOOLBAR_IMAGES_ENABLED].Detach();
2032 m_ToolbarImages[TOOLBAR_IMAGES_DISABLED].Detach();
2033 CSize sizeButton(0, 0);
2035 LoadToolbarImageList(toolbarOrgImgSize, toolbarNewImgSize,
2036 toolbarOrgImgSize <= 16 ? IDB_TOOLBAR_ENABLED : IDB_TOOLBAR_ENABLED32,
2037 false, m_ToolbarImages[TOOLBAR_IMAGES_ENABLED]);
2038 LoadToolbarImageList(toolbarOrgImgSize, toolbarNewImgSize,
2039 toolbarOrgImgSize <= 16 ? IDB_TOOLBAR_ENABLED : IDB_TOOLBAR_ENABLED32,
2040 true, m_ToolbarImages[TOOLBAR_IMAGES_DISABLED]);
2042 sizeButton = CSize(toolbarNewImgSize + 8, toolbarNewImgSize + 8);
2044 BarCtrl.SetButtonSize(sizeButton);
2045 if (CImageList *pImgList = BarCtrl.SetImageList(&m_ToolbarImages[TOOLBAR_IMAGES_ENABLED]))
2046 pImgList->DeleteImageList();
2047 if (CImageList *pImgList = BarCtrl.SetDisabledImageList(&m_ToolbarImages[TOOLBAR_IMAGES_DISABLED]))
2048 pImgList->DeleteImageList();
2050 // resize the rebar.
2051 REBARBANDINFO rbbi = { sizeof REBARBANDINFO };
2052 rbbi.fMask = RBBIM_CHILDSIZE;
2053 rbbi.cyMinChild = sizeButton.cy;
2054 m_wndReBar.GetReBarCtrl().SetBandInfo(0, &rbbi);
2059 * @brief Load a transparent 32-bit color image list.
2061 static void LoadHiColImageList(UINT nIDResource, int nWidth, int nHeight, int nNewWidth, int nNewHeight, int nCount, bool bGrayscale, CImageList& ImgList)
2063 auto convert24bitImageTo32bit = [](int width, int height, bool grayscale, const BYTE* src, BYTE* dst)
2065 for (int y = 0; y < height; ++y)
2067 const BYTE* pSrc = src + y * ((width * 3 * 4 + 3) / 4);
2068 BYTE* pDst = dst + (height - 1 - y) * ((width * 4 * 4 + 3) / 4);
2071 for (int x = 0; x < width; ++x)
2073 if (pSrc[x * 3] == 0xff && pSrc[x * 3 + 1] == 0 && pSrc[x * 3 + 2] == 0xff) // rgb(0xff, 0, 0xff) == mask color
2076 pDst[x * 4 + 1] = 0;
2077 pDst[x * 4 + 2] = 0;
2078 pDst[x * 4 + 3] = 0;
2082 pDst[x * 4 + 0] = pSrc[x * 3 + 0];
2083 pDst[x * 4 + 1] = pSrc[x * 3 + 1];
2084 pDst[x * 4 + 2] = pSrc[x * 3 + 2];
2085 pDst[x * 4 + 3] = 0xff;
2091 for (int x = 0; x < width; ++x)
2093 if (pSrc[x * 3] == 0xff && pSrc[x * 3 + 1] == 0 && pSrc[x * 3 + 2] == 0xff) // rgb(0xff, 0, 0xff) == mask color
2096 pDst[x * 4 + 1] = 0;
2097 pDst[x * 4 + 2] = 0;
2098 pDst[x * 4 + 3] = 0;
2102 const BYTE b = pSrc[x * 3];
2103 const BYTE g = pSrc[x * 3 + 1];
2104 const BYTE r = pSrc[x * 3 + 2];
2105 const BYTE gray = static_cast<BYTE>(
2106 (static_cast<int>(0.114 * 256) * (((255 - b) >> 1) + b)
2107 + static_cast<int>(0.587 * 256) * (((255 - g) >> 1) + g)
2108 + static_cast<int>(0.299 * 256) * (((255 - r) >> 1) + r)) >> 8);
2109 pDst[x * 4 + 0] = gray;
2110 pDst[x * 4 + 1] = gray;
2111 pDst[x * 4 + 2] = gray;
2112 pDst[x * 4 + 3] = 0xff;
2119 auto createImageListFromBitmap = [](CImageList& imgList, Gdiplus::Bitmap& bitmap, int width, int height, int count)
2123 bitmap.GetHBITMAP(Gdiplus::Color::Transparent, &hBitmap);
2126 VERIFY(imgList.Create(width, height, ILC_COLOR32, count, 0));
2127 int nIndex = imgList.Add(&bm, nullptr);
2128 ASSERT(-1 != nIndex);
2132 img.Attach((HBITMAP)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(nIDResource), IMAGE_BITMAP, nWidth * nCount, nHeight, LR_CREATEDIBSECTION), ATL::CImage::DIBOR_TOPDOWN);
2133 const int stride = (nWidth * nCount * 4 * 4 + 3) / 4;
2134 std::vector<BYTE> buf(stride * nHeight);
2135 convert24bitImageTo32bit(nWidth * nCount, nHeight, bGrayscale, reinterpret_cast<BYTE*>(img.GetBits()), buf.data());
2137 if (nWidth != nNewWidth && nHeight != nNewHeight)
2139 Gdiplus::Bitmap bitmapSrc(nWidth * nCount, nHeight, stride, PixelFormat32bppPARGB, buf.data());
2140 Gdiplus::Bitmap bitmapDst(nNewWidth * nCount, nNewHeight, PixelFormat32bppPARGB);
2141 Gdiplus::Graphics dcDst(&bitmapDst);
2142 dcDst.SetInterpolationMode(Gdiplus::InterpolationMode::InterpolationModeHighQualityBicubic);
2143 dcDst.DrawImage(&bitmapSrc, 0, 0, nNewWidth * nCount, nNewHeight);
2145 createImageListFromBitmap(ImgList, bitmapDst, nNewWidth, nNewHeight, nCount);
2149 Gdiplus::Bitmap bitmapDst(nWidth * nCount, nHeight, stride, PixelFormat32bppPARGB, buf.data());
2151 createImageListFromBitmap(ImgList, bitmapDst, nNewWidth, nNewHeight, nCount);
2156 * @brief Load toolbar image list.
2158 static void LoadToolbarImageList(int orgImageWidth, int newImageWidth, UINT nIDResource, bool bGrayscale, CImageList& ImgList)
2160 const int ImageCount = 22;
2161 const int orgImageHeight = orgImageWidth - 1;
2162 const int newImageHeight = newImageWidth - 1;
2163 LoadHiColImageList(nIDResource, orgImageWidth, orgImageHeight, newImageWidth, newImageHeight, ImageCount, bGrayscale, ImgList);
2167 * @brief Called when the document title is modified.
2169 void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
2171 CFrameWnd::OnUpdateFrameTitle(bAddToTitle);
2173 if (m_wndTabBar.m_hWnd != nullptr)
2174 m_wndTabBar.UpdateTabs();
2177 /** @brief Show none/small/big/huge toolbar. */
2178 void CMainFrame::OnToolbarSize(UINT id)
2180 if (id == ID_TOOLBAR_NONE)
2182 GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, false);
2183 CMDIFrameWnd::ShowControlBar(&m_wndToolBar, false, 0);
2187 GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, true);
2188 GetOptionsMgr()->SaveOption(OPT_TOOLBAR_SIZE, id - ID_TOOLBAR_SMALL);
2190 LoadToolbarImages();
2192 CMDIFrameWnd::ShowControlBar(&m_wndToolBar, true, 0);
2196 /** @brief Show none/small/big/huge toolbar. */
2197 void CMainFrame::OnUpdateToolbarSize(CCmdUI *pCmdUI)
2199 if (!GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR))
2200 pCmdUI->SetRadio(pCmdUI->m_nID == ID_TOOLBAR_NONE);
2202 pCmdUI->SetRadio((pCmdUI->m_nID - ID_TOOLBAR_SMALL) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE)));
2205 /** @brief Lang aware version of CFrameWnd::OnToolTipText() */
2206 BOOL CMainFrame::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
2208 ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);
2210 // need to handle both ANSI and UNICODE versions of the message
2211 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
2212 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
2215 UINT_PTR nID = pNMHDR->idFrom;
2216 if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
2217 pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
2219 // idFrom is actually the HWND of the tool
2220 nID = ::GetDlgCtrlID((HWND)nID);
2223 if (nID != 0) // will be zero on a separator
2225 strFullText = theApp.LoadString(static_cast<UINT>(nID));
2226 // don't handle the message if no string resource found
2227 if (strFullText.empty())
2230 // this is the command id, not the button index
2231 AfxExtractSubString(strTipText, strFullText.c_str(), 1, '\n');
2233 if (pNMHDR->code == TTN_NEEDTEXTA)
2234 _wcstombsz(pTTTA->szText, strTipText, static_cast<ULONG>(std::size(pTTTA->szText)));
2236 lstrcpyn(pTTTW->szText, strTipText, static_cast<int>(std::size(pTTTW->szText)));
2239 // bring the tooltip window above other popup windows
2240 ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
2241 SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
2243 return TRUE; // message was handled
2247 * @brief Ask user for close confirmation when closing the mainframe.
2248 * This function asks if user wants to close multiple open windows when user
2249 * selects (perhaps accidentally) to close WinMerge (application).
2250 * @return true if user agreeds to close all windows.
2252 bool CMainFrame::AskCloseConfirmation()
2254 const DirDocList &dirdocs = GetAllDirDocs();
2255 const MergeDocList &mergedocs = GetAllMergeDocs();
2258 const size_t count = dirdocs.GetCount() + mergedocs.GetCount();
2261 // Check that we don't have one empty dirdoc + mergedoc situation.
2262 // That happens since we open "hidden" dirdoc for every file compare.
2263 if (dirdocs.GetCount() == 1)
2265 CDirDoc *pDoc = dirdocs.GetHead();
2266 if (!pDoc->HasDiffs())
2269 ret = LangMessageBox(IDS_CLOSEALL_WINDOWS, MB_YESNO | MB_ICONWARNING);
2271 return (ret == IDYES);
2275 * @brief Shows the release notes for user.
2276 * This function opens release notes HTML document into browser.
2278 void CMainFrame::OnHelpReleasenotes()
2280 const String sPath = paths::ConcatPath(env::GetProgPath(), RelNotes);
2281 ShellExecute(nullptr, _T("open"), sPath.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
2285 * @brief Shows the translations page.
2286 * This function opens translations page URL into browser.
2288 void CMainFrame::OnHelpTranslations()
2290 ShellExecute(nullptr, _T("open"), TranslationsUrl, nullptr, nullptr, SW_SHOWNORMAL);
2294 * @brief Called when user selects File/Open Conflict...
2296 void CMainFrame::OnFileOpenConflict()
2298 String conflictFile;
2299 if (SelectFile(GetSafeHwnd(), conflictFile))
2301 DoOpenConflict(conflictFile);
2306 * @brief Select and open conflict file for resolving.
2307 * This function lets user to select conflict file to resolve.
2308 * Then we parse conflict file to two files to "merge" and
2309 * save resulting file over original file.
2311 * Set left-side file read-only as it is the repository file which cannot
2312 * be modified anyway. Right-side file is user's file which is set as
2313 * modified by default so user can just save it and accept workspace
2314 * file as resolved file.
2315 * @param [in] conflictFile Full path to conflict file to open.
2316 * @param [in] checked If true, do not check if it really is project file.
2317 * @return `true` if conflict file was opened for resolving.
2319 bool CMainFrame::DoOpenConflict(const String& conflictFile, const String strDesc[] /*= nullptr*/, bool checked /*= false*/)
2321 bool conflictCompared = false;
2325 bool confFile = IsConflictFile(conflictFile);
2328 String message = strutils::format_string1(_("The file\n%1\nis not a conflict file."), conflictFile);
2329 AfxMessageBox(message.c_str(), MB_ICONSTOP);
2334 // Create temp files and put them into the list,
2335 // from where they get deleted when MainFrame is deleted.
2336 String ext = paths::FindExtension(conflictFile);
2337 TempFilePtr wTemp(new TempFile());
2338 String workFile = wTemp->Create(_T("confw_"), ext);
2339 m_tempFiles.push_back(wTemp);
2340 TempFilePtr vTemp(new TempFile());
2341 String revFile = vTemp->Create(_T("confv_"), ext);
2342 m_tempFiles.push_back(vTemp);
2343 TempFilePtr bTemp(new TempFile());
2344 String baseFile = vTemp->Create(_T("confb_"), ext);
2345 m_tempFiles.push_back(bTemp);
2347 // Parse conflict file into two files.
2348 bool inners, threeWay;
2349 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
2350 bool success = ParseConflictFile(conflictFile, workFile, revFile, baseFile, iGuessEncodingType, inners, threeWay);
2354 // Open two parsed files to WinMerge, telling WinMerge to
2355 // save over original file (given as third filename).
2356 theApp.m_strSaveAsPath = conflictFile;
2359 String strDesc2[2] = {
2360 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Theirs File"),
2361 (strDesc && !strDesc[2].empty()) ? strDesc[2] : _("Mine File") };
2362 DWORD dwFlags[2] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU | FFILEOPEN_MODIFIED};
2363 PathContext tmpPathContext(revFile, workFile);
2364 conflictCompared = DoFileOpen(&tmpPathContext, dwFlags, strDesc2);
2368 String strDesc3[3] = {
2369 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Base File"),
2370 (strDesc && !strDesc[1].empty()) ? strDesc[1] : _("Theirs File"),
2371 (strDesc && !strDesc[2].empty()) ? strDesc[2] : _("Mine File") };
2372 PathContext tmpPathContext(baseFile, revFile, workFile);
2373 DWORD dwFlags[3] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU | FFILEOPEN_MODIFIED};
2374 conflictCompared = DoFileOpen(&tmpPathContext, dwFlags, strDesc3);
2379 LangMessageBox(IDS_ERROR_CONF_RESOLVE, MB_ICONSTOP);
2381 return conflictCompared;
2385 * @brief Get type of frame (File/Folder compare).
2386 * @param [in] pFrame Pointer to frame to check.
2387 * @return FRAMETYPE of the given frame.
2389 CMainFrame::FRAMETYPE CMainFrame::GetFrameType(const CFrameWnd * pFrame) const
2391 bool bMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame));
2392 bool bHexMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame));
2393 bool bImgMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame));
2394 bool bDirFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame));
2398 else if (bHexMergeFrame)
2399 return FRAME_HEXFILE;
2400 else if (bImgMergeFrame)
2401 return FRAME_IMGFILE;
2403 return FRAME_FOLDER;
2409 * @brief Show the plugins list dialog.
2411 void CMainFrame::OnPluginsList()
2417 void CMainFrame::OnDiffOptionsDropDown(NMHDR* pNMHDR, LRESULT* pResult)
2419 LPNMTOOLBAR pToolBar = reinterpret_cast<LPNMTOOLBAR>(pNMHDR);
2420 ClientToScreen(&(pToolBar->rcButton));
2422 VERIFY(menu.LoadMenu(IDR_POPUP_DIFF_OPTIONS));
2423 theApp.TranslateMenu(menu.m_hMenu);
2424 CMenu* pPopup = menu.GetSubMenu(0);
2425 if (pPopup != nullptr)
2427 pPopup->TrackPopupMenu(TPM_RIGHTALIGN | TPM_RIGHTBUTTON,
2428 pToolBar->rcButton.right, pToolBar->rcButton.bottom, this);
2432 void CMainFrame::OnDiffWhitespace(UINT nID)
2434 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, nID - IDC_DIFF_WHITESPACE_COMPARE);
2438 void CMainFrame::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
2440 pCmdUI->SetRadio((pCmdUI->m_nID - IDC_DIFF_WHITESPACE_COMPARE) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE)));
2444 void CMainFrame::OnDiffCaseSensitive()
2446 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE));
2450 void CMainFrame::OnUpdateDiffCaseSensitive(CCmdUI* pCmdUI)
2452 pCmdUI->SetCheck(!GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE));
2456 void CMainFrame::OnDiffIgnoreEOL()
2458 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL));
2462 void CMainFrame::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
2464 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL));
2468 void CMainFrame::OnDiffIgnoreCP()
2470 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE));
2474 void CMainFrame::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
2476 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE));
2480 void CMainFrame::OnIncludeSubfolders()
2482 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, !GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS));
2483 // Update all dirdoc settings
2484 for (auto pDirDoc : GetAllDirDocs())
2485 pDirDoc->RefreshOptions();
2486 for (auto pOpenDoc : GetAllOpenDocs())
2487 pOpenDoc->RefreshOptions();
2490 void CMainFrame::OnUpdateIncludeSubfolders(CCmdUI* pCmdUI)
2492 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS));
2496 void CMainFrame::OnCompareMethod(UINT nID)
2498 GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, nID - ID_COMPMETHOD_FULL_CONTENTS);
2501 void CMainFrame::OnUpdateCompareMethod(CCmdUI* pCmdUI)
2503 pCmdUI->SetRadio((pCmdUI->m_nID - ID_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_CMP_METHOD)));
2507 void CMainFrame::OnMRUs(UINT nID)
2509 std::vector<JumpList::Item> mrus = JumpList::GetRecentDocs(GetOptionsMgr()->GetInt(OPT_MRU_MAX));
2510 const size_t idx = nID - ID_MRU_FIRST;
2511 if (idx < mrus.size())
2513 MergeCmdLineInfo cmdInfo((_T("\"") + mrus[idx].path + _T("\" ") + mrus[idx].params).c_str());
2514 theApp.ParseArgsAndDoOpen(cmdInfo, this);
2518 void CMainFrame::OnUpdateNoMRUs(CCmdUI* pCmdUI)
2520 // append the MRU submenu
2521 HMENU hMenu = GetSubmenu(AfxGetMainWnd()->GetMenu()->m_hMenu, ID_FILE_NEW, false);
2522 if (hMenu == nullptr)
2526 size_t i = ::GetMenuItemCount(hMenu);
2528 ::DeleteMenu(hMenu, 0, MF_BYPOSITION);
2530 std::vector<JumpList::Item> mrus = JumpList::GetRecentDocs(GetOptionsMgr()->GetInt(OPT_MRU_MAX));
2532 if (mrus.size() == 0)
2534 // no script : create a <empty> entry
2535 ::AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, theApp.LoadString(IDS_NO_EDIT_SCRIPTS).c_str());
2539 // or fill in the submenu with the scripts names
2540 int ID = ID_MRU_FIRST; // first ID in menu
2541 for (i = 0 ; i < mrus.size() ; i++, ID++)
2542 ::AppendMenu(hMenu, MF_STRING, ID,
2543 ((i < 9 ? strutils::format(_T("&%d "), i+1) : strutils::format(_T("&%c "), 'a' + i - 9))
2544 + mrus[i].title).c_str());
2547 pCmdUI->Enable(true);
2551 * @brief Update plugin name
2552 * @param [in] pCmdUI UI component to update.
2554 void CMainFrame::OnUpdatePluginName(CCmdUI* pCmdUI)
2556 pCmdUI->SetText(_T(""));
2559 void CMainFrame::ReloadMenu()
2561 // set the menu of the main frame window
2562 UINT idMenu = IDR_MAINFRAME;
2563 CMergeApp *pApp = dynamic_cast<CMergeApp *> (AfxGetApp());
2564 CMainFrame * pMainFrame = dynamic_cast<CMainFrame *> ((CFrameWnd*)pApp->m_pMainWnd);
2565 HMENU hNewDefaultMenu = pMainFrame->NewDefaultMenu(idMenu);
2566 HMENU hNewMergeMenu = pMainFrame->NewMergeViewMenu();
2567 HMENU hNewImgMergeMenu = pMainFrame->NewImgMergeViewMenu();
2568 HMENU hNewDirMenu = pMainFrame->NewDirViewMenu();
2569 if (hNewDefaultMenu != nullptr && hNewMergeMenu != nullptr && hNewDirMenu != nullptr)
2571 // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
2572 CMenu * pNewDefaultMenu = CMenu::FromHandle(hNewDefaultMenu);
2573 CMenu * pNewMergeMenu = CMenu::FromHandle(hNewMergeMenu);
2574 CMenu * pNewImgMergeMenu = CMenu::FromHandle(hNewImgMergeMenu);
2575 CMenu * pNewDirMenu = CMenu::FromHandle(hNewDirMenu);
2577 CWnd *pFrame = CWnd::FromHandle(::GetWindow(pMainFrame->m_hWndMDIClient, GW_CHILD));
2578 while (pFrame != nullptr)
2580 if (pFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame)))
2581 static_cast<CMergeEditFrame *>(pFrame)->SetSharedMenu(hNewMergeMenu);
2582 if (pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame)))
2583 static_cast<CHexMergeFrame *>(pFrame)->SetSharedMenu(hNewMergeMenu);
2584 if (pFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame)))
2585 static_cast<CImgMergeFrame *>(pFrame)->SetSharedMenu(hNewImgMergeMenu);
2586 else if (pFrame->IsKindOf(RUNTIME_CLASS(COpenFrame)))
2587 static_cast<COpenFrame *>(pFrame)->SetSharedMenu(hNewDefaultMenu);
2588 else if (pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
2589 static_cast<CDirFrame *>(pFrame)->SetSharedMenu(hNewDirMenu);
2590 pFrame = pFrame->GetNextWindow();
2593 CFrameWnd *pActiveFrame = pMainFrame->GetActiveFrame();
2594 if (pActiveFrame != nullptr)
2596 if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame)))
2597 pMainFrame->MDISetMenu(pNewMergeMenu, nullptr);
2598 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame)))
2599 pMainFrame->MDISetMenu(pNewMergeMenu, nullptr);
2600 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame)))
2601 pMainFrame->MDISetMenu(pNewImgMergeMenu, nullptr);
2602 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
2603 pMainFrame->MDISetMenu(pNewDirMenu, nullptr);
2605 pMainFrame->MDISetMenu(pNewDefaultMenu, nullptr);
2608 pMainFrame->MDISetMenu(pNewDefaultMenu, nullptr);
2610 // Don't delete the old menu
2611 // There is a bug in BCMenu or in Windows98 : the new menu does not
2612 // appear correctly if we destroy the old one
2613 // if (pOldDefaultMenu != nullptr)
2614 // pOldDefaultMenu->DestroyMenu();
2615 // if (pOldMergeMenu != nullptr)
2616 // pOldMergeMenu->DestroyMenu();
2617 // if (pOldDirMenu = nullptr)
2618 // pOldDirMenu->DestroyMenu();
2620 // m_hMenuDefault is used to redraw the main menu when we close a child frame
2621 // if this child frame had a different menu
2622 pMainFrame->m_hMenuDefault = hNewDefaultMenu;
2623 pApp->m_pOpenTemplate->m_hMenuShared = hNewDefaultMenu;
2624 pApp->m_pDiffTemplate->m_hMenuShared = hNewMergeMenu;
2625 pApp->m_pDirTemplate->m_hMenuShared = hNewDirMenu;
2627 // force redrawing the menu bar
2628 pMainFrame->DrawMenuBar();
2632 void CMainFrame::UpdateDocTitle()
2634 CDocManager* pDocManager = AfxGetApp()->m_pDocManager;
2635 POSITION posTemplate = pDocManager->GetFirstDocTemplatePosition();
2636 ASSERT(posTemplate != nullptr);
2638 while (posTemplate != nullptr)
2640 CDocTemplate* pTemplate = pDocManager->GetNextDocTemplate(posTemplate);
2642 ASSERT(pTemplate != nullptr);
2644 for (auto pDoc : GetDocList(static_cast<CMultiDocTemplate *>(pTemplate)))
2646 static_cast<CDocument *>(const_cast<void *>(pDoc))->SetTitle(nullptr);
2647 ((CFrameWnd*)AfxGetApp()->m_pMainWnd)->OnUpdateFrameTitle(TRUE);
2652 void CMainFrame::OnAccelQuit()
2654 // TODO: Add your command handler code here
2656 SendMessage(WM_CLOSE);
2659 LRESULT CMainFrame::OnChildFrameAdded(WPARAM wParam, LPARAM lParam)
2661 for (int i = 0; i < m_arrChild.GetSize(); ++i)
2663 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
2669 m_arrChild.InsertAt(0, (CMDIChildWnd*)lParam);
2674 LRESULT CMainFrame::OnChildFrameRemoved(WPARAM wParam, LPARAM lParam)
2676 for (int i = 0; i < m_arrChild.GetSize(); ++i)
2678 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
2680 m_arrChild.RemoveAt(i);
2688 LRESULT CMainFrame::OnChildFrameActivate(WPARAM wParam, LPARAM lParam)
2690 for (int i = 0; i < m_arrChild.GetSize(); ++i)
2692 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
2694 CMDIChildWnd* pMDIChild = m_arrChild.GetAt(i);
2695 if (pMDIChild->IsIconic())
2696 pMDIChild->ShowWindow(SW_RESTORE);
2697 MDIActivate(pMDIChild);
2704 // put lParam as index 0 in m_arrChild
2705 LRESULT CMainFrame::OnChildFrameActivated(WPARAM wParam, LPARAM lParam)
2707 for (int i = 0; i < m_arrChild.GetSize(); ++i)
2709 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
2711 m_arrChild.RemoveAt(i);
2716 m_arrChild.InsertAt(0, (CMDIChildWnd*)lParam);