1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
10 * @brief Main implementation file for CDirView
15 #include "Constants.h"
17 #include "ClipBoard.h"
18 #include "DirActions.h"
19 #include "DirViewColItems.h"
20 #include "DirFrame.h" // StatePane
22 #include "IMergeDoc.h"
23 #include "FileLocation.h"
26 #include "FileTransform.h"
27 #include "SelectUnpackerDlg.h"
30 #include "OptionsDef.h"
31 #include "OptionsMgr.h"
33 #include "DirCmpReportDlg.h"
34 #include "DirCmpReport.h"
35 #include "DirCompProgressBar.h"
36 #include "CompareStatisticsDlg.h"
37 #include "LoadSaveCodepageDlg.h"
38 #include "ConfirmFolderCopyDlg.h"
39 #include "DirColsDlg.h"
40 #include "DirSelectFilesDlg.h"
42 #include "ShellContextMenu.h"
44 #include "IListCtrlImpl.h"
45 #include "Merge7zFormatMergePluginImpl.h"
46 #include "FileOrFolderSelect.h"
47 #include "IntToIntMap.h"
48 #include "PatchTool.h"
49 #include "SyntaxColors.h"
58 using namespace std::placeholders;
61 * @brief Location for folder compare specific help to open.
63 static TCHAR DirViewHelpLocation[] = _T("::/htmlhelp/Compare_dirs.html");
66 * @brief Limit (in seconds) to signal compare is ready for user.
67 * If compare takes longer than this value (in seconds) we inform
68 * user about it. Current implementation uses MessageBeep(IDOK).
70 const int TimeToSignalCompare = 3;
72 // The resource ID constants/limits for the Shell context menu
73 const UINT LeftCmdFirst = 0x9000; // this should be greater than any of already defined command IDs
74 const UINT RightCmdLast = 0xffff; // maximum available value
75 const UINT LeftCmdLast = LeftCmdFirst + (RightCmdLast - LeftCmdFirst) / 3; // divide available range equally between two context menus
76 const UINT MiddleCmdFirst = LeftCmdLast + 1;
77 const UINT MiddleCmdLast = MiddleCmdFirst + (RightCmdLast - LeftCmdFirst) / 3;
78 const UINT RightCmdFirst = MiddleCmdLast + 1;
80 /////////////////////////////////////////////////////////////////////////////
85 STATUSBAR_UPDATE = 100
88 IMPLEMENT_DYNCREATE(CDirView, CListView)
93 , m_pCmpProgressBar(nullptr)
96 , m_dirfilter(std::bind(&COptionsMgr::GetBool, GetOptionsMgr(), _1))
97 , m_pShellContextMenuLeft(nullptr)
98 , m_pShellContextMenuMiddle(nullptr)
99 , m_pShellContextMenuRight(nullptr)
100 , m_hCurrentMenu(nullptr)
101 , m_pSavedTreeState(nullptr)
102 , m_pColItems(nullptr)
105 m_dwDefaultStyle &= ~LVS_TYPEMASK;
106 // Show selection all the time, so user can see current item even when
107 // focus is elsewhere (ie, on file edit window)
108 m_dwDefaultStyle |= LVS_REPORT | LVS_SHOWSELALWAYS | LVS_EDITLABELS;
110 m_bTreeMode = GetOptionsMgr()->GetBool(OPT_TREE_MODE);
111 m_bExpandSubdirs = GetOptionsMgr()->GetBool(OPT_DIRVIEW_EXPAND_SUBDIRS);
112 m_nEscCloses = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
113 Options::DirColors::Load(GetOptionsMgr(), m_cachedColors);
114 m_bUseColors = GetOptionsMgr()->GetBool(OPT_DIRCLR_USE_COLORS);
117 CDirView::~CDirView()
121 BEGIN_MESSAGE_MAP(CDirView, CListView)
123 //{{AFX_MSG_MAP(CDirView)
124 ON_WM_LBUTTONDBLCLK()
125 ON_COMMAND_RANGE(ID_L2R, ID_R2L, OnDirCopy)
126 ON_UPDATE_COMMAND_UI_RANGE(ID_L2R, ID_R2L, OnUpdateDirCopy)
127 ON_COMMAND(ID_DIR_COPY_LEFT_TO_RIGHT, (OnCtxtDirCopy<SIDE_LEFT, SIDE_RIGHT>))
128 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_RIGHT, (OnUpdateCtxtDirCopy<SIDE_LEFT, SIDE_RIGHT>))
129 ON_COMMAND(ID_DIR_COPY_LEFT_TO_MIDDLE, (OnCtxtDirCopy<SIDE_LEFT, SIDE_MIDDLE>))
130 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_MIDDLE, (OnUpdateCtxtDirCopy<SIDE_LEFT, SIDE_MIDDLE>))
131 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_LEFT, (OnCtxtDirCopy<SIDE_RIGHT, SIDE_LEFT>))
132 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_LEFT, (OnUpdateCtxtDirCopy<SIDE_RIGHT, SIDE_LEFT>))
133 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_MIDDLE, (OnCtxtDirCopy<SIDE_RIGHT, SIDE_MIDDLE>))
134 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_MIDDLE, (OnUpdateCtxtDirCopy<SIDE_RIGHT, SIDE_MIDDLE>))
135 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_LEFT, (OnCtxtDirCopy<SIDE_MIDDLE, SIDE_LEFT>))
136 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_LEFT, (OnUpdateCtxtDirCopy<SIDE_MIDDLE, SIDE_LEFT>))
137 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_RIGHT, (OnCtxtDirCopy<SIDE_MIDDLE, SIDE_RIGHT>))
138 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_RIGHT, (OnUpdateCtxtDirCopy<SIDE_MIDDLE, SIDE_RIGHT>))
139 ON_COMMAND(ID_DIR_DEL_LEFT, OnCtxtDirDel<SIDE_LEFT>)
140 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_LEFT, OnUpdateCtxtDirDel<SIDE_LEFT>)
141 ON_COMMAND(ID_DIR_DEL_RIGHT, OnCtxtDirDel<SIDE_RIGHT>)
142 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_MIDDLE, OnUpdateCtxtDirDel<SIDE_MIDDLE>)
143 ON_COMMAND(ID_DIR_DEL_MIDDLE, OnCtxtDirDel<SIDE_MIDDLE>)
144 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_RIGHT, OnUpdateCtxtDirDel<SIDE_RIGHT>)
145 ON_COMMAND(ID_DIR_DEL_BOTH, OnCtxtDirDelBoth)
146 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_BOTH, OnUpdateCtxtDirDelBoth)
147 ON_COMMAND(ID_DIR_DEL_ALL, OnCtxtDirDelBoth)
148 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_ALL, OnUpdateCtxtDirDelBoth)
149 ON_COMMAND(ID_DIR_OPEN_LEFT, OnCtxtDirOpen<SIDE_LEFT>)
150 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT, OnUpdateCtxtDirOpen<SIDE_LEFT>)
151 ON_COMMAND(ID_DIR_OPEN_LEFT_WITH, OnCtxtDirOpenWith<SIDE_LEFT>)
152 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_WITH, OnUpdateCtxtDirOpenWith<SIDE_LEFT>)
153 ON_COMMAND(ID_DIR_OPEN_LEFT_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_LEFT>)
154 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_LEFT>)
155 ON_COMMAND(ID_DIR_OPEN_MIDDLE, OnCtxtDirOpen<SIDE_MIDDLE>)
156 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE, OnUpdateCtxtDirOpen<SIDE_MIDDLE>)
157 ON_COMMAND(ID_DIR_OPEN_MIDDLE_WITH, OnCtxtDirOpenWith<SIDE_MIDDLE>)
158 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_WITH, OnUpdateCtxtDirOpenWith<SIDE_MIDDLE>)
159 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_MIDDLE>)
160 ON_COMMAND(ID_DIR_OPEN_MIDDLE_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_MIDDLE>)
161 ON_COMMAND(ID_DIR_OPEN_RIGHT, OnCtxtDirOpen<SIDE_RIGHT>)
162 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT, OnUpdateCtxtDirOpen<SIDE_RIGHT>)
163 ON_COMMAND(ID_DIR_OPEN_RIGHT_WITH, OnCtxtDirOpenWith<SIDE_RIGHT>)
164 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_WITH, OnUpdateCtxtDirOpenWith<SIDE_RIGHT>)
165 ON_COMMAND(ID_DIR_OPEN_RIGHT_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_RIGHT>)
166 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_RIGHT>)
167 ON_COMMAND(ID_POPUP_OPEN_WITH_UNPACKER, OnCtxtOpenWithUnpacker)
168 ON_UPDATE_COMMAND_UI(ID_POPUP_OPEN_WITH_UNPACKER, OnUpdateCtxtOpenWithUnpacker)
169 ON_COMMAND(ID_DIR_OPEN_LEFT_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_LEFT>)
170 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_LEFT>)
171 ON_COMMAND(ID_DIR_OPEN_MIDDLE_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_MIDDLE>)
172 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_MIDDLE>)
173 ON_COMMAND(ID_DIR_OPEN_RIGHT_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_RIGHT>)
174 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_RIGHT>)
175 ON_COMMAND(ID_DIR_COPY_LEFT_TO_BROWSE, OnCtxtDirCopyTo<SIDE_LEFT>)
176 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_BROWSE, OnCtxtDirCopyTo<SIDE_MIDDLE>)
177 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_BROWSE, OnCtxtDirCopyTo<SIDE_RIGHT>)
178 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_LEFT>)
179 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_MIDDLE>)
180 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_RIGHT>)
184 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
185 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
186 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
187 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
188 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
189 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
190 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
191 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
192 ON_COMMAND(ID_CURDIFF, OnCurdiff)
193 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
194 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateSave)
195 ON_MESSAGE(MSG_UI_UPDATE, OnUpdateUIMessage)
196 ON_COMMAND(ID_REFRESH, OnRefresh)
197 ON_UPDATE_COMMAND_UI(ID_REFRESH, OnUpdateRefresh)
199 ON_UPDATE_COMMAND_UI(ID_STATUS_RIGHTDIR_RO, OnUpdateStatusRightRO)
200 ON_UPDATE_COMMAND_UI(ID_STATUS_MIDDLEDIR_RO, OnUpdateStatusMiddleRO)
201 ON_UPDATE_COMMAND_UI(ID_STATUS_LEFTDIR_RO, OnUpdateStatusLeftRO)
202 ON_COMMAND(ID_FILE_LEFT_READONLY, OnReadOnly<SIDE_LEFT>)
203 ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateReadOnly<SIDE_LEFT>)
204 ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnReadOnly<SIDE_MIDDLE>)
205 ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateReadOnly<SIDE_MIDDLE>)
206 ON_COMMAND(ID_FILE_RIGHT_READONLY, OnReadOnly<SIDE_RIGHT>)
207 ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateReadOnly<SIDE_RIGHT>)
208 ON_COMMAND(ID_TOOLS_CUSTOMIZECOLUMNS, OnCustomizeColumns)
209 ON_COMMAND(ID_TOOLS_GENERATEREPORT, OnToolsGenerateReport)
210 ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
211 ON_MESSAGE(MSG_GENERATE_FLIE_COMPARE_REPORT, OnGenerateFileCmpReport)
212 ON_COMMAND(ID_DIR_ZIP_LEFT, OnCtxtDirZip<DirItemEnumerator::Left>)
213 ON_COMMAND(ID_DIR_ZIP_MIDDLE, OnCtxtDirZip<DirItemEnumerator::Middle>)
214 ON_COMMAND(ID_DIR_ZIP_RIGHT, OnCtxtDirZip<DirItemEnumerator::Right>)
215 ON_COMMAND(ID_DIR_ZIP_BOTH, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders>)
216 ON_COMMAND(ID_DIR_ZIP_ALL, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders>)
217 ON_COMMAND(ID_DIR_ZIP_BOTH_DIFFS_ONLY, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders | DirItemEnumerator::DiffsOnly>)
218 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_LEFT, OnUpdateCtxtDirCopyTo<SIDE_LEFT>)
219 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_MIDDLE, OnUpdateCtxtDirCopyTo<SIDE_MIDDLE>)
220 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_RIGHT, OnUpdateCtxtDirCopyTo<SIDE_RIGHT>)
221 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_BOTH, OnUpdateCtxtDirCopyBothTo)
222 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_ALL, OnUpdateCtxtDirCopyBothTo)
223 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_BOTH_DIFFS_ONLY, OnUpdateCtxtDirCopyBothDiffsOnlyTo)
224 ON_COMMAND(ID_DIR_SHELL_CONTEXT_MENU_LEFT, OnCtxtDirShellContextMenu<SIDE_LEFT>)
225 ON_COMMAND(ID_DIR_SHELL_CONTEXT_MENU_MIDDLE, OnCtxtDirShellContextMenu<SIDE_MIDDLE>)
226 ON_COMMAND(ID_DIR_SHELL_CONTEXT_MENU_RIGHT, OnCtxtDirShellContextMenu<SIDE_RIGHT>)
227 ON_COMMAND(ID_EDIT_SELECT_ALL, OnSelectAll)
228 ON_UPDATE_COMMAND_UI(ID_EDIT_SELECT_ALL, OnUpdateSelectAll)
229 ON_COMMAND_RANGE(ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, OnPluginPredifferMode)
230 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, OnUpdatePluginPredifferMode)
231 ON_COMMAND(ID_DIR_COPY_PATHNAMES_LEFT, OnCopyPathnames<SIDE_LEFT>)
232 ON_COMMAND(ID_DIR_COPY_PATHNAMES_MIDDLE, OnCopyPathnames<SIDE_MIDDLE>)
233 ON_COMMAND(ID_DIR_COPY_PATHNAMES_RIGHT, OnCopyPathnames<SIDE_RIGHT>)
234 ON_COMMAND(ID_DIR_COPY_PATHNAMES_BOTH, OnCopyBothPathnames)
235 ON_COMMAND(ID_DIR_COPY_PATHNAMES_ALL, OnCopyBothPathnames)
236 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_LEFT, OnUpdateCtxtDirCopy2<SIDE_LEFT>)
237 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_MIDDLE, OnUpdateCtxtDirCopy2<SIDE_MIDDLE>)
238 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_RIGHT, OnUpdateCtxtDirCopy2<SIDE_RIGHT>)
239 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_BOTH, OnUpdateCtxtDirCopyBoth2)
240 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_ALL, OnUpdateCtxtDirCopyBoth2)
241 ON_COMMAND(ID_DIR_COPY_FILENAMES, OnCopyFilenames)
242 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_FILENAMES, OnUpdateCopyFilenames)
243 ON_COMMAND(ID_DIR_COPY_LEFT_TO_CLIPBOARD, OnCopyToClipboard<SIDE_LEFT>)
244 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, OnCopyToClipboard<SIDE_MIDDLE>)
245 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_CLIPBOARD, OnCopyToClipboard<SIDE_RIGHT>)
246 ON_COMMAND(ID_DIR_COPY_BOTH_TO_CLIPBOARD, OnCopyBothToClipboard)
247 ON_COMMAND(ID_DIR_COPY_ALL_TO_CLIPBOARD, OnCopyBothToClipboard)
248 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_CLIPBOARD, OnUpdateCtxtDirCopy2<SIDE_LEFT>)
249 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, OnUpdateCtxtDirCopy2<SIDE_MIDDLE>)
250 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_CLIPBOARD, OnUpdateCtxtDirCopy2<SIDE_RIGHT>)
251 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_BOTH_TO_CLIPBOARD, OnUpdateCtxtDirCopyBoth2)
252 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_ALL_TO_CLIPBOARD, OnUpdateCtxtDirCopyBoth2)
253 ON_COMMAND(ID_DIR_ITEM_RENAME, OnItemRename)
254 ON_UPDATE_COMMAND_UI(ID_DIR_ITEM_RENAME, OnUpdateItemRename)
255 ON_COMMAND(ID_DIR_HIDE_FILENAMES, OnHideFilenames)
256 ON_COMMAND(ID_DIR_MOVE_LEFT_TO_BROWSE, OnCtxtDirMoveTo<SIDE_LEFT>)
257 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_LEFT_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_LEFT>)
258 ON_COMMAND(ID_DIR_MOVE_MIDDLE_TO_BROWSE, OnCtxtDirMoveTo<SIDE_MIDDLE>)
259 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_MIDDLE_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_MIDDLE>)
260 ON_COMMAND(ID_DIR_MOVE_RIGHT_TO_BROWSE, OnCtxtDirMoveTo<SIDE_RIGHT>)
261 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_RIGHT_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_RIGHT>)
262 ON_UPDATE_COMMAND_UI(ID_DIR_HIDE_FILENAMES, OnUpdateHideFilenames)
264 ON_COMMAND(ID_MERGE_DELETE, OnDelete)
265 ON_UPDATE_COMMAND_UI(ID_MERGE_DELETE, OnUpdateDelete)
266 ON_COMMAND(ID_RESCAN, OnMarkedRescan)
267 ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
268 ON_COMMAND(ID_VIEW_SHOWHIDDENITEMS, OnViewShowHiddenItems)
269 ON_UPDATE_COMMAND_UI(ID_VIEW_SHOWHIDDENITEMS, OnUpdateViewShowHiddenItems)
270 ON_COMMAND(ID_MERGE_COMPARE, OnMergeCompare)
271 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE, OnUpdateMergeCompare)
272 ON_COMMAND(ID_MERGE_COMPARE_LEFT1_LEFT2, OnMergeCompare2<SELECTIONTYPE_LEFT1LEFT2>)
273 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT1_LEFT2, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT1LEFT2>)
274 ON_COMMAND(ID_MERGE_COMPARE_RIGHT1_RIGHT2, OnMergeCompare2<SELECTIONTYPE_RIGHT1RIGHT2>)
275 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_RIGHT1_RIGHT2, OnUpdateMergeCompare2<SELECTIONTYPE_RIGHT1RIGHT2>)
276 ON_COMMAND(ID_MERGE_COMPARE_LEFT1_RIGHT2, OnMergeCompare2<SELECTIONTYPE_LEFT1RIGHT2>)
277 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT1_RIGHT2, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT1RIGHT2>)
278 ON_COMMAND(ID_MERGE_COMPARE_LEFT2_RIGHT1, OnMergeCompare2<SELECTIONTYPE_LEFT2RIGHT1>)
279 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT2_RIGHT1, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT2RIGHT1>)
280 ON_COMMAND(ID_MERGE_COMPARE_NONHORIZONTALLY, OnMergeCompareNonHorizontally)
281 ON_COMMAND(ID_MERGE_COMPARE_XML, OnMergeCompareXML)
282 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_XML, OnUpdateMergeCompare)
283 ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_IMAGE, OnMergeCompareAs)
284 ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_IMAGE, OnUpdateMergeCompare)
285 ON_COMMAND(ID_VIEW_TREEMODE, OnViewTreeMode)
286 ON_UPDATE_COMMAND_UI(ID_VIEW_TREEMODE, OnUpdateViewTreeMode)
287 ON_COMMAND(ID_VIEW_EXPAND_ALLSUBDIRS, OnViewExpandAllSubdirs)
288 ON_UPDATE_COMMAND_UI(ID_VIEW_EXPAND_ALLSUBDIRS, OnUpdateViewExpandAllSubdirs)
289 ON_COMMAND(ID_VIEW_COLLAPSE_ALLSUBDIRS, OnViewCollapseAllSubdirs)
290 ON_UPDATE_COMMAND_UI(ID_VIEW_COLLAPSE_ALLSUBDIRS, OnUpdateViewCollapseAllSubdirs)
291 ON_COMMAND(ID_SWAPPANES_SWAP12, (OnViewSwapPanes<0, 1>))
292 ON_COMMAND(ID_SWAPPANES_SWAP23, (OnViewSwapPanes<1, 2>))
293 ON_COMMAND(ID_SWAPPANES_SWAP13, (OnViewSwapPanes<0, 2>))
294 ON_UPDATE_COMMAND_UI(ID_SWAPPANES_SWAP12, (OnUpdateViewSwapPanes<0, 1>))
295 ON_UPDATE_COMMAND_UI(ID_SWAPPANES_SWAP23, (OnUpdateViewSwapPanes<1, 2>))
296 ON_UPDATE_COMMAND_UI(ID_SWAPPANES_SWAP13, (OnUpdateViewSwapPanes<0, 2>))
297 ON_COMMAND(ID_VIEW_DIR_STATISTICS, OnViewCompareStatistics)
298 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENT, OnOptionsShowDifferent)
299 ON_COMMAND(ID_OPTIONS_SHOWIDENTICAL, OnOptionsShowIdentical)
300 ON_COMMAND(ID_OPTIONS_SHOWUNIQUELEFT, OnOptionsShowUniqueLeft)
301 ON_COMMAND(ID_OPTIONS_SHOWUNIQUEMIDDLE, OnOptionsShowUniqueMiddle)
302 ON_COMMAND(ID_OPTIONS_SHOWUNIQUERIGHT, OnOptionsShowUniqueRight)
303 ON_COMMAND(ID_OPTIONS_SHOWBINARIES, OnOptionsShowBinaries)
304 ON_COMMAND(ID_OPTIONS_SHOWSKIPPED, OnOptionsShowSkipped)
305 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTLEFTONLY, OnOptionsShowDifferentLeftOnly)
306 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTMIDDLEONLY, OnOptionsShowDifferentMiddleOnly)
307 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTRIGHTONLY, OnOptionsShowDifferentRightOnly)
308 ON_COMMAND(ID_OPTIONS_SHOWMISSINGLEFTONLY, OnOptionsShowMissingLeftOnly)
309 ON_COMMAND(ID_OPTIONS_SHOWMISSINGMIDDLEONLY, OnOptionsShowMissingMiddleOnly)
310 ON_COMMAND(ID_OPTIONS_SHOWMISSINGRIGHTONLY, OnOptionsShowMissingRightOnly)
311 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENT, OnUpdateOptionsShowdifferent)
312 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWIDENTICAL, OnUpdateOptionsShowidentical)
313 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUELEFT, OnUpdateOptionsShowuniqueleft)
314 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUEMIDDLE, OnUpdateOptionsShowuniquemiddle)
315 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUERIGHT, OnUpdateOptionsShowuniqueright)
316 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWBINARIES, OnUpdateOptionsShowBinaries)
317 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWSKIPPED, OnUpdateOptionsShowSkipped)
318 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTLEFTONLY, OnUpdateOptionsShowDifferentLeftOnly)
319 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTMIDDLEONLY, OnUpdateOptionsShowDifferentMiddleOnly)
320 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTRIGHTONLY, OnUpdateOptionsShowDifferentRightOnly)
321 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGLEFTONLY, OnUpdateOptionsShowMissingLeftOnly)
322 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGMIDDLEONLY, OnUpdateOptionsShowMissingMiddleOnly)
323 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGRIGHTONLY, OnUpdateOptionsShowMissingRightOnly)
324 ON_COMMAND(ID_FILE_ENCODING, OnFileEncoding)
325 ON_COMMAND(ID_HELP, OnHelp)
326 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
327 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
328 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
329 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
330 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
332 ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnClick)
333 ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, OnItemChanged)
334 ON_NOTIFY_REFLECT(LVN_BEGINLABELEDIT, OnBeginLabelEdit)
335 ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, OnEndLabelEdit)
336 ON_NOTIFY_REFLECT(NM_CLICK, OnClick)
337 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBeginDrag)
338 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
339 ON_BN_CLICKED(IDC_COMPARISON_STOP, OnBnClickedComparisonStop)
340 ON_BN_CLICKED(IDC_COMPARISON_PAUSE, OnBnClickedComparisonPause)
341 ON_BN_CLICKED(IDC_COMPARISON_CONTINUE, OnBnClickedComparisonContinue)
344 /////////////////////////////////////////////////////////////////////////////
345 // CDirView diagnostics
349 CDirDoc* CDirView::GetDocument() // non-debug version is inline
351 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDirDoc)));
352 return (CDirDoc*)m_pDocument;
356 /////////////////////////////////////////////////////////////////////////////
357 // CDirView message handlers
359 void CDirView::OnInitialUpdate()
361 const int iconCX = []() {
362 const int cx = GetSystemMetrics(SM_CXSMICON);
371 const int iconCY = iconCX;
372 CListView::OnInitialUpdate();
373 m_pList = &GetListCtrl();
374 m_pIList.reset(new IListCtrlImpl(m_pList->m_hWnd));
375 GetDocument()->SetDirView(this);
376 m_pColItems.reset(new DirViewColItems(GetDocument()->m_nDirs));
378 m_pList->SendMessage(CCM_SETUNICODEFORMAT, TRUE, 0);
380 // Load user-selected font
381 if (GetOptionsMgr()->GetBool(OPT_FONT_DIRCMP + OPT_FONT_USECUSTOM))
383 m_font.CreateFontIndirect(&GetMainFrame()->m_lfDir);
384 CWnd::SetFont(&m_font, TRUE);
388 m_pList->SetBkColor(m_cachedColors.clrDirMargin);
390 // Replace standard header with sort header
391 HWND hWnd = ListView_GetHeader(m_pList->m_hWnd);
393 m_ctlSortHeader.SubclassWindow(hWnd);
395 // Load the icons used for the list view (to reflect diff status)
396 // NOTE: these must be in the exactly the same order as in the `enum`
397 // definition in the DirActions.h file (ref: DIFFIMG_LUNIQUE)
398 VERIFY(m_imageList.Create(iconCX, iconCY, ILC_COLOR32 | ILC_MASK, 15, 1));
400 IDI_LFILE, IDI_MFILE, IDI_RFILE,
401 IDI_MRFILE, IDI_LRFILE, IDI_LMFILE,
402 IDI_NOTEQUALFILE, IDI_EQUALFILE, IDI_FILE,
403 IDI_EQUALBINARY, IDI_BINARYDIFF,
404 IDI_LFOLDER, IDI_MFOLDER, IDI_RFOLDER,
405 IDI_MRFOLDER, IDI_LRFOLDER, IDI_LMFOLDER,
406 IDI_FILESKIP, IDI_FOLDERSKIP,
407 IDI_NOTEQUALFOLDER, IDI_EQUALFOLDER, IDI_FOLDER,
409 IDI_FOLDERUP, IDI_FOLDERUP_DISABLE,
411 IDI_NOTEQUALTEXTFILE, IDI_EQUALTEXTFILE,
412 IDI_NOTEQUALIMAGE, IDI_EQUALIMAGE,
414 for (auto id : icon_ids)
415 VERIFY(-1 != m_imageList.Add((HICON)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(id), IMAGE_ICON, iconCX, iconCY, 0)));
416 m_pList->SetImageList(&m_imageList, LVSIL_SMALL);
418 // Load the icons used for the list view (expanded/collapsed state icons)
419 VERIFY(m_imageState.Create(iconCX, iconCY, ILC_COLOR32 | ILC_MASK, 15, 1));
420 for (auto id : { IDI_TREE_STATE_COLLAPSED, IDI_TREE_STATE_EXPANDED })
421 VERIFY(-1 != m_imageState.Add((HICON)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(id), IMAGE_ICON, iconCX, iconCY, 0)));
423 // Restore column orders as they had them last time they ran
424 m_pColItems->LoadColumnOrders(
425 GetOptionsMgr()->GetString(GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_ORDERS : OPT_DIRVIEW3_COLUMN_ORDERS));
427 // Display column headers (in appropriate order)
430 // Show selection across entire row.u
431 // Also allow user to rearrange columns via drag&drop of headers.
432 // Also enable infotips.
433 DWORD exstyle = LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP;
434 m_pList->SetExtendedStyle(exstyle);
437 BOOL CDirView::PreCreateWindow(CREATESTRUCT& cs)
439 CListView::PreCreateWindow(cs);
440 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
445 * @brief Called before compare is started.
446 * CDirDoc calls this function before new compare is started, so this
447 * is good place to setup GUI for compare.
448 * @param [in] pCompareStats Pointer to class having current compare stats.
450 void CDirView::StartCompare(CompareStats *pCompareStats)
452 if (m_pCmpProgressBar == nullptr)
453 m_pCmpProgressBar.reset(new DirCompProgressBar());
455 if (!::IsWindow(m_pCmpProgressBar->GetSafeHwnd()))
456 m_pCmpProgressBar->Create(GetParentFrame());
458 m_pCmpProgressBar->SetCompareStat(pCompareStats);
459 m_pCmpProgressBar->StartUpdating();
461 GetParentFrame()->ShowControlBar(m_pCmpProgressBar.get(), TRUE, FALSE);
463 m_compareStart = clock();
467 * @brief Called when folder compare row is double-clicked with mouse.
468 * Selected item is opened to folder or file compare.
470 void CDirView::OnLButtonDblClk(UINT nFlags, CPoint point)
474 m_pList->SubItemHitTest(&lvhti);
475 if (lvhti.iItem >= 0)
477 const DIFFITEM& di = GetDiffItem(lvhti.iItem);
478 if (m_bTreeMode && GetDiffContext().m_bRecursive && di.diffcode.isDirectory())
480 if (di.customFlags & ViewCustomFlags::EXPANDED)
481 CollapseSubdir(lvhti.iItem);
483 ExpandSubdir(lvhti.iItem);
487 CWaitCursor waitstatus;
491 CListView::OnLButtonDblClk(nFlags, point);
495 * @brief Load or reload the columns (headers) of the list view
497 void CDirView::ReloadColumns()
499 LoadColumnHeaderItems();
502 m_pColItems->LoadColumnWidths(
503 GetOptionsMgr()->GetString(GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS),
504 std::bind(&CListCtrl::SetColumnWidth, m_pList, _1, _2), GetDefColumnWidth());
509 * @brief Redisplay items in subfolder
510 * @param [in] diffpos First item position in subfolder.
511 * @param [in] level Indent level
512 * @param [in,out] index Index of the item to be inserted.
513 * @param [in,out] alldiffs Number of different items
515 void CDirView::RedisplayChildren(DIFFITEM *diffpos, int level, UINT &index, int &alldiffs)
517 const CDiffContext &ctxt = GetDiffContext();
518 while (diffpos != nullptr)
520 DIFFITEM *curdiffpos = diffpos;
521 const DIFFITEM &di = ctxt.GetNextSiblingDiffPosition(diffpos);
523 if (di.diffcode.isResultDiff() || (!di.diffcode.existAll() && !di.diffcode.isResultFiltered()))
526 bool bShowable = IsShowable(ctxt, di, m_dirfilter);
531 AddNewItem(index, curdiffpos, I_IMAGECALLBACK, level);
533 if (di.HasChildren())
535 m_pList->SetItemState(index - 1, INDEXTOSTATEIMAGEMASK((di.customFlags & ViewCustomFlags::EXPANDED) ? 2 : 1), LVIS_STATEIMAGEMASK);
536 if (di.customFlags & ViewCustomFlags::EXPANDED)
537 RedisplayChildren(ctxt.GetFirstChildDiffPosition(curdiffpos), level + 1, index, alldiffs);
542 if (!ctxt.m_bRecursive || !di.diffcode.isDirectory() || !di.diffcode.existAll())
544 AddNewItem(index, curdiffpos, I_IMAGECALLBACK, 0);
547 if (di.HasChildren())
549 RedisplayChildren(ctxt.GetFirstChildDiffPosition(curdiffpos), level + 1, index, alldiffs);
554 m_firstDiffItem.reset();
555 m_lastDiffItem.reset();
559 * @brief Redisplay folder compare view.
560 * This function clears folder compare view and then adds
561 * items from current compare to it.
563 void CDirView::Redisplay()
565 const CDirDoc *pDoc = GetDocument();
566 const CDiffContext &ctxt = GetDiffContext();
567 PathContext pathsParent;
568 CImageList emptyImageList;
571 // Disable redrawing while adding new items
574 DeleteAllDisplayItems();
576 m_pList->SetImageList((m_bTreeMode && ctxt.m_bRecursive) ? &m_imageState : &emptyImageList, LVSIL_STATE);
578 // If non-recursive compare, add special item(s)
579 if (!ctxt.m_bRecursive ||
580 CheckAllowUpwardDirectory(ctxt, pDoc->m_pTempPathContext, pathsParent) == AllowUpwardDirectory::ParentIsTempPath)
582 cnt += AddSpecialItems();
586 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
587 RedisplayChildren(diffpos, 0, cnt, alldiffs);
588 if (pDoc->m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPLETED)
589 GetParentFrame()->SetLastCompareResult(alldiffs);
590 SortColumnsAppropriately();
595 * @brief User right-clicked somewhere in this view
597 void CDirView::OnContextMenu(CWnd*, CPoint point)
599 if (GetListCtrl().GetItemCount() == 0)
601 // Make sure window is active
602 GetParentFrame()->ActivateFrame();
605 if (point.x == -1 && point.y == -1)
607 //keystroke invocation
610 ClientToScreen(rect);
612 point = rect.TopLeft();
617 // Check if user right-clicked on header
618 // convert screen coordinates to client coordinates of listview
619 CPoint insidePt = point;
620 GetListCtrl().ScreenToClient(&insidePt);
621 // TODO: correct for hscroll ?
622 // Ask header control if click was on one of its header items
623 HDHITTESTINFO hhti = { 0 };
625 int col = static_cast<int>(GetListCtrl().GetHeaderCtrl()->SendMessage(HDM_HITTEST, 0, (LPARAM) & hhti));
628 // Presumably hhti.flags & HHT_ONHEADER is true
629 HeaderContextMenu(point, m_pColItems->ColPhysToLog(col));
632 // bail out if point is not in any row
633 LVHITTESTINFO lhti = { 0 };
635 ScreenToClient(&insidePt);
637 i = GetListCtrl().HitTest(insidePt);
638 TRACE(_T("i=%d\n"), i);
643 ListContextMenu(point, i);
647 * @brief Format context menu string and disable item if it cannot be applied.
649 static void NTAPI FormatContextMenu(BCMenu *pPopup, UINT uIDItem, int n1, int n2 = 0, int n3 = 0)
652 pPopup->GetMenuText(uIDItem, s1, MF_BYCOMMAND);
653 s2.FormatMessage(s1, NumToStr(n1).c_str(), NumToStr(n2).c_str(), NumToStr(n3).c_str());
654 pPopup->SetMenuText(uIDItem, s2, MF_BYCOMMAND);
657 pPopup->EnableMenuItem(uIDItem, MF_GRAYED);
662 * @brief Toggle context menu item
664 static void NTAPI CheckContextMenu(BCMenu *pPopup, UINT uIDItem, BOOL bCheck)
667 pPopup->CheckMenuItem(uIDItem, MF_CHECKED);
669 pPopup->CheckMenuItem(uIDItem, MF_UNCHECKED);
673 * @brief User right-clicked in listview rows
675 void CDirView::ListContextMenu(CPoint point, int /*i*/)
677 CDirDoc* pDoc = GetDocument();
679 VERIFY(menu.LoadMenu(IDR_POPUP_DIRVIEW));
680 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
681 theApp.TranslateMenu(menu.m_hMenu);
683 // 1st submenu of IDR_POPUP_DIRVIEW is for item popup
684 BCMenu *pPopup = static_cast<BCMenu*>(menu.GetSubMenu(0));
685 ASSERT(pPopup != nullptr);
687 if (pDoc->m_nDirs < 3)
689 pPopup->RemoveMenu(ID_DIR_COPY_LEFT_TO_MIDDLE, MF_BYCOMMAND);
690 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_LEFT, MF_BYCOMMAND);
691 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_RIGHT, MF_BYCOMMAND);
692 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_BROWSE, MF_BYCOMMAND);
693 pPopup->RemoveMenu(ID_DIR_COPY_RIGHT_TO_MIDDLE, MF_BYCOMMAND);
694 pPopup->RemoveMenu(ID_DIR_MOVE_MIDDLE_TO_BROWSE, MF_BYCOMMAND);
695 pPopup->RemoveMenu(ID_DIR_DEL_MIDDLE, MF_BYCOMMAND);
696 pPopup->RemoveMenu(ID_DIR_DEL_ALL, MF_BYCOMMAND);
697 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE, MF_BYCOMMAND);
699 for (int i = 0; i < pPopup->GetMenuItemCount(); ++i)
701 if (pPopup->GetMenuItemID(i) == ID_DIR_HIDE_FILENAMES)
702 pPopup->RemoveMenu(i + 3, MF_BYPOSITION);
705 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE_WITHEDITOR, MF_BYCOMMAND);
706 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE_WITH, MF_BYCOMMAND);
707 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_MIDDLE, MF_BYCOMMAND);
708 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_ALL, MF_BYCOMMAND);
709 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, MF_BYCOMMAND);
710 pPopup->RemoveMenu(ID_DIR_COPY_ALL_TO_CLIPBOARD, MF_BYCOMMAND);
711 pPopup->RemoveMenu(ID_DIR_ZIP_MIDDLE, MF_BYCOMMAND);
712 pPopup->RemoveMenu(ID_DIR_ZIP_ALL, MF_BYCOMMAND);
713 pPopup->RemoveMenu(ID_DIR_SHELL_CONTEXT_MENU_MIDDLE, MF_BYCOMMAND);
714 pPopup->RemoveMenu(ID_MERGE_COMPARE_NONHORIZONTALLY, MF_BYCOMMAND);
718 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_BOTH, MF_BYCOMMAND);
719 pPopup->RemoveMenu(ID_DIR_COPY_BOTH_TO_CLIPBOARD, MF_BYCOMMAND);
720 pPopup->RemoveMenu(ID_DIR_ZIP_BOTH, MF_BYCOMMAND);
721 pPopup->RemoveMenu(ID_DIR_DEL_BOTH, MF_BYCOMMAND);
722 pPopup->RemoveMenu(2, MF_BYPOSITION); // Compare Non-horizontally
725 CMenu menuPluginsHolder;
726 menuPluginsHolder.LoadMenu(IDR_POPUP_PLUGINS_SETTINGS);
727 theApp.TranslateMenu(menuPluginsHolder.m_hMenu);
728 String s = _("Plugin Settings");
729 pPopup->AppendMenu(MF_SEPARATOR);
730 pPopup->AppendMenu(MF_POPUP, static_cast<int>(reinterpret_cast<uintptr_t>(menuPluginsHolder.m_hMenu)), s.c_str());
732 CFrameWnd *pFrame = GetTopLevelFrame();
733 ASSERT(pFrame != nullptr);
734 pFrame->m_bAutoMenuEnable = FALSE;
735 // invoke context menu
736 // this will invoke all the OnUpdate methods to enable/disable the individual items
737 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
740 pFrame->m_bAutoMenuEnable = TRUE;
744 * @brief User right-clicked on specified logical column
746 void CDirView::HeaderContextMenu(CPoint point, int /*i*/)
749 VERIFY(menu.LoadMenu(IDR_POPUP_DIRVIEW));
750 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
751 theApp.TranslateMenu(menu.m_hMenu);
752 // 2nd submenu of IDR_POPUP_DIRVIEW is for header popup
753 BCMenu* pPopup = static_cast<BCMenu *>(menu.GetSubMenu(1));
754 ASSERT(pPopup != nullptr);
756 // invoke context menu
757 // this will invoke all the OnUpdate methods to enable/disable the individual items
758 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
763 * @brief Gets Explorer's context menu for a group of selected files.
765 * @param [in] Side whether to get context menu for the files from the left or
767 * @retval true menu successfully retrieved.
768 * @retval falsea an error occurred while retrieving the menu.
770 bool CDirView::ListShellContextMenu(SIDE_TYPE stype)
772 CShellContextMenu* shellContextMenu;
775 shellContextMenu = m_pShellContextMenuMiddle.get(); break;
777 shellContextMenu = m_pShellContextMenuRight.get(); break;
779 shellContextMenu = m_pShellContextMenuLeft.get(); break;
781 shellContextMenu->Initialize();
782 ApplyFolderNameAndFileName(SelBegin(), SelEnd(), stype, GetDiffContext(),
783 [&](const String& path, const String& filename) { shellContextMenu->AddItem(path, filename); });
784 return shellContextMenu->RequeryShellContextMenu();
788 * @brief User chose (main menu) Copy from right to left
790 void CDirView::OnDirCopy(UINT id)
792 bool to_right = (id == ID_L2R) ? true : false;
793 if (GetDocument()->m_nDirs < 3)
796 DoDirAction(&DirActions::Copy<SIDE_LEFT, SIDE_RIGHT>, _("Copying files..."));
798 DoDirAction(&DirActions::Copy<SIDE_RIGHT, SIDE_LEFT>, _("Copying files..."));
804 switch (m_nActivePane)
807 DoDirAction(&DirActions::Copy<SIDE_LEFT, SIDE_MIDDLE>, _("Copying files..."));
811 DoDirAction(&DirActions::Copy<SIDE_MIDDLE, SIDE_RIGHT>, _("Copying files..."));
817 switch (m_nActivePane)
821 DoDirAction(&DirActions::Copy<SIDE_MIDDLE, SIDE_LEFT>, _("Copying files..."));
824 DoDirAction(&DirActions::Copy<SIDE_RIGHT, SIDE_MIDDLE>, _("Copying files..."));
831 /// User chose (context men) Copy from right to left
832 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
833 void CDirView::OnCtxtDirCopy()
835 DoDirAction(&DirActions::Copy<srctype, dsttype>, _("Copying files..."));
838 /// User chose (context menu) Copy left to...
839 template<SIDE_TYPE stype>
840 void CDirView::OnCtxtDirCopyTo()
842 DoDirActionTo(stype, &DirActions::CopyTo<stype>, _("Copying files..."));
845 /// Update context menu Copy Right to Left item
846 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
847 void CDirView::OnUpdateCtxtDirCopy(CCmdUI* pCmdUI)
849 DoUpdateDirCopy<srctype, dsttype>(pCmdUI, eContext);
852 /// Update main menu Copy Right to Left item
853 void CDirView::OnUpdateDirCopy(CCmdUI* pCmdUI)
855 bool to_right = pCmdUI->m_nID == ID_L2R ? true : false;
856 if (GetDocument()->m_nDirs < 3)
859 DoUpdateDirCopy<SIDE_LEFT, SIDE_RIGHT>(pCmdUI, eContext);
861 DoUpdateDirCopy<SIDE_RIGHT, SIDE_LEFT>(pCmdUI, eContext);
867 switch (m_nActivePane)
870 DoUpdateDirCopy<SIDE_LEFT, SIDE_MIDDLE>(pCmdUI, eContext);
874 DoUpdateDirCopy<SIDE_MIDDLE, SIDE_RIGHT>(pCmdUI, eContext);
880 switch (m_nActivePane)
884 DoUpdateDirCopy<SIDE_MIDDLE, SIDE_LEFT>(pCmdUI, eContext);
887 DoUpdateDirCopy<SIDE_RIGHT, SIDE_MIDDLE>(pCmdUI, eContext);
894 void CDirView::DoDirAction(DirActions::method_type func, const String& status_message)
896 CWaitCursor waitstatus;
899 // First we build a list of desired actions
900 FileActionScript actionScript;
901 DirItemWithIndexIterator begin(m_pIList.get(), -1, true);
902 DirItemWithIndexIterator end;
903 FileActionScript *rsltScript;
904 rsltScript = std::accumulate(begin, end, &actionScript, MakeDirActions(func));
905 ASSERT(rsltScript == &actionScript);
906 // Now we prompt, and execute actions
907 ConfirmAndPerformActions(actionScript);
908 } catch (ContentsChangedException& e) {
909 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
910 } catch (FileOperationException& e) {
911 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
915 void CDirView::DoDirActionTo(SIDE_TYPE stype, DirActions::method_type func, const String& status_message)
918 String startPath(m_lastCopyFolder);
919 String selectfolder_title;
921 if (stype == SIDE_LEFT)
922 selectfolder_title = _("Left side - select destination folder:");
923 else if (stype == SIDE_MIDDLE)
924 selectfolder_title = _("Middle side - select destination folder:");
925 else if (stype == SIDE_RIGHT)
926 selectfolder_title = _("Right side - select destination folder:");
928 if (!SelectFolder(destPath, startPath.c_str(), selectfolder_title))
931 m_lastCopyFolder = destPath;
932 CWaitCursor waitstatus;
935 // First we build a list of desired actions
936 FileActionScript actionScript;
937 actionScript.m_destBase = destPath;
938 DirItemWithIndexIterator begin(m_pIList.get(), -1, true);
939 DirItemWithIndexIterator end;
940 FileActionScript *rsltScript;
941 rsltScript = std::accumulate(begin, end, &actionScript, MakeDirActions(func));
942 ASSERT(rsltScript == &actionScript);
943 // Now we prompt, and execute actions
944 ConfirmAndPerformActions(actionScript);
945 } catch (ContentsChangedException& e) {
946 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
950 // Confirm with user, then perform the action list
951 void CDirView::ConfirmAndPerformActions(FileActionScript & actionList)
953 if (actionList.GetActionItemCount() == 0) // Not sure it is possible to get right-click menu without
954 return; // any selected items, but may as well be safe
956 ASSERT(actionList.GetActionItemCount()>0); // Or else the update handler got it wrong
958 // Set parent window so modality is correct and correct window gets focus
960 actionList.SetParentWindow(this->GetSafeHwnd());
963 ConfirmActionList(GetDiffContext(), actionList);
964 } catch (ConfirmationNeededException& e) {
965 ConfirmFolderCopyDlg dlg;
966 dlg.m_caption = e.m_caption;
967 dlg.m_question = e.m_question;
968 dlg.m_fromText = e.m_fromText;
969 dlg.m_toText = e.m_toText;
970 dlg.m_fromPath = e.m_fromPath;
971 dlg.m_toPath = e.m_toPath;
972 INT_PTR ans = dlg.DoModal();
973 if (ans != IDOK && ans != IDYES)
976 PerformActionList(actionList);
980 * @brief Perform an array of actions
981 * @note There can be only COPY or DELETE actions, not both!
983 void CDirView::PerformActionList(FileActionScript & actionScript)
985 // Check option and enable putting deleted items to Recycle Bin
986 if (GetOptionsMgr()->GetBool(OPT_USE_RECYCLE_BIN))
987 actionScript.UseRecycleBin(true);
989 actionScript.UseRecycleBin(false);
991 actionScript.SetParentWindow(GetMainFrame()->GetSafeHwnd());
993 theApp.AddOperation();
994 bool succeeded = actionScript.Run();
996 UpdateAfterFileScript(actionScript);
997 theApp.RemoveOperation();
998 if (!succeeded && !actionScript.IsCanceled())
999 throw FileOperationException(_T("File operation failed"));
1003 * @brief Update results after running FileActionScript.
1004 * This functions is called after script is finished to update
1005 * results (including UI).
1006 * @param [in] actionlist Script that was run.
1008 void CDirView::UpdateAfterFileScript(FileActionScript & actionList)
1010 bool bItemsRemoved = false;
1011 int curSel = GetFirstSelectedInd();
1012 CDiffContext& ctxt = GetDiffContext();
1013 while (actionList.GetActionItemCount()>0)
1015 // Start handling from tail of list, so removing items
1016 // doesn't invalidate our item indexes.
1017 FileActionItem act = actionList.RemoveTailActionItem();
1019 // Update doc (difflist)
1020 UPDATEITEM_TYPE updatetype = UpdateDiffAfterOperation(act, ctxt, GetDiffItem(act.context));
1021 if (updatetype == UPDATEITEM_REMOVE)
1023 DeleteItem(act.context, true);
1024 bItemsRemoved = true;
1026 else if (updatetype == UPDATEITEM_UPDATE)
1027 UpdateDiffItemStatus(act.context);
1030 // Make sure selection is at sensible place if all selected items
1034 UINT selected = GetSelectedCount();
1039 MoveFocus(0, curSel - 1, selected);
1044 Counts CDirView::Count(DirActions::method_type2 func) const
1046 return ::Count(SelBegin(), SelEnd(), MakeDirActions(func));
1049 /// Should Copy to Left be enabled or disabled ? (both main menu & context menu use this)
1050 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
1051 void CDirView::DoUpdateDirCopy(CCmdUI* pCmdUI, eMenuType menuType)
1053 Counts counts = Count(&DirActions::IsItemCopyableOnTo<srctype, dsttype>);
1054 pCmdUI->Enable(counts.count > 0);
1055 if (menuType == eContext)
1056 pCmdUI->SetText(FormatMenuItemString(srctype, dsttype, counts.count, counts.total).c_str());
1060 * @brief Update any resources necessary after a GUI language change
1062 void CDirView::UpdateResources()
1064 UpdateColumnNames();
1065 GetParentFrame()->UpdateResources();
1069 * @brief User just clicked a column, so perform sort
1071 void CDirView::OnColumnClick(NMHDR *pNMHDR, LRESULT *pResult)
1073 // set sort parameters and handle ascending/descending
1074 NM_LISTVIEW* pNMListView = (NM_LISTVIEW*) pNMHDR;
1075 int oldSortColumn = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
1076 int sortcol = m_pColItems->ColPhysToLog(pNMListView->iSubItem);
1077 if (sortcol == oldSortColumn)
1080 bool bSortAscending = GetOptionsMgr()->GetBool(OPT_DIRVIEW_SORT_ASCENDING);
1081 GetOptionsMgr()->SaveOption(OPT_DIRVIEW_SORT_ASCENDING, !bSortAscending);
1085 GetOptionsMgr()->SaveOption((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3, sortcol);
1086 // most columns start off ascending, but not dates
1087 bool bSortAscending = m_pColItems->IsDefaultSortAscending(sortcol);
1088 GetOptionsMgr()->SaveOption(OPT_DIRVIEW_SORT_ASCENDING, bSortAscending);
1091 SortColumnsAppropriately();
1095 void CDirView::SortColumnsAppropriately()
1097 int sortCol = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
1098 if (sortCol == -1 || sortCol >= m_pColItems->GetColCount())
1101 bool bSortAscending = GetOptionsMgr()->GetBool(OPT_DIRVIEW_SORT_ASCENDING);
1102 m_ctlSortHeader.SetSortImage(m_pColItems->ColLogToPhys(sortCol), bSortAscending);
1103 //sort using static CompareFunc comparison function
1104 CompareState cs(&GetDiffContext(), m_pColItems.get(), sortCol, bSortAscending, m_bTreeMode);
1105 GetListCtrl().SortItems(cs.CompareFunc, reinterpret_cast<DWORD_PTR>(&cs));
1107 m_firstDiffItem.reset();
1108 m_lastDiffItem.reset();
1111 /// Do any last minute work as view closes
1112 void CDirView::OnDestroy()
1114 DeleteAllDisplayItems();
1117 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_ORDERS : OPT_DIRVIEW3_COLUMN_ORDERS;
1118 GetOptionsMgr()->SaveOption(keyname, m_pColItems->SaveColumnOrders());
1121 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS;
1122 GetOptionsMgr()->SaveOption(keyname,
1123 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1)));
1126 CListView::OnDestroy();
1128 GetMainFrame()->ClearStatusbarItemCount();
1132 * @brief Open selected item when user presses ENTER key.
1134 void CDirView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
1136 if (nChar == VK_RETURN)
1138 int sel = GetFocusedItem();
1141 const DIFFITEM& di = GetDiffItem(sel);
1142 if (m_bTreeMode && GetDiffContext().m_bRecursive && di.diffcode.isDirectory())
1144 if (di.customFlags & ViewCustomFlags::EXPANDED)
1145 CollapseSubdir(sel);
1151 CWaitCursor waitstatus;
1156 CListView::OnChar(nChar, nRepCnt, nFlags);
1160 * @brief Expand/collapse subfolder when "+/-" icon is clicked.
1162 void CDirView::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
1164 LPNMITEMACTIVATE pNM = (LPNMITEMACTIVATE)pNMHDR;
1165 LVHITTESTINFO lvhti;
1166 lvhti.pt = pNM->ptAction;
1167 m_pList->SubItemHitTest(&lvhti);
1168 if (lvhti.flags == LVHT_ONITEMSTATEICON)
1170 const DIFFITEM &di = GetDiffItem(pNM->iItem);
1171 if (di.customFlags & ViewCustomFlags::EXPANDED)
1172 CollapseSubdir(pNM->iItem);
1174 ExpandSubdir(pNM->iItem);
1181 * @brief Collapse subfolder
1182 * @param [in] sel Folder item index in listview.
1184 void CDirView::CollapseSubdir(int sel)
1186 DIFFITEM& dip = this->GetDiffItem(sel);
1187 if (!m_bTreeMode || !(dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren())
1190 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
1192 dip.customFlags &= ~ViewCustomFlags::EXPANDED;
1193 m_pList->SetItemState(sel, INDEXTOSTATEIMAGEMASK(1), LVIS_STATEIMAGEMASK);
1195 int count = m_pList->GetItemCount();
1196 for (int i = sel + 1; i < count; i++)
1198 const DIFFITEM& di = GetDiffItem(i);
1199 if (!di.IsAncestor(&dip))
1201 m_pList->DeleteItem(i--);
1205 m_pList->SetRedraw(TRUE); // Turn updating back on
1209 * @brief Expand subfolder
1210 * @param [in] sel Folder item index in listview.
1212 void CDirView::ExpandSubdir(int sel, bool bRecursive)
1214 DIFFITEM& dip = GetDiffItem(sel);
1215 if (!m_bTreeMode || (dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren())
1218 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
1219 m_pList->SetItemState(sel, INDEXTOSTATEIMAGEMASK(2), LVIS_STATEIMAGEMASK);
1221 CDiffContext &ctxt = GetDiffContext();
1222 dip.customFlags |= ViewCustomFlags::EXPANDED;
1224 ExpandSubdirs(ctxt, dip);
1226 DIFFITEM *diffpos = ctxt.GetFirstChildDiffPosition(GetItemKey(sel));
1227 UINT indext = sel + 1;
1229 RedisplayChildren(diffpos, dip.GetDepth() + 1, indext, alldiffs);
1231 SortColumnsAppropriately();
1233 m_pList->SetRedraw(TRUE); // Turn updating back on
1237 * @brief Open parent folder if possible.
1239 void CDirView::OpenParentDirectory()
1241 CDirDoc *pDoc = GetDocument();
1242 PathContext pathsParent;
1243 switch (CheckAllowUpwardDirectory(GetDiffContext(), pDoc->m_pTempPathContext, pathsParent))
1245 case AllowUpwardDirectory::ParentIsTempPath:
1246 pDoc->m_pTempPathContext = pDoc->m_pTempPathContext->DeleteHead();
1248 case AllowUpwardDirectory::ParentIsRegularPath:
1250 for (int nIndex = 0; nIndex < pathsParent.GetSize(); ++nIndex)
1251 dwFlags[nIndex] = FFILEOPEN_NOMRU | (pDoc->GetReadOnly(nIndex) ? FFILEOPEN_READONLY : 0);
1252 GetMainFrame()->DoFileOpen(&pathsParent, dwFlags, nullptr, _T(""), GetDiffContext().m_bRecursive, (GetAsyncKeyState(VK_CONTROL) & 0x8000) ? nullptr : pDoc);
1254 case AllowUpwardDirectory::No:
1257 LangMessageBox(IDS_INVALID_DIRECTORY, MB_ICONSTOP);
1263 * @brief Get one or two selected items
1265 * Returns false if 0 or more than 3 items selecte
1267 bool CDirView::GetSelectedItems(int * sel1, int * sel2, int * sel3)
1271 *sel1 = m_pList->GetNextItem(-1, LVNI_SELECTED);
1274 *sel2 = m_pList->GetNextItem(*sel1, LVNI_SELECTED);
1277 *sel3 = m_pList->GetNextItem(*sel2, LVNI_SELECTED);
1280 int extra = m_pList->GetNextItem(*sel3, LVNI_SELECTED);
1281 return (extra == -1);
1285 * @brief Open special items (parent folders etc).
1286 * @param [in] pos1 First item position.
1287 * @param [in] pos2 Second item position.
1289 void CDirView::OpenSpecialItems(DIFFITEM *pos1, DIFFITEM *pos2, DIFFITEM *pos3)
1291 if (pos2==nullptr && pos3==nullptr)
1293 // Browse to parent folder(s) selected
1294 // SPECIAL_ITEM_POS is position for
1295 // special items, but there is currenly
1296 // only one (parent folder)
1297 OpenParentDirectory();
1301 // Parent directory & something else selected
1307 * @brief Creates a pairing folder for unique folder item.
1308 * This function creates a pairing folder for unique folder item in
1309 * folder compare. This way user can browse into unique folder's
1310 * contents and don't necessarily need to copy whole folder structure.
1311 * @return true if user agreed and folder was created.
1313 static bool CreateFoldersPair(const PathContext& paths)
1315 bool created = false;
1316 for (const auto& path : paths)
1318 if (!paths::DoesPathExist(path))
1321 strutils::format_string1(
1322 _("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?"),
1324 int res = AfxMessageBox(message.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN);
1326 created = paths::CreateIfNeeded(path);
1332 void CDirView::Open(const PathContext& paths, DWORD dwFlags[3], FileTextEncoding encoding[3], PackingInfo * infoUnpacker)
1335 for (auto path : paths)
1337 if (paths::DoesPathExist(path) == paths::IS_EXISTING_DIR)
1340 CDirDoc * pDoc = GetDocument();
1344 // Don't add folders to MRU
1345 GetMainFrame()->DoFileOpen(&paths, dwFlags, nullptr, _T(""), GetDiffContext().m_bRecursive, (GetAsyncKeyState(VK_CONTROL) & 0x8000) ? nullptr : pDoc);
1347 else if (HasZipSupport() && std::count_if(paths.begin(), paths.end(), ArchiveGuessFormat) == paths.GetSize())
1349 // Open archives, not adding paths to MRU
1350 GetMainFrame()->DoFileOpen(&paths, dwFlags, nullptr, _T(""), GetDiffContext().m_bRecursive, nullptr, _T(""), infoUnpacker);
1354 // Regular file case
1356 // Binary attributes are set after files are unpacked
1357 // so after plugins such as the MS-Office plugins have had a chance to make them textual
1358 // We haven't done unpacking yet in this diff, but if a binary flag is already set,
1359 // then it was set in a previous diff after unpacking, so we trust it
1361 // Open identical and different files
1362 FileLocation fileloc[3];
1364 const String sUntitled[] = { _("Untitled left"), paths.GetSize() < 3 ? _("Untitled right") : _("Untitled middle"), _("Untitled right") };
1365 for (int i = 0; i < paths.GetSize(); ++i)
1367 if (paths::DoesPathExist(paths[i]) == paths::DOES_NOT_EXIST)
1368 strDesc[i] = sUntitled[i];
1371 fileloc[i].setPath(paths[i]);
1372 fileloc[i].encoding = encoding[i];
1376 GetMainFrame()->ShowAutoMergeDoc(pDoc, paths.GetSize(), fileloc,
1377 dwFlags, strDesc, _T(""), infoUnpacker);
1382 * @brief Open selected files or directories.
1384 * Opens selected files to file compare. If comparing
1385 * directories non-recursively, then subfolders and parent
1386 * folder are opened too.
1388 * This handles the case that one item is selected
1389 * and the case that two items are selected (one on each side)
1391 void CDirView::OpenSelection(SELECTIONTYPE selectionType /*= SELECTIONTYPE_NORMAL*/, PackingInfo * infoUnpacker /*= nullptr*/, bool openableForDir /*= true*/)
1393 Merge7zFormatMergePluginScope scope(infoUnpacker);
1394 CDirDoc * pDoc = GetDocument();
1395 const CDiffContext& ctxt = GetDiffContext();
1397 // First, figure out what was selected (store into pos1 & pos2)
1398 DIFFITEM *pos1 = nullptr, *pos2 = nullptr, *pos3 = nullptr;
1399 int sel1 = -1, sel2 = -1, sel3 = -1;
1400 if (!GetSelectedItems(&sel1, &sel2, &sel3))
1402 // Must have 1 or 2 or 3 items selected
1407 pos1 = GetItemKey(sel1);
1408 ASSERT(pos1 != nullptr);
1411 pos2 = GetItemKey(sel2);
1412 ASSERT(pos2 != nullptr);
1414 pos3 = GetItemKey(sel3);
1417 // Now handle the various cases of what was selected
1419 if (pos1 == (DIFFITEM *)SPECIAL_ITEM_POS)
1421 OpenSpecialItems(pos1, pos2, pos3);
1425 // Common variables which both code paths below are responsible for setting
1427 const DIFFITEM *pdi[3] = {0}; // left & right items (di1==di2 if single selection)
1428 bool isdir = false; // set if we're comparing directories
1430 FileTextEncoding encoding[3];
1434 success = GetOpenTwoItems(ctxt, selectionType, pos1, pos2, pdi,
1435 paths, sel1, sel2, isdir, nPane, encoding, errmsg, openableForDir);
1436 else if (pos2 && pos3)
1437 success = GetOpenThreeItems(ctxt, pos1, pos2, pos3, pdi,
1438 paths, sel1, sel2, sel3, isdir, nPane, encoding, errmsg, openableForDir);
1441 // Only one item selected, so perform diff on its sides
1442 success = GetOpenOneItem(ctxt, pos1, pdi,
1443 paths, sel1, isdir, nPane, encoding, errmsg, openableForDir);
1445 CreateFoldersPair(paths);
1449 if (!errmsg.empty())
1450 AfxMessageBox(errmsg.c_str(), MB_ICONSTOP);
1454 // Now pathLeft, pathRight, di1, di2, and isdir are all set
1455 // We have two items to compare, no matter whether same or different underlying DirView item
1458 for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
1459 dwFlags[nIndex] = FFILEOPEN_NOMRU | (pDoc->GetReadOnly(nPane[nIndex]) ? FFILEOPEN_READONLY : 0);
1461 Open(paths, dwFlags, encoding, infoUnpacker);
1464 void CDirView::OpenSelectionAs(UINT id)
1466 CDirDoc * pDoc = GetDocument();
1467 const CDiffContext& ctxt = GetDiffContext();
1469 // First, figure out what was selected (store into pos1 & pos2 & pos3)
1470 DIFFITEM *pos1 = nullptr, *pos2 = nullptr, *pos3 = nullptr;
1471 int sel1 = -1, sel2 = -1, sel3 = -1;
1472 if (!GetSelectedItems(&sel1, &sel2, &sel3))
1474 // Must have 1 or 2 or 3 items selected
1479 pos1 = GetItemKey(sel1);
1483 pos2 = GetItemKey(sel2);
1484 ASSERT(pos2 != nullptr);
1486 pos3 = GetItemKey(sel3);
1489 // Now handle the various cases of what was selected
1491 if (pos1 == (DIFFITEM *)SPECIAL_ITEM_POS)
1497 // Common variables which both code paths below are responsible for setting
1499 const DIFFITEM *pdi[3]; // left & right items (di1==di2 if single selection)
1500 bool isdir = false; // set if we're comparing directories
1502 FileTextEncoding encoding[3];
1506 success = GetOpenTwoItems(ctxt, SELECTIONTYPE_NORMAL, pos1, pos2, pdi,
1507 paths, sel1, sel2, isdir, nPane, encoding, errmsg, false);
1508 else if (pos2 && pos3)
1509 success = GetOpenThreeItems(ctxt, pos1, pos2, pos3, pdi,
1510 paths, sel1, sel2, sel3, isdir, nPane, encoding, errmsg, false);
1513 // Only one item selected, so perform diff on its sides
1514 success = GetOpenOneItem(ctxt, pos1, pdi,
1515 paths, sel1, isdir, nPane, encoding, errmsg, false);
1519 if (!errmsg.empty())
1520 AfxMessageBox(errmsg.c_str(), MB_ICONSTOP);
1524 // Open identical and different files
1525 DWORD dwFlags[3] = { 0 };
1526 FileLocation fileloc[3];
1527 for (int pane = 0; pane < paths.GetSize(); pane++)
1529 fileloc[pane].setPath(paths[pane]);
1530 fileloc[pane].encoding = encoding[pane];
1531 dwFlags[pane] |= FFILEOPEN_NOMRU | (pDoc->GetReadOnly(nPane[pane]) ? FFILEOPEN_READONLY : 0);
1533 GetMainFrame()->ShowMergeDoc(id, pDoc, paths.GetSize(), fileloc, dwFlags, nullptr);
1536 /// User chose (context menu) delete left
1537 template<SIDE_TYPE stype>
1538 void CDirView::OnCtxtDirDel()
1540 DoDirAction(&DirActions::DeleteOn<stype>, _("Deleting files..."));
1543 /// User chose (context menu) delete both
1544 void CDirView::OnCtxtDirDelBoth()
1546 DoDirAction(&DirActions::DeleteOnBoth, _("Deleting files..."));
1549 /// Enable/disable Delete Left menu choice on context menu
1550 template<SIDE_TYPE stype>
1551 void CDirView::OnUpdateCtxtDirDel(CCmdUI* pCmdUI)
1553 Counts counts = Count(&DirActions::IsItemDeletableOn<stype>);
1554 pCmdUI->Enable(counts.count > 0);
1555 pCmdUI->SetText(FormatMenuItemString(stype, counts.count, counts.total).c_str());
1558 /// Enable/disable Delete Both menu choice on context menu
1559 void CDirView::OnUpdateCtxtDirDelBoth(CCmdUI* pCmdUI)
1561 Counts counts = Count(&DirActions::IsItemDeletableOnBoth);
1562 pCmdUI->Enable(counts.count > 0);
1563 pCmdUI->SetText(FormatMenuItemStringAll(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1567 * @brief Update "Copy | Right to..." item
1569 template<SIDE_TYPE stype>
1570 void CDirView::OnUpdateCtxtDirCopyTo(CCmdUI* pCmdUI)
1572 Counts counts = Count(&DirActions::IsItemCopyableToOn<stype>);
1573 pCmdUI->Enable(counts.count > 0);
1574 pCmdUI->SetText(FormatMenuItemStringTo(stype, counts.count, counts.total).c_str());
1577 void CDirView::OnUpdateCtxtDirCopyBothTo(CCmdUI* pCmdUI)
1579 Counts counts = Count(&DirActions::IsItemCopyableBothToOn);
1580 pCmdUI->Enable(counts.count > 0);
1581 pCmdUI->SetText(FormatMenuItemStringAllTo(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1584 void CDirView::OnUpdateCtxtDirCopyBothDiffsOnlyTo(CCmdUI* pCmdUI)
1586 Counts counts = Count(&DirActions::IsItemNavigableDiff);
1587 pCmdUI->Enable(counts.count > 0);
1588 pCmdUI->SetText(FormatMenuItemStringDifferencesTo(counts.count, counts.total).c_str());
1592 * @brief Update "Copy | Left/Right/Both " item
1594 template<SIDE_TYPE stype>
1595 void CDirView::OnUpdateCtxtDirCopy2(CCmdUI* pCmdUI)
1597 Counts counts = Count(&DirActions::IsItemCopyableToOn<stype>);
1598 pCmdUI->Enable(counts.count > 0);
1599 pCmdUI->SetText(FormatMenuItemString(stype, counts.count, counts.total).c_str());
1602 void CDirView::OnUpdateCtxtDirCopyBoth2(CCmdUI* pCmdUI)
1604 Counts counts = Count(&DirActions::IsItemCopyableBothToOn);
1605 pCmdUI->Enable(counts.count > 0);
1606 pCmdUI->SetText(FormatMenuItemStringAll(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1610 * @brief Get keydata associated with item in given index.
1611 * @param [in] idx Item's index to list in UI.
1612 * @return Key for item in given index.
1614 DIFFITEM *CDirView::GetItemKey(int idx) const
1616 return (DIFFITEM *) m_pList->GetItemData(idx);
1619 // SetItemKey & GetItemKey encapsulate how the display list items
1620 // are mapped to DiffItems, which in turn are DiffContext keys to the actual DIFFITEM data
1623 * @brief Get DIFFITEM data for item.
1624 * This function returns DIFFITEM data for item in given index in GUI.
1625 * @param [in] sel Item's index in folder compare GUI list.
1626 * @return DIFFITEM for item.
1628 const DIFFITEM &CDirView::GetDiffItem(int sel) const
1630 CDirView * pDirView = const_cast<CDirView *>(this);
1631 return pDirView->GetDiffItem(sel);
1635 * Given index in list control, get modifiable reference to its DIFFITEM data
1637 DIFFITEM &CDirView::GetDiffItem(int sel)
1639 DIFFITEM *diffpos = GetItemKey(sel);
1641 // If it is special item, return empty DIFFITEM
1642 if (diffpos == (DIFFITEM *)SPECIAL_ITEM_POS)
1644 return *DIFFITEM::GetEmptyItem();
1646 return GetDiffContext().GetDiffRefAt(diffpos);
1649 void CDirView::DeleteItem(int sel, bool removeDIFFITEM)
1651 DIFFITEM *diffpos = GetItemKey(sel);
1652 if (diffpos == (DIFFITEM*)SPECIAL_ITEM_POS)
1656 CollapseSubdir(sel);
1657 m_pList->DeleteItem(sel);
1659 else if (GetDiffContext().m_bRecursive || diffpos->HasChildren())
1662 for (it = RevBegin(); it != RevEnd(); )
1665 int cursel = it.m_sel;
1667 if (di.IsAncestor(diffpos) || diffpos == &di)
1668 m_pList->DeleteItem(cursel);
1673 m_pList->DeleteItem(sel);
1677 if (diffpos->HasChildren())
1678 diffpos->RemoveChildren();
1679 diffpos->DelinkFromSiblings();
1683 m_firstDiffItem.reset();
1684 m_lastDiffItem.reset();
1687 void CDirView::DeleteAllDisplayItems()
1689 // item data are just positions (diffposes)
1690 // that is, they contain no memory needing to be freed
1691 m_pList->DeleteAllItems();
1693 m_firstDiffItem.reset();
1694 m_lastDiffItem.reset();
1698 * @brief Given key, get index of item which has it stored.
1699 * This function searches from list in UI.
1701 int CDirView::GetItemIndex(DIFFITEM *key)
1703 LVFINDINFO findInfo;
1705 findInfo.flags = LVFI_PARAM; // Search for itemdata
1706 findInfo.lParam = (LPARAM)key;
1707 return m_pList->FindItem(&findInfo);
1711 * @brief Get the file names on both sides for specified item.
1712 * @note Return empty strings if item is special item.
1714 void CDirView::GetItemFileNames(int sel, String& strLeft, String& strRight) const
1716 DIFFITEM *diffpos = GetItemKey(sel);
1717 if (diffpos == (DIFFITEM *)SPECIAL_ITEM_POS)
1724 const CDiffContext& ctxt = GetDiffContext();
1725 ::GetItemFileNames(ctxt, ctxt.GetDiffAt(diffpos), strLeft, strRight);
1730 * @brief Get the file names on both sides for specified item.
1731 * @note Return empty strings if item is special item.
1733 void CDirView::GetItemFileNames(int sel, PathContext * paths) const
1735 DIFFITEM *diffpos = GetItemKey(sel);
1736 if (diffpos == (DIFFITEM *)SPECIAL_ITEM_POS)
1738 for (int nIndex = 0; nIndex < GetDocument()->m_nDirs; nIndex++)
1739 paths->SetPath(nIndex, _T(""));
1743 const CDiffContext& ctxt = GetDiffContext();
1744 *paths = ::GetItemFileNames(ctxt, ctxt.GetDiffAt(diffpos));
1749 * @brief Open selected file with registered application.
1750 * Uses shell file associations to open file with registered
1751 * application. We first try to use "Edit" action which should
1752 * open file to editor, since we are more interested editing
1753 * files than running them (scripts).
1754 * @param [in] stype Side of file to open.
1756 void CDirView::DoOpen(SIDE_TYPE stype)
1758 int sel = GetSingleSelectedItem();
1759 if (sel == -1) return;
1760 DirItemIterator dirBegin = SelBegin();
1761 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1762 if (file.empty()) return;
1763 HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), file.c_str(), 0, 0, SW_SHOWNORMAL);
1764 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
1765 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), file.c_str(), 0, 0, SW_SHOWNORMAL);
1766 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
1770 /// Open with dialog for file on selected side
1771 void CDirView::DoOpenWith(SIDE_TYPE stype)
1773 int sel = GetSingleSelectedItem();
1774 if (sel == -1) return;
1775 DirItemIterator dirBegin = SelBegin();
1776 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1777 if (file.empty()) return;
1779 if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH)) return;
1780 sysdir.ReleaseBuffer();
1781 CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + file.c_str();
1782 ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg, sysdir, SW_SHOWNORMAL);
1785 /// Open selected file on specified side to external editor
1786 void CDirView::DoOpenWithEditor(SIDE_TYPE stype)
1788 int sel = GetSingleSelectedItem();
1789 if (sel == -1) return;
1790 DirItemIterator dirBegin = SelBegin();
1791 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1792 if (file.empty()) return;
1794 theApp.OpenFileToExternalEditor(file);
1797 void CDirView::DoOpenParentFolder(SIDE_TYPE stype)
1799 int sel = GetSingleSelectedItem();
1800 if (sel == -1) return;
1801 DirItemIterator dirBegin = SelBegin();
1802 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1803 if (file.empty()) return;
1804 String parentFolder = paths::GetParentPath(file);
1805 ShellExecute(::GetDesktopWindow(), _T("open"), parentFolder.c_str(), 0, 0, SW_SHOWNORMAL);
1808 /// User chose (context menu) open left
1809 template<SIDE_TYPE stype>
1810 void CDirView::OnCtxtDirOpen()
1815 /// User chose (context menu) open left with
1816 template<SIDE_TYPE stype>
1817 void CDirView::OnCtxtDirOpenWith()
1822 /// User chose (context menu) open left with editor
1823 template<SIDE_TYPE stype>
1824 void CDirView::OnCtxtDirOpenWithEditor()
1826 DoOpenWithEditor(stype);
1829 /// User chose (context menu) open left parent folder
1830 template<SIDE_TYPE stype>
1831 void CDirView::OnCtxtDirOpenParentFolder()
1833 DoOpenParentFolder(stype);
1836 /// Update context menuitem "Open left | with editor"
1837 template<SIDE_TYPE stype>
1838 void CDirView::OnUpdateCtxtDirOpenWithEditor(CCmdUI* pCmdUI)
1840 Counts counts = Count(&DirActions::IsItemOpenableOnWith<stype>);
1841 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1844 // return selected item index, or -1 if none or multiple
1845 int CDirView::GetSingleSelectedItem() const
1847 int sel = -1, sel2 = -1;
1848 sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
1849 if (sel == -1) return -1;
1850 sel2 = m_pList->GetNextItem(sel, LVNI_SELECTED);
1851 if (sel2 != -1) return -1;
1854 // Enable/disable Open Left menu choice on context menu
1855 template<SIDE_TYPE stype>
1856 void CDirView::OnUpdateCtxtDirOpen(CCmdUI* pCmdUI)
1858 Counts counts = Count(&DirActions::IsItemOpenableOn<stype>);
1859 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1862 // Enable/disable Open Left With menu choice on context menu
1863 template<SIDE_TYPE stype>
1864 void CDirView::OnUpdateCtxtDirOpenWith(CCmdUI* pCmdUI)
1866 Counts counts = Count(&DirActions::IsItemOpenableOnWith<stype>);
1867 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1870 // Enable/disable Open Parent Folder menu choice on context menu
1871 template<SIDE_TYPE stype>
1872 void CDirView::OnUpdateCtxtDirOpenParentFolder(CCmdUI* pCmdUI)
1874 Counts counts = Count(&DirActions::IsParentFolderOpenable<stype>);
1875 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1879 void CDirView::DoUpdateOpen(SELECTIONTYPE selectionType, CCmdUI* pCmdUI, bool openableForDir /*= true*/)
1881 int sel1 = -1, sel2 = -1, sel3 = -1;
1882 if (!GetSelectedItems(&sel1, &sel2, &sel3))
1884 // 0 items or more than 2 items seleted
1885 pCmdUI->Enable(FALSE);
1890 // One item selected
1891 if (selectionType != SELECTIONTYPE_NORMAL)
1893 pCmdUI->Enable(FALSE);
1896 if (!openableForDir)
1898 const DIFFITEM& di1 = GetDiffItem(sel1);
1899 if (di1.diffcode.isDirectory())
1901 pCmdUI->Enable(FALSE);
1906 else if (sel3 == -1)
1908 // Two items selected
1909 const DIFFITEM& di1 = GetDiffItem(sel1);
1910 const DIFFITEM& di2 = GetDiffItem(sel2);
1911 if (!AreItemsOpenable(GetDiffContext(), selectionType, di1, di2, openableForDir))
1913 pCmdUI->Enable(FALSE);
1919 // Three items selected
1920 const DIFFITEM& di1 = GetDiffItem(sel1);
1921 const DIFFITEM& di2 = GetDiffItem(sel2);
1922 const DIFFITEM& di3 = GetDiffItem(sel3);
1923 if (selectionType != SELECTIONTYPE_NORMAL || !::AreItemsOpenable(GetDiffContext(), di1, di2, di3, openableForDir))
1925 pCmdUI->Enable(FALSE);
1929 pCmdUI->Enable(TRUE);
1933 * @brief Return count of selected items in folder compare.
1935 UINT CDirView::GetSelectedCount() const
1937 return m_pList->GetSelectedCount();
1941 * @brief Return index of first selected item in folder compare.
1943 int CDirView::GetFirstSelectedInd()
1945 return m_pList->GetNextItem(-1, LVNI_SELECTED);
1949 // If none or one item selected select found item
1950 // This is used for scrolling to first diff too
1951 void CDirView::OnFirstdiff()
1953 DirItemIterator it =
1954 std::find_if(Begin(), End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
1956 MoveFocus(GetFirstSelectedInd(), it.m_sel, GetSelectedCount());
1959 void CDirView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1961 pCmdUI->Enable(GetFirstDifferentItem() > -1);
1965 // If none or one item selected select found item
1966 void CDirView::OnLastdiff()
1968 DirItemIterator it =
1969 std::find_if(RevBegin(), RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
1971 MoveFocus(GetFirstSelectedInd(), it.m_sel, GetSelectedCount());
1974 void CDirView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1976 pCmdUI->Enable(GetFirstDifferentItem() > -1);
1979 bool CDirView::HasNextDiff()
1981 int lastDiff = GetLastDifferentItem();
1983 // Check if different files were found and
1984 // there is different item after focused item
1985 return (lastDiff > -1) && (GetFocusedItem() < lastDiff);
1988 bool CDirView::HasPrevDiff()
1990 int firstDiff = GetFirstDifferentItem();
1992 // Check if different files were found and
1993 // there is different item before focused item
1994 return (firstDiff > -1) && (firstDiff < GetFocusedItem());
1997 void CDirView::MoveToNextDiff()
1999 int currentInd = GetFocusedItem();
2000 DirItemIterator begin(m_pIList.get(), currentInd + 1);
2001 DirItemIterator it =
2002 std::find_if(begin, End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2004 MoveFocus(currentInd, it.m_sel, GetSelectedCount());
2007 void CDirView::MoveToPrevDiff()
2009 int currentInd = GetFocusedItem();
2010 if (currentInd <= 0)
2012 DirItemIterator begin(m_pIList.get(), currentInd - 1, false, true);
2013 DirItemIterator it =
2014 std::find_if(begin, RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2016 MoveFocus(currentInd, it.m_sel, GetSelectedCount());
2019 void CDirView::OpenNextDiff()
2022 int currentInd = GetFocusedItem();
2023 const DIFFITEM& dip = GetDiffItem(currentInd);
2024 if (!dip.diffcode.isDirectory())
2030 GetParentFrame()->ActivateFrame();
2034 void CDirView::OpenPrevDiff()
2037 int currentInd = GetFocusedItem();
2038 const DIFFITEM& dip = GetDiffItem(currentInd);
2039 if (!dip.diffcode.isDirectory())
2045 GetParentFrame()->ActivateFrame();
2049 void CDirView::OpenFirstFile()
2051 int currentInd = GetFocusedItem();
2052 int firstFileInd = 0;
2054 while (firstFileInd <= currentInd)
2056 DIFFITEM& dip = GetDiffItem(firstFileInd);
2057 if (!dip.diffcode.isDirectory())
2059 MoveFocus(currentInd, firstFileInd, 1);
2067 bool CDirView::IsFirstFile()
2069 int currentInd = GetFocusedItem();
2070 int firstFileInd = 0;
2071 while (firstFileInd <= currentInd)
2073 DIFFITEM& dip = GetDiffItem(firstFileInd);
2074 if (!dip.diffcode.isDirectory())
2076 if (currentInd == firstFileInd)
2086 void CDirView::OpenLastFile()
2088 const int count = m_pList->GetItemCount();
2089 int currentInd = GetFocusedItem();
2090 int lastFileInd = count - 1;
2092 while (lastFileInd >= 0)
2094 DIFFITEM& dip = GetDiffItem(lastFileInd);
2095 if (!dip.diffcode.isDirectory())
2097 MoveFocus(currentInd, lastFileInd, 1);
2105 bool CDirView::IsLastFile()
2107 const int count = m_pList->GetItemCount();
2108 int currentInd = GetFocusedItem();
2109 int lastFileInd = count - 1;
2110 while (lastFileInd >= currentInd)
2112 DIFFITEM& dip = GetDiffItem(lastFileInd);
2113 if (!dip.diffcode.isDirectory())
2115 if (currentInd == lastFileInd)
2125 void CDirView::OpenNextFile()
2127 const int count = m_pList->GetItemCount();
2128 int currentInd = GetFocusedItem();
2129 int nextInd = currentInd + 1;
2130 if (currentInd >= 0)
2132 while (nextInd < count)
2134 DIFFITEM& dip = GetDiffItem(nextInd);
2135 MoveFocus(nextInd - 1, nextInd, 1);
2136 if (!dip.diffcode.isDirectory())
2146 void CDirView::OpenPrevFile()
2148 int currentInd = GetFocusedItem();
2149 int prevInd = currentInd - 1;
2150 if (currentInd >= 0)
2152 while (prevInd >= 0)
2154 DIFFITEM& dip = GetDiffItem(prevInd);
2155 MoveFocus(prevInd + 1, prevInd, 1);
2156 if (!dip.diffcode.isDirectory())
2166 void CDirView::SetActivePane(int pane)
2168 if (m_nActivePane >= 0)
2169 GetParentFrame()->GetHeaderInterface()->SetActive(m_nActivePane, false);
2170 GetParentFrame()->GetHeaderInterface()->SetActive(pane, true);
2171 m_nActivePane = pane;
2175 // If none or one item selected select found item
2176 void CDirView::OnNextdiff()
2182 void CDirView::OnUpdateNextdiff(CCmdUI* pCmdUI)
2184 pCmdUI->Enable(HasNextDiff());
2188 // If none or one item selected select found item
2189 void CDirView::OnPrevdiff()
2195 void CDirView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
2197 pCmdUI->Enable(HasPrevDiff());
2200 void CDirView::OnCurdiff()
2202 const int count = m_pList->GetItemCount();
2204 int i = GetFirstSelectedInd();
2206 // No selection - no diff to go
2210 while (i < count && !found)
2212 UINT selected = m_pList->GetItemState(i, LVIS_SELECTED);
2213 UINT focused = m_pList->GetItemState(i, LVIS_FOCUSED);
2215 if (selected == LVIS_SELECTED && focused == LVIS_FOCUSED)
2217 m_pList->EnsureVisible(i, FALSE);
2224 void CDirView::OnUpdateCurdiff(CCmdUI* pCmdUI)
2226 pCmdUI->Enable(GetFirstSelectedInd() > -1);
2229 int CDirView::GetFocusedItem()
2231 return m_pList->GetNextItem(-1, LVNI_FOCUSED);
2234 int CDirView::GetFirstDifferentItem()
2236 if (!m_firstDiffItem.has_value())
2238 DirItemIterator it =
2239 std::find_if(Begin(), End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2240 m_firstDiffItem = it.m_sel;
2242 return m_firstDiffItem.value();
2245 int CDirView::GetLastDifferentItem()
2247 if (!m_lastDiffItem.has_value())
2249 DirItemIterator it =
2250 std::find_if(RevBegin(), RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2251 m_lastDiffItem = it.m_sel;
2253 return m_lastDiffItem.value();
2257 * @brief Move focus to specified item (and selection if multiple items not selected)
2259 * Moves the focus from item [currentInd] to item [i]
2260 * Additionally, if there are not multiple items selected,
2261 * deselects item [currentInd] and selects item [i]
2263 void CDirView::MoveFocus(int currentInd, int i, int selCount)
2267 // Not multiple items selected, so bring selection with us
2268 m_pList->SetItemState(currentInd, 0, LVIS_SELECTED);
2269 m_pList->SetItemState(currentInd, 0, LVIS_FOCUSED);
2270 m_pList->SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2273 // Move focus to specified item
2274 // (this automatically defocuses old item)
2275 m_pList->SetItemState(i, LVIS_FOCUSED, LVIS_FOCUSED);
2276 m_pList->EnsureVisible(i, FALSE);
2279 void CDirView::OnUpdateSave(CCmdUI* pCmdUI)
2281 pCmdUI->Enable(FALSE);
2284 CDirFrame * CDirView::GetParentFrame()
2286 // can't verify cast without introducing more coupling
2287 // (CDirView doesn't include DirFrame.h)
2288 return static_cast<CDirFrame *>(CListView::GetParentFrame());
2291 void CDirView::OnRefresh()
2293 m_pSavedTreeState.reset(SaveTreeState(GetDiffContext()));
2294 GetDocument()->Rescan();
2297 BOOL CDirView::PreTranslateMessage(MSG* pMsg)
2299 // Handle special shortcuts here
2300 if (pMsg->message == WM_KEYDOWN)
2304 // Check if we got 'ESC pressed' -message
2305 if (pMsg->wParam == VK_ESCAPE)
2307 if (m_pCmpProgressBar != nullptr)
2309 OnBnClickedComparisonStop();
2313 if (m_nEscCloses != 0)
2315 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
2319 // Check if we got 'DEL pressed' -message
2320 if (pMsg->wParam == VK_DELETE)
2322 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_MERGE_DELETE);
2325 int sel = GetFocusedItem();
2326 // Check if we got 'Backspace pressed' -message
2327 if (pMsg->wParam == VK_BACK)
2329 if (!GetDiffContext().m_bRecursive)
2331 OpenParentDirectory();
2334 else if (m_bTreeMode && sel >= 0)
2336 const DIFFITEM& di = GetDiffItem(sel);
2337 assert(di.HasParent());
2340 int i = GetItemIndex(di.GetParentLink());
2342 MoveFocus(sel, i, GetSelectedCount());
2348 DIFFITEM& dip = this->GetDiffItem(sel);
2349 if (pMsg->wParam == VK_LEFT)
2351 if (m_bTreeMode && GetDiffContext().m_bRecursive && (!(dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren()))
2352 PostMessage(WM_KEYDOWN, VK_BACK);
2354 CollapseSubdir(sel);
2357 if (pMsg->wParam == VK_SUBTRACT)
2359 CollapseSubdir(sel);
2362 if (pMsg->wParam == VK_RIGHT)
2364 if (m_bTreeMode && GetDiffContext().m_bRecursive && dip.customFlags & ViewCustomFlags::EXPANDED && dip.HasChildren())
2365 PostMessage(WM_KEYDOWN, VK_DOWN);
2370 if (pMsg->wParam == VK_ADD)
2375 if (pMsg->wParam == VK_MULTIPLY)
2377 ExpandSubdir(sel, true);
2384 // ESC doesn't close window when user is renaming an item.
2385 if (pMsg->wParam == VK_ESCAPE)
2387 m_bUserCancelEdit = true;
2389 // The edit control send LVN_ENDLABELEDIT when it loses focus,
2390 // so we use it to cancel the rename action.
2391 m_pList->SetFocus();
2393 // Stop the ESC before it reach the main frame which might
2394 // cause a program termination.
2399 return CListView::PreTranslateMessage(pMsg);
2402 void CDirView::OnUpdateRefresh(CCmdUI* pCmdUI)
2404 UINT threadState = GetDocument()->m_diffThread.GetThreadState();
2405 pCmdUI->Enable(threadState != CDiffThread::THREAD_COMPARING);
2409 * @brief Called when compare thread asks UI update.
2410 * @note Currently thread asks update after compare is ready
2413 LRESULT CDirView::OnUpdateUIMessage(WPARAM wParam, LPARAM lParam)
2415 UNREFERENCED_PARAMETER(lParam);
2417 CDirDoc * pDoc = GetDocument();
2418 ASSERT(pDoc != nullptr);
2420 if (wParam == CDiffThread::EVENT_COMPARE_COMPLETED)
2422 // Close and destroy the dialog after compare
2423 if (m_pCmpProgressBar != nullptr)
2424 GetParentFrame()->ShowControlBar(m_pCmpProgressBar.get(), FALSE, FALSE);
2425 m_pCmpProgressBar.reset();
2427 pDoc->CompareReady();
2429 if (!pDoc->GetGeneratingReport())
2432 if (!pDoc->GetReportFile().empty())
2434 OnToolsGenerateReport();
2435 pDoc->SetReportFile(_T(""));
2438 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
2443 // If compare took more than TimeToSignalCompare seconds, notify user
2444 clock_t elapsed = clock() - m_compareStart;
2445 GetParentFrame()->SetStatus(
2446 strutils::format(_("Elapsed time: %ld ms"), elapsed).c_str()
2448 if (elapsed > TimeToSignalCompare * CLOCKS_PER_SEC)
2450 GetMainFrame()->StartFlashing();
2452 else if (wParam == CDiffThread::EVENT_COMPARE_PROGRESSED)
2454 InvalidateRect(nullptr, FALSE);
2456 else if (wParam == CDiffThread::EVENT_COLLECT_COMPLETED)
2458 if (m_pSavedTreeState != nullptr)
2460 RestoreTreeState(GetDiffContext(), m_pSavedTreeState.get());
2461 m_pSavedTreeState.reset();
2466 if (m_bExpandSubdirs)
2467 OnViewExpandAllSubdirs();
2473 return 0; // return value unused
2477 BOOL CDirView::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
2479 NMHDR * hdr = reinterpret_cast<NMHDR *>(lParam);
2480 if (hdr->code == HDN_ENDDRAG)
2481 return OnHeaderEndDrag((LPNMHEADER)hdr, pResult);
2482 if (hdr->code == HDN_BEGINDRAG)
2483 return OnHeaderBeginDrag((LPNMHEADER)hdr, pResult);
2485 return CListView::OnNotify(wParam, lParam, pResult);
2488 BOOL CDirView::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
2490 if (uMsg == WM_NOTIFY)
2492 NMHDR *pNMHDR = (NMHDR *)lParam;
2493 switch (pNMHDR->code)
2495 case LVN_GETDISPINFO:
2496 ReflectGetdispinfo((NMLVDISPINFO *)lParam);
2498 case LVN_GETINFOTIPW:
2499 case LVN_GETINFOTIPA:
2503 return CListView::OnChildNotify(uMsg, wParam, lParam, pResult);
2507 * @brief User is starting to drag a column header
2509 bool CDirView::OnHeaderBeginDrag(LPNMHEADER hdr, LRESULT* pResult)
2511 // save column widths before user reorders them
2512 // so we can reload them on the end drag
2513 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS;
2514 GetOptionsMgr()->SaveOption(keyname,
2515 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1)).c_str());
2520 * @brief User just finished dragging a column header
2522 bool CDirView::OnHeaderEndDrag(LPNMHEADER hdr, LRESULT* pResult)
2524 int src = hdr->iItem;
2525 int dest = hdr->pitem->iOrder;
2526 bool allowDrop = true;
2527 *pResult = allowDrop ? FALSE : TRUE;
2528 if (allowDrop && src != dest && dest != -1)
2530 m_pColItems->MoveColumn(src, dest);
2537 * @brief Remove any windows reordering of columns
2539 void CDirView::FixReordering()
2542 lvcol.mask = LVCF_ORDER;
2545 lvcol.pszText = nullptr;
2547 for (int i = 0; i < m_pColItems->GetColCount(); ++i)
2550 GetListCtrl().SetColumn(i, &lvcol);
2554 /** @brief Add columns to display, loading width & order from registry. */
2555 void CDirView::LoadColumnHeaderItems()
2557 bool dummyflag = false;
2559 CHeaderCtrl * h = m_pList->GetHeaderCtrl();
2560 if (h->GetItemCount())
2563 while (m_pList->GetHeaderCtrl()->GetItemCount() > 1)
2564 m_pList->DeleteColumn(1);
2567 for (int i = 0; i < m_pColItems->GetDispColCount(); ++i)
2570 lvc.mask = LVCF_FMT + LVCF_SUBITEM + LVCF_TEXT;
2571 lvc.fmt = LVCFMT_LEFT;
2573 lvc.pszText = _T("text");
2575 m_pList->InsertColumn(i, &lvc);
2578 m_pList->DeleteColumn(1);
2582 void CDirView::SetFont(const LOGFONT & lf)
2584 m_font.DeleteObject();
2585 m_font.CreateFontIndirect(&lf);
2586 CWnd::SetFont(&m_font);
2589 /** @brief Fire off a resort of the data, to take place when things stabilize. */
2590 void CDirView::InitiateSort()
2592 PostMessage(WM_TIMER, COLUMN_REORDER);
2595 void CDirView::OnTimer(UINT_PTR nIDEvent)
2597 if (nIDEvent == COLUMN_REORDER)
2599 // Remove the windows reordering, as we're doing it ourselves
2601 // Now redraw screen
2602 UpdateColumnNames();
2603 m_pColItems->LoadColumnWidths(
2604 GetOptionsMgr()->GetString(GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS),
2605 std::bind(&CListCtrl::SetColumnWidth, m_pList, _1, _2), GetDefColumnWidth());
2608 else if (nIDEvent == STATUSBAR_UPDATE)
2610 KillTimer(STATUSBAR_UPDATE);
2611 int items = GetSelectedCount();
2612 String msg = (items == 1) ? _("1 item selected") : strutils::format_string1(_("%1 items selected"), strutils::to_str(items));
2613 GetParentFrame()->SetStatus(msg.c_str());
2616 CListView::OnTimer(nIDEvent);
2620 * @brief Change left-side readonly-status
2622 template<SIDE_TYPE stype>
2623 void CDirView::OnReadOnly()
2625 const int index = SideToIndex(GetDiffContext(), stype);
2626 bool bReadOnly = GetDocument()->GetReadOnly(index);
2627 GetDocument()->SetReadOnly(index, !bReadOnly);
2631 * @brief Update left-readonly menu item
2633 template<SIDE_TYPE stype>
2634 void CDirView::OnUpdateReadOnly(CCmdUI* pCmdUI)
2636 const int index = SideToIndex(GetDiffContext(), stype);
2637 bool bReadOnly = GetDocument()->GetReadOnly(index);
2638 if (stype != SIDE_MIDDLE)
2640 pCmdUI->Enable(TRUE);
2641 pCmdUI->SetCheck(bReadOnly);
2645 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
2646 pCmdUI->SetCheck(bReadOnly && GetDocument()->m_nDirs > 2);
2651 * @brief Update left-side readonly statusbar item
2653 void CDirView::OnUpdateStatusLeftRO(CCmdUI* pCmdUI)
2655 bool bROLeft = GetDocument()->GetReadOnly(0);
2656 pCmdUI->Enable(bROLeft);
2660 * @brief Update middle readonly statusbar item
2662 void CDirView::OnUpdateStatusMiddleRO(CCmdUI* pCmdUI)
2664 bool bROMiddle = GetDocument()->GetReadOnly(1);
2665 pCmdUI->Enable(bROMiddle && GetDocument()->m_nDirs > 2);
2669 * @brief Update right-side readonly statusbar item
2671 void CDirView::OnUpdateStatusRightRO(CCmdUI* pCmdUI)
2673 bool bRORight = GetDocument()->GetReadOnly(GetDocument()->m_nDirs - 1);
2674 pCmdUI->Enable(bRORight);
2678 * @brief Open dialog to customize dirview columns
2680 void CDirView::OnCustomizeColumns()
2682 // Located in DirViewColHandler.cpp
2684 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_ORDERS : OPT_DIRVIEW3_COLUMN_ORDERS;
2685 GetOptionsMgr()->SaveOption(keyname, m_pColItems->SaveColumnOrders());
2688 void CDirView::OnCtxtOpenWithUnpacker()
2691 sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
2694 // let the user choose a handler
2695 CSelectUnpackerDlg dlg(GetDiffItem(sel).diffFileInfo[0].filename, this);
2696 // create now a new infoUnpacker to initialize the manual/automatic flag
2697 PackingInfo infoUnpacker(PLUGIN_MODE::PLUGIN_AUTO);
2698 dlg.SetInitialInfoHandler(&infoUnpacker);
2700 if (dlg.DoModal() == IDOK)
2702 infoUnpacker = dlg.GetInfoHandler();
2703 OpenSelection(SELECTIONTYPE_NORMAL, &infoUnpacker, false);
2709 void CDirView::OnUpdateCtxtOpenWithUnpacker(CCmdUI* pCmdUI)
2711 if (!GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
2713 pCmdUI->Enable(FALSE);
2717 // we need one selected file, existing on both side
2718 if (m_pList->GetSelectedCount() != 1)
2719 pCmdUI->Enable(FALSE);
2723 sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
2724 const DIFFITEM& di = GetDiffItem(sel);
2725 if (di.diffcode.isDirectory())
2727 pCmdUI->Enable(FALSE);
2730 pCmdUI->Enable(IsItemDeletableOnBoth(GetDiffContext(), di));
2735 * @brief Fill string list with current dirview column registry key names
2737 std::vector<String> CDirView::GetCurrentColRegKeys()
2739 std::vector<String> colKeys;
2740 int nphyscols = GetListCtrl().GetHeaderCtrl()->GetItemCount();
2741 for (int col = 0; col < nphyscols; ++col)
2743 int logcol = m_pColItems->ColPhysToLog(col);
2744 colKeys.push_back(m_pColItems->GetColRegValueNameBase(logcol));
2749 struct FileCmpReport: public IFileCmpReport
2751 explicit FileCmpReport(CDirView *pDirView) : m_pDirView(pDirView) {}
2752 ~FileCmpReport() override {}
2753 bool operator()(REPORT_TYPE nReportType, IListCtrl *pList, int nIndex, const String &sDestDir, String &sLinkPath) override
2755 const CDiffContext& ctxt = m_pDirView->GetDiffContext();
2756 const DIFFITEM &di = m_pDirView->GetDiffItem(nIndex);
2758 String sLinkFullPath = paths::ConcatPath(ctxt.GetLeftPath(), di.diffFileInfo[0].GetFile());
2760 if (di.diffcode.isDirectory() || !IsItemNavigableDiff(ctxt, di) || IsArchiveFile(sLinkFullPath))
2766 sLinkPath = di.diffFileInfo[0].GetFile();
2768 strutils::replace(sLinkPath, _T("\\"), _T("_"));
2769 sLinkPath += _T(".html");
2770 String sReportPath = paths::ConcatPath(sDestDir, sLinkPath);
2771 bool completed = false;
2773 m_pDirView->MoveFocus(m_pDirView->GetFirstSelectedInd(), nIndex, m_pDirView->GetSelectedCount());
2774 m_pDirView->PostMessage(MSG_GENERATE_FLIE_COMPARE_REPORT,
2775 reinterpret_cast<WPARAM>(sReportPath.c_str()),
2776 reinterpret_cast<LPARAM>(&completed));
2781 while (::PeekMessage(&msg, nullptr, NULL, NULL, PM_NOREMOVE))
2783 if (!AfxGetApp()->PumpMessage())
2793 CDirView *m_pDirView;
2796 LRESULT CDirView::OnGenerateFileCmpReport(WPARAM wParam, LPARAM lParam)
2799 CFrameWnd * pFrame = GetMainFrame()->GetActiveFrame();
2800 IMergeDoc * pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame->GetActiveDocument());
2801 if (pMergeDoc == nullptr)
2802 pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
2804 auto *pReportFileName = reinterpret_cast<const TCHAR *>(wParam);
2805 bool *pCompleted = reinterpret_cast<bool *>(lParam);
2806 if (pMergeDoc != nullptr)
2808 pMergeDoc->GenerateReport(pReportFileName);
2809 pMergeDoc->CloseNow();
2812 while (::PeekMessage(&msg, nullptr, NULL, NULL, PM_NOREMOVE))
2813 if (!AfxGetApp()->PumpMessage())
2815 GetMainFrame()->OnUpdateFrameTitle(FALSE);
2821 * @brief Generate report from dir compare results.
2823 void CDirView::OnToolsGenerateReport()
2825 CDirDoc *pDoc = GetDocument();
2826 DirCmpReportDlg dlg;
2828 dlg.m_sReportFile = pDoc->GetReportFile();
2829 if (dlg.m_sReportFile.empty() && dlg.DoModal() != IDOK)
2832 pDoc->SetGeneratingReport(true);
2833 const CDiffContext& ctxt = GetDiffContext();
2835 PathContext paths = ctxt.GetNormalizedPaths();
2837 // If inside archive, convert paths
2838 if (pDoc->IsArchiveFolders())
2840 for (int i = 0; i < paths.GetSize(); i++)
2841 pDoc->ApplyDisplayRoot(i, paths[i]);
2844 DirCmpReport *pReport = new DirCmpReport(GetCurrentColRegKeys());
2845 pReport->SetRootPaths(paths);
2846 pReport->SetColumns(m_pColItems->GetDispColCount());
2847 pReport->SetFileCmpReport(new FileCmpReport(this));
2848 pReport->SetList(new IListCtrlImpl(m_pList->m_hWnd));
2849 pReport->SetReportType(dlg.m_nReportType);
2850 pReport->SetReportFile(dlg.m_sReportFile);
2851 pReport->SetCopyToClipboard(dlg.m_bCopyToClipboard);
2852 pReport->SetIncludeFileCmpReport(dlg.m_bIncludeFileCmpReport);
2853 pDoc->SetReport(pReport);
2858 * @brief Generate patch from files selected.
2860 * Creates a patch from selected files in active directory compare, or
2861 * active file compare. Files in file compare must be saved before
2864 void CDirView::OnToolsGeneratePatch()
2867 const CDiffContext& ctxt = GetDiffContext();
2869 // Get selected items from folder compare
2870 bool bValidFiles = true;
2871 for (DirItemIterator it = SelBegin(); bValidFiles && it != SelEnd(); ++it)
2873 const DIFFITEM &item = *it;
2874 if (item.diffcode.isBin())
2876 LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING |
2877 MB_DONT_DISPLAY_AGAIN, IDS_CANNOT_CREATE_BINARYPATCH);
2878 bValidFiles = false;
2883 // Format full paths to files (leftFile/rightFile)
2884 String leftFile = item.getFilepath(0, ctxt.GetNormalizedPath(0));
2885 if (!leftFile.empty())
2886 leftFile = paths::ConcatPath(leftFile, item.diffFileInfo[0].filename);
2887 String rightFile = item.getFilepath(1, ctxt.GetNormalizedPath(1));
2888 if (!rightFile.empty())
2889 rightFile = paths::ConcatPath(rightFile, item.diffFileInfo[1].filename);
2891 // Format relative paths to files in folder compare
2892 String leftpatch = item.diffFileInfo[0].path;
2893 if (!leftpatch.empty())
2894 leftpatch += _T("/");
2895 leftpatch += item.diffFileInfo[0].filename;
2896 String rightpatch = item.diffFileInfo[1].path;
2897 if (!rightpatch.empty())
2898 rightpatch += _T("/");
2899 rightpatch += item.diffFileInfo[1].filename;
2900 patcher.AddFiles(leftFile, leftpatch, rightFile, rightpatch);
2904 patcher.CreatePatch();
2908 * @brief Add special items for non-recursive compare
2909 * to directory view.
2911 * Currently only special item is ".." for browsing to
2913 * @return number of items added to view
2915 int CDirView::AddSpecialItems()
2917 CDirDoc *pDoc = GetDocument();
2919 bool bEnable = true;
2920 PathContext pathsParent;
2921 switch (CheckAllowUpwardDirectory(GetDiffContext(), pDoc->m_pTempPathContext, pathsParent))
2923 case AllowUpwardDirectory::No:
2927 AddParentFolderItem(bEnable);
2930 case AllowUpwardDirectory::Never:
2937 * @brief Add "Parent folder" ("..") item to directory view
2939 void CDirView::AddParentFolderItem(bool bEnable)
2941 AddNewItem(0, (DIFFITEM *)SPECIAL_ITEM_POS, bEnable ? DIFFIMG_DIRUP : DIFFIMG_DIRUP_DISABLE, 0);
2945 void CDirView::OnCtxtDirZip()
2947 if (!HasZipSupport())
2949 LangMessageBox(IDS_NO_ZIP_SUPPORT, MB_ICONINFORMATION);
2955 this, LVNI_SELECTED | flag
2956 ).CompressArchive();
2959 void CDirView::ShowShellContextMenu(SIDE_TYPE stype)
2961 CShellContextMenu *pContextMenu = nullptr;
2965 if (m_pShellContextMenuLeft == nullptr)
2966 m_pShellContextMenuLeft.reset(new CShellContextMenu(LeftCmdFirst, LeftCmdLast));
2967 pContextMenu = m_pShellContextMenuLeft.get();
2970 if (m_pShellContextMenuMiddle == nullptr)
2971 m_pShellContextMenuMiddle.reset(new CShellContextMenu(MiddleCmdFirst, MiddleCmdLast));
2972 pContextMenu = m_pShellContextMenuMiddle.get();
2975 if (m_pShellContextMenuRight == nullptr)
2976 m_pShellContextMenuRight.reset(new CShellContextMenu(RightCmdFirst, RightCmdLast));
2977 pContextMenu = m_pShellContextMenuRight.get();
2980 if (pContextMenu!=nullptr && ListShellContextMenu(stype))
2983 GetCursorPos(&point);
2984 HWND hWnd = GetSafeHwnd();
2985 CFrameWnd *pFrame = GetTopLevelFrame();
2986 ASSERT(pFrame != nullptr);
2987 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
2988 pFrame->m_bAutoMenuEnable = FALSE;
2989 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
2991 pContextMenu->InvokeCommand(nCmd, hWnd);
2992 pContextMenu->ReleaseShellContextMenu();
2993 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
2997 template <SIDE_TYPE stype>
2998 void CDirView::OnCtxtDirShellContextMenu()
3000 ShowShellContextMenu(stype);
3004 * @brief Select all visible items in dir compare
3006 void CDirView::OnSelectAll()
3008 // While the user is renaming an item, select all the edited text.
3009 CEdit *pEdit = m_pList->GetEditControl();
3010 if (pEdit != nullptr)
3012 pEdit->SetSel(pEdit->GetWindowTextLength());
3016 int selCount = m_pList->GetItemCount();
3018 for (int i = 0; i < selCount; i++)
3020 // Don't select special items (SPECIAL_ITEM_POS)
3021 DIFFITEM *diffpos = GetItemKey(i);
3022 if (diffpos != (DIFFITEM *)SPECIAL_ITEM_POS)
3023 m_pList->SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
3029 * @brief Update "Select All" item
3031 void CDirView::OnUpdateSelectAll(CCmdUI* pCmdUI)
3033 bool bEnable = (!IsLabelEdit()) || (m_pList->GetItemCount() > 0);
3034 pCmdUI->Enable(bEnable);
3038 * @brief Handle clicks in plugin context view in list
3040 void CDirView::OnPluginPredifferMode(UINT nID)
3042 ApplyPluginPrediffSetting(SelBegin(), SelEnd(), GetDiffContext(),
3043 (nID == ID_PREDIFF_AUTO) ? PLUGIN_MODE::PLUGIN_AUTO : PLUGIN_MODE::PLUGIN_MANUAL);
3047 * @brief Updates just before displaying plugin context view in list
3049 void CDirView::OnUpdatePluginPredifferMode(CCmdUI* pCmdUI)
3051 // 2004-04-03, Perry
3052 // CMainFrame::OnUpdatePluginUnpackMode handles this for global unpacking
3053 // and is the template to copy, but here, this is a bit tricky
3054 // as a group of files may be selected
3055 // and they may not all have the same setting
3056 // so I'm not trying this right now
3058 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
3060 BCMenu *pPopup = static_cast<BCMenu*>(pCmdUI->m_pSubMenu);
3061 if (pPopup == nullptr)
3064 std::pair<int, int> counts = CountPredifferYesNo(SelBegin(), SelEnd(), GetDiffContext());
3066 CheckContextMenu(pPopup, ID_PREDIFF_AUTO, (counts.first > 0));
3067 CheckContextMenu(pPopup, ID_PREDIFF_MANUAL, (counts.second > 0));
3071 * @brief Refresh cached options.
3073 void CDirView::RefreshOptions()
3075 m_nEscCloses = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
3076 m_bExpandSubdirs = GetOptionsMgr()->GetBool(OPT_DIRVIEW_EXPAND_SUBDIRS);
3077 Options::DirColors::Load(GetOptionsMgr(), m_cachedColors);
3078 m_bUseColors = GetOptionsMgr()->GetBool(OPT_DIRCLR_USE_COLORS);
3079 m_pList->SetBkColor(m_bUseColors ? m_cachedColors.clrDirMargin : GetSysColor(COLOR_WINDOW));
3084 * @brief Copy selected item left side paths (containing filenames) to clipboard.
3086 template<SIDE_TYPE stype>
3087 void CDirView::OnCopyPathnames()
3089 std::list<String> list;
3090 CopyPathnames(SelBegin(), SelEnd(), std::back_inserter(list), stype, GetDiffContext());
3091 PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
3094 void CDirView::OnCopyBothPathnames()
3096 std::list<String> list;
3097 CopyBothPathnames(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
3098 PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
3102 * @brief Copy selected item filenames to clipboard.
3104 void CDirView::OnCopyFilenames()
3106 std::list<String> list;
3107 CopyFilenames(SelBegin(), SelEnd(), std::back_inserter(list));
3108 PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
3112 * @brief Enable/Disable dirview Copy Filenames context menu item.
3114 void CDirView::OnUpdateCopyFilenames(CCmdUI* pCmdUI)
3116 pCmdUI->Enable(Count(&DirActions::IsItemFile).count > 0);
3120 * @brief Copy selected item left side to clipboard.
3122 template<SIDE_TYPE stype>
3123 void CDirView::OnCopyToClipboard()
3125 std::list<String> list;
3126 CopyPathnames(SelBegin(), SelEnd(), std::back_inserter(list), stype, GetDiffContext());
3127 PutFilesToClipboard(list, GetMainFrame()->GetSafeHwnd());
3131 * @brief Copy selected item both side to clipboard.
3133 void CDirView::OnCopyBothToClipboard()
3135 std::list<String> list;
3136 CopyBothPathnames(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
3137 PutFilesToClipboard(list, GetMainFrame()->GetSafeHwnd());
3141 * @brief Rename a selected item on both sides.
3144 void CDirView::OnItemRename()
3146 ASSERT(1 == m_pList->GetSelectedCount());
3147 int nSelItem = m_pList->GetNextItem(-1, LVNI_SELECTED);
3148 ASSERT(-1 != nSelItem);
3149 m_pList->EditLabel(nSelItem);
3153 * @brief Enable/Disable dirview Rename context menu item.
3156 void CDirView::OnUpdateItemRename(CCmdUI* pCmdUI)
3158 bool bEnabled = (1 == m_pList->GetSelectedCount());
3159 pCmdUI->Enable(bEnabled && SelBegin() != SelEnd());
3163 * @brief hide selected item filenames (removes them from the ListView)
3165 void CDirView::OnHideFilenames()
3167 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
3169 while ((it = SelRevBegin()) != SelRevEnd())
3172 SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
3173 DeleteItem(it.m_sel);
3176 m_pList->SetRedraw(TRUE); // Turn updating back on
3180 * @brief update menu item
3182 void CDirView::OnUpdateHideFilenames(CCmdUI* pCmdUI)
3184 pCmdUI->Enable(m_pList->GetSelectedCount() != 0);
3187 /// User chose (context menu) Move left to...
3188 template<SIDE_TYPE stype>
3189 void CDirView::OnCtxtDirMoveTo()
3191 DoDirActionTo(stype, &DirActions::MoveTo<stype>, _("Moving files..."));
3195 * @brief Update "Move | Left to..." item
3197 template<SIDE_TYPE stype>
3198 void CDirView::OnUpdateCtxtDirMoveTo(CCmdUI* pCmdUI)
3200 Counts counts = Count(&DirActions::IsItemMovableToOn<stype>);
3201 pCmdUI->Enable(counts.count > 0);
3202 pCmdUI->SetText(FormatMenuItemStringTo(stype, counts.count, counts.total).c_str());
3206 * @brief Update title after window is resized.
3208 void CDirView::OnSize(UINT nType, int cx, int cy)
3210 CListView::OnSize(nType, cx, cy);
3211 GetDocument()->SetTitle(nullptr);
3215 * @brief Called when user selects 'Delete' from 'Merge' menu.
3217 void CDirView::OnDelete()
3219 DoDirAction(&DirActions::DeleteOnEitherOrBoth, _("Deleting files..."));
3223 * @brief Enables/disables 'Delete' item in 'Merge' menu.
3225 void CDirView::OnUpdateDelete(CCmdUI* pCmdUI)
3227 pCmdUI->Enable(Count(&DirActions::IsItemDeletableOnEitherOrBoth).count > 0);
3231 * @brief Called when item state is changed.
3233 * Show count of selected items in statusbar.
3235 void CDirView::OnItemChanged(NMHDR* pNMHDR, LRESULT* pResult)
3237 NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
3239 // If item's selected state changed
3240 if ((pNMListView->uOldState & LVIS_SELECTED) !=
3241 (pNMListView->uNewState & LVIS_SELECTED))
3243 if ((pNMListView->iItem % 5000) > 0)
3244 SetTimer(STATUSBAR_UPDATE, 100, nullptr);
3246 OnTimer(STATUSBAR_UPDATE);
3252 * @brief Called before user start to item label edit.
3254 * Disable label edit if initiated from a user double-click.
3256 afx_msg void CDirView::OnBeginLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
3258 *pResult = (SelBegin() == SelEnd());
3260 // If label edit is allowed.
3261 if (*pResult == FALSE)
3263 const NMLVDISPINFO *pdi = (NMLVDISPINFO*)pNMHDR;
3264 ASSERT(pdi != nullptr);
3266 // Locate the edit box on the right column in case the user changed the
3268 const int nColPos = m_pColItems->ColLogToPhys(0);
3270 // Get text from the "File Name" column.
3271 CString sText = m_pList->GetItemText(pdi->item.iItem, nColPos);
3272 ASSERT(!sText.IsEmpty());
3274 // Keep only left file name (separated by '|'). This form occurs
3275 // when two files exists with same name but not in same case.
3276 int nPos = sText.Find('|');
3279 sText = sText.Left(nPos);
3282 // Set the edit control with the updated text.
3283 CEdit *pEdit = m_pList->GetEditControl();
3284 ASSERT(pEdit != nullptr);
3285 pEdit->SetWindowText(sText);
3287 m_bUserCancelEdit = false;
3292 * @brief Called when user done with item label edit.
3295 afx_msg void CDirView::OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
3299 // We can't use the normal condition of pszText==`nullptr` to know if the
3300 // user cancels editing when file names had different case (e.g.
3301 // "file.txt|FILE.txt"). The edit text was changed to "file.txt" and
3302 // if the user accept it as the new file name, pszText is `nullptr`.
3304 if (!m_bUserCancelEdit)
3306 CEdit *pEdit = m_pList->GetEditControl();
3307 ASSERT(pEdit != nullptr);
3310 pEdit->GetWindowText(sText);
3312 if (!sText.IsEmpty())
3315 DirItemIterator it(m_pIList.get(), reinterpret_cast<NMLVDISPINFO *>(pNMHDR)->item.iItem);
3316 *pResult = DoItemRename(it, GetDiffContext(), String(sText));
3317 } catch (ContentsChangedException& e) {
3318 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
3325 * @brief Called when item is marked for rescan.
3326 * This function marks selected items for rescan and rescans them.
3328 void CDirView::OnMarkedRescan()
3330 std::for_each(SelBegin(), SelEnd(), MarkForRescan);
3331 if (std::distance(SelBegin(), SelEnd()) > 0)
3333 m_pSavedTreeState.reset(SaveTreeState(GetDiffContext()));
3334 GetDocument()->SetMarkedRescan();
3335 GetDocument()->Rescan();
3340 * @brief Called to update the item count in the status bar
3342 void CDirView::OnUpdateStatusNum(CCmdUI* pCmdUI)
3344 String s; // text to display
3346 int count = m_pList->GetItemCount();
3347 int focusItem = GetFocusedItem();
3349 if (focusItem == -1)
3351 // No item has focus
3353 s = strutils::format_string1(_("Items: %1"), strutils::to_str(count));
3357 // Don't show number to special items
3358 DIFFITEM *pos = GetItemKey(focusItem);
3359 if (pos != (DIFFITEM *)SPECIAL_ITEM_POS)
3361 // If compare is non-recursive reduce special items count
3362 bool bRecursive = GetDiffContext().m_bRecursive;
3369 s = strutils::format_string2(_("Item %1 of %2"),
3370 strutils::to_str(focusItem + 1), strutils::to_str(count));
3373 pCmdUI->SetText(s.c_str());
3377 * @brief Show all hidden items.
3379 void CDirView::OnViewShowHiddenItems()
3381 SetItemViewFlag(GetDiffContext(), ViewCustomFlags::VISIBLE, ViewCustomFlags::VISIBILITY);
3387 * @brief Enable/Disable 'Show hidden items' menuitem.
3389 void CDirView::OnUpdateViewShowHiddenItems(CCmdUI* pCmdUI)
3391 pCmdUI->Enable(m_nHiddenItems > 0);
3395 * @brief Toggle Tree Mode
3397 void CDirView::OnViewTreeMode()
3399 m_bTreeMode = !m_bTreeMode;
3400 m_dirfilter.tree_mode = m_bTreeMode;
3401 GetOptionsMgr()->SaveOption(OPT_TREE_MODE, m_bTreeMode); // reverse
3406 * @brief Check/Uncheck 'Tree Mode' menuitem.
3408 void CDirView::OnUpdateViewTreeMode(CCmdUI* pCmdUI)
3410 // Don't show Tree Mode as 'checked' if the
3411 // menu item is greyed out (disabled). Its very confusing.
3412 if( GetDocument()->GetDiffContext().m_bRecursive ) {
3413 pCmdUI->SetCheck(m_bTreeMode);
3414 pCmdUI->Enable(TRUE);
3416 pCmdUI->SetCheck(FALSE);
3417 pCmdUI->Enable(FALSE);
3422 * @brief Expand all subfolders
3424 void CDirView::OnViewExpandAllSubdirs()
3426 ExpandAllSubdirs(GetDiffContext());
3431 * @brief Update "Expand All Subfolders" item
3433 void CDirView::OnUpdateViewExpandAllSubdirs(CCmdUI* pCmdUI)
3435 pCmdUI->Enable(m_bTreeMode && GetDiffContext().m_bRecursive);
3439 * @brief Collapse all subfolders
3441 void CDirView::OnViewCollapseAllSubdirs()
3443 CollapseAllSubdirs(GetDiffContext());
3448 * @brief Update "Collapse All Subfolders" item
3450 void CDirView::OnUpdateViewCollapseAllSubdirs(CCmdUI* pCmdUI)
3452 pCmdUI->Enable(m_bTreeMode && GetDiffContext().m_bRecursive);
3455 template <int pane1, int pane2>
3456 void CDirView::OnViewSwapPanes()
3458 GetDocument()->Swap(pane1, pane2);
3462 template <int pane1, int pane2>
3463 void CDirView::OnUpdateViewSwapPanes(CCmdUI* pCmdUI)
3465 pCmdUI->Enable(pane2 < GetDocument()->m_nDirs);
3469 * @brief Show/Hide different files/directories
3471 void CDirView::OnOptionsShowDifferent()
3473 m_dirfilter.show_different = !m_dirfilter.show_different;
3474 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT, m_dirfilter.show_different);
3479 * @brief Show/Hide identical files/directories
3481 void CDirView::OnOptionsShowIdentical()
3483 m_dirfilter.show_identical = !m_dirfilter.show_identical;
3484 GetOptionsMgr()->SaveOption(OPT_SHOW_IDENTICAL, m_dirfilter.show_identical);
3489 * @brief Show/Hide left-only files/directories
3491 void CDirView::OnOptionsShowUniqueLeft()
3493 m_dirfilter.show_unique_left = !m_dirfilter.show_unique_left;
3494 GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_LEFT, m_dirfilter.show_unique_left);
3499 * @brief Show/Hide middle-only files/directories
3501 void CDirView::OnOptionsShowUniqueMiddle()
3503 m_dirfilter.show_unique_middle = !m_dirfilter.show_unique_middle;
3504 GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_MIDDLE, m_dirfilter.show_unique_middle);
3509 * @brief Show/Hide right-only files/directories
3511 void CDirView::OnOptionsShowUniqueRight()
3513 m_dirfilter.show_unique_right = !m_dirfilter.show_unique_right;
3514 GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_RIGHT, m_dirfilter.show_unique_right);
3519 * @brief Show/Hide binary files
3521 void CDirView::OnOptionsShowBinaries()
3523 m_dirfilter.show_binaries = !m_dirfilter.show_binaries;
3524 GetOptionsMgr()->SaveOption(OPT_SHOW_BINARIES, m_dirfilter.show_binaries);
3529 * @brief Show/Hide skipped files/directories
3531 void CDirView::OnOptionsShowSkipped()
3533 m_dirfilter.show_skipped = !m_dirfilter.show_skipped;
3534 GetOptionsMgr()->SaveOption(OPT_SHOW_SKIPPED, m_dirfilter.show_skipped);
3539 * @brief Show/Hide different files/folders (Middle and right are identical)
3541 void CDirView::OnOptionsShowDifferentLeftOnly()
3543 m_dirfilter.show_different_left_only = !m_dirfilter.show_different_left_only;
3544 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_LEFT_ONLY, m_dirfilter.show_different_left_only);
3549 * @brief Show/Hide different files/folders (Left and right are identical)
3551 void CDirView::OnOptionsShowDifferentMiddleOnly()
3553 m_dirfilter.show_different_middle_only = !m_dirfilter.show_different_middle_only;
3554 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_MIDDLE_ONLY, m_dirfilter.show_different_middle_only);
3559 * @brief Show/Hide different files/folders (Left and middle are identical)
3561 void CDirView::OnOptionsShowDifferentRightOnly()
3563 m_dirfilter.show_different_right_only = !m_dirfilter.show_different_right_only;
3564 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_RIGHT_ONLY, m_dirfilter.show_different_right_only);
3569 * @brief Show/Hide missing left only files/folders
3571 void CDirView::OnOptionsShowMissingLeftOnly()
3573 m_dirfilter.show_missing_left_only = !m_dirfilter.show_missing_left_only;
3574 GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_LEFT_ONLY, m_dirfilter.show_missing_left_only);
3579 * @brief Show/Hide missing middle only files/folders
3581 void CDirView::OnOptionsShowMissingMiddleOnly()
3583 m_dirfilter.show_missing_middle_only = !m_dirfilter.show_missing_middle_only;
3584 GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_MIDDLE_ONLY, m_dirfilter.show_missing_middle_only);
3589 * @brief Show/Hide missing right only files/folders
3591 void CDirView::OnOptionsShowMissingRightOnly()
3593 m_dirfilter.show_missing_right_only = !m_dirfilter.show_missing_right_only;
3594 GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_RIGHT_ONLY, m_dirfilter.show_missing_right_only);
3598 void CDirView::OnUpdateOptionsShowdifferent(CCmdUI* pCmdUI)
3600 pCmdUI->SetCheck(m_dirfilter.show_different);
3603 void CDirView::OnUpdateOptionsShowidentical(CCmdUI* pCmdUI)
3605 pCmdUI->SetCheck(m_dirfilter.show_identical);
3608 void CDirView::OnUpdateOptionsShowuniqueleft(CCmdUI* pCmdUI)
3610 pCmdUI->SetCheck(m_dirfilter.show_unique_left);
3613 void CDirView::OnUpdateOptionsShowuniquemiddle(CCmdUI* pCmdUI)
3615 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3616 pCmdUI->SetCheck(m_dirfilter.show_unique_middle);
3619 void CDirView::OnUpdateOptionsShowuniqueright(CCmdUI* pCmdUI)
3621 pCmdUI->SetCheck(m_dirfilter.show_unique_right);
3624 void CDirView::OnUpdateOptionsShowBinaries(CCmdUI* pCmdUI)
3626 pCmdUI->SetCheck(m_dirfilter.show_binaries);
3629 void CDirView::OnUpdateOptionsShowSkipped(CCmdUI* pCmdUI)
3631 pCmdUI->SetCheck(m_dirfilter.show_skipped);
3634 void CDirView::OnUpdateOptionsShowDifferentLeftOnly(CCmdUI* pCmdUI)
3636 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3637 pCmdUI->SetCheck(m_dirfilter.show_different_left_only);
3640 void CDirView::OnUpdateOptionsShowDifferentMiddleOnly(CCmdUI* pCmdUI)
3642 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3643 pCmdUI->SetCheck(m_dirfilter.show_different_middle_only);
3646 void CDirView::OnUpdateOptionsShowDifferentRightOnly(CCmdUI* pCmdUI)
3648 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3649 pCmdUI->SetCheck(m_dirfilter.show_different_right_only);
3652 void CDirView::OnUpdateOptionsShowMissingLeftOnly(CCmdUI* pCmdUI)
3654 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3655 pCmdUI->SetCheck(m_dirfilter.show_missing_left_only);
3658 void CDirView::OnUpdateOptionsShowMissingMiddleOnly(CCmdUI* pCmdUI)
3660 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3661 pCmdUI->SetCheck(m_dirfilter.show_missing_middle_only);
3664 void CDirView::OnUpdateOptionsShowMissingRightOnly(CCmdUI* pCmdUI)
3666 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3667 pCmdUI->SetCheck(m_dirfilter.show_missing_right_only);
3670 void CDirView::OnMergeCompare()
3672 CWaitCursor waitstatus;
3676 template<SELECTIONTYPE seltype>
3677 void CDirView::OnMergeCompare2()
3679 CWaitCursor waitstatus;
3680 OpenSelection(seltype);
3683 void CDirView::OnMergeCompareNonHorizontally()
3685 int sel1, sel2, sel3;
3686 if (!GetSelectedItems(&sel1, &sel2, &sel3))
3688 DirSelectFilesDlg dlg;
3690 dlg.m_pdi[0] = &GetDiffItem(sel1);
3692 dlg.m_pdi[1] = &GetDiffItem(sel2);
3694 dlg.m_pdi[2] = &GetDiffItem(sel3);
3695 if (dlg.DoModal() == IDOK && dlg.m_selectedButtons.size() > 0)
3697 CDirDoc *pDoc = GetDocument();
3698 FileTextEncoding encoding[3];
3699 DWORD dwFlags[3] = {};
3701 for (int nIndex = 0; nIndex < static_cast<int>(dlg.m_selectedButtons.size()); ++nIndex)
3703 int n = dlg.m_selectedButtons[nIndex];
3704 dwFlags[nIndex] = FFILEOPEN_NOMRU | (pDoc->GetReadOnly(n % 3) ? FFILEOPEN_READONLY : 0);
3705 if (dlg.m_pdi[n / 3])
3707 paths.SetPath(nIndex, GetItemFileName(pDoc->GetDiffContext(), *dlg.m_pdi[n / 3], n % 3));
3708 encoding[nIndex] = dlg.m_pdi[n / 3]->diffFileInfo[n % 3].encoding;
3711 if (paths.GetSize() == 1)
3712 paths.SetRight(_T(""));
3713 Open(paths, dwFlags, encoding);
3717 void CDirView::OnMergeCompareXML()
3719 CWaitCursor waitstatus;
3720 PackingInfo packingInfo(PLUGIN_MODE::PLUGIN_BUILTIN_XML);
3721 OpenSelection(SELECTIONTYPE_NORMAL, &packingInfo, false);
3724 void CDirView::OnMergeCompareAs(UINT nID)
3726 CWaitCursor waitstatus;
3727 OpenSelectionAs(nID);
3730 void CDirView::OnUpdateMergeCompare(CCmdUI *pCmdUI)
3732 bool openableForDir = (pCmdUI->m_nID != ID_MERGE_COMPARE_XML &&
3733 pCmdUI->m_nID != ID_MERGE_COMPARE_HEX &&
3734 pCmdUI->m_nID != ID_MERGE_COMPARE_IMAGE);
3736 DoUpdateOpen(SELECTIONTYPE_NORMAL, pCmdUI, openableForDir);
3739 template<SELECTIONTYPE seltype>
3740 void CDirView::OnUpdateMergeCompare2(CCmdUI *pCmdUI)
3742 DoUpdateOpen(seltype, pCmdUI);
3745 void CDirView::OnViewCompareStatistics()
3747 CompareStatisticsDlg dlg(GetDocument()->GetCompareStats());
3752 * @brief Count left & right files, and number with editable text encoding
3753 * @param nLeft [out] #files on left side selected
3754 * @param nLeftAffected [out] #files on left side selected which can have text encoding changed
3755 * @param nRight [out] #files on right side selected
3756 * @param nRightAffected [out] #files on right side selected which can have text encoding changed
3758 * Affected files include all except unicode files
3760 void CDirView::FormatEncodingDialogDisplays(CLoadSaveCodepageDlg * dlg)
3762 IntToIntMap currentCodepages = CountCodepages(SelBegin(), SelEnd(), GetDiffContext());
3764 Counts left, middle, right;
3765 left = Count(&DirActions::IsItemEditableEncoding<SIDE_LEFT>);
3766 if (GetDocument()->m_nDirs > 2)
3767 middle = Count(&DirActions::IsItemEditableEncoding<SIDE_MIDDLE>);
3768 right = Count(&DirActions::IsItemEditableEncoding<SIDE_RIGHT>);
3770 // Format strings such as "25 of 30 Files Affected"
3771 String sLeftAffected = FormatFilesAffectedString(left.count, left.total);
3772 String sMiddleAffected = (GetDocument()->m_nDirs < 3) ? _T("") : FormatFilesAffectedString(middle.count, middle.total);
3773 String sRightAffected = FormatFilesAffectedString(right.count, right.total);
3774 dlg->SetLeftRightAffectStrings(sLeftAffected, sMiddleAffected, sRightAffected);
3775 int codepage = currentCodepages.FindMaxKey();
3776 dlg->SetCodepages(codepage);
3780 * @brief Display file encoding dialog to user & handle user's choices
3782 * This handles DirView invocation, so multiple files may be affected
3784 void CDirView::DoFileEncodingDialog()
3786 CLoadSaveCodepageDlg dlg(GetDocument()->m_nDirs);
3787 // set up labels about what will be affected
3788 FormatEncodingDialogDisplays(&dlg);
3789 dlg.EnableSaveCodepage(false); // disallow setting a separate codepage for saving
3792 if (dlg.DoModal() != IDOK)
3796 affected[0] = dlg.DoesAffectLeft();
3797 affected[1] = dlg.DoesAffectMiddle();
3798 affected[SideToIndex(GetDiffContext(), SIDE_RIGHT)] = dlg.DoesAffectRight();
3800 ApplyCodepage(SelBegin(), SelEnd(), GetDiffContext(), affected, dlg.GetLoadCodepage());
3802 m_pList->InvalidateRect(nullptr);
3803 m_pList->UpdateWindow();
3805 // TODO: We could loop through any active merge windows belonging to us
3806 // and see if any of their files are affected
3807 // but, if they've been edited, we cannot throw away the user's work?
3811 * @brief Display file encoding dialog & handle user's actions
3813 void CDirView::OnFileEncoding()
3815 DoFileEncodingDialog();
3818 /** @brief Open help from mainframe when user presses F1*/
3819 void CDirView::OnHelp()
3821 theApp.ShowHelp(DirViewHelpLocation);
3825 * @brief true while user is editing a file name.
3827 bool CDirView::IsLabelEdit() const
3829 return (m_pList->GetEditControl() != nullptr);
3833 * @brief Allow edit "Paste" when renaming an item.
3835 void CDirView::OnEditCopy()
3837 CEdit *pEdit = m_pList->GetEditControl();
3838 if (pEdit != nullptr)
3845 * @brief Allow edit "Cut" when renaming an item.
3847 void CDirView::OnEditCut()
3849 CEdit *pEdit = m_pList->GetEditControl();
3850 if (pEdit != nullptr)
3857 * @brief Allow edit "Paste" when renaming an item.
3859 void CDirView::OnEditPaste()
3861 CEdit *pEdit = m_pList->GetEditControl();
3862 if (pEdit != nullptr)
3869 * @brief Allow edit "Undo" when renaming an item.
3871 void CDirView::OnEditUndo()
3873 CEdit *pEdit = m_pList->GetEditControl();
3874 if (pEdit != nullptr)
3881 * @brief Update the tool bar's "Undo" icon. It should be enabled when
3882 * renaming an item and undo is possible.
3884 void CDirView::OnUpdateEditUndo(CCmdUI* pCmdUI)
3886 CEdit *pEdit = m_pList->GetEditControl();
3887 pCmdUI->Enable(pEdit && pEdit->CanUndo());
3890 * @brief Returns CShellContextMenu object that owns given HMENU.
3892 * @param [in] hMenu Handle to the menu to check ownership of.
3893 * @return Either m_pShellContextMenuLeft, m_pShellContextMenuRight
3894 * or `nullptr` if hMenu is not owned by these two.
3896 CShellContextMenu* CDirView::GetCorrespondingShellContextMenu(HMENU hMenu) const
3898 CShellContextMenu* pMenu = nullptr;
3899 if (m_pShellContextMenuLeft!=nullptr && hMenu == m_pShellContextMenuLeft->GetHMENU())
3900 pMenu = m_pShellContextMenuLeft.get();
3901 else if (m_pShellContextMenuMiddle!=nullptr && hMenu == m_pShellContextMenuMiddle->GetHMENU())
3902 pMenu = m_pShellContextMenuMiddle.get();
3903 else if (m_pShellContextMenuRight!=nullptr && hMenu == m_pShellContextMenuRight->GetHMENU())
3904 pMenu = m_pShellContextMenuRight.get();
3910 * @brief Handle messages related to correct menu working.
3912 * We need to requery shell context menu each time we switch from context menu
3913 * for one side to context menu for other side. Here we check whether we need to
3914 * requery and call ShellContextMenuHandleMenuMessage.
3916 LRESULT CDirView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
3918 while (message == WM_INITMENUPOPUP)
3920 HMENU hMenu = (HMENU)wParam;
3921 if (CShellContextMenu* pMenu = GetCorrespondingShellContextMenu(hMenu))
3923 if (m_hCurrentMenu != hMenu)
3925 // re-query context menu once more, because if context menu was queried for right
3926 // group of files and we are showing menu for left group (or vice versa) menu will
3927 // be shown incorrectly
3928 // also, if context menu was last queried for right group of files and we are
3929 // invoking command for left command will be executed for right group (the last
3930 // group that menu was requested for)
3931 // may be a "feature" of Shell
3933 pMenu->RequeryShellContextMenu();
3934 m_hCurrentMenu = hMenu;
3940 CShellContextMenu* pMenu = GetCorrespondingShellContextMenu(m_hCurrentMenu);
3942 if (pMenu != nullptr)
3945 pMenu->HandleMenuMessage(message, wParam, lParam, res);
3948 return CListView::WindowProc(message, wParam, lParam);
3952 * @brief Implement background item coloring
3954 void CDirView::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
3956 if (!m_bUseColors) {
3960 LPNMLISTVIEW pNM = (LPNMLISTVIEW)pNMHDR;
3961 *pResult = CDRF_DODEFAULT;
3963 if (pNM->hdr.code == NM_CUSTOMDRAW)
3965 LPNMLVCUSTOMDRAW lpC = (LPNMLVCUSTOMDRAW)pNMHDR;
3967 if (lpC->nmcd.dwDrawStage == CDDS_PREPAINT)
3969 *pResult = CDRF_NOTIFYITEMDRAW;
3973 if (lpC->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)
3975 *pResult = CDRF_NOTIFYITEMDRAW;
3979 if (lpC->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM ))
3981 GetColors (static_cast<int>(lpC->nmcd.dwItemSpec), lpC->iSubItem, lpC->clrTextBk, lpC->clrText);
3986 void CDirView::OnBnClickedComparisonStop()
3988 if (m_pCmpProgressBar != nullptr)
3989 m_pCmpProgressBar->EndUpdating();
3990 GetDocument()->AbortCurrentScan();
3993 void CDirView::OnBnClickedComparisonPause()
3995 if (m_pCmpProgressBar != nullptr)
3996 m_pCmpProgressBar->SetPaused(true);
3997 GetDocument()->PauseCurrentScan();
4000 void CDirView::OnBnClickedComparisonContinue()
4002 if (m_pCmpProgressBar != nullptr)
4003 m_pCmpProgressBar->SetPaused(false);
4004 GetDocument()->ContinueCurrentScan();
4008 * @brief Populate colors for items in view, depending on difference status
4010 void CDirView::GetColors (int nRow, int nCol, COLORREF& clrBk, COLORREF& clrText) const
4012 const DIFFITEM& di = GetDiffItem (nRow);
4016 clrText = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_NORMALTEXT);
4017 clrBk = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_BKGND);
4019 else if (di.diffcode.isResultFiltered())
4021 clrText = m_cachedColors.clrDirItemFilteredText;
4022 clrBk = m_cachedColors.clrDirItemFiltered;
4024 else if (!IsItemExistAll(GetDiffContext(), di))
4026 clrText = m_cachedColors.clrDirItemNotExistAllText;
4027 clrBk = m_cachedColors.clrDirItemNotExistAll;
4029 else if (di.diffcode.isResultDiff())
4031 clrText = m_cachedColors.clrDirItemDiffText;
4032 clrBk = m_cachedColors.clrDirItemDiff;
4034 else if (di.diffcode.isResultSame())
4036 clrText = m_cachedColors.clrDirItemEqualText;
4037 clrBk = m_cachedColors.clrDirItemEqual;
4041 clrText = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_NORMALTEXT);
4042 clrBk = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_BKGND);
4046 void CDirView::OnSearch()
4048 CDirDoc *pDoc = GetDocument();
4049 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
4050 int nRows = m_pList->GetItemCount();
4051 for (int currRow = nRows - 1; currRow >= 0; currRow--)
4053 DIFFITEM *pos = GetItemKey(currRow);
4054 if (pos == (DIFFITEM *)SPECIAL_ITEM_POS)
4057 bool bFound = false;
4058 DIFFITEM &di = GetDiffItem(currRow);
4060 for (int i = 0; i < pDoc->m_nDirs; i++)
4062 if (di.diffcode.exists(i) && !di.diffcode.isDirectory())
4064 GetItemFileNames(currRow, &paths);
4066 if (!ufile.OpenReadOnly(paths[i]))
4069 ufile.SetUnicoding(di.diffFileInfo[i].encoding.m_unicoding);
4070 ufile.SetBom(di.diffFileInfo[i].encoding.m_bom);
4071 ufile.SetCodepage(di.diffFileInfo[i].encoding.m_codepage);
4079 if (!ufile.ReadString(line, &lossy))
4082 if (_tcsstr(line.c_str(), _T("DirView")))
4096 SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
4097 DeleteItem(currRow);
4101 m_pList->SetRedraw(TRUE); // Turn updating back on
4105 * @brief Drag files/directories from folder compare listing view.
4107 void CDirView::OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult)
4109 COleDataSource *DropData = new COleDataSource();
4111 std::list<String> list;
4112 CopyPathnamesForDragAndDrop(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
4113 String filesForDroping = strutils::join(list.begin(), list.end(), _T("\n")) + _T("\n");
4115 CSharedFile file(GMEM_DDESHARE | GMEM_MOVEABLE | GMEM_ZEROINIT);
4116 file.Write(filesForDroping.data(), static_cast<unsigned>((filesForDroping.length() + 1) * sizeof(TCHAR)));
4118 HGLOBAL hMem = GlobalReAlloc(file.Detach(), (filesForDroping.length() + 1) * sizeof(TCHAR), 0);
4119 if (hMem != nullptr)
4121 DropData->CacheGlobalData(CF_UNICODETEXT, hMem);
4122 DROPEFFECT de = DropData->DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE, nullptr);
4128 /// Assign column name, using string resource & current column ordering
4129 void CDirView::NameColumn(const DirColInfo *col, int subitem)
4131 int phys = m_pColItems->ColLogToPhys(subitem);
4134 String s = tr(col->idNameContext, col->idName);
4136 lvc.mask = LVCF_TEXT;
4137 lvc.pszText = const_cast<LPTSTR>(s.c_str());
4138 m_pList->SetColumn(phys, &lvc);
4142 /// Load column names from string table
4143 void CDirView::UpdateColumnNames()
4145 int ncols = m_pColItems->GetColCount();
4146 for (int i=0; i<ncols; ++i)
4148 const DirColInfo* col = m_pColItems->GetDirColInfo(i);
4154 * @brief Set alignment of columns.
4156 void CDirView::SetColAlignments()
4158 int ncols = m_pColItems->GetColCount();
4159 for (int i=0; i<ncols; ++i)
4161 const DirColInfo * col = m_pColItems->GetDirColInfo(i);
4163 lvc.mask = LVCF_FMT;
4164 lvc.fmt = col->alignment;
4165 m_pList->SetColumn(m_pColItems->ColLogToPhys(i), &lvc);
4169 CDirView::CompareState::CompareState(const CDiffContext *pCtxt, const DirViewColItems *pColItems, int sortCol, bool bSortAscending, bool bTreeMode)
4171 , pColItems(pColItems)
4173 , bSortAscending(bSortAscending)
4174 , bTreeMode(bTreeMode)
4178 /// Compare two specified rows during a sort operation (windows callback)
4179 int CALLBACK CDirView::CompareState::CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
4181 CompareState *pThis = reinterpret_cast<CompareState*>(lParamSort);
4182 // Sort special items always first in dir view
4188 DIFFITEM *diffposl = (DIFFITEM *)lParam1;
4189 DIFFITEM *diffposr = (DIFFITEM *)lParam2;
4190 const DIFFITEM &ldi = pThis->pCtxt->GetDiffAt(diffposl);
4191 const DIFFITEM &rdi = pThis->pCtxt->GetDiffAt(diffposr);
4192 // compare 'left' and 'right' parameters as appropriate
4193 int retVal = pThis->pColItems->ColSort(pThis->pCtxt, pThis->sortCol, ldi, rdi, pThis->bTreeMode);
4194 // return compare result, considering sort direction
4195 return pThis->bSortAscending ? retVal : -retVal;
4198 /// Add new item to list view
4199 int CDirView::AddNewItem(int i, DIFFITEM *diffpos, int iImage, int iIndent)
4202 lvItem.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE | LVIF_INDENT;
4204 lvItem.iIndent = iIndent;
4205 lvItem.iSubItem = 0;
4206 lvItem.pszText = LPSTR_TEXTCALLBACK;
4207 lvItem.lParam = (LPARAM)diffpos;
4208 lvItem.iImage = iImage;
4209 return GetListCtrl().InsertItem(&lvItem);
4213 * @brief Update listview display of details for specified row
4214 * @note Customising shownd data should be done here
4216 void CDirView::UpdateDiffItemStatus(UINT nIdx)
4218 GetListCtrl().RedrawItems(nIdx, nIdx);
4219 const DIFFITEM& di = GetDiffItem(nIdx);
4220 if (di.diffcode.isDirectory())
4223 for (it = RevBegin(); it != RevEnd(); )
4225 DIFFITEM& di2 = *it;
4226 int cursel = it.m_sel;
4228 if (di2.IsAncestor(&di))
4230 if ((di2.diffcode.diffcode & DIFFCODE::SIDEFLAGS) == 0)
4231 DeleteItem(cursel, true);
4233 GetListCtrl().RedrawItems(cursel, cursel);
4239 static String rgDispinfoText[2]; // used in function below
4242 * @brief Allocate a text buffer to assign to NMLVDISPINFO::item::pszText
4243 * Quoting from SDK Docs:
4244 * If the LVITEM structure is receiving item text, the pszText and cchTextMax
4245 * members specify the address and size of a buffer. You can either copy text to
4246 * the buffer or assign the address of a string to the pszText member. In the
4247 * latter case, you must not change or delete the string until the corresponding
4248 * item text is deleted or two additional LVN_GETDISPINFO messages have been sent.
4250 static LPTSTR NTAPI AllocDispinfoText(const String &s)
4253 LPCTSTR pszText = (rgDispinfoText[i] = s).c_str();
4255 return (LPTSTR)pszText;
4259 * @brief Respond to LVN_GETDISPINFO message
4261 void CDirView::ReflectGetdispinfo(NMLVDISPINFO *pParam)
4263 int nIdx = pParam->item.iItem;
4264 int i = m_pColItems->ColPhysToLog(pParam->item.iSubItem);
4265 DIFFITEM *key = GetItemKey(nIdx);
4266 if (key == (DIFFITEM *)SPECIAL_ITEM_POS)
4268 if (m_pColItems->IsColName(i))
4270 pParam->item.pszText = _T("..");
4274 if (!GetDocument()->HasDiffs())
4276 const CDiffContext &ctxt = GetDiffContext();
4277 const DIFFITEM &di = ctxt.GetDiffAt(key);
4278 if (pParam->item.mask & LVIF_TEXT)
4280 String s = m_pColItems->ColGetTextToDisplay(&ctxt, i, di);
4281 pParam->item.pszText = AllocDispinfoText(s);
4283 if (pParam->item.mask & LVIF_IMAGE)
4285 pParam->item.iImage = GetColImage(di);
4290 * @brief User examines & edits which columns are displayed in dirview, and in which order
4292 void CDirView::OnEditColumns()
4295 // List all the currently displayed columns
4296 for (int col=0; col<GetListCtrl().GetHeaderCtrl()->GetItemCount(); ++col)
4298 int l = m_pColItems->ColPhysToLog(col);
4299 dlg.AddColumn(m_pColItems->GetColDisplayName(l), m_pColItems->GetColDescription(l), l, col);
4301 // Now add all the columns not currently displayed
4303 for (l=0; l<m_pColItems->GetColCount(); ++l)
4305 if (m_pColItems->ColLogToPhys(l)==-1)
4307 dlg.AddColumn(m_pColItems->GetColDisplayName(l), m_pColItems->GetColDescription(l), l);
4311 // Add default order of columns for resetting to defaults
4312 for (l = 0; l < m_pColItems->GetColCount(); ++l)
4314 int phy = m_pColItems->GetColDefaultOrder(l);
4315 dlg.AddDefColumn(m_pColItems->GetColDisplayName(l), l, phy);
4318 if (dlg.DoModal() != IDOK)
4321 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS;
4322 GetOptionsMgr()->SaveOption(keyname,
4323 (dlg.m_bReset ? m_pColItems->ResetColumnWidths(GetDefColumnWidth()) :
4324 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1))));
4326 // Reset our data to reflect the new data from the dialog
4327 const CDirColsDlg::ColumnArray & cols = dlg.GetColumns();
4328 m_pColItems->ClearColumnOrders();
4329 const int sortColumn = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
4330 std::vector<int> colorder(m_pColItems->GetColCount(), -1);
4331 for (CDirColsDlg::ColumnArray::const_iterator iter = cols.begin();
4332 iter != cols.end(); ++iter)
4334 int log = iter->log_col;
4335 int phy = iter->phy_col;
4336 colorder[log] = phy;
4338 // If sorted column was hidden, reset sorting
4339 if (log == sortColumn && phy < 0)
4341 GetOptionsMgr()->Reset((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
4342 GetOptionsMgr()->Reset(OPT_DIRVIEW_SORT_ASCENDING);
4346 m_pColItems->SetColumnOrdering(&colorder[0]);
4348 if (m_pColItems->GetDispColCount() < 1)
4350 // Ignore them if they didn't leave a column showing
4351 m_pColItems->ResetColumnOrdering();
4360 DirActions CDirView::MakeDirActions(DirActions::method_type func) const
4362 const CDirDoc *pDoc = GetDocument();
4363 return DirActions(pDoc->GetDiffContext(), pDoc->GetReadOnly(), func);
4366 DirActions CDirView::MakeDirActions(DirActions::method_type2 func) const
4368 const CDirDoc *pDoc = GetDocument();
4369 return DirActions(pDoc->GetDiffContext(), pDoc->GetReadOnly(), nullptr, func);
4372 const CDiffContext& CDirView::GetDiffContext() const
4374 return GetDocument()->GetDiffContext();
4377 CDiffContext& CDirView::GetDiffContext()
4379 return GetDocument()->GetDiffContext();