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 "SubstitutionFiltersList.h"
37 #include "ConflictFileParser.h"
38 #include "LineFiltersDlg.h"
39 #include "SubstitutionFiltersDlg.h"
41 #include "Environment.h"
42 #include "PatchTool.h"
44 #include "ConfigLog.h"
46 #include "Merge7zFormatMergePluginImpl.h"
47 #include "FileFiltersDlg.h"
48 #include "OptionsMgr.h"
49 #include "OptionsDef.h"
50 #include "codepage_detect.h"
52 #include "PreferencesDlg.h"
53 #include "FileOrFolderSelect.h"
54 #include "PluginsListDlg.h"
55 #include "SelectPluginDlg.h"
56 #include "stringdiffs.h"
57 #include "MergeCmdLineInfo.h"
58 #include "OptionsFont.h"
60 #include "DropHandler.h"
61 #include "LanguageSelect.h"
62 #include "VersionInfo.h"
64 #include "CCrystalTextMarkers.h"
65 #include "utils/hqbitmap.h"
69 #include "WindowsManagerDialog.h"
79 static void LoadToolbarImageList(int orgImageWidth, int newImageHeight, UINT nIDResource, bool bGrayscale, CImageList& ImgList);
80 static CPtrList &GetDocList(CMultiDocTemplate *pTemplate);
81 template<class DocClass>
82 DocClass * GetMergeDocForDiff(CMultiDocTemplate *pTemplate, CDirDoc *pDirDoc, int nFiles, bool bMakeVisible = true);
85 * @brief A table associating menuitem id, icon and menus to apply.
87 const CMainFrame::MENUITEM_ICON CMainFrame::m_MenuIcons[] = {
88 { ID_FILE_OPENCONFLICT, IDB_FILE_OPENCONFLICT, CMainFrame::MENU_ALL },
89 { ID_FILE_NEW_TABLE, IDB_FILE_NEW_TABLE, CMainFrame::MENU_ALL },
90 { ID_FILE_NEW_HEX, IDB_FILE_NEW_HEX, CMainFrame::MENU_ALL },
91 { ID_FILE_NEW_IMAGE, IDB_FILE_NEW_IMAGE, CMainFrame::MENU_ALL },
92 { ID_FILE_NEW3, IDB_FILE_NEW3, CMainFrame::MENU_ALL },
93 { ID_FILE_NEW3_TABLE, IDB_FILE_NEW3_TABLE, CMainFrame::MENU_ALL },
94 { ID_FILE_NEW3_HEX, IDB_FILE_NEW3_HEX, CMainFrame::MENU_ALL },
95 { ID_FILE_NEW3_IMAGE, IDB_FILE_NEW3_IMAGE, CMainFrame::MENU_ALL },
96 { ID_EDIT_COPY, IDB_EDIT_COPY, CMainFrame::MENU_ALL },
97 { ID_EDIT_CUT, IDB_EDIT_CUT, CMainFrame::MENU_ALL },
98 { ID_EDIT_PASTE, IDB_EDIT_PASTE, CMainFrame::MENU_ALL },
99 { ID_EDIT_FIND, IDB_EDIT_SEARCH, CMainFrame::MENU_ALL },
100 { ID_WINDOW_CASCADE, IDB_WINDOW_CASCADE, CMainFrame::MENU_ALL },
101 { ID_WINDOW_TILE_HORZ, IDB_WINDOW_HORIZONTAL, CMainFrame::MENU_ALL },
102 { ID_WINDOW_TILE_VERT, IDB_WINDOW_VERTICAL, CMainFrame::MENU_ALL },
103 { ID_FILE_CLOSE, IDB_WINDOW_CLOSE, CMainFrame::MENU_ALL },
104 { ID_WINDOW_CHANGE_PANE, IDB_WINDOW_CHANGEPANE, CMainFrame::MENU_ALL },
105 { ID_EDIT_WMGOTO, IDB_EDIT_GOTO, CMainFrame::MENU_ALL },
106 { ID_EDIT_REPLACE, IDB_EDIT_REPLACE, CMainFrame::MENU_ALL },
107 { ID_VIEW_SELECTFONT, IDB_VIEW_SELECTFONT, CMainFrame::MENU_ALL },
108 { ID_APP_EXIT, IDB_FILE_EXIT, CMainFrame::MENU_ALL },
109 { ID_HELP_CONTENTS, IDB_HELP_CONTENTS, CMainFrame::MENU_ALL },
110 { ID_EDIT_SELECT_ALL, IDB_EDIT_SELECTALL, CMainFrame::MENU_ALL },
111 { ID_TOOLS_FILTERS, IDB_TOOLS_FILTERS, CMainFrame::MENU_ALL },
112 { ID_TOOLS_CUSTOMIZECOLUMNS, IDB_TOOLS_COLUMNS, CMainFrame::MENU_ALL },
113 { ID_TOOLS_GENERATEPATCH, IDB_TOOLS_GENERATEPATCH, CMainFrame::MENU_ALL },
114 { ID_PLUGINS_LIST, IDB_PLUGINS_LIST, CMainFrame::MENU_ALL },
115 { ID_FILE_PRINT, IDB_FILE_PRINT, CMainFrame::MENU_FILECMP },
116 { ID_TOOLS_GENERATEREPORT, IDB_TOOLS_GENERATEREPORT, CMainFrame::MENU_FILECMP },
117 { ID_EDIT_TOGGLE_BOOKMARK, IDB_EDIT_TOGGLE_BOOKMARK, CMainFrame::MENU_FILECMP },
118 { ID_EDIT_GOTO_NEXT_BOOKMARK, IDB_EDIT_GOTO_NEXT_BOOKMARK, CMainFrame::MENU_FILECMP },
119 { ID_EDIT_GOTO_PREV_BOOKMARK, IDB_EDIT_GOTO_PREV_BOOKMARK, CMainFrame::MENU_FILECMP },
120 { ID_EDIT_CLEAR_ALL_BOOKMARKS, IDB_EDIT_CLEAR_ALL_BOOKMARKS, CMainFrame::MENU_FILECMP },
121 { ID_VIEW_ZOOMIN, IDB_VIEW_ZOOMIN, CMainFrame::MENU_FILECMP },
122 { ID_VIEW_ZOOMOUT, IDB_VIEW_ZOOMOUT, CMainFrame::MENU_FILECMP },
123 { ID_COPY_FROM_LEFT, IDB_COPY_FROM_LEFT, CMainFrame::MENU_FILECMP },
124 { ID_COPY_FROM_RIGHT, IDB_COPY_FROM_RIGHT, CMainFrame::MENU_FILECMP },
125 { ID_LINES_R2L, IDB_COPY_SELECTED_LINES_TO_LEFT, CMainFrame::MENU_FILECMP },
126 { ID_LINES_L2R, IDB_COPY_SELECTED_LINES_TO_RIGHT, CMainFrame::MENU_FILECMP },
127 { ID_COPY_LINES_FROM_LEFT, IDB_COPY_SELECTED_LINES_FROM_LEFT, CMainFrame::MENU_FILECMP },
128 { ID_COPY_LINES_FROM_RIGHT, IDB_COPY_SELECTED_LINES_FROM_RIGHT, CMainFrame::MENU_FILECMP },
129 { ID_MERGE_COMPARE, IDB_MERGE_COMPARE, CMainFrame::MENU_FOLDERCMP },
130 { ID_MERGE_COMPARE_LEFT1_LEFT2, IDB_MERGE_COMPARE_LEFT1_LEFT2, CMainFrame::MENU_FOLDERCMP },
131 { ID_MERGE_COMPARE_RIGHT1_RIGHT2, IDB_MERGE_COMPARE_RIGHT1_RIGHT2,CMainFrame::MENU_FOLDERCMP },
132 { ID_MERGE_COMPARE_LEFT1_RIGHT2, IDB_MERGE_COMPARE_LEFT1_RIGHT2, CMainFrame::MENU_FOLDERCMP },
133 { ID_MERGE_COMPARE_LEFT2_RIGHT1, IDB_MERGE_COMPARE_LEFT2_RIGHT1, CMainFrame::MENU_FOLDERCMP },
134 { ID_MERGE_DELETE, IDB_MERGE_DELETE, CMainFrame::MENU_FOLDERCMP },
135 { ID_TOOLS_GENERATEREPORT, IDB_TOOLS_GENERATEREPORT, CMainFrame::MENU_FOLDERCMP },
136 { ID_DIR_COPY_LEFT_TO_RIGHT, IDB_LEFT_TO_RIGHT, CMainFrame::MENU_FOLDERCMP },
137 { ID_DIR_COPY_LEFT_TO_MIDDLE, IDB_LEFT_TO_MIDDLE, CMainFrame::MENU_FOLDERCMP },
138 { ID_DIR_COPY_RIGHT_TO_LEFT, IDB_RIGHT_TO_LEFT, CMainFrame::MENU_FOLDERCMP },
139 { ID_DIR_COPY_RIGHT_TO_MIDDLE, IDB_RIGHT_TO_MIDDLE, CMainFrame::MENU_FOLDERCMP },
140 { ID_DIR_COPY_MIDDLE_TO_LEFT, IDB_MIDDLE_TO_LEFT, CMainFrame::MENU_FOLDERCMP },
141 { ID_DIR_COPY_MIDDLE_TO_RIGHT, IDB_MIDDLE_TO_RIGHT, CMainFrame::MENU_FOLDERCMP },
142 { ID_DIR_COPY_LEFT_TO_BROWSE, IDB_LEFT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
143 { ID_DIR_COPY_MIDDLE_TO_BROWSE, IDB_MIDDLE_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
144 { ID_DIR_COPY_RIGHT_TO_BROWSE, IDB_RIGHT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
145 { ID_DIR_MOVE_LEFT_TO_BROWSE, IDB_MOVE_LEFT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
146 { ID_DIR_MOVE_MIDDLE_TO_BROWSE, IDB_MOVE_MIDDLE_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
147 { ID_DIR_MOVE_RIGHT_TO_BROWSE, IDB_MOVE_RIGHT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
148 { ID_DIR_DEL_LEFT, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
149 { ID_DIR_DEL_MIDDLE, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
150 { ID_DIR_DEL_RIGHT, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
151 { ID_DIR_DEL_BOTH, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
152 { ID_DIR_DEL_ALL, IDB_ALL, CMainFrame::MENU_FOLDERCMP },
153 { ID_DIR_COPY_PATHNAMES_LEFT, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
154 { ID_DIR_COPY_PATHNAMES_MIDDLE, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
155 { ID_DIR_COPY_PATHNAMES_RIGHT, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
156 { ID_DIR_COPY_PATHNAMES_BOTH, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
157 { ID_DIR_COPY_PATHNAMES_ALL, IDB_ALL, CMainFrame::MENU_FOLDERCMP },
158 { ID_DIR_COPY_LEFT_TO_CLIPBOARD, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
159 { ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
160 { ID_DIR_COPY_RIGHT_TO_CLIPBOARD, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
161 { ID_DIR_COPY_BOTH_TO_CLIPBOARD, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
162 { ID_DIR_COPY_ALL_TO_CLIPBOARD, IDB_ALL, CMainFrame::MENU_FOLDERCMP },
163 { ID_DIR_ZIP_LEFT, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
164 { ID_DIR_ZIP_MIDDLE, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
165 { ID_DIR_ZIP_RIGHT, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
166 { ID_DIR_ZIP_BOTH, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
167 { ID_DIR_ZIP_ALL, IDB_ALL, CMainFrame::MENU_FOLDERCMP }
171 /////////////////////////////////////////////////////////////////////////////
174 IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd)
176 BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
177 //{{AFX_MSG_MAP(CMainFrame)
180 ON_WM_INITMENUPOPUP()
183 ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
184 ON_COMMAND(ID_HELP_GNULICENSE, OnHelpGnulicense)
185 ON_COMMAND(ID_OPTIONS, OnOptions)
186 ON_COMMAND(ID_VIEW_SELECTFONT, OnViewSelectfont)
187 ON_COMMAND(ID_VIEW_USEDEFAULTFONT, OnViewUsedefaultfont)
188 ON_COMMAND(ID_HELP_CONTENTS, OnHelpContents)
190 ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
193 ON_COMMAND_RANGE(ID_UNPACK_MANUAL, ID_UNPACK_AUTO, OnPluginUnpackMode)
194 ON_UPDATE_COMMAND_UI_RANGE(ID_UNPACK_MANUAL, ID_UNPACK_AUTO, OnUpdatePluginUnpackMode)
195 ON_COMMAND_RANGE(ID_PREDIFFER_MANUAL, ID_PREDIFFER_AUTO, OnPluginPrediffMode)
196 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFER_MANUAL, ID_PREDIFFER_AUTO, OnUpdatePluginPrediffMode)
197 ON_UPDATE_COMMAND_UI(ID_OPEN_WITH_UNPACKER, OnUpdatePluginRelatedMenu)
198 ON_UPDATE_COMMAND_UI(ID_APPLY_PREDIFFER, OnUpdatePluginRelatedMenu)
199 ON_UPDATE_COMMAND_UI(ID_TRANSFORM_WITH_SCRIPT, OnUpdatePluginRelatedMenu)
200 ON_UPDATE_COMMAND_UI(ID_RELOAD_PLUGINS, OnUpdatePluginRelatedMenu)
201 ON_COMMAND(ID_RELOAD_PLUGINS, OnReloadPlugins)
202 ON_COMMAND(ID_HELP_GETCONFIG, OnSaveConfigData)
203 ON_COMMAND(ID_FILE_NEW, (OnFileNew<2, ID_MERGE_COMPARE_TEXT>))
204 ON_COMMAND(ID_FILE_NEW_TABLE, (OnFileNew<2, ID_MERGE_COMPARE_TABLE>))
205 ON_COMMAND(ID_FILE_NEW_HEX, (OnFileNew<2, ID_MERGE_COMPARE_HEX>))
206 ON_COMMAND(ID_FILE_NEW_IMAGE, (OnFileNew<2, ID_MERGE_COMPARE_IMAGE>))
207 ON_COMMAND(ID_FILE_NEW3, (OnFileNew<3, ID_MERGE_COMPARE_TEXT>))
208 ON_COMMAND(ID_FILE_NEW3_TABLE, (OnFileNew<2, ID_MERGE_COMPARE_TABLE>))
209 ON_COMMAND(ID_FILE_NEW3_HEX, (OnFileNew<3, ID_MERGE_COMPARE_HEX>))
210 ON_COMMAND(ID_FILE_NEW3_IMAGE, (OnFileNew<3, ID_MERGE_COMPARE_IMAGE>))
211 ON_COMMAND(ID_TOOLS_FILTERS, OnToolsFilters)
212 ON_COMMAND(ID_VIEW_STATUS_BAR, OnViewStatusBar)
213 ON_UPDATE_COMMAND_UI(ID_VIEW_TAB_BAR, OnUpdateViewTabBar)
214 ON_COMMAND(ID_VIEW_TAB_BAR, OnViewTabBar)
215 ON_UPDATE_COMMAND_UI(ID_VIEW_RESIZE_PANES, OnUpdateResizePanes)
216 ON_COMMAND(ID_VIEW_RESIZE_PANES, OnResizePanes)
217 ON_COMMAND(ID_FILE_OPENPROJECT, OnFileOpenProject)
218 ON_MESSAGE(WM_COPYDATA, OnCopyData)
219 ON_MESSAGE(WM_USER+1, OnUser1)
220 ON_COMMAND(ID_WINDOW_CLOSEALL, OnWindowCloseAll)
221 ON_UPDATE_COMMAND_UI(ID_WINDOW_CLOSEALL, OnUpdateWindowCloseAll)
222 ON_COMMAND(ID_FILE_SAVEPROJECT, OnSaveProject)
224 ON_COMMAND_RANGE(ID_TOOLBAR_NONE, ID_TOOLBAR_HUGE, OnToolbarSize)
225 ON_UPDATE_COMMAND_UI_RANGE(ID_TOOLBAR_NONE, ID_TOOLBAR_HUGE, OnUpdateToolbarSize)
226 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
227 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
228 ON_COMMAND(ID_HELP_RELEASENOTES, OnHelpReleasenotes)
229 ON_COMMAND(ID_HELP_TRANSLATIONS, OnHelpTranslations)
230 ON_COMMAND(ID_FILE_OPENCONFLICT, OnFileOpenConflict)
231 ON_COMMAND(ID_PLUGINS_LIST, OnPluginsList)
232 ON_UPDATE_COMMAND_UI(ID_STATUS_PLUGIN, OnUpdatePluginName)
233 ON_NOTIFY(TBN_DROPDOWN, AFX_IDW_TOOLBAR, OnToolbarButtonDropDown)
234 ON_COMMAND_RANGE(ID_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnDiffWhitespace)
235 ON_UPDATE_COMMAND_UI_RANGE(ID_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
236 ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_BLANKLINES, OnDiffIgnoreBlankLines)
237 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_BLANKLINES, OnUpdateDiffIgnoreBlankLines)
238 ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_CASE, OnDiffIgnoreCase)
239 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_CASE, OnUpdateDiffIgnoreCase)
240 ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_EOL, OnDiffIgnoreEOL)
241 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_EOL, OnUpdateDiffIgnoreEOL)
242 ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_CODEPAGE, OnDiffIgnoreCP)
243 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_CODEPAGE, OnUpdateDiffIgnoreCP)
244 ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_COMMENTS, OnDiffIgnoreComments)
245 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_COMMENTS, OnUpdateDiffIgnoreComments)
246 ON_COMMAND(ID_DIFF_OPTIONS_INCLUDE_SUBFOLDERS, OnIncludeSubfolders)
247 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_INCLUDE_SUBFOLDERS, OnUpdateIncludeSubfolders)
248 ON_COMMAND_RANGE(ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_DIFF_OPTIONS_COMPMETHOD_SIZE, OnCompareMethod)
249 ON_UPDATE_COMMAND_UI_RANGE(ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_DIFF_OPTIONS_COMPMETHOD_SIZE, OnUpdateCompareMethod)
250 ON_COMMAND_RANGE(ID_MRU_FIRST, ID_MRU_LAST, OnMRUs)
251 ON_UPDATE_COMMAND_UI(ID_MRU_FIRST, OnUpdateNoMRUs)
252 ON_UPDATE_COMMAND_UI(ID_NO_MRU, OnUpdateNoMRUs)
253 ON_COMMAND(ID_FIRSTFILE, OnFirstFile)
254 ON_UPDATE_COMMAND_UI(ID_FIRSTFILE, OnUpdateFirstFile)
255 ON_COMMAND(ID_PREVFILE, OnPrevFile)
256 ON_UPDATE_COMMAND_UI(ID_PREVFILE, OnUpdatePrevFile)
257 ON_COMMAND(ID_NEXTFILE, OnNextFile)
258 ON_UPDATE_COMMAND_UI(ID_NEXTFILE, OnUpdateNextFile)
259 ON_COMMAND(ID_LASTFILE, OnLastFile)
260 ON_UPDATE_COMMAND_UI(ID_LASTFILE, OnUpdateLastFile)
261 ON_COMMAND(ID_ACCEL_QUIT, &CMainFrame::OnAccelQuit)
262 ON_MESSAGE(WMU_CHILDFRAMEADDED, &CMainFrame::OnChildFrameAdded)
263 ON_MESSAGE(WMU_CHILDFRAMEREMOVED, &CMainFrame::OnChildFrameRemoved)
264 ON_MESSAGE(WMU_CHILDFRAMEACTIVATE, &CMainFrame::OnChildFrameActivate)
265 ON_MESSAGE(WMU_CHILDFRAMEACTIVATED, &CMainFrame::OnChildFrameActivated)
270 * @brief MainFrame statusbar panels/indicators
272 static UINT StatusbarIndicators[] =
274 ID_SEPARATOR, // Plugin name
275 ID_SEPARATOR, // status line indicator
276 ID_SEPARATOR, // Merge mode
277 ID_SEPARATOR, // Diff number
278 ID_INDICATOR_CAPS, // Caps Lock
279 ID_INDICATOR_NUM, // Num Lock
280 ID_INDICATOR_OVR, // Insert
284 * @brief Return a const reference to a CMultiDocTemplate's list of documents.
286 static CPtrList &GetDocList(CMultiDocTemplate *pTemplate)
288 struct Template : public CMultiDocTemplate
291 using CMultiDocTemplate::m_docList;
293 return static_cast<struct Template *>(pTemplate)->m_docList;
296 /////////////////////////////////////////////////////////////////////////////
297 // CMainFrame construction/destruction
300 * @brief MainFrame constructor. Loads settings from registry.
301 * @todo Preference for logging?
303 CMainFrame::CMainFrame()
305 , m_pDropHandler(nullptr)
306 , m_bShowErrors(false)
307 , m_lfDiff(Options::Font::Load(GetOptionsMgr(), OPT_FONT_FILECMP))
308 , m_lfDir(Options::Font::Load(GetOptionsMgr(), OPT_FONT_DIRCMP))
312 CMainFrame::~CMainFrame()
314 GetOptionsMgr()->SaveOption(OPT_TABBAR_AUTO_MAXWIDTH, m_wndTabBar.GetAutoMaxWidth());
317 m_arrChild.RemoveAll();
320 const TCHAR CMainFrame::szClassName[] = _T("WinMergeWindowClassW");
323 * @brief Change MainFrame window class name
324 * see http://support.microsoft.com/kb/403825/ja
326 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
329 BOOL bRes = CMDIFrameWnd::PreCreateWindow(cs);
330 HINSTANCE hInst = AfxGetInstanceHandle();
331 // see if the class already exists
332 if (!::GetClassInfo(hInst, szClassName, &wndcls))
335 ::GetClassInfo(hInst, cs.lpszClass, &wndcls);
336 // register a new class
337 wndcls.lpszClassName = szClassName;
338 wndcls.hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(IDR_MAINFRAME));
339 ::RegisterClass(&wndcls);
341 cs.lpszClass = szClassName;
345 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
347 if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
350 m_wndMDIClient.SubclassWindow(m_hWndMDIClient);
352 if (!CreateToolbar())
354 TRACE0("Failed to create toolbar\n");
355 return -1; // fail to create
358 if (!m_wndTabBar.Create(this))
360 TRACE0("Failed to create tab bar\n");
361 return -1; // fail to create
363 m_wndTabBar.SetAutoMaxWidth(GetOptionsMgr()->GetBool(OPT_TABBAR_AUTO_MAXWIDTH));
365 if (!GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR))
366 CMDIFrameWnd::ShowControlBar(&m_wndTabBar, false, 0);
368 if (!m_wndStatusBar.Create(this))
370 TRACE0("Failed to create status bar\n");
371 return -1; // fail to create
373 theApp.SetIndicators(m_wndStatusBar, StatusbarIndicators,
374 static_cast<int>(std::size(StatusbarIndicators)));
376 const int lpx = CClientDC(this).GetDeviceCaps(LOGPIXELSX);
377 auto pointToPixel = [lpx](int point) { return MulDiv(point, lpx, 72); };
378 m_wndStatusBar.SetPaneInfo(0, 0, SBPS_STRETCH | SBPS_NOBORDERS, 0);
379 m_wndStatusBar.SetPaneInfo(1, ID_STATUS_PLUGIN, 0, pointToPixel(225));
380 m_wndStatusBar.SetPaneInfo(2, ID_STATUS_MERGINGMODE, 0, pointToPixel(75));
381 m_wndStatusBar.SetPaneInfo(3, ID_STATUS_DIFFNUM, 0, pointToPixel(112));
383 if (!GetOptionsMgr()->GetBool(OPT_SHOW_STATUSBAR))
384 CMDIFrameWnd::ShowControlBar(&m_wndStatusBar, false, 0);
386 m_pDropHandler = new DropHandler(std::bind(&CMainFrame::OnDropFiles, this, std::placeholders::_1));
387 RegisterDragDrop(m_hWnd, m_pDropHandler);
389 m_wndMDIClient.ModifyStyleEx(WS_EX_CLIENTEDGE, 0);
394 void CMainFrame::OnTimer(UINT_PTR nIDEvent)
396 CMDIFrameWnd::OnTimer(nIDEvent);
398 if (nIDEvent == IDT_UPDATEMAINMENU)
403 MDIGetActive(&bMaximized);
405 // When MDI maximized the window icon is drawn on the menu bar, so we
406 // need to notify it that our icon has changed.
410 OnUpdateFrameTitle(FALSE);
414 void CMainFrame::OnDestroy(void)
416 if (m_pDropHandler != nullptr)
417 RevokeDragDrop(m_hWnd);
420 static HMENU GetSubmenu(HMENU menu, int nthSubmenu)
422 for (int nth = 0, i = 0; i < ::GetMenuItemCount(menu); i++)
424 if (::GetSubMenu(menu, i) != nullptr)
426 if (nth == nthSubmenu)
427 return ::GetSubMenu(menu, i);
431 // error, submenu not found
435 static HMENU GetSubmenu(HMENU mainMenu, UINT nIDFirstMenuItem, int nthSubmenu)
438 for (i = 0 ; i < ::GetMenuItemCount(mainMenu) ; i++)
439 if (::GetMenuItemID(::GetSubMenu(mainMenu, i), 0) == nIDFirstMenuItem)
441 HMENU menu = ::GetSubMenu(mainMenu, i);
444 return GetSubmenu(menu, nthSubmenu);
448 * @brief Find the scripts submenu from the main menu
449 * As now this is the first submenu in "Plugins" menu
450 * We find the "Plugins" menu by looking for a menu
451 * starting with ID_UNPACK_MANUAL.
453 HMENU CMainFrame::GetPrediffersSubmenu(HMENU mainMenu)
455 return GetSubmenu(mainMenu, ID_PLUGINS_LIST, 1);
459 * @brief Create a new menu for the view..
460 * @param [in] view Menu view either MENU_DEFAULT, MENU_MERGEVIEW or MENU_DIRVIEW.
461 * @param [in] ID Menu's resource ID.
462 * @return Menu for the view.
464 HMENU CMainFrame::NewMenu(int view, int ID)
467 if (m_pMenus[view] == nullptr)
469 m_pMenus[view].reset(new BCMenu());
470 if (m_pMenus[view] == nullptr)
477 menu_view = MENU_FILECMP;
480 menu_view = MENU_FOLDERCMP;
484 menu_view = MENU_MAINFRM;
488 if (!m_pMenus[view]->LoadMenu(ID))
494 if (view == MENU_IMGMERGEVIEW)
496 m_pImageMenu.reset(new BCMenu);
497 m_pImageMenu->LoadMenu(MAKEINTRESOURCE(IDR_POPUP_IMGMERGEVIEW));
498 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()));
501 // Load bitmaps to menuitems
502 for (auto& menu_icon: m_MenuIcons)
504 if (menu_view == (menu_icon.menusToApply & menu_view))
506 m_pMenus[view]->ModifyODMenu(nullptr, menu_icon.menuitemID, menu_icon.iconResID);
510 m_pMenus[view]->LoadToolbar(IDR_MAINFRAME);
512 theApp.TranslateMenu(m_pMenus[view]->m_hMenu);
514 return (m_pMenus[view]->Detach());
518 * @brief Create new default (CMainFrame) menu.
520 HMENU CMainFrame::NewDefaultMenu(int ID /*=0*/)
524 return NewMenu( MENU_DEFAULT, ID );
528 * @brief Create new File compare (CMergeEditView) menu.
530 HMENU CMainFrame::NewMergeViewMenu()
532 return NewMenu( MENU_MERGEVIEW, IDR_MERGEDOCTYPE);
536 * @brief Create new Dir compare (CDirView) menu
538 HMENU CMainFrame::NewDirViewMenu()
540 return NewMenu(MENU_DIRVIEW, IDR_DIRDOCTYPE );
544 * @brief Create new File compare (CHexMergeView) menu.
546 HMENU CMainFrame::NewHexMergeViewMenu()
548 return NewMenu( MENU_HEXMERGEVIEW, IDR_MERGEDOCTYPE);
552 * @brief Create new Image compare (CImgMergeView) menu.
554 HMENU CMainFrame::NewImgMergeViewMenu()
556 return NewMenu( MENU_IMGMERGEVIEW, IDR_MERGEDOCTYPE);
560 * @brief Create new File compare (COpenView) menu.
562 HMENU CMainFrame::NewOpenViewMenu()
564 return NewMenu( MENU_OPENVIEW, IDR_MAINFRAME);
568 * @brief This handler ensures that the popup menu items are drawn correctly.
570 void CMainFrame::OnMeasureItem(int nIDCtl,
571 LPMEASUREITEMSTRUCT lpMeasureItemStruct)
573 bool setflag = false;
574 if (lpMeasureItemStruct->CtlType == ODT_MENU)
576 if (IsMenu(reinterpret_cast<HMENU>(static_cast<uintptr_t>(lpMeasureItemStruct->itemID))))
579 CMenu::FromHandle(reinterpret_cast<HMENU>(static_cast<uintptr_t>(lpMeasureItemStruct->itemID)));
581 if (m_pMenus[MENU_DEFAULT]->IsMenu(cmenu))
583 m_pMenus[MENU_DEFAULT]->MeasureItem(lpMeasureItemStruct);
590 CMDIFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
594 * @brief This handler ensures that keyboard shortcuts work.
596 LRESULT CMainFrame::OnMenuChar(UINT nChar, UINT nFlags,
600 if(m_pMenus[MENU_DEFAULT]->IsMenu(pMenu))
601 lresult=BCMenu::FindKeyboardShortcut(nChar, nFlags, pMenu);
603 lresult=CMDIFrameWnd::OnMenuChar(nChar, nFlags, pMenu);
608 * @brief This handler updates the menus from time to time.
610 void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
614 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
617 for (int i = 0; i < pMergeDoc->GetFileCount(); ++i)
618 paths.SetPath(i, pMergeDoc->GetPath(i));
619 String filteredFilenames = strutils::join(paths.begin(), paths.end(), _T("|"));
620 unsigned topMenuId = pPopupMenu->GetMenuItemID(0);
621 if (topMenuId == ID_NO_PREDIFFER)
623 UpdatePrediffersMenu();
625 else if (topMenuId == ID_MERGE_COMPARE_TEXT)
627 CMenu* pMenu = pPopupMenu;
629 for (int i = pMenu->GetMenuItemCount() - 1; i > (ID_MERGE_COMPARE_IMAGE - ID_MERGE_COMPARE_TEXT); --i)
630 pMenu->DeleteMenu(i, MF_BYPOSITION);
632 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
634 else if (topMenuId == ID_NO_EDIT_SCRIPTS)
636 CMenu* pMenu = pPopupMenu;
637 ASSERT(pMenu != nullptr);
640 int i = pMenu->GetMenuItemCount();
642 pMenu->DeleteMenu(0, MF_BYPOSITION);
644 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::EditorScriptEventNames, false, ID_SCRIPT_FIRST);
646 else if (topMenuId == ID_PLUGINS_LIST)
648 for (int j = 0; j < 2; j++)
650 CMenu* pMenu = pPopupMenu->GetSubMenu((j == 0) ? 8 : (pPopupMenu->GetMenuItemCount() - 4));
651 ASSERT(pMenu != nullptr);
654 int i = pMenu->GetMenuItemCount();
656 pMenu->DeleteMenu(0, MF_BYPOSITION);
659 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::UnpackerEventNames, false, ID_UNPACKERS_FIRST);
661 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::EditorScriptEventNames, false, ID_SCRIPT_FIRST);
666 CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
667 if (BCMenu::IsMenu(pPopupMenu))
669 BCMenu::UpdateMenu(pPopupMenu);
674 CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
678 /////////////////////////////////////////////////////////////////////////////
679 // CMainFrame message handlers
681 void CMainFrame::OnFileOpen()
683 DoFileOrFolderOpen();
687 * @brief Check for BOM, and also, if bGuessEncoding, try to deduce codepage
689 * Unpacks info from FileLocation & delegates all work to codepage_detect module
692 FileLocationGuessEncodings(FileLocation & fileloc, int iGuessEncoding)
694 fileloc.encoding = codepage_detect::Guess(fileloc.filepath, iGuessEncoding);
697 bool CMainFrame::ShowAutoMergeDoc(CDirDoc * pDirDoc,
698 int nFiles, const FileLocation ifileloc[],
699 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
700 const PackingInfo * infoUnpacker /*= nullptr*/, OpenTextFileParams* pOpenParams /*= nullptr*/)
702 ASSERT(pDirDoc != nullptr);
704 if (sReportFile.empty() && pDirDoc->CompareFilesIfFilesAreLarge(nFiles, ifileloc))
707 String unpackedFileExtension;
708 if (infoUnpacker && GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
710 std::vector<String> filepaths(nFiles);
711 std::transform(ifileloc, ifileloc + nFiles, filepaths.begin(),
712 [](auto& file) { return file.filepath; });
713 String filteredFilenames = strutils::join(filepaths.begin(), filepaths.end(), _T("|"));
714 unpackedFileExtension = infoUnpacker->GetUnpackedFileExtension(filteredFilenames);
716 FileFilterHelper filterImg, filterBin;
717 filterImg.UseMask(true);
718 filterImg.SetMask(GetOptionsMgr()->GetString(OPT_CMP_IMG_FILEPATTERNS));
719 filterBin.UseMask(true);
720 filterBin.SetMask(GetOptionsMgr()->GetString(OPT_CMP_BIN_FILEPATTERNS));
721 for (int pane = 0; pane < nFiles; ++pane)
723 String filepath = ifileloc[pane].filepath + unpackedFileExtension;
724 if (filterImg.includeFile(filepath) && CImgMergeFrame::IsLoadable())
725 return ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
726 else if (filterBin.includeFile(filepath) && CHexMergeView::IsLoadable())
727 return ShowHexMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
729 return ShowTextOrTableMergeDoc({}, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams);
732 bool CMainFrame::ShowMergeDoc(UINT nID, CDirDoc* pDirDoc,
733 int nFiles, const FileLocation ifileloc[],
734 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
735 const PackingInfo* infoUnpacker /*= nullptr*/, OpenTextFileParams* pOpenParams /*= nullptr*/)
739 case ID_MERGE_COMPARE_TEXT:
740 return ShowTextMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
741 strDesc, sReportFile, infoUnpacker, pOpenParams);
742 case ID_MERGE_COMPARE_TABLE:
743 return ShowTableMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
744 strDesc, sReportFile, infoUnpacker, pOpenParams);
745 case ID_MERGE_COMPARE_HEX:
746 return ShowHexMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
747 strDesc, sReportFile, infoUnpacker);
748 case ID_MERGE_COMPARE_IMAGE:
749 return ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
750 strDesc, sReportFile, infoUnpacker);
752 return ShowAutoMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
753 strDesc, sReportFile, infoUnpacker, pOpenParams);
757 std::array<bool, 3> GetROFromFlags(int nFiles, const DWORD dwFlags[])
759 std::array<bool, 3> bRO = { false, false, false };
760 for (int pane = 0; pane < nFiles; pane++)
763 bRO[pane] = ((dwFlags[pane] & FFILEOPEN_READONLY) > 0);
768 int GetActivePaneFromFlags(int nFiles, const DWORD dwFlags[])
770 int nActivePane = -1;
771 for (int pane = 0; pane < nFiles; ++pane)
773 if (dwFlags && (dwFlags[pane] & FFILEOPEN_SETFOCUS))
780 * @brief Creates new MergeDoc instance and shows documents.
781 * @param [in] pDirDoc Dir compare document to create a new Merge document for.
782 * @param [in] ifilelocLeft Left side file location info.
783 * @param [in] ifilelocRight Right side file location info.
784 * @param [in] dwLeftFlags Left side flags.
785 * @param [in] dwRightFlags Right side flags.
786 * @param [in] infoUnpacker Plugin info.
787 * @return success/failure
789 bool CMainFrame::ShowTextOrTableMergeDoc(std::optional<bool> table, CDirDoc * pDirDoc,
790 int nFiles, const FileLocation ifileloc[],
791 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
792 const PackingInfo * infoUnpacker /*= nullptr*/, OpenTextFileParams* pOpenParams /*= nullptr*/)
794 if (m_pMenus[MENU_MERGEVIEW] == nullptr)
795 theApp.m_pDiffTemplate->m_hMenuShared = NewMergeViewMenu();
796 CMergeDoc * pMergeDoc = GetMergeDocForDiff<CMergeDoc>(theApp.m_pDiffTemplate, pDirDoc, nFiles, false);
798 // Make local copies, so we can change encoding if we guess it below
799 FileLocation fileloc[3];
800 std::copy_n(ifileloc, nFiles, fileloc);
802 ASSERT(pMergeDoc != nullptr); // must ASSERT to get an answer to the question below ;-)
803 if (pMergeDoc == nullptr)
804 return false; // when does this happen ?
806 // if an unpacker is selected, it must be used during LoadFromFile
807 // MergeDoc must memorize it for SaveToFile
808 // Warning : this unpacker may differ from the pDirDoc one
809 // (through menu : "Plugins"->"Open with unpacker")
810 pMergeDoc->SetUnpacker(infoUnpacker);
813 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
814 for (int pane = 0; pane < nFiles; pane++)
816 if (fileloc[pane].encoding.m_unicoding == -1)
817 fileloc[pane].encoding.m_unicoding = ucr::NONE;
818 if (fileloc[pane].encoding.m_unicoding == ucr::NONE && fileloc[pane].encoding.m_codepage == -1)
820 FileLocationGuessEncodings(fileloc[pane], iGuessEncodingType);
824 pMergeDoc->SetEnableTableEditing(table);
825 if (pOpenParams && table.value_or(false))
827 CMergeDoc::TableProps props = CMergeDoc::GetTablePropertiesByFileName(
828 pOpenParams->m_fileExt.empty() ? fileloc[0].filepath : pOpenParams->m_fileExt, true, false);
829 props.delimiter = pOpenParams->m_tableDelimiter.value_or(props.delimiter);
830 props.quote = pOpenParams->m_tableQuote.value_or(props.quote);
831 props.allowNewlinesInQuotes = pOpenParams->m_tableAllowNewlinesInQuotes.value_or(props.allowNewlinesInQuotes);
832 pMergeDoc->SetTableProperties(props);
835 // Note that OpenDocs() takes care of closing compare window when needed.
836 bool bResult = pMergeDoc->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc);
839 if (CMergeEditFrame *pFrame = pMergeDoc->GetParentFrame())
840 if (!pFrame->IsActivated())
841 pFrame->InitialUpdateFrame(pMergeDoc, true);
848 if (pOpenParams && !pOpenParams->m_fileExt.empty())
849 pMergeDoc->SetTextType(pOpenParams->m_fileExt);
851 for (int pane = 0; pane < nFiles; pane++)
855 bool bModified = (dwFlags[pane] & FFILEOPEN_MODIFIED) > 0;
858 pMergeDoc->m_ptBuf[pane]->SetModified(true);
859 pMergeDoc->UpdateHeaderPath(pane);
861 if (dwFlags[pane] & FFILEOPEN_AUTOMERGE)
863 pMergeDoc->DoAutoMerge(pane);
868 pMergeDoc->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags), pOpenParams ? pOpenParams->m_line : -1, true);
870 if (!sReportFile.empty())
871 pMergeDoc->GenerateReport(sReportFile);
876 bool CMainFrame::ShowTextMergeDoc(CDirDoc* pDirDoc,
877 int nFiles, const FileLocation ifileloc[],
878 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
879 const PackingInfo* infoUnpacker /*= nullptr*/, OpenTextFileParams* pOpenParams /*= nullptr*/)
881 return ShowTextOrTableMergeDoc(false, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams);
884 bool CMainFrame::ShowTableMergeDoc(CDirDoc* pDirDoc,
885 int nFiles, const FileLocation ifileloc[],
886 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
887 const PackingInfo* infoUnpacker /*= nullptr*/, OpenTextFileParams* pOpenParams /*= nullptr*/)
889 return ShowTextOrTableMergeDoc(true, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams);
892 bool CMainFrame::ShowHexMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
893 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
894 const PackingInfo * infoUnpacker /*= nullptr*/)
896 if (m_pMenus[MENU_HEXMERGEVIEW] == nullptr)
897 theApp.m_pHexMergeTemplate->m_hMenuShared = NewHexMergeViewMenu();
898 CHexMergeDoc *pHexMergeDoc = GetMergeDocForDiff<CHexMergeDoc>(theApp.m_pHexMergeTemplate, pDirDoc, nFiles);
899 if (pHexMergeDoc == nullptr)
902 pHexMergeDoc->SetUnpacker(infoUnpacker);
904 if (!pHexMergeDoc->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc))
907 pHexMergeDoc->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags));
909 if (!sReportFile.empty())
910 pHexMergeDoc->GenerateReport(sReportFile);
915 bool CMainFrame::ShowImgMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
916 const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
917 const PackingInfo * infoUnpacker /*= nullptr*/)
919 CImgMergeFrame *pImgMergeFrame = new CImgMergeFrame();
920 if (!CImgMergeFrame::menu.m_hMenu)
921 CImgMergeFrame::menu.m_hMenu = NewImgMergeViewMenu();
922 pImgMergeFrame->SetSharedMenu(CImgMergeFrame::menu.m_hMenu);
923 pImgMergeFrame->SetUnpacker(infoUnpacker);
924 pImgMergeFrame->SetDirDoc(pDirDoc);
925 pDirDoc->AddMergeDoc(pImgMergeFrame);
927 if (!pImgMergeFrame->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc, this))
930 for (int pane = 0; pane < nFiles; pane++)
932 if (dwFlags && (dwFlags[pane] & FFILEOPEN_AUTOMERGE))
933 pImgMergeFrame->DoAutoMerge(pane);
936 pImgMergeFrame->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags));
938 if (!sReportFile.empty())
939 pImgMergeFrame->GenerateReport(sReportFile);
944 bool CMainFrame::ShowTextMergeDoc(CDirDoc* pDirDoc, int nBuffers, const String text[],
945 const String strDesc[], const String& strFileExt, OpenTextFileParams* pOpenParams /*= nullptr*/)
947 FileLocation fileloc[3];
948 DWORD dwFlags[3] = {};
949 CDirDoc* pDirDoc2 = pDirDoc->GetMainView() ? pDirDoc :
950 static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
951 for (int nBuffer = 0; nBuffer < nBuffers; ++nBuffer)
953 TempFilePtr wTemp(new TempFile());
954 String workFile = wTemp->Create(_T("text_"), strFileExt);
955 m_tempFiles.push_back(wTemp);
956 wTemp->Create(_T(""), strFileExt);
958 if (file.OpenCreateUtf8(workFile))
960 file.WriteString(text[nBuffer]);
962 fileloc[nBuffer].setPath(workFile);
964 return ShowTextMergeDoc(pDirDoc2, nBuffers, fileloc, dwFlags, strDesc, _T(""), nullptr, pOpenParams);
968 * @brief Show GNU licence information in notepad (local file) or in Web Browser
970 void CMainFrame::OnHelpGnulicense()
972 const String spath = paths::ConcatPath(env::GetProgPath(), LicenseFile);
973 shell::OpenFileOrUrl(spath.c_str(), LicenceUrl);
977 * @brief Opens Options-dialog and saves changed options
979 void CMainFrame::OnOptions()
981 // Using singleton shared syntax colors
982 CPreferencesDlg dlg(GetOptionsMgr(), theApp.GetMainSyntaxColors());
983 INT_PTR rv = dlg.DoModal();
987 LANGID lang = static_cast<LANGID>(GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE));
988 if (lang != theApp.m_pLangDlg->GetLangId())
990 theApp.m_pLangDlg->SetLanguage(lang, true);
992 // Update status bar inicator texts
993 theApp.SetIndicators(m_wndStatusBar, 0, 0);
995 // Update the current menu
998 // update the title text of the document
1004 // Set new temporary path
1005 theApp.SetupTempPath();
1007 // Set new filterpath
1008 String filterPath = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
1009 theApp.m_pGlobalFileFilter->SetUserFilterPath(filterPath);
1011 CCrystalTextView::RENDERING_MODE nRenderingMode = static_cast<CCrystalTextView::RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
1012 CCrystalTextView::SetRenderingModeDefault(nRenderingMode);
1014 theApp.UpdateCodepageModule();
1016 strdiff::SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
1018 // make an attempt at rescanning any open diff sessions
1021 // Update all dirdoc settings
1022 for (auto pDirDoc : GetAllDirDocs())
1023 pDirDoc->RefreshOptions();
1024 for (auto pOpenDoc : GetAllOpenDocs())
1025 pOpenDoc->RefreshOptions();
1026 for (auto pMergeDoc : GetAllHexMergeDocs())
1027 pMergeDoc->RefreshOptions();
1031 static bool AddToRecentDocs(const PathContext& paths, const unsigned flags[], bool recurse, const String& filter)
1033 String params, title;
1034 for (int nIndex = 0; nIndex < paths.GetSize(); ++nIndex)
1036 if (flags && (flags[nIndex] & FFILEOPEN_READONLY))
1040 case 0: params += _T("/wl "); break;
1041 case 1: params += paths.GetSize() == 2 ? _T("/wr ") : _T("/wm "); break;
1042 case 2: params += _T("/wr "); break;
1045 params += _T("\"") + paths[nIndex] + _T("\" ");
1047 String path = paths[nIndex];
1048 paths::normalize(path);
1049 title += paths::FindFileName(path);
1050 if (nIndex < paths.GetSize() - 1)
1054 params += _T("/r ");
1055 if (!filter.empty())
1056 params += _T("/f \"") + filter + _T("\" ");
1058 Concurrent::CreateTask([params, title](){
1059 CoInitialize(nullptr);
1060 JumpList::AddToRecentDocs(_T(""), params, title, params, 0);
1067 * @brief Begin a diff: open dirdoc if it is directories, else open a mergedoc for editing.
1068 * @param [in] pszLeft Left-side path.
1069 * @param [in] pszRight Right-side path.
1070 * @param [in] dwLeftFlags Left-side flags.
1071 * @param [in] dwRightFlags Right-side flags.
1072 * @param [in] bRecurse Do we run recursive (folder) compare?
1073 * @param [in] pDirDoc Dir compare document to use.
1074 * @param [in] infoUnpacker Unpacker plugin name.
1075 * @param [in] infoPrediffer Prediffer plugin name.
1076 * @return `true` if opening files and compare succeeded, `false` otherwise.
1078 bool CMainFrame::DoFileOrFolderOpen(const PathContext * pFiles /*= nullptr*/,
1079 const DWORD dwFlags[] /*= nullptr*/, const String strDesc[] /*= nullptr*/, const String& sReportFile /*= T("")*/,
1080 bool bRecurse /*= false*/, CDirDoc* pDirDoc/*= nullptr*/,
1081 const PackingInfo *infoUnpacker /*= nullptr*/, const PrediffingInfo *infoPrediffer /*= nullptr*/,
1082 UINT nID /*= 0*/, OpenTextFileParams *pOpenParams /*= nullptr*/)
1084 if (pDirDoc != nullptr && !pDirDoc->CloseMergeDocs())
1087 FileTransform::AutoUnpacking = GetOptionsMgr()->GetBool(OPT_PLUGINS_UNPACKER_MODE);
1088 FileTransform::AutoPrediffing = GetOptionsMgr()->GetBool(OPT_PLUGINS_PREDIFFER_MODE);
1090 Merge7zFormatMergePluginScope scope(infoUnpacker);
1093 if (pFiles != nullptr)
1098 bRO[0] = (dwFlags[0] & FFILEOPEN_READONLY) != 0;
1099 bRO[1] = (dwFlags[1] & FFILEOPEN_READONLY) != 0;
1100 bRO[2] = (dwFlags[2] & FFILEOPEN_READONLY) != 0;
1103 // pop up dialog unless arguments exist (and are compatible)
1104 paths::PATH_EXISTENCE pathsType = paths::GetPairComparability(tFiles, IsArchiveFile);
1105 if (pathsType == paths::DOES_NOT_EXIST)
1107 if (m_pMenus[MENU_OPENVIEW] == nullptr)
1108 theApp.m_pOpenTemplate->m_hMenuShared = NewOpenViewMenu();
1109 COpenDoc *pOpenDoc = static_cast<COpenDoc *>(theApp.m_pOpenTemplate->CreateNewDocument());
1112 pOpenDoc->m_dwFlags[0] = dwFlags[0];
1113 pOpenDoc->m_dwFlags[1] = dwFlags[1];
1114 pOpenDoc->m_dwFlags[2] = dwFlags[2];
1116 pOpenDoc->m_files = tFiles;
1117 pOpenDoc->m_bRecurse = bRecurse;
1119 pOpenDoc->m_strUnpackerPipeline = infoUnpacker->GetPluginPipeline();
1120 CFrameWnd *pFrame = theApp.m_pOpenTemplate->CreateNewFrame(pOpenDoc, nullptr);
1121 theApp.m_pOpenTemplate->InitialUpdateFrame(pFrame, pOpenDoc);
1126 // Add trailing '\' for directories if its missing
1127 if (pathsType == paths::IS_EXISTING_DIR)
1129 if (!paths::EndsWithSlash(tFiles[0]) && !IsArchiveFile(tFiles[0]))
1130 tFiles[0] = paths::AddTrailingSlash(tFiles[0]);
1131 if (!paths::EndsWithSlash(tFiles[1]) && !IsArchiveFile(tFiles[1]))
1132 tFiles[1] = paths::AddTrailingSlash(tFiles[1]);
1133 if (tFiles.GetSize() == 3 && !paths::EndsWithSlash(tFiles[2]) && !IsArchiveFile(tFiles[1]))
1134 tFiles[2] = paths::AddTrailingSlash(tFiles[2]);
1137 //save the MRU left and right files.
1140 if (!(dwFlags[0] & FFILEOPEN_NOMRU))
1141 addToMru(tFiles[0].c_str(), _T("Files\\Left"));
1142 if (!(dwFlags[1] & FFILEOPEN_NOMRU))
1143 addToMru(tFiles[1].c_str(), _T("Files\\Right"));
1144 if (tFiles.GetSize() == 3 && !(dwFlags[2] & FFILEOPEN_NOMRU))
1145 addToMru(tFiles[2].c_str(), _T("Files\\Option"));
1149 CTempPathContext *pTempPathContext = nullptr;
1150 if (nID == 0 && pathsType == paths::IS_EXISTING_DIR)
1152 DecompressResult res= DecompressArchive(m_hWnd, tFiles);
1153 if (res.pTempPathContext)
1155 pathsType = res.pathsType;
1157 pTempPathContext = res.pTempPathContext;
1161 // Determine if we want a new dirview open, now that we know if it was
1162 // an archive. Don't open a new dirview if we are comparing files.
1163 if (pDirDoc == nullptr)
1165 if (nID == 0 && pathsType == paths::IS_EXISTING_DIR)
1167 CDirDoc::m_nDirsTemp = tFiles.GetSize();
1168 if (m_pMenus[MENU_DIRVIEW] == nullptr)
1169 theApp.m_pDirTemplate->m_hMenuShared = NewDirViewMenu();
1170 pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->OpenDocumentFile(nullptr));
1174 pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
1179 if (nID == 0 && pathsType == paths::IS_EXISTING_DIR)
1181 if (pDirDoc != nullptr)
1183 // Anything that can go wrong inside InitCompare() will yield an
1184 // exception. There is no point in checking return value.
1185 pDirDoc->InitCompare(tFiles, bRecurse, pTempPathContext);
1187 pDirDoc->SetReportFile(sReportFile);
1188 pDirDoc->SetDescriptions(strDesc);
1189 pDirDoc->SetTitle(nullptr);
1190 for (int nIndex = 0; nIndex < tFiles.GetSize(); nIndex++)
1191 pDirDoc->SetReadOnly(nIndex, bRO[nIndex]);
1198 FileLocation fileloc[3];
1200 for (int nPane = 0; nPane < tFiles.GetSize(); nPane++)
1201 fileloc[nPane].setPath(tFiles[nPane]);
1203 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
1205 String strBothFilenames = strutils::join(tFiles.begin(), tFiles.end(), _T("|"));
1206 pDirDoc->GetPluginManager().SetPrediffer(strBothFilenames, infoPrediffer->GetPluginPipeline());
1209 ShowMergeDoc(nID, pDirDoc, tFiles.GetSize(), fileloc, dwFlags, strDesc, sReportFile,
1210 infoUnpacker, pOpenParams);
1213 if (pFiles != nullptr && (!dwFlags || !(dwFlags[0] & FFILEOPEN_NOMRU)))
1215 String filter = GetOptionsMgr()->GetString(OPT_FILEFILTER_CURRENT);
1216 AddToRecentDocs(*pFiles, (unsigned *)dwFlags, bRecurse, filter);
1222 bool CMainFrame::DoFileOpen(UINT nID, const PathContext* pFiles /*= nullptr*/,
1223 const DWORD dwFlags[] /*= nullptr*/, const String strDesc[] /*= nullptr*/,
1224 const String& sReportFile /*= _T("")*/,
1225 const PackingInfo *infoUnpacker /*= nullptr*/, const PrediffingInfo *infoPrediffer /*= nullptr*/,
1226 OpenTextFileParams *pOpenParams /*= nullptr*/)
1228 CDirDoc* pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
1229 FileLocation fileloc[3];
1230 for (int pane = 0; pane < pFiles->GetSize(); pane++)
1231 fileloc[pane].setPath((*pFiles)[pane]);
1232 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
1233 pDirDoc->GetPluginManager().SetPrediffer(_T("|"), infoPrediffer->GetPluginPipeline());
1234 return ShowMergeDoc(nID, pDirDoc, pFiles->GetSize(), fileloc,
1235 dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams);
1238 void CMainFrame::UpdateFont(FRAMETYPE frame)
1240 if (frame == FRAME_FOLDER)
1242 for (auto pDoc : GetAllDirDocs())
1244 if (pDoc != nullptr)
1246 CDirView *pView = pDoc->GetMainView();
1247 if (pView != nullptr)
1248 pView->SetFont(m_lfDir);
1254 for (auto pDoc : GetAllMergeDocs())
1256 CMergeDoc *pMergeDoc = dynamic_cast<CMergeDoc *>(pDoc);
1257 if (pMergeDoc != nullptr)
1258 for (auto& pView: pMergeDoc->GetViewList())
1259 pView->SetFont(m_lfDiff);
1265 * @brief Select font for Merge/Dir view
1267 * Shows font selection dialog to user, sets current font and saves
1268 * selected font properties to registry. Selects fon type to active
1269 * view (Merge/Dir compare). If there is no open views, then font
1270 * is selected for Merge view (for example user may want to change to
1271 * unicode font before comparing files).
1273 void CMainFrame::OnViewSelectfont()
1275 FRAMETYPE frame = GetFrameType(GetActiveFrame());
1276 CHOOSEFONT cf = { sizeof CHOOSEFONT };
1277 LOGFONT *lf = nullptr;
1278 cf.Flags = CF_INITTOLOGFONTSTRUCT|CF_FORCEFONTEXIST|CF_SCREENFONTS;
1279 if (frame == FRAME_FILE)
1280 cf.Flags |= CF_FIXEDPITCHONLY; // Only fixed-width fonts for merge view
1282 // CF_FIXEDPITCHONLY = 0x00004000L
1283 // in case you are a developer and want to disable it to test with, eg, a Chinese capable font
1284 if (frame == FRAME_FOLDER)
1291 if (ChooseFont(&cf))
1293 Options::Font::Save(GetOptionsMgr(), frame == FRAME_FOLDER ? OPT_FONT_DIRCMP : OPT_FONT_FILECMP, lf, true);
1299 * @brief Use default font for active view type
1301 * Disable user-selected font for active view type (Merge/Dir compare).
1302 * If there is no open views, then Merge view font is changed.
1304 void CMainFrame::OnViewUsedefaultfont()
1306 FRAMETYPE frame = GetFrameType(GetActiveFrame());
1308 if (frame == FRAME_FOLDER)
1310 Options::Font::Reset(GetOptionsMgr(), OPT_FONT_DIRCMP);
1311 m_lfDir = Options::Font::Load(GetOptionsMgr(), OPT_FONT_DIRCMP);
1312 Options::Font::Save(GetOptionsMgr(), OPT_FONT_DIRCMP, &m_lfDir, false);
1316 Options::Font::Reset(GetOptionsMgr(), OPT_FONT_FILECMP);
1317 m_lfDiff = Options::Font::Load(GetOptionsMgr(), OPT_FONT_FILECMP);
1318 Options::Font::Save(GetOptionsMgr(), OPT_FONT_FILECMP, &m_lfDiff, false);
1325 * @brief Update any resources necessary after a GUI language change
1327 void CMainFrame::UpdateResources()
1329 m_wndStatusBar.SetPaneText(0, theApp.LoadString(AFX_IDS_IDLEMESSAGE).c_str());
1331 for (auto pDoc : GetAllDirDocs())
1332 pDoc->UpdateResources();
1333 for (auto pDoc : GetAllMergeDocs())
1334 pDoc->UpdateResources();
1335 for (auto pDoc : GetAllOpenDocs())
1336 pDoc->UpdateResources();
1337 for (auto pFrame: GetAllImgMergeFrames())
1338 pFrame->UpdateResources();
1342 * @brief Open WinMerge help.
1344 * If local HTMLhelp file is found, open it, otherwise open HTML page from web.
1346 void CMainFrame::OnHelpContents()
1352 * @brief Handle translation of default messages on the status bar
1354 void CMainFrame::GetMessageString(UINT nID, CString& rMessage) const
1356 // load appropriate string
1357 const String s = theApp.LoadString(nID);
1359 AfxExtractSubString(rMessage, s.c_str(), 0);
1362 void CMainFrame::ActivateFrame(int nCmdShow)
1366 CMDIFrameWnd::ActivateFrame(nCmdShow);
1370 m_bFirstTime = false;
1372 WINDOWPLACEMENT wp = {};
1373 wp.length = sizeof(WINDOWPLACEMENT);
1374 GetWindowPlacement(&wp);
1375 wp.rcNormalPosition.left=theApp.GetProfileInt(_T("Settings"), _T("MainLeft"),0);
1376 wp.rcNormalPosition.top=theApp.GetProfileInt(_T("Settings"), _T("MainTop"),0);
1377 wp.rcNormalPosition.right=theApp.GetProfileInt(_T("Settings"), _T("MainRight"),0);
1378 wp.rcNormalPosition.bottom=theApp.GetProfileInt(_T("Settings"), _T("MainBottom"),0);
1379 if (nCmdShow != SW_MINIMIZE && theApp.GetProfileInt(_T("Settings"), _T("MainMax"), FALSE))
1380 wp.showCmd = SW_MAXIMIZE;
1382 wp.showCmd = nCmdShow;
1384 CRect dsk_rc,rc(wp.rcNormalPosition);
1386 dsk_rc.left = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
1387 dsk_rc.top = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
1388 dsk_rc.right = dsk_rc.left + ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
1389 dsk_rc.bottom = dsk_rc.top + ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
1390 if (rc.Width() != 0 && rc.Height() != 0)
1392 // Ensure top-left corner is on visible area,
1393 // 20 points margin is added to prevent "lost" window
1394 CPoint ptTopLeft(rc.TopLeft());
1395 ptTopLeft += CPoint(20, 20);
1397 if (dsk_rc.PtInRect(ptTopLeft))
1398 SetWindowPlacement(&wp);
1400 CMDIFrameWnd::ActivateFrame(nCmdShow);
1403 CMDIFrameWnd::ActivateFrame(nCmdShow);
1407 * @brief Called when mainframe is about to be closed.
1408 * This function is called when mainframe is to be closed (not for
1409 * file/compare windows.
1411 void CMainFrame::OnClose()
1413 if (theApp.GetActiveOperations())
1416 // Check if there are multiple windows open and ask for closing them
1417 bool bAskClosing = GetOptionsMgr()->GetBool(OPT_ASK_MULTIWINDOW_CLOSE);
1420 bool quit = AskCloseConfirmation();
1425 // Save last selected filter
1426 String filter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1427 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
1429 // save main window position
1430 WINDOWPLACEMENT wp = {};
1431 wp.length = sizeof(WINDOWPLACEMENT);
1432 GetWindowPlacement(&wp);
1433 theApp.WriteProfileInt(_T("Settings"), _T("MainLeft"),wp.rcNormalPosition.left);
1434 theApp.WriteProfileInt(_T("Settings"), _T("MainTop"),wp.rcNormalPosition.top);
1435 theApp.WriteProfileInt(_T("Settings"), _T("MainRight"),wp.rcNormalPosition.right);
1436 theApp.WriteProfileInt(_T("Settings"), _T("MainBottom"),wp.rcNormalPosition.bottom);
1437 theApp.WriteProfileInt(_T("Settings"), _T("MainMax"), (wp.showCmd == SW_MAXIMIZE));
1439 for (auto pFrame: GetAllImgMergeFrames())
1441 if (!pFrame->CloseNow())
1445 CMDIFrameWnd::OnClose();
1449 * @brief Utility function to update CSuperComboBox format MRU
1451 void CMainFrame::addToMru(LPCTSTR szItem, LPCTSTR szRegSubKey, UINT nMaxItems)
1453 std::vector<CString> list;
1455 UINT cnt = AfxGetApp()->GetProfileInt(szRegSubKey, _T("Count"), 0);
1456 list.push_back(szItem);
1457 for (UINT i=0 ; i<cnt; ++i)
1459 s = AfxGetApp()->GetProfileString(szRegSubKey, strutils::format(_T("Item_%d"), i).c_str());
1463 cnt = list.size() > nMaxItems ? nMaxItems : static_cast<UINT>(list.size());
1464 for (UINT i=0 ; i<cnt; ++i)
1465 AfxGetApp()->WriteProfileString(szRegSubKey, strutils::format(_T("Item_%d"), i).c_str(), list[i]);
1467 AfxGetApp()->WriteProfileInt(szRegSubKey, _T("Count"), cnt);
1470 void CMainFrame::ApplyDiffOptions()
1472 for (auto pMergeDoc : GetAllMergeDocs())
1474 // Re-read MergeDoc settings (also updates view settings)
1475 // and rescan using new options
1476 pMergeDoc->RefreshOptions();
1477 pMergeDoc->FlushAndRescan(true);
1481 /// Get list of OpenDocs (documents underlying edit sessions)
1482 OpenDocList &CMainFrame::GetAllOpenDocs()
1484 return static_cast<OpenDocList &>(GetDocList(theApp.m_pOpenTemplate));
1487 /// Get list of MergeDocs (documents underlying edit sessions)
1488 MergeDocList &CMainFrame::GetAllMergeDocs()
1490 return static_cast<MergeDocList &>(GetDocList(theApp.m_pDiffTemplate));
1493 /// Get list of DirDocs (documents underlying a scan)
1494 DirDocList &CMainFrame::GetAllDirDocs()
1496 return static_cast<DirDocList &>(GetDocList(theApp.m_pDirTemplate));
1499 /// Get list of HexMergeDocs (documents underlying edit sessions)
1500 HexMergeDocList &CMainFrame::GetAllHexMergeDocs()
1502 return static_cast<HexMergeDocList &>(GetDocList(theApp.m_pHexMergeTemplate));
1505 std::list<CImgMergeFrame *> CMainFrame::GetAllImgMergeFrames()
1507 std::list<CImgMergeFrame *> list;
1508 // Close Non-Document/View frame with confirmation
1509 CMDIChildWnd *pChild = static_cast<CMDIChildWnd *>(CWnd::FromHandle(m_hWndMDIClient)->GetWindow(GW_CHILD));
1510 while (pChild != nullptr)
1512 CMDIChildWnd *pNextChild = static_cast<CMDIChildWnd *>(pChild->GetWindow(GW_HWNDNEXT));
1513 if (GetFrameType(pChild) == FRAME_IMGFILE)
1514 list.push_back(static_cast<CImgMergeFrame *>(pChild));
1515 pChild = pNextChild;
1521 * @brief Obtain a merge doc to display a difference in files.
1522 * @return Pointer to CMergeDoc to use.
1524 template<class DocClass>
1525 DocClass * GetMergeDocForDiff(CMultiDocTemplate *pTemplate, CDirDoc *pDirDoc, int nFiles, bool bMakeVisible)
1527 // Create a new merge doc
1528 DocClass::m_nBuffersTemp = nFiles;
1529 DocClass *pMergeDoc = static_cast<DocClass*>(pTemplate->OpenDocumentFile(nullptr, bMakeVisible));
1530 if (pMergeDoc != nullptr)
1532 pDirDoc->AddMergeDoc(pMergeDoc);
1533 pMergeDoc->SetDirDoc(pDirDoc);
1538 // Clear the item count in the main status pane
1539 void CMainFrame::ClearStatusbarItemCount()
1541 m_wndStatusBar.SetPaneText(2, _T(""));
1545 * @brief Generate patch from files selected.
1547 * Creates a patch from selected files in active directory compare, or
1548 * active file compare. Files in file compare must be saved before
1551 void CMainFrame::OnToolsGeneratePatch()
1554 patcher.CreatePatch();
1557 void CMainFrame::OnDropFiles(const std::vector<String>& dropped_files)
1559 PathContext tFiles(dropped_files);
1560 const size_t fileCount = tFiles.GetSize();
1562 bool recurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
1563 // Do a reverse comparison with the current 'Include Subfolders' settings when pressing Control key
1564 if (::GetAsyncKeyState(VK_CONTROL) & 0x8000)
1567 // If user has <Shift> pressed with one file selected,
1568 // assume it is an archive and set filenames to same
1569 if (::GetAsyncKeyState(VK_SHIFT) < 0 && fileCount == 1)
1571 tFiles.SetRight(tFiles[0]);
1574 // Check if they dropped a project file
1575 DWORD dwFlags[3] = {FFILEOPEN_NONE, FFILEOPEN_NONE, FFILEOPEN_NONE};
1578 if (theApp.IsProjectFile(tFiles[0]))
1580 theApp.LoadAndOpenProjectFile(tFiles[0]);
1583 if (IsConflictFile(tFiles[0]))
1585 DoOpenConflict(tFiles[0], nullptr, true);
1590 DoFileOrFolderOpen(&tFiles, dwFlags, nullptr, _T(""), recurse);
1593 void CMainFrame::OnPluginUnpackMode(UINT nID )
1597 case ID_UNPACK_MANUAL:
1598 FileTransform::AutoUnpacking = false;
1600 case ID_UNPACK_AUTO:
1601 FileTransform::AutoUnpacking = true;
1604 for (auto pDirDoc : GetAllDirDocs())
1606 pDirDoc->GetPluginManager().SetUnpackerSettingAll(FileTransform::AutoUnpacking);
1607 pDirDoc->UpdateAllViews(nullptr);
1609 GetOptionsMgr()->SaveOption(OPT_PLUGINS_UNPACKER_MODE, static_cast<int>(FileTransform::AutoUnpacking));
1612 void CMainFrame::OnUpdatePluginUnpackMode(CCmdUI* pCmdUI)
1614 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
1616 if (pCmdUI->m_nID == ID_UNPACK_MANUAL)
1617 pCmdUI->SetRadio(!FileTransform::AutoUnpacking);
1618 if (pCmdUI->m_nID == ID_UNPACK_AUTO)
1619 pCmdUI->SetRadio(FileTransform::AutoUnpacking);
1622 void CMainFrame::OnPluginPrediffMode(UINT nID )
1626 case ID_PREDIFFER_MANUAL:
1627 FileTransform::AutoPrediffing = false;
1629 case ID_PREDIFFER_AUTO:
1630 FileTransform::AutoPrediffing = true;
1633 PrediffingInfo infoPrediffer;
1634 for (auto pMergeDoc : GetAllMergeDocs())
1635 pMergeDoc->SetPrediffer(&infoPrediffer);
1636 for (auto pDirDoc : GetAllDirDocs())
1638 pDirDoc->GetPluginManager().SetPrediffSettingAll(FileTransform::AutoPrediffing);
1639 pDirDoc->UpdateAllViews(nullptr);
1641 GetOptionsMgr()->SaveOption(OPT_PLUGINS_PREDIFFER_MODE, FileTransform::AutoPrediffing);
1644 void CMainFrame::OnUpdatePluginPrediffMode(CCmdUI* pCmdUI)
1646 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
1648 if (pCmdUI->m_nID == ID_PREDIFFER_MANUAL)
1649 pCmdUI->SetRadio(!FileTransform::AutoPrediffing);
1650 if (pCmdUI->m_nID == ID_PREDIFFER_AUTO)
1651 pCmdUI->SetRadio(FileTransform::AutoPrediffing);
1654 * @brief Called when "Reload Plugins" item is updated
1656 void CMainFrame::OnUpdatePluginRelatedMenu(CCmdUI* pCmdUI)
1658 bool enabled = GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED);
1659 if (enabled && (pCmdUI->m_nID == ID_APPLY_PREDIFFER || pCmdUI->m_nID == ID_TRANSFORM_WITH_SCRIPT))
1660 enabled = GetFrameType(GetActiveFrame()) == FRAME_FILE;
1661 pCmdUI->Enable(enabled);
1664 void CMainFrame::OnReloadPlugins()
1666 UpdatePrediffersMenu();
1669 /** @brief Return active merge edit view, if can figure it out/is available */
1670 CMergeEditView * CMainFrame::GetActiveMergeEditView()
1672 // NB: GetActiveDocument does not return the Merge Doc
1673 // even when the merge edit view is in front
1674 // NB: CMergeEditFrame::GetActiveView returns `nullptr` when location view active
1675 // So we have this rather complicated logic to try to get a merge edit view
1676 // We look at the front child window, which should be a frame
1677 // and we can get a MergeEditView from it, if it is a CMergeEditFrame
1678 // (DirViews use a different frame type)
1679 CMergeEditFrame * pFrame = dynamic_cast<CMergeEditFrame *>(GetActiveFrame());
1680 if (pFrame == nullptr) return nullptr;
1681 // Try to get the active MergeEditView (ie, left or right)
1682 if (pFrame->GetActiveView() != nullptr && pFrame->GetActiveView()->IsKindOf(RUNTIME_CLASS(CMergeEditView)))
1684 return dynamic_cast<CMergeEditView *>(pFrame->GetActiveView());
1686 return pFrame->GetMergeDoc()->GetActiveMergeView();
1689 void CMainFrame::UpdatePrediffersMenu()
1691 CMenu* menu = GetMenu();
1692 if (menu == nullptr)
1697 HMENU hMainMenu = menu->m_hMenu;
1698 HMENU prediffersSubmenu = GetPrediffersSubmenu(hMainMenu);
1699 if (prediffersSubmenu != nullptr)
1701 CMergeEditView * pEditView = GetActiveMergeEditView();
1702 if (pEditView != nullptr)
1703 pEditView->GetDocument()->createPrediffersSubmenu(prediffersSubmenu);
1706 // no view or dir view : display an empty submenu
1707 int i = GetMenuItemCount(prediffersSubmenu);
1709 ::DeleteMenu(prediffersSubmenu, 0, MF_BYPOSITION);
1710 ::AppendMenu(prediffersSubmenu, MF_SEPARATOR, 0, nullptr);
1716 * @brief Save WinMerge configuration and info to file
1718 void CMainFrame::OnSaveConfigData()
1720 CConfigLog configLog;
1723 if (configLog.WriteLogFile(sError))
1725 String sFileName = configLog.GetFileName();
1726 CMergeApp::OpenFileToExternalEditor(sFileName);
1730 String sFileName = configLog.GetFileName();
1731 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sFileName, sError);
1732 AfxMessageBox(msg.c_str(), MB_OK | MB_ICONSTOP);
1737 * @brief Open two new empty docs, 'Scratchpads'
1739 * Allows user to open two empty docs, to paste text to
1740 * compare from clipboard.
1741 * @note File filenames are set emptys and filedescriptors
1742 * are loaded from resource.
1743 * @sa CMergeDoc::OpenDocs()
1744 * @sa CMergeDoc::TrySaveAs()
1746 bool CMainFrame::DoFileNew(UINT nID, int nPanes, const String strDesc[],
1747 const PrediffingInfo *infoPrediffer /*= nullptr*/,
1748 OpenTextFileParams *pOpenParams)
1750 CDirDoc *pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
1752 // Load emptyfile descriptors and open empty docs
1753 // Use default codepage
1754 DWORD dwFlags[3] = {0, 0, 0};
1755 FileLocation fileloc[3];
1759 strDesc2[0] = _("Untitled left");
1760 strDesc2[1] = _("Untitled right");
1764 strDesc2[0] = _("Untitled left");
1765 strDesc2[1] = _("Untitled middle");
1766 strDesc2[2] = _("Untitled right");
1768 for (int i = 0; i < nPanes; ++i)
1770 if (strDesc && !strDesc[i].empty())
1771 strDesc2[i] = strDesc[i];
1772 fileloc[i].encoding.SetCodepage(ucr::getDefaultCodepage());
1774 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
1775 pDirDoc->GetPluginManager().SetPrediffer(_T("|"), infoPrediffer->GetPluginPipeline());
1776 return ShowMergeDoc(nID, pDirDoc, nPanes, fileloc, dwFlags, strDesc2, _T(""), nullptr, pOpenParams);
1780 * @brief Open two new empty docs, 'Scratchpads'
1782 * Allows user to open two empty docs, to paste text to
1783 * compare from clipboard.
1784 * @note File filenames are set emptys and filedescriptors
1785 * are loaded from resource.
1786 * @sa CMergeDoc::OpenDocs()
1787 * @sa CMergeDoc::TrySaveAs()
1789 template <int nFiles, unsigned nID>
1790 void CMainFrame::OnFileNew()
1792 DoFileNew(nID, nFiles);
1796 * @brief Open Filters dialog
1798 void CMainFrame::OnToolsFilters()
1800 String title = _("Filters");
1801 CPropertySheet sht(title.c_str());
1802 LineFiltersDlg lineFiltersDlg;
1803 SubstitutionFiltersDlg substitutionFiltersDlg;
1804 FileFiltersDlg fileFiltersDlg;
1805 std::unique_ptr<LineFiltersList> lineFilters(new LineFiltersList());
1806 std::unique_ptr<SubstitutionFiltersList> SubstitutionFilters(new SubstitutionFiltersList());
1807 String selectedFilter;
1808 const String origFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1809 sht.AddPage(&fileFiltersDlg);
1810 sht.AddPage(&lineFiltersDlg);
1811 sht.AddPage(&substitutionFiltersDlg);
1812 sht.m_psh.dwFlags |= PSH_NOAPPLYNOW; // Hide 'Apply' button since we don't need it
1814 // Make sure all filters are up-to-date
1815 theApp.m_pGlobalFileFilter->ReloadUpdatedFilters();
1817 fileFiltersDlg.SetFilterArray(theApp.m_pGlobalFileFilter->GetFileFilters(selectedFilter));
1818 fileFiltersDlg.SetSelected(selectedFilter);
1819 const bool lineFiltersEnabledOrig = GetOptionsMgr()->GetBool(OPT_LINEFILTER_ENABLED);
1820 lineFiltersDlg.m_bIgnoreRegExp = lineFiltersEnabledOrig;
1822 lineFilters->CloneFrom(theApp.m_pLineFilters.get());
1823 lineFiltersDlg.SetList(lineFilters.get());
1825 SubstitutionFilters->CloneFrom(theApp.m_pSubstitutionFiltersList.get());
1826 substitutionFiltersDlg.SetList(SubstitutionFilters.get());
1828 sht.SetActivePage(AfxGetApp()->GetProfileInt(_T("Settings"), _T("FilterStartPage"), 0));
1830 if (sht.DoModal() == IDOK)
1832 String strNone = _("<None>");
1833 String path = fileFiltersDlg.GetSelected();
1834 if (path.find(strNone) != String::npos)
1836 // Don't overwrite mask we already have
1837 if (!theApp.m_pGlobalFileFilter->IsUsingMask())
1839 String sFilter(_T("*.*"));
1840 theApp.m_pGlobalFileFilter->SetFilter(sFilter);
1841 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, sFilter);
1846 theApp.m_pGlobalFileFilter->SetFileFilterPath(path);
1847 theApp.m_pGlobalFileFilter->UseMask(false);
1848 String sFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1849 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, sFilter);
1851 bool linefiltersEnabled = lineFiltersDlg.m_bIgnoreRegExp;
1852 GetOptionsMgr()->SaveOption(OPT_LINEFILTER_ENABLED, linefiltersEnabled);
1854 // Check if compare documents need rescanning
1855 bool bFileCompareRescan = false;
1856 bool bFolderCompareRescan = false;
1857 CFrameWnd * pFrame = GetActiveFrame();
1858 FRAMETYPE frame = GetFrameType(pFrame);
1859 if (frame == FRAME_FILE)
1863 linefiltersEnabled != lineFiltersEnabledOrig
1864 || !lineFilters->Compare(theApp.m_pLineFilters.get())
1865 || !SubstitutionFilters->Compare(theApp.m_pSubstitutionFiltersList.get())
1868 bFileCompareRescan = true;
1871 else if (frame == FRAME_FOLDER)
1873 const String newFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1874 if (lineFiltersEnabledOrig != linefiltersEnabled ||
1875 !theApp.m_pLineFilters->Compare(lineFilters.get()) || origFilter != newFilter)
1877 int res = LangMessageBox(IDS_FILTERCHANGED, MB_ICONWARNING | MB_YESNO);
1879 bFolderCompareRescan = true;
1883 // Save new filters before (possibly) rescanning
1884 theApp.m_pLineFilters->CloneFrom(lineFilters.get());
1885 theApp.m_pLineFilters->SaveFilters();
1887 theApp.m_pSubstitutionFiltersList->CloneFrom(SubstitutionFilters.get());
1888 theApp.m_pSubstitutionFiltersList->SaveFilters();
1890 if (bFileCompareRescan)
1892 for (auto pMergeDoc : GetAllMergeDocs())
1893 pMergeDoc->FlushAndRescan(true);
1895 else if (bFolderCompareRescan)
1897 for (auto pDirDoc : GetAllDirDocs())
1904 * @brief Open Filters dialog.
1906 void CMainFrame::SelectFilter()
1912 * @brief Closes application with ESC.
1914 * Application is closed if:
1915 * - 'Close Windows with ESC' option is enabled and
1916 * there is no open document windows
1917 * - '-e' commandline switch is given
1919 BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
1921 // Check if we got 'ESC pressed' -message
1922 if ((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
1924 if (theApp.m_bEscShutdown && m_wndTabBar.GetItemCount() <= 1)
1926 AfxGetMainWnd()->SendMessage(WM_COMMAND, ID_APP_EXIT);
1929 else if (GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC) == 1 && m_wndTabBar.GetItemCount() == 0)
1931 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_APP_EXIT);
1936 if (WM_KEYDOWN == pMsg->message && VK_TAB == pMsg->wParam && GetAsyncKeyState(VK_CONTROL) < 0 && m_arrChild.GetSize() > 1)
1938 CWindowsManagerDialog* pDlg = new CWindowsManagerDialog;
1939 pDlg->Create(CWindowsManagerDialog::IDD, this);
1940 pDlg->ShowWindow(SW_SHOW);
1944 return CMDIFrameWnd::PreTranslateMessage(pMsg);
1948 * @brief Show/hide statusbar.
1950 void CMainFrame::OnViewStatusBar()
1952 bool bShow = !GetOptionsMgr()->GetBool(OPT_SHOW_STATUSBAR);
1953 GetOptionsMgr()->SaveOption(OPT_SHOW_STATUSBAR, bShow);
1955 CMDIFrameWnd::ShowControlBar(&m_wndStatusBar, bShow, 0);
1959 * @brief Updates "Show Tabbar" menuitem.
1961 void CMainFrame::OnUpdateViewTabBar(CCmdUI* pCmdUI)
1963 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR));
1967 * @brief Show/hide tabbar.
1969 void CMainFrame::OnViewTabBar()
1971 bool bShow = !GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR);
1972 GetOptionsMgr()->SaveOption(OPT_SHOW_TABBAR, bShow);
1974 CMDIFrameWnd::ShowControlBar(&m_wndTabBar, bShow, 0);
1978 * @brief Updates "Automatically Resize Panes" menuitem.
1980 void CMainFrame::OnUpdateResizePanes(CCmdUI* pCmdUI)
1982 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_RESIZE_PANES));
1987 * @brief Enable/disable automatic pane resizing.
1989 void CMainFrame::OnResizePanes()
1991 bool bResize = !GetOptionsMgr()->GetBool(OPT_RESIZE_PANES);
1992 GetOptionsMgr()->SaveOption(OPT_RESIZE_PANES, bResize);
1993 // TODO: Introduce a common merge frame superclass?
1994 CFrameWnd *pActiveFrame = GetActiveFrame();
1995 if (CMergeEditFrame *pFrame = DYNAMIC_DOWNCAST(CMergeEditFrame, pActiveFrame))
1997 pFrame->UpdateAutoPaneResize();
1999 pFrame->UpdateSplitter();
2001 else if (CHexMergeFrame *pFrame1 = DYNAMIC_DOWNCAST(CHexMergeFrame, pActiveFrame))
2003 pFrame1->UpdateAutoPaneResize();
2005 pFrame1->UpdateSplitter();
2010 * @brief Open project-file.
2012 void CMainFrame::OnFileOpenProject()
2016 // get the default projects path
2017 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
2018 if (!SelectFile(GetSafeHwnd(), sFilepath, true, strProjectPath.c_str(), _T(""),
2019 _("WinMerge Project Files (*.WinMerge)|*.WinMerge||")))
2022 strProjectPath = paths::GetParentPath(sFilepath);
2023 // store this as the new project path
2024 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
2026 theApp.LoadAndOpenProjectFile(sFilepath);
2030 * @brief Receive command line from another instance.
2032 * This function receives command line when only single-instance
2033 * is allowed. New instance tried to start sends its command line
2034 * to here so we can open paths it was meant to.
2036 LRESULT CMainFrame::OnCopyData(WPARAM wParam, LPARAM lParam)
2038 COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;
2039 LPCTSTR pchData = (LPCTSTR)pCopyData->lpData;
2040 // Bail out if data isn't zero-terminated
2041 DWORD cchData = pCopyData->cbData / sizeof(TCHAR);
2042 if (cchData == 0 || pchData[cchData - 1] != _T('\0'))
2045 MergeCmdLineInfo cmdInfo(pchData);
2046 theApp.ApplyCommandLineConfigOptions(cmdInfo);
2047 theApp.ParseArgsAndDoOpen(cmdInfo, this);
2051 LRESULT CMainFrame::OnUser1(WPARAM wParam, LPARAM lParam)
2053 if (IMergeDoc *pMergeDoc = GetActiveIMergeDoc())
2054 pMergeDoc->CheckFileChanged();
2059 * @brief Close all open windows.
2061 * Asks about saving unsaved files and then closes all open windows.
2063 void CMainFrame::OnWindowCloseAll()
2065 CMDIChildWnd *pChild = MDIGetActive();
2066 while (pChild != nullptr)
2069 if ((pDoc = pChild->GetActiveDocument()) != nullptr)
2071 if (!pDoc->SaveModified())
2073 pDoc->OnCloseDocument();
2075 else if (GetFrameType(pChild) == FRAME_IMGFILE)
2077 if (!static_cast<CImgMergeFrame *>(pChild)->CloseNow())
2082 pChild->DestroyWindow();
2084 pChild = MDIGetActive();
2090 * @brief Enables Window/Close All item if there are open windows.
2092 void CMainFrame::OnUpdateWindowCloseAll(CCmdUI* pCmdUI)
2094 pCmdUI->Enable(MDIGetActive() != nullptr);
2098 * @brief Access to the singleton main frame (where we have some globals)
2100 CMainFrame * GetMainFrame()
2102 CWnd * mainwnd = AfxGetMainWnd();
2103 ASSERT(mainwnd != nullptr);
2104 CMainFrame *pMainframe = dynamic_cast<CMainFrame*>(mainwnd);
2105 ASSERT(pMainframe != nullptr);
2110 * @brief Opens dialog for user to Load, edit and save project files.
2111 * This dialog gets current compare paths and filter (+other properties
2112 * possible in project files) and initializes the dialog with them.
2114 void CMainFrame::OnSaveProject()
2116 if (m_pMenus[MENU_OPENVIEW] == nullptr)
2117 theApp.m_pOpenTemplate->m_hMenuShared = NewOpenViewMenu();
2118 COpenDoc *pOpenDoc = static_cast<COpenDoc *>(theApp.m_pOpenTemplate->CreateNewDocument());
2120 CFrameWnd * pFrame = GetActiveFrame();
2121 FRAMETYPE frame = pFrame ? GetFrameType(pFrame) : FRAME_OTHER;
2123 if (frame == FRAME_FILE || frame == FRAME_HEXFILE || frame == FRAME_IMGFILE)
2125 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2128 for (int pane = 0; pane < pMergeDoc->GetFileCount(); ++pane)
2130 pOpenDoc->m_dwFlags[pane] = FFILEOPEN_PROJECT | (pMergeDoc->GetReadOnly(pane) ? FFILEOPEN_READONLY : 0);
2131 paths.SetPath(pane, pMergeDoc->GetPath(pane));
2133 pOpenDoc->m_files = paths;
2134 pOpenDoc->m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
2135 pOpenDoc->m_strExt = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
2136 pOpenDoc->m_strUnpackerPipeline = pMergeDoc->GetUnpacker()->GetPluginPipeline();
2139 else if (frame == FRAME_FOLDER)
2141 // Get paths currently in compare
2142 if (const CDirDoc* pDoc = static_cast<const CDirDoc*>(pFrame->GetActiveDocument()))
2144 const CDiffContext& ctxt = pDoc->GetDiffContext();
2146 // Set-up the dialog
2147 for (int pane = 0; pane < ctxt.GetCompareDirs(); ++pane)
2149 pOpenDoc->m_dwFlags[pane] = FFILEOPEN_PROJECT | (pDoc->GetReadOnly(pane) ? FFILEOPEN_READONLY : 0);
2150 pOpenDoc->m_files.SetPath(pane, paths::AddTrailingSlash(ctxt.GetNormalizedPath(pane)));
2152 pOpenDoc->m_bRecurse = ctxt.m_bRecursive;
2153 pOpenDoc->m_strExt = static_cast<FileFilterHelper*>(ctxt.m_piFilterGlobal)->GetFilterNameOrMask();
2157 CFrameWnd *pOpenFrame = theApp.m_pOpenTemplate->CreateNewFrame(pOpenDoc, nullptr);
2158 theApp.m_pOpenTemplate->InitialUpdateFrame(pOpenFrame, pOpenDoc);
2162 * @brief Start flashing window if window is inactive.
2164 void CMainFrame::StartFlashing()
2166 CWnd * activeWindow = GetActiveWindow();
2167 if (activeWindow != this)
2168 FlashWindowEx(FLASHW_ALL | FLASHW_TIMERNOFG, 3, 0);
2171 #if _MFC_VER > 0x0600
2172 void CMainFrame::OnActivateApp(BOOL bActive, DWORD dwThreadID)
2174 void CMainFrame::OnActivateApp(BOOL bActive, HTASK hTask)
2177 #if _MFC_VER > 0x0600
2178 CMDIFrameWnd::OnActivateApp(bActive, dwThreadID);
2180 CMDIFrameWnd::OnActivateApp(bActive, hTask);
2183 if (IMergeDoc *pMergeDoc = GetActiveIMergeDoc())
2184 PostMessage(WM_USER+1);
2187 BOOL CMainFrame::CreateToolbar()
2189 if (!m_wndToolBar.CreateEx(this) ||
2190 !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
2195 if (!m_wndReBar.Create(this, RBS_BANDBORDERS,
2196 WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CBRS_ALIGN_TOP))
2201 VERIFY(m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT|TBSTYLE_TRANSPARENT));
2203 // Remove this if you don't want tool tips or a resizable toolbar
2204 m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
2205 CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
2206 m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS);
2208 m_wndReBar.AddBar(&m_wndToolBar);
2210 LoadToolbarImages();
2213 for (auto cmd : { ID_OPTIONS, ID_FILE_NEW, ID_FILE_OPEN, ID_FILE_SAVE })
2216 int index = m_wndToolBar.GetToolBarCtrl().CommandToIndex(cmd);
2217 m_wndToolBar.GetButtonInfo(index, nID, nStyle, iImage);
2218 nStyle |= TBSTYLE_DROPDOWN;
2219 m_wndToolBar.SetButtonInfo(index, nID, nStyle, iImage);
2222 if (!GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR))
2224 CMDIFrameWnd::ShowControlBar(&m_wndToolBar, false, 0);
2230 /** @brief Load toolbar images from the resource. */
2231 void CMainFrame::LoadToolbarImages()
2233 const int toolbarNewImgSize = MulDiv(16, GetSystemMetrics(SM_CXSMICON), 16) * (1 + GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE));
2234 const int toolbarOrgImgSize = toolbarNewImgSize <= 20 ? 16 : 32;
2235 CToolBarCtrl& BarCtrl = m_wndToolBar.GetToolBarCtrl();
2237 m_ToolbarImages[TOOLBAR_IMAGES_ENABLED].Detach();
2238 m_ToolbarImages[TOOLBAR_IMAGES_DISABLED].Detach();
2239 CSize sizeButton(0, 0);
2241 LoadToolbarImageList(toolbarOrgImgSize, toolbarNewImgSize,
2242 toolbarOrgImgSize <= 16 ? IDB_TOOLBAR_ENABLED : IDB_TOOLBAR_ENABLED32,
2243 false, m_ToolbarImages[TOOLBAR_IMAGES_ENABLED]);
2244 LoadToolbarImageList(toolbarOrgImgSize, toolbarNewImgSize,
2245 toolbarOrgImgSize <= 16 ? IDB_TOOLBAR_ENABLED : IDB_TOOLBAR_ENABLED32,
2246 true, m_ToolbarImages[TOOLBAR_IMAGES_DISABLED]);
2248 sizeButton = CSize(toolbarNewImgSize + 8, toolbarNewImgSize + 8);
2250 BarCtrl.SetButtonSize(sizeButton);
2251 if (CImageList *pImgList = BarCtrl.SetImageList(&m_ToolbarImages[TOOLBAR_IMAGES_ENABLED]))
2252 pImgList->DeleteImageList();
2253 if (CImageList *pImgList = BarCtrl.SetDisabledImageList(&m_ToolbarImages[TOOLBAR_IMAGES_DISABLED]))
2254 pImgList->DeleteImageList();
2256 // resize the rebar.
2257 REBARBANDINFO rbbi = { sizeof REBARBANDINFO };
2258 rbbi.fMask = RBBIM_CHILDSIZE;
2259 rbbi.cyMinChild = sizeButton.cy;
2260 m_wndReBar.GetReBarCtrl().SetBandInfo(0, &rbbi);
2265 * @brief Load a transparent 32-bit color image list.
2267 static void LoadHiColImageList(UINT nIDResource, int nWidth, int nHeight, int nNewWidth, int nNewHeight, int nCount, bool bGrayscale, CImageList& ImgList)
2270 bm.Attach(LoadBitmapAndConvertTo32bit(AfxGetInstanceHandle(), nIDResource, nNewWidth * nCount, nNewHeight, bGrayscale, RGB(0xff, 0, 0xff)));
2272 VERIFY(ImgList.Create(nNewWidth, nNewHeight, ILC_COLOR32, nCount, 0));
2273 int nIndex = ImgList.Add(&bm, nullptr);
2274 ASSERT(-1 != nIndex);
2278 * @brief Load toolbar image list.
2280 static void LoadToolbarImageList(int orgImageWidth, int newImageWidth, UINT nIDResource, bool bGrayscale, CImageList& ImgList)
2282 const int ImageCount = 26;
2283 const int orgImageHeight = orgImageWidth - 1;
2284 const int newImageHeight = newImageWidth - 1;
2285 LoadHiColImageList(nIDResource, orgImageWidth, orgImageHeight, newImageWidth, newImageHeight, ImageCount, bGrayscale, ImgList);
2289 * @brief Called when the document title is modified.
2291 void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
2293 CFrameWnd::OnUpdateFrameTitle(bAddToTitle);
2295 if (m_wndTabBar.m_hWnd != nullptr)
2296 m_wndTabBar.UpdateTabs();
2299 /** @brief Show none/small/big/huge toolbar. */
2300 void CMainFrame::OnToolbarSize(UINT id)
2302 if (id == ID_TOOLBAR_NONE)
2304 GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, false);
2305 CMDIFrameWnd::ShowControlBar(&m_wndToolBar, false, 0);
2309 GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, true);
2310 GetOptionsMgr()->SaveOption(OPT_TOOLBAR_SIZE, id - ID_TOOLBAR_SMALL);
2312 LoadToolbarImages();
2314 CMDIFrameWnd::ShowControlBar(&m_wndToolBar, true, 0);
2318 /** @brief Show none/small/big/huge toolbar. */
2319 void CMainFrame::OnUpdateToolbarSize(CCmdUI *pCmdUI)
2321 if (!GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR))
2322 pCmdUI->SetRadio(pCmdUI->m_nID == ID_TOOLBAR_NONE);
2324 pCmdUI->SetRadio((pCmdUI->m_nID - ID_TOOLBAR_SMALL) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE)));
2327 /** @brief Lang aware version of CFrameWnd::OnToolTipText() */
2328 BOOL CMainFrame::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
2330 ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);
2332 // need to handle both ANSI and UNICODE versions of the message
2333 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
2334 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
2337 UINT_PTR nID = pNMHDR->idFrom;
2338 if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
2339 pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
2341 // idFrom is actually the HWND of the tool
2342 nID = ::GetDlgCtrlID((HWND)nID);
2345 if (nID != 0) // will be zero on a separator
2347 strFullText = theApp.LoadString(static_cast<UINT>(nID));
2348 // don't handle the message if no string resource found
2349 if (strFullText.empty())
2352 // this is the command id, not the button index
2353 AfxExtractSubString(strTipText, strFullText.c_str(), 1, '\n');
2355 if (pNMHDR->code == TTN_NEEDTEXTA)
2356 _wcstombsz(pTTTA->szText, strTipText, static_cast<ULONG>(std::size(pTTTA->szText)));
2358 lstrcpyn(pTTTW->szText, strTipText, static_cast<int>(std::size(pTTTW->szText)));
2361 // bring the tooltip window above other popup windows
2362 ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
2363 SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
2365 return TRUE; // message was handled
2369 * @brief Ask user for close confirmation when closing the mainframe.
2370 * This function asks if user wants to close multiple open windows when user
2371 * selects (perhaps accidentally) to close WinMerge (application).
2372 * @return true if user agreeds to close all windows.
2374 bool CMainFrame::AskCloseConfirmation()
2376 const DirDocList &dirdocs = GetAllDirDocs();
2377 const MergeDocList &mergedocs = GetAllMergeDocs();
2380 const size_t count = dirdocs.GetCount() + mergedocs.GetCount();
2383 // Check that we don't have one empty dirdoc + mergedoc situation.
2384 // That happens since we open "hidden" dirdoc for every file compare.
2385 if (dirdocs.GetCount() == 1)
2387 CDirDoc *pDoc = dirdocs.GetHead();
2388 if (!pDoc->HasDiffs())
2391 ret = LangMessageBox(IDS_CLOSEALL_WINDOWS, MB_YESNO | MB_ICONWARNING);
2393 return (ret == IDYES);
2397 * @brief Shows the release notes for user.
2398 * This function opens release notes HTML document into browser.
2400 void CMainFrame::OnHelpReleasenotes()
2402 const String sPath = paths::ConcatPath(env::GetProgPath(), RelNotes);
2403 shell::Open(sPath.c_str());
2407 * @brief Shows the translations page.
2408 * This function opens translations page URL into browser.
2410 void CMainFrame::OnHelpTranslations()
2412 shell::Open(TranslationsUrl);
2416 * @brief Called when user selects File/Open Conflict...
2418 void CMainFrame::OnFileOpenConflict()
2420 String conflictFile;
2421 if (SelectFile(GetSafeHwnd(), conflictFile))
2423 DoOpenConflict(conflictFile);
2428 * @brief Select and open conflict file for resolving.
2429 * This function lets user to select conflict file to resolve.
2430 * Then we parse conflict file to two files to "merge" and
2431 * save resulting file over original file.
2433 * Set left-side file read-only as it is the repository file which cannot
2434 * be modified anyway. Right-side file is user's file which is set as
2435 * modified by default so user can just save it and accept workspace
2436 * file as resolved file.
2437 * @param [in] conflictFile Full path to conflict file to open.
2438 * @param [in] checked If true, do not check if it really is project file.
2439 * @return `true` if conflict file was opened for resolving.
2441 bool CMainFrame::DoOpenConflict(const String& conflictFile, const String strDesc[] /*= nullptr*/, bool checked /*= false*/)
2443 bool conflictCompared = false;
2447 bool confFile = IsConflictFile(conflictFile);
2450 String message = strutils::format_string1(_("The file\n%1\nis not a conflict file."), conflictFile);
2451 AfxMessageBox(message.c_str(), MB_ICONSTOP);
2456 // Create temp files and put them into the list,
2457 // from where they get deleted when MainFrame is deleted.
2458 String ext = paths::FindExtension(conflictFile);
2459 TempFilePtr wTemp(new TempFile());
2460 String workFile = wTemp->Create(_T("confw_"), ext);
2461 m_tempFiles.push_back(wTemp);
2462 TempFilePtr vTemp(new TempFile());
2463 String revFile = vTemp->Create(_T("confv_"), ext);
2464 m_tempFiles.push_back(vTemp);
2465 TempFilePtr bTemp(new TempFile());
2466 String baseFile = vTemp->Create(_T("confb_"), ext);
2467 m_tempFiles.push_back(bTemp);
2469 // Parse conflict file into two files.
2470 bool inners, threeWay;
2471 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
2472 bool success = ParseConflictFile(conflictFile, workFile, revFile, baseFile, iGuessEncodingType, inners, threeWay);
2476 // Open two parsed files to WinMerge, telling WinMerge to
2477 // save over original file (given as third filename).
2478 theApp.m_strSaveAsPath = conflictFile;
2481 String strDesc2[2] = {
2482 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Theirs File"),
2483 (strDesc && !strDesc[2].empty()) ? strDesc[2] : _("Mine File") };
2484 DWORD dwFlags[2] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU | FFILEOPEN_MODIFIED};
2485 PathContext tmpPathContext(revFile, workFile);
2486 conflictCompared = DoFileOrFolderOpen(&tmpPathContext, dwFlags, strDesc2);
2490 String strDesc3[3] = {
2491 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Base File"),
2492 (strDesc && !strDesc[1].empty()) ? strDesc[1] : _("Theirs File"),
2493 (strDesc && !strDesc[2].empty()) ? strDesc[2] : _("Mine File") };
2494 PathContext tmpPathContext(baseFile, revFile, workFile);
2495 DWORD dwFlags[3] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU | FFILEOPEN_MODIFIED};
2496 conflictCompared = DoFileOrFolderOpen(&tmpPathContext, dwFlags, strDesc3);
2501 LangMessageBox(IDS_ERROR_CONF_RESOLVE, MB_ICONSTOP);
2503 return conflictCompared;
2506 bool CMainFrame::DoSelfCompare(UINT nID, const String& file, const String strDesc[] /*= nullptr*/,
2507 const PackingInfo *infoUnpacker /*= nullptr*/, const PrediffingInfo *infoPrediffer /*= nullptr*/,
2508 OpenTextFileParams *pOpenParams /*= nullptr*/)
2510 String ext = paths::FindExtension(file);
2511 TempFilePtr wTemp(new TempFile());
2512 String copiedFile = wTemp->Create(_T("self-compare_"), ext);
2513 m_tempFiles.push_back(wTemp);
2515 TFile(file).copyTo(copiedFile);
2517 String strDesc2[2] = {
2518 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Original File"),
2519 (strDesc && !strDesc[1].empty()) ? strDesc[1] : _("") };
2520 DWORD dwFlags[2] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU};
2521 PathContext tmpPathContext(copiedFile, file);
2522 return DoFileOpen(nID, &tmpPathContext, dwFlags, strDesc2, _T(""), infoUnpacker, infoPrediffer, pOpenParams);
2526 * @brief Get type of frame (File/Folder compare).
2527 * @param [in] pFrame Pointer to frame to check.
2528 * @return FRAMETYPE of the given frame.
2530 CMainFrame::FRAMETYPE CMainFrame::GetFrameType(const CFrameWnd * pFrame)
2532 bool bMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame));
2533 bool bHexMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame));
2534 bool bImgMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame));
2535 bool bDirFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame));
2539 else if (bHexMergeFrame)
2540 return FRAME_HEXFILE;
2541 else if (bImgMergeFrame)
2542 return FRAME_IMGFILE;
2544 return FRAME_FOLDER;
2550 * @brief Show the plugins list dialog.
2552 void CMainFrame::OnPluginsList()
2558 void CMainFrame::OnToolbarButtonDropDown(NMHDR* pNMHDR, LRESULT* pResult)
2560 LPNMTOOLBAR pToolBar = reinterpret_cast<LPNMTOOLBAR>(pNMHDR);
2561 ClientToScreen(&(pToolBar->rcButton));
2564 switch (pToolBar->iItem)
2570 id = IDR_POPUP_OPEN;
2573 id = IDR_POPUP_SAVE;
2576 id = IDR_POPUP_DIFF_OPTIONS;
2579 VERIFY(menu.LoadMenu(id));
2580 theApp.TranslateMenu(menu.m_hMenu);
2581 CMenu* pPopup = menu.GetSubMenu(0);
2582 if (pPopup != nullptr)
2584 pPopup->TrackPopupMenu(TPM_RIGHTALIGN | TPM_RIGHTBUTTON,
2585 pToolBar->rcButton.right, pToolBar->rcButton.bottom, this);
2589 void CMainFrame::OnDiffWhitespace(UINT nID)
2591 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, nID - ID_DIFF_OPTIONS_WHITESPACE_COMPARE);
2595 void CMainFrame::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
2597 pCmdUI->SetRadio((pCmdUI->m_nID - ID_DIFF_OPTIONS_WHITESPACE_COMPARE) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE)));
2601 void CMainFrame::OnDiffIgnoreBlankLines()
2603 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES));
2607 void CMainFrame::OnUpdateDiffIgnoreBlankLines(CCmdUI* pCmdUI)
2609 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES));
2613 void CMainFrame::OnDiffIgnoreCase()
2615 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE));
2619 void CMainFrame::OnUpdateDiffIgnoreCase(CCmdUI* pCmdUI)
2621 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE));
2625 void CMainFrame::OnDiffIgnoreEOL()
2627 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL));
2631 void CMainFrame::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
2633 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL));
2637 void CMainFrame::OnDiffIgnoreCP()
2639 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE));
2643 void CMainFrame::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
2645 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE));
2649 void CMainFrame::OnDiffIgnoreComments()
2651 GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, !GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES));
2655 void CMainFrame::OnUpdateDiffIgnoreComments(CCmdUI* pCmdUI)
2657 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES));
2661 void CMainFrame::OnIncludeSubfolders()
2663 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, !GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS));
2664 // Update all dirdoc settings
2665 for (auto pDirDoc : GetAllDirDocs())
2666 pDirDoc->RefreshOptions();
2667 for (auto pOpenDoc : GetAllOpenDocs())
2668 pOpenDoc->RefreshOptions();
2671 void CMainFrame::OnUpdateIncludeSubfolders(CCmdUI* pCmdUI)
2673 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS));
2677 void CMainFrame::OnCompareMethod(UINT nID)
2679 GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, nID - ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS);
2682 void CMainFrame::OnUpdateCompareMethod(CCmdUI* pCmdUI)
2684 pCmdUI->SetRadio((pCmdUI->m_nID - ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_CMP_METHOD)));
2688 void CMainFrame::OnMRUs(UINT nID)
2690 std::vector<JumpList::Item> mrus = JumpList::GetRecentDocs(GetOptionsMgr()->GetInt(OPT_MRU_MAX));
2691 const size_t idx = static_cast<size_t>(nID) - ID_MRU_FIRST;
2692 if (idx < mrus.size())
2694 MergeCmdLineInfo cmdInfo((_T("\"") + mrus[idx].path + _T("\" ") + mrus[idx].params).c_str());
2695 theApp.ParseArgsAndDoOpen(cmdInfo, this);
2699 void CMainFrame::OnUpdateNoMRUs(CCmdUI* pCmdUI)
2701 // append the MRU submenu
2702 CMenu *pMenu = pCmdUI->m_pSubMenu ? pCmdUI->m_pSubMenu : pCmdUI->m_pMenu;
2703 if (pMenu == nullptr)
2705 HMENU hMenu = pMenu->m_hMenu;
2708 size_t i = ::GetMenuItemCount(hMenu);
2710 ::DeleteMenu(hMenu, 0, MF_BYPOSITION);
2712 std::vector<JumpList::Item> mrus = JumpList::GetRecentDocs(GetOptionsMgr()->GetInt(OPT_MRU_MAX));
2714 if (mrus.size() == 0)
2716 // no script : create a <empty> entry
2717 ::AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, theApp.LoadString(IDS_NO_EDIT_SCRIPTS).c_str());
2721 // or fill in the submenu with the scripts names
2722 int ID = ID_MRU_FIRST; // first ID in menu
2723 for (i = 0 ; i < mrus.size() ; i++, ID++)
2724 ::AppendMenu(hMenu, MF_STRING, ID,
2725 ((i < 9 ? strutils::format(_T("&%d "), i+1) : strutils::format(_T("&%c "), 'a' + i - 9))
2726 + mrus[i].title).c_str());
2729 pCmdUI->Enable(true);
2733 * @brief Update plugin name
2734 * @param [in] pCmdUI UI component to update.
2736 void CMainFrame::OnUpdatePluginName(CCmdUI* pCmdUI)
2738 if (auto pMergeDoc = GetActiveIMergeDoc())
2741 const PackingInfo* infoUnpacker = pMergeDoc->GetUnpacker();
2742 if (infoUnpacker && !infoUnpacker->GetPluginPipeline().empty())
2743 pluginNames += infoUnpacker->GetPluginPipeline() + _T("&");
2744 const PrediffingInfo* infoPrediffer = pMergeDoc->GetPrediffer();
2745 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
2746 pluginNames += infoPrediffer->GetPluginPipeline() + _T("&");
2747 pCmdUI->SetText(pluginNames.substr(0, pluginNames.length() - 1).c_str());
2750 pCmdUI->SetText(_T(""));
2754 * @brief Move to next file
2756 void CMainFrame::OnNextFile()
2758 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2759 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2760 pDirDoc->MoveToNextFile(pMergeDoc);
2764 * @brief Called when Move to next file is updated
2766 void CMainFrame::OnUpdateNextFile(CCmdUI* pCmdUI)
2768 bool enabled = false;
2769 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2770 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2771 enabled = !pDirDoc->IsLastFile();
2772 pCmdUI->Enable(enabled);
2776 * @brief Move to previous file
2778 void CMainFrame::OnPrevFile()
2780 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2781 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2782 pDirDoc->MoveToPrevFile(pMergeDoc);
2786 * @brief Called when Move to previous file is updated
2788 void CMainFrame::OnUpdatePrevFile(CCmdUI* pCmdUI)
2790 bool enabled = false;
2791 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2792 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2793 enabled = !pDirDoc->IsFirstFile();
2794 pCmdUI->Enable(enabled);
2798 * @brief Move to first file
2800 void CMainFrame::OnFirstFile()
2802 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2803 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2804 pDirDoc->MoveToFirstFile(pMergeDoc);
2808 * @brief Called when Move to first file is updated
2810 void CMainFrame::OnUpdateFirstFile(CCmdUI* pCmdUI)
2812 bool enabled = false;
2813 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2814 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2815 enabled = !pDirDoc->IsFirstFile();
2816 pCmdUI->Enable(enabled);
2820 * @brief Move to last file
2822 void CMainFrame::OnLastFile()
2824 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2825 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2826 pDirDoc->MoveToLastFile(pMergeDoc);
2830 * @brief Called when Move to last file item is updated
2832 void CMainFrame::OnUpdateLastFile(CCmdUI* pCmdUI)
2834 bool enabled = false;
2835 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2836 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
2837 enabled = !pDirDoc->IsLastFile();
2838 pCmdUI->Enable(enabled);
2841 void CMainFrame::ReloadMenu()
2843 // set the menu of the main frame window
2844 UINT idMenu = IDR_MAINFRAME;
2845 CMergeApp *pApp = dynamic_cast<CMergeApp *> (AfxGetApp());
2846 CMainFrame * pMainFrame = dynamic_cast<CMainFrame *> ((CFrameWnd*)pApp->m_pMainWnd);
2847 HMENU hNewDefaultMenu = pMainFrame->NewDefaultMenu(idMenu);
2848 HMENU hNewMergeMenu = pMainFrame->NewMergeViewMenu();
2849 HMENU hNewImgMergeMenu = pMainFrame->NewImgMergeViewMenu();
2850 HMENU hNewDirMenu = pMainFrame->NewDirViewMenu();
2851 if (hNewDefaultMenu != nullptr && hNewMergeMenu != nullptr && hNewDirMenu != nullptr)
2853 // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
2854 CMenu * pNewDefaultMenu = CMenu::FromHandle(hNewDefaultMenu);
2855 CMenu * pNewMergeMenu = CMenu::FromHandle(hNewMergeMenu);
2856 CMenu * pNewImgMergeMenu = CMenu::FromHandle(hNewImgMergeMenu);
2857 CMenu * pNewDirMenu = CMenu::FromHandle(hNewDirMenu);
2859 CWnd *pFrame = CWnd::FromHandle(::GetWindow(pMainFrame->m_hWndMDIClient, GW_CHILD));
2860 while (pFrame != nullptr)
2862 if (pFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame)))
2863 static_cast<CMergeEditFrame *>(pFrame)->SetSharedMenu(hNewMergeMenu);
2864 if (pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame)))
2865 static_cast<CHexMergeFrame *>(pFrame)->SetSharedMenu(hNewMergeMenu);
2866 if (pFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame)))
2867 static_cast<CImgMergeFrame *>(pFrame)->SetSharedMenu(hNewImgMergeMenu);
2868 else if (pFrame->IsKindOf(RUNTIME_CLASS(COpenFrame)))
2869 static_cast<COpenFrame *>(pFrame)->SetSharedMenu(hNewDefaultMenu);
2870 else if (pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
2871 static_cast<CDirFrame *>(pFrame)->SetSharedMenu(hNewDirMenu);
2872 pFrame = pFrame->GetNextWindow();
2875 CFrameWnd *pActiveFrame = pMainFrame->GetActiveFrame();
2876 if (pActiveFrame != nullptr)
2878 if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame)))
2879 pMainFrame->MDISetMenu(pNewMergeMenu, nullptr);
2880 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame)))
2881 pMainFrame->MDISetMenu(pNewMergeMenu, nullptr);
2882 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame)))
2883 pMainFrame->MDISetMenu(pNewImgMergeMenu, nullptr);
2884 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
2885 pMainFrame->MDISetMenu(pNewDirMenu, nullptr);
2887 pMainFrame->MDISetMenu(pNewDefaultMenu, nullptr);
2890 pMainFrame->MDISetMenu(pNewDefaultMenu, nullptr);
2892 // Don't delete the old menu
2893 // There is a bug in BCMenu or in Windows98 : the new menu does not
2894 // appear correctly if we destroy the old one
2895 // if (pOldDefaultMenu != nullptr)
2896 // pOldDefaultMenu->DestroyMenu();
2897 // if (pOldMergeMenu != nullptr)
2898 // pOldMergeMenu->DestroyMenu();
2899 // if (pOldDirMenu = nullptr)
2900 // pOldDirMenu->DestroyMenu();
2902 // m_hMenuDefault is used to redraw the main menu when we close a child frame
2903 // if this child frame had a different menu
2904 pMainFrame->m_hMenuDefault = hNewDefaultMenu;
2905 pApp->m_pOpenTemplate->m_hMenuShared = hNewDefaultMenu;
2906 pApp->m_pDiffTemplate->m_hMenuShared = hNewMergeMenu;
2907 pApp->m_pDirTemplate->m_hMenuShared = hNewDirMenu;
2909 // force redrawing the menu bar
2910 pMainFrame->DrawMenuBar();
2914 void CMainFrame::AppendPluginMenus(CMenu *pMenu, const String& filteredFilenames,
2915 const std::vector<std::wstring>& events, bool addAllMenu, unsigned baseId)
2917 if (!GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
2920 auto [suggestedPlugins, allPlugins] = FileTransform::CreatePluginMenuInfos(filteredFilenames, events, baseId);
2924 pMenu->AppendMenu(MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2927 for (const auto& [caption, name, id, plugin] : suggestedPlugins)
2928 pMenu->AppendMenu(MF_STRING, id, caption.c_str());
2930 CMenu* pMenu2 = pMenu;
2934 popupAll.CreatePopupMenu();
2935 pMenu->AppendMenu(MF_POPUP, reinterpret_cast<UINT_PTR>(popupAll.m_hMenu), _("Al&l").c_str());
2940 pMenu->AppendMenu(MF_SEPARATOR, 0);
2941 pMenu->AppendMenu(MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2944 std::vector<String> processTypes;
2945 for (const auto& [processType, pluginList] : allPlugins)
2946 processTypes.push_back(processType);
2947 auto it = std::find(processTypes.begin(), processTypes.end(), _("&Others"));
2948 if (it != processTypes.end())
2950 processTypes.erase(it);
2951 processTypes.push_back(_("&Others"));
2954 for (const auto& processType: processTypes)
2957 popup.CreatePopupMenu();
2958 if (processType.empty())
2960 for (const auto& [caption, name, id, plugin] : allPlugins[processType])
2961 pMenu2->AppendMenu(MF_STRING, id, caption.c_str());
2965 for (const auto& [caption, name, id, plugin] : allPlugins[processType])
2966 popup.AppendMenu(MF_STRING, id, caption.c_str());
2967 pMenu2->AppendMenu(MF_POPUP, reinterpret_cast<UINT_PTR>(popup.m_hMenu), processType.c_str());
2974 if (baseId == ID_UNPACKERS_FIRST)
2975 pMenu2->AppendMenu(MF_STRING, ID_OPEN_WITH_UNPACKER, _("&Select...").c_str());
2976 else if (baseId == ID_PREDIFFERS_FIRST)
2977 pMenu2->AppendMenu(MF_STRING, ID_APPLY_PREDIFFER, _("&Select...").c_str());
2982 String CMainFrame::GetPluginPipelineByMenuId(unsigned idSearch, const std::vector<std::wstring>& events, unsigned baseId)
2984 PluginInfo* pluginFound = nullptr;
2986 auto [suggestedPlugins, allPlugins] = FileTransform::CreatePluginMenuInfos(_T(""), events, baseId);
2987 for (const auto& [processType, pluginList] : allPlugins)
2989 for (const auto& [caption, name, id, plugin] : pluginList)
2994 pluginFound = plugin;
3001 if (!pluginFound->GetExtendedPropertyValue(_T("ArgumentsRequired")).has_value() &&
3002 !pluginFound->GetExtendedPropertyValue(pluginName + _T(".ArgumentsRequired")).has_value())
3004 CSelectPluginDlg dlg(pluginName, _T(""),
3005 (baseId == ID_UNPACKERS_FIRST) ? CSelectPluginDlg::PluginType::Unpacker : (
3006 (baseId == ID_PREDIFFERS_FIRST) ? CSelectPluginDlg::PluginType::Prediffer :
3007 CSelectPluginDlg::PluginType::EditorScript), true);
3008 if (dlg.DoModal() != IDOK)
3010 return dlg.GetPluginPipeline();
3015 IMergeDoc* CMainFrame::GetActiveIMergeDoc()
3017 CFrameWnd* pFrame = GetActiveFrame();
3020 IMergeDoc* pMergeDoc = dynamic_cast<IMergeDoc*>(pFrame->GetActiveDocument());
3022 pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
3026 void CMainFrame::UpdateDocTitle()
3028 CDocManager* pDocManager = AfxGetApp()->m_pDocManager;
3029 POSITION posTemplate = pDocManager->GetFirstDocTemplatePosition();
3030 ASSERT(posTemplate != nullptr);
3032 while (posTemplate != nullptr)
3034 CDocTemplate* pTemplate = pDocManager->GetNextDocTemplate(posTemplate);
3036 ASSERT(pTemplate != nullptr);
3038 for (auto pDoc : GetDocList(static_cast<CMultiDocTemplate *>(pTemplate)))
3040 static_cast<CDocument *>(const_cast<void *>(pDoc))->SetTitle(nullptr);
3041 ((CFrameWnd*)AfxGetApp()->m_pMainWnd)->OnUpdateFrameTitle(TRUE);
3046 void CMainFrame::OnAccelQuit()
3048 // TODO: Add your command handler code here
3050 SendMessage(WM_CLOSE);
3053 LRESULT CMainFrame::OnChildFrameAdded(WPARAM wParam, LPARAM lParam)
3055 for (int i = 0; i < m_arrChild.GetSize(); ++i)
3057 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3063 m_arrChild.InsertAt(0, (CMDIChildWnd*)lParam);
3068 LRESULT CMainFrame::OnChildFrameRemoved(WPARAM wParam, LPARAM lParam)
3070 for (int i = 0; i < m_arrChild.GetSize(); ++i)
3072 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3074 m_arrChild.RemoveAt(i);
3082 LRESULT CMainFrame::OnChildFrameActivate(WPARAM wParam, LPARAM lParam)
3084 for (int i = 0; i < m_arrChild.GetSize(); ++i)
3086 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3088 CMDIChildWnd* pMDIChild = m_arrChild.GetAt(i);
3089 if (pMDIChild->IsIconic())
3090 pMDIChild->ShowWindow(SW_RESTORE);
3091 MDIActivate(pMDIChild);
3098 // put lParam as index 0 in m_arrChild
3099 LRESULT CMainFrame::OnChildFrameActivated(WPARAM wParam, LPARAM lParam)
3101 for (int i = 0; i < m_arrChild.GetSize(); ++i)
3103 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3105 m_arrChild.RemoveAt(i);
3110 m_arrChild.InsertAt(0, (CMDIChildWnd*)lParam);