1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 /////////////////////////////////////////////////////////////////////////////
24 * @brief Main implementation file for CDirView
29 #include "Constants.h"
31 #include "ClipBoard.h"
32 #include "DirActions.h"
33 #include "DirViewColItems.h"
34 #include "DirFrame.h" // StatePane
36 #include "IMergeDoc.h"
37 #include "FileLocation.h"
40 #include "FileTransform.h"
41 #include "SelectUnpackerDlg.h"
44 #include "OptionsDef.h"
45 #include "OptionsMgr.h"
47 #include "DirCmpReportDlg.h"
48 #include "DirCmpReport.h"
49 #include "DirCompProgressBar.h"
50 #include "CompareStatisticsDlg.h"
51 #include "LoadSaveCodepageDlg.h"
52 #include "ConfirmFolderCopyDlg.h"
53 #include "DirColsDlg.h"
54 #include "DirSelectFilesDlg.h"
56 #include "ShellContextMenu.h"
58 #include "IListCtrlImpl.h"
59 #include "Merge7zFormatMergePluginImpl.h"
60 #include "FileOrFolderSelect.h"
61 #include "IntToIntMap.h"
62 #include "PatchTool.h"
63 #include "SyntaxColors.h"
72 using namespace std::placeholders;
75 * @brief Location for folder compare specific help to open.
77 static TCHAR DirViewHelpLocation[] = _T("::/htmlhelp/Compare_dirs.html");
80 * @brief Limit (in seconds) to signal compare is ready for user.
81 * If compare takes longer than this value (in seconds) we inform
82 * user about it. Current implementation uses MessageBeep(IDOK).
84 const int TimeToSignalCompare = 3;
86 // The resource ID constants/limits for the Shell context menu
87 const UINT LeftCmdFirst = 0x9000; // this should be greater than any of already defined command IDs
88 const UINT RightCmdLast = 0xffff; // maximum available value
89 const UINT LeftCmdLast = LeftCmdFirst + (RightCmdLast - LeftCmdFirst) / 3; // divide available range equally between two context menus
90 const UINT MiddleCmdFirst = LeftCmdLast + 1;
91 const UINT MiddleCmdLast = MiddleCmdFirst + (RightCmdLast - LeftCmdFirst) / 3;
92 const UINT RightCmdFirst = MiddleCmdLast + 1;
94 /////////////////////////////////////////////////////////////////////////////
99 STATUSBAR_UPDATE = 100
102 IMPLEMENT_DYNCREATE(CDirView, CListView)
107 , m_bNeedSearchFirstDiffItem(true)
108 , m_bNeedSearchLastDiffItem(true)
109 , m_firstDiffItem(-1)
111 , m_pCmpProgressBar(nullptr)
114 , m_dirfilter(std::bind(&COptionsMgr::GetBool, GetOptionsMgr(), _1))
115 , m_pShellContextMenuLeft(nullptr)
116 , m_pShellContextMenuMiddle(nullptr)
117 , m_pShellContextMenuRight(nullptr)
118 , m_hCurrentMenu(nullptr)
119 , m_pSavedTreeState(nullptr)
120 , m_pColItems(nullptr)
123 m_dwDefaultStyle &= ~LVS_TYPEMASK;
124 // Show selection all the time, so user can see current item even when
125 // focus is elsewhere (ie, on file edit window)
126 m_dwDefaultStyle |= LVS_REPORT | LVS_SHOWSELALWAYS | LVS_EDITLABELS;
128 m_bTreeMode = GetOptionsMgr()->GetBool(OPT_TREE_MODE);
129 m_bExpandSubdirs = GetOptionsMgr()->GetBool(OPT_DIRVIEW_EXPAND_SUBDIRS);
130 m_nEscCloses = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
131 Options::DirColors::Load(GetOptionsMgr(), m_cachedColors);
132 m_bUseColors = GetOptionsMgr()->GetBool(OPT_DIRCLR_USE_COLORS);
135 CDirView::~CDirView()
139 BEGIN_MESSAGE_MAP(CDirView, CListView)
141 //{{AFX_MSG_MAP(CDirView)
142 ON_WM_LBUTTONDBLCLK()
143 ON_COMMAND_RANGE(ID_L2R, ID_R2L, OnDirCopy)
144 ON_UPDATE_COMMAND_UI_RANGE(ID_L2R, ID_R2L, OnUpdateDirCopy)
145 ON_COMMAND(ID_DIR_COPY_LEFT_TO_RIGHT, (OnCtxtDirCopy<SIDE_LEFT, SIDE_RIGHT>))
146 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_RIGHT, (OnUpdateCtxtDirCopy<SIDE_LEFT, SIDE_RIGHT>))
147 ON_COMMAND(ID_DIR_COPY_LEFT_TO_MIDDLE, (OnCtxtDirCopy<SIDE_LEFT, SIDE_MIDDLE>))
148 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_MIDDLE, (OnUpdateCtxtDirCopy<SIDE_LEFT, SIDE_MIDDLE>))
149 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_LEFT, (OnCtxtDirCopy<SIDE_RIGHT, SIDE_LEFT>))
150 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_LEFT, (OnUpdateCtxtDirCopy<SIDE_RIGHT, SIDE_LEFT>))
151 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_MIDDLE, (OnCtxtDirCopy<SIDE_RIGHT, SIDE_MIDDLE>))
152 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_MIDDLE, (OnUpdateCtxtDirCopy<SIDE_RIGHT, SIDE_MIDDLE>))
153 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_LEFT, (OnCtxtDirCopy<SIDE_MIDDLE, SIDE_LEFT>))
154 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_LEFT, (OnUpdateCtxtDirCopy<SIDE_MIDDLE, SIDE_LEFT>))
155 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_RIGHT, (OnCtxtDirCopy<SIDE_MIDDLE, SIDE_RIGHT>))
156 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_RIGHT, (OnUpdateCtxtDirCopy<SIDE_MIDDLE, SIDE_RIGHT>))
157 ON_COMMAND(ID_DIR_DEL_LEFT, OnCtxtDirDel<SIDE_LEFT>)
158 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_LEFT, OnUpdateCtxtDirDel<SIDE_LEFT>)
159 ON_COMMAND(ID_DIR_DEL_RIGHT, OnCtxtDirDel<SIDE_RIGHT>)
160 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_MIDDLE, OnUpdateCtxtDirDel<SIDE_MIDDLE>)
161 ON_COMMAND(ID_DIR_DEL_MIDDLE, OnCtxtDirDel<SIDE_MIDDLE>)
162 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_RIGHT, OnUpdateCtxtDirDel<SIDE_RIGHT>)
163 ON_COMMAND(ID_DIR_DEL_BOTH, OnCtxtDirDelBoth)
164 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_BOTH, OnUpdateCtxtDirDelBoth)
165 ON_COMMAND(ID_DIR_DEL_ALL, OnCtxtDirDelBoth)
166 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_ALL, OnUpdateCtxtDirDelBoth)
167 ON_COMMAND(ID_DIR_OPEN_LEFT, OnCtxtDirOpen<SIDE_LEFT>)
168 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT, OnUpdateCtxtDirOpen<SIDE_LEFT>)
169 ON_COMMAND(ID_DIR_OPEN_LEFT_WITH, OnCtxtDirOpenWith<SIDE_LEFT>)
170 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_WITH, OnUpdateCtxtDirOpenWith<SIDE_LEFT>)
171 ON_COMMAND(ID_DIR_OPEN_LEFT_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_LEFT>)
172 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_LEFT>)
173 ON_COMMAND(ID_DIR_OPEN_MIDDLE, OnCtxtDirOpen<SIDE_MIDDLE>)
174 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE, OnUpdateCtxtDirOpen<SIDE_MIDDLE>)
175 ON_COMMAND(ID_DIR_OPEN_MIDDLE_WITH, OnCtxtDirOpenWith<SIDE_MIDDLE>)
176 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_WITH, OnUpdateCtxtDirOpenWith<SIDE_MIDDLE>)
177 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_MIDDLE>)
178 ON_COMMAND(ID_DIR_OPEN_MIDDLE_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_MIDDLE>)
179 ON_COMMAND(ID_DIR_OPEN_RIGHT, OnCtxtDirOpen<SIDE_RIGHT>)
180 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT, OnUpdateCtxtDirOpen<SIDE_RIGHT>)
181 ON_COMMAND(ID_DIR_OPEN_RIGHT_WITH, OnCtxtDirOpenWith<SIDE_RIGHT>)
182 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_WITH, OnUpdateCtxtDirOpenWith<SIDE_RIGHT>)
183 ON_COMMAND(ID_DIR_OPEN_RIGHT_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_RIGHT>)
184 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_RIGHT>)
185 ON_COMMAND(ID_POPUP_OPEN_WITH_UNPACKER, OnCtxtOpenWithUnpacker)
186 ON_UPDATE_COMMAND_UI(ID_POPUP_OPEN_WITH_UNPACKER, OnUpdateCtxtOpenWithUnpacker)
187 ON_COMMAND(ID_DIR_OPEN_LEFT_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_LEFT>)
188 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_LEFT>)
189 ON_COMMAND(ID_DIR_OPEN_MIDDLE_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_MIDDLE>)
190 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_MIDDLE>)
191 ON_COMMAND(ID_DIR_OPEN_RIGHT_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_RIGHT>)
192 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_RIGHT>)
193 ON_COMMAND(ID_DIR_COPY_LEFT_TO_BROWSE, OnCtxtDirCopyTo<SIDE_LEFT>)
194 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_BROWSE, OnCtxtDirCopyTo<SIDE_MIDDLE>)
195 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_BROWSE, OnCtxtDirCopyTo<SIDE_RIGHT>)
196 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_LEFT>)
197 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_MIDDLE>)
198 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_RIGHT>)
202 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
203 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
204 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
205 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
206 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
207 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
208 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
209 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
210 ON_COMMAND(ID_CURDIFF, OnCurdiff)
211 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
212 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateSave)
213 ON_MESSAGE(MSG_UI_UPDATE, OnUpdateUIMessage)
214 ON_COMMAND(ID_REFRESH, OnRefresh)
215 ON_UPDATE_COMMAND_UI(ID_REFRESH, OnUpdateRefresh)
217 ON_UPDATE_COMMAND_UI(ID_STATUS_RIGHTDIR_RO, OnUpdateStatusRightRO)
218 ON_UPDATE_COMMAND_UI(ID_STATUS_MIDDLEDIR_RO, OnUpdateStatusMiddleRO)
219 ON_UPDATE_COMMAND_UI(ID_STATUS_LEFTDIR_RO, OnUpdateStatusLeftRO)
220 ON_COMMAND(ID_FILE_LEFT_READONLY, OnReadOnly<SIDE_LEFT>)
221 ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateReadOnly<SIDE_LEFT>)
222 ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnReadOnly<SIDE_MIDDLE>)
223 ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateReadOnly<SIDE_MIDDLE>)
224 ON_COMMAND(ID_FILE_RIGHT_READONLY, OnReadOnly<SIDE_RIGHT>)
225 ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateReadOnly<SIDE_RIGHT>)
226 ON_COMMAND(ID_TOOLS_CUSTOMIZECOLUMNS, OnCustomizeColumns)
227 ON_COMMAND(ID_TOOLS_GENERATEREPORT, OnToolsGenerateReport)
228 ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
229 ON_MESSAGE(MSG_GENERATE_FLIE_COMPARE_REPORT, OnGenerateFileCmpReport)
230 ON_COMMAND(ID_DIR_ZIP_LEFT, OnCtxtDirZip<DirItemEnumerator::Left>)
231 ON_COMMAND(ID_DIR_ZIP_MIDDLE, OnCtxtDirZip<DirItemEnumerator::Middle>)
232 ON_COMMAND(ID_DIR_ZIP_RIGHT, OnCtxtDirZip<DirItemEnumerator::Right>)
233 ON_COMMAND(ID_DIR_ZIP_BOTH, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders>)
234 ON_COMMAND(ID_DIR_ZIP_ALL, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders>)
235 ON_COMMAND(ID_DIR_ZIP_BOTH_DIFFS_ONLY, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders | DirItemEnumerator::DiffsOnly>)
236 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_LEFT, OnUpdateCtxtDirCopyTo<SIDE_LEFT>)
237 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_MIDDLE, OnUpdateCtxtDirCopyTo<SIDE_MIDDLE>)
238 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_RIGHT, OnUpdateCtxtDirCopyTo<SIDE_RIGHT>)
239 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_BOTH, OnUpdateCtxtDirCopyBothTo)
240 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_ALL, OnUpdateCtxtDirCopyBothTo)
241 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_BOTH_DIFFS_ONLY, OnUpdateCtxtDirCopyBothDiffsOnlyTo)
242 ON_COMMAND(ID_DIR_SHELL_CONTEXT_MENU_LEFT, OnCtxtDirShellContextMenu<SIDE_LEFT>)
243 ON_COMMAND(ID_DIR_SHELL_CONTEXT_MENU_MIDDLE, OnCtxtDirShellContextMenu<SIDE_MIDDLE>)
244 ON_COMMAND(ID_DIR_SHELL_CONTEXT_MENU_RIGHT, OnCtxtDirShellContextMenu<SIDE_RIGHT>)
245 ON_COMMAND(ID_EDIT_SELECT_ALL, OnSelectAll)
246 ON_UPDATE_COMMAND_UI(ID_EDIT_SELECT_ALL, OnUpdateSelectAll)
247 ON_COMMAND_RANGE(ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, OnPluginPredifferMode)
248 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, OnUpdatePluginPredifferMode)
249 ON_COMMAND(ID_DIR_COPY_PATHNAMES_LEFT, OnCopyPathnames<SIDE_LEFT>)
250 ON_COMMAND(ID_DIR_COPY_PATHNAMES_MIDDLE, OnCopyPathnames<SIDE_MIDDLE>)
251 ON_COMMAND(ID_DIR_COPY_PATHNAMES_RIGHT, OnCopyPathnames<SIDE_RIGHT>)
252 ON_COMMAND(ID_DIR_COPY_PATHNAMES_BOTH, OnCopyBothPathnames)
253 ON_COMMAND(ID_DIR_COPY_PATHNAMES_ALL, OnCopyBothPathnames)
254 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_LEFT, OnUpdateCtxtDirCopyTo<SIDE_LEFT>)
255 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_MIDDLE, OnUpdateCtxtDirCopyTo<SIDE_MIDDLE>)
256 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_RIGHT, OnUpdateCtxtDirCopyTo<SIDE_RIGHT>)
257 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_BOTH, OnUpdateCtxtDirCopyBothTo)
258 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_ALL, OnUpdateCtxtDirCopyBothTo)
259 ON_COMMAND(ID_DIR_COPY_FILENAMES, OnCopyFilenames)
260 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_FILENAMES, OnUpdateCopyFilenames)
261 ON_COMMAND(ID_DIR_COPY_LEFT_TO_CLIPBOARD, OnCopyToClipboard<SIDE_LEFT>)
262 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, OnCopyToClipboard<SIDE_MIDDLE>)
263 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_CLIPBOARD, OnCopyToClipboard<SIDE_RIGHT>)
264 ON_COMMAND(ID_DIR_COPY_BOTH_TO_CLIPBOARD, OnCopyBothToClipboard)
265 ON_COMMAND(ID_DIR_COPY_ALL_TO_CLIPBOARD, OnCopyBothToClipboard)
266 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_CLIPBOARD, OnUpdateCtxtDirCopyTo<SIDE_LEFT>)
267 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, OnUpdateCtxtDirCopyTo<SIDE_MIDDLE>)
268 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_CLIPBOARD, OnUpdateCtxtDirCopyTo<SIDE_RIGHT>)
269 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_BOTH_TO_CLIPBOARD, OnUpdateCtxtDirCopyBothTo)
270 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_ALL_TO_CLIPBOARD, OnUpdateCtxtDirCopyBothTo)
271 ON_COMMAND(ID_DIR_ITEM_RENAME, OnItemRename)
272 ON_UPDATE_COMMAND_UI(ID_DIR_ITEM_RENAME, OnUpdateItemRename)
273 ON_COMMAND(ID_DIR_HIDE_FILENAMES, OnHideFilenames)
274 ON_COMMAND(ID_DIR_MOVE_LEFT_TO_BROWSE, OnCtxtDirMoveTo<SIDE_LEFT>)
275 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_LEFT_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_LEFT>)
276 ON_COMMAND(ID_DIR_MOVE_MIDDLE_TO_BROWSE, OnCtxtDirMoveTo<SIDE_MIDDLE>)
277 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_MIDDLE_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_MIDDLE>)
278 ON_COMMAND(ID_DIR_MOVE_RIGHT_TO_BROWSE, OnCtxtDirMoveTo<SIDE_RIGHT>)
279 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_RIGHT_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_RIGHT>)
280 ON_UPDATE_COMMAND_UI(ID_DIR_HIDE_FILENAMES, OnUpdateHideFilenames)
282 ON_COMMAND(ID_MERGE_DELETE, OnDelete)
283 ON_UPDATE_COMMAND_UI(ID_MERGE_DELETE, OnUpdateDelete)
284 ON_COMMAND(ID_RESCAN, OnMarkedRescan)
285 ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
286 ON_COMMAND(ID_VIEW_SHOWHIDDENITEMS, OnViewShowHiddenItems)
287 ON_UPDATE_COMMAND_UI(ID_VIEW_SHOWHIDDENITEMS, OnUpdateViewShowHiddenItems)
288 ON_COMMAND(ID_MERGE_COMPARE, OnMergeCompare)
289 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE, OnUpdateMergeCompare)
290 ON_COMMAND(ID_MERGE_COMPARE_LEFT1_LEFT2, OnMergeCompare2<SELECTIONTYPE_LEFT1LEFT2>)
291 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT1_LEFT2, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT1LEFT2>)
292 ON_COMMAND(ID_MERGE_COMPARE_RIGHT1_RIGHT2, OnMergeCompare2<SELECTIONTYPE_RIGHT1RIGHT2>)
293 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_RIGHT1_RIGHT2, OnUpdateMergeCompare2<SELECTIONTYPE_RIGHT1RIGHT2>)
294 ON_COMMAND(ID_MERGE_COMPARE_LEFT1_RIGHT2, OnMergeCompare2<SELECTIONTYPE_LEFT1RIGHT2>)
295 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT1_RIGHT2, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT1RIGHT2>)
296 ON_COMMAND(ID_MERGE_COMPARE_LEFT2_RIGHT1, OnMergeCompare2<SELECTIONTYPE_LEFT2RIGHT1>)
297 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT2_RIGHT1, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT2RIGHT1>)
298 ON_COMMAND(ID_MERGE_COMPARE_NONHORIZONTALLY, OnMergeCompareNonHorizontally)
299 ON_COMMAND(ID_MERGE_COMPARE_XML, OnMergeCompareXML)
300 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_XML, OnUpdateMergeCompare)
301 ON_COMMAND_RANGE(ID_MERGE_COMPARE_HEX, ID_MERGE_COMPARE_IMAGE, OnMergeCompareAs)
302 ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_HEX, ID_MERGE_COMPARE_IMAGE, OnUpdateMergeCompare)
303 ON_COMMAND(ID_VIEW_TREEMODE, OnViewTreeMode)
304 ON_UPDATE_COMMAND_UI(ID_VIEW_TREEMODE, OnUpdateViewTreeMode)
305 ON_COMMAND(ID_VIEW_EXPAND_ALLSUBDIRS, OnViewExpandAllSubdirs)
306 ON_UPDATE_COMMAND_UI(ID_VIEW_EXPAND_ALLSUBDIRS, OnUpdateViewExpandAllSubdirs)
307 ON_COMMAND(ID_VIEW_COLLAPSE_ALLSUBDIRS, OnViewCollapseAllSubdirs)
308 ON_UPDATE_COMMAND_UI(ID_VIEW_COLLAPSE_ALLSUBDIRS, OnUpdateViewCollapseAllSubdirs)
309 ON_COMMAND(ID_VIEW_SWAPPANES, OnViewSwapPanes)
310 ON_COMMAND(ID_VIEW_DIR_STATISTICS, OnViewCompareStatistics)
311 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENT, OnOptionsShowDifferent)
312 ON_COMMAND(ID_OPTIONS_SHOWIDENTICAL, OnOptionsShowIdentical)
313 ON_COMMAND(ID_OPTIONS_SHOWUNIQUELEFT, OnOptionsShowUniqueLeft)
314 ON_COMMAND(ID_OPTIONS_SHOWUNIQUEMIDDLE, OnOptionsShowUniqueMiddle)
315 ON_COMMAND(ID_OPTIONS_SHOWUNIQUERIGHT, OnOptionsShowUniqueRight)
316 ON_COMMAND(ID_OPTIONS_SHOWBINARIES, OnOptionsShowBinaries)
317 ON_COMMAND(ID_OPTIONS_SHOWSKIPPED, OnOptionsShowSkipped)
318 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTLEFTONLY, OnOptionsShowDifferentLeftOnly)
319 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTMIDDLEONLY, OnOptionsShowDifferentMiddleOnly)
320 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTRIGHTONLY, OnOptionsShowDifferentRightOnly)
321 ON_COMMAND(ID_OPTIONS_SHOWMISSINGLEFTONLY, OnOptionsShowMissingLeftOnly)
322 ON_COMMAND(ID_OPTIONS_SHOWMISSINGMIDDLEONLY, OnOptionsShowMissingMiddleOnly)
323 ON_COMMAND(ID_OPTIONS_SHOWMISSINGRIGHTONLY, OnOptionsShowMissingRightOnly)
324 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENT, OnUpdateOptionsShowdifferent)
325 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWIDENTICAL, OnUpdateOptionsShowidentical)
326 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUELEFT, OnUpdateOptionsShowuniqueleft)
327 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUEMIDDLE, OnUpdateOptionsShowuniquemiddle)
328 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUERIGHT, OnUpdateOptionsShowuniqueright)
329 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWBINARIES, OnUpdateOptionsShowBinaries)
330 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWSKIPPED, OnUpdateOptionsShowSkipped)
331 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTLEFTONLY, OnUpdateOptionsShowDifferentLeftOnly)
332 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTMIDDLEONLY, OnUpdateOptionsShowDifferentMiddleOnly)
333 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTRIGHTONLY, OnUpdateOptionsShowDifferentRightOnly)
334 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGLEFTONLY, OnUpdateOptionsShowMissingLeftOnly)
335 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGMIDDLEONLY, OnUpdateOptionsShowMissingMiddleOnly)
336 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGRIGHTONLY, OnUpdateOptionsShowMissingRightOnly)
337 ON_COMMAND(ID_FILE_ENCODING, OnFileEncoding)
338 ON_COMMAND(ID_HELP, OnHelp)
339 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
340 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
341 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
342 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
343 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
345 ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnClick)
346 ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, OnItemChanged)
347 ON_NOTIFY_REFLECT(LVN_BEGINLABELEDIT, OnBeginLabelEdit)
348 ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, OnEndLabelEdit)
349 ON_NOTIFY_REFLECT(NM_CLICK, OnClick)
350 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBeginDrag)
351 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
352 ON_BN_CLICKED(IDC_COMPARISON_STOP, OnBnClickedComparisonStop)
353 ON_BN_CLICKED(IDC_COMPARISON_PAUSE, OnBnClickedComparisonPause)
354 ON_BN_CLICKED(IDC_COMPARISON_CONTINUE, OnBnClickedComparisonContinue)
357 /////////////////////////////////////////////////////////////////////////////
358 // CDirView diagnostics
362 CDirDoc* CDirView::GetDocument() // non-debug version is inline
364 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDirDoc)));
365 return (CDirDoc*)m_pDocument;
369 /////////////////////////////////////////////////////////////////////////////
370 // CDirView message handlers
372 void CDirView::OnInitialUpdate()
374 const int iconCX = []() {
375 const int cx = GetSystemMetrics(SM_CXSMICON);
384 const int iconCY = iconCX;
385 CListView::OnInitialUpdate();
386 m_pList = &GetListCtrl();
387 m_pIList.reset(new IListCtrlImpl(m_pList->m_hWnd));
388 GetDocument()->SetDirView(this);
389 m_pColItems.reset(new DirViewColItems(GetDocument()->m_nDirs));
391 m_pList->SendMessage(CCM_SETUNICODEFORMAT, TRUE, 0);
393 // Load user-selected font
394 if (GetOptionsMgr()->GetBool(OPT_FONT_DIRCMP + OPT_FONT_USECUSTOM))
396 m_font.CreateFontIndirect(&GetMainFrame()->m_lfDir);
397 CWnd::SetFont(&m_font, TRUE);
401 m_pList->SetBkColor(m_cachedColors.clrDirMargin);
403 // Replace standard header with sort header
404 HWND hWnd = ListView_GetHeader(m_pList->m_hWnd);
406 m_ctlSortHeader.SubclassWindow(hWnd);
408 // Load the icons used for the list view (to reflect diff status)
409 // NOTE: these must be in the exactly the same order as in the `enum`
410 // definition in the DirActions.h file (ref: DIFFIMG_LUNIQUE)
411 VERIFY(m_imageList.Create(iconCX, iconCY, ILC_COLOR32 | ILC_MASK, 15, 1));
413 IDI_LFILE, IDI_MFILE, IDI_RFILE,
414 IDI_MRFILE, IDI_LRFILE, IDI_LMFILE,
415 IDI_NOTEQUALFILE, IDI_EQUALFILE, IDI_FILE,
416 IDI_EQUALBINARY, IDI_BINARYDIFF,
417 IDI_LFOLDER, IDI_MFOLDER, IDI_RFOLDER,
418 IDI_MRFOLDER, IDI_LRFOLDER, IDI_LMFOLDER,
419 IDI_FILESKIP, IDI_FOLDERSKIP,
420 IDI_NOTEQUALFOLDER, IDI_EQUALFOLDER, IDI_FOLDER,
422 IDI_FOLDERUP, IDI_FOLDERUP_DISABLE,
424 IDI_NOTEQUALTEXTFILE, IDI_EQUALTEXTFILE
426 for (auto id : icon_ids)
427 VERIFY(-1 != m_imageList.Add((HICON)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(id), IMAGE_ICON, iconCX, iconCY, 0)));
428 m_pList->SetImageList(&m_imageList, LVSIL_SMALL);
430 // Load the icons used for the list view (expanded/collapsed state icons)
431 VERIFY(m_imageState.Create(iconCX, iconCY, ILC_COLOR32 | ILC_MASK, 15, 1));
432 for (auto id : { IDI_TREE_STATE_COLLAPSED, IDI_TREE_STATE_EXPANDED })
433 VERIFY(-1 != m_imageState.Add((HICON)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(id), IMAGE_ICON, iconCX, iconCY, 0)));
435 // Restore column orders as they had them last time they ran
436 m_pColItems->LoadColumnOrders(
437 (const TCHAR *)theApp.GetProfileString(GetDocument()->m_nDirs < 3 ? _T("DirView") : _T("DirView3"), _T("ColumnOrders")));
439 // Display column headers (in appropriate order)
442 // Show selection across entire row.u
443 // Also allow user to rearrange columns via drag&drop of headers.
444 // Also enable infotips.
445 DWORD exstyle = LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP;
446 m_pList->SetExtendedStyle(exstyle);
449 BOOL CDirView::PreCreateWindow(CREATESTRUCT& cs)
451 CListView::PreCreateWindow(cs);
452 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
457 * @brief Called before compare is started.
458 * CDirDoc calls this function before new compare is started, so this
459 * is good place to setup GUI for compare.
460 * @param [in] pCompareStats Pointer to class having current compare stats.
462 void CDirView::StartCompare(CompareStats *pCompareStats)
464 if (m_pCmpProgressBar == nullptr)
465 m_pCmpProgressBar.reset(new DirCompProgressBar());
467 if (!::IsWindow(m_pCmpProgressBar->GetSafeHwnd()))
468 m_pCmpProgressBar->Create(GetParentFrame());
470 m_pCmpProgressBar->SetCompareStat(pCompareStats);
471 m_pCmpProgressBar->StartUpdating();
473 GetParentFrame()->ShowControlBar(m_pCmpProgressBar.get(), TRUE, FALSE);
475 m_compareStart = clock();
479 * @brief Called when folder compare row is double-clicked with mouse.
480 * Selected item is opened to folder or file compare.
482 void CDirView::OnLButtonDblClk(UINT nFlags, CPoint point)
486 m_pList->SubItemHitTest(&lvhti);
487 if (lvhti.iItem >= 0)
489 const DIFFITEM& di = GetDiffItem(lvhti.iItem);
490 if (m_bTreeMode && GetDiffContext().m_bRecursive && di.diffcode.isDirectory())
492 if (di.customFlags & ViewCustomFlags::EXPANDED)
493 CollapseSubdir(lvhti.iItem);
495 ExpandSubdir(lvhti.iItem);
499 CWaitCursor waitstatus;
503 CListView::OnLButtonDblClk(nFlags, point);
507 * @brief Load or reload the columns (headers) of the list view
509 void CDirView::ReloadColumns()
511 LoadColumnHeaderItems();
514 m_pColItems->LoadColumnWidths(
515 (const TCHAR *)theApp.GetProfileString(GetDocument()->m_nDirs < 3 ? _T("DirView") : _T("DirView3"), _T("ColumnWidths")),
516 std::bind(&CListCtrl::SetColumnWidth, m_pList, _1, _2), DefColumnWidth);
521 * @brief Redisplay items in subfolder
522 * @param [in] diffpos First item position in subfolder.
523 * @param [in] level Indent level
524 * @param [in,out] index Index of the item to be inserted.
525 * @param [in,out] alldiffs Number of different items
527 void CDirView::RedisplayChildren(DIFFITEM *diffpos, int level, UINT &index, int &alldiffs)
529 const CDiffContext &ctxt = GetDiffContext();
530 while (diffpos != nullptr)
532 DIFFITEM *curdiffpos = diffpos;
533 const DIFFITEM &di = ctxt.GetNextSiblingDiffPosition(diffpos);
535 if (di.diffcode.isResultDiff() || (!di.diffcode.existAll() && !di.diffcode.isResultFiltered()))
538 bool bShowable = IsShowable(ctxt, di, m_dirfilter);
543 AddNewItem(index, curdiffpos, I_IMAGECALLBACK, level);
545 if (di.HasChildren())
547 m_pList->SetItemState(index - 1, INDEXTOSTATEIMAGEMASK((di.customFlags & ViewCustomFlags::EXPANDED) ? 2 : 1), LVIS_STATEIMAGEMASK);
548 if (di.customFlags & ViewCustomFlags::EXPANDED)
549 RedisplayChildren(ctxt.GetFirstChildDiffPosition(curdiffpos), level + 1, index, alldiffs);
554 if (!ctxt.m_bRecursive || !di.diffcode.isDirectory() || !di.diffcode.existAll())
556 AddNewItem(index, curdiffpos, I_IMAGECALLBACK, 0);
559 if (di.HasChildren())
561 RedisplayChildren(ctxt.GetFirstChildDiffPosition(curdiffpos), level + 1, index, alldiffs);
569 * @brief Redisplay folder compare view.
570 * This function clears folder compare view and then adds
571 * items from current compare to it.
573 void CDirView::Redisplay()
575 const CDirDoc *pDoc = GetDocument();
576 const CDiffContext &ctxt = GetDiffContext();
577 PathContext pathsParent;
578 CImageList emptyImageList;
581 // Disable redrawing while adding new items
584 DeleteAllDisplayItems();
586 m_pList->SetImageList((m_bTreeMode && ctxt.m_bRecursive) ? &m_imageState : &emptyImageList, LVSIL_STATE);
588 // If non-recursive compare, add special item(s)
589 if (!ctxt.m_bRecursive ||
590 CheckAllowUpwardDirectory(ctxt, pDoc->m_pTempPathContext, pathsParent) == AllowUpwardDirectory::ParentIsTempPath)
592 cnt += AddSpecialItems();
596 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
597 RedisplayChildren(diffpos, 0, cnt, alldiffs);
598 if (pDoc->m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPLETED)
599 GetParentFrame()->SetLastCompareResult(alldiffs);
600 SortColumnsAppropriately();
603 m_bNeedSearchLastDiffItem = true;
604 m_bNeedSearchFirstDiffItem = true;
608 * @brief User right-clicked somewhere in this view
610 void CDirView::OnContextMenu(CWnd*, CPoint point)
612 if (GetListCtrl().GetItemCount() == 0)
614 // Make sure window is active
615 GetParentFrame()->ActivateFrame();
618 if (point.x == -1 && point.y == -1)
620 //keystroke invocation
623 ClientToScreen(rect);
625 point = rect.TopLeft();
630 // Check if user right-clicked on header
631 // convert screen coordinates to client coordinates of listview
632 CPoint insidePt = point;
633 GetListCtrl().ScreenToClient(&insidePt);
634 // TODO: correct for hscroll ?
635 // Ask header control if click was on one of its header items
636 HDHITTESTINFO hhti = { 0 };
638 int col = static_cast<int>(GetListCtrl().GetHeaderCtrl()->SendMessage(HDM_HITTEST, 0, (LPARAM) & hhti));
641 // Presumably hhti.flags & HHT_ONHEADER is true
642 HeaderContextMenu(point, m_pColItems->ColPhysToLog(col));
645 // bail out if point is not in any row
646 LVHITTESTINFO lhti = { 0 };
648 ScreenToClient(&insidePt);
650 i = GetListCtrl().HitTest(insidePt);
651 TRACE(_T("i=%d\n"), i);
656 ListContextMenu(point, i);
660 * @brief Format context menu string and disable item if it cannot be applied.
662 static void NTAPI FormatContextMenu(BCMenu *pPopup, UINT uIDItem, int n1, int n2 = 0, int n3 = 0)
665 pPopup->GetMenuText(uIDItem, s1, MF_BYCOMMAND);
666 s2.FormatMessage(s1, NumToStr(n1).c_str(), NumToStr(n2).c_str(), NumToStr(n3).c_str());
667 pPopup->SetMenuText(uIDItem, s2, MF_BYCOMMAND);
670 pPopup->EnableMenuItem(uIDItem, MF_GRAYED);
675 * @brief Toggle context menu item
677 static void NTAPI CheckContextMenu(BCMenu *pPopup, UINT uIDItem, BOOL bCheck)
680 pPopup->CheckMenuItem(uIDItem, MF_CHECKED);
682 pPopup->CheckMenuItem(uIDItem, MF_UNCHECKED);
686 * @brief User right-clicked in listview rows
688 void CDirView::ListContextMenu(CPoint point, int /*i*/)
690 CDirDoc* pDoc = GetDocument();
692 VERIFY(menu.LoadMenu(IDR_POPUP_DIRVIEW));
693 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
694 theApp.TranslateMenu(menu.m_hMenu);
696 // 1st submenu of IDR_POPUP_DIRVIEW is for item popup
697 BCMenu *pPopup = static_cast<BCMenu*>(menu.GetSubMenu(0));
698 ASSERT(pPopup != nullptr);
700 if (pDoc->m_nDirs < 3)
702 pPopup->RemoveMenu(ID_DIR_COPY_LEFT_TO_MIDDLE, MF_BYCOMMAND);
703 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_LEFT, MF_BYCOMMAND);
704 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_RIGHT, MF_BYCOMMAND);
705 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_BROWSE, MF_BYCOMMAND);
706 pPopup->RemoveMenu(ID_DIR_COPY_RIGHT_TO_MIDDLE, MF_BYCOMMAND);
707 pPopup->RemoveMenu(ID_DIR_MOVE_MIDDLE_TO_BROWSE, MF_BYCOMMAND);
708 pPopup->RemoveMenu(ID_DIR_DEL_MIDDLE, MF_BYCOMMAND);
709 pPopup->RemoveMenu(ID_DIR_DEL_ALL, MF_BYCOMMAND);
710 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE, MF_BYCOMMAND);
712 for (int i = 0; i < pPopup->GetMenuItemCount(); ++i)
714 if (pPopup->GetMenuItemID(i) == ID_DIR_HIDE_FILENAMES)
715 pPopup->RemoveMenu(i + 3, MF_BYPOSITION);
718 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE_WITHEDITOR, MF_BYCOMMAND);
719 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE_WITH, MF_BYCOMMAND);
720 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_MIDDLE, MF_BYCOMMAND);
721 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_ALL, MF_BYCOMMAND);
722 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, MF_BYCOMMAND);
723 pPopup->RemoveMenu(ID_DIR_COPY_ALL_TO_CLIPBOARD, MF_BYCOMMAND);
724 pPopup->RemoveMenu(ID_DIR_ZIP_MIDDLE, MF_BYCOMMAND);
725 pPopup->RemoveMenu(ID_DIR_ZIP_ALL, MF_BYCOMMAND);
726 pPopup->RemoveMenu(ID_DIR_SHELL_CONTEXT_MENU_MIDDLE, MF_BYCOMMAND);
727 pPopup->RemoveMenu(ID_MERGE_COMPARE_NONHORIZONTALLY, MF_BYCOMMAND);
731 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_BOTH, MF_BYCOMMAND);
732 pPopup->RemoveMenu(ID_DIR_COPY_BOTH_TO_CLIPBOARD, MF_BYCOMMAND);
733 pPopup->RemoveMenu(ID_DIR_ZIP_BOTH, MF_BYCOMMAND);
734 pPopup->RemoveMenu(ID_DIR_DEL_BOTH, MF_BYCOMMAND);
735 pPopup->RemoveMenu(2, MF_BYPOSITION); // Compare Non-horizontally
738 CMenu menuPluginsHolder;
739 menuPluginsHolder.LoadMenu(IDR_POPUP_PLUGINS_SETTINGS);
740 theApp.TranslateMenu(menuPluginsHolder.m_hMenu);
741 String s = _("Plugin Settings");
742 pPopup->AppendMenu(MF_SEPARATOR);
743 pPopup->AppendMenu(MF_POPUP, static_cast<int>(reinterpret_cast<uintptr_t>(menuPluginsHolder.m_hMenu)), s.c_str());
745 CFrameWnd *pFrame = GetTopLevelFrame();
746 ASSERT(pFrame != nullptr);
747 pFrame->m_bAutoMenuEnable = FALSE;
748 // invoke context menu
749 // this will invoke all the OnUpdate methods to enable/disable the individual items
750 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
753 pFrame->m_bAutoMenuEnable = TRUE;
757 * @brief User right-clicked on specified logical column
759 void CDirView::HeaderContextMenu(CPoint point, int /*i*/)
762 VERIFY(menu.LoadMenu(IDR_POPUP_DIRVIEW));
763 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
764 theApp.TranslateMenu(menu.m_hMenu);
765 // 2nd submenu of IDR_POPUP_DIRVIEW is for header popup
766 BCMenu* pPopup = static_cast<BCMenu *>(menu.GetSubMenu(1));
767 ASSERT(pPopup != nullptr);
769 // invoke context menu
770 // this will invoke all the OnUpdate methods to enable/disable the individual items
771 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
776 * @brief Gets Explorer's context menu for a group of selected files.
778 * @param [in] Side whether to get context menu for the files from the left or
780 * @retval true menu successfully retrieved.
781 * @retval falsea an error occurred while retrieving the menu.
783 bool CDirView::ListShellContextMenu(SIDE_TYPE stype)
785 CShellContextMenu* shellContextMenu;
788 shellContextMenu = m_pShellContextMenuMiddle.get(); break;
790 shellContextMenu = m_pShellContextMenuRight.get(); break;
792 shellContextMenu = m_pShellContextMenuLeft.get(); break;
794 shellContextMenu->Initialize();
795 ApplyFolderNameAndFileName(SelBegin(), SelEnd(), stype, GetDiffContext(),
796 [&](const String& path, const String& filename) { shellContextMenu->AddItem(path, filename); });
797 return shellContextMenu->RequeryShellContextMenu();
801 * @brief User chose (main menu) Copy from right to left
803 void CDirView::OnDirCopy(UINT id)
805 bool to_right = (id == ID_L2R) ? true : false;
806 if (GetDocument()->m_nDirs < 3)
809 DoDirAction(&DirActions::Copy<SIDE_LEFT, SIDE_RIGHT>, _("Copying files..."));
811 DoDirAction(&DirActions::Copy<SIDE_RIGHT, SIDE_LEFT>, _("Copying files..."));
817 switch (m_nActivePane)
820 DoDirAction(&DirActions::Copy<SIDE_LEFT, SIDE_MIDDLE>, _("Copying files..."));
824 DoDirAction(&DirActions::Copy<SIDE_MIDDLE, SIDE_RIGHT>, _("Copying files..."));
830 switch (m_nActivePane)
834 DoDirAction(&DirActions::Copy<SIDE_MIDDLE, SIDE_LEFT>, _("Copying files..."));
837 DoDirAction(&DirActions::Copy<SIDE_RIGHT, SIDE_MIDDLE>, _("Copying files..."));
844 /// User chose (context men) Copy from right to left
845 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
846 void CDirView::OnCtxtDirCopy()
848 DoDirAction(&DirActions::Copy<srctype, dsttype>, _("Copying files..."));
851 /// User chose (context menu) Copy left to...
852 template<SIDE_TYPE stype>
853 void CDirView::OnCtxtDirCopyTo()
855 DoDirActionTo(stype, &DirActions::CopyTo<stype>, _("Copying files..."));
858 /// Update context menu Copy Right to Left item
859 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
860 void CDirView::OnUpdateCtxtDirCopy(CCmdUI* pCmdUI)
862 DoUpdateDirCopy<srctype, dsttype>(pCmdUI, eContext);
865 /// Update main menu Copy Right to Left item
866 void CDirView::OnUpdateDirCopy(CCmdUI* pCmdUI)
868 bool to_right = pCmdUI->m_nID == ID_L2R ? true : false;
869 if (GetDocument()->m_nDirs < 3)
872 DoUpdateDirCopy<SIDE_LEFT, SIDE_RIGHT>(pCmdUI, eContext);
874 DoUpdateDirCopy<SIDE_RIGHT, SIDE_LEFT>(pCmdUI, eContext);
880 switch (m_nActivePane)
883 DoUpdateDirCopy<SIDE_LEFT, SIDE_MIDDLE>(pCmdUI, eContext);
887 DoUpdateDirCopy<SIDE_MIDDLE, SIDE_RIGHT>(pCmdUI, eContext);
893 switch (m_nActivePane)
897 DoUpdateDirCopy<SIDE_MIDDLE, SIDE_LEFT>(pCmdUI, eContext);
900 DoUpdateDirCopy<SIDE_RIGHT, SIDE_MIDDLE>(pCmdUI, eContext);
907 void CDirView::DoDirAction(DirActions::method_type func, const String& status_message)
909 CWaitCursor waitstatus;
912 // First we build a list of desired actions
913 FileActionScript actionScript;
914 DirItemWithIndexIterator begin(m_pIList.get(), -1, true);
915 DirItemWithIndexIterator end;
916 FileActionScript *rsltScript;
917 rsltScript = std::accumulate(begin, end, &actionScript, MakeDirActions(func));
918 ASSERT(rsltScript == &actionScript);
919 // Now we prompt, and execute actions
920 ConfirmAndPerformActions(actionScript);
921 } catch (ContentsChangedException& e) {
922 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
926 void CDirView::DoDirActionTo(SIDE_TYPE stype, DirActions::method_type func, const String& status_message)
929 String startPath(m_lastCopyFolder);
930 String selectfolder_title;
932 if (stype == SIDE_LEFT)
933 selectfolder_title = _("Left side - select destination folder:");
934 else if (stype == SIDE_MIDDLE)
935 selectfolder_title = _("Middle side - select destination folder:");
936 else if (stype == SIDE_RIGHT)
937 selectfolder_title = _("Right side - select destination folder:");
939 if (!SelectFolder(destPath, startPath.c_str(), selectfolder_title))
942 m_lastCopyFolder = destPath;
943 CWaitCursor waitstatus;
946 // First we build a list of desired actions
947 FileActionScript actionScript;
948 actionScript.m_destBase = destPath;
949 DirItemWithIndexIterator begin(m_pIList.get(), -1, true);
950 DirItemWithIndexIterator end;
951 FileActionScript *rsltScript;
952 rsltScript = std::accumulate(begin, end, &actionScript, MakeDirActions(func));
953 ASSERT(rsltScript == &actionScript);
954 // Now we prompt, and execute actions
955 ConfirmAndPerformActions(actionScript);
956 } catch (ContentsChangedException& e) {
957 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
961 // Confirm with user, then perform the action list
962 void CDirView::ConfirmAndPerformActions(FileActionScript & actionList)
964 if (actionList.GetActionItemCount() == 0) // Not sure it is possible to get right-click menu without
965 return; // any selected items, but may as well be safe
967 ASSERT(actionList.GetActionItemCount()>0); // Or else the update handler got it wrong
969 // Set parent window so modality is correct and correct window gets focus
971 actionList.SetParentWindow(this->GetSafeHwnd());
974 ConfirmActionList(GetDiffContext(), actionList);
975 } catch (ConfirmationNeededException& e) {
976 ConfirmFolderCopyDlg dlg;
977 dlg.m_caption = e.m_caption;
978 dlg.m_question = e.m_question;
979 dlg.m_fromText = e.m_fromText;
980 dlg.m_toText = e.m_toText;
981 dlg.m_fromPath = e.m_fromPath;
982 dlg.m_toPath = e.m_toPath;
983 INT_PTR ans = dlg.DoModal();
984 if (ans != IDOK && ans != IDYES)
987 PerformActionList(actionList);
991 * @brief Perform an array of actions
992 * @note There can be only COPY or DELETE actions, not both!
994 void CDirView::PerformActionList(FileActionScript & actionScript)
996 // Check option and enable putting deleted items to Recycle Bin
997 if (GetOptionsMgr()->GetBool(OPT_USE_RECYCLE_BIN))
998 actionScript.UseRecycleBin(true);
1000 actionScript.UseRecycleBin(false);
1002 actionScript.SetParentWindow(GetMainFrame()->GetSafeHwnd());
1004 theApp.AddOperation();
1005 if (actionScript.Run())
1006 UpdateAfterFileScript(actionScript);
1007 theApp.RemoveOperation();
1011 * @brief Update results after running FileActionScript.
1012 * This functions is called after script is finished to update
1013 * results (including UI).
1014 * @param [in] actionlist Script that was run.
1016 void CDirView::UpdateAfterFileScript(FileActionScript & actionList)
1018 bool bItemsRemoved = false;
1019 int curSel = GetFirstSelectedInd();
1020 CDiffContext& ctxt = GetDiffContext();
1021 while (actionList.GetActionItemCount()>0)
1023 // Start handling from tail of list, so removing items
1024 // doesn't invalidate our item indexes.
1025 FileActionItem act = actionList.RemoveTailActionItem();
1027 // Update doc (difflist)
1028 UPDATEITEM_TYPE updatetype = UpdateDiffAfterOperation(act, ctxt, GetDiffItem(act.context));
1029 if (updatetype == UPDATEITEM_REMOVE)
1031 DeleteItem(act.context, true);
1032 bItemsRemoved = true;
1034 else if (updatetype == UPDATEITEM_UPDATE)
1035 UpdateDiffItemStatus(act.context);
1038 // Make sure selection is at sensible place if all selected items
1042 UINT selected = GetSelectedCount();
1047 MoveFocus(0, curSel - 1, selected);
1052 Counts CDirView::Count(DirActions::method_type2 func) const
1054 return ::Count(SelBegin(), SelEnd(), MakeDirActions(func));
1057 /// Should Copy to Left be enabled or disabled ? (both main menu & context menu use this)
1058 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
1059 void CDirView::DoUpdateDirCopy(CCmdUI* pCmdUI, eMenuType menuType)
1061 Counts counts = Count(&DirActions::IsItemCopyableOnTo<srctype, dsttype>);
1062 pCmdUI->Enable(counts.count > 0);
1063 if (menuType == eContext)
1064 pCmdUI->SetText(FormatMenuItemString(srctype, dsttype, counts.count, counts.total).c_str());
1068 * @brief Update any resources necessary after a GUI language change
1070 void CDirView::UpdateResources()
1072 UpdateColumnNames();
1073 GetParentFrame()->UpdateResources();
1077 * @brief User just clicked a column, so perform sort
1079 void CDirView::OnColumnClick(NMHDR *pNMHDR, LRESULT *pResult)
1081 // set sort parameters and handle ascending/descending
1082 NM_LISTVIEW* pNMListView = (NM_LISTVIEW*) pNMHDR;
1083 int oldSortColumn = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
1084 int sortcol = m_pColItems->ColPhysToLog(pNMListView->iSubItem);
1085 if (sortcol == oldSortColumn)
1088 bool bSortAscending = GetOptionsMgr()->GetBool(OPT_DIRVIEW_SORT_ASCENDING);
1089 GetOptionsMgr()->SaveOption(OPT_DIRVIEW_SORT_ASCENDING, !bSortAscending);
1093 GetOptionsMgr()->SaveOption((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3, sortcol);
1094 // most columns start off ascending, but not dates
1095 bool bSortAscending = m_pColItems->IsDefaultSortAscending(sortcol);
1096 GetOptionsMgr()->SaveOption(OPT_DIRVIEW_SORT_ASCENDING, bSortAscending);
1099 SortColumnsAppropriately();
1103 void CDirView::SortColumnsAppropriately()
1105 int sortCol = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
1106 if (sortCol == -1 || sortCol >= m_pColItems->GetColCount())
1109 bool bSortAscending = GetOptionsMgr()->GetBool(OPT_DIRVIEW_SORT_ASCENDING);
1110 m_ctlSortHeader.SetSortImage(m_pColItems->ColLogToPhys(sortCol), bSortAscending);
1111 //sort using static CompareFunc comparison function
1112 CompareState cs(&GetDiffContext(), m_pColItems.get(), sortCol, bSortAscending, m_bTreeMode);
1113 GetListCtrl().SortItems(cs.CompareFunc, reinterpret_cast<DWORD_PTR>(&cs));
1115 m_bNeedSearchLastDiffItem = true;
1116 m_bNeedSearchFirstDiffItem = true;
1119 /// Do any last minute work as view closes
1120 void CDirView::OnDestroy()
1122 DeleteAllDisplayItems();
1124 String secname = GetDocument()->m_nDirs < 3 ? _T("DirView") : _T("DirView3");
1125 theApp.WriteProfileString(secname.c_str(), _T("ColumnOrders"), m_pColItems->SaveColumnOrders().c_str());
1126 theApp.WriteProfileString(secname.c_str(), _T("ColumnWidths"),
1127 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1)).c_str());
1129 CListView::OnDestroy();
1131 GetMainFrame()->ClearStatusbarItemCount();
1135 * @brief Open selected item when user presses ENTER key.
1137 void CDirView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
1139 if (nChar == VK_RETURN)
1141 int sel = GetFocusedItem();
1144 const DIFFITEM& di = GetDiffItem(sel);
1145 if (m_bTreeMode && GetDiffContext().m_bRecursive && di.diffcode.isDirectory())
1147 if (di.customFlags & ViewCustomFlags::EXPANDED)
1148 CollapseSubdir(sel);
1154 CWaitCursor waitstatus;
1159 CListView::OnChar(nChar, nRepCnt, nFlags);
1163 * @brief Expand/collapse subfolder when "+/-" icon is clicked.
1165 void CDirView::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
1167 LPNMITEMACTIVATE pNM = (LPNMITEMACTIVATE)pNMHDR;
1168 LVHITTESTINFO lvhti;
1169 lvhti.pt = pNM->ptAction;
1170 m_pList->SubItemHitTest(&lvhti);
1171 if (lvhti.flags == LVHT_ONITEMSTATEICON)
1173 const DIFFITEM &di = GetDiffItem(pNM->iItem);
1174 if (di.customFlags & ViewCustomFlags::EXPANDED)
1175 CollapseSubdir(pNM->iItem);
1177 ExpandSubdir(pNM->iItem);
1184 * @brief Collapse subfolder
1185 * @param [in] sel Folder item index in listview.
1187 void CDirView::CollapseSubdir(int sel)
1189 DIFFITEM& dip = this->GetDiffItem(sel);
1190 if (!m_bTreeMode || !(dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren())
1193 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
1195 dip.customFlags &= ~ViewCustomFlags::EXPANDED;
1196 m_pList->SetItemState(sel, INDEXTOSTATEIMAGEMASK(1), LVIS_STATEIMAGEMASK);
1198 int count = m_pList->GetItemCount();
1199 for (int i = sel + 1; i < count; i++)
1201 const DIFFITEM& di = GetDiffItem(i);
1202 if (!di.IsAncestor(&dip))
1204 m_pList->DeleteItem(i--);
1208 m_pList->SetRedraw(TRUE); // Turn updating back on
1212 * @brief Expand subfolder
1213 * @param [in] sel Folder item index in listview.
1215 void CDirView::ExpandSubdir(int sel, bool bRecursive)
1217 DIFFITEM& dip = GetDiffItem(sel);
1218 if (!m_bTreeMode || (dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren())
1221 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
1222 m_pList->SetItemState(sel, INDEXTOSTATEIMAGEMASK(2), LVIS_STATEIMAGEMASK);
1224 CDiffContext &ctxt = GetDiffContext();
1225 dip.customFlags |= ViewCustomFlags::EXPANDED;
1227 ExpandSubdirs(ctxt, dip);
1229 DIFFITEM *diffpos = ctxt.GetFirstChildDiffPosition(GetItemKey(sel));
1230 UINT indext = sel + 1;
1232 RedisplayChildren(diffpos, dip.GetDepth() + 1, indext, alldiffs);
1234 SortColumnsAppropriately();
1236 m_pList->SetRedraw(TRUE); // Turn updating back on
1240 * @brief Open parent folder if possible.
1242 void CDirView::OpenParentDirectory()
1244 CDirDoc *pDoc = GetDocument();
1245 PathContext pathsParent;
1246 switch (CheckAllowUpwardDirectory(GetDiffContext(), pDoc->m_pTempPathContext, pathsParent))
1248 case AllowUpwardDirectory::ParentIsTempPath:
1249 pDoc->m_pTempPathContext = pDoc->m_pTempPathContext->DeleteHead();
1250 // fall through (no break!)
1251 case AllowUpwardDirectory::ParentIsRegularPath:
1254 for (int nIndex = 0; nIndex < pathsParent.GetSize(); ++nIndex)
1255 dwFlags[nIndex] = FFILEOPEN_NOMRU | (pDoc->GetReadOnly(nIndex) ? FFILEOPEN_READONLY : 0);
1256 GetMainFrame()->DoFileOpen(&pathsParent, dwFlags, nullptr, _T(""), GetDiffContext().m_bRecursive, (GetAsyncKeyState(VK_CONTROL) & 0x8000) ? nullptr : pDoc);
1258 // fall through (no break!)
1259 case AllowUpwardDirectory::No:
1262 LangMessageBox(IDS_INVALID_DIRECTORY, MB_ICONSTOP);
1268 * @brief Get one or two selected items
1270 * Returns false if 0 or more than 3 items selecte
1272 bool CDirView::GetSelectedItems(int * sel1, int * sel2, int * sel3)
1276 *sel1 = m_pList->GetNextItem(-1, LVNI_SELECTED);
1279 *sel2 = m_pList->GetNextItem(*sel1, LVNI_SELECTED);
1282 *sel3 = m_pList->GetNextItem(*sel2, LVNI_SELECTED);
1285 int extra = m_pList->GetNextItem(*sel3, LVNI_SELECTED);
1286 return (extra == -1);
1290 * @brief Open special items (parent folders etc).
1291 * @param [in] pos1 First item position.
1292 * @param [in] pos2 Second item position.
1294 void CDirView::OpenSpecialItems(DIFFITEM *pos1, DIFFITEM *pos2, DIFFITEM *pos3)
1296 if (pos2==nullptr && pos3==nullptr)
1298 // Browse to parent folder(s) selected
1299 // SPECIAL_ITEM_POS is position for
1300 // special items, but there is currenly
1301 // only one (parent folder)
1302 OpenParentDirectory();
1306 // Parent directory & something else selected
1312 * @brief Creates a pairing folder for unique folder item.
1313 * This function creates a pairing folder for unique folder item in
1314 * folder compare. This way user can browse into unique folder's
1315 * contents and don't necessarily need to copy whole folder structure.
1316 * @return true if user agreed and folder was created.
1318 static bool CreateFoldersPair(const PathContext& paths)
1320 bool created = false;
1321 for (const auto& path : paths)
1323 if (!paths::DoesPathExist(path))
1326 strutils::format_string1(
1327 _("The folder exists only in other side and cannot be opened.\n\nDo you want to create a matching folder:\n%1\nto the other side and open these folders?"),
1329 int res = AfxMessageBox(message.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN);
1331 created = paths::CreateIfNeeded(path);
1337 void CDirView::Open(const PathContext& paths, DWORD dwFlags[3], PackingInfo * infoUnpacker)
1340 for (auto path : paths)
1342 if (paths::DoesPathExist(path) == paths::IS_EXISTING_DIR)
1345 CDirDoc * pDoc = GetDocument();
1349 // Don't add folders to MRU
1350 GetMainFrame()->DoFileOpen(&paths, dwFlags, nullptr, _T(""), GetDiffContext().m_bRecursive, (GetAsyncKeyState(VK_CONTROL) & 0x8000) ? nullptr : pDoc);
1352 else if (HasZipSupport() && std::count_if(paths.begin(), paths.end(), ArchiveGuessFormat) == paths.GetSize())
1354 // Open archives, not adding paths to MRU
1355 GetMainFrame()->DoFileOpen(&paths, dwFlags, nullptr, _T(""), GetDiffContext().m_bRecursive, nullptr, _T(""), infoUnpacker);
1359 // Regular file case
1361 // Binary attributes are set after files are unpacked
1362 // so after plugins such as the MS-Office plugins have had a chance to make them textual
1363 // We haven't done unpacking yet in this diff, but if a binary flag is already set,
1364 // then it was set in a previous diff after unpacking, so we trust it
1366 // Open identical and different files
1367 FileLocation fileloc[3];
1369 const String sUntitled[] = { _("Untitled left"), paths.GetSize() < 3 ? _("Untitled right") : _("untitled middle"), _("Untitled right") };
1370 for (int i = 0; i < paths.GetSize(); ++i)
1372 if (paths::DoesPathExist(paths[i]) == paths::DOES_NOT_EXIST)
1373 strDesc[i] = sUntitled[i];
1375 fileloc[i].setPath(paths[i]);
1378 GetMainFrame()->ShowAutoMergeDoc(pDoc, paths.GetSize(), fileloc,
1379 dwFlags, strDesc, _T(""), infoUnpacker);
1384 * @brief Open selected files or directories.
1386 * Opens selected files to file compare. If comparing
1387 * directories non-recursively, then subfolders and parent
1388 * folder are opened too.
1390 * This handles the case that one item is selected
1391 * and the case that two items are selected (one on each side)
1393 void CDirView::OpenSelection(SELECTIONTYPE selectionType /*= SELECTIONTYPE_NORMAL*/, PackingInfo * infoUnpacker /*= nullptr*/)
1395 Merge7zFormatMergePluginScope scope(infoUnpacker);
1396 CDirDoc * pDoc = GetDocument();
1397 const CDiffContext& ctxt = GetDiffContext();
1399 // First, figure out what was selected (store into pos1 & pos2)
1400 DIFFITEM *pos1 = nullptr, *pos2 = nullptr, *pos3 = nullptr;
1401 int sel1 = -1, sel2 = -1, sel3 = -1;
1402 if (!GetSelectedItems(&sel1, &sel2, &sel3))
1404 // Must have 1 or 2 or 3 items selected
1409 pos1 = GetItemKey(sel1);
1410 ASSERT(pos1 != nullptr);
1413 pos2 = GetItemKey(sel2);
1414 ASSERT(pos2 != nullptr);
1416 pos3 = GetItemKey(sel3);
1419 // Now handle the various cases of what was selected
1421 if (pos1 == (DIFFITEM *)SPECIAL_ITEM_POS)
1423 OpenSpecialItems(pos1, pos2, pos3);
1427 // Common variables which both code paths below are responsible for setting
1429 const DIFFITEM *pdi[3] = {0}; // left & right items (di1==di2 if single selection)
1430 bool isdir = false; // set if we're comparing directories
1435 success = GetOpenTwoItems(ctxt, selectionType, pos1, pos2, pdi,
1436 paths, sel1, sel2, isdir, nPane, errmsg);
1437 else if (pos2 && pos3)
1438 success = GetOpenThreeItems(ctxt, pos1, pos2, pos3, pdi,
1439 paths, sel1, sel2, sel3, isdir, nPane, errmsg);
1442 // Only one item selected, so perform diff on its sides
1443 success = GetOpenOneItem(ctxt, pos1, pdi,
1444 paths, sel1, isdir, nPane, errmsg);
1446 CreateFoldersPair(paths);
1450 if (!errmsg.empty())
1451 AfxMessageBox(errmsg.c_str(), MB_ICONSTOP);
1455 // Now pathLeft, pathRight, di1, di2, and isdir are all set
1456 // We have two items to compare, no matter whether same or different underlying DirView item
1459 for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
1460 dwFlags[nIndex] = FFILEOPEN_NOMRU | (pDoc->GetReadOnly(nPane[nIndex]) ? FFILEOPEN_READONLY : 0);
1462 Open(paths, dwFlags, infoUnpacker);
1465 void CDirView::OpenSelectionAs(UINT id)
1467 CDirDoc * pDoc = GetDocument();
1468 const CDiffContext& ctxt = GetDiffContext();
1470 // First, figure out what was selected (store into pos1 & pos2)
1471 DIFFITEM *pos1 = nullptr, *pos2 = nullptr;
1472 int sel1 = -1, sel2 = -1, sel3 = -1;
1473 if (!GetSelectedItems(&sel1, &sel2, &sel3))
1475 // Must have 1 or 2 items selected
1480 pos1 = GetItemKey(sel1);
1483 pos2 = GetItemKey(sel2);
1485 // Now handle the various cases of what was selected
1487 if (pos1 == (DIFFITEM *)SPECIAL_ITEM_POS)
1493 // Common variables which both code paths below are responsible for setting
1495 const DIFFITEM *pdi[3]; // left & right items (di1==di2 if single selection)
1496 bool isdir = false; // set if we're comparing directories
1501 success = GetOpenTwoItems(ctxt, SELECTIONTYPE_NORMAL, pos1, pos2, pdi,
1502 paths, sel1, sel2, isdir, nPane, errmsg);
1505 // Only one item selected, so perform diff on its sides
1506 success = GetOpenOneItem(ctxt, pos1, pdi,
1507 paths, sel1, isdir, nPane, errmsg);
1511 if (!errmsg.empty())
1512 AfxMessageBox(errmsg.c_str(), MB_ICONSTOP);
1516 // Open identical and different files
1517 DWORD dwFlags[3] = { 0 };
1518 FileLocation fileloc[3];
1519 for (int pane = 0; pane < paths.GetSize(); pane++)
1521 fileloc[pane].setPath(paths[pane]);
1522 dwFlags[pane] |= FFILEOPEN_NOMRU | (pDoc->GetReadOnly(nPane[pane]) ? FFILEOPEN_READONLY : 0);
1524 if (id == ID_MERGE_COMPARE_HEX)
1525 GetMainFrame()->ShowHexMergeDoc(pDoc, paths.GetSize(), fileloc, dwFlags, nullptr);
1527 GetMainFrame()->ShowImgMergeDoc(pDoc, paths.GetSize(), fileloc, dwFlags, nullptr);
1530 /// User chose (context menu) delete left
1531 template<SIDE_TYPE stype>
1532 void CDirView::OnCtxtDirDel()
1534 DoDirAction(&DirActions::DeleteOn<stype>, _("Deleting files..."));
1537 /// User chose (context menu) delete both
1538 void CDirView::OnCtxtDirDelBoth()
1540 DoDirAction(&DirActions::DeleteOnBoth, _("Deleting files..."));
1543 /// Enable/disable Delete Left menu choice on context menu
1544 template<SIDE_TYPE stype>
1545 void CDirView::OnUpdateCtxtDirDel(CCmdUI* pCmdUI)
1547 Counts counts = Count(&DirActions::IsItemDeletableOn<stype>);
1548 pCmdUI->Enable(counts.count > 0);
1549 pCmdUI->SetText(FormatMenuItemString(stype, counts.count, counts.total).c_str());
1552 /// Enable/disable Delete Both menu choice on context menu
1553 void CDirView::OnUpdateCtxtDirDelBoth(CCmdUI* pCmdUI)
1555 Counts counts = Count(&DirActions::IsItemDeletableOnBoth);
1556 pCmdUI->Enable(counts.count > 0);
1557 pCmdUI->SetText(FormatMenuItemStringAll(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1561 * @brief Update "Copy | Right to..." item
1563 template<SIDE_TYPE stype>
1564 void CDirView::OnUpdateCtxtDirCopyTo(CCmdUI* pCmdUI)
1566 Counts counts = Count(&DirActions::IsItemCopyableToOn<stype>);
1567 pCmdUI->Enable(counts.count > 0);
1568 pCmdUI->SetText(FormatMenuItemStringTo(stype, counts.count, counts.total).c_str());
1571 void CDirView::OnUpdateCtxtDirCopyBothTo(CCmdUI* pCmdUI)
1573 Counts counts = Count(&DirActions::IsItemCopyableBothToOn);
1574 pCmdUI->Enable(counts.count > 0);
1575 pCmdUI->SetText(FormatMenuItemStringAllTo(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1578 void CDirView::OnUpdateCtxtDirCopyBothDiffsOnlyTo(CCmdUI* pCmdUI)
1580 Counts counts = Count(&DirActions::IsItemNavigableDiff);
1581 pCmdUI->Enable(counts.count > 0);
1582 pCmdUI->SetText(FormatMenuItemStringDifferencesTo(counts.count, counts.total).c_str());
1586 * @brief Get keydata associated with item in given index.
1587 * @param [in] idx Item's index to list in UI.
1588 * @return Key for item in given index.
1590 DIFFITEM *CDirView::GetItemKey(int idx) const
1592 return (DIFFITEM *) m_pList->GetItemData(idx);
1595 // SetItemKey & GetItemKey encapsulate how the display list items
1596 // are mapped to DiffItems, which in turn are DiffContext keys to the actual DIFFITEM data
1599 * @brief Get DIFFITEM data for item.
1600 * This function returns DIFFITEM data for item in given index in GUI.
1601 * @param [in] sel Item's index in folder compare GUI list.
1602 * @return DIFFITEM for item.
1604 const DIFFITEM &CDirView::GetDiffItem(int sel) const
1606 CDirView * pDirView = const_cast<CDirView *>(this);
1607 return pDirView->GetDiffItem(sel);
1611 * Given index in list control, get modifiable reference to its DIFFITEM data
1613 DIFFITEM &CDirView::GetDiffItem(int sel)
1615 DIFFITEM *diffpos = GetItemKey(sel);
1617 // If it is special item, return empty DIFFITEM
1618 if (diffpos == (DIFFITEM *)SPECIAL_ITEM_POS)
1620 return *DIFFITEM::GetEmptyItem();
1622 return GetDiffContext().GetDiffRefAt(diffpos);
1625 void CDirView::DeleteItem(int sel, bool removeDIFFITEM)
1628 CollapseSubdir(sel);
1631 DIFFITEM *diffpos = GetItemKey(sel);
1632 if (diffpos != (DIFFITEM *)SPECIAL_ITEM_POS)
1634 if (diffpos->HasChildren())
1635 diffpos->RemoveChildren();
1636 diffpos->DelinkFromSiblings();
1640 m_pList->DeleteItem(sel);
1643 void CDirView::DeleteAllDisplayItems()
1645 // item data are just positions (diffposes)
1646 // that is, they contain no memory needing to be freed
1647 m_pList->DeleteAllItems();
1651 * @brief Given key, get index of item which has it stored.
1652 * This function searches from list in UI.
1654 int CDirView::GetItemIndex(DIFFITEM *key)
1656 LVFINDINFO findInfo;
1658 findInfo.flags = LVFI_PARAM; // Search for itemdata
1659 findInfo.lParam = (LPARAM)key;
1660 return m_pList->FindItem(&findInfo);
1664 * @brief Get the file names on both sides for specified item.
1665 * @note Return empty strings if item is special item.
1667 void CDirView::GetItemFileNames(int sel, String& strLeft, String& strRight) const
1669 DIFFITEM *diffpos = GetItemKey(sel);
1670 if (diffpos == (DIFFITEM *)SPECIAL_ITEM_POS)
1677 const CDiffContext& ctxt = GetDiffContext();
1678 ::GetItemFileNames(ctxt, ctxt.GetDiffAt(diffpos), strLeft, strRight);
1683 * @brief Get the file names on both sides for specified item.
1684 * @note Return empty strings if item is special item.
1686 void CDirView::GetItemFileNames(int sel, PathContext * paths) const
1688 DIFFITEM *diffpos = GetItemKey(sel);
1689 if (diffpos == (DIFFITEM *)SPECIAL_ITEM_POS)
1691 for (int nIndex = 0; nIndex < GetDocument()->m_nDirs; nIndex++)
1692 paths->SetPath(nIndex, _T(""));
1696 const CDiffContext& ctxt = GetDiffContext();
1697 *paths = ::GetItemFileNames(ctxt, ctxt.GetDiffAt(diffpos));
1702 * @brief Open selected file with registered application.
1703 * Uses shell file associations to open file with registered
1704 * application. We first try to use "Edit" action which should
1705 * open file to editor, since we are more interested editing
1706 * files than running them (scripts).
1707 * @param [in] stype Side of file to open.
1709 void CDirView::DoOpen(SIDE_TYPE stype)
1711 int sel = GetSingleSelectedItem();
1712 if (sel == -1) return;
1713 DirItemIterator dirBegin = SelBegin();
1714 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1715 if (file.empty()) return;
1716 HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), file.c_str(), 0, 0, SW_SHOWNORMAL);
1717 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
1718 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), file.c_str(), 0, 0, SW_SHOWNORMAL);
1719 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
1723 /// Open with dialog for file on selected side
1724 void CDirView::DoOpenWith(SIDE_TYPE stype)
1726 int sel = GetSingleSelectedItem();
1727 if (sel == -1) return;
1728 DirItemIterator dirBegin = SelBegin();
1729 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1730 if (file.empty()) return;
1732 if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH)) return;
1733 sysdir.ReleaseBuffer();
1734 CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + file.c_str();
1735 ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg, sysdir, SW_SHOWNORMAL);
1738 /// Open selected file on specified side to external editor
1739 void CDirView::DoOpenWithEditor(SIDE_TYPE stype)
1741 int sel = GetSingleSelectedItem();
1742 if (sel == -1) return;
1743 DirItemIterator dirBegin = SelBegin();
1744 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1745 if (file.empty()) return;
1747 theApp.OpenFileToExternalEditor(file);
1750 void CDirView::DoOpenParentFolder(SIDE_TYPE stype)
1752 int sel = GetSingleSelectedItem();
1753 if (sel == -1) return;
1754 DirItemIterator dirBegin = SelBegin();
1755 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1756 if (file.empty()) return;
1757 String parentFolder = paths::GetParentPath(file);
1758 ShellExecute(::GetDesktopWindow(), _T("open"), parentFolder.c_str(), 0, 0, SW_SHOWNORMAL);
1761 /// User chose (context menu) open left
1762 template<SIDE_TYPE stype>
1763 void CDirView::OnCtxtDirOpen()
1768 /// User chose (context menu) open left with
1769 template<SIDE_TYPE stype>
1770 void CDirView::OnCtxtDirOpenWith()
1775 /// User chose (context menu) open left with editor
1776 template<SIDE_TYPE stype>
1777 void CDirView::OnCtxtDirOpenWithEditor()
1779 DoOpenWithEditor(stype);
1782 /// User chose (context menu) open left parent folder
1783 template<SIDE_TYPE stype>
1784 void CDirView::OnCtxtDirOpenParentFolder()
1786 DoOpenParentFolder(stype);
1789 /// Update context menuitem "Open left | with editor"
1790 template<SIDE_TYPE stype>
1791 void CDirView::OnUpdateCtxtDirOpenWithEditor(CCmdUI* pCmdUI)
1793 Counts counts = Count(&DirActions::IsItemOpenableOnWith<stype>);
1794 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1797 // return selected item index, or -1 if none or multiple
1798 int CDirView::GetSingleSelectedItem() const
1800 int sel = -1, sel2 = -1;
1801 sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
1802 if (sel == -1) return -1;
1803 sel2 = m_pList->GetNextItem(sel, LVNI_SELECTED);
1804 if (sel2 != -1) return -1;
1807 // Enable/disable Open Left menu choice on context menu
1808 template<SIDE_TYPE stype>
1809 void CDirView::OnUpdateCtxtDirOpen(CCmdUI* pCmdUI)
1811 Counts counts = Count(&DirActions::IsItemOpenableOn<stype>);
1812 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1815 // Enable/disable Open Left With menu choice on context menu
1816 template<SIDE_TYPE stype>
1817 void CDirView::OnUpdateCtxtDirOpenWith(CCmdUI* pCmdUI)
1819 Counts counts = Count(&DirActions::IsItemOpenableOnWith<stype>);
1820 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1823 // Enable/disable Open Parent Folder menu choice on context menu
1824 template<SIDE_TYPE stype>
1825 void CDirView::OnUpdateCtxtDirOpenParentFolder(CCmdUI* pCmdUI)
1827 Counts counts = Count(&DirActions::IsParentFolderOpenable<stype>);
1828 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1832 void CDirView::DoUpdateOpen(SELECTIONTYPE selectionType, CCmdUI* pCmdUI)
1834 int sel1 = -1, sel2 = -1, sel3 = -1;
1835 if (!GetSelectedItems(&sel1, &sel2, &sel3))
1837 // 0 items or more than 2 items seleted
1838 pCmdUI->Enable(FALSE);
1843 // One item selected
1844 if (selectionType != SELECTIONTYPE_NORMAL)
1846 pCmdUI->Enable(FALSE);
1850 else if (sel3 == -1)
1852 // Two items selected
1853 const DIFFITEM& di1 = GetDiffItem(sel1);
1854 const DIFFITEM& di2 = GetDiffItem(sel2);
1855 if (!AreItemsOpenable(GetDiffContext(), selectionType, di1, di2))
1857 pCmdUI->Enable(FALSE);
1863 // Three items selected
1864 const DIFFITEM& di1 = GetDiffItem(sel1);
1865 const DIFFITEM& di2 = GetDiffItem(sel2);
1866 const DIFFITEM& di3 = GetDiffItem(sel3);
1867 if (selectionType != SELECTIONTYPE_NORMAL || !::AreItemsOpenable(GetDiffContext(), di1, di2, di3))
1869 pCmdUI->Enable(FALSE);
1873 pCmdUI->Enable(TRUE);
1877 * @brief Return count of selected items in folder compare.
1879 UINT CDirView::GetSelectedCount() const
1881 return m_pList->GetSelectedCount();
1885 * @brief Return index of first selected item in folder compare.
1887 int CDirView::GetFirstSelectedInd()
1889 return m_pList->GetNextItem(-1, LVNI_SELECTED);
1893 // If none or one item selected select found item
1894 // This is used for scrolling to first diff too
1895 void CDirView::OnFirstdiff()
1897 DirItemIterator it =
1898 std::find_if(Begin(), End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
1900 MoveFocus(GetFirstSelectedInd(), it.m_sel, GetSelectedCount());
1903 void CDirView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1905 pCmdUI->Enable(GetFirstDifferentItem() > -1);
1909 // If none or one item selected select found item
1910 void CDirView::OnLastdiff()
1912 DirItemIterator it =
1913 std::find_if(RevBegin(), RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
1915 MoveFocus(GetFirstSelectedInd(), it.m_sel, GetSelectedCount());
1918 void CDirView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1920 pCmdUI->Enable(GetFirstDifferentItem() > -1);
1923 bool CDirView::HasNextDiff()
1925 int lastDiff = GetLastDifferentItem();
1927 // Check if different files were found and
1928 // there is different item after focused item
1929 return (lastDiff > -1) && (GetFocusedItem() < lastDiff);
1932 bool CDirView::HasPrevDiff()
1934 int firstDiff = GetFirstDifferentItem();
1936 // Check if different files were found and
1937 // there is different item before focused item
1938 return (firstDiff > -1) && (firstDiff < GetFocusedItem());
1941 void CDirView::MoveToNextDiff()
1943 int currentInd = GetFocusedItem();
1944 DirItemIterator begin(m_pIList.get(), currentInd + 1);
1945 DirItemIterator it =
1946 std::find_if(begin, End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
1948 MoveFocus(currentInd, it.m_sel, GetSelectedCount());
1951 void CDirView::MoveToPrevDiff()
1953 int currentInd = GetFocusedItem();
1954 if (currentInd <= 0)
1956 DirItemIterator begin(m_pIList.get(), currentInd - 1, false, true);
1957 DirItemIterator it =
1958 std::find_if(begin, RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
1960 MoveFocus(currentInd, it.m_sel, GetSelectedCount());
1963 void CDirView::OpenNextDiff()
1966 int currentInd = GetFocusedItem();
1967 const DIFFITEM& dip = GetDiffItem(currentInd);
1968 if (!dip.diffcode.isDirectory())
1974 GetParentFrame()->ActivateFrame();
1978 void CDirView::OpenPrevDiff()
1981 int currentInd = GetFocusedItem();
1982 const DIFFITEM& dip = GetDiffItem(currentInd);
1983 if (!dip.diffcode.isDirectory())
1989 GetParentFrame()->ActivateFrame();
1993 void CDirView::SetActivePane(int pane)
1995 if (m_nActivePane >= 0)
1996 GetParentFrame()->GetHeaderInterface()->SetActive(m_nActivePane, false);
1997 GetParentFrame()->GetHeaderInterface()->SetActive(pane, true);
1998 m_nActivePane = pane;
2002 // If none or one item selected select found item
2003 void CDirView::OnNextdiff()
2009 void CDirView::OnUpdateNextdiff(CCmdUI* pCmdUI)
2011 pCmdUI->Enable(HasNextDiff());
2015 // If none or one item selected select found item
2016 void CDirView::OnPrevdiff()
2022 void CDirView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
2024 pCmdUI->Enable(HasPrevDiff());
2027 void CDirView::OnCurdiff()
2029 const int count = m_pList->GetItemCount();
2031 int i = GetFirstSelectedInd();
2033 // No selection - no diff to go
2037 while (i < count && !found)
2039 UINT selected = m_pList->GetItemState(i, LVIS_SELECTED);
2040 UINT focused = m_pList->GetItemState(i, LVIS_FOCUSED);
2042 if (selected == LVIS_SELECTED && focused == LVIS_FOCUSED)
2044 m_pList->EnsureVisible(i, FALSE);
2051 void CDirView::OnUpdateCurdiff(CCmdUI* pCmdUI)
2053 pCmdUI->Enable(GetFirstSelectedInd() > -1);
2056 int CDirView::GetFocusedItem()
2058 return m_pList->GetNextItem(-1, LVNI_FOCUSED);
2061 int CDirView::GetFirstDifferentItem()
2063 if (!m_bNeedSearchFirstDiffItem)
2064 return m_firstDiffItem;
2066 DirItemIterator it =
2067 std::find_if(Begin(), End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2068 m_firstDiffItem = it.m_sel;
2069 m_bNeedSearchFirstDiffItem = false;
2071 return m_firstDiffItem;
2074 int CDirView::GetLastDifferentItem()
2076 if (!m_bNeedSearchLastDiffItem)
2077 return m_lastDiffItem;
2079 DirItemIterator it =
2080 std::find_if(RevBegin(), RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2081 m_lastDiffItem = it.m_sel;
2082 m_bNeedSearchLastDiffItem = false;
2084 return m_lastDiffItem;
2088 * @brief Move focus to specified item (and selection if multiple items not selected)
2090 * Moves the focus from item [currentInd] to item [i]
2091 * Additionally, if there are not multiple items selected,
2092 * deselects item [currentInd] and selects item [i]
2094 void CDirView::MoveFocus(int currentInd, int i, int selCount)
2098 // Not multiple items selected, so bring selection with us
2099 m_pList->SetItemState(currentInd, 0, LVIS_SELECTED);
2100 m_pList->SetItemState(currentInd, 0, LVIS_FOCUSED);
2101 m_pList->SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2104 // Move focus to specified item
2105 // (this automatically defocuses old item)
2106 m_pList->SetItemState(i, LVIS_FOCUSED, LVIS_FOCUSED);
2107 m_pList->EnsureVisible(i, FALSE);
2110 void CDirView::OnUpdateSave(CCmdUI* pCmdUI)
2112 pCmdUI->Enable(FALSE);
2115 CDirFrame * CDirView::GetParentFrame()
2117 // can't verify cast without introducing more coupling
2118 // (CDirView doesn't include DirFrame.h)
2119 return static_cast<CDirFrame *>(CListView::GetParentFrame());
2122 void CDirView::OnRefresh()
2124 m_pSavedTreeState.reset(SaveTreeState(GetDiffContext()));
2125 GetDocument()->Rescan();
2128 BOOL CDirView::PreTranslateMessage(MSG* pMsg)
2130 // Handle special shortcuts here
2131 if (pMsg->message == WM_KEYDOWN)
2135 // Check if we got 'ESC pressed' -message
2136 if (pMsg->wParam == VK_ESCAPE)
2138 if (m_pCmpProgressBar != nullptr)
2140 OnBnClickedComparisonStop();
2144 if (m_nEscCloses != 0)
2146 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
2150 // Check if we got 'DEL pressed' -message
2151 if (pMsg->wParam == VK_DELETE)
2153 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_MERGE_DELETE);
2156 int sel = GetFocusedItem();
2157 // Check if we got 'Backspace pressed' -message
2158 if (pMsg->wParam == VK_BACK)
2160 if (!GetDiffContext().m_bRecursive)
2162 OpenParentDirectory();
2165 else if (m_bTreeMode && sel >= 0)
2167 const DIFFITEM& di = GetDiffItem(sel);
2168 assert(di.HasParent());
2171 int i = GetItemIndex(di.GetParentLink());
2173 MoveFocus(sel, i, GetSelectedCount());
2179 DIFFITEM& dip = this->GetDiffItem(sel);
2180 if (pMsg->wParam == VK_LEFT)
2182 if (m_bTreeMode && GetDiffContext().m_bRecursive && (!(dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren()))
2183 PostMessage(WM_KEYDOWN, VK_BACK);
2185 CollapseSubdir(sel);
2188 if (pMsg->wParam == VK_SUBTRACT)
2190 CollapseSubdir(sel);
2193 if (pMsg->wParam == VK_RIGHT)
2195 if (m_bTreeMode && GetDiffContext().m_bRecursive && dip.customFlags & ViewCustomFlags::EXPANDED && dip.HasChildren())
2196 PostMessage(WM_KEYDOWN, VK_DOWN);
2201 if (pMsg->wParam == VK_ADD)
2206 if (pMsg->wParam == VK_MULTIPLY)
2208 ExpandSubdir(sel, true);
2215 // ESC doesn't close window when user is renaming an item.
2216 if (pMsg->wParam == VK_ESCAPE)
2218 m_bUserCancelEdit = true;
2220 // The edit control send LVN_ENDLABELEDIT when it loses focus,
2221 // so we use it to cancel the rename action.
2222 m_pList->SetFocus();
2224 // Stop the ESC before it reach the main frame which might
2225 // cause a program termination.
2230 return CListView::PreTranslateMessage(pMsg);
2233 void CDirView::OnUpdateRefresh(CCmdUI* pCmdUI)
2235 UINT threadState = GetDocument()->m_diffThread.GetThreadState();
2236 pCmdUI->Enable(threadState != CDiffThread::THREAD_COMPARING);
2240 * @brief Called when compare thread asks UI update.
2241 * @note Currently thread asks update after compare is ready
2244 LRESULT CDirView::OnUpdateUIMessage(WPARAM wParam, LPARAM lParam)
2246 UNREFERENCED_PARAMETER(lParam);
2248 CDirDoc * pDoc = GetDocument();
2249 ASSERT(pDoc != nullptr);
2251 if (wParam == CDiffThread::EVENT_COMPARE_COMPLETED)
2253 // Close and destroy the dialog after compare
2254 if (m_pCmpProgressBar != nullptr)
2255 GetParentFrame()->ShowControlBar(m_pCmpProgressBar.get(), FALSE, FALSE);
2256 m_pCmpProgressBar.reset();
2258 pDoc->CompareReady();
2260 if (!pDoc->GetGeneratingReport())
2263 if (!pDoc->GetReportFile().empty())
2265 OnToolsGenerateReport();
2266 pDoc->SetReportFile(_T(""));
2269 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
2274 // If compare took more than TimeToSignalCompare seconds, notify user
2275 clock_t elapsed = clock() - m_compareStart;
2276 GetParentFrame()->SetStatus(
2277 strutils::format(_("Elapsed time: %ld ms"), elapsed).c_str()
2279 if (elapsed > TimeToSignalCompare * CLOCKS_PER_SEC)
2281 GetMainFrame()->StartFlashing();
2283 else if (wParam == CDiffThread::EVENT_COMPARE_PROGRESSED)
2285 InvalidateRect(nullptr, FALSE);
2287 else if (wParam == CDiffThread::EVENT_COLLECT_COMPLETED)
2289 if (m_pSavedTreeState != nullptr)
2291 RestoreTreeState(GetDiffContext(), m_pSavedTreeState.get());
2292 m_pSavedTreeState.reset();
2297 if (m_bExpandSubdirs)
2298 OnViewExpandAllSubdirs();
2304 return 0; // return value unused
2308 BOOL CDirView::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
2310 NMHDR * hdr = reinterpret_cast<NMHDR *>(lParam);
2311 if (hdr->code == HDN_ENDDRAG)
2312 return OnHeaderEndDrag((LPNMHEADER)hdr, pResult);
2313 if (hdr->code == HDN_BEGINDRAG)
2314 return OnHeaderBeginDrag((LPNMHEADER)hdr, pResult);
2316 return CListView::OnNotify(wParam, lParam, pResult);
2319 BOOL CDirView::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
2321 if (uMsg == WM_NOTIFY)
2323 NMHDR *pNMHDR = (NMHDR *)lParam;
2324 switch (pNMHDR->code)
2326 case LVN_GETDISPINFO:
2327 ReflectGetdispinfo((NMLVDISPINFO *)lParam);
2329 case LVN_GETINFOTIPW:
2330 case LVN_GETINFOTIPA:
2334 return CListView::OnChildNotify(uMsg, wParam, lParam, pResult);
2338 * @brief User is starting to drag a column header
2340 bool CDirView::OnHeaderBeginDrag(LPNMHEADER hdr, LRESULT* pResult)
2342 // save column widths before user reorders them
2343 // so we can reload them on the end drag
2344 String secname = GetDocument()->m_nDirs < 3 ? _T("DirView") : _T("DirView3");
2345 theApp.WriteProfileString(secname.c_str(), _T("ColumnWidths"),
2346 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1)).c_str());
2351 * @brief User just finished dragging a column header
2353 bool CDirView::OnHeaderEndDrag(LPNMHEADER hdr, LRESULT* pResult)
2355 int src = hdr->iItem;
2356 int dest = hdr->pitem->iOrder;
2357 bool allowDrop = true;
2358 *pResult = allowDrop ? FALSE : TRUE;
2359 if (allowDrop && src != dest && dest != -1)
2361 m_pColItems->MoveColumn(src, dest);
2368 * @brief Remove any windows reordering of columns
2370 void CDirView::FixReordering()
2373 lvcol.mask = LVCF_ORDER;
2376 lvcol.pszText = nullptr;
2378 for (int i = 0; i < m_pColItems->GetColCount(); ++i)
2381 GetListCtrl().SetColumn(i, &lvcol);
2385 /** @brief Add columns to display, loading width & order from registry. */
2386 void CDirView::LoadColumnHeaderItems()
2388 bool dummyflag = false;
2390 CHeaderCtrl * h = m_pList->GetHeaderCtrl();
2391 if (h->GetItemCount())
2394 while (m_pList->GetHeaderCtrl()->GetItemCount() > 1)
2395 m_pList->DeleteColumn(1);
2398 for (int i = 0; i < m_pColItems->GetDispColCount(); ++i)
2401 lvc.mask = LVCF_FMT + LVCF_SUBITEM + LVCF_TEXT;
2402 lvc.fmt = LVCFMT_LEFT;
2404 lvc.pszText = _T("text");
2406 m_pList->InsertColumn(i, &lvc);
2409 m_pList->DeleteColumn(1);
2413 void CDirView::SetFont(const LOGFONT & lf)
2415 m_font.DeleteObject();
2416 m_font.CreateFontIndirect(&lf);
2417 CWnd::SetFont(&m_font);
2420 /** @brief Fire off a resort of the data, to take place when things stabilize. */
2421 void CDirView::InitiateSort()
2423 PostMessage(WM_TIMER, COLUMN_REORDER);
2426 void CDirView::OnTimer(UINT_PTR nIDEvent)
2428 if (nIDEvent == COLUMN_REORDER)
2430 // Remove the windows reordering, as we're doing it ourselves
2432 // Now redraw screen
2433 UpdateColumnNames();
2434 m_pColItems->LoadColumnWidths(
2435 (const TCHAR *)theApp.GetProfileString(GetDocument()->m_nDirs < 3 ? _T("DirView") : _T("DirView3"), _T("ColumnWidths")),
2436 std::bind(&CListCtrl::SetColumnWidth, m_pList, _1, _2), DefColumnWidth);
2439 else if (nIDEvent == STATUSBAR_UPDATE)
2441 int items = GetSelectedCount();
2442 String msg = (items == 1) ? _("1 item selected") : strutils::format_string1(_("%1 items selected"), strutils::to_str(items));
2443 GetParentFrame()->SetStatus(msg.c_str());
2446 CListView::OnTimer(nIDEvent);
2450 * @brief Change left-side readonly-status
2452 template<SIDE_TYPE stype>
2453 void CDirView::OnReadOnly()
2455 const int index = SideToIndex(GetDiffContext(), stype);
2456 bool bReadOnly = GetDocument()->GetReadOnly(index);
2457 GetDocument()->SetReadOnly(index, !bReadOnly);
2461 * @brief Update left-readonly menu item
2463 template<SIDE_TYPE stype>
2464 void CDirView::OnUpdateReadOnly(CCmdUI* pCmdUI)
2466 const int index = SideToIndex(GetDiffContext(), stype);
2467 bool bReadOnly = GetDocument()->GetReadOnly(index);
2468 if (stype != SIDE_MIDDLE)
2470 pCmdUI->Enable(TRUE);
2471 pCmdUI->SetCheck(bReadOnly);
2475 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
2476 pCmdUI->SetCheck(bReadOnly && GetDocument()->m_nDirs > 2);
2481 * @brief Update left-side readonly statusbar item
2483 void CDirView::OnUpdateStatusLeftRO(CCmdUI* pCmdUI)
2485 bool bROLeft = GetDocument()->GetReadOnly(0);
2486 pCmdUI->Enable(bROLeft);
2490 * @brief Update middle readonly statusbar item
2492 void CDirView::OnUpdateStatusMiddleRO(CCmdUI* pCmdUI)
2494 bool bROMiddle = GetDocument()->GetReadOnly(1);
2495 pCmdUI->Enable(bROMiddle && GetDocument()->m_nDirs > 2);
2499 * @brief Update right-side readonly statusbar item
2501 void CDirView::OnUpdateStatusRightRO(CCmdUI* pCmdUI)
2503 bool bRORight = GetDocument()->GetReadOnly(GetDocument()->m_nDirs - 1);
2504 pCmdUI->Enable(bRORight);
2508 * @brief Open dialog to customize dirview columns
2510 void CDirView::OnCustomizeColumns()
2512 // Located in DirViewColHandler.cpp
2514 String secname = GetDocument()->m_nDirs < 3 ? _T("DirView") : _T("DirView3");
2515 theApp.WriteProfileString(secname.c_str(), _T("ColumnOrders"), m_pColItems->SaveColumnOrders().c_str());
2518 void CDirView::OnCtxtOpenWithUnpacker()
2521 sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
2524 // let the user choose a handler
2525 CSelectUnpackerDlg dlg(GetDiffItem(sel).diffFileInfo[0].filename, this);
2526 // create now a new infoUnpacker to initialize the manual/automatic flag
2527 PackingInfo infoUnpacker(PLUGIN_AUTO);
2528 dlg.SetInitialInfoHandler(&infoUnpacker);
2530 if (dlg.DoModal() == IDOK)
2532 infoUnpacker = dlg.GetInfoHandler();
2533 OpenSelection(SELECTIONTYPE_NORMAL, &infoUnpacker);
2539 void CDirView::OnUpdateCtxtOpenWithUnpacker(CCmdUI* pCmdUI)
2541 if (!GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
2543 pCmdUI->Enable(FALSE);
2547 // we need one selected file, existing on both side
2548 if (m_pList->GetSelectedCount() != 1)
2549 pCmdUI->Enable(FALSE);
2553 sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
2554 const DIFFITEM& di = GetDiffItem(sel);
2555 pCmdUI->Enable(IsItemDeletableOnBoth(GetDiffContext(), di));
2560 * @brief Fill string list with current dirview column registry key names
2562 std::vector<String> CDirView::GetCurrentColRegKeys()
2564 std::vector<String> colKeys;
2565 int nphyscols = GetListCtrl().GetHeaderCtrl()->GetItemCount();
2566 for (int col = 0; col < nphyscols; ++col)
2568 int logcol = m_pColItems->ColPhysToLog(col);
2569 colKeys.push_back(m_pColItems->GetColRegValueNameBase(logcol));
2574 struct FileCmpReport: public IFileCmpReport
2576 explicit FileCmpReport(CDirView *pDirView) : m_pDirView(pDirView) {}
2577 bool operator()(REPORT_TYPE nReportType, IListCtrl *pList, int nIndex, const String &sDestDir, String &sLinkPath) override
2579 const CDiffContext& ctxt = m_pDirView->GetDiffContext();
2580 const DIFFITEM &di = m_pDirView->GetDiffItem(nIndex);
2582 String sLinkFullPath = paths::ConcatPath(ctxt.GetLeftPath(), di.diffFileInfo[0].GetFile());
2584 if (di.diffcode.isDirectory() || !IsItemNavigableDiff(ctxt, di) || IsArchiveFile(sLinkFullPath))
2590 sLinkPath = di.diffFileInfo[0].GetFile();
2592 strutils::replace(sLinkPath, _T("\\"), _T("_"));
2593 sLinkPath += _T(".html");
2594 String sReportPath = paths::ConcatPath(sDestDir, sLinkPath);
2595 bool completed = false;
2597 m_pDirView->MoveFocus(m_pDirView->GetFirstSelectedInd(), nIndex, m_pDirView->GetSelectedCount());
2598 m_pDirView->PostMessage(MSG_GENERATE_FLIE_COMPARE_REPORT,
2599 reinterpret_cast<WPARAM>(sReportPath.c_str()),
2600 reinterpret_cast<LPARAM>(&completed));
2605 while (::PeekMessage(&msg, nullptr, NULL, NULL, PM_NOREMOVE))
2607 if (!AfxGetApp()->PumpMessage())
2617 CDirView *m_pDirView;
2620 LRESULT CDirView::OnGenerateFileCmpReport(WPARAM wParam, LPARAM lParam)
2623 CFrameWnd * pFrame = GetMainFrame()->GetActiveFrame();
2624 IMergeDoc * pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame->GetActiveDocument());
2625 if (pMergeDoc == nullptr)
2626 pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
2628 auto *pReportFileName = reinterpret_cast<const TCHAR *>(wParam);
2629 bool *pCompleted = reinterpret_cast<bool *>(lParam);
2630 if (pMergeDoc != nullptr)
2632 pMergeDoc->GenerateReport(pReportFileName);
2633 pMergeDoc->CloseNow();
2636 while (::PeekMessage(&msg, nullptr, NULL, NULL, PM_NOREMOVE))
2637 if (!AfxGetApp()->PumpMessage())
2639 GetMainFrame()->OnUpdateFrameTitle(FALSE);
2645 * @brief Generate report from dir compare results.
2647 void CDirView::OnToolsGenerateReport()
2649 CDirDoc *pDoc = GetDocument();
2650 DirCmpReportDlg dlg;
2652 dlg.m_sReportFile = pDoc->GetReportFile();
2653 if (dlg.m_sReportFile.empty() && dlg.DoModal() != IDOK)
2656 pDoc->SetGeneratingReport(true);
2657 const CDiffContext& ctxt = GetDiffContext();
2659 PathContext paths = ctxt.GetNormalizedPaths();
2661 // If inside archive, convert paths
2662 if (pDoc->IsArchiveFolders())
2664 for (int i = 0; i < paths.GetSize(); i++)
2665 pDoc->ApplyDisplayRoot(i, paths[i]);
2668 DirCmpReport *pReport = new DirCmpReport(GetCurrentColRegKeys());
2669 pReport->SetRootPaths(paths);
2670 pReport->SetColumns(m_pColItems->GetDispColCount());
2671 pReport->SetFileCmpReport(new FileCmpReport(this));
2672 pReport->SetList(new IListCtrlImpl(m_pList->m_hWnd));
2673 pReport->SetReportType(dlg.m_nReportType);
2674 pReport->SetReportFile(dlg.m_sReportFile);
2675 pReport->SetCopyToClipboard(dlg.m_bCopyToClipboard);
2676 pReport->SetIncludeFileCmpReport(dlg.m_bIncludeFileCmpReport);
2677 pDoc->SetReport(pReport);
2682 * @brief Generate patch from files selected.
2684 * Creates a patch from selected files in active directory compare, or
2685 * active file compare. Files in file compare must be saved before
2688 void CDirView::OnToolsGeneratePatch()
2691 const CDiffContext& ctxt = GetDiffContext();
2693 // Get selected items from folder compare
2694 bool bValidFiles = true;
2695 for (DirItemIterator it = SelBegin(); bValidFiles && it != SelEnd(); ++it)
2697 const DIFFITEM &item = *it;
2698 if (item.diffcode.isBin())
2700 LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING |
2701 MB_DONT_DISPLAY_AGAIN, IDS_CANNOT_CREATE_BINARYPATCH);
2702 bValidFiles = false;
2704 else if (item.diffcode.isDirectory())
2706 LangMessageBox(IDS_CANNOT_CREATE_DIRPATCH, MB_ICONWARNING |
2707 MB_DONT_DISPLAY_AGAIN, IDS_CANNOT_CREATE_DIRPATCH);
2708 bValidFiles = false;
2713 // Format full paths to files (leftFile/rightFile)
2714 String leftFile = item.getFilepath(0, ctxt.GetNormalizedPath(0));
2715 if (!leftFile.empty())
2716 leftFile = paths::ConcatPath(leftFile, item.diffFileInfo[0].filename);
2717 String rightFile = item.getFilepath(1, ctxt.GetNormalizedPath(1));
2718 if (!rightFile.empty())
2719 rightFile = paths::ConcatPath(rightFile, item.diffFileInfo[1].filename);
2721 // Format relative paths to files in folder compare
2722 String leftpatch = item.diffFileInfo[0].path;
2723 if (!leftpatch.empty())
2724 leftpatch += _T("/");
2725 leftpatch += item.diffFileInfo[0].filename;
2726 String rightpatch = item.diffFileInfo[1].path;
2727 if (!rightpatch.empty())
2728 rightpatch += _T("/");
2729 rightpatch += item.diffFileInfo[1].filename;
2730 patcher.AddFiles(leftFile, leftpatch, rightFile, rightpatch);
2734 patcher.CreatePatch();
2738 * @brief Add special items for non-recursive compare
2739 * to directory view.
2741 * Currently only special item is ".." for browsing to
2743 * @return number of items added to view
2745 int CDirView::AddSpecialItems()
2747 CDirDoc *pDoc = GetDocument();
2749 bool bEnable = true;
2750 PathContext pathsParent;
2751 switch (CheckAllowUpwardDirectory(GetDiffContext(), pDoc->m_pTempPathContext, pathsParent))
2753 case AllowUpwardDirectory::No:
2757 AddParentFolderItem(bEnable);
2760 case AllowUpwardDirectory::Never:
2767 * @brief Add "Parent folder" ("..") item to directory view
2769 void CDirView::AddParentFolderItem(bool bEnable)
2771 AddNewItem(0, (DIFFITEM *)SPECIAL_ITEM_POS, bEnable ? DIFFIMG_DIRUP : DIFFIMG_DIRUP_DISABLE, 0);
2775 void CDirView::OnCtxtDirZip()
2777 if (!HasZipSupport())
2779 LangMessageBox(IDS_NO_ZIP_SUPPORT, MB_ICONINFORMATION);
2785 this, LVNI_SELECTED | flag
2786 ).CompressArchive();
2789 void CDirView::ShowShellContextMenu(SIDE_TYPE stype)
2791 CShellContextMenu *pContextMenu = nullptr;
2795 if (m_pShellContextMenuLeft == nullptr)
2796 m_pShellContextMenuLeft.reset(new CShellContextMenu(LeftCmdFirst, LeftCmdLast));
2797 pContextMenu = m_pShellContextMenuLeft.get();
2800 if (m_pShellContextMenuMiddle == nullptr)
2801 m_pShellContextMenuMiddle.reset(new CShellContextMenu(MiddleCmdFirst, MiddleCmdLast));
2802 pContextMenu = m_pShellContextMenuMiddle.get();
2805 if (m_pShellContextMenuRight == nullptr)
2806 m_pShellContextMenuRight.reset(new CShellContextMenu(RightCmdFirst, RightCmdLast));
2807 pContextMenu = m_pShellContextMenuRight.get();
2810 if (pContextMenu!=nullptr && ListShellContextMenu(stype))
2813 GetCursorPos(&point);
2814 HWND hWnd = GetSafeHwnd();
2815 CFrameWnd *pFrame = GetTopLevelFrame();
2816 ASSERT(pFrame != nullptr);
2817 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
2818 pFrame->m_bAutoMenuEnable = FALSE;
2819 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
2821 pContextMenu->InvokeCommand(nCmd, hWnd);
2822 pContextMenu->ReleaseShellContextMenu();
2823 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
2827 template <SIDE_TYPE stype>
2828 void CDirView::OnCtxtDirShellContextMenu()
2830 ShowShellContextMenu(stype);
2834 * @brief Select all visible items in dir compare
2836 void CDirView::OnSelectAll()
2838 // While the user is renaming an item, select all the edited text.
2839 CEdit *pEdit = m_pList->GetEditControl();
2840 if (pEdit != nullptr)
2842 pEdit->SetSel(pEdit->GetWindowTextLength());
2846 int selCount = m_pList->GetItemCount();
2848 for (int i = 0; i < selCount; i++)
2850 // Don't select special items (SPECIAL_ITEM_POS)
2851 DIFFITEM *diffpos = GetItemKey(i);
2852 if (diffpos != (DIFFITEM *)SPECIAL_ITEM_POS)
2853 m_pList->SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2859 * @brief Update "Select All" item
2861 void CDirView::OnUpdateSelectAll(CCmdUI* pCmdUI)
2863 bool bEnable = (!IsLabelEdit()) || (m_pList->GetItemCount() > 0);
2864 pCmdUI->Enable(bEnable);
2868 * @brief Handle clicks in plugin context view in list
2870 void CDirView::OnPluginPredifferMode(UINT nID)
2872 ApplyPluginPrediffSetting(SelBegin(), SelEnd(), GetDiffContext(),
2873 (nID == ID_PREDIFF_AUTO) ? PLUGIN_AUTO : PLUGIN_MANUAL);
2877 * @brief Updates just before displaying plugin context view in list
2879 void CDirView::OnUpdatePluginPredifferMode(CCmdUI* pCmdUI)
2881 // 2004-04-03, Perry
2882 // CMainFrame::OnUpdatePluginUnpackMode handles this for global unpacking
2883 // and is the template to copy, but here, this is a bit tricky
2884 // as a group of files may be selected
2885 // and they may not all have the same setting
2886 // so I'm not trying this right now
2888 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
2890 BCMenu *pPopup = static_cast<BCMenu*>(pCmdUI->m_pSubMenu);
2891 if (pPopup == nullptr)
2894 std::pair<int, int> counts = CountPredifferYesNo(SelBegin(), SelEnd(), GetDiffContext());
2896 CheckContextMenu(pPopup, ID_PREDIFF_AUTO, (counts.first > 0));
2897 CheckContextMenu(pPopup, ID_PREDIFF_MANUAL, (counts.second > 0));
2901 * @brief Refresh cached options.
2903 void CDirView::RefreshOptions()
2905 m_nEscCloses = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
2906 m_bExpandSubdirs = GetOptionsMgr()->GetBool(OPT_DIRVIEW_EXPAND_SUBDIRS);
2907 Options::DirColors::Load(GetOptionsMgr(), m_cachedColors);
2908 m_bUseColors = GetOptionsMgr()->GetBool(OPT_DIRCLR_USE_COLORS);
2909 m_pList->SetBkColor(m_bUseColors ? m_cachedColors.clrDirMargin : GetSysColor(COLOR_WINDOW));
2914 * @brief Copy selected item left side paths (containing filenames) to clipboard.
2916 template<SIDE_TYPE stype>
2917 void CDirView::OnCopyPathnames()
2919 std::list<String> list;
2920 CopyPathnames(SelBegin(), SelEnd(), std::back_inserter(list), stype, GetDiffContext());
2921 PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
2924 void CDirView::OnCopyBothPathnames()
2926 std::list<String> list;
2927 CopyBothPathnames(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
2928 PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
2932 * @brief Copy selected item filenames to clipboard.
2934 void CDirView::OnCopyFilenames()
2936 std::list<String> list;
2937 CopyFilenames(SelBegin(), SelEnd(), std::back_inserter(list));
2938 PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
2942 * @brief Enable/Disable dirview Copy Filenames context menu item.
2944 void CDirView::OnUpdateCopyFilenames(CCmdUI* pCmdUI)
2946 pCmdUI->Enable(Count(&DirActions::IsItemFile).count > 0);
2950 * @brief Copy selected item left side to clipboard.
2952 template<SIDE_TYPE stype>
2953 void CDirView::OnCopyToClipboard()
2955 std::list<String> list;
2956 CopyPathnames(SelBegin(), SelEnd(), std::back_inserter(list), stype, GetDiffContext());
2957 PutFilesToClipboard(list, GetMainFrame()->GetSafeHwnd());
2961 * @brief Copy selected item both side to clipboard.
2963 void CDirView::OnCopyBothToClipboard()
2965 std::list<String> list;
2966 CopyBothPathnames(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
2967 PutFilesToClipboard(list, GetMainFrame()->GetSafeHwnd());
2971 * @brief Rename a selected item on both sides.
2974 void CDirView::OnItemRename()
2976 ASSERT(1 == m_pList->GetSelectedCount());
2977 int nSelItem = m_pList->GetNextItem(-1, LVNI_SELECTED);
2978 ASSERT(-1 != nSelItem);
2979 m_pList->EditLabel(nSelItem);
2983 * @brief Enable/Disable dirview Rename context menu item.
2986 void CDirView::OnUpdateItemRename(CCmdUI* pCmdUI)
2988 bool bEnabled = (1 == m_pList->GetSelectedCount());
2989 pCmdUI->Enable(bEnabled && SelBegin() != SelEnd());
2993 * @brief hide selected item filenames (removes them from the ListView)
2995 void CDirView::OnHideFilenames()
2997 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
2999 while ((it = SelRevBegin()) != SelRevEnd())
3002 SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
3003 DeleteItem(it.m_sel);
3006 m_pList->SetRedraw(TRUE); // Turn updating back on
3010 * @brief update menu item
3012 void CDirView::OnUpdateHideFilenames(CCmdUI* pCmdUI)
3014 pCmdUI->Enable(m_pList->GetSelectedCount() != 0);
3017 /// User chose (context menu) Move left to...
3018 template<SIDE_TYPE stype>
3019 void CDirView::OnCtxtDirMoveTo()
3021 DoDirActionTo(stype, &DirActions::MoveTo<stype>, _("Moving files..."));
3025 * @brief Update "Move | Left to..." item
3027 template<SIDE_TYPE stype>
3028 void CDirView::OnUpdateCtxtDirMoveTo(CCmdUI* pCmdUI)
3030 Counts counts = Count(&DirActions::IsItemMovableToOn<stype>);
3031 pCmdUI->Enable(counts.count > 0);
3032 pCmdUI->SetText(FormatMenuItemStringTo(stype, counts.count, counts.total).c_str());
3036 * @brief Update title after window is resized.
3038 void CDirView::OnSize(UINT nType, int cx, int cy)
3040 CListView::OnSize(nType, cx, cy);
3041 GetDocument()->SetTitle(nullptr);
3045 * @brief Called when user selects 'Delete' from 'Merge' menu.
3047 void CDirView::OnDelete()
3049 DoDirAction(&DirActions::DeleteOnEitherOrBoth, _("Deleting files..."));
3053 * @brief Enables/disables 'Delete' item in 'Merge' menu.
3055 void CDirView::OnUpdateDelete(CCmdUI* pCmdUI)
3057 pCmdUI->Enable(Count(&DirActions::IsItemDeletableOnEitherOrBoth).count > 0);
3061 * @brief Called when item state is changed.
3063 * Show count of selected items in statusbar.
3065 void CDirView::OnItemChanged(NMHDR* pNMHDR, LRESULT* pResult)
3067 NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
3069 // If item's selected state changed
3070 if ((pNMListView->uOldState & LVIS_SELECTED) !=
3071 (pNMListView->uNewState & LVIS_SELECTED))
3073 int items = GetSelectedCount();
3074 String msg = (items == 1) ? _("1 item selected") : strutils::format_string1(_("%1 items selected"), strutils::to_str(items));
3075 GetParentFrame()->SetStatus(msg.c_str());
3081 * @brief Called before user start to item label edit.
3083 * Disable label edit if initiated from a user double-click.
3085 afx_msg void CDirView::OnBeginLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
3087 *pResult = (SelBegin() == SelEnd());
3089 // If label edit is allowed.
3090 if (*pResult == FALSE)
3092 const NMLVDISPINFO *pdi = (NMLVDISPINFO*)pNMHDR;
3093 ASSERT(pdi != nullptr);
3095 // Locate the edit box on the right column in case the user changed the
3097 const int nColPos = m_pColItems->ColLogToPhys(0);
3099 // Get text from the "File Name" column.
3100 CString sText = m_pList->GetItemText(pdi->item.iItem, nColPos);
3101 ASSERT(!sText.IsEmpty());
3103 // Keep only left file name (separated by '|'). This form occurs
3104 // when two files exists with same name but not in same case.
3105 int nPos = sText.Find('|');
3108 sText = sText.Left(nPos);
3111 // Set the edit control with the updated text.
3112 CEdit *pEdit = m_pList->GetEditControl();
3113 ASSERT(pEdit != nullptr);
3114 pEdit->SetWindowText(sText);
3116 m_bUserCancelEdit = false;
3121 * @brief Called when user done with item label edit.
3124 afx_msg void CDirView::OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
3128 // We can't use the normal condition of pszText==`nullptr` to know if the
3129 // user cancels editing when file names had different case (e.g.
3130 // "file.txt|FILE.txt"). The edit text was changed to "file.txt" and
3131 // if the user accept it as the new file name, pszText is `nullptr`.
3133 if (!m_bUserCancelEdit)
3135 CEdit *pEdit = m_pList->GetEditControl();
3136 ASSERT(pEdit != nullptr);
3139 pEdit->GetWindowText(sText);
3141 if (!sText.IsEmpty())
3144 DirItemIterator dirBegin = SelBegin();
3145 *pResult = DoItemRename(dirBegin, GetDiffContext(), String(sText));
3146 } catch (ContentsChangedException& e) {
3147 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
3154 * @brief Called when item is marked for rescan.
3155 * This function marks selected items for rescan and rescans them.
3157 void CDirView::OnMarkedRescan()
3159 std::for_each(SelBegin(), SelEnd(), MarkForRescan);
3160 if (std::distance(SelBegin(), SelEnd()) > 0)
3162 m_pSavedTreeState.reset(SaveTreeState(GetDiffContext()));
3163 GetDocument()->SetMarkedRescan();
3164 GetDocument()->Rescan();
3169 * @brief Called to update the item count in the status bar
3171 void CDirView::OnUpdateStatusNum(CCmdUI* pCmdUI)
3173 String s; // text to display
3175 int count = m_pList->GetItemCount();
3176 int focusItem = GetFocusedItem();
3178 if (focusItem == -1)
3180 // No item has focus
3182 s = strutils::format_string1(_("Items: %1"), strutils::to_str(count));
3186 // Don't show number to special items
3187 DIFFITEM *pos = GetItemKey(focusItem);
3188 if (pos != (DIFFITEM *)SPECIAL_ITEM_POS)
3190 // If compare is non-recursive reduce special items count
3191 bool bRecursive = GetDiffContext().m_bRecursive;
3198 s = strutils::format_string2(_("Item %1 of %2"),
3199 strutils::to_str(focusItem + 1), strutils::to_str(count));
3202 pCmdUI->SetText(s.c_str());
3206 * @brief Show all hidden items.
3208 void CDirView::OnViewShowHiddenItems()
3210 SetItemViewFlag(GetDiffContext(), ViewCustomFlags::VISIBLE, ViewCustomFlags::VISIBILITY);
3216 * @brief Enable/Disable 'Show hidden items' menuitem.
3218 void CDirView::OnUpdateViewShowHiddenItems(CCmdUI* pCmdUI)
3220 pCmdUI->Enable(m_nHiddenItems > 0);
3224 * @brief Toggle Tree Mode
3226 void CDirView::OnViewTreeMode()
3228 m_bTreeMode = !m_bTreeMode;
3229 m_dirfilter.tree_mode = m_bTreeMode;
3230 GetOptionsMgr()->SaveOption(OPT_TREE_MODE, m_bTreeMode); // reverse
3235 * @brief Check/Uncheck 'Tree Mode' menuitem.
3237 void CDirView::OnUpdateViewTreeMode(CCmdUI* pCmdUI)
3239 // Don't show Tree Mode as 'checked' if the
3240 // menu item is greyed out (disabled). Its very confusing.
3241 if( GetDocument()->GetDiffContext().m_bRecursive ) {
3242 pCmdUI->SetCheck(m_bTreeMode);
3243 pCmdUI->Enable(TRUE);
3245 pCmdUI->SetCheck(FALSE);
3246 pCmdUI->Enable(FALSE);
3251 * @brief Expand all subfolders
3253 void CDirView::OnViewExpandAllSubdirs()
3255 ExpandAllSubdirs(GetDiffContext());
3260 * @brief Update "Expand All Subfolders" item
3262 void CDirView::OnUpdateViewExpandAllSubdirs(CCmdUI* pCmdUI)
3264 pCmdUI->Enable(m_bTreeMode && GetDiffContext().m_bRecursive);
3268 * @brief Collapse all subfolders
3270 void CDirView::OnViewCollapseAllSubdirs()
3272 CollapseAllSubdirs(GetDiffContext());
3277 * @brief Update "Collapse All Subfolders" item
3279 void CDirView::OnUpdateViewCollapseAllSubdirs(CCmdUI* pCmdUI)
3281 pCmdUI->Enable(m_bTreeMode && GetDiffContext().m_bRecursive);
3284 void CDirView::OnViewSwapPanes()
3286 GetDocument()->Swap(0, GetDocument()->m_nDirs - 1);
3291 * @brief Show/Hide different files/directories
3293 void CDirView::OnOptionsShowDifferent()
3295 m_dirfilter.show_different = !m_dirfilter.show_different;
3296 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT, m_dirfilter.show_different);
3301 * @brief Show/Hide identical files/directories
3303 void CDirView::OnOptionsShowIdentical()
3305 m_dirfilter.show_identical = !m_dirfilter.show_identical;
3306 GetOptionsMgr()->SaveOption(OPT_SHOW_IDENTICAL, m_dirfilter.show_identical);
3311 * @brief Show/Hide left-only files/directories
3313 void CDirView::OnOptionsShowUniqueLeft()
3315 m_dirfilter.show_unique_left = !m_dirfilter.show_unique_left;
3316 GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_LEFT, m_dirfilter.show_unique_left);
3321 * @brief Show/Hide middle-only files/directories
3323 void CDirView::OnOptionsShowUniqueMiddle()
3325 m_dirfilter.show_unique_middle = !m_dirfilter.show_unique_middle;
3326 GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_MIDDLE, m_dirfilter.show_unique_middle);
3331 * @brief Show/Hide right-only files/directories
3333 void CDirView::OnOptionsShowUniqueRight()
3335 m_dirfilter.show_unique_right = !m_dirfilter.show_unique_right;
3336 GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_RIGHT, m_dirfilter.show_unique_right);
3341 * @brief Show/Hide binary files
3343 void CDirView::OnOptionsShowBinaries()
3345 m_dirfilter.show_binaries = !m_dirfilter.show_binaries;
3346 GetOptionsMgr()->SaveOption(OPT_SHOW_BINARIES, m_dirfilter.show_binaries);
3351 * @brief Show/Hide skipped files/directories
3353 void CDirView::OnOptionsShowSkipped()
3355 m_dirfilter.show_skipped = !m_dirfilter.show_skipped;
3356 GetOptionsMgr()->SaveOption(OPT_SHOW_SKIPPED, m_dirfilter.show_skipped);
3361 * @brief Show/Hide different files/folders (Middle and right are identical)
3363 void CDirView::OnOptionsShowDifferentLeftOnly()
3365 m_dirfilter.show_different_left_only = !m_dirfilter.show_different_left_only;
3366 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_LEFT_ONLY, m_dirfilter.show_different_left_only);
3371 * @brief Show/Hide different files/folders (Left and right are identical)
3373 void CDirView::OnOptionsShowDifferentMiddleOnly()
3375 m_dirfilter.show_different_middle_only = !m_dirfilter.show_different_middle_only;
3376 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_MIDDLE_ONLY, m_dirfilter.show_different_middle_only);
3381 * @brief Show/Hide different files/folders (Left and middle are identical)
3383 void CDirView::OnOptionsShowDifferentRightOnly()
3385 m_dirfilter.show_different_right_only = !m_dirfilter.show_different_right_only;
3386 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_RIGHT_ONLY, m_dirfilter.show_different_right_only);
3391 * @brief Show/Hide missing left only files/folders
3393 void CDirView::OnOptionsShowMissingLeftOnly()
3395 m_dirfilter.show_missing_left_only = !m_dirfilter.show_missing_left_only;
3396 GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_LEFT_ONLY, m_dirfilter.show_missing_left_only);
3401 * @brief Show/Hide missing middle only files/folders
3403 void CDirView::OnOptionsShowMissingMiddleOnly()
3405 m_dirfilter.show_missing_middle_only = !m_dirfilter.show_missing_middle_only;
3406 GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_MIDDLE_ONLY, m_dirfilter.show_missing_middle_only);
3411 * @brief Show/Hide missing right only files/folders
3413 void CDirView::OnOptionsShowMissingRightOnly()
3415 m_dirfilter.show_missing_right_only = !m_dirfilter.show_missing_right_only;
3416 GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_RIGHT_ONLY, m_dirfilter.show_missing_right_only);
3420 void CDirView::OnUpdateOptionsShowdifferent(CCmdUI* pCmdUI)
3422 pCmdUI->SetCheck(m_dirfilter.show_different);
3425 void CDirView::OnUpdateOptionsShowidentical(CCmdUI* pCmdUI)
3427 pCmdUI->SetCheck(m_dirfilter.show_identical);
3430 void CDirView::OnUpdateOptionsShowuniqueleft(CCmdUI* pCmdUI)
3432 pCmdUI->SetCheck(m_dirfilter.show_unique_left);
3435 void CDirView::OnUpdateOptionsShowuniquemiddle(CCmdUI* pCmdUI)
3437 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3438 pCmdUI->SetCheck(m_dirfilter.show_unique_middle);
3441 void CDirView::OnUpdateOptionsShowuniqueright(CCmdUI* pCmdUI)
3443 pCmdUI->SetCheck(m_dirfilter.show_unique_right);
3446 void CDirView::OnUpdateOptionsShowBinaries(CCmdUI* pCmdUI)
3448 pCmdUI->SetCheck(m_dirfilter.show_binaries);
3451 void CDirView::OnUpdateOptionsShowSkipped(CCmdUI* pCmdUI)
3453 pCmdUI->SetCheck(m_dirfilter.show_skipped);
3456 void CDirView::OnUpdateOptionsShowDifferentLeftOnly(CCmdUI* pCmdUI)
3458 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3459 pCmdUI->SetCheck(m_dirfilter.show_different_left_only);
3462 void CDirView::OnUpdateOptionsShowDifferentMiddleOnly(CCmdUI* pCmdUI)
3464 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3465 pCmdUI->SetCheck(m_dirfilter.show_different_middle_only);
3468 void CDirView::OnUpdateOptionsShowDifferentRightOnly(CCmdUI* pCmdUI)
3470 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3471 pCmdUI->SetCheck(m_dirfilter.show_different_right_only);
3474 void CDirView::OnUpdateOptionsShowMissingLeftOnly(CCmdUI* pCmdUI)
3476 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3477 pCmdUI->SetCheck(m_dirfilter.show_missing_left_only);
3480 void CDirView::OnUpdateOptionsShowMissingMiddleOnly(CCmdUI* pCmdUI)
3482 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3483 pCmdUI->SetCheck(m_dirfilter.show_missing_middle_only);
3486 void CDirView::OnUpdateOptionsShowMissingRightOnly(CCmdUI* pCmdUI)
3488 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3489 pCmdUI->SetCheck(m_dirfilter.show_missing_right_only);
3492 void CDirView::OnMergeCompare()
3494 CWaitCursor waitstatus;
3498 template<SELECTIONTYPE seltype>
3499 void CDirView::OnMergeCompare2()
3501 CWaitCursor waitstatus;
3502 OpenSelection(seltype);
3505 void CDirView::OnMergeCompareNonHorizontally()
3507 int sel1, sel2, sel3;
3508 if (!GetSelectedItems(&sel1, &sel2, &sel3))
3510 DirSelectFilesDlg dlg;
3512 dlg.m_pdi[0] = &GetDiffItem(sel1);
3514 dlg.m_pdi[1] = &GetDiffItem(sel2);
3516 dlg.m_pdi[2] = &GetDiffItem(sel3);
3517 if (dlg.DoModal() == IDOK && dlg.m_selectedButtons.size() > 0)
3519 CDirDoc *pDoc = GetDocument();
3520 DWORD dwFlags[3] = {};
3522 for (int nIndex = 0; nIndex < static_cast<int>(dlg.m_selectedButtons.size()); ++nIndex)
3524 int n = dlg.m_selectedButtons[nIndex];
3525 dwFlags[nIndex] = FFILEOPEN_NOMRU | (pDoc->GetReadOnly(n % 3) ? FFILEOPEN_READONLY : 0);
3526 if (dlg.m_pdi[n / 3])
3527 paths.SetPath(nIndex, GetItemFileName(pDoc->GetDiffContext(), *dlg.m_pdi[n / 3], n % 3));
3529 if (paths.GetSize() == 1)
3530 paths.SetRight(_T(""));
3531 Open(paths, dwFlags);
3535 void CDirView::OnMergeCompareXML()
3537 CWaitCursor waitstatus;
3538 PackingInfo packingInfo(PLUGIN_BUILTIN_XML);
3539 OpenSelection(SELECTIONTYPE_NORMAL, &packingInfo);
3542 void CDirView::OnMergeCompareAs(UINT nID)
3544 CWaitCursor waitstatus;
3545 OpenSelectionAs(nID);
3548 void CDirView::OnUpdateMergeCompare(CCmdUI *pCmdUI)
3550 DoUpdateOpen(SELECTIONTYPE_NORMAL, pCmdUI);
3553 template<SELECTIONTYPE seltype>
3554 void CDirView::OnUpdateMergeCompare2(CCmdUI *pCmdUI)
3556 DoUpdateOpen(seltype, pCmdUI);
3559 void CDirView::OnViewCompareStatistics()
3561 CompareStatisticsDlg dlg(GetDocument()->GetCompareStats());
3566 * @brief Count left & right files, and number with editable text encoding
3567 * @param nLeft [out] #files on left side selected
3568 * @param nLeftAffected [out] #files on left side selected which can have text encoding changed
3569 * @param nRight [out] #files on right side selected
3570 * @param nRightAffected [out] #files on right side selected which can have text encoding changed
3572 * Affected files include all except unicode files
3574 void CDirView::FormatEncodingDialogDisplays(CLoadSaveCodepageDlg * dlg)
3576 IntToIntMap currentCodepages = CountCodepages(SelBegin(), SelEnd(), GetDiffContext());
3578 Counts left, middle, right;
3579 left = Count(&DirActions::IsItemEditableEncoding<SIDE_LEFT>);
3580 if (GetDocument()->m_nDirs > 2)
3581 middle = Count(&DirActions::IsItemEditableEncoding<SIDE_MIDDLE>);
3582 right = Count(&DirActions::IsItemEditableEncoding<SIDE_RIGHT>);
3584 // Format strings such as "25 of 30 Files Affected"
3585 String sLeftAffected = FormatFilesAffectedString(left.count, left.total);
3586 String sMiddleAffected = (GetDocument()->m_nDirs < 3) ? _T("") : FormatFilesAffectedString(middle.count, middle.total);
3587 String sRightAffected = FormatFilesAffectedString(right.count, right.total);
3588 dlg->SetLeftRightAffectStrings(sLeftAffected, sMiddleAffected, sRightAffected);
3589 int codepage = currentCodepages.FindMaxKey();
3590 dlg->SetCodepages(codepage);
3594 * @brief Display file encoding dialog to user & handle user's choices
3596 * This handles DirView invocation, so multiple files may be affected
3598 void CDirView::DoFileEncodingDialog()
3600 CLoadSaveCodepageDlg dlg(GetDocument()->m_nDirs);
3601 // set up labels about what will be affected
3602 FormatEncodingDialogDisplays(&dlg);
3603 dlg.EnableSaveCodepage(false); // disallow setting a separate codepage for saving
3606 if (dlg.DoModal() != IDOK)
3610 affected[0] = dlg.DoesAffectLeft();
3611 affected[1] = dlg.DoesAffectMiddle();
3612 affected[SideToIndex(GetDiffContext(), SIDE_RIGHT)] = dlg.DoesAffectRight();
3614 ApplyCodepage(SelBegin(), SelEnd(), GetDiffContext(), affected, dlg.GetLoadCodepage());
3616 m_pList->InvalidateRect(nullptr);
3617 m_pList->UpdateWindow();
3619 // TODO: We could loop through any active merge windows belonging to us
3620 // and see if any of their files are affected
3621 // but, if they've been edited, we cannot throw away the user's work?
3625 * @brief Display file encoding dialog & handle user's actions
3627 void CDirView::OnFileEncoding()
3629 DoFileEncodingDialog();
3632 /** @brief Open help from mainframe when user presses F1*/
3633 void CDirView::OnHelp()
3635 theApp.ShowHelp(DirViewHelpLocation);
3639 * @brief true while user is editing a file name.
3641 bool CDirView::IsLabelEdit() const
3643 return (m_pList->GetEditControl() != nullptr);
3647 * @brief Allow edit "Paste" when renaming an item.
3649 void CDirView::OnEditCopy()
3651 CEdit *pEdit = m_pList->GetEditControl();
3652 if (pEdit != nullptr)
3659 * @brief Allow edit "Cut" when renaming an item.
3661 void CDirView::OnEditCut()
3663 CEdit *pEdit = m_pList->GetEditControl();
3664 if (pEdit != nullptr)
3671 * @brief Allow edit "Paste" when renaming an item.
3673 void CDirView::OnEditPaste()
3675 CEdit *pEdit = m_pList->GetEditControl();
3676 if (pEdit != nullptr)
3683 * @brief Allow edit "Undo" when renaming an item.
3685 void CDirView::OnEditUndo()
3687 CEdit *pEdit = m_pList->GetEditControl();
3688 if (pEdit != nullptr)
3695 * @brief Update the tool bar's "Undo" icon. It should be enabled when
3696 * renaming an item and undo is possible.
3698 void CDirView::OnUpdateEditUndo(CCmdUI* pCmdUI)
3700 CEdit *pEdit = m_pList->GetEditControl();
3701 pCmdUI->Enable(pEdit && pEdit->CanUndo());
3704 * @brief Returns CShellContextMenu object that owns given HMENU.
3706 * @param [in] hMenu Handle to the menu to check ownership of.
3707 * @return Either m_pShellContextMenuLeft, m_pShellContextMenuRight
3708 * or `nullptr` if hMenu is not owned by these two.
3710 CShellContextMenu* CDirView::GetCorrespondingShellContextMenu(HMENU hMenu) const
3712 CShellContextMenu* pMenu = nullptr;
3713 if (m_pShellContextMenuLeft!=nullptr && hMenu == m_pShellContextMenuLeft->GetHMENU())
3714 pMenu = m_pShellContextMenuLeft.get();
3715 else if (m_pShellContextMenuMiddle!=nullptr && hMenu == m_pShellContextMenuMiddle->GetHMENU())
3716 pMenu = m_pShellContextMenuMiddle.get();
3717 else if (m_pShellContextMenuRight!=nullptr && hMenu == m_pShellContextMenuRight->GetHMENU())
3718 pMenu = m_pShellContextMenuRight.get();
3724 * @brief Handle messages related to correct menu working.
3726 * We need to requery shell context menu each time we switch from context menu
3727 * for one side to context menu for other side. Here we check whether we need to
3728 * requery and call ShellContextMenuHandleMenuMessage.
3730 LRESULT CDirView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
3732 while (message == WM_INITMENUPOPUP)
3734 HMENU hMenu = (HMENU)wParam;
3735 if (CShellContextMenu* pMenu = GetCorrespondingShellContextMenu(hMenu))
3737 if (m_hCurrentMenu != hMenu)
3739 // re-query context menu once more, because if context menu was queried for right
3740 // group of files and we are showing menu for left group (or vice versa) menu will
3741 // be shown incorrectly
3742 // also, if context menu was last queried for right group of files and we are
3743 // invoking command for left command will be executed for right group (the last
3744 // group that menu was requested for)
3745 // may be a "feature" of Shell
3747 pMenu->RequeryShellContextMenu();
3748 m_hCurrentMenu = hMenu;
3754 CShellContextMenu* pMenu = GetCorrespondingShellContextMenu(m_hCurrentMenu);
3756 if (pMenu != nullptr)
3759 pMenu->HandleMenuMessage(message, wParam, lParam, res);
3762 return CListView::WindowProc(message, wParam, lParam);
3766 * @brief Implement background item coloring
3768 void CDirView::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
3770 if (!m_bUseColors) {
3774 LPNMLISTVIEW pNM = (LPNMLISTVIEW)pNMHDR;
3775 *pResult = CDRF_DODEFAULT;
3777 if (pNM->hdr.code == NM_CUSTOMDRAW)
3779 LPNMLVCUSTOMDRAW lpC = (LPNMLVCUSTOMDRAW)pNMHDR;
3781 if (lpC->nmcd.dwDrawStage == CDDS_PREPAINT)
3783 *pResult = CDRF_NOTIFYITEMDRAW;
3787 if (lpC->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)
3789 *pResult = CDRF_NOTIFYITEMDRAW;
3793 if (lpC->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM ))
3795 GetColors (static_cast<int>(lpC->nmcd.dwItemSpec), lpC->iSubItem, lpC->clrTextBk, lpC->clrText);
3800 void CDirView::OnBnClickedComparisonStop()
3802 if (m_pCmpProgressBar != nullptr)
3803 m_pCmpProgressBar->EndUpdating();
3804 GetDocument()->AbortCurrentScan();
3807 void CDirView::OnBnClickedComparisonPause()
3809 if (m_pCmpProgressBar != nullptr)
3810 m_pCmpProgressBar->SetPaused(true);
3811 GetDocument()->PauseCurrentScan();
3814 void CDirView::OnBnClickedComparisonContinue()
3816 if (m_pCmpProgressBar != nullptr)
3817 m_pCmpProgressBar->SetPaused(false);
3818 GetDocument()->ContinueCurrentScan();
3822 * @brief Populate colors for items in view, depending on difference status
3824 void CDirView::GetColors (int nRow, int nCol, COLORREF& clrBk, COLORREF& clrText) const
3826 const DIFFITEM& di = GetDiffItem (nRow);
3830 clrText = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_NORMALTEXT);
3831 clrBk = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_BKGND);
3833 else if (di.diffcode.isResultFiltered())
3835 clrText = m_cachedColors.clrDirItemFilteredText;
3836 clrBk = m_cachedColors.clrDirItemFiltered;
3838 else if (!IsItemExistAll(GetDiffContext(), di))
3840 clrText = m_cachedColors.clrDirItemNotExistAllText;
3841 clrBk = m_cachedColors.clrDirItemNotExistAll;
3843 else if (di.diffcode.isResultDiff())
3845 clrText = m_cachedColors.clrDirItemDiffText;
3846 clrBk = m_cachedColors.clrDirItemDiff;
3848 else if (di.diffcode.isResultSame())
3850 clrText = m_cachedColors.clrDirItemEqualText;
3851 clrBk = m_cachedColors.clrDirItemEqual;
3855 clrText = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_NORMALTEXT);
3856 clrBk = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_BKGND);
3860 void CDirView::OnSearch()
3862 CDirDoc *pDoc = GetDocument();
3863 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
3864 int nRows = m_pList->GetItemCount();
3865 for (int currRow = nRows - 1; currRow >= 0; currRow--)
3867 DIFFITEM *pos = GetItemKey(currRow);
3868 if (pos == (DIFFITEM *)SPECIAL_ITEM_POS)
3871 bool bFound = false;
3872 DIFFITEM &di = GetDiffItem(currRow);
3874 for (int i = 0; i < pDoc->m_nDirs; i++)
3876 if (di.diffcode.exists(i) && !di.diffcode.isDirectory())
3878 GetItemFileNames(currRow, &paths);
3880 if (!ufile.OpenReadOnly(paths[i]))
3883 ufile.SetUnicoding(di.diffFileInfo[i].encoding.m_unicoding);
3884 ufile.SetBom(di.diffFileInfo[i].encoding.m_bom);
3885 ufile.SetCodepage(di.diffFileInfo[i].encoding.m_codepage);
3893 if (!ufile.ReadString(line, &lossy))
3896 if (_tcsstr(line.c_str(), _T("DirView")))
3910 SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
3911 DeleteItem(currRow);
3915 m_pList->SetRedraw(TRUE); // Turn updating back on
3919 * @brief Drag files/directories from folder compare listing view.
3921 void CDirView::OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult)
3923 COleDataSource *DropData = new COleDataSource();
3925 std::list<String> list;
3926 CopyPathnamesForDragAndDrop(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
3927 String filesForDroping = strutils::join(list.begin(), list.end(), _T("\n")) + _T("\n");
3929 CSharedFile file(GMEM_DDESHARE | GMEM_MOVEABLE | GMEM_ZEROINIT);
3930 file.Write(filesForDroping.data(), static_cast<unsigned>((filesForDroping.length() + 1) * sizeof(TCHAR)));
3932 HGLOBAL hMem = GlobalReAlloc(file.Detach(), (filesForDroping.length() + 1) * sizeof(TCHAR), 0);
3933 if (hMem != nullptr)
3935 DropData->CacheGlobalData(CF_UNICODETEXT, hMem);
3936 DROPEFFECT de = DropData->DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE, nullptr);
3942 /// Assign column name, using string resource & current column ordering
3943 void CDirView::NameColumn(const DirColInfo *col, int subitem)
3945 int phys = m_pColItems->ColLogToPhys(subitem);
3948 String s = tr(col->idNameContext, col->idName);
3950 lvc.mask = LVCF_TEXT;
3951 lvc.pszText = const_cast<LPTSTR>(s.c_str());
3952 m_pList->SetColumn(phys, &lvc);
3956 /// Load column names from string table
3957 void CDirView::UpdateColumnNames()
3959 int ncols = m_pColItems->GetColCount();
3960 for (int i=0; i<ncols; ++i)
3962 const DirColInfo* col = m_pColItems->GetDirColInfo(i);
3968 * @brief Set alignment of columns.
3970 void CDirView::SetColAlignments()
3972 int ncols = m_pColItems->GetColCount();
3973 for (int i=0; i<ncols; ++i)
3975 const DirColInfo * col = m_pColItems->GetDirColInfo(i);
3977 lvc.mask = LVCF_FMT;
3978 lvc.fmt = col->alignment;
3979 m_pList->SetColumn(m_pColItems->ColLogToPhys(i), &lvc);
3983 CDirView::CompareState::CompareState(const CDiffContext *pCtxt, const DirViewColItems *pColItems, int sortCol, bool bSortAscending, bool bTreeMode)
3985 , pColItems(pColItems)
3987 , bSortAscending(bSortAscending)
3988 , bTreeMode(bTreeMode)
3992 /// Compare two specified rows during a sort operation (windows callback)
3993 int CALLBACK CDirView::CompareState::CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
3995 CompareState *pThis = reinterpret_cast<CompareState*>(lParamSort);
3996 // Sort special items always first in dir view
4002 DIFFITEM *diffposl = (DIFFITEM *)lParam1;
4003 DIFFITEM *diffposr = (DIFFITEM *)lParam2;
4004 const DIFFITEM &ldi = pThis->pCtxt->GetDiffAt(diffposl);
4005 const DIFFITEM &rdi = pThis->pCtxt->GetDiffAt(diffposr);
4006 // compare 'left' and 'right' parameters as appropriate
4007 int retVal = pThis->pColItems->ColSort(pThis->pCtxt, pThis->sortCol, ldi, rdi, pThis->bTreeMode);
4008 // return compare result, considering sort direction
4009 return pThis->bSortAscending ? retVal : -retVal;
4012 /// Add new item to list view
4013 int CDirView::AddNewItem(int i, DIFFITEM *diffpos, int iImage, int iIndent)
4016 lvItem.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE | LVIF_INDENT;
4018 lvItem.iIndent = iIndent;
4019 lvItem.iSubItem = 0;
4020 lvItem.pszText = LPSTR_TEXTCALLBACK;
4021 lvItem.lParam = (LPARAM)diffpos;
4022 lvItem.iImage = iImage;
4023 return GetListCtrl().InsertItem(&lvItem);
4027 * @brief Update listview display of details for specified row
4028 * @note Customising shownd data should be done here
4030 void CDirView::UpdateDiffItemStatus(UINT nIdx)
4032 GetListCtrl().RedrawItems(nIdx, nIdx);
4035 static String rgDispinfoText[2]; // used in function below
4038 * @brief Allocate a text buffer to assign to NMLVDISPINFO::item::pszText
4039 * Quoting from SDK Docs:
4040 * If the LVITEM structure is receiving item text, the pszText and cchTextMax
4041 * members specify the address and size of a buffer. You can either copy text to
4042 * the buffer or assign the address of a string to the pszText member. In the
4043 * latter case, you must not change or delete the string until the corresponding
4044 * item text is deleted or two additional LVN_GETDISPINFO messages have been sent.
4046 static LPTSTR NTAPI AllocDispinfoText(const String &s)
4049 LPCTSTR pszText = (rgDispinfoText[i] = s).c_str();
4051 return (LPTSTR)pszText;
4055 * @brief Respond to LVN_GETDISPINFO message
4057 void CDirView::ReflectGetdispinfo(NMLVDISPINFO *pParam)
4059 int nIdx = pParam->item.iItem;
4060 int i = m_pColItems->ColPhysToLog(pParam->item.iSubItem);
4061 DIFFITEM *key = GetItemKey(nIdx);
4062 if (key == (DIFFITEM *)SPECIAL_ITEM_POS)
4064 if (m_pColItems->IsColName(i))
4066 pParam->item.pszText = _T("..");
4070 if (!GetDocument()->HasDiffs())
4072 const CDiffContext &ctxt = GetDiffContext();
4073 const DIFFITEM &di = ctxt.GetDiffAt(key);
4074 if (pParam->item.mask & LVIF_TEXT)
4076 String s = m_pColItems->ColGetTextToDisplay(&ctxt, i, di);
4077 pParam->item.pszText = AllocDispinfoText(s);
4079 if (pParam->item.mask & LVIF_IMAGE)
4081 pParam->item.iImage = GetColImage(di);
4084 m_bNeedSearchLastDiffItem = true;
4085 m_bNeedSearchFirstDiffItem = true;
4089 * @brief User examines & edits which columns are displayed in dirview, and in which order
4091 void CDirView::OnEditColumns()
4094 // List all the currently displayed columns
4095 for (int col=0; col<GetListCtrl().GetHeaderCtrl()->GetItemCount(); ++col)
4097 int l = m_pColItems->ColPhysToLog(col);
4098 dlg.AddColumn(m_pColItems->GetColDisplayName(l), m_pColItems->GetColDescription(l), l, col);
4100 // Now add all the columns not currently displayed
4102 for (l=0; l<m_pColItems->GetColCount(); ++l)
4104 if (m_pColItems->ColLogToPhys(l)==-1)
4106 dlg.AddColumn(m_pColItems->GetColDisplayName(l), m_pColItems->GetColDescription(l), l);
4110 // Add default order of columns for resetting to defaults
4111 for (l = 0; l < m_pColItems->GetColCount(); ++l)
4113 int phy = m_pColItems->GetColDefaultOrder(l);
4114 dlg.AddDefColumn(m_pColItems->GetColDisplayName(l), l, phy);
4117 if (dlg.DoModal() != IDOK)
4120 String secname = GetDocument()->m_nDirs < 3 ? _T("DirView") : _T("DirView3");
4121 theApp.WriteProfileString(secname.c_str(), _T("ColumnWidths"),
4122 (dlg.m_bReset ? m_pColItems->ResetColumnWidths(DefColumnWidth) :
4123 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1))).c_str());
4125 // Reset our data to reflect the new data from the dialog
4126 const CDirColsDlg::ColumnArray & cols = dlg.GetColumns();
4127 m_pColItems->ClearColumnOrders();
4128 const int sortColumn = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
4129 std::vector<int> colorder(m_pColItems->GetColCount(), -1);
4130 for (CDirColsDlg::ColumnArray::const_iterator iter = cols.begin();
4131 iter != cols.end(); ++iter)
4133 int log = iter->log_col;
4134 int phy = iter->phy_col;
4135 colorder[log] = phy;
4137 // If sorted column was hidden, reset sorting
4138 if (log == sortColumn && phy < 0)
4140 GetOptionsMgr()->Reset((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
4141 GetOptionsMgr()->Reset(OPT_DIRVIEW_SORT_ASCENDING);
4145 m_pColItems->SetColumnOrdering(&colorder[0]);
4147 if (m_pColItems->GetDispColCount() < 1)
4149 // Ignore them if they didn't leave a column showing
4150 m_pColItems->ResetColumnOrdering();
4159 DirActions CDirView::MakeDirActions(DirActions::method_type func) const
4161 const CDirDoc *pDoc = GetDocument();
4162 return DirActions(pDoc->GetDiffContext(), pDoc->GetReadOnly(), func);
4165 DirActions CDirView::MakeDirActions(DirActions::method_type2 func) const
4167 const CDirDoc *pDoc = GetDocument();
4168 return DirActions(pDoc->GetDiffContext(), pDoc->GetReadOnly(), nullptr, func);
4171 const CDiffContext& CDirView::GetDiffContext() const
4173 return GetDocument()->GetDiffContext();
4176 CDiffContext& CDirView::GetDiffContext()
4178 return GetDocument()->GetDiffContext();