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 "WebPageDiffFrm.h"
36 #include "LineFiltersList.h"
37 #include "SubstitutionFiltersList.h"
38 #include "ConflictFileParser.h"
39 #include "LineFiltersDlg.h"
40 #include "SubstitutionFiltersDlg.h"
42 #include "Environment.h"
43 #include "PatchTool.h"
45 #include "ConfigLog.h"
47 #include "Merge7zFormatMergePluginImpl.h"
48 #include "FileFiltersDlg.h"
49 #include "OptionsMgr.h"
50 #include "OptionsDef.h"
51 #include "codepage_detect.h"
53 #include "PreferencesDlg.h"
54 #include "FileOrFolderSelect.h"
55 #include "PluginsListDlg.h"
56 #include "SelectPluginDlg.h"
57 #include "stringdiffs.h"
58 #include "MergeCmdLineInfo.h"
59 #include "OptionsFont.h"
61 #include "DropHandler.h"
62 #include "LanguageSelect.h"
63 #include "VersionInfo.h"
65 #include "CCrystalTextMarkers.h"
66 #include "utils/hqbitmap.h"
70 #include "WindowsManagerDialog.h"
71 #include "ClipboardHistory.h"
73 #include "DirWatcher.h"
83 static void LoadToolbarImageList(int orgImageWidth, int newImageHeight, UINT nIDResource, bool bGrayscale, CImageList& ImgList);
84 static CPtrList &GetDocList(CMultiDocTemplate *pTemplate);
85 template<class DocClass>
86 DocClass * GetMergeDocForDiff(CMultiDocTemplate *pTemplate, CDirDoc *pDirDoc, int nFiles, bool bMakeVisible = true);
89 * @brief A table associating menuitem id, icon and menus to apply.
91 const CMainFrame::MENUITEM_ICON CMainFrame::m_MenuIcons[] = {
92 { ID_FILE_OPENCONFLICT, IDB_FILE_OPENCONFLICT, CMainFrame::MENU_ALL },
93 { ID_FILE_NEW_TABLE, IDB_FILE_NEW_TABLE, CMainFrame::MENU_ALL },
94 { ID_FILE_NEW_HEX, IDB_FILE_NEW_HEX, CMainFrame::MENU_ALL },
95 { ID_FILE_NEW_IMAGE, IDB_FILE_NEW_IMAGE, CMainFrame::MENU_ALL },
96 { ID_FILE_NEW_WEBPAGE, IDB_FILE_NEW_WEBPAGE, CMainFrame::MENU_ALL },
97 { ID_FILE_NEW3, IDB_FILE_NEW3, CMainFrame::MENU_ALL },
98 { ID_FILE_NEW3_TABLE, IDB_FILE_NEW3_TABLE, CMainFrame::MENU_ALL },
99 { ID_FILE_NEW3_HEX, IDB_FILE_NEW3_HEX, CMainFrame::MENU_ALL },
100 { ID_FILE_NEW3_IMAGE, IDB_FILE_NEW3_IMAGE, CMainFrame::MENU_ALL },
101 { ID_FILE_NEW3_WEBPAGE, IDB_FILE_NEW3_WEBPAGE, CMainFrame::MENU_ALL },
102 { ID_EDIT_COPY, IDB_EDIT_COPY, CMainFrame::MENU_ALL },
103 { ID_EDIT_CUT, IDB_EDIT_CUT, CMainFrame::MENU_ALL },
104 { ID_EDIT_PASTE, IDB_EDIT_PASTE, CMainFrame::MENU_ALL },
105 { ID_EDIT_FIND, IDB_EDIT_SEARCH, CMainFrame::MENU_ALL },
106 { ID_WINDOW_CASCADE, IDB_WINDOW_CASCADE, CMainFrame::MENU_ALL },
107 { ID_WINDOW_TILE_HORZ, IDB_WINDOW_HORIZONTAL, CMainFrame::MENU_ALL },
108 { ID_WINDOW_TILE_VERT, IDB_WINDOW_VERTICAL, CMainFrame::MENU_ALL },
109 { ID_FILE_CLOSE, IDB_WINDOW_CLOSE, CMainFrame::MENU_ALL },
110 { ID_NEXT_PANE, IDB_WINDOW_CHANGEPANE, CMainFrame::MENU_ALL },
111 { ID_EDIT_WMGOTO, IDB_EDIT_GOTO, CMainFrame::MENU_ALL },
112 { ID_EDIT_REPLACE, IDB_EDIT_REPLACE, CMainFrame::MENU_ALL },
113 { ID_VIEW_SELECTFONT, IDB_VIEW_SELECTFONT, CMainFrame::MENU_ALL },
114 { ID_APP_EXIT, IDB_FILE_EXIT, CMainFrame::MENU_ALL },
115 { ID_HELP_CONTENTS, IDB_HELP_CONTENTS, CMainFrame::MENU_ALL },
116 { ID_EDIT_SELECT_ALL, IDB_EDIT_SELECTALL, CMainFrame::MENU_ALL },
117 { ID_TOOLS_FILTERS, IDB_TOOLS_FILTERS, CMainFrame::MENU_ALL },
118 { ID_TOOLS_CUSTOMIZECOLUMNS, IDB_TOOLS_COLUMNS, CMainFrame::MENU_ALL },
119 { ID_TOOLS_GENERATEPATCH, IDB_TOOLS_GENERATEPATCH, CMainFrame::MENU_ALL },
120 { ID_PLUGINS_LIST, IDB_PLUGINS_LIST, CMainFrame::MENU_ALL },
121 { ID_FILE_PRINT, IDB_FILE_PRINT, CMainFrame::MENU_FILECMP },
122 { ID_TOOLS_GENERATEREPORT, IDB_TOOLS_GENERATEREPORT, CMainFrame::MENU_FILECMP },
123 { ID_EDIT_TOGGLE_BOOKMARK, IDB_EDIT_TOGGLE_BOOKMARK, CMainFrame::MENU_FILECMP },
124 { ID_EDIT_GOTO_NEXT_BOOKMARK, IDB_EDIT_GOTO_NEXT_BOOKMARK, CMainFrame::MENU_FILECMP },
125 { ID_EDIT_GOTO_PREV_BOOKMARK, IDB_EDIT_GOTO_PREV_BOOKMARK, CMainFrame::MENU_FILECMP },
126 { ID_EDIT_CLEAR_ALL_BOOKMARKS, IDB_EDIT_CLEAR_ALL_BOOKMARKS, CMainFrame::MENU_FILECMP },
127 { ID_VIEW_ZOOMIN, IDB_VIEW_ZOOMIN, CMainFrame::MENU_FILECMP },
128 { ID_VIEW_ZOOMOUT, IDB_VIEW_ZOOMOUT, CMainFrame::MENU_FILECMP },
129 { ID_COPY_TO_LEFT_M, IDB_COPY_MIDDLE_TO_LEFT, CMainFrame::MENU_FILECMP },
130 { ID_COPY_TO_LEFT_R, IDB_COPY_RIGHT_TO_LEFT, CMainFrame::MENU_FILECMP },
131 { ID_COPY_TO_MIDDLE_L, IDB_COPY_LEFT_TO_MIDDLE, CMainFrame::MENU_FILECMP },
132 { ID_COPY_TO_MIDDLE_R, IDB_COPY_RIGHT_TO_MIDDLE, CMainFrame::MENU_FILECMP },
133 { ID_COPY_TO_RIGHT_L, IDB_COPY_LEFT_TO_RIGHT, CMainFrame::MENU_FILECMP },
134 { ID_COPY_TO_RIGHT_M, IDB_COPY_MIDDLE_TO_RIGHT, CMainFrame::MENU_FILECMP },
135 { ID_COPY_FROM_LEFT_R, IDB_COPY_LEFT_TO_RIGHT, CMainFrame::MENU_FILECMP },
136 { ID_COPY_FROM_LEFT_M, IDB_COPY_LEFT_TO_MIDDLE, CMainFrame::MENU_FILECMP },
137 { ID_COPY_FROM_MIDDLE_L, IDB_COPY_MIDDLE_TO_LEFT, CMainFrame::MENU_FILECMP },
138 { ID_COPY_FROM_MIDDLE_R, IDB_COPY_MIDDLE_TO_RIGHT, CMainFrame::MENU_FILECMP },
139 { ID_COPY_FROM_RIGHT_L, IDB_COPY_RIGHT_TO_LEFT, CMainFrame::MENU_FILECMP },
140 { ID_COPY_FROM_RIGHT_M, IDB_COPY_RIGHT_TO_MIDDLE, CMainFrame::MENU_FILECMP },
141 { ID_COPY_FROM_LEFT, IDB_COPY_FROM_LEFT, CMainFrame::MENU_FILECMP },
142 { ID_COPY_FROM_RIGHT, IDB_COPY_FROM_RIGHT, CMainFrame::MENU_FILECMP },
143 { ID_LINES_R2L, IDB_COPY_SELECTED_LINES_RIGHT_TO_LEFT, CMainFrame::MENU_FILECMP },
144 { ID_LINES_L2R, IDB_COPY_SELECTED_LINES_LEFT_TO_RIGHT, CMainFrame::MENU_FILECMP },
145 { ID_COPY_LINES_FROM_LEFT, IDB_COPY_SELECTED_LINES_FROM_LEFT, CMainFrame::MENU_FILECMP },
146 { ID_COPY_LINES_FROM_RIGHT, IDB_COPY_SELECTED_LINES_FROM_RIGHT, CMainFrame::MENU_FILECMP },
147 { ID_COPY_LINES_TO_LEFT_M, IDB_COPY_SELECTED_LINES_MIDDLE_TO_LEFT, CMainFrame::MENU_FILECMP },
148 { ID_COPY_LINES_TO_LEFT_R, IDB_COPY_SELECTED_LINES_RIGHT_TO_LEFT, CMainFrame::MENU_FILECMP },
149 { ID_COPY_LINES_TO_MIDDLE_L, IDB_COPY_SELECTED_LINES_LEFT_TO_MIDDLE, CMainFrame::MENU_FILECMP },
150 { ID_COPY_LINES_TO_MIDDLE_R, IDB_COPY_SELECTED_LINES_RIGHT_TO_MIDDLE,CMainFrame::MENU_FILECMP },
151 { ID_COPY_LINES_TO_RIGHT_L, IDB_COPY_SELECTED_LINES_LEFT_TO_RIGHT, CMainFrame::MENU_FILECMP },
152 { ID_COPY_LINES_TO_RIGHT_M, IDB_COPY_SELECTED_LINES_MIDDLE_TO_RIGHT,CMainFrame::MENU_FILECMP },
153 { ID_COPY_LINES_FROM_LEFT_R, IDB_COPY_SELECTED_LINES_LEFT_TO_RIGHT, CMainFrame::MENU_FILECMP },
154 { ID_COPY_LINES_FROM_LEFT_M, IDB_COPY_SELECTED_LINES_LEFT_TO_MIDDLE, CMainFrame::MENU_FILECMP },
155 { ID_COPY_LINES_FROM_MIDDLE_L, IDB_COPY_SELECTED_LINES_MIDDLE_TO_LEFT, CMainFrame::MENU_FILECMP },
156 { ID_COPY_LINES_FROM_MIDDLE_R, IDB_COPY_SELECTED_LINES_MIDDLE_TO_RIGHT,CMainFrame::MENU_FILECMP },
157 { ID_COPY_LINES_FROM_RIGHT_L, IDB_COPY_SELECTED_LINES_RIGHT_TO_LEFT, CMainFrame::MENU_FILECMP },
158 { ID_COPY_LINES_FROM_RIGHT_M, IDB_COPY_SELECTED_LINES_RIGHT_TO_MIDDLE,CMainFrame::MENU_FILECMP },
159 { ID_MERGE_COMPARE, IDB_MERGE_COMPARE, CMainFrame::MENU_FOLDERCMP },
160 { ID_MERGE_COMPARE_LEFT1_LEFT2, IDB_MERGE_COMPARE_LEFT1_LEFT2, CMainFrame::MENU_FOLDERCMP },
161 { ID_MERGE_COMPARE_RIGHT1_RIGHT2, IDB_MERGE_COMPARE_RIGHT1_RIGHT2,CMainFrame::MENU_FOLDERCMP },
162 { ID_MERGE_COMPARE_LEFT1_RIGHT2, IDB_MERGE_COMPARE_LEFT1_RIGHT2, CMainFrame::MENU_FOLDERCMP },
163 { ID_MERGE_COMPARE_LEFT2_RIGHT1, IDB_MERGE_COMPARE_LEFT2_RIGHT1, CMainFrame::MENU_FOLDERCMP },
164 { ID_MERGE_DELETE, IDB_MERGE_DELETE, CMainFrame::MENU_FOLDERCMP },
165 { ID_TOOLS_GENERATEREPORT, IDB_TOOLS_GENERATEREPORT, CMainFrame::MENU_FOLDERCMP },
166 { ID_DIR_COPY_LEFT_TO_RIGHT, IDB_COPY_LEFT_TO_RIGHT, CMainFrame::MENU_FOLDERCMP },
167 { ID_DIR_COPY_LEFT_TO_MIDDLE, IDB_COPY_LEFT_TO_MIDDLE, CMainFrame::MENU_FOLDERCMP },
168 { ID_DIR_COPY_RIGHT_TO_LEFT, IDB_COPY_RIGHT_TO_LEFT, CMainFrame::MENU_FOLDERCMP },
169 { ID_DIR_COPY_RIGHT_TO_MIDDLE, IDB_COPY_RIGHT_TO_MIDDLE, CMainFrame::MENU_FOLDERCMP },
170 { ID_DIR_COPY_MIDDLE_TO_LEFT, IDB_COPY_MIDDLE_TO_LEFT, CMainFrame::MENU_FOLDERCMP },
171 { ID_DIR_COPY_MIDDLE_TO_RIGHT, IDB_COPY_MIDDLE_TO_RIGHT, CMainFrame::MENU_FOLDERCMP },
172 { ID_DIR_COPY_LEFT_TO_BROWSE, IDB_COPY_LEFT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
173 { ID_DIR_COPY_MIDDLE_TO_BROWSE, IDB_COPY_MIDDLE_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
174 { ID_DIR_COPY_RIGHT_TO_BROWSE, IDB_COPY_RIGHT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
175 { ID_DIR_MOVE_LEFT_TO_RIGHT, IDB_MOVE_LEFT_TO_RIGHT, CMainFrame::MENU_FOLDERCMP },
176 { ID_DIR_MOVE_LEFT_TO_MIDDLE, IDB_MOVE_LEFT_TO_MIDDLE, CMainFrame::MENU_FOLDERCMP },
177 { ID_DIR_MOVE_RIGHT_TO_LEFT, IDB_MOVE_RIGHT_TO_LEFT, CMainFrame::MENU_FOLDERCMP },
178 { ID_DIR_MOVE_RIGHT_TO_MIDDLE, IDB_MOVE_RIGHT_TO_MIDDLE, CMainFrame::MENU_FOLDERCMP },
179 { ID_DIR_MOVE_MIDDLE_TO_LEFT, IDB_MOVE_MIDDLE_TO_LEFT, CMainFrame::MENU_FOLDERCMP },
180 { ID_DIR_MOVE_MIDDLE_TO_RIGHT, IDB_MOVE_MIDDLE_TO_RIGHT, CMainFrame::MENU_FOLDERCMP },
181 { ID_DIR_MOVE_LEFT_TO_BROWSE, IDB_MOVE_LEFT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
182 { ID_DIR_MOVE_MIDDLE_TO_BROWSE, IDB_MOVE_MIDDLE_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
183 { ID_DIR_MOVE_RIGHT_TO_BROWSE, IDB_MOVE_RIGHT_TO_BROWSE, CMainFrame::MENU_FOLDERCMP },
184 { ID_DIR_DEL_LEFT, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
185 { ID_DIR_DEL_MIDDLE, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
186 { ID_DIR_DEL_RIGHT, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
187 { ID_DIR_DEL_BOTH, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
188 { ID_DIR_DEL_ALL, IDB_ALL, CMainFrame::MENU_FOLDERCMP },
189 { ID_DIR_COPY_PATHNAMES_LEFT, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
190 { ID_DIR_COPY_PATHNAMES_MIDDLE, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
191 { ID_DIR_COPY_PATHNAMES_RIGHT, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
192 { ID_DIR_COPY_PATHNAMES_BOTH, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
193 { ID_DIR_COPY_PATHNAMES_ALL, IDB_ALL, CMainFrame::MENU_FOLDERCMP },
194 { ID_DIR_COPY_LEFT_TO_CLIPBOARD, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
195 { ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
196 { ID_DIR_COPY_RIGHT_TO_CLIPBOARD, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
197 { ID_DIR_COPY_BOTH_TO_CLIPBOARD, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
198 { ID_DIR_COPY_ALL_TO_CLIPBOARD, IDB_ALL, CMainFrame::MENU_FOLDERCMP },
199 { ID_DIR_ZIP_LEFT, IDB_LEFT, CMainFrame::MENU_FOLDERCMP },
200 { ID_DIR_ZIP_MIDDLE, IDB_MIDDLE, CMainFrame::MENU_FOLDERCMP },
201 { ID_DIR_ZIP_RIGHT, IDB_RIGHT, CMainFrame::MENU_FOLDERCMP },
202 { ID_DIR_ZIP_BOTH, IDB_BOTH, CMainFrame::MENU_FOLDERCMP },
203 { ID_DIR_ZIP_ALL, IDB_ALL, CMainFrame::MENU_FOLDERCMP }
207 /////////////////////////////////////////////////////////////////////////////
210 IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd)
212 BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
213 //{{AFX_MSG_MAP(CMainFrame)
216 ON_WM_INITMENUPOPUP()
222 ON_MESSAGE(WM_COPYDATA, OnCopyData)
223 ON_MESSAGE(WM_USER+1, OnUser1)
226 ON_COMMAND(ID_FILE_NEW, (OnFileNew<2, ID_MERGE_COMPARE_TEXT>))
227 ON_COMMAND(ID_FILE_NEW_TABLE, (OnFileNew<2, ID_MERGE_COMPARE_TABLE>))
228 ON_COMMAND(ID_FILE_NEW_HEX, (OnFileNew<2, ID_MERGE_COMPARE_HEX>))
229 ON_COMMAND(ID_FILE_NEW_IMAGE, (OnFileNew<2, ID_MERGE_COMPARE_IMAGE>))
230 ON_COMMAND(ID_FILE_NEW_WEBPAGE, (OnFileNew<2, ID_MERGE_COMPARE_WEBPAGE>))
231 ON_COMMAND(ID_FILE_NEW3, (OnFileNew<3, ID_MERGE_COMPARE_TEXT>))
232 ON_COMMAND(ID_FILE_NEW3_TABLE, (OnFileNew<3, ID_MERGE_COMPARE_TABLE>))
233 ON_COMMAND(ID_FILE_NEW3_HEX, (OnFileNew<3, ID_MERGE_COMPARE_HEX>))
234 ON_COMMAND(ID_FILE_NEW3_IMAGE, (OnFileNew<3, ID_MERGE_COMPARE_IMAGE>))
235 ON_COMMAND(ID_FILE_NEW3_WEBPAGE, (OnFileNew<3, ID_MERGE_COMPARE_WEBPAGE>))
236 ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
237 ON_COMMAND(ID_FILE_OPENPROJECT, OnFileOpenProject)
238 ON_COMMAND(ID_FILE_SAVEPROJECT, OnSaveProject)
239 ON_COMMAND(ID_FILE_OPENCONFLICT, OnFileOpenConflict)
240 ON_COMMAND(ID_FILE_OPENCLIPBOARD, OnFileOpenClipboard)
241 ON_COMMAND(ID_EDIT_PASTE, OnFileOpenClipboard)
242 ON_COMMAND_RANGE(ID_MRU_FIRST, ID_MRU_LAST, OnMRUs)
243 ON_UPDATE_COMMAND_UI(ID_MRU_FIRST, OnUpdateNoMRUs)
244 ON_UPDATE_COMMAND_UI(ID_NO_MRU, OnUpdateNoMRUs)
245 ON_COMMAND(ID_ACCEL_QUIT, &CMainFrame::OnAccelQuit)
247 ON_COMMAND(ID_OPTIONS, OnOptions)
249 ON_COMMAND(ID_VIEW_SELECTFONT, OnViewSelectfont)
250 ON_COMMAND(ID_VIEW_USEDEFAULTFONT, OnViewUsedefaultfont)
251 ON_COMMAND(ID_VIEW_STATUS_BAR, OnViewStatusBar)
252 ON_COMMAND(ID_VIEW_TAB_BAR, OnViewTabBar)
253 ON_UPDATE_COMMAND_UI(ID_VIEW_TAB_BAR, OnUpdateViewTabBar)
254 ON_COMMAND(ID_VIEW_RESIZE_PANES, OnResizePanes)
255 ON_UPDATE_COMMAND_UI(ID_VIEW_RESIZE_PANES, OnUpdateResizePanes)
256 ON_COMMAND_RANGE(ID_TOOLBAR_NONE, ID_TOOLBAR_HUGE, OnToolbarSize)
257 ON_UPDATE_COMMAND_UI_RANGE(ID_TOOLBAR_NONE, ID_TOOLBAR_HUGE, OnUpdateToolbarSize)
259 ON_COMMAND(ID_PLUGINS_LIST, OnPluginsList)
260 ON_COMMAND_RANGE(ID_UNPACK_MANUAL, ID_UNPACK_AUTO, OnPluginUnpackMode)
261 ON_UPDATE_COMMAND_UI_RANGE(ID_UNPACK_MANUAL, ID_UNPACK_AUTO, OnUpdatePluginUnpackMode)
262 ON_COMMAND_RANGE(ID_PREDIFFER_MANUAL, ID_PREDIFFER_AUTO, OnPluginPrediffMode)
263 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFER_MANUAL, ID_PREDIFFER_AUTO, OnUpdatePluginPrediffMode)
264 ON_UPDATE_COMMAND_UI(ID_OPEN_WITH_UNPACKER, OnUpdatePluginRelatedMenu)
265 ON_UPDATE_COMMAND_UI(ID_APPLY_PREDIFFER, OnUpdatePluginRelatedMenu)
266 ON_UPDATE_COMMAND_UI(ID_TRANSFORM_WITH_SCRIPT, OnUpdatePluginRelatedMenu)
267 ON_UPDATE_COMMAND_UI(ID_RELOAD_PLUGINS, OnUpdatePluginRelatedMenu)
268 ON_COMMAND(ID_RELOAD_PLUGINS, OnReloadPlugins)
270 ON_COMMAND(ID_TOOLS_FILTERS, OnToolsFilters)
271 ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
273 ON_COMMAND(ID_WINDOW_CLOSEALL, OnWindowCloseAll)
274 ON_UPDATE_COMMAND_UI(ID_WINDOW_CLOSEALL, OnUpdateWindowCloseAll)
276 ON_COMMAND(ID_HELP_CONTENTS, OnHelpContents)
277 ON_COMMAND(ID_HELP_GNULICENSE, OnHelpGnulicense)
278 ON_COMMAND(ID_HELP_GETCONFIG, OnSaveConfigData)
279 ON_COMMAND(ID_HELP_RELEASENOTES, OnHelpReleasenotes)
280 ON_COMMAND(ID_HELP_TRANSLATIONS, OnHelpTranslations)
281 ON_COMMAND(ID_HELP_CHECKFORUPDATES, OnHelpCheckForUpdates)
282 ON_UPDATE_COMMAND_UI(ID_HELP_CHECKFORUPDATES, OnUpdateHelpCheckForUpdates)
284 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
285 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
286 ON_COMMAND(ID_FIRSTFILE, OnFirstFile)
287 ON_UPDATE_COMMAND_UI(ID_FIRSTFILE, OnUpdateFirstFile)
288 ON_COMMAND(ID_PREVFILE, OnPrevFile)
289 ON_UPDATE_COMMAND_UI(ID_PREVFILE, OnUpdatePrevFile)
290 ON_COMMAND(ID_NEXTFILE, OnNextFile)
291 ON_UPDATE_COMMAND_UI(ID_NEXTFILE, OnUpdateNextFile)
292 ON_COMMAND(ID_LASTFILE, OnLastFile)
293 ON_UPDATE_COMMAND_UI(ID_LASTFILE, OnUpdateLastFile)
294 // Tool bar drop-down menu
295 ON_NOTIFY(TBN_DROPDOWN, AFX_IDW_TOOLBAR, OnToolbarButtonDropDown)
296 ON_COMMAND_RANGE(ID_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnDiffWhitespace)
297 ON_UPDATE_COMMAND_UI_RANGE(ID_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
298 ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_BLANKLINES, OnDiffIgnoreBlankLines)
299 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_BLANKLINES, OnUpdateDiffIgnoreBlankLines)
300 ON_COMMAND(IDC_DIFF_IGNORENUMBERS, OnDiffIgnoreNumbers)
301 ON_UPDATE_COMMAND_UI(IDC_DIFF_IGNORENUMBERS, OnUpdateDiffIgnoreNumbers)
302 ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_CASE, OnDiffIgnoreCase)
303 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_CASE, OnUpdateDiffIgnoreCase)
304 ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_EOL, OnDiffIgnoreEOL)
305 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_EOL, OnUpdateDiffIgnoreEOL)
306 ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_CODEPAGE, OnDiffIgnoreCP)
307 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_CODEPAGE, OnUpdateDiffIgnoreCP)
308 ON_COMMAND(ID_DIFF_OPTIONS_IGNORE_COMMENTS, OnDiffIgnoreComments)
309 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_IGNORE_COMMENTS, OnUpdateDiffIgnoreComments)
310 ON_COMMAND(ID_DIFF_OPTIONS_INCLUDE_SUBFOLDERS, OnIncludeSubfolders)
311 ON_UPDATE_COMMAND_UI(ID_DIFF_OPTIONS_INCLUDE_SUBFOLDERS, OnUpdateIncludeSubfolders)
312 ON_COMMAND_RANGE(ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_DIFF_OPTIONS_COMPMETHOD_SIZE, OnCompareMethod)
313 ON_UPDATE_COMMAND_UI_RANGE(ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_DIFF_OPTIONS_COMPMETHOD_SIZE, OnUpdateCompareMethod)
315 ON_UPDATE_COMMAND_UI(ID_STATUS_PLUGIN, OnUpdatePluginName)
316 ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
318 ON_MESSAGE(WMU_CHILDFRAMEADDED, &CMainFrame::OnChildFrameAdded)
319 ON_MESSAGE(WMU_CHILDFRAMEREMOVED, &CMainFrame::OnChildFrameRemoved)
320 ON_MESSAGE(WMU_CHILDFRAMEACTIVATE, &CMainFrame::OnChildFrameActivate)
321 ON_MESSAGE(WMU_CHILDFRAMEACTIVATED, &CMainFrame::OnChildFrameActivated)
326 * @brief MainFrame statusbar panels/indicators
328 static UINT StatusbarIndicators[] =
330 ID_SEPARATOR, // Plugin name
331 ID_SEPARATOR, // status line indicator
332 ID_SEPARATOR, // Merge mode
333 ID_SEPARATOR, // Diff number
334 ID_INDICATOR_CAPS, // Caps Lock
335 ID_INDICATOR_NUM, // Num Lock
336 ID_INDICATOR_OVR, // Insert
340 * @brief Return a const reference to a CMultiDocTemplate's list of documents.
342 static CPtrList &GetDocList(CMultiDocTemplate *pTemplate)
344 struct Template : public CMultiDocTemplate
347 using CMultiDocTemplate::m_docList;
349 return static_cast<struct Template *>(pTemplate)->m_docList;
352 /////////////////////////////////////////////////////////////////////////////
353 // CMainFrame construction/destruction
356 * @brief MainFrame constructor. Loads settings from registry.
357 * @todo Preference for logging?
359 CMainFrame::CMainFrame()
361 , m_pDropHandler(nullptr)
362 , m_bShowErrors(false)
363 , m_lfDiff(Options::Font::Load(GetOptionsMgr(), OPT_FONT_FILECMP))
364 , m_lfDir(Options::Font::Load(GetOptionsMgr(), OPT_FONT_DIRCMP))
365 , m_pDirWatcher(new DirWatcher())
369 CMainFrame::~CMainFrame()
371 GetOptionsMgr()->SaveOption(OPT_TABBAR_AUTO_MAXWIDTH, m_wndTabBar.GetAutoMaxWidth());
374 m_arrChild.RemoveAll();
377 const tchar_t CMainFrame::szClassName[] = _T("WinMergeWindowClassW");
380 * @brief Change MainFrame window class name
381 * see http://support.microsoft.com/kb/403825/ja
383 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
386 BOOL bRes = __super::PreCreateWindow(cs);
387 HINSTANCE hInst = AfxGetInstanceHandle();
388 // see if the class already exists
389 if (!::GetClassInfo(hInst, szClassName, &wndcls))
392 ::GetClassInfo(hInst, cs.lpszClass, &wndcls);
393 // register a new class
394 wndcls.lpszClassName = szClassName;
395 wndcls.hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(IDR_MAINFRAME));
396 ::RegisterClass(&wndcls);
398 cs.lpszClass = szClassName;
402 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
404 if (__super::OnCreate(lpCreateStruct) == -1)
407 m_wndMDIClient.SubclassWindow(m_hWndMDIClient);
409 if (!CreateToolbar())
411 TRACE0("Failed to create toolbar\n");
412 return -1; // fail to create
415 if (!m_wndTabBar.Create(this))
417 TRACE0("Failed to create tab bar\n");
418 return -1; // fail to create
420 m_wndTabBar.SetAutoMaxWidth(GetOptionsMgr()->GetBool(OPT_TABBAR_AUTO_MAXWIDTH));
422 if (!GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR))
423 __super::ShowControlBar(&m_wndTabBar, false, 0);
425 if (!m_wndStatusBar.Create(this))
427 TRACE0("Failed to create status bar\n");
428 return -1; // fail to create
430 theApp.SetIndicators(m_wndStatusBar, StatusbarIndicators,
431 static_cast<int>(std::size(StatusbarIndicators)));
433 const int lpx = CClientDC(this).GetDeviceCaps(LOGPIXELSX);
434 auto pointToPixel = [lpx](int point) { return MulDiv(point, lpx, 72); };
435 m_wndStatusBar.SetPaneInfo(0, 0, SBPS_STRETCH | SBPS_NOBORDERS, 0);
436 m_wndStatusBar.SetPaneInfo(1, ID_STATUS_PLUGIN, 0, pointToPixel(225));
437 m_wndStatusBar.SetPaneInfo(2, ID_STATUS_MERGINGMODE, 0, pointToPixel(75));
438 m_wndStatusBar.SetPaneInfo(3, ID_STATUS_DIFFNUM, 0, pointToPixel(112));
440 if (!GetOptionsMgr()->GetBool(OPT_SHOW_STATUSBAR))
441 __super::ShowControlBar(&m_wndStatusBar, false, 0);
443 theApp.RegisterIdleFunc([this]() {
444 m_pDropHandler = new DropHandler(std::bind(&CMainFrame::OnDropFiles, this, std::placeholders::_1));
445 RegisterDragDrop(m_hWnd, m_pDropHandler);
448 m_wndMDIClient.ModifyStyleEx(WS_EX_CLIENTEDGE, 0);
453 void CMainFrame::OnTimer(UINT_PTR nIDEvent)
455 __super::OnTimer(nIDEvent);
457 if (nIDEvent == IDT_UPDATEMAINMENU)
462 MDIGetActive(&bMaximized);
464 // When MDI maximized the window icon is drawn on the menu bar, so we
465 // need to notify it that our icon has changed.
469 OnUpdateFrameTitle(FALSE);
473 void CMainFrame::OnDestroy(void)
475 if (m_pDropHandler != nullptr)
476 RevokeDragDrop(m_hWnd);
479 static HMENU GetSubmenu(HMENU menu, int nthSubmenu)
481 for (int nth = 0, i = 0; i < ::GetMenuItemCount(menu); i++)
483 if (::GetSubMenu(menu, i) != nullptr)
485 if (nth == nthSubmenu)
486 return ::GetSubMenu(menu, i);
490 // error, submenu not found
494 static HMENU GetSubmenu(HMENU mainMenu, UINT nIDFirstMenuItem, int nthSubmenu)
497 for (i = 0 ; i < ::GetMenuItemCount(mainMenu) ; i++)
498 if (::GetMenuItemID(::GetSubMenu(mainMenu, i), 0) == nIDFirstMenuItem)
500 HMENU menu = ::GetSubMenu(mainMenu, i);
503 return GetSubmenu(menu, nthSubmenu);
507 * @brief Find the scripts submenu from the main menu
508 * As now this is the first submenu in "Plugins" menu
509 * We find the "Plugins" menu by looking for a menu
510 * starting with ID_UNPACK_MANUAL.
512 HMENU CMainFrame::GetPrediffersSubmenu(HMENU mainMenu)
514 return GetSubmenu(mainMenu, ID_PLUGINS_LIST, 1);
518 * @brief Create a new menu for the view..
519 * @param [in] view Menu view either MENU_DEFAULT, MENU_MERGEVIEW or MENU_DIRVIEW.
520 * @param [in] ID Menu's resource ID.
521 * @return Menu for the view.
523 HMENU CMainFrame::NewMenu(int view, int ID)
526 if (m_pMenus[view] == nullptr)
528 m_pMenus[view].reset(new BCMenu());
529 if (m_pMenus[view] == nullptr)
536 case MENU_HEXMERGEVIEW:
537 case MENU_IMGMERGEVIEW:
538 case MENU_WEBPAGEDIFFVIEW:
539 menu_view = MENU_FILECMP;
542 menu_view = MENU_FOLDERCMP;
546 menu_view = MENU_MAINFRM;
550 if (!m_pMenus[view]->LoadMenu(ID))
556 if (view == MENU_IMGMERGEVIEW)
558 m_pImageMenu.reset(new BCMenu);
559 m_pImageMenu->LoadMenu(MAKEINTRESOURCE(IDR_POPUP_IMGMERGEVIEW));
560 m_pMenus[view]->InsertMenu(4, MF_BYPOSITION | MF_POPUP, (UINT_PTR)m_pImageMenu->GetSubMenu(0)->m_hMenu, const_cast<tchar_t *>(LoadResString(IDS_IMAGE_MENU).c_str()));
563 if (view == MENU_WEBPAGEDIFFVIEW)
565 m_pWebPageMenu.reset(new BCMenu);
566 m_pWebPageMenu->LoadMenu(MAKEINTRESOURCE(IDR_POPUP_WEBPAGEDIFFVIEW));
567 m_pMenus[view]->InsertMenu(4, MF_BYPOSITION | MF_POPUP, (UINT_PTR)m_pWebPageMenu->GetSubMenu(0)->m_hMenu, const_cast<tchar_t *>(LoadResString(IDS_WEBPAGE_MENU).c_str()));
570 // Load bitmaps to menuitems
571 for (auto& menu_icon: m_MenuIcons)
573 if (menu_view == (menu_icon.menusToApply & menu_view))
574 m_pMenus[view]->ModifyODMenu(nullptr, menu_icon.menuitemID, menu_icon.iconResID);
577 m_pMenus[view]->LoadToolbar(IDR_MAINFRAME, &m_wndToolBar);
579 theApp.TranslateMenu(m_pMenus[view]->m_hMenu);
581 return (m_pMenus[view]->Detach());
585 * @brief Create new default (CMainFrame) menu.
587 HMENU CMainFrame::NewDefaultMenu(int ID /*=0*/)
591 return NewMenu( MENU_DEFAULT, ID );
595 * @brief Create new File compare (CMergeEditView) menu.
597 HMENU CMainFrame::NewMergeViewMenu()
599 return NewMenu( MENU_MERGEVIEW, IDR_MERGEDOCTYPE);
603 * @brief Create new Dir compare (CDirView) menu
605 HMENU CMainFrame::NewDirViewMenu()
607 return NewMenu(MENU_DIRVIEW, IDR_DIRDOCTYPE );
611 * @brief Create new File compare (CHexMergeView) menu.
613 HMENU CMainFrame::NewHexMergeViewMenu()
615 return NewMenu( MENU_HEXMERGEVIEW, IDR_MERGEDOCTYPE);
619 * @brief Create new Image compare (CImgMergeView) menu.
621 HMENU CMainFrame::NewImgMergeViewMenu()
623 return NewMenu( MENU_IMGMERGEVIEW, IDR_MERGEDOCTYPE);
627 * @brief Create new Webpage compare (CWebPageMergeView) menu.
629 HMENU CMainFrame::NewWebPageDiffViewMenu()
631 return NewMenu( MENU_WEBPAGEDIFFVIEW, IDR_MERGEDOCTYPE);
635 * @brief Create new File compare (COpenView) menu.
637 HMENU CMainFrame::NewOpenViewMenu()
639 return NewMenu( MENU_OPENVIEW, IDR_MAINFRAME);
643 * @brief This handler ensures that the popup menu items are drawn correctly.
645 void CMainFrame::OnMeasureItem(int nIDCtl,
646 LPMEASUREITEMSTRUCT lpMeasureItemStruct)
648 bool setflag = false;
649 if (lpMeasureItemStruct->CtlType == ODT_MENU)
651 if (IsMenu(reinterpret_cast<HMENU>(static_cast<uintptr_t>(lpMeasureItemStruct->itemID))))
654 CMenu::FromHandle(reinterpret_cast<HMENU>(static_cast<uintptr_t>(lpMeasureItemStruct->itemID)));
656 if (m_pMenus[MENU_DEFAULT]->IsMenu(cmenu))
658 m_pMenus[MENU_DEFAULT]->MeasureItem(lpMeasureItemStruct);
665 __super::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
669 * @brief This handler ensures that keyboard shortcuts work.
671 LRESULT CMainFrame::OnMenuChar(UINT nChar, UINT nFlags,
675 if(m_pMenus[MENU_DEFAULT]->IsMenu(pMenu))
676 lresult=BCMenu::FindKeyboardShortcut(nChar, nFlags, pMenu);
678 lresult=__super::OnMenuChar(nChar, nFlags, pMenu);
683 * @brief This handler updates the menus from time to time.
685 void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
689 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
692 for (int i = 0; i < pMergeDoc->GetFileCount(); ++i)
693 paths.SetPath(i, pMergeDoc->GetPath(i));
694 String filteredFilenames = strutils::join(paths.begin(), paths.end(), _T("|"));
695 unsigned topMenuId = pPopupMenu->GetMenuItemID(0);
696 if (topMenuId == ID_NO_PREDIFFER)
698 UpdatePrediffersMenu();
700 else if (topMenuId == ID_MERGE_COMPARE_TEXT)
702 CMenu* pMenu = pPopupMenu;
704 for (int i = pMenu->GetMenuItemCount() - 1; i > (ID_MERGE_COMPARE_WEBPAGE - ID_MERGE_COMPARE_TEXT); --i)
705 pMenu->DeleteMenu(i, MF_BYPOSITION);
707 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
709 else if (topMenuId == ID_NO_EDIT_SCRIPTS)
711 CMenu* pMenu = pPopupMenu;
712 ASSERT(pMenu != nullptr);
715 int i = pMenu->GetMenuItemCount();
717 pMenu->DeleteMenu(0, MF_BYPOSITION);
719 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::EditorScriptEventNames, false, ID_SCRIPT_FIRST);
721 else if (topMenuId == ID_PLUGINS_LIST)
723 for (int j = 0; j < 2; j++)
725 CMenu* pMenu = pPopupMenu->GetSubMenu((j == 0) ? 8 : (pPopupMenu->GetMenuItemCount() - 4));
726 ASSERT(pMenu != nullptr);
729 int i = pMenu->GetMenuItemCount();
731 pMenu->DeleteMenu(0, MF_BYPOSITION);
734 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::UnpackerEventNames, false, ID_UNPACKERS_FIRST);
736 CMainFrame::AppendPluginMenus(pMenu, filteredFilenames, FileTransform::EditorScriptEventNames, false, ID_SCRIPT_FIRST);
741 CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
742 if (BCMenu::IsMenu(pPopupMenu))
744 BCMenu::UpdateMenu(pPopupMenu);
749 CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
753 /////////////////////////////////////////////////////////////////////////////
754 // CMainFrame message handlers
756 void CMainFrame::OnFileOpen()
758 DoFileOrFolderOpen();
762 * @brief Check for BOM, and also, if bGuessEncoding, try to deduce codepage
764 * Unpacks info from FileLocation & delegates all work to codepage_detect module
767 FileLocationGuessEncodings(FileLocation & fileloc, int iGuessEncoding)
769 fileloc.encoding = codepage_detect::Guess(fileloc.filepath, iGuessEncoding);
772 bool CMainFrame::ShowAutoMergeDoc(UINT nID, CDirDoc * pDirDoc,
773 int nFiles, const FileLocation ifileloc[],
774 const fileopenflags_t dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
775 const PackingInfo * infoUnpacker /*= nullptr*/, const OpenFileParams* pOpenParams /*= nullptr*/)
777 ASSERT(pDirDoc != nullptr);
779 if (sReportFile.empty() && pDirDoc->CompareFilesIfFilesAreLarge(nFiles, ifileloc))
782 String unpackedFileExtension;
783 if ((infoUnpacker || FileTransform::AutoUnpacking) && GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
785 std::vector<String> filepaths(nFiles);
786 std::transform(ifileloc, ifileloc + nFiles, filepaths.begin(),
787 [](auto& file) { return file.filepath; });
788 String filteredFilenames = strutils::join(filepaths.begin(), filepaths.end(), _T("|"));
789 int preferredWindowType = -1;
790 PackingInfo infoUnpacker2;
791 unpackedFileExtension = (infoUnpacker ? infoUnpacker : &infoUnpacker2)
792 ->GetUnpackedFileExtension(filteredFilenames, preferredWindowType);
793 if (static_cast<int>(nID) <= 0 && preferredWindowType >= 0)
794 nID = ID_MERGE_COMPARE_TEXT + preferredWindowType;
796 FileFilterHelper filterImg, filterBin;
797 filterImg.UseMask(true);
798 filterImg.SetMask(GetOptionsMgr()->GetString(OPT_CMP_IMG_FILEPATTERNS));
799 filterBin.UseMask(true);
800 filterBin.SetMask(GetOptionsMgr()->GetString(OPT_CMP_BIN_FILEPATTERNS));
801 for (int pane = 0; pane < nFiles; ++pane)
803 if (CWebPageDiffFrame::MatchURLPattern(ifileloc[pane].filepath))
804 return ShowWebDiffDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenWebPageParams*>(pOpenParams));
805 String filepath = ifileloc[pane].filepath + unpackedFileExtension;
806 if (filterImg.includeFile(filepath) && CImgMergeFrame::IsLoadable())
807 return ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenImageFileParams *>(pOpenParams));
808 else if (filterBin.includeFile(filepath) && CHexMergeView::IsLoadable())
809 return ShowHexMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenBinaryFileParams *>(pOpenParams));
811 switch (std::abs(static_cast<int>(nID)))
813 case ID_MERGE_COMPARE_TEXT:
814 return ShowTextMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
815 strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenTextFileParams*>(pOpenParams));
816 case ID_MERGE_COMPARE_TABLE:
817 return ShowTableMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
818 strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenTextFileParams*>(pOpenParams));
819 case ID_MERGE_COMPARE_HEX:
820 return ShowHexMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
821 strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenBinaryFileParams*>(pOpenParams));
822 case ID_MERGE_COMPARE_IMAGE:
823 return ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
824 strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenImageFileParams*>(pOpenParams));
825 case ID_MERGE_COMPARE_WEBPAGE:
826 return ShowWebDiffDoc(pDirDoc, nFiles, ifileloc, dwFlags,
827 strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenWebPageParams*>(pOpenParams));
829 return ShowTextOrTableMergeDoc({}, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenTextFileParams*>(pOpenParams));
833 bool CMainFrame::ShowMergeDoc(UINT nID, CDirDoc* pDirDoc,
834 int nFiles, const FileLocation ifileloc[],
835 const fileopenflags_t dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
836 const PackingInfo* infoUnpacker /*= nullptr*/, const OpenFileParams* pOpenParams /*= nullptr*/)
840 case ID_MERGE_COMPARE_TEXT:
841 return ShowTextMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
842 strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenTextFileParams*>(pOpenParams));
843 case ID_MERGE_COMPARE_TABLE:
844 return ShowTableMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
845 strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenTextFileParams*>(pOpenParams));
846 case ID_MERGE_COMPARE_HEX:
847 return ShowHexMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
848 strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenBinaryFileParams*>(pOpenParams));
849 case ID_MERGE_COMPARE_IMAGE:
850 return ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
851 strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenImageFileParams*>(pOpenParams));
852 case ID_MERGE_COMPARE_WEBPAGE:
853 return ShowWebDiffDoc(pDirDoc, nFiles, ifileloc, dwFlags,
854 strDesc, sReportFile, infoUnpacker, dynamic_cast<const OpenWebPageParams*>(pOpenParams));
856 return ShowAutoMergeDoc(nID, pDirDoc, nFiles, ifileloc, dwFlags,
857 strDesc, sReportFile, infoUnpacker, pOpenParams);
861 std::array<bool, 3> GetROFromFlags(int nFiles, const fileopenflags_t dwFlags[])
863 std::array<bool, 3> bRO = { false, false, false };
864 for (int pane = 0; pane < nFiles; pane++)
867 bRO[pane] = ((dwFlags[pane] & FFILEOPEN_READONLY) > 0);
872 int GetActivePaneFromFlags(int nFiles, const fileopenflags_t dwFlags[])
874 int nActivePane = -1;
875 for (int pane = 0; pane < nFiles; ++pane)
877 if (dwFlags && (dwFlags[pane] & FFILEOPEN_SETFOCUS))
884 * @brief Creates new MergeDoc instance and shows documents.
885 * @param [in] pDirDoc Dir compare document to create a new Merge document for.
886 * @param [in] ifilelocLeft Left side file location info.
887 * @param [in] ifilelocRight Right side file location info.
888 * @param [in] dwLeftFlags Left side flags.
889 * @param [in] dwRightFlags Right side flags.
890 * @param [in] infoUnpacker Plugin info.
891 * @return success/failure
893 bool CMainFrame::ShowTextOrTableMergeDoc(std::optional<bool> table, CDirDoc * pDirDoc,
894 int nFiles, const FileLocation ifileloc[],
895 const fileopenflags_t dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
896 const PackingInfo * infoUnpacker /*= nullptr*/, const OpenTextFileParams* pOpenParams /*= nullptr*/)
898 CMultiDocTemplate* pDiffTemplate = theApp.GetDiffTemplate();
899 if (m_pMenus[MENU_MERGEVIEW] == nullptr)
900 pDiffTemplate->m_hMenuShared = NewMergeViewMenu();
901 CMergeDoc * pMergeDoc = GetMergeDocForDiff<CMergeDoc>(pDiffTemplate, pDirDoc, nFiles, false);
903 // Make local copies, so we can change encoding if we guess it below
904 FileLocation fileloc[3];
905 std::copy_n(ifileloc, nFiles, fileloc);
907 ASSERT(pMergeDoc != nullptr); // must ASSERT to get an answer to the question below ;-)
908 if (pMergeDoc == nullptr)
909 return false; // when does this happen ?
911 // if an unpacker is selected, it must be used during LoadFromFile
912 // MergeDoc must memorize it for SaveToFile
913 // Warning : this unpacker may differ from the pDirDoc one
914 // (through menu : "Plugins"->"Open with unpacker")
915 pMergeDoc->SetUnpacker(infoUnpacker);
918 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
919 for (int pane = 0; pane < nFiles; pane++)
921 if (fileloc[pane].encoding.m_unicoding == -1)
922 fileloc[pane].encoding.m_unicoding = ucr::NONE;
923 if (fileloc[pane].encoding.m_unicoding == ucr::NONE && fileloc[pane].encoding.m_codepage == -1)
925 FileLocationGuessEncodings(fileloc[pane], iGuessEncodingType);
929 pMergeDoc->SetEnableTableEditing(table);
930 if (pOpenParams && table.value_or(false))
932 CMergeDoc::TableProps props = CMergeDoc::MakeTablePropertiesByFileName(
933 pOpenParams->m_fileExt.empty() ? fileloc[0].filepath : pOpenParams->m_fileExt, true, false);
934 if (const auto* pOpenTableFileParams = dynamic_cast<const OpenTableFileParams*>(pOpenParams))
936 props.delimiter = pOpenTableFileParams->m_tableDelimiter.value_or(props.delimiter);
937 props.quote = pOpenTableFileParams->m_tableQuote.value_or(props.quote);
938 props.allowNewlinesInQuotes = pOpenTableFileParams->m_tableAllowNewlinesInQuotes.value_or(props.allowNewlinesInQuotes);
940 pMergeDoc->SetPreparedTableProperties(props);
943 // Note that OpenDocs() takes care of closing compare window when needed.
944 bool bResult = pMergeDoc->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc);
947 if (CMergeEditFrame *pFrame = pMergeDoc->GetParentFrame())
948 if (!pFrame->IsActivated())
949 pFrame->InitialUpdateFrame(pMergeDoc, true);
956 if (pOpenParams && !pOpenParams->m_fileExt.empty())
957 pMergeDoc->SetTextType(pOpenParams->m_fileExt);
959 for (int pane = 0; pane < nFiles; pane++)
963 bool bModified = (dwFlags[pane] & FFILEOPEN_MODIFIED) > 0;
966 pMergeDoc->m_ptBuf[pane]->SetModified(true);
967 pMergeDoc->UpdateHeaderPath(pane);
969 if (dwFlags[pane] & FFILEOPEN_AUTOMERGE)
971 pMergeDoc->DoAutoMerge(pane);
976 pMergeDoc->MoveOnLoad(
977 GetActivePaneFromFlags(nFiles, dwFlags),
978 pOpenParams ? pOpenParams->m_line : -1,
980 pOpenParams ? pOpenParams->m_char: -1);
982 if (pOpenParams && !pOpenParams->m_strSaveAsPath.empty())
983 pMergeDoc->SetSaveAsPath(pOpenParams->m_strSaveAsPath);
985 if (!sReportFile.empty())
986 pMergeDoc->GenerateReport(sReportFile);
991 bool CMainFrame::ShowTextMergeDoc(CDirDoc* pDirDoc,
992 int nFiles, const FileLocation ifileloc[],
993 const fileopenflags_t dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
994 const PackingInfo* infoUnpacker /*= nullptr*/, const OpenTextFileParams* pOpenParams /*= nullptr*/)
996 return ShowTextOrTableMergeDoc(false, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams);
999 bool CMainFrame::ShowTableMergeDoc(CDirDoc* pDirDoc,
1000 int nFiles, const FileLocation ifileloc[],
1001 const fileopenflags_t dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
1002 const PackingInfo* infoUnpacker /*= nullptr*/, const OpenTextFileParams* pOpenParams /*= nullptr*/)
1004 return ShowTextOrTableMergeDoc(true, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams);
1007 bool CMainFrame::ShowHexMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
1008 const fileopenflags_t dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
1009 const PackingInfo * infoUnpacker /*= nullptr*/, const OpenBinaryFileParams* pOpenParams /*= nullptr*/)
1011 CMultiDocTemplate* pHexMergeTemplate = theApp.GetHexMergeTemplate();
1012 if (m_pMenus[MENU_HEXMERGEVIEW] == nullptr)
1013 pHexMergeTemplate->m_hMenuShared = NewHexMergeViewMenu();
1014 CHexMergeDoc *pHexMergeDoc = GetMergeDocForDiff<CHexMergeDoc>(pHexMergeTemplate, pDirDoc, nFiles);
1015 if (pHexMergeDoc == nullptr)
1018 pHexMergeDoc->SetUnpacker(infoUnpacker);
1020 if (!pHexMergeDoc->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc))
1023 pHexMergeDoc->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags));
1025 if (pOpenParams && !pOpenParams->m_strSaveAsPath.empty())
1026 pHexMergeDoc->SetSaveAsPath(pOpenParams->m_strSaveAsPath);
1028 if (!sReportFile.empty())
1029 pHexMergeDoc->GenerateReport(sReportFile);
1034 bool CMainFrame::ShowImgMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
1035 const fileopenflags_t dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
1036 const PackingInfo * infoUnpacker /*= nullptr*/, const OpenImageFileParams* pOpenParams /*= nullptr*/)
1038 CImgMergeFrame *pImgMergeFrame = new CImgMergeFrame();
1039 if (!CImgMergeFrame::menu.m_hMenu)
1040 CImgMergeFrame::menu.m_hMenu = NewImgMergeViewMenu();
1041 pImgMergeFrame->SetSharedMenu(CImgMergeFrame::menu.m_hMenu);
1042 pImgMergeFrame->SetUnpacker(infoUnpacker);
1043 pImgMergeFrame->SetDirDoc(pDirDoc);
1044 pDirDoc->AddMergeDoc(pImgMergeFrame);
1046 if (!pImgMergeFrame->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc, this))
1049 for (int pane = 0; pane < nFiles; pane++)
1051 if (dwFlags && (dwFlags[pane] & FFILEOPEN_AUTOMERGE))
1052 pImgMergeFrame->DoAutoMerge(pane);
1055 pImgMergeFrame->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags));
1057 if (pOpenParams && !pOpenParams->m_strSaveAsPath.empty())
1058 pImgMergeFrame->SetSaveAsPath(pOpenParams->m_strSaveAsPath);
1060 if (!sReportFile.empty())
1061 pImgMergeFrame->GenerateReport(sReportFile);
1066 bool CMainFrame::ShowWebDiffDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
1067 const fileopenflags_t dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
1068 const PackingInfo * infoUnpacker /*= nullptr*/, const OpenWebPageParams* pOpenParams /*= nullptr*/)
1070 CWebPageDiffFrame *pWebPageMergeFrame = new CWebPageDiffFrame();
1071 if (!CWebPageDiffFrame::menu.m_hMenu)
1072 CWebPageDiffFrame::menu.m_hMenu = NewWebPageDiffViewMenu();
1073 pWebPageMergeFrame->SetSharedMenu(CWebPageDiffFrame::menu.m_hMenu);
1074 pWebPageMergeFrame->SetUnpacker(infoUnpacker);
1075 pWebPageMergeFrame->SetDirDoc(pDirDoc);
1076 pDirDoc->AddMergeDoc(pWebPageMergeFrame);
1078 bool completed = false, result = false;
1079 if (!pWebPageMergeFrame->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc, this,
1080 [&completed]() { completed = true; }))
1083 WaitAndDoMessageLoop(completed, 0);
1085 pWebPageMergeFrame->MoveOnLoad(GetActivePaneFromFlags(nFiles, dwFlags));
1087 if (!sReportFile.empty())
1090 if (pWebPageMergeFrame->GenerateReport(sReportFile, [&result, &completed](bool res) { result = res; completed = true; }))
1091 WaitAndDoMessageLoop(completed, 0);
1097 bool CMainFrame::ShowTextMergeDoc(CDirDoc* pDirDoc, int nBuffers, const String text[],
1098 const String strDesc[], const String& strFileExt, const OpenTextFileParams* pOpenParams /*= nullptr*/)
1100 FileLocation fileloc[3];
1101 fileopenflags_t dwFlags[3] = {};
1102 CDirDoc* pDirDoc2 = pDirDoc->GetMainView() ? pDirDoc :
1103 static_cast<CDirDoc*>(theApp.GetDirTemplate()->CreateNewDocument());
1104 for (int nBuffer = 0; nBuffer < nBuffers; ++nBuffer)
1106 auto wTemp = std::make_shared<TempFile>(TempFile());
1107 String workFile = wTemp->Create(_T("text_"), strFileExt);
1108 m_tempFiles.push_back(wTemp);
1109 wTemp->Create(_T(""), strFileExt);
1111 if (file.OpenCreateUtf8(workFile))
1113 file.WriteString(text[nBuffer]);
1115 fileloc[nBuffer].setPath(workFile);
1117 return ShowTextMergeDoc(pDirDoc2, nBuffers, fileloc, dwFlags, strDesc, _T(""), nullptr, pOpenParams);
1121 * @brief Show GNU licence information in notepad (local file) or in Web Browser
1123 void CMainFrame::OnHelpGnulicense()
1125 const String spath = paths::ConcatPath(env::GetProgPath(), LicenseFile);
1126 shell::OpenFileOrUrl(spath.c_str(), LicenceUrl);
1130 * @brief Opens Options-dialog and saves changed options
1132 void CMainFrame::OnOptions()
1134 // Using singleton shared syntax colors
1135 CPreferencesDlg dlg(GetOptionsMgr(), theApp.GetMainSyntaxColors());
1136 INT_PTR rv = dlg.DoModal();
1140 LANGID lang = static_cast<LANGID>(GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE));
1141 if (lang != theApp.m_pLangDlg->GetLangId())
1143 theApp.m_pLangDlg->SetLanguage(lang, true);
1145 // Update status bar inicator texts
1146 theApp.SetIndicators(m_wndStatusBar, 0, 0);
1148 // Update the current menu
1151 // update the title text of the document
1157 // Set new temporary path
1158 theApp.SetupTempPath();
1160 // Set new filterpath
1161 const String& filterPath = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
1162 theApp.GetGlobalFileFilter()->SetUserFilterPath(filterPath);
1164 CCrystalTextView::RENDERING_MODE nRenderingMode = static_cast<CCrystalTextView::RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
1165 CCrystalTextView::SetRenderingModeDefault(nRenderingMode);
1167 theApp.UpdateCodepageModule();
1169 strdiff::SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
1171 // make an attempt at rescanning any open diff sessions
1174 // Update all dirdoc settings
1175 for (auto pDirDoc : GetAllDirDocs())
1176 pDirDoc->RefreshOptions();
1177 for (auto pHexMergeDoc : GetAllHexMergeDocs())
1178 pHexMergeDoc->RefreshOptions();
1179 for (auto pImgMergeFrame : GetAllImgMergeFrames())
1180 pImgMergeFrame->RefreshOptions();
1184 static bool AddToRecentDocs(const PathContext& paths,
1185 const unsigned flags[], const String desc[],
1186 std::optional<bool> recurse, const String& filter,
1187 const PackingInfo *infoUnpacker, const PrediffingInfo *infoPrediffer,
1188 UINT nID, const CMainFrame::OpenFileParams *pOpenParams)
1190 ASSERT(paths.GetSize() <= 3);
1191 const tchar_t *lmr= (paths.GetSize() == 2) ? _T("lr") : _T("lmr");
1192 String params, title;
1193 for (int nIndex = 0; nIndex < paths.GetSize(); ++nIndex)
1197 if (flags[nIndex] & FFILEOPEN_READONLY)
1198 params += strutils::format(_T("/w%c "), lmr[nIndex]);
1199 if (flags[nIndex] & FFILEOPEN_SETFOCUS)
1200 params += strutils::format(_T("/f%c "), lmr[nIndex]);
1201 if (flags[nIndex] & FFILEOPEN_AUTOMERGE)
1202 params += strutils::format(_T("/a%c "), lmr[nIndex]);
1204 if (desc && !desc[nIndex].empty())
1205 params += strutils::format(_T("/d%c \"%s\" "), lmr[nIndex], desc[nIndex]);
1206 params += _T("\"") + paths[nIndex] + _T("\" ");
1208 String path = paths[nIndex];
1209 paths::normalize(path);
1210 title += paths::FindFileName(path);
1211 if (nIndex < paths.GetSize() - 1)
1214 if (recurse.has_value())
1215 params += *recurse ? _T("/r ") : _T("/r- ");
1216 if (!filter.empty())
1217 params += _T("/f \"") + filter + _T("\" ");
1220 case ID_MERGE_COMPARE_TEXT: params += _T("/t text "); break;
1221 case ID_MERGE_COMPARE_TABLE: params += _T("/t table "); break;
1222 case ID_MERGE_COMPARE_HEX: params += _T("/t binary "); break;
1223 case ID_MERGE_COMPARE_IMAGE: params += _T("/t image "); break;
1224 case ID_MERGE_COMPARE_WEBPAGE: params += _T("/t webpage "); break;
1228 if (const auto* pOpenTextFileParams = dynamic_cast<const CMainFrame::OpenTextFileParams*>(pOpenParams))
1230 if (pOpenTextFileParams->m_line >= 0)
1231 params += strutils::format(_T("/l %d "), pOpenTextFileParams->m_line + 1);
1232 if (!pOpenTextFileParams->m_fileExt.empty())
1233 params += _T("/fileext ") + pOpenTextFileParams->m_fileExt + _T(" ");
1235 if (const auto* pOpenTableFileParams = dynamic_cast<const CMainFrame::OpenTableFileParams*>(pOpenParams))
1237 if (pOpenTableFileParams->m_tableDelimiter.has_value())
1239 String delim = strutils::to_charstr(*pOpenTableFileParams->m_tableDelimiter);
1240 if (*pOpenTableFileParams->m_tableDelimiter == '\'')
1242 else if (*pOpenTableFileParams->m_tableDelimiter == '"')
1244 params += strutils::format(_T("/table-delimiter %s "), delim);
1246 if (pOpenTableFileParams->m_tableQuote.has_value())
1248 String quote = strutils::to_charstr(*pOpenTableFileParams->m_tableQuote);
1249 if (*pOpenTableFileParams->m_tableDelimiter == '\'')
1251 else if (*pOpenTableFileParams->m_tableDelimiter == '"')
1253 params += strutils::format(_T("/table-quote %s "), quote);
1255 if (pOpenTableFileParams->m_tableAllowNewlinesInQuotes.has_value())
1256 params += strutils::format(_T("/table-allownewlinesinquotes %d "), *pOpenTableFileParams->m_tableAllowNewlinesInQuotes);
1259 if (infoUnpacker && !infoUnpacker->GetPluginPipeline().empty())
1261 String pipeline = infoUnpacker->GetPluginPipeline();
1262 strutils::replace(pipeline, _T("\""), _T("\"\""));
1263 params += _T("/unpacker \"") + pipeline + _T("\" ");
1265 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
1267 String pipeline = infoPrediffer->GetPluginPipeline();
1268 strutils::replace(pipeline, _T("\""), _T("\"\""));
1269 params += _T("/prediffer \"") + pipeline + _T("\" ");
1272 Concurrent::CreateTask([params, title](){
1273 if (SUCCEEDED(CoInitialize(nullptr)))
1275 JumpList::AddToRecentDocs(_T(""), params, title, params, _T(""), 0);
1284 * @brief Begin a diff: open dirdoc if it is directories, else open a mergedoc for editing.
1285 * @param [in] pszLeft Left-side path.
1286 * @param [in] pszRight Right-side path.
1287 * @param [in] dwLeftFlags Left-side flags.
1288 * @param [in] dwRightFlags Right-side flags.
1289 * @param [in] bRecurse Do we run recursive (folder) compare?
1290 * @param [in] pDirDoc Dir compare document to use.
1291 * @param [in] infoUnpacker Unpacker plugin name.
1292 * @param [in] infoPrediffer Prediffer plugin name.
1293 * @return `true` if opening files and compare succeeded, `false` otherwise.
1295 bool CMainFrame::DoFileOrFolderOpen(const PathContext * pFiles /*= nullptr*/,
1296 const fileopenflags_t dwFlags[] /*= nullptr*/, const String strDesc[] /*= nullptr*/, const String& sReportFile /*= T("")*/,
1297 std::optional<bool> bRecurse /*= false*/, CDirDoc* pDirDoc/*= nullptr*/,
1298 const PackingInfo *infoUnpacker /*= nullptr*/, const PrediffingInfo *infoPrediffer /*= nullptr*/,
1299 UINT nID /*= 0*/, const OpenFileParams *pOpenParams /*= nullptr*/)
1301 if (pDirDoc != nullptr && !pDirDoc->CloseMergeDocs())
1304 FileTransform::AutoUnpacking = GetOptionsMgr()->GetBool(OPT_PLUGINS_UNPACKER_MODE);
1305 FileTransform::AutoPrediffing = GetOptionsMgr()->GetBool(OPT_PLUGINS_PREDIFFER_MODE);
1307 Merge7zFormatMergePluginScope scope(infoUnpacker);
1310 if (pFiles != nullptr)
1315 bRO[0] = (dwFlags[0] & FFILEOPEN_READONLY) != 0;
1316 bRO[1] = (dwFlags[1] & FFILEOPEN_READONLY) != 0;
1317 bRO[2] = (dwFlags[2] & FFILEOPEN_READONLY) != 0;
1320 bool bRecurse2 = bRecurse.has_value() ? *bRecurse : GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
1322 // pop up dialog unless arguments exist (and are compatible)
1323 paths::PATH_EXISTENCE pathsType = paths::GetPairComparability(tFiles, IsArchiveFile);
1324 bool allowFolderCompare = (static_cast<int>(nID) <= 0);
1325 if (tFiles.GetSize() < 2 || pathsType == paths::DOES_NOT_EXIST &&
1326 !std::any_of(tFiles.begin(), tFiles.end(), [](const auto& path) { return path.empty() || paths::IsURL(path); }))
1328 CMultiDocTemplate* pOpenTemplate = theApp.GetOpenTemplate();
1329 if (m_pMenus[MENU_OPENVIEW] == nullptr)
1330 pOpenTemplate->m_hMenuShared = NewOpenViewMenu();
1331 COpenDoc *pOpenDoc = static_cast<COpenDoc *>(pOpenTemplate->CreateNewDocument());
1334 pOpenDoc->m_dwFlags[0] = dwFlags[0];
1335 pOpenDoc->m_dwFlags[1] = dwFlags[1];
1336 pOpenDoc->m_dwFlags[2] = dwFlags[2];
1338 pOpenDoc->m_files = tFiles;
1339 pOpenDoc->m_bRecurse = bRecurse2;
1341 pOpenDoc->m_strUnpackerPipeline = infoUnpacker->GetPluginPipeline();
1342 CFrameWnd *pFrame = pOpenTemplate->CreateNewFrame(pOpenDoc, nullptr);
1343 pOpenTemplate->InitialUpdateFrame(pFrame, pOpenDoc);
1347 // Add trailing '\' for directories if its missing
1348 if (pathsType == paths::IS_EXISTING_DIR)
1350 if (!paths::EndsWithSlash(tFiles[0]) && !IsArchiveFile(tFiles[0]))
1351 tFiles[0] = paths::AddTrailingSlash(tFiles[0]);
1352 if (!paths::EndsWithSlash(tFiles[1]) && !IsArchiveFile(tFiles[1]))
1353 tFiles[1] = paths::AddTrailingSlash(tFiles[1]);
1354 if (tFiles.GetSize() == 3 && !paths::EndsWithSlash(tFiles[2]) && !IsArchiveFile(tFiles[1]))
1355 tFiles[2] = paths::AddTrailingSlash(tFiles[2]);
1358 //save the MRU left and right files.
1361 if (!(dwFlags[0] & FFILEOPEN_NOMRU))
1362 addToMru(tFiles[0].c_str(), _T("Files\\Left"));
1363 if (!(dwFlags[1] & FFILEOPEN_NOMRU))
1364 addToMru(tFiles[1].c_str(), _T("Files\\Right"));
1365 if (tFiles.GetSize() == 3 && !(dwFlags[2] & FFILEOPEN_NOMRU))
1366 addToMru(tFiles[2].c_str(), _T("Files\\Option"));
1369 CTempPathContext *pTempPathContext = nullptr;
1370 if (allowFolderCompare && pathsType == paths::IS_EXISTING_DIR)
1372 DecompressResult res = DecompressArchive(m_hWnd, tFiles);
1375 int ans = AfxMessageBox(IDS_FAILED_EXTRACT_ARCHIVE_FILES, MB_YESNO | MB_DONT_ASK_AGAIN | MB_ICONWARNING, IDS_FAILED_EXTRACT_ARCHIVE_FILES);
1378 pathsType = paths::IS_EXISTING_FILE;
1379 delete res.pTempPathContext;
1380 res.pTempPathContext = nullptr;
1383 if (res.pTempPathContext)
1385 pathsType = res.pathsType;
1387 pTempPathContext = res.pTempPathContext;
1391 // Determine if we want a new dirview open, now that we know if it was
1392 // an archive. Don't open a new dirview if we are comparing files.
1393 if (pDirDoc == nullptr)
1395 CMultiDocTemplate* pDirTemplate = theApp.GetDirTemplate();
1396 if (allowFolderCompare && pathsType == paths::IS_EXISTING_DIR)
1398 CDirDoc::m_nDirsTemp = tFiles.GetSize();
1399 if (m_pMenus[MENU_DIRVIEW] == nullptr)
1400 pDirTemplate->m_hMenuShared = NewDirViewMenu();
1401 pDirDoc = static_cast<CDirDoc*>(pDirTemplate->OpenDocumentFile(nullptr));
1405 pDirDoc = static_cast<CDirDoc*>(pDirTemplate->CreateNewDocument());
1410 if (allowFolderCompare && pathsType == paths::IS_EXISTING_DIR)
1412 if (pDirDoc != nullptr)
1414 // Anything that can go wrong inside InitCompare() will yield an
1415 // exception. There is no point in checking return value.
1416 pDirDoc->InitCompare(tFiles, bRecurse2, pTempPathContext);
1418 const auto* pOpenFolderParams = dynamic_cast<const OpenFolderParams*>(pOpenParams);
1419 if (pOpenFolderParams)
1420 pDirDoc->SetHiddenItems(pOpenFolderParams->m_hiddenItems);
1421 pDirDoc->SetReportFile(sReportFile);
1422 pDirDoc->SetDescriptions(strDesc);
1423 pDirDoc->SetTitle(nullptr);
1424 for (int nIndex = 0; nIndex < tFiles.GetSize(); nIndex++)
1425 pDirDoc->SetReadOnly(nIndex, bRO[nIndex]);
1432 FileLocation fileloc[3];
1434 for (int nPane = 0; nPane < tFiles.GetSize(); nPane++)
1435 fileloc[nPane].setPath(tFiles[nPane]);
1437 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
1439 String strBothFilenames = strutils::join(tFiles.begin(), tFiles.end(), _T("|"));
1440 pDirDoc->GetPluginManager().SetPrediffer(strBothFilenames, infoPrediffer->GetPluginPipeline());
1443 ShowMergeDoc(nID, pDirDoc, tFiles.GetSize(), fileloc, dwFlags, strDesc, sReportFile,
1444 infoUnpacker, pOpenParams);
1447 if (pFiles != nullptr && (!dwFlags || !(dwFlags[0] & FFILEOPEN_NOMRU)))
1449 String filter = (allowFolderCompare && pathsType == paths::IS_EXISTING_DIR) ?
1450 theApp.GetGlobalFileFilter()->GetFilterNameOrMask() : _T("");
1451 AddToRecentDocs(*pFiles, (unsigned *)dwFlags, strDesc, bRecurse, filter, infoUnpacker, infoPrediffer, nID, pOpenParams);
1457 bool CMainFrame::DoFileOpen(UINT nID, const PathContext* pFiles,
1458 const fileopenflags_t dwFlags[] /*= nullptr*/, const String strDesc[] /*= nullptr*/,
1459 const String& sReportFile /*= _T("")*/,
1460 const PackingInfo *infoUnpacker /*= nullptr*/, const PrediffingInfo *infoPrediffer /*= nullptr*/,
1461 const OpenFileParams *pOpenParams /*= nullptr*/)
1463 ASSERT(pFiles != nullptr);
1464 CDirDoc* pDirDoc = static_cast<CDirDoc*>(theApp.GetDirTemplate()->CreateNewDocument());
1465 FileLocation fileloc[3];
1466 for (int pane = 0; pane < pFiles->GetSize(); pane++)
1467 fileloc[pane].setPath((*pFiles)[pane]);
1468 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
1470 String strBothFilenames = strutils::join(pFiles->begin(), pFiles->end(), _T("|"));
1471 pDirDoc->GetPluginManager().SetPrediffer(strBothFilenames, infoPrediffer->GetPluginPipeline());
1473 bool result = ShowMergeDoc(nID, pDirDoc, pFiles->GetSize(), fileloc,
1474 dwFlags, strDesc, sReportFile, infoUnpacker, pOpenParams);
1475 if (!dwFlags || !(dwFlags[0] & FFILEOPEN_NOMRU))
1476 AddToRecentDocs(*pFiles, (unsigned *)dwFlags, strDesc, false, _T(""), infoUnpacker, infoPrediffer, nID, pOpenParams);
1480 void CMainFrame::UpdateFont(FRAMETYPE frame)
1482 if (frame == FRAME_FOLDER)
1484 for (auto pDoc : GetAllDirDocs())
1486 if (pDoc != nullptr)
1488 CDirView *pView = pDoc->GetMainView();
1489 if (pView != nullptr)
1490 pView->SetFont(m_lfDir);
1496 for (auto pDoc : GetAllMergeDocs())
1498 CMergeDoc *pMergeDoc = dynamic_cast<CMergeDoc *>(pDoc);
1499 if (pMergeDoc != nullptr)
1500 for (auto& pView: pMergeDoc->GetViewList())
1501 pView->SetFont(m_lfDiff);
1507 * @brief Select font for Merge/Dir view
1509 * Shows font selection dialog to user, sets current font and saves
1510 * selected font properties to registry. Selects fon type to active
1511 * view (Merge/Dir compare). If there is no open views, then font
1512 * is selected for Merge view (for example user may want to change to
1513 * unicode font before comparing files).
1515 void CMainFrame::OnViewSelectfont()
1517 FRAMETYPE frame = GetFrameType(GetActiveFrame());
1518 CHOOSEFONT cf = { sizeof CHOOSEFONT };
1519 LOGFONT *lf = nullptr;
1520 cf.Flags = CF_INITTOLOGFONTSTRUCT|CF_FORCEFONTEXIST|CF_SCREENFONTS;
1521 if (frame == FRAME_FILE)
1522 cf.Flags |= CF_FIXEDPITCHONLY; // Only fixed-width fonts for merge view
1524 // CF_FIXEDPITCHONLY = 0x00004000L
1525 // in case you are a developer and want to disable it to test with, eg, a Chinese capable font
1526 if (frame == FRAME_FOLDER)
1532 cf.hwndOwner = m_hWnd;
1534 if (ChooseFont(&cf))
1536 Options::Font::Save(GetOptionsMgr(), frame == FRAME_FOLDER ? OPT_FONT_DIRCMP : OPT_FONT_FILECMP, lf, true);
1542 * @brief Use default font for active view type
1544 * Disable user-selected font for active view type (Merge/Dir compare).
1545 * If there is no open views, then Merge view font is changed.
1547 void CMainFrame::OnViewUsedefaultfont()
1549 FRAMETYPE frame = GetFrameType(GetActiveFrame());
1551 if (frame == FRAME_FOLDER)
1553 Options::Font::Reset(GetOptionsMgr(), OPT_FONT_DIRCMP);
1554 m_lfDir = Options::Font::Load(GetOptionsMgr(), OPT_FONT_DIRCMP);
1555 Options::Font::Save(GetOptionsMgr(), OPT_FONT_DIRCMP, &m_lfDir, false);
1559 Options::Font::Reset(GetOptionsMgr(), OPT_FONT_FILECMP);
1560 m_lfDiff = Options::Font::Load(GetOptionsMgr(), OPT_FONT_FILECMP);
1561 Options::Font::Save(GetOptionsMgr(), OPT_FONT_FILECMP, &m_lfDiff, false);
1568 * @brief Update any resources necessary after a GUI language change
1570 void CMainFrame::UpdateResources()
1572 m_wndStatusBar.SetPaneText(0, theApp.LoadString(AFX_IDS_IDLEMESSAGE).c_str());
1574 for (auto pDoc : GetAllDirDocs())
1575 pDoc->UpdateResources();
1576 for (auto pDoc : GetAllMergeDocs())
1577 pDoc->UpdateResources();
1578 for (auto pDoc : GetAllOpenDocs())
1579 pDoc->UpdateResources();
1580 for (auto pFrame: GetAllImgMergeFrames())
1581 pFrame->UpdateResources();
1582 for (auto pFrame: GetAllWebPageDiffFrames())
1583 pFrame->UpdateResources();
1587 * @brief Open WinMerge help.
1589 * If local HTMLhelp file is found, open it, otherwise open HTML page from web.
1591 void CMainFrame::OnHelpContents()
1597 * @brief Handle translation of default messages on the status bar
1599 void CMainFrame::GetMessageString(UINT nID, CString& rMessage) const
1601 // load appropriate string
1602 const String s = theApp.LoadString(nID);
1604 AfxExtractSubString(rMessage, s.c_str(), 0);
1607 void CMainFrame::ActivateFrame(int nCmdShow)
1611 __super::ActivateFrame(nCmdShow);
1615 m_bFirstTime = false;
1617 WINDOWPLACEMENT wp = { sizeof(WINDOWPLACEMENT) };
1618 GetWindowPlacement(&wp);
1619 wp.rcNormalPosition.left=theApp.GetProfileInt(_T("Settings"), _T("MainLeft"),0);
1620 wp.rcNormalPosition.top=theApp.GetProfileInt(_T("Settings"), _T("MainTop"),0);
1621 wp.rcNormalPosition.right=theApp.GetProfileInt(_T("Settings"), _T("MainRight"),0);
1622 wp.rcNormalPosition.bottom=theApp.GetProfileInt(_T("Settings"), _T("MainBottom"),0);
1623 if (nCmdShow != SW_MINIMIZE && theApp.GetProfileInt(_T("Settings"), _T("MainMax"), FALSE))
1624 wp.showCmd = SW_MAXIMIZE;
1626 wp.showCmd = nCmdShow;
1628 CRect dsk_rc,rc(wp.rcNormalPosition);
1630 dsk_rc.left = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
1631 dsk_rc.top = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
1632 dsk_rc.right = dsk_rc.left + ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
1633 dsk_rc.bottom = dsk_rc.top + ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
1634 if (rc.Width() != 0 && rc.Height() != 0)
1636 // Ensure top-left corner is on visible area,
1637 // 20 points margin is added to prevent "lost" window
1638 CPoint ptTopLeft(rc.TopLeft());
1639 ptTopLeft += CPoint(20, 20);
1641 if (dsk_rc.PtInRect(ptTopLeft))
1642 SetWindowPlacement(&wp);
1644 __super::ActivateFrame(nCmdShow);
1647 __super::ActivateFrame(nCmdShow);
1651 * @brief Called when mainframe is about to be closed.
1652 * This function is called when mainframe is to be closed (not for
1653 * file/compare windows.
1655 void CMainFrame::OnClose()
1657 if (theApp.GetActiveOperations())
1660 // Check if there are multiple windows open and ask for closing them
1661 bool bAskClosing = GetOptionsMgr()->GetBool(OPT_ASK_MULTIWINDOW_CLOSE);
1664 bool quit = AskCloseConfirmation();
1669 // save main window position
1670 WINDOWPLACEMENT wp = { sizeof(WINDOWPLACEMENT) };
1671 GetWindowPlacement(&wp);
1672 theApp.WriteProfileInt(_T("Settings"), _T("MainLeft"),wp.rcNormalPosition.left);
1673 theApp.WriteProfileInt(_T("Settings"), _T("MainTop"),wp.rcNormalPosition.top);
1674 theApp.WriteProfileInt(_T("Settings"), _T("MainRight"),wp.rcNormalPosition.right);
1675 theApp.WriteProfileInt(_T("Settings"), _T("MainBottom"),wp.rcNormalPosition.bottom);
1676 theApp.WriteProfileInt(_T("Settings"), _T("MainMax"), (wp.showCmd == SW_MAXIMIZE));
1678 for (auto pFrame: GetAllImgMergeFrames())
1680 if (!pFrame->CloseNow())
1683 for (auto pFrame: GetAllWebPageDiffFrames())
1685 if (!pFrame->CloseNow())
1693 * @brief Utility function to update CSuperComboBox format MRU
1695 void CMainFrame::addToMru(const tchar_t* szItem, const tchar_t* szRegSubKey, UINT nMaxItems)
1697 std::vector<CString> list;
1699 UINT cnt = AfxGetApp()->GetProfileInt(szRegSubKey, _T("Count"), 0);
1700 list.push_back(szItem);
1701 for (UINT i=0 ; i<cnt; ++i)
1703 s = AfxGetApp()->GetProfileString(szRegSubKey, strutils::format(_T("Item_%d"), i).c_str());
1707 cnt = list.size() > nMaxItems ? nMaxItems : static_cast<UINT>(list.size());
1708 for (UINT i=0 ; i<cnt; ++i)
1709 AfxGetApp()->WriteProfileString(szRegSubKey, strutils::format(_T("Item_%d"), i).c_str(), list[i]);
1711 AfxGetApp()->WriteProfileInt(szRegSubKey, _T("Count"), cnt);
1714 void CMainFrame::ApplyDiffOptions()
1716 for (auto pMergeDoc : GetAllMergeDocs())
1718 // Re-read MergeDoc settings (also updates view settings)
1719 // and rescan using new options
1720 pMergeDoc->RefreshOptions();
1721 pMergeDoc->FlushAndRescan(true);
1722 GetMainFrame()->WatchDocuments(pMergeDoc);
1724 for (auto pWebPageDiffFrame : GetAllWebPageDiffFrames())
1725 pWebPageDiffFrame->RefreshOptions();
1726 for (auto pOpenDoc : GetAllOpenDocs())
1727 pOpenDoc->RefreshOptions();
1730 /// Get list of OpenDocs (documents underlying edit sessions)
1731 OpenDocList &CMainFrame::GetAllOpenDocs()
1733 return static_cast<OpenDocList &>(GetDocList(theApp.GetOpenTemplate()));
1736 /// Get list of MergeDocs (documents underlying edit sessions)
1737 MergeDocList &CMainFrame::GetAllMergeDocs()
1739 return static_cast<MergeDocList &>(GetDocList(theApp.GetDiffTemplate()));
1742 /// Get list of DirDocs (documents underlying a scan)
1743 DirDocList &CMainFrame::GetAllDirDocs()
1745 return static_cast<DirDocList &>(GetDocList(theApp.GetDirTemplate()));
1748 /// Get list of HexMergeDocs (documents underlying edit sessions)
1749 HexMergeDocList &CMainFrame::GetAllHexMergeDocs()
1751 return static_cast<HexMergeDocList &>(GetDocList(theApp.GetHexMergeTemplate()));
1754 std::vector<CImgMergeFrame *> CMainFrame::GetAllImgMergeFrames()
1756 std::vector<CImgMergeFrame *> list;
1757 // Close Non-Document/View frame with confirmation
1758 CMDIChildWnd *pChild = static_cast<CMDIChildWnd *>(CWnd::FromHandle(m_hWndMDIClient)->GetWindow(GW_CHILD));
1759 while (pChild != nullptr)
1761 CMDIChildWnd *pNextChild = static_cast<CMDIChildWnd *>(pChild->GetWindow(GW_HWNDNEXT));
1762 if (GetFrameType(pChild) == FRAME_IMGFILE)
1763 list.push_back(static_cast<CImgMergeFrame *>(pChild));
1764 pChild = pNextChild;
1769 std::vector<CWebPageDiffFrame *> CMainFrame::GetAllWebPageDiffFrames()
1771 std::vector<CWebPageDiffFrame *> list;
1772 // Close Non-Document/View frame with confirmation
1773 CMDIChildWnd *pChild = static_cast<CMDIChildWnd *>(CWnd::FromHandle(m_hWndMDIClient)->GetWindow(GW_CHILD));
1774 while (pChild != nullptr)
1776 CMDIChildWnd *pNextChild = static_cast<CMDIChildWnd *>(pChild->GetWindow(GW_HWNDNEXT));
1777 if (GetFrameType(pChild) == FRAME_WEBPAGE)
1778 list.push_back(static_cast<CWebPageDiffFrame *>(pChild));
1779 pChild = pNextChild;
1785 * @brief Obtain a merge doc to display a difference in files.
1786 * @return Pointer to CMergeDoc to use.
1788 template<class DocClass>
1789 DocClass * GetMergeDocForDiff(CMultiDocTemplate *pTemplate, CDirDoc *pDirDoc, int nFiles, bool bMakeVisible)
1791 // Create a new merge doc
1792 DocClass::m_nBuffersTemp = nFiles;
1793 DocClass *pMergeDoc = static_cast<DocClass*>(pTemplate->OpenDocumentFile(nullptr, bMakeVisible));
1794 if (pMergeDoc != nullptr)
1796 pDirDoc->AddMergeDoc(pMergeDoc);
1797 pMergeDoc->SetDirDoc(pDirDoc);
1803 * @brief Generate patch from files selected.
1805 * Creates a patch from selected files in active directory compare, or
1806 * active file compare. Files in file compare must be saved before
1809 void CMainFrame::OnToolsGeneratePatch()
1812 patcher.CreatePatch();
1815 void CMainFrame::OnDropFiles(const std::vector<String>& dropped_files)
1817 PathContext tFiles(dropped_files);
1818 const size_t fileCount = tFiles.GetSize();
1820 bool recurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
1821 // Do a reverse comparison with the current 'Include subfolders' settings when pressing Control key
1822 if (::GetAsyncKeyState(VK_CONTROL) & 0x8000)
1825 // If user has <Shift> pressed with one file selected,
1826 // assume it is an archive and set filenames to same
1827 if (::GetAsyncKeyState(VK_SHIFT) < 0 && fileCount == 1)
1829 tFiles.SetRight(tFiles[0]);
1832 // Check if they dropped a project file
1833 fileopenflags_t dwFlags[3] = {FFILEOPEN_NONE, FFILEOPEN_NONE, FFILEOPEN_NONE};
1836 if (theApp.IsProjectFile(tFiles[0]))
1838 theApp.LoadAndOpenProjectFile(tFiles[0]);
1841 if (ConflictFileParser::IsConflictFile(tFiles[0]))
1843 DoOpenConflict(tFiles[0], nullptr, true);
1848 DoFileOrFolderOpen(&tFiles, dwFlags, nullptr, _T(""), recurse);
1851 void CMainFrame::OnPluginUnpackMode(UINT nID )
1855 case ID_UNPACK_MANUAL:
1856 FileTransform::AutoUnpacking = false;
1858 case ID_UNPACK_AUTO:
1859 FileTransform::AutoUnpacking = true;
1862 for (auto pDirDoc : GetAllDirDocs())
1864 pDirDoc->GetPluginManager().SetUnpackerSettingAll(FileTransform::AutoUnpacking);
1865 pDirDoc->UpdateAllViews(nullptr);
1867 GetOptionsMgr()->SaveOption(OPT_PLUGINS_UNPACKER_MODE, static_cast<int>(FileTransform::AutoUnpacking));
1870 void CMainFrame::OnUpdatePluginUnpackMode(CCmdUI* pCmdUI)
1872 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
1874 if (pCmdUI->m_nID == ID_UNPACK_MANUAL)
1875 pCmdUI->SetRadio(!FileTransform::AutoUnpacking);
1876 if (pCmdUI->m_nID == ID_UNPACK_AUTO)
1877 pCmdUI->SetRadio(FileTransform::AutoUnpacking);
1880 void CMainFrame::OnPluginPrediffMode(UINT nID )
1884 case ID_PREDIFFER_MANUAL:
1885 FileTransform::AutoPrediffing = false;
1887 case ID_PREDIFFER_AUTO:
1888 FileTransform::AutoPrediffing = true;
1891 PrediffingInfo infoPrediffer;
1892 for (auto pMergeDoc : GetAllMergeDocs())
1893 pMergeDoc->SetPrediffer(&infoPrediffer);
1894 for (auto pDirDoc : GetAllDirDocs())
1896 pDirDoc->GetPluginManager().SetPrediffSettingAll(FileTransform::AutoPrediffing);
1897 pDirDoc->UpdateAllViews(nullptr);
1899 GetOptionsMgr()->SaveOption(OPT_PLUGINS_PREDIFFER_MODE, FileTransform::AutoPrediffing);
1902 void CMainFrame::OnUpdatePluginPrediffMode(CCmdUI* pCmdUI)
1904 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
1906 if (pCmdUI->m_nID == ID_PREDIFFER_MANUAL)
1907 pCmdUI->SetRadio(!FileTransform::AutoPrediffing);
1908 if (pCmdUI->m_nID == ID_PREDIFFER_AUTO)
1909 pCmdUI->SetRadio(FileTransform::AutoPrediffing);
1912 * @brief Called when "Reload Plugins" item is updated
1914 void CMainFrame::OnUpdatePluginRelatedMenu(CCmdUI* pCmdUI)
1916 bool enabled = GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED);
1917 if (enabled && (pCmdUI->m_nID == ID_APPLY_PREDIFFER || pCmdUI->m_nID == ID_TRANSFORM_WITH_SCRIPT))
1918 enabled = GetFrameType(GetActiveFrame()) == FRAME_FILE;
1919 pCmdUI->Enable(enabled);
1922 void CMainFrame::OnReloadPlugins()
1924 CAllThreadsScripts::ReloadAllScripts();
1927 /** @brief Return active merge edit view, if can figure it out/is available */
1928 CMergeEditView * CMainFrame::GetActiveMergeEditView()
1930 // NB: GetActiveDocument does not return the Merge Doc
1931 // even when the merge edit view is in front
1932 // NB: CMergeEditFrame::GetActiveView returns `nullptr` when location view active
1933 // So we have this rather complicated logic to try to get a merge edit view
1934 // We look at the front child window, which should be a frame
1935 // and we can get a MergeEditView from it, if it is a CMergeEditFrame
1936 // (DirViews use a different frame type)
1937 CMergeEditFrame * pFrame = dynamic_cast<CMergeEditFrame *>(GetActiveFrame());
1938 if (pFrame == nullptr) return nullptr;
1939 // Try to get the active MergeEditView (ie, left or right)
1940 if (pFrame->GetActiveView() != nullptr && pFrame->GetActiveView()->IsKindOf(RUNTIME_CLASS(CMergeEditView)))
1942 return dynamic_cast<CMergeEditView *>(pFrame->GetActiveView());
1944 return pFrame->GetMergeDoc()->GetActiveMergeView();
1947 void CMainFrame::UpdatePrediffersMenu()
1949 CMenu* menu = GetMenu();
1950 if (menu == nullptr)
1955 HMENU hMainMenu = menu->m_hMenu;
1956 HMENU prediffersSubmenu = GetPrediffersSubmenu(hMainMenu);
1957 if (prediffersSubmenu != nullptr)
1959 CMergeEditView * pEditView = GetActiveMergeEditView();
1960 if (pEditView != nullptr)
1961 pEditView->GetDocument()->createPrediffersSubmenu(prediffersSubmenu);
1964 // no view or dir view : display an empty submenu
1965 int i = GetMenuItemCount(prediffersSubmenu);
1967 ::DeleteMenu(prediffersSubmenu, 0, MF_BYPOSITION);
1968 ::AppendMenu(prediffersSubmenu, MF_SEPARATOR, 0, nullptr);
1974 * @brief Save WinMerge configuration and info to file
1976 void CMainFrame::OnSaveConfigData()
1978 CConfigLog configLog;
1981 if (configLog.WriteLogFile(sError))
1983 String sFileName = configLog.GetFileName();
1984 CMergeApp::OpenFileToExternalEditor(sFileName);
1988 String sFileName = configLog.GetFileName();
1989 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sFileName, sError);
1990 AfxMessageBox(msg.c_str(), MB_OK | MB_ICONSTOP);
1995 * @brief Open two new empty docs, 'Scratchpads'
1997 * Allows user to open two empty docs, to paste text to
1998 * compare from clipboard.
1999 * @note File filenames are set emptys and filedescriptors
2000 * are loaded from resource.
2001 * @sa CMergeDoc::OpenDocs()
2002 * @sa CMergeDoc::TrySaveAs()
2004 bool CMainFrame::DoFileNew(UINT nID, int nPanes,
2005 const fileopenflags_t dwFlags[], const String strDesc[],
2006 const PrediffingInfo *infoPrediffer /*= nullptr*/,
2007 const OpenFileParams *pOpenParams)
2009 CDirDoc *pDirDoc = static_cast<CDirDoc*>(theApp.GetDirTemplate()->CreateNewDocument());
2011 // Load emptyfile descriptors and open empty docs
2012 // Use default codepage
2013 FileLocation fileloc[3];
2017 strDesc2[0] = _("Untitled left");
2018 strDesc2[1] = _("Untitled right");
2022 strDesc2[0] = _("Untitled left");
2023 strDesc2[1] = _("Untitled middle");
2024 strDesc2[2] = _("Untitled right");
2026 for (int i = 0; i < nPanes; ++i)
2028 if (strDesc && !strDesc[i].empty())
2029 strDesc2[i] = strDesc[i];
2030 fileloc[i].encoding.SetCodepage(ucr::getDefaultCodepage());
2032 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
2033 pDirDoc->GetPluginManager().SetPrediffer(_T("|"), infoPrediffer->GetPluginPipeline());
2034 return ShowMergeDoc(nID, pDirDoc, nPanes, fileloc, dwFlags, strDesc2, _T(""), nullptr, pOpenParams);
2038 * @brief Open Filters dialog
2040 void CMainFrame::OnToolsFilters()
2042 String title = _("Filters");
2043 CPropertySheet sht(title.c_str());
2044 LineFiltersDlg lineFiltersDlg;
2045 SubstitutionFiltersDlg substitutionFiltersDlg;
2046 FileFiltersDlg fileFiltersDlg;
2047 auto lineFilters = std::make_unique<LineFiltersList>(LineFiltersList());
2048 auto SubstitutionFilters = std::make_unique<SubstitutionFiltersList>(SubstitutionFiltersList());
2049 String selectedFilter;
2050 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
2051 const String origFilter = pGlobalFileFilter->GetFilterNameOrMask();
2052 sht.AddPage(&fileFiltersDlg);
2053 sht.AddPage(&lineFiltersDlg);
2054 sht.AddPage(&substitutionFiltersDlg);
2055 sht.m_psh.dwFlags |= PSH_NOAPPLYNOW; // Hide 'Apply' button since we don't need it
2057 // Make sure all filters are up-to-date
2058 pGlobalFileFilter->ReloadUpdatedFilters();
2060 fileFiltersDlg.SetFilterArray(pGlobalFileFilter->GetFileFilters(selectedFilter));
2061 fileFiltersDlg.SetSelected(selectedFilter);
2062 const bool lineFiltersEnabledOrig = GetOptionsMgr()->GetBool(OPT_LINEFILTER_ENABLED);
2063 lineFiltersDlg.m_bIgnoreRegExp = lineFiltersEnabledOrig;
2065 lineFilters->CloneFrom(theApp.m_pLineFilters.get());
2066 lineFiltersDlg.SetList(lineFilters.get());
2068 SubstitutionFilters->CloneFrom(theApp.m_pSubstitutionFiltersList.get());
2069 substitutionFiltersDlg.SetList(SubstitutionFilters.get());
2071 sht.SetActivePage(AfxGetApp()->GetProfileInt(_T("Settings"), _T("FilterStartPage"), 0));
2073 if (sht.DoModal() == IDOK)
2075 String strNone = _("<None>");
2076 String path = fileFiltersDlg.GetSelected();
2077 if (path.find(strNone) != String::npos)
2079 // Don't overwrite mask we already have
2080 if (!pGlobalFileFilter->IsUsingMask())
2082 String sFilter(_T("*.*"));
2083 pGlobalFileFilter->SetFilter(sFilter);
2084 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, sFilter);
2089 pGlobalFileFilter->SetFileFilterPath(path);
2090 pGlobalFileFilter->UseMask(false);
2091 String sFilter = pGlobalFileFilter->GetFilterNameOrMask();
2092 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, sFilter);
2094 bool linefiltersEnabled = lineFiltersDlg.m_bIgnoreRegExp;
2095 GetOptionsMgr()->SaveOption(OPT_LINEFILTER_ENABLED, linefiltersEnabled);
2097 // Check if compare documents need rescanning
2098 bool bFileCompareRescan = false;
2099 bool bFolderCompareRescan = false;
2100 CFrameWnd * pFrame = GetActiveFrame();
2101 FRAMETYPE frame = GetFrameType(pFrame);
2102 if (frame == FRAME_FILE)
2106 linefiltersEnabled != lineFiltersEnabledOrig
2107 || !lineFilters->Compare(theApp.m_pLineFilters.get())
2108 || !SubstitutionFilters->Compare(theApp.m_pSubstitutionFiltersList.get())
2111 bFileCompareRescan = true;
2114 else if (frame == FRAME_FOLDER)
2116 const String newFilter = pGlobalFileFilter->GetFilterNameOrMask();
2117 if (lineFiltersEnabledOrig != linefiltersEnabled ||
2118 !theApp.m_pLineFilters->Compare(lineFilters.get()) || origFilter != newFilter)
2120 int res = LangMessageBox(IDS_FILTERCHANGED, MB_ICONWARNING | MB_YESNO);
2122 bFolderCompareRescan = true;
2126 // Save new filters before (possibly) rescanning
2127 theApp.m_pLineFilters->CloneFrom(lineFilters.get());
2128 theApp.m_pLineFilters->SaveFilters();
2130 theApp.m_pSubstitutionFiltersList->CloneFrom(SubstitutionFilters.get());
2131 theApp.m_pSubstitutionFiltersList->SaveFilters();
2133 if (bFileCompareRescan)
2135 for (auto pMergeDoc : GetAllMergeDocs())
2136 pMergeDoc->FlushAndRescan(true);
2138 else if (bFolderCompareRescan)
2140 for (auto pDirDoc : GetAllDirDocs())
2147 * @brief Open Filters dialog.
2149 void CMainFrame::SelectFilter()
2155 * @brief Closes application with ESC.
2157 * Application is closed if:
2158 * - 'Close Windows with ESC' option is enabled and
2159 * there is no open document windows
2160 * - '-e' commandline switch is given
2162 BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
2164 // Check if we got 'ESC pressed' -message
2165 if ((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
2167 int nEscCloses = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
2168 if ((theApp.m_bEscShutdown || nEscCloses == 3) && m_wndTabBar.GetItemCount() <= 1)
2170 AfxGetMainWnd()->SendMessage(WM_COMMAND, ID_APP_EXIT);
2173 else if (nEscCloses == 1 && m_wndTabBar.GetItemCount() == 0)
2175 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_APP_EXIT);
2180 if (WM_KEYDOWN == pMsg->message && VK_TAB == pMsg->wParam && GetAsyncKeyState(VK_CONTROL) < 0 && m_arrChild.GetSize() > 1)
2182 CWindowsManagerDialog* pDlg = new CWindowsManagerDialog;
2183 pDlg->Create(CWindowsManagerDialog::IDD, this);
2184 pDlg->ShowWindow(SW_SHOW);
2188 return __super::PreTranslateMessage(pMsg);
2192 * @brief Show/hide statusbar.
2194 void CMainFrame::OnViewStatusBar()
2196 bool bShow = !GetOptionsMgr()->GetBool(OPT_SHOW_STATUSBAR);
2197 GetOptionsMgr()->SaveOption(OPT_SHOW_STATUSBAR, bShow);
2199 __super::ShowControlBar(&m_wndStatusBar, bShow, 0);
2203 * @brief Updates "Show Tabbar" menuitem.
2205 void CMainFrame::OnUpdateViewTabBar(CCmdUI* pCmdUI)
2207 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR));
2211 * @brief Show/hide tabbar.
2213 void CMainFrame::OnViewTabBar()
2215 bool bShow = !GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR);
2216 GetOptionsMgr()->SaveOption(OPT_SHOW_TABBAR, bShow);
2218 __super::ShowControlBar(&m_wndTabBar, bShow, 0);
2222 * @brief Updates "Automatically Resize Panes" menuitem.
2224 void CMainFrame::OnUpdateResizePanes(CCmdUI* pCmdUI)
2226 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_RESIZE_PANES));
2231 * @brief Enable/disable automatic pane resizing.
2233 void CMainFrame::OnResizePanes()
2235 bool bResize = !GetOptionsMgr()->GetBool(OPT_RESIZE_PANES);
2236 GetOptionsMgr()->SaveOption(OPT_RESIZE_PANES, bResize);
2237 // TODO: Introduce a common merge frame superclass?
2238 CFrameWnd *pActiveFrame = GetActiveFrame();
2239 if (CMergeEditFrame *pFrame = DYNAMIC_DOWNCAST(CMergeEditFrame, pActiveFrame))
2241 pFrame->UpdateAutoPaneResize();
2243 pFrame->UpdateSplitter();
2245 else if (CHexMergeFrame *pFrame1 = DYNAMIC_DOWNCAST(CHexMergeFrame, pActiveFrame))
2247 pFrame1->UpdateAutoPaneResize();
2249 pFrame1->UpdateSplitter();
2254 * @brief Open project-file.
2256 void CMainFrame::OnFileOpenProject()
2260 // get the default projects path
2261 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
2262 if (!SelectFile(GetSafeHwnd(), sFilepath, true, strProjectPath.c_str(), _T(""),
2263 _("WinMerge Project Files (*.WinMerge)|*.WinMerge||")))
2266 strProjectPath = paths::GetParentPath(sFilepath);
2267 // store this as the new project path
2268 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
2270 theApp.LoadAndOpenProjectFile(sFilepath);
2274 * @brief Receive command line from another instance.
2276 * This function receives command line when only single-instance
2277 * is allowed. New instance tried to start sends its command line
2278 * to here so we can open paths it was meant to.
2280 LRESULT CMainFrame::OnCopyData(WPARAM wParam, LPARAM lParam)
2282 COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;
2283 const tchar_t* pchData = (const tchar_t*)pCopyData->lpData;
2284 // Bail out if data isn't zero-terminated
2285 DWORD cchData = pCopyData->cbData / sizeof(tchar_t);
2286 if (cchData == 0 || pchData[cchData - 1] != _T('\0'))
2289 MergeCmdLineInfo cmdInfo(pchData);
2290 theApp.ApplyCommandLineConfigOptions(cmdInfo);
2291 theApp.ParseArgsAndDoOpen(cmdInfo, this);
2295 LRESULT CMainFrame::OnUser1(WPARAM wParam, LPARAM lParam)
2297 IMergeDoc* pMergeDoc = (wParam == 0) ? GetActiveIMergeDoc() : reinterpret_cast<IMergeDoc*>(wParam);
2299 pMergeDoc->CheckFileChanged();
2304 * @brief Close all open windows.
2306 * Asks about saving unsaved files and then closes all open windows.
2308 void CMainFrame::OnWindowCloseAll()
2310 CMDIChildWnd *pChild = MDIGetActive();
2311 while (pChild != nullptr)
2314 if ((pDoc = pChild->GetActiveDocument()) != nullptr)
2316 if (!pDoc->SaveModified())
2318 pDoc->OnCloseDocument();
2320 else if (GetFrameType(pChild) == FRAME_IMGFILE)
2322 if (!static_cast<CImgMergeFrame *>(pChild)->CloseNow())
2325 else if (GetFrameType(pChild) == FRAME_WEBPAGE)
2327 if (!static_cast<CWebPageDiffFrame *>(pChild)->CloseNow())
2332 pChild->DestroyWindow();
2334 pChild = MDIGetActive();
2340 * @brief Enables Window/Close All item if there are open windows.
2342 void CMainFrame::OnUpdateWindowCloseAll(CCmdUI* pCmdUI)
2344 pCmdUI->Enable(MDIGetActive() != nullptr);
2348 * @brief Access to the singleton main frame (where we have some globals)
2350 CMainFrame * GetMainFrame()
2352 CWnd * mainwnd = AfxGetMainWnd();
2353 ASSERT(mainwnd != nullptr);
2354 CMainFrame *pMainframe = dynamic_cast<CMainFrame*>(mainwnd);
2355 ASSERT(pMainframe != nullptr);
2360 * @brief Opens dialog for user to Load, edit and save project files.
2361 * This dialog gets current compare paths and filter (+other properties
2362 * possible in project files) and initializes the dialog with them.
2364 void CMainFrame::OnSaveProject()
2366 CMultiDocTemplate* pOpenTemplate = theApp.GetOpenTemplate();
2367 if (m_pMenus[MENU_OPENVIEW] == nullptr)
2368 pOpenTemplate->m_hMenuShared = NewOpenViewMenu();
2369 COpenDoc *pOpenDoc = static_cast<COpenDoc *>(pOpenTemplate->CreateNewDocument());
2371 CFrameWnd * pFrame = GetActiveFrame();
2372 FRAMETYPE frame = pFrame ? GetFrameType(pFrame) : FRAME_OTHER;
2374 if (frame == FRAME_FILE || frame == FRAME_HEXFILE || frame == FRAME_IMGFILE || frame == FRAME_WEBPAGE)
2376 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2379 for (int pane = 0; pane < pMergeDoc->GetFileCount(); ++pane)
2381 pOpenDoc->m_dwFlags[pane] = FFILEOPEN_PROJECT | (pMergeDoc->GetReadOnly(pane) ? FFILEOPEN_READONLY : 0);
2382 paths.SetPath(pane, pMergeDoc->GetPath(pane), false);
2383 pOpenDoc->m_strDesc[pane] = pMergeDoc->GetDescription(pane);
2385 pOpenDoc->m_files = paths;
2386 pOpenDoc->m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
2387 pOpenDoc->m_strExt = theApp.GetGlobalFileFilter()->GetFilterNameOrMask();
2388 pOpenDoc->m_strUnpackerPipeline = pMergeDoc->GetUnpacker() ? pMergeDoc->GetUnpacker()->GetPluginPipeline() : _T("");
2389 pOpenDoc->m_strPredifferPipeline = pMergeDoc->GetPrediffer() ? pMergeDoc->GetPrediffer()->GetPluginPipeline() : _T("");
2394 CMergeDoc* pDoc = static_cast<CMergeDoc*>(pMergeDoc);
2395 if (pDoc->m_ptBuf[0]->GetTableEditing())
2397 pOpenDoc->m_nWindowType = ID_MERGE_COMPARE_TABLE - ID_MERGE_COMPARE_TEXT + 1;
2398 pOpenDoc->m_cTableDelimiter = pDoc->m_ptBuf[0]->GetFieldDelimiter();
2399 pOpenDoc->m_cTableQuote = pDoc->m_ptBuf[0]->GetFieldEnclosure();
2400 pOpenDoc->m_bTableAllowNewLinesInQuotes = pDoc->m_ptBuf[0]->GetAllowNewlinesInQuotes();
2404 pOpenDoc->m_nWindowType = ID_MERGE_COMPARE_TEXT - ID_MERGE_COMPARE_TEXT + 1;
2409 pOpenDoc->m_nWindowType = ID_MERGE_COMPARE_HEX - ID_MERGE_COMPARE_TEXT + 1;
2412 pOpenDoc->m_nWindowType = ID_MERGE_COMPARE_IMAGE - ID_MERGE_COMPARE_TEXT + 1;
2415 pOpenDoc->m_nWindowType = ID_MERGE_COMPARE_WEBPAGE - ID_MERGE_COMPARE_TEXT + 1;
2420 else if (frame == FRAME_FOLDER)
2422 // Get paths currently in compare
2423 if (const CDirDoc* pDoc = static_cast<const CDirDoc*>(pFrame->GetActiveDocument()))
2425 const CDiffContext& ctxt = pDoc->GetDiffContext();
2427 // Set-up the dialog
2428 for (int pane = 0; pane < ctxt.GetCompareDirs(); ++pane)
2430 pOpenDoc->m_dwFlags[pane] = FFILEOPEN_PROJECT | (pDoc->GetReadOnly(pane) ? FFILEOPEN_READONLY : 0);
2431 pOpenDoc->m_files.SetPath(pane, paths::AddTrailingSlash(ctxt.GetNormalizedPath(pane)));
2432 pOpenDoc->m_strDesc[pane] = pDoc->GetDescription(pane);
2434 pOpenDoc->m_bRecurse = ctxt.m_bRecursive;
2435 pOpenDoc->m_strExt = static_cast<FileFilterHelper*>(ctxt.m_piFilterGlobal)->GetFilterNameOrMask();
2436 pOpenDoc->m_hiddenItems = ctxt.m_vCurrentlyHiddenItems;
2440 CFrameWnd *pOpenFrame = pOpenTemplate->CreateNewFrame(pOpenDoc, nullptr);
2441 pOpenTemplate->InitialUpdateFrame(pOpenFrame, pOpenDoc);
2445 * @brief Start flashing window if window is inactive.
2447 void CMainFrame::StartFlashing()
2449 CWnd * activeWindow = GetActiveWindow();
2450 if (activeWindow != this)
2451 FlashWindowEx(FLASHW_ALL | FLASHW_TIMERNOFG, 3, 0);
2454 void CMainFrame::OnActivateApp(BOOL bActive, DWORD dwThreadID)
2456 __super::OnActivateApp(bActive, dwThreadID);
2458 if (GetOptionsMgr()->GetInt(OPT_AUTO_RELOAD_MODIFIED_FILES) == AUTO_RELOAD_MODIFIED_FILES_ONWINDOWACTIVATED)
2460 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
2461 PostMessage(WM_USER + 1, reinterpret_cast<WPARAM>(pMergeDoc));
2465 BOOL CMainFrame::CreateToolbar()
2467 if (!m_wndToolBar.CreateEx(this) ||
2468 !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
2473 if (!m_wndReBar.Create(this, RBS_BANDBORDERS,
2474 WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CBRS_ALIGN_TOP))
2479 VERIFY(m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT|TBSTYLE_TRANSPARENT));
2481 // Remove this if you don't want tool tips or a resizable toolbar
2482 m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
2483 CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
2484 m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS);
2486 m_wndReBar.AddBar(&m_wndToolBar);
2488 LoadToolbarImages();
2491 for (auto cmd : { ID_OPTIONS, ID_FILE_NEW, ID_FILE_OPEN, ID_FILE_SAVE })
2494 int index = m_wndToolBar.GetToolBarCtrl().CommandToIndex(cmd);
2495 m_wndToolBar.GetButtonInfo(index, nID, nStyle, iImage);
2496 nStyle |= TBSTYLE_DROPDOWN;
2497 m_wndToolBar.SetButtonInfo(index, nID, nStyle, iImage);
2500 if (!GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR))
2502 __super::ShowControlBar(&m_wndToolBar, false, 0);
2508 /** @brief Load toolbar images from the resource. */
2509 void CMainFrame::LoadToolbarImages()
2511 const int toolbarNewImgSize = MulDiv(16, GetSystemMetrics(SM_CXSMICON), 16) *
2512 (1 + std::clamp(GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE), 0, ID_TOOLBAR_HUGE - ID_TOOLBAR_SMALL));
2513 const int toolbarOrgImgSize = toolbarNewImgSize <= 20 ? 16 : 32;
2514 CToolBarCtrl& BarCtrl = m_wndToolBar.GetToolBarCtrl();
2515 CImageList imgEnabled, imgDisabled;
2516 CSize sizeButton(0, 0);
2518 LoadToolbarImageList(toolbarOrgImgSize, toolbarNewImgSize,
2519 toolbarOrgImgSize <= 16 ? IDB_TOOLBAR_ENABLED : IDB_TOOLBAR_ENABLED32,
2521 LoadToolbarImageList(toolbarOrgImgSize, toolbarNewImgSize,
2522 toolbarOrgImgSize <= 16 ? IDB_TOOLBAR_ENABLED : IDB_TOOLBAR_ENABLED32,
2525 sizeButton = CSize(toolbarNewImgSize + 8, toolbarNewImgSize + 8);
2527 BarCtrl.SetButtonSize(sizeButton);
2528 if (CImageList* pImgList = BarCtrl.SetImageList(&imgEnabled))
2529 pImgList->DeleteImageList();
2530 if (CImageList* pImgList = BarCtrl.SetDisabledImageList(&imgDisabled))
2531 pImgList->DeleteImageList();
2532 imgEnabled.Detach();
2533 imgDisabled.Detach();
2535 // resize the rebar.
2536 REBARBANDINFO rbbi = { sizeof REBARBANDINFO };
2537 rbbi.fMask = RBBIM_CHILDSIZE;
2538 rbbi.cyMinChild = sizeButton.cy;
2539 m_wndReBar.GetReBarCtrl().SetBandInfo(0, &rbbi);
2544 * @brief Load a transparent 32-bit color image list.
2546 static void LoadHiColImageList(UINT nIDResource, int nWidth, int nHeight, int nNewWidth, int nNewHeight, int nCount, bool bGrayscale, CImageList& ImgList)
2549 bm.Attach(LoadBitmapAndConvertTo32bit(AfxGetInstanceHandle(), nIDResource, nNewWidth * nCount, nNewHeight, bGrayscale, RGB(0xff, 0, 0xff)));
2551 VERIFY(ImgList.Create(nNewWidth, nNewHeight, ILC_COLOR32, nCount, 0));
2552 VERIFY(-1 != ImgList.Add(&bm, nullptr));
2556 * @brief Load toolbar image list.
2558 static void LoadToolbarImageList(int orgImageWidth, int newImageWidth, UINT nIDResource, bool bGrayscale, CImageList& ImgList)
2560 const int ImageCount = 26;
2561 const int orgImageHeight = orgImageWidth - 1;
2562 const int newImageHeight = newImageWidth - 1;
2563 LoadHiColImageList(nIDResource, orgImageWidth, orgImageHeight, newImageWidth, newImageHeight, ImageCount, bGrayscale, ImgList);
2567 * @brief Called when the document title is modified.
2569 void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
2571 CFrameWnd::OnUpdateFrameTitle(bAddToTitle);
2573 if (m_wndTabBar.m_hWnd != nullptr)
2574 m_wndTabBar.UpdateTabs();
2577 /** @brief Show none/small/big/huge toolbar. */
2578 void CMainFrame::OnToolbarSize(UINT id)
2580 if (id == ID_TOOLBAR_NONE)
2582 GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, false);
2583 __super::ShowControlBar(&m_wndToolBar, false, 0);
2587 GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, true);
2588 GetOptionsMgr()->SaveOption(OPT_TOOLBAR_SIZE, id - ID_TOOLBAR_SMALL);
2590 LoadToolbarImages();
2592 __super::ShowControlBar(&m_wndToolBar, true, 0);
2596 /** @brief Show none/small/big/huge toolbar. */
2597 void CMainFrame::OnUpdateToolbarSize(CCmdUI *pCmdUI)
2599 if (!GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR))
2600 pCmdUI->SetRadio(pCmdUI->m_nID == ID_TOOLBAR_NONE);
2602 pCmdUI->SetRadio((pCmdUI->m_nID - ID_TOOLBAR_SMALL) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE)));
2605 /** @brief Lang aware version of CFrameWnd::OnToolTipText() */
2606 BOOL CMainFrame::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
2608 ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);
2610 // need to handle both ANSI and UNICODE versions of the message
2611 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
2612 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
2615 UINT_PTR nID = pNMHDR->idFrom;
2616 if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
2617 pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
2619 // idFrom is actually the HWND of the tool
2620 nID = ::GetDlgCtrlID((HWND)nID);
2623 if (nID != 0) // will be zero on a separator
2625 strFullText = theApp.LoadString(static_cast<UINT>(nID));
2626 // don't handle the message if no string resource found
2627 if (strFullText.empty())
2630 // this is the command id, not the button index
2631 AfxExtractSubString(strTipText, strFullText.c_str(), 1, '\n');
2633 if (pNMHDR->code == TTN_NEEDTEXTA)
2634 _wcstombsz(pTTTA->szText, strTipText, static_cast<ULONG>(std::size(pTTTA->szText)));
2636 lstrcpyn(pTTTW->szText, strTipText, static_cast<int>(std::size(pTTTW->szText)));
2639 // bring the tooltip window above other popup windows
2640 ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
2641 SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
2643 return TRUE; // message was handled
2647 * @brief Ask user for close confirmation when closing the mainframe.
2648 * This function asks if user wants to close multiple open windows when user
2649 * selects (perhaps accidentally) to close WinMerge (application).
2650 * @return true if user agreeds to close all windows.
2652 bool CMainFrame::AskCloseConfirmation()
2654 const DirDocList &dirdocs = GetAllDirDocs();
2655 const MergeDocList &mergedocs = GetAllMergeDocs();
2658 const size_t count = dirdocs.GetCount() + mergedocs.GetCount();
2661 // Check that we don't have one empty dirdoc + mergedoc situation.
2662 // That happens since we open "hidden" dirdoc for every file compare.
2663 if (dirdocs.GetCount() == 1)
2665 CDirDoc *pDoc = dirdocs.GetHead();
2666 if (!pDoc->HasDiffs())
2669 ret = LangMessageBox(IDS_CLOSEALL_WINDOWS, MB_YESNO | MB_ICONWARNING);
2671 return (ret == IDYES);
2675 * @brief Shows the release notes for user.
2676 * This function opens release notes HTML document into browser.
2678 void CMainFrame::OnHelpReleasenotes()
2680 String sPath = paths::ConcatPath(env::GetProgPath(),strutils::format(RelNotes, theApp.GetLangName()));
2681 if (paths::DoesPathExist(sPath) != paths::IS_EXISTING_FILE)
2682 sPath = paths::ConcatPath(env::GetProgPath(), strutils::format(RelNotes, _T("")));
2683 shell::Open(sPath.c_str());
2687 * @brief Shows the translations page.
2688 * This function opens translations page URL into browser.
2690 void CMainFrame::OnHelpTranslations()
2692 shell::Open(TranslationsUrl);
2695 void CMainFrame::OnHelpCheckForUpdates()
2697 CVersionInfo version(AfxGetResourceHandle());
2698 CInternetSession session;
2701 CHttpFile *file = (CHttpFile *)session.OpenURL(GetOptionsMgr()->GetString(OPT_CURRENT_VERSION_URL).c_str());
2704 char buf[256] = { 0 };
2705 file->Read(buf, sizeof(buf));
2707 String current_version = ucr::toTString(buf);
2708 strutils::replace(current_version, _T("\r\n"), _T(""));
2711 int exe_vers[4] = { 0 }, cur_vers[4] = { 0 };
2712 _stscanf_s(version.GetProductVersion().c_str(), _T("%d.%d.%d.%d"), &exe_vers[0], &exe_vers[1], &exe_vers[2], &exe_vers[3]);
2713 _stscanf_s(current_version.c_str(), _T("%d.%d.%d.%d"), &cur_vers[0], &cur_vers[1], &cur_vers[2], &cur_vers[3]);
2714 String exe_version_hex = strutils::format(_T("%08x%08x%08x%08x"), exe_vers[0], exe_vers[1], exe_vers[2], exe_vers[3]);
2715 String cur_version_hex = strutils::format(_T("%08x%08x%08x%08x"), cur_vers[0], cur_vers[1], cur_vers[2], cur_vers[3]);
2717 switch (exe_version_hex.compare(cur_version_hex))
2720 if (cur_version_hex == _T("00000000000000000000000000000000"))
2722 String msg = _("Failed to download latest version information");
2723 AfxMessageBox(msg.c_str(), MB_ICONERROR);
2729 String msg = _("Your software is up to date.");
2730 AfxMessageBox(msg.c_str(), MB_ICONINFORMATION);
2735 String msg = strutils::format_string2(_("A new version of WinMerge is available.\n%1 is now available (you have %2). Would you like to download it now?"), current_version, version.GetProductVersion());
2736 if (AfxMessageBox(msg.c_str(), MB_ICONINFORMATION | MB_YESNO) == IDYES)
2737 ShellExecute(NULL, _T("open"), GetOptionsMgr()->GetString(OPT_DOWNLOAD_URL).c_str(), NULL, NULL, SW_SHOWNORMAL);
2742 catch (CException& e)
2745 e.GetErrorMessage(msg, sizeof(msg)/sizeof(msg[0]));
2746 AfxMessageBox(msg, MB_ICONERROR);
2750 void CMainFrame::OnUpdateHelpCheckForUpdates(CCmdUI* pCmdUI)
2752 pCmdUI->Enable(!GetOptionsMgr()->GetString(OPT_CURRENT_VERSION_URL).empty());
2756 * @brief Called when user selects File/Open Conflict...
2758 void CMainFrame::OnFileOpenConflict()
2760 String conflictFile;
2761 if (SelectFile(GetSafeHwnd(), conflictFile))
2763 DoOpenConflict(conflictFile);
2768 * @brief Called when user selects File/Open Clipboard
2770 void CMainFrame::OnFileOpenClipboard()
2775 bool CMainFrame::DoOpenClipboard(UINT nID, int nBuffers /*= 2*/, const fileopenflags_t dwFlags[] /*= nullptr*/,
2776 const String strDesc[] /*= nullptr*/, const PackingInfo* infoUnpacker /*= nullptr*/,
2777 const PrediffingInfo* infoPrediffer /*= nullptr*/, const OpenFileParams* pOpenParams /*= nullptr*/)
2779 auto historyItems = ClipboardHistory::GetItems(nBuffers);
2782 fileopenflags_t dwFlags2[3];
2783 for (int i = 0; i < nBuffers; ++i)
2785 int64_t t = historyItems[nBuffers - i - 1].timestamp;
2786 String timestr = t == 0 ? _T("---") : locality::TimeString(&t);
2787 strDesc2[i] = (strDesc && !strDesc[i].empty()) ?
2788 strDesc[i] : strutils::format(_("Clipboard at %s"), timestr);
2789 dwFlags2[i] = (dwFlags ? dwFlags[i] : 0) | FFILEOPEN_NOMRU;
2791 for (int i = 0; i < 2; ++i)
2793 PathContext tmpPathContext;
2794 for (int pane = 0; pane < nBuffers; ++pane)
2796 auto item = historyItems[nBuffers - pane - 1];
2797 if (i == 0 && item.pBitmapTempFile)
2799 tmpPathContext.SetPath(pane, item.pBitmapTempFile->GetPath());
2800 m_tempFiles.push_back(item.pBitmapTempFile);
2802 if (i == 1 && item.pTextTempFile)
2804 tmpPathContext.SetPath(pane, item.pTextTempFile->GetPath());
2805 m_tempFiles.push_back(item.pTextTempFile);
2808 if (tmpPathContext.GetSize() == nBuffers)
2809 DoFileOpen(nID, &tmpPathContext, dwFlags2, strDesc2, _T(""), infoUnpacker, infoPrediffer, pOpenParams);
2815 * @brief Select and open conflict file for resolving.
2816 * This function lets user to select conflict file to resolve.
2817 * Then we parse conflict file to two files to "merge" and
2818 * save resulting file over original file.
2820 * Set left-side file read-only as it is the repository file which cannot
2821 * be modified anyway. Right-side file is user's file which is set as
2822 * modified by default so user can just save it and accept workspace
2823 * file as resolved file.
2824 * @param [in] conflictFile Full path to conflict file to open.
2825 * @param [in] checked If true, do not check if it really is project file.
2826 * @return `true` if conflict file was opened for resolving.
2828 bool CMainFrame::DoOpenConflict(const String& conflictFile, const String strDesc[] /*= nullptr*/, bool checked /*= false*/)
2830 bool conflictCompared = false;
2834 bool confFile = ConflictFileParser::IsConflictFile(conflictFile);
2837 String message = strutils::format_string1(_("The file\n%1\nis not a conflict file."), conflictFile);
2838 AfxMessageBox(message.c_str(), MB_ICONSTOP);
2843 // Create temp files and put them into the list,
2844 // from where they get deleted when MainFrame is deleted.
2845 String ext = paths::FindExtension(conflictFile);
2846 auto wTemp = std::make_shared<TempFile>(TempFile());
2847 String workFile = wTemp->Create(_T("confw_"), ext);
2848 m_tempFiles.push_back(wTemp);
2849 auto vTemp = std::make_shared<TempFile>(TempFile());
2850 String revFile = vTemp->Create(_T("confv_"), ext);
2851 m_tempFiles.push_back(vTemp);
2852 auto bTemp = std::make_shared<TempFile>(TempFile());
2853 String baseFile = vTemp->Create(_T("confb_"), ext);
2854 m_tempFiles.push_back(bTemp);
2856 // Parse conflict file into two files.
2857 bool inners, threeWay;
2858 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
2859 bool success = ConflictFileParser::ParseConflictFile(conflictFile, workFile, revFile, baseFile, iGuessEncodingType, inners, threeWay);
2863 // Open two parsed files to WinMerge, telling WinMerge to
2864 // save over original file (given as third filename).
2865 OpenTextFileParams openParams;
2866 openParams.m_strSaveAsPath = conflictFile;
2869 String strDesc2[2] = {
2870 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Theirs File"),
2871 (strDesc && !strDesc[2].empty()) ? strDesc[2] : _("Mine File") };
2872 fileopenflags_t dwFlags[2] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU | FFILEOPEN_MODIFIED};
2873 PathContext tmpPathContext(revFile, workFile);
2874 conflictCompared = DoFileOrFolderOpen(&tmpPathContext, dwFlags, strDesc2, L"", false, nullptr, nullptr, nullptr, 0, &openParams);
2878 String strDesc3[3] = {
2879 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Base File"),
2880 (strDesc && !strDesc[1].empty()) ? strDesc[1] : _("Theirs File"),
2881 (strDesc && !strDesc[2].empty()) ? strDesc[2] : _("Mine File") };
2882 PathContext tmpPathContext(baseFile, revFile, workFile);
2883 fileopenflags_t dwFlags[3] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU | FFILEOPEN_MODIFIED};
2884 conflictCompared = DoFileOrFolderOpen(&tmpPathContext, dwFlags, strDesc3, L"", false, nullptr, nullptr, nullptr, 0, &openParams);
2889 LangMessageBox(IDS_ERROR_CONF_RESOLVE, MB_ICONSTOP);
2891 return conflictCompared;
2894 bool CMainFrame::DoSelfCompare(UINT nID, const String& file, const String strDesc[] /*= nullptr*/,
2895 const PackingInfo *infoUnpacker /*= nullptr*/, const PrediffingInfo *infoPrediffer /*= nullptr*/,
2896 const OpenFileParams *pOpenParams /*= nullptr*/)
2898 String ext = paths::FindExtension(file);
2899 auto wTemp = std::make_shared<TempFile>(TempFile());
2901 if (paths::IsURL(file))
2905 PackingInfo infoUnpacker2 = infoUnpacker ? *infoUnpacker : PackingInfo{};
2906 if (!infoUnpacker2.Unpacking(nullptr, copiedFile, copiedFile, { copiedFile }))
2908 String sError = strutils::format_string1(_("File not unpacked: %1"), file);
2909 AfxMessageBox(sError.c_str(), MB_OK | MB_ICONSTOP | MB_MODELESS);
2912 wTemp->Attach(copiedFile);
2916 copiedFile = wTemp->Create(_T("self-compare_"), ext);
2919 TFile(file).copyTo(copiedFile);
2921 catch (Poco::FileException& e)
2924 LogErrorStringUTF8(e.displayText());
2927 m_tempFiles.push_back(wTemp);
2929 String strDesc2[2] = {
2930 (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Original File"),
2931 (strDesc && !strDesc[1].empty()) ? strDesc[1] : _("") };
2932 fileopenflags_t dwFlags[2] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU};
2933 PathContext tmpPathContext(copiedFile, file);
2934 return DoFileOpen(nID, &tmpPathContext, dwFlags, strDesc2, _T(""), infoUnpacker, infoPrediffer, pOpenParams);
2938 * @brief Get type of frame (File/Folder compare).
2939 * @param [in] pFrame Pointer to frame to check.
2940 * @return FRAMETYPE of the given frame.
2942 CMainFrame::FRAMETYPE CMainFrame::GetFrameType(const CFrameWnd * pFrame)
2944 bool bMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame));
2945 bool bHexMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame));
2946 bool bImgMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame));
2947 bool bWebPageDiffFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CWebPageDiffFrame));
2948 bool bDirFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame));
2952 else if (bHexMergeFrame)
2953 return FRAME_HEXFILE;
2954 else if (bImgMergeFrame)
2955 return FRAME_IMGFILE;
2956 else if (bWebPageDiffFrame)
2957 return FRAME_WEBPAGE;
2959 return FRAME_FOLDER;
2965 * @brief Show the plugins list dialog.
2967 void CMainFrame::OnPluginsList()
2973 void CMainFrame::OnToolbarButtonDropDown(NMHDR* pNMHDR, LRESULT* pResult)
2975 LPNMTOOLBAR pToolBar = reinterpret_cast<LPNMTOOLBAR>(pNMHDR);
2976 ClientToScreen(&(pToolBar->rcButton));
2979 switch (pToolBar->iItem)
2985 id = IDR_POPUP_OPEN;
2988 id = IDR_POPUP_SAVE;
2991 id = IDR_POPUP_DIFF_OPTIONS;
2994 VERIFY(menu.LoadMenu(id));
2995 theApp.TranslateMenu(menu.m_hMenu);
2996 CMenu* pPopup = menu.GetSubMenu(0);
2997 if (pPopup != nullptr)
2999 pPopup->TrackPopupMenu(TPM_RIGHTALIGN | TPM_RIGHTBUTTON,
3000 pToolBar->rcButton.right, pToolBar->rcButton.bottom, this);
3005 void CMainFrame::OnDiffWhitespace(UINT nID)
3007 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, nID - ID_DIFF_OPTIONS_WHITESPACE_COMPARE);
3011 void CMainFrame::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
3013 pCmdUI->SetRadio((pCmdUI->m_nID - ID_DIFF_OPTIONS_WHITESPACE_COMPARE) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE)));
3017 void CMainFrame::OnDiffIgnoreBlankLines()
3019 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES));
3023 void CMainFrame::OnUpdateDiffIgnoreBlankLines(CCmdUI* pCmdUI)
3025 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES));
3029 void CMainFrame::OnDiffIgnoreCase()
3031 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE));
3035 void CMainFrame::OnUpdateDiffIgnoreCase(CCmdUI* pCmdUI)
3037 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE));
3041 void CMainFrame::OnDiffIgnoreNumbers()
3043 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_NUMBERS, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS));
3047 void CMainFrame::OnUpdateDiffIgnoreNumbers(CCmdUI* pCmdUI)
3049 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS));
3053 void CMainFrame::OnDiffIgnoreEOL()
3055 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL));
3059 void CMainFrame::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
3061 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL));
3065 void CMainFrame::OnDiffIgnoreCP()
3067 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE));
3071 void CMainFrame::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
3073 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE));
3077 void CMainFrame::OnDiffIgnoreComments()
3079 GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, !GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES));
3083 void CMainFrame::OnUpdateDiffIgnoreComments(CCmdUI* pCmdUI)
3085 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES));
3089 void CMainFrame::OnIncludeSubfolders()
3091 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, !GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS));
3092 // Update all dirdoc settings
3093 for (auto pDirDoc : GetAllDirDocs())
3094 pDirDoc->RefreshOptions();
3095 for (auto pOpenDoc : GetAllOpenDocs())
3096 pOpenDoc->RefreshOptions();
3099 void CMainFrame::OnUpdateIncludeSubfolders(CCmdUI* pCmdUI)
3101 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS));
3105 void CMainFrame::OnCompareMethod(UINT nID)
3107 GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, nID - ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS);
3108 for (auto pOpenDoc : GetAllOpenDocs())
3109 pOpenDoc->RefreshOptions();
3112 void CMainFrame::OnUpdateCompareMethod(CCmdUI* pCmdUI)
3114 pCmdUI->SetRadio((pCmdUI->m_nID - ID_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(GetOptionsMgr()->GetInt(OPT_CMP_METHOD)));
3118 void CMainFrame::OnMRUs(UINT nID)
3120 std::vector<JumpList::Item> mrus = JumpList::GetRecentDocs(GetOptionsMgr()->GetInt(OPT_MRU_MAX));
3121 const size_t idx = static_cast<size_t>(nID) - ID_MRU_FIRST;
3122 if (idx < mrus.size())
3124 MergeCmdLineInfo cmdInfo((_T("\"") + mrus[idx].path + _T("\" ") + mrus[idx].params).c_str());
3125 theApp.ParseArgsAndDoOpen(cmdInfo, this);
3129 void CMainFrame::OnUpdateNoMRUs(CCmdUI* pCmdUI)
3131 // append the MRU submenu
3132 CMenu *pMenu = pCmdUI->m_pSubMenu ? pCmdUI->m_pSubMenu : pCmdUI->m_pMenu;
3133 if (pMenu == nullptr)
3135 HMENU hMenu = pMenu->m_hMenu;
3138 size_t i = ::GetMenuItemCount(hMenu);
3140 ::DeleteMenu(hMenu, 0, MF_BYPOSITION);
3142 std::vector<JumpList::Item> mrus = JumpList::GetRecentDocs(GetOptionsMgr()->GetInt(OPT_MRU_MAX));
3144 if (mrus.size() == 0)
3146 // no script : create a <empty> entry
3147 ::AppendMenu(hMenu, MF_STRING, ID_NO_MRU, theApp.LoadString(IDS_NO_EDIT_SCRIPTS).c_str());
3151 // or fill in the submenu with the scripts names
3152 int ID = ID_MRU_FIRST; // first ID in menu
3153 for (i = 0 ; i < mrus.size() ; i++, ID++)
3154 ::AppendMenu(hMenu, MF_STRING, ID,
3156 strutils::format(_T("&%d %.128s"), i+1, mrus[i].title) :
3157 strutils::format(_T("&%c %.128s"), 'a' + i - 9, mrus[i].title))
3161 pCmdUI->Enable(true);
3165 * @brief Update plugin name
3166 * @param [in] pCmdUI UI component to update.
3168 void CMainFrame::OnUpdatePluginName(CCmdUI* pCmdUI)
3170 if (auto pMergeDoc = GetActiveIMergeDoc())
3173 const PackingInfo* infoUnpacker = pMergeDoc->GetUnpacker();
3174 if (infoUnpacker && !infoUnpacker->GetPluginPipeline().empty())
3175 pluginNames += infoUnpacker->GetPluginPipeline() + _T("&");
3176 const PrediffingInfo* infoPrediffer = pMergeDoc->GetPrediffer();
3177 if (infoPrediffer && !infoPrediffer->GetPluginPipeline().empty())
3178 pluginNames += infoPrediffer->GetPluginPipeline() + _T("&");
3179 pCmdUI->SetText(pluginNames.substr(0, pluginNames.length() - 1).c_str());
3182 pCmdUI->SetText(_T(""));
3186 * @brief Called to update the item count in the status bar
3188 void CMainFrame::OnUpdateStatusNum(CCmdUI* pCmdUI)
3190 pCmdUI->SetText(_T(""));
3194 * @brief Move to next file
3196 void CMainFrame::OnNextFile()
3198 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
3199 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
3200 pDirDoc->MoveToNextFile(pMergeDoc);
3204 * @brief Called when Move to next file is updated
3206 void CMainFrame::OnUpdateNextFile(CCmdUI* pCmdUI)
3208 bool enabled = false;
3209 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
3210 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
3211 enabled = !pDirDoc->IsLastFile();
3212 pCmdUI->Enable(enabled);
3216 * @brief Move to previous file
3218 void CMainFrame::OnPrevFile()
3220 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
3221 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
3222 pDirDoc->MoveToPrevFile(pMergeDoc);
3226 * @brief Called when Move to previous file is updated
3228 void CMainFrame::OnUpdatePrevFile(CCmdUI* pCmdUI)
3230 bool enabled = false;
3231 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
3232 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
3233 enabled = !pDirDoc->IsFirstFile();
3234 pCmdUI->Enable(enabled);
3238 * @brief Move to first file
3240 void CMainFrame::OnFirstFile()
3242 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
3243 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
3244 pDirDoc->MoveToFirstFile(pMergeDoc);
3248 * @brief Called when Move to first file is updated
3250 void CMainFrame::OnUpdateFirstFile(CCmdUI* pCmdUI)
3252 bool enabled = false;
3253 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
3254 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
3255 enabled = !pDirDoc->IsFirstFile();
3256 pCmdUI->Enable(enabled);
3260 * @brief Move to last file
3262 void CMainFrame::OnLastFile()
3264 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
3265 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
3266 pDirDoc->MoveToLastFile(pMergeDoc);
3270 * @brief Called when Move to last file item is updated
3272 void CMainFrame::OnUpdateLastFile(CCmdUI* pCmdUI)
3274 bool enabled = false;
3275 if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
3276 if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
3277 enabled = !pDirDoc->IsLastFile();
3278 pCmdUI->Enable(enabled);
3281 void CMainFrame::ReloadMenu()
3283 // set the menu of the main frame window
3284 UINT idMenu = IDR_MAINFRAME;
3285 CMergeApp *pApp = dynamic_cast<CMergeApp *> (AfxGetApp());
3286 CMainFrame * pMainFrame = dynamic_cast<CMainFrame *> ((CFrameWnd*)pApp->m_pMainWnd);
3287 HMENU hNewDefaultMenu = pMainFrame->NewDefaultMenu(idMenu);
3288 HMENU hNewMergeMenu = pMainFrame->NewMergeViewMenu();
3289 HMENU hNewImgMergeMenu = pMainFrame->NewImgMergeViewMenu();
3290 HMENU hNewWebPageDiffMenu = pMainFrame->NewWebPageDiffViewMenu();
3291 HMENU hNewDirMenu = pMainFrame->NewDirViewMenu();
3292 if (hNewDefaultMenu != nullptr && hNewMergeMenu != nullptr && hNewDirMenu != nullptr)
3294 // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
3295 CMenu * pNewDefaultMenu = CMenu::FromHandle(hNewDefaultMenu);
3296 CMenu * pNewMergeMenu = CMenu::FromHandle(hNewMergeMenu);
3297 CMenu * pNewImgMergeMenu = CMenu::FromHandle(hNewImgMergeMenu);
3298 CMenu * pNewWebPageDiffMenu = CMenu::FromHandle(hNewWebPageDiffMenu);
3299 CMenu * pNewDirMenu = CMenu::FromHandle(hNewDirMenu);
3301 CWnd *pFrame = CWnd::FromHandle(::GetWindow(pMainFrame->m_hWndMDIClient, GW_CHILD));
3302 while (pFrame != nullptr)
3304 if (pFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame)))
3305 static_cast<CMergeEditFrame *>(pFrame)->SetSharedMenu(hNewMergeMenu);
3306 if (pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame)))
3307 static_cast<CHexMergeFrame *>(pFrame)->SetSharedMenu(hNewMergeMenu);
3308 if (pFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame)))
3309 static_cast<CImgMergeFrame *>(pFrame)->SetSharedMenu(hNewImgMergeMenu);
3310 if (pFrame->IsKindOf(RUNTIME_CLASS(CWebPageDiffFrame)))
3311 static_cast<CWebPageDiffFrame *>(pFrame)->SetSharedMenu(hNewWebPageDiffMenu);
3312 else if (pFrame->IsKindOf(RUNTIME_CLASS(COpenFrame)))
3313 static_cast<COpenFrame *>(pFrame)->SetSharedMenu(hNewDefaultMenu);
3314 else if (pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
3315 static_cast<CDirFrame *>(pFrame)->SetSharedMenu(hNewDirMenu);
3316 pFrame = pFrame->GetNextWindow();
3319 CFrameWnd *pActiveFrame = pMainFrame->GetActiveFrame();
3320 if (pActiveFrame != nullptr)
3322 if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame)))
3323 pMainFrame->MDISetMenu(pNewMergeMenu, nullptr);
3324 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame)))
3325 pMainFrame->MDISetMenu(pNewMergeMenu, nullptr);
3326 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame)))
3327 pMainFrame->MDISetMenu(pNewImgMergeMenu, nullptr);
3328 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CWebPageDiffFrame)))
3329 pMainFrame->MDISetMenu(pNewWebPageDiffMenu, nullptr);
3330 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
3331 pMainFrame->MDISetMenu(pNewDirMenu, nullptr);
3333 pMainFrame->MDISetMenu(pNewDefaultMenu, nullptr);
3336 pMainFrame->MDISetMenu(pNewDefaultMenu, nullptr);
3338 // Don't delete the old menu
3339 // There is a bug in BCMenu or in Windows98 : the new menu does not
3340 // appear correctly if we destroy the old one
3341 // if (pOldDefaultMenu != nullptr)
3342 // pOldDefaultMenu->DestroyMenu();
3343 // if (pOldMergeMenu != nullptr)
3344 // pOldMergeMenu->DestroyMenu();
3345 // if (pOldDirMenu = nullptr)
3346 // pOldDirMenu->DestroyMenu();
3348 // m_hMenuDefault is used to redraw the main menu when we close a child frame
3349 // if this child frame had a different menu
3350 pMainFrame->m_hMenuDefault = hNewDefaultMenu;
3351 pApp->GetOpenTemplate()->m_hMenuShared = hNewDefaultMenu;
3352 pApp->GetDiffTemplate()->m_hMenuShared = hNewMergeMenu;
3353 pApp->GetDirTemplate()->m_hMenuShared = hNewDirMenu;
3355 // force redrawing the menu bar
3356 pMainFrame->DrawMenuBar();
3360 void CMainFrame::AppendPluginMenus(CMenu *pMenu, const String& filteredFilenames,
3361 const std::vector<std::wstring>& events, bool addAllMenu, unsigned baseId)
3363 if (!GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
3366 CWaitCursor waitstatus;
3368 auto [suggestedPlugins, allPlugins] = FileTransform::CreatePluginMenuInfos(filteredFilenames, events, baseId);
3372 pMenu->AppendMenu(MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
3376 pMenu->AppendMenu(MF_SEPARATOR);
3379 for (const auto& [caption, name, id, plugin] : suggestedPlugins)
3380 pMenu->AppendMenu(MF_STRING, id, caption.c_str());
3382 CMenu* pMenu2 = pMenu;
3386 popupAll.CreatePopupMenu();
3387 pMenu->AppendMenu(MF_POPUP, reinterpret_cast<UINT_PTR>(popupAll.m_hMenu), _("Al&l").c_str());
3392 pMenu->AppendMenu(MF_SEPARATOR, 0);
3393 pMenu->AppendMenu(MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("All plugins").c_str());
3396 std::list<String> processTypes;
3397 for (const auto& [processType, pluginList] : allPlugins)
3398 processTypes.push_back(processType);
3399 auto it = std::find(processTypes.begin(), processTypes.end(), _("&Others"));
3400 if (it != processTypes.end())
3402 processTypes.erase(it);
3403 processTypes.push_back(_("&Others"));
3406 for (const auto& processType : processTypes)
3409 popup.CreatePopupMenu();
3410 if (processType.empty())
3412 for (const auto& [caption, name, id, plugin] : allPlugins[processType])
3413 pMenu2->AppendMenu(MF_STRING, id, caption.c_str());
3417 for (const auto& [caption, name, id, plugin] : allPlugins[processType])
3418 popup.AppendMenu(MF_STRING, id, caption.c_str());
3419 pMenu2->AppendMenu(MF_POPUP, reinterpret_cast<UINT_PTR>(popup.m_hMenu), processType.c_str());
3426 if (baseId == ID_UNPACKERS_FIRST)
3427 pMenu2->AppendMenu(MF_STRING, ID_OPEN_WITH_UNPACKER, _("&Select...").c_str());
3428 else if (baseId == ID_PREDIFFERS_FIRST)
3429 pMenu2->AppendMenu(MF_STRING, ID_APPLY_PREDIFFER, _("&Select...").c_str());
3434 String CMainFrame::GetPluginPipelineByMenuId(unsigned idSearch, const std::vector<std::wstring>& events, unsigned baseId)
3436 PluginInfo* pluginFound = nullptr;
3438 [[maybe_unused]] auto [suggestedPlugins, allPlugins] = FileTransform::CreatePluginMenuInfos(_T(""), events, baseId);
3439 for (const auto& [processType, pluginList] : allPlugins)
3441 for (const auto& [caption, name, id, plugin] : pluginList)
3446 pluginFound = plugin;
3453 if (!pluginFound->GetExtendedPropertyValue(_T("ArgumentsRequired")).has_value() &&
3454 !pluginFound->GetExtendedPropertyValue(pluginName + _T(".ArgumentsRequired")).has_value())
3456 CSelectPluginDlg dlg(pluginName, _T(""),
3457 (baseId == ID_UNPACKERS_FIRST) ? CSelectPluginDlg::PluginType::Unpacker : (
3458 (baseId == ID_PREDIFFERS_FIRST) ? CSelectPluginDlg::PluginType::Prediffer :
3459 CSelectPluginDlg::PluginType::EditorScript), true);
3460 if (dlg.DoModal() != IDOK)
3462 return dlg.GetPluginPipeline();
3467 IMergeDoc* CMainFrame::GetActiveIMergeDoc()
3469 CFrameWnd* pFrame = GetActiveFrame();
3472 IMergeDoc* pMergeDoc = dynamic_cast<IMergeDoc*>(pFrame->GetActiveDocument());
3474 pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
3478 void CMainFrame::WatchDocuments(IMergeDoc* pMergeDoc)
3480 const int reloadType = GetOptionsMgr()->GetInt(OPT_AUTO_RELOAD_MODIFIED_FILES);
3481 const int nFiles = pMergeDoc->GetFileCount();
3482 for (int pane = 0; pane < nFiles; ++pane)
3484 const String path = pMergeDoc->GetPath(pane);
3487 if (reloadType == AUTO_RELOAD_MODIFIED_FILES_IMMEDIATELY)
3489 m_pDirWatcher->Add(reinterpret_cast<uintptr_t>(pMergeDoc) + pane,
3491 pMergeDoc->GetPath(pane),
3492 [this, pMergeDoc](const String& path, DirWatcher::ACTION action)
3494 PostMessage(WM_USER + 1, reinterpret_cast<WPARAM>(pMergeDoc));
3499 m_pDirWatcher->Remove(reinterpret_cast<uintptr_t>(pMergeDoc) + pane);
3505 void CMainFrame::UnwatchDocuments(IMergeDoc* pMergeDoc)
3507 const int nFiles = pMergeDoc->GetFileCount();
3508 for (int pane = 0; pane < nFiles; ++pane)
3509 m_pDirWatcher->Remove(reinterpret_cast<uintptr_t>(pMergeDoc) + pane);
3512 void CMainFrame::WaitAndDoMessageLoop(bool& completed, int ms)
3517 while (::PeekMessage(&msg, nullptr, NULL, NULL, PM_NOREMOVE))
3519 if (!AfxGetApp()->PumpMessage())
3526 void CMainFrame::UpdateDocTitle()
3528 CDocManager* pDocManager = AfxGetApp()->m_pDocManager;
3529 POSITION posTemplate = pDocManager->GetFirstDocTemplatePosition();
3530 ASSERT(posTemplate != nullptr);
3532 while (posTemplate != nullptr)
3534 CDocTemplate* pTemplate = pDocManager->GetNextDocTemplate(posTemplate);
3536 ASSERT(pTemplate != nullptr);
3538 for (auto pDoc : GetDocList(static_cast<CMultiDocTemplate *>(pTemplate)))
3540 static_cast<CDocument *>(const_cast<void *>(pDoc))->SetTitle(nullptr);
3541 ((CFrameWnd*)AfxGetApp()->m_pMainWnd)->OnUpdateFrameTitle(TRUE);
3546 void CMainFrame::OnAccelQuit()
3548 // TODO: Add your command handler code here
3550 SendMessage(WM_CLOSE);
3553 LRESULT CMainFrame::OnChildFrameAdded(WPARAM wParam, LPARAM lParam)
3555 for (int i = 0; i < m_arrChild.GetSize(); ++i)
3557 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3563 m_arrChild.InsertAt(0, (CMDIChildWnd*)lParam);
3568 LRESULT CMainFrame::OnChildFrameRemoved(WPARAM wParam, LPARAM lParam)
3570 for (int i = 0; i < m_arrChild.GetSize(); ++i)
3572 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3574 m_arrChild.RemoveAt(i);
3582 LRESULT CMainFrame::OnChildFrameActivate(WPARAM wParam, LPARAM lParam)
3584 for (int i = 0; i < m_arrChild.GetSize(); ++i)
3586 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3588 CMDIChildWnd* pMDIChild = m_arrChild.GetAt(i);
3589 if (pMDIChild->IsIconic())
3590 pMDIChild->ShowWindow(SW_RESTORE);
3591 MDIActivate(pMDIChild);
3598 // put lParam as index 0 in m_arrChild
3599 LRESULT CMainFrame::OnChildFrameActivated(WPARAM wParam, LPARAM lParam)
3601 for (int i = 0; i < m_arrChild.GetSize(); ++i)
3603 if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
3605 m_arrChild.RemoveAt(i);
3610 m_arrChild.InsertAt(0, (CMDIChildWnd*)lParam);