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 "SelectPluginDlg.h"
30 #include "OptionsDef.h"
31 #include "OptionsMgr.h"
33 #include "DirCmpReportDlg.h"
34 #include "DirCmpReport.h"
35 #include "CompareStatisticsDlg.h"
36 #include "LoadSaveCodepageDlg.h"
37 #include "ConfirmFolderCopyDlg.h"
38 #include "DirColsDlg.h"
39 #include "DirAdditionalPropertiesDlg.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"
51 #include "DirTravel.h"
60 using namespace std::placeholders;
63 * @brief Limit (in seconds) to signal compare is ready for user.
64 * If compare takes longer than this value (in seconds) we inform
65 * user about it. Current implementation uses MessageBeep(IDOK).
67 constexpr int TimeToSignalCompare = 3;
69 // The resource ID constants/limits for the Shell context menu
70 constexpr UINT LeftCmdFirst = 0x9000; // this should be greater than any of already defined command IDs
71 constexpr UINT BothCmdLast = 0xffff; // maximum available value
72 constexpr UINT LeftCmdLast = LeftCmdFirst + (BothCmdLast - LeftCmdFirst) / 4; // divide available range equally between two context menus
73 constexpr UINT MiddleCmdFirst = LeftCmdLast + 1;
74 constexpr UINT MiddleCmdLast = MiddleCmdFirst + (BothCmdLast - LeftCmdFirst) / 4;
75 constexpr UINT RightCmdFirst = MiddleCmdLast + 1;
76 constexpr UINT RightCmdLast = RightCmdFirst + (BothCmdLast - LeftCmdFirst) / 4;
77 constexpr UINT BothCmdFirst = RightCmdLast + 1;
79 /////////////////////////////////////////////////////////////////////////////
84 STATUSBAR_UPDATE = 100
87 IMPLEMENT_DYNCREATE(CDirView, CListView)
93 , m_dirfilter(std::bind(&COptionsMgr::GetBool, GetOptionsMgr(), _1))
94 , m_pShellContextMenuLeft(nullptr)
95 , m_pShellContextMenuMiddle(nullptr)
96 , m_pShellContextMenuRight(nullptr)
97 , m_pShellContextMenuBoth(nullptr)
98 , m_hCurrentMenu(nullptr)
99 , m_pSavedTreeState(nullptr)
100 , m_pColItems(nullptr)
102 , m_nExpandSubdirs(DO_NOT_EXPAND)
104 m_dwDefaultStyle &= ~LVS_TYPEMASK;
105 // Show selection all the time, so user can see current item even when
106 // focus is elsewhere (ie, on file edit window)
107 m_dwDefaultStyle |= LVS_REPORT | LVS_SHOWSELALWAYS | LVS_EDITLABELS | LVS_OWNERDATA;
109 m_bTreeMode = GetOptionsMgr()->GetBool(OPT_TREE_MODE);
110 m_nExpandSubdirs = static_cast<eExpandSubfoldersType>(GetOptionsMgr()->GetInt(OPT_DIRVIEW_EXPAND_SUBDIRS));
111 m_nEscCloses = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
112 Options::DirColors::Load(GetOptionsMgr(), m_cachedColors);
113 m_bUseColors = GetOptionsMgr()->GetBool(OPT_DIRCLR_USE_COLORS);
116 CDirView::~CDirView()
120 BEGIN_MESSAGE_MAP(CDirView, CListView)
121 //{{AFX_MSG_MAP(CDirView)
123 ON_WM_LBUTTONDBLCLK()
129 ON_MESSAGE(MSG_UI_UPDATE, OnUpdateUIMessage)
130 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
131 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
132 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
133 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
134 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
135 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateSave)
136 ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnClick)
137 ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, OnItemChanged)
138 ON_NOTIFY_REFLECT(LVN_BEGINLABELEDIT, OnBeginLabelEdit)
139 ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, OnEndLabelEdit)
140 ON_NOTIFY_REFLECT(LVN_ODFINDITEM, OnODFindItem)
141 ON_NOTIFY_REFLECT(NM_CLICK, OnClick)
142 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBeginDrag)
143 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
145 ON_COMMAND(ID_FILE_LEFT_READONLY, OnReadOnly<SIDE_LEFT>)
146 ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnReadOnly<SIDE_MIDDLE>)
147 ON_COMMAND(ID_FILE_RIGHT_READONLY, OnReadOnly<SIDE_RIGHT>)
148 ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateReadOnly<SIDE_LEFT>)
149 ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateReadOnly<SIDE_MIDDLE>)
150 ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateReadOnly<SIDE_RIGHT>)
151 ON_COMMAND(ID_FILE_ENCODING, OnFileEncoding)
153 ON_COMMAND(ID_EDIT_SELECT_ALL, OnSelectAll)
154 ON_UPDATE_COMMAND_UI(ID_EDIT_SELECT_ALL, OnUpdateSelectAll)
156 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENT, OnOptionsShowDifferent)
157 ON_COMMAND(ID_OPTIONS_SHOWIDENTICAL, OnOptionsShowIdentical)
158 ON_COMMAND(ID_OPTIONS_SHOWUNIQUELEFT, OnOptionsShowUniqueLeft)
159 ON_COMMAND(ID_OPTIONS_SHOWUNIQUEMIDDLE, OnOptionsShowUniqueMiddle)
160 ON_COMMAND(ID_OPTIONS_SHOWUNIQUERIGHT, OnOptionsShowUniqueRight)
161 ON_COMMAND(ID_OPTIONS_SHOWBINARIES, OnOptionsShowBinaries)
162 ON_COMMAND(ID_OPTIONS_SHOWSKIPPED, OnOptionsShowSkipped)
163 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTLEFTONLY, OnOptionsShowDifferentLeftOnly)
164 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTMIDDLEONLY, OnOptionsShowDifferentMiddleOnly)
165 ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTRIGHTONLY, OnOptionsShowDifferentRightOnly)
166 ON_COMMAND(ID_OPTIONS_SHOWMISSINGLEFTONLY, OnOptionsShowMissingLeftOnly)
167 ON_COMMAND(ID_OPTIONS_SHOWMISSINGMIDDLEONLY, OnOptionsShowMissingMiddleOnly)
168 ON_COMMAND(ID_OPTIONS_SHOWMISSINGRIGHTONLY, OnOptionsShowMissingRightOnly)
169 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENT, OnUpdateOptionsShowdifferent)
170 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWIDENTICAL, OnUpdateOptionsShowidentical)
171 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUELEFT, OnUpdateOptionsShowuniqueleft)
172 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUEMIDDLE, OnUpdateOptionsShowuniquemiddle)
173 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUERIGHT, OnUpdateOptionsShowuniqueright)
174 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWBINARIES, OnUpdateOptionsShowBinaries)
175 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWSKIPPED, OnUpdateOptionsShowSkipped)
176 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTLEFTONLY, OnUpdateOptionsShowDifferentLeftOnly)
177 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTMIDDLEONLY, OnUpdateOptionsShowDifferentMiddleOnly)
178 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTRIGHTONLY, OnUpdateOptionsShowDifferentRightOnly)
179 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGLEFTONLY, OnUpdateOptionsShowMissingLeftOnly)
180 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGMIDDLEONLY, OnUpdateOptionsShowMissingMiddleOnly)
181 ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGRIGHTONLY, OnUpdateOptionsShowMissingRightOnly)
182 ON_COMMAND(ID_VIEW_SHOWHIDDENITEMS, OnViewShowHiddenItems)
183 ON_UPDATE_COMMAND_UI(ID_VIEW_SHOWHIDDENITEMS, OnUpdateViewShowHiddenItems)
184 ON_COMMAND(ID_VIEW_TREEMODE, OnViewTreeMode)
185 ON_COMMAND(ID_VIEW_EXPAND_ALLSUBDIRS, OnViewExpandAllSubdirs)
186 ON_COMMAND(ID_VIEW_EXPAND_DIFFERENT_SUBDIRS, OnViewExpandDifferentSubdirs)
187 ON_COMMAND(ID_VIEW_EXPAND_IDENTICAL_SUBDIRS, OnViewExpandIdenticalSubdirs)
188 ON_COMMAND(ID_VIEW_COLLAPSE_ALLSUBDIRS, OnViewCollapseAllSubdirs)
189 ON_UPDATE_COMMAND_UI(ID_VIEW_TREEMODE, OnUpdateViewTreeMode)
190 ON_UPDATE_COMMAND_UI(ID_VIEW_EXPAND_ALLSUBDIRS, OnUpdateViewExpandSubdirs)
191 ON_UPDATE_COMMAND_UI(ID_VIEW_EXPAND_DIFFERENT_SUBDIRS, OnUpdateViewExpandSubdirs)
192 ON_UPDATE_COMMAND_UI(ID_VIEW_EXPAND_IDENTICAL_SUBDIRS, OnUpdateViewExpandSubdirs)
193 ON_UPDATE_COMMAND_UI(ID_VIEW_COLLAPSE_ALLSUBDIRS, OnUpdateViewCollapseAllSubdirs)
194 ON_COMMAND(ID_SWAPPANES_SWAP12, (OnViewSwapPanes<0, 1>))
195 ON_COMMAND(ID_SWAPPANES_SWAP23, (OnViewSwapPanes<1, 2>))
196 ON_COMMAND(ID_SWAPPANES_SWAP13, (OnViewSwapPanes<0, 2>))
197 ON_UPDATE_COMMAND_UI(ID_SWAPPANES_SWAP12, (OnUpdateViewSwapPanes<0, 1>))
198 ON_UPDATE_COMMAND_UI(ID_SWAPPANES_SWAP23, (OnUpdateViewSwapPanes<1, 2>))
199 ON_UPDATE_COMMAND_UI(ID_SWAPPANES_SWAP13, (OnUpdateViewSwapPanes<0, 2>))
200 ON_COMMAND(ID_VIEW_DIR_STATISTICS, OnViewCompareStatistics)
201 ON_COMMAND(ID_REFRESH, OnRefresh)
202 ON_UPDATE_COMMAND_UI(ID_REFRESH, OnUpdateRefresh)
203 ON_COMMAND(ID_RESCAN, OnMarkedRescan)
204 // [Merge] menu or Context menu
205 ON_COMMAND_RANGE(ID_MERGE_COMPARE, ID_MERGE_COMPARE_IN_NEW_WINDOW, OnMergeCompare)
206 ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE, ID_MERGE_COMPARE_IN_NEW_WINDOW, OnUpdateMergeCompare)
207 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
208 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
209 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
210 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
211 ON_COMMAND(ID_CURDIFF, OnCurdiff)
212 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
213 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
214 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
215 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
216 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
217 ON_COMMAND_RANGE(ID_L2R, ID_R2L, OnDirCopy)
218 ON_UPDATE_COMMAND_UI_RANGE(ID_L2R, ID_R2L, OnUpdateDirCopy)
219 ON_COMMAND(ID_MERGE_DELETE, OnDelete)
220 ON_UPDATE_COMMAND_UI(ID_MERGE_DELETE, OnUpdateDelete)
222 ON_COMMAND(ID_TOOLS_CUSTOMIZECOLUMNS, OnCustomizeColumns)
223 ON_COMMAND(ID_TOOLS_GENERATEREPORT, OnToolsGenerateReport)
224 ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
225 ON_MESSAGE(MSG_GENERATE_FLIE_COMPARE_REPORT, OnGenerateFileCmpReport)
227 ON_COMMAND(ID_OPEN_WITH_UNPACKER, OnOpenWithUnpacker)
228 ON_UPDATE_COMMAND_UI(ID_OPEN_WITH_UNPACKER, OnUpdateCtxtOpenWithUnpacker)
230 ON_COMMAND(ID_HELP, OnHelp)
232 // Context menu -> Compare Non-horizontally
233 ON_COMMAND(ID_MERGE_COMPARE_LEFT1_LEFT2, OnMergeCompare2<SELECTIONTYPE_LEFT1LEFT2>)
234 ON_COMMAND(ID_MERGE_COMPARE_RIGHT1_RIGHT2, OnMergeCompare2<SELECTIONTYPE_RIGHT1RIGHT2>)
235 ON_COMMAND(ID_MERGE_COMPARE_LEFT1_RIGHT2, OnMergeCompare2<SELECTIONTYPE_LEFT1RIGHT2>)
236 ON_COMMAND(ID_MERGE_COMPARE_LEFT2_RIGHT1, OnMergeCompare2<SELECTIONTYPE_LEFT2RIGHT1>)
237 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT1_LEFT2, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT1LEFT2>)
238 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_RIGHT1_RIGHT2, OnUpdateMergeCompare2<SELECTIONTYPE_RIGHT1RIGHT2>)
239 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT1_RIGHT2, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT1RIGHT2>)
240 ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT2_RIGHT1, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT2RIGHT1>)
241 ON_COMMAND(ID_MERGE_COMPARE_NONHORIZONTALLY, OnMergeCompareNonHorizontally)
242 // Context menu -> Compare As
243 ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnMergeCompareAs)
244 ON_COMMAND_RANGE(ID_UNPACKERS_FIRST, ID_UNPACKERS_LAST, OnMergeCompareAs)
245 ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnUpdateMergeCompare)
246 ON_UPDATE_COMMAND_UI(ID_NO_UNPACKER, OnUpdateNoUnpacker)
247 // Context menu -> Copy
248 ON_COMMAND(ID_DIR_COPY_LEFT_TO_RIGHT, (OnCtxtDirCopy<SIDE_LEFT, SIDE_RIGHT>))
249 ON_COMMAND(ID_DIR_COPY_LEFT_TO_MIDDLE, (OnCtxtDirCopy<SIDE_LEFT, SIDE_MIDDLE>))
250 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_LEFT, (OnCtxtDirCopy<SIDE_RIGHT, SIDE_LEFT>))
251 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_MIDDLE, (OnCtxtDirCopy<SIDE_RIGHT, SIDE_MIDDLE>))
252 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_LEFT, (OnCtxtDirCopy<SIDE_MIDDLE, SIDE_LEFT>))
253 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_RIGHT, (OnCtxtDirCopy<SIDE_MIDDLE, SIDE_RIGHT>))
254 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_RIGHT, (OnUpdateCtxtDirCopy<SIDE_LEFT, SIDE_RIGHT>))
255 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_MIDDLE, (OnUpdateCtxtDirCopy<SIDE_LEFT, SIDE_MIDDLE>))
256 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_LEFT, (OnUpdateCtxtDirCopy<SIDE_RIGHT, SIDE_LEFT>))
257 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_MIDDLE, (OnUpdateCtxtDirCopy<SIDE_RIGHT, SIDE_MIDDLE>))
258 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_LEFT, (OnUpdateCtxtDirCopy<SIDE_MIDDLE, SIDE_LEFT>))
259 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_RIGHT, (OnUpdateCtxtDirCopy<SIDE_MIDDLE, SIDE_RIGHT>))
260 ON_COMMAND(ID_DIR_COPY_LEFT_TO_BROWSE, OnCtxtDirCopyTo<SIDE_LEFT>)
261 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_BROWSE, OnCtxtDirCopyTo<SIDE_MIDDLE>)
262 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_BROWSE, OnCtxtDirCopyTo<SIDE_RIGHT>)
263 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_LEFT>)
264 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_MIDDLE>)
265 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_RIGHT>)
266 // Context menu -> Move
267 ON_COMMAND(ID_DIR_MOVE_LEFT_TO_RIGHT, (OnCtxtDirMove<SIDE_LEFT, SIDE_RIGHT>))
268 ON_COMMAND(ID_DIR_MOVE_LEFT_TO_MIDDLE, (OnCtxtDirMove<SIDE_LEFT, SIDE_MIDDLE>))
269 ON_COMMAND(ID_DIR_MOVE_RIGHT_TO_LEFT, (OnCtxtDirMove<SIDE_RIGHT, SIDE_LEFT>))
270 ON_COMMAND(ID_DIR_MOVE_RIGHT_TO_MIDDLE, (OnCtxtDirMove<SIDE_RIGHT, SIDE_MIDDLE>))
271 ON_COMMAND(ID_DIR_MOVE_MIDDLE_TO_LEFT, (OnCtxtDirMove<SIDE_MIDDLE, SIDE_LEFT>))
272 ON_COMMAND(ID_DIR_MOVE_MIDDLE_TO_RIGHT, (OnCtxtDirMove<SIDE_MIDDLE, SIDE_RIGHT>))
273 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_LEFT_TO_RIGHT, (OnUpdateCtxtDirMove<SIDE_LEFT, SIDE_RIGHT>))
274 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_LEFT_TO_MIDDLE, (OnUpdateCtxtDirMove<SIDE_LEFT, SIDE_MIDDLE>))
275 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_RIGHT_TO_LEFT, (OnUpdateCtxtDirMove<SIDE_RIGHT, SIDE_LEFT>))
276 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_RIGHT_TO_MIDDLE, (OnUpdateCtxtDirMove<SIDE_RIGHT, SIDE_MIDDLE>))
277 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_MIDDLE_TO_LEFT, (OnUpdateCtxtDirMove<SIDE_MIDDLE, SIDE_LEFT>))
278 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_MIDDLE_TO_RIGHT, (OnUpdateCtxtDirMove<SIDE_MIDDLE, SIDE_RIGHT>))
279 ON_COMMAND(ID_DIR_MOVE_LEFT_TO_BROWSE, OnCtxtDirMoveTo<SIDE_LEFT>)
280 ON_COMMAND(ID_DIR_MOVE_MIDDLE_TO_BROWSE, OnCtxtDirMoveTo<SIDE_MIDDLE>)
281 ON_COMMAND(ID_DIR_MOVE_RIGHT_TO_BROWSE, OnCtxtDirMoveTo<SIDE_RIGHT>)
282 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_LEFT_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_LEFT>)
283 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_MIDDLE_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_MIDDLE>)
284 ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_RIGHT_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_RIGHT>)
285 // Context menu -> Delete
286 ON_COMMAND(ID_DIR_DEL_LEFT, OnCtxtDirDel<SIDE_LEFT>)
287 ON_COMMAND(ID_DIR_DEL_RIGHT, OnCtxtDirDel<SIDE_RIGHT>)
288 ON_COMMAND(ID_DIR_DEL_MIDDLE, OnCtxtDirDel<SIDE_MIDDLE>)
289 ON_COMMAND(ID_DIR_DEL_BOTH, OnCtxtDirDelBoth)
290 ON_COMMAND(ID_DIR_DEL_ALL, OnCtxtDirDelBoth)
291 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_LEFT, OnUpdateCtxtDirDel<SIDE_LEFT>)
292 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_MIDDLE, OnUpdateCtxtDirDel<SIDE_MIDDLE>)
293 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_RIGHT, OnUpdateCtxtDirDel<SIDE_RIGHT>)
294 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_BOTH, OnUpdateCtxtDirDelBoth)
295 ON_UPDATE_COMMAND_UI(ID_DIR_DEL_ALL, OnUpdateCtxtDirDelBoth)
296 // Context menu -> Rename, Hide Items
297 ON_COMMAND(ID_DIR_ITEM_RENAME, OnItemRename)
298 ON_UPDATE_COMMAND_UI(ID_DIR_ITEM_RENAME, OnUpdateItemRename)
299 ON_COMMAND(ID_DIR_HIDE_FILENAMES, OnHideFilenames)
300 ON_UPDATE_COMMAND_UI(ID_DIR_HIDE_FILENAMES, OnUpdateHideFilenames)
301 // Context menu -> Open Left
302 ON_COMMAND(ID_DIR_OPEN_LEFT, OnCtxtDirOpen<SIDE_LEFT>)
303 ON_COMMAND(ID_DIR_OPEN_LEFT_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_LEFT>)
304 ON_COMMAND(ID_DIR_OPEN_LEFT_WITH, OnCtxtDirOpenWith<SIDE_LEFT>)
305 ON_COMMAND(ID_DIR_OPEN_LEFT_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_LEFT>)
306 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT, OnUpdateCtxtDirOpen<SIDE_LEFT>)
307 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_LEFT>)
308 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_WITH, OnUpdateCtxtDirOpenWith<SIDE_LEFT>)
309 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_LEFT>)
310 // Context menu -> Open Middle
311 ON_COMMAND(ID_DIR_OPEN_MIDDLE, OnCtxtDirOpen<SIDE_MIDDLE>)
312 ON_COMMAND(ID_DIR_OPEN_MIDDLE_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_MIDDLE>)
313 ON_COMMAND(ID_DIR_OPEN_MIDDLE_WITH, OnCtxtDirOpenWith<SIDE_MIDDLE>)
314 ON_COMMAND(ID_DIR_OPEN_MIDDLE_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_MIDDLE>)
315 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE, OnUpdateCtxtDirOpen<SIDE_MIDDLE>)
316 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_MIDDLE>)
317 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_WITH, OnUpdateCtxtDirOpenWith<SIDE_MIDDLE>)
318 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_MIDDLE>)
319 // Context menu -> Open Right
320 ON_COMMAND(ID_DIR_OPEN_RIGHT, OnCtxtDirOpen<SIDE_RIGHT>)
321 ON_COMMAND(ID_DIR_OPEN_RIGHT_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_RIGHT>)
322 ON_COMMAND(ID_DIR_OPEN_RIGHT_WITH, OnCtxtDirOpenWith<SIDE_RIGHT>)
323 ON_COMMAND(ID_DIR_OPEN_RIGHT_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_RIGHT>)
324 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT, OnUpdateCtxtDirOpen<SIDE_RIGHT>)
325 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_RIGHT>)
326 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_WITH, OnUpdateCtxtDirOpenWith<SIDE_RIGHT>)
327 ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_RIGHT>)
328 // Context menu -> Copy Pathnames
329 ON_COMMAND(ID_DIR_COPY_PATHNAMES_LEFT, OnCopyPathnames<SIDE_LEFT>)
330 ON_COMMAND(ID_DIR_COPY_PATHNAMES_MIDDLE, OnCopyPathnames<SIDE_MIDDLE>)
331 ON_COMMAND(ID_DIR_COPY_PATHNAMES_RIGHT, OnCopyPathnames<SIDE_RIGHT>)
332 ON_COMMAND(ID_DIR_COPY_PATHNAMES_BOTH, OnCopyBothPathnames)
333 ON_COMMAND(ID_DIR_COPY_PATHNAMES_ALL, OnCopyBothPathnames)
334 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_LEFT, OnUpdateCtxtDirCopy2<SIDE_LEFT>)
335 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_MIDDLE, OnUpdateCtxtDirCopy2<SIDE_MIDDLE>)
336 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_RIGHT, OnUpdateCtxtDirCopy2<SIDE_RIGHT>)
337 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_BOTH, OnUpdateCtxtDirCopyBoth2)
338 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_ALL, OnUpdateCtxtDirCopyBoth2)
339 // Context menu -> Zip
340 ON_COMMAND(ID_DIR_ZIP_LEFT, OnCtxtDirZip<DirItemEnumerator::Left>)
341 ON_COMMAND(ID_DIR_ZIP_MIDDLE, OnCtxtDirZip<DirItemEnumerator::Middle>)
342 ON_COMMAND(ID_DIR_ZIP_RIGHT, OnCtxtDirZip<DirItemEnumerator::Right>)
343 ON_COMMAND(ID_DIR_ZIP_BOTH, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders>)
344 ON_COMMAND(ID_DIR_ZIP_ALL, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders>)
345 ON_COMMAND(ID_DIR_ZIP_BOTH_DIFFS_ONLY, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders | DirItemEnumerator::DiffsOnly>)
346 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_LEFT, OnUpdateCtxtDirCopyTo<SIDE_LEFT>)
347 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_MIDDLE, OnUpdateCtxtDirCopyTo<SIDE_MIDDLE>)
348 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_RIGHT, OnUpdateCtxtDirCopyTo<SIDE_RIGHT>)
349 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_BOTH, OnUpdateCtxtDirCopyBothTo)
350 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_ALL, OnUpdateCtxtDirCopyBothTo)
351 ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_BOTH_DIFFS_ONLY, OnUpdateCtxtDirCopyBothDiffsOnlyTo)
352 // Context menu -> Left/Middle/Right Shell menu
353 ON_COMMAND_RANGE(ID_DIR_SHELL_CONTEXT_MENU_LEFT, ID_DIR_SHELL_CONTEXT_MENU_ALL, OnCtxtDirShellContextMenu)
354 // Context menu -> Plugin settings
355 ON_COMMAND_RANGE(ID_PREDIFFER_SETTINGS_NONE, ID_PREDIFFER_SETTINGS_SELECT, OnPluginSettings)
356 ON_COMMAND_RANGE(ID_UNPACKER_SETTINGS_NONE, ID_UNPACKER_SETTINGS_SELECT, OnPluginSettings)
357 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFER_SETTINGS_NONE, ID_PREDIFFER_SETTINGS_SELECT, OnUpdatePluginMode)
358 ON_UPDATE_COMMAND_UI_RANGE(ID_UNPACKER_SETTINGS_NONE, ID_UNPACKER_SETTINGS_SELECT, OnUpdatePluginMode)
359 // Context menu -> Copy Filenames
360 ON_COMMAND(ID_DIR_COPY_FILENAMES, OnCopyFilenames)
361 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_FILENAMES, OnUpdateCopyFilenames)
362 // Context menu -> Copy Items to Clipboard
363 ON_COMMAND(ID_DIR_COPY_LEFT_TO_CLIPBOARD, OnCopyToClipboard<SIDE_LEFT>)
364 ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, OnCopyToClipboard<SIDE_MIDDLE>)
365 ON_COMMAND(ID_DIR_COPY_RIGHT_TO_CLIPBOARD, OnCopyToClipboard<SIDE_RIGHT>)
366 ON_COMMAND(ID_DIR_COPY_BOTH_TO_CLIPBOARD, OnCopyBothToClipboard)
367 ON_COMMAND(ID_DIR_COPY_ALL_TO_CLIPBOARD, OnCopyBothToClipboard)
368 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_CLIPBOARD, OnUpdateCtxtDirCopy2<SIDE_LEFT>)
369 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, OnUpdateCtxtDirCopy2<SIDE_MIDDLE>)
370 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_CLIPBOARD, OnUpdateCtxtDirCopy2<SIDE_RIGHT>)
371 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_BOTH_TO_CLIPBOARD, OnUpdateCtxtDirCopyBoth2)
372 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_ALL_TO_CLIPBOARD, OnUpdateCtxtDirCopyBoth2)
373 // Context menu -> Copy All Displayed Columns
374 ON_COMMAND(ID_DIR_COPY_ALL_DISP_COLUMNS, OnCopyAllDisplayedColumns)
375 ON_UPDATE_COMMAND_UI(ID_DIR_COPY_ALL_DISP_COLUMNS, OnUpdateCopyAllDisplayedColumns)
377 ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
378 ON_UPDATE_COMMAND_UI(ID_STATUS_RIGHTDIR_RO, OnUpdateStatusRightRO)
379 ON_UPDATE_COMMAND_UI(ID_STATUS_MIDDLEDIR_RO, OnUpdateStatusMiddleRO)
380 ON_UPDATE_COMMAND_UI(ID_STATUS_LEFTDIR_RO, OnUpdateStatusLeftRO)
384 /////////////////////////////////////////////////////////////////////////////
385 // CDirView diagnostics
389 CDirDoc* CDirView::GetDocument() // non-debug version is inline
391 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDirDoc)));
392 return (CDirDoc*)m_pDocument;
396 /////////////////////////////////////////////////////////////////////////////
397 // CDirView message handlers
399 void CDirView::OnInitialUpdate()
401 const int iconCX = []() {
402 const int cx = GetSystemMetrics(SM_CXSMICON);
411 const int iconCY = iconCX;
412 __super::OnInitialUpdate();
413 m_pList = &GetListCtrl();
414 m_pIList.reset(new IListCtrlImpl(m_pList->m_hWnd, m_listViewItems));
415 CDirDoc* pDoc = GetDocument();
416 pDoc->SetDirView(this);
418 auto properties = strutils::split<std::vector<String>>(GetOptionsMgr()->GetString(OPT_ADDITIONAL_PROPERTIES), ' ');
419 m_pColItems.reset(new DirViewColItems(pDoc->m_nDirs, properties));
421 m_pList->SendMessage(CCM_SETUNICODEFORMAT, TRUE, 0);
423 // Load user-selected font
424 if (GetOptionsMgr()->GetBool(OPT_FONT_DIRCMP + OPT_FONT_USECUSTOM))
426 m_font.CreateFontIndirect(&GetMainFrame()->m_lfDir);
427 CWnd::SetFont(&m_font, TRUE);
431 m_pList->SetBkColor(m_cachedColors.clrDirMargin);
433 // Replace standard header with sort header
434 HWND hWnd = ListView_GetHeader(m_pList->m_hWnd);
436 m_ctlSortHeader.SubclassWindow(hWnd);
438 // Load the icons used for the list view (to reflect diff status)
439 // NOTE: these must be in the exactly the same order as in the `enum`
440 // definition in the DirActions.h file (ref: DIFFIMG_LUNIQUE)
441 VERIFY(m_imageList.Create(iconCX, iconCY, ILC_COLOR32 | ILC_MASK, 15, 1));
443 IDI_LFILE, IDI_MFILE, IDI_RFILE,
444 IDI_MRFILE, IDI_LRFILE, IDI_LMFILE,
445 IDI_NOTEQUALFILE, IDI_EQUALFILE, IDI_FILE,
446 IDI_EQUALBINARY, IDI_BINARYDIFF,
447 IDI_LFOLDER, IDI_MFOLDER, IDI_RFOLDER,
448 IDI_MRFOLDER, IDI_LRFOLDER, IDI_LMFOLDER,
449 IDI_FILESKIP, IDI_FOLDERSKIP,
450 IDI_NOTEQUALFOLDER, IDI_EQUALFOLDER, IDI_FOLDER,
452 IDI_FOLDERUP, IDI_FOLDERUP_DISABLE,
454 IDI_NOTEQUALTEXTFILE, IDI_EQUALTEXTFILE,
455 IDI_NOTEQUALIMAGE, IDI_EQUALIMAGE,
457 for (auto id : icon_ids)
458 VERIFY(-1 != m_imageList.Add((HICON)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(id), IMAGE_ICON, iconCX, iconCY, 0)));
459 m_pList->SetImageList(&m_imageList, LVSIL_SMALL);
461 // Load the icons used for the list view (expanded/collapsed state icons)
462 VERIFY(m_imageState.Create(iconCX, iconCY, ILC_COLOR32 | ILC_MASK, 15, 1));
463 for (auto id : { IDI_TREE_STATE_COLLAPSED, IDI_TREE_STATE_EXPANDED })
464 VERIFY(-1 != m_imageState.Add((HICON)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(id), IMAGE_ICON, iconCX, iconCY, 0)));
466 // Restore column orders as they had them last time they ran
467 m_pColItems->LoadColumnOrders(
468 GetOptionsMgr()->GetString(pDoc->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_ORDERS : OPT_DIRVIEW3_COLUMN_ORDERS));
470 // Display column headers (in appropriate order)
473 // Show selection across entire row.u
474 // Also allow user to rearrange columns via drag&drop of headers.
475 // Also enable infotips.
476 DWORD exstyle = LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP | LVS_EX_DOUBLEBUFFER;
477 m_pList->SetExtendedStyle(exstyle);
480 BOOL CDirView::PreCreateWindow(CREATESTRUCT& cs)
482 __super::PreCreateWindow(cs);
483 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
488 * @brief Called before compare is started.
489 * CDirDoc calls this function before new compare is started, so this
490 * is good place to setup GUI for compare.
491 * @param [in] pCompareStats Pointer to class having current compare stats.
493 void CDirView::StartCompare(CompareStats *pCompareStats)
499 * @brief Called when folder compare row is double-clicked with mouse.
500 * Selected item is opened to folder or file compare.
502 void CDirView::OnLButtonDblClk(UINT nFlags, CPoint point)
506 m_pList->SubItemHitTest(&lvhti);
507 if (lvhti.iItem >= 0)
509 const DIFFITEM& di = GetDiffItem(lvhti.iItem);
510 if (m_bTreeMode && GetDiffContext().m_bRecursive && di.diffcode.isDirectory())
512 if (di.customFlags & ViewCustomFlags::EXPANDED)
513 CollapseSubdir(lvhti.iItem);
515 ExpandSubdir(lvhti.iItem);
522 if (GetFocus() == this)
523 __super::OnLButtonDblClk(nFlags, point);
527 * @brief Load or reload the columns (headers) of the list view
529 void CDirView::ReloadColumns()
531 LoadColumnHeaderItems();
534 m_pColItems->LoadColumnWidths(
535 GetOptionsMgr()->GetString(GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS),
536 std::bind(&CListCtrl::SetColumnWidth, m_pList, _1, _2), GetDefColumnWidth());
541 * @brief Redisplay items in subfolder
542 * @param [in] diffpos First item position in subfolder.
543 * @param [in] level Indent level
544 * @param [in,out] index Index of the item to be inserted.
545 * @param [in,out] alldiffs Number of different items
547 void CDirView::RedisplayChildren(DIFFITEM *diffpos, int level, UINT &index, int &alldiffs)
549 const CDiffContext &ctxt = GetDiffContext();
550 while (diffpos != nullptr)
552 DIFFITEM *curdiffpos = diffpos;
553 const DIFFITEM &di = ctxt.GetNextSiblingDiffPosition(diffpos);
555 if (di.diffcode.isResultDiff() || (!di.diffcode.existAll() && !di.diffcode.isResultFiltered()))
558 bool bShowable = IsShowable(ctxt, di, m_dirfilter);
563 AddNewItem(index, curdiffpos, I_IMAGECALLBACK, level);
565 if (di.HasChildren())
567 if (di.customFlags & ViewCustomFlags::EXPANDED)
568 RedisplayChildren(ctxt.GetFirstChildDiffPosition(curdiffpos), level + 1, index, alldiffs);
573 if (!ctxt.m_bRecursive || !di.diffcode.isDirectory() || !di.diffcode.existAll())
575 AddNewItem(index, curdiffpos, I_IMAGECALLBACK, 0);
578 if (di.HasChildren())
580 RedisplayChildren(ctxt.GetFirstChildDiffPosition(curdiffpos), level + 1, index, alldiffs);
585 m_firstDiffItem.reset();
586 m_lastDiffItem.reset();
590 * @brief Redisplay folder compare view.
591 * This function clears folder compare view and then adds
592 * items from current compare to it.
594 void CDirView::Redisplay()
596 const CDirDoc *pDoc = GetDocument();
597 const CDiffContext &ctxt = GetDiffContext();
598 PathContext pathsParent;
599 CImageList emptyImageList;
602 // Disable redrawing while adding new items
605 DeleteAllDisplayItems();
607 m_pList->SetImageList((m_bTreeMode && ctxt.m_bRecursive) ? &m_imageState : &emptyImageList, LVSIL_STATE);
609 // If non-recursive compare, add special item(s)
610 if (!ctxt.m_bRecursive ||
611 CheckAllowUpwardDirectory(ctxt, pDoc->m_pTempPathContext, pathsParent) == AllowUpwardDirectory::ParentIsTempPath)
613 cnt += AddSpecialItems();
617 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
618 RedisplayChildren(diffpos, 0, cnt, alldiffs);
619 if (pDoc->m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPLETED)
620 GetParentFrame()->SetLastCompareResult(alldiffs);
621 SortColumnsAppropriately();
623 m_pList->SetItemCount(static_cast<int>(m_listViewItems.size()));
624 m_pList->Invalidate();
628 * @brief User right-clicked somewhere in this view
630 void CDirView::OnContextMenu(CWnd*, CPoint point)
632 if (GetListCtrl().GetItemCount() == 0)
634 // Make sure window is active
635 GetParentFrame()->ActivateFrame();
638 if (point.x == -1 && point.y == -1)
640 //keystroke invocation
643 ClientToScreen(rect);
645 point = rect.TopLeft();
650 // Check if user right-clicked on header
651 // convert screen coordinates to client coordinates of listview
652 CPoint insidePt = point;
653 GetListCtrl().ScreenToClient(&insidePt);
654 // TODO: correct for hscroll ?
655 // Ask header control if click was on one of its header items
656 HDHITTESTINFO hhti = { 0 };
658 int col = static_cast<int>(GetListCtrl().GetHeaderCtrl()->SendMessage(HDM_HITTEST, 0, (LPARAM) & hhti));
661 // Presumably hhti.flags & HHT_ONHEADER is true
662 HeaderContextMenu(point, m_pColItems->ColPhysToLog(col));
665 // bail out if point is not in any row
666 LVHITTESTINFO lhti = { 0 };
668 ScreenToClient(&insidePt);
670 i = GetListCtrl().HitTest(insidePt);
671 TRACE(_T("i=%d\n"), i);
676 ListContextMenu(point, i);
680 * @brief Format context menu string and disable item if it cannot be applied.
682 static void NTAPI FormatContextMenu(BCMenu *pPopup, UINT uIDItem, int n1, int n2 = 0, int n3 = 0)
685 pPopup->GetMenuText(uIDItem, s1, MF_BYCOMMAND);
686 s2.FormatMessage(s1, NumToStr(n1).c_str(), NumToStr(n2).c_str(), NumToStr(n3).c_str());
687 pPopup->SetMenuText(uIDItem, s2, MF_BYCOMMAND);
690 pPopup->EnableMenuItem(uIDItem, MF_GRAYED);
695 * @brief Toggle context menu item
697 static void NTAPI CheckContextMenu(BCMenu *pPopup, UINT uIDItem, BOOL bCheck)
700 pPopup->CheckMenuItem(uIDItem, MF_CHECKED);
702 pPopup->CheckMenuItem(uIDItem, MF_UNCHECKED);
706 * @brief User right-clicked in listview rows
708 void CDirView::ListContextMenu(CPoint point, int /*i*/)
710 CDirDoc* pDoc = GetDocument();
712 VERIFY(menu.LoadMenu(IDR_POPUP_DIRVIEW));
713 VERIFY(menu.LoadToolbar(IDR_MAINFRAME, GetMainFrame()->GetToolbar()));
714 theApp.TranslateMenu(menu.m_hMenu);
716 // 1st submenu of IDR_POPUP_DIRVIEW is for item popup
717 BCMenu *pPopup = static_cast<BCMenu*>(menu.GetSubMenu(0));
718 ASSERT(pPopup != nullptr);
720 int sel = GetFocusedItem();
722 sel = GetFirstSelectedInd();
725 const DIFFITEM& di = GetDiffItem(sel);
726 if (GetDiffContext().m_bRecursive && di.diffcode.isDirectory())
727 pPopup->RemoveMenu(ID_MERGE_COMPARE, MF_BYCOMMAND);
728 if (!di.diffcode.isDirectory())
729 pPopup->RemoveMenu(ID_MERGE_COMPARE_IN_NEW_WINDOW, MF_BYCOMMAND);
730 if (pDoc->m_nDirs < 3)
732 pPopup->RemoveMenu(ID_DIR_COPY_LEFT_TO_MIDDLE, MF_BYCOMMAND);
733 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_LEFT, MF_BYCOMMAND);
734 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_RIGHT, MF_BYCOMMAND);
735 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_BROWSE, MF_BYCOMMAND);
736 pPopup->RemoveMenu(ID_DIR_COPY_RIGHT_TO_MIDDLE, MF_BYCOMMAND);
737 pPopup->RemoveMenu(ID_DIR_MOVE_LEFT_TO_MIDDLE, MF_BYCOMMAND);
738 pPopup->RemoveMenu(ID_DIR_MOVE_MIDDLE_TO_LEFT, MF_BYCOMMAND);
739 pPopup->RemoveMenu(ID_DIR_MOVE_MIDDLE_TO_RIGHT, MF_BYCOMMAND);
740 pPopup->RemoveMenu(ID_DIR_MOVE_MIDDLE_TO_BROWSE, MF_BYCOMMAND);
741 pPopup->RemoveMenu(ID_DIR_MOVE_RIGHT_TO_MIDDLE, MF_BYCOMMAND);
742 pPopup->RemoveMenu(ID_DIR_DEL_MIDDLE, MF_BYCOMMAND);
743 pPopup->RemoveMenu(ID_DIR_DEL_ALL, MF_BYCOMMAND);
744 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE, MF_BYCOMMAND);
746 for (int i = 0; i < pPopup->GetMenuItemCount(); ++i)
748 if (pPopup->GetMenuItemID(i) == ID_DIR_HIDE_FILENAMES)
749 pPopup->RemoveMenu(i + 3, MF_BYPOSITION);
752 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE_WITHEDITOR, MF_BYCOMMAND);
753 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE_WITH, MF_BYCOMMAND);
754 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_MIDDLE, MF_BYCOMMAND);
755 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_ALL, MF_BYCOMMAND);
756 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, MF_BYCOMMAND);
757 pPopup->RemoveMenu(ID_DIR_COPY_ALL_TO_CLIPBOARD, MF_BYCOMMAND);
758 pPopup->RemoveMenu(ID_DIR_ZIP_MIDDLE, MF_BYCOMMAND);
759 pPopup->RemoveMenu(ID_DIR_ZIP_ALL, MF_BYCOMMAND);
760 pPopup->RemoveMenu(ID_DIR_SHELL_CONTEXT_MENU_MIDDLE, MF_BYCOMMAND);
761 pPopup->RemoveMenu(ID_DIR_SHELL_CONTEXT_MENU_ALL, MF_BYCOMMAND);
762 pPopup->RemoveMenu(ID_MERGE_COMPARE_NONHORIZONTALLY, MF_BYCOMMAND);
766 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_BOTH, MF_BYCOMMAND);
767 pPopup->RemoveMenu(ID_DIR_COPY_BOTH_TO_CLIPBOARD, MF_BYCOMMAND);
768 pPopup->RemoveMenu(ID_DIR_ZIP_BOTH, MF_BYCOMMAND);
769 pPopup->RemoveMenu(ID_DIR_DEL_BOTH, MF_BYCOMMAND);
770 pPopup->RemoveMenu(ID_DIR_SHELL_CONTEXT_MENU_BOTH, MF_BYCOMMAND);
771 pPopup->RemoveMenu(2, MF_BYPOSITION); // Compare Non-horizontally
774 CMenu menuPluginsHolder;
775 menuPluginsHolder.LoadMenu(IDR_POPUP_PLUGINS_SETTINGS);
776 theApp.TranslateMenu(menuPluginsHolder.m_hMenu);
777 String s = _("Plugin Settings");
778 pPopup->AppendMenu(MF_SEPARATOR);
779 pPopup->AppendMenu(MF_POPUP, static_cast<int>(reinterpret_cast<uintptr_t>(menuPluginsHolder.m_hMenu)), s.c_str());
781 CFrameWnd *pFrame = GetTopLevelFrame();
782 ASSERT(pFrame != nullptr);
783 pFrame->m_bAutoMenuEnable = FALSE;
784 // invoke context menu
785 // this will invoke all the OnUpdate methods to enable/disable the individual items
786 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
789 pFrame->m_bAutoMenuEnable = TRUE;
793 * @brief User right-clicked on specified logical column
795 void CDirView::HeaderContextMenu(CPoint point, int /*i*/)
798 VERIFY(menu.LoadMenu(IDR_POPUP_DIRVIEW));
799 VERIFY(menu.LoadToolbar(IDR_MAINFRAME, GetMainFrame()->GetToolbar()));
800 theApp.TranslateMenu(menu.m_hMenu);
801 // 2nd submenu of IDR_POPUP_DIRVIEW is for header popup
802 BCMenu* pPopup = static_cast<BCMenu *>(menu.GetSubMenu(1));
803 ASSERT(pPopup != nullptr);
805 // invoke context menu
806 // this will invoke all the OnUpdate methods to enable/disable the individual items
807 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
812 * @brief User chose (main menu) Copy from right to left
814 void CDirView::OnDirCopy(UINT id)
816 bool to_right = (id == ID_L2R) ? true : false;
817 if (GetDocument()->m_nDirs < 3)
820 DoDirAction(&DirActions::Copy<SIDE_LEFT, SIDE_RIGHT>, _("Copying files..."));
822 DoDirAction(&DirActions::Copy<SIDE_RIGHT, SIDE_LEFT>, _("Copying files..."));
828 switch (m_nActivePane)
831 DoDirAction(&DirActions::Copy<SIDE_LEFT, SIDE_MIDDLE>, _("Copying files..."));
835 DoDirAction(&DirActions::Copy<SIDE_MIDDLE, SIDE_RIGHT>, _("Copying files..."));
841 switch (m_nActivePane)
845 DoDirAction(&DirActions::Copy<SIDE_MIDDLE, SIDE_LEFT>, _("Copying files..."));
848 DoDirAction(&DirActions::Copy<SIDE_RIGHT, SIDE_MIDDLE>, _("Copying files..."));
855 /// User chose (context men) Copy from right to left
856 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
857 void CDirView::OnCtxtDirCopy()
859 DoDirAction(&DirActions::Copy<srctype, dsttype>, _("Copying files..."));
862 /// User chose (context menu) Copy left to...
863 template<SIDE_TYPE stype>
864 void CDirView::OnCtxtDirCopyTo()
866 DoDirActionTo(stype, &DirActions::CopyTo<stype>, _("Copying files..."));
869 /// Update context menu Copy Right to Left item
870 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
871 void CDirView::OnUpdateCtxtDirCopy(CCmdUI* pCmdUI)
873 DoUpdateDirCopy<srctype, dsttype>(pCmdUI, eContext);
876 /// Update main menu Copy Right to Left item
877 void CDirView::OnUpdateDirCopy(CCmdUI* pCmdUI)
879 bool to_right = pCmdUI->m_nID == ID_L2R ? true : false;
880 if (GetDocument()->m_nDirs < 3)
883 DoUpdateDirCopy<SIDE_LEFT, SIDE_RIGHT>(pCmdUI, eContext);
885 DoUpdateDirCopy<SIDE_RIGHT, SIDE_LEFT>(pCmdUI, eContext);
891 switch (m_nActivePane)
894 DoUpdateDirCopy<SIDE_LEFT, SIDE_MIDDLE>(pCmdUI, eContext);
898 DoUpdateDirCopy<SIDE_MIDDLE, SIDE_RIGHT>(pCmdUI, eContext);
904 switch (m_nActivePane)
908 DoUpdateDirCopy<SIDE_MIDDLE, SIDE_LEFT>(pCmdUI, eContext);
911 DoUpdateDirCopy<SIDE_RIGHT, SIDE_MIDDLE>(pCmdUI, eContext);
918 void CDirView::DoDirAction(DirActions::method_type func, const String& status_message)
920 CWaitCursor waitstatus;
923 // First we build a list of desired actions
924 FileActionScript actionScript;
925 DirItemWithIndexIterator begin(m_pIList.get(), -1, true);
926 DirItemWithIndexIterator end;
927 FileActionScript *rsltScript;
928 rsltScript = std::accumulate(begin, end, &actionScript, MakeDirActions(func));
929 ASSERT(rsltScript == &actionScript);
930 // Now we prompt, and execute actions
931 ConfirmAndPerformActions(actionScript);
932 } catch (ContentsChangedException& e) {
933 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
934 } catch (FileOperationException& e) {
935 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
939 void CDirView::DoDirActionTo(SIDE_TYPE stype, DirActions::method_type func, const String& status_message)
942 String startPath(m_lastCopyFolder);
943 String selectfolder_title;
945 if (stype == SIDE_LEFT)
946 selectfolder_title = _("Left side - select destination folder:");
947 else if (stype == SIDE_MIDDLE)
948 selectfolder_title = _("Middle side - select destination folder:");
949 else if (stype == SIDE_RIGHT)
950 selectfolder_title = _("Right side - select destination folder:");
952 if (!SelectFolder(destPath, startPath.c_str(), selectfolder_title))
955 m_lastCopyFolder = destPath;
956 CWaitCursor waitstatus;
959 // First we build a list of desired actions
960 FileActionScript actionScript;
961 actionScript.m_destBase = std::move(destPath);
962 DirItemWithIndexIterator begin(m_pIList.get(), -1, true);
963 DirItemWithIndexIterator end;
964 FileActionScript *rsltScript;
965 rsltScript = std::accumulate(begin, end, &actionScript, MakeDirActions(func));
966 ASSERT(rsltScript == &actionScript);
967 // Now we prompt, and execute actions
968 ConfirmAndPerformActions(actionScript);
969 } catch (ContentsChangedException& e) {
970 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
974 // Confirm with user, then perform the action list
975 void CDirView::ConfirmAndPerformActions(FileActionScript & actionList)
977 if (actionList.GetActionItemCount() == 0) // Not sure it is possible to get right-click menu without
978 return; // any selected items, but may as well be safe
980 ASSERT(actionList.GetActionItemCount()>0); // Or else the update handler got it wrong
982 // Set parent window so modality is correct and correct window gets focus
984 actionList.SetParentWindow(this->GetSafeHwnd());
987 ConfirmActionList(GetDiffContext(), actionList);
988 } catch (ConfirmationNeededException& e) {
989 ConfirmFolderCopyDlg dlg;
990 dlg.m_caption = e.m_caption;
991 dlg.m_question = e.m_question;
992 dlg.m_fromText = e.m_fromText;
993 dlg.m_toText = e.m_toText;
994 dlg.m_fromPath = e.m_fromPath;
995 dlg.m_toPath = e.m_toPath;
996 INT_PTR ans = dlg.DoModal();
997 if (ans != IDOK && ans != IDYES)
1000 PerformActionList(actionList);
1004 * @brief Perform an array of actions
1005 * @note There can be only COPY or DELETE actions, not both!
1007 void CDirView::PerformActionList(FileActionScript & actionScript)
1009 // Check option and enable putting deleted items to Recycle Bin
1010 if (GetOptionsMgr()->GetBool(OPT_USE_RECYCLE_BIN))
1011 actionScript.UseRecycleBin(true);
1013 actionScript.UseRecycleBin(false);
1015 actionScript.SetParentWindow(GetMainFrame()->GetSafeHwnd());
1017 theApp.AddOperation();
1018 bool succeeded = actionScript.Run();
1020 UpdateAfterFileScript(actionScript);
1021 theApp.RemoveOperation();
1022 if (!succeeded && !actionScript.IsCanceled())
1023 throw FileOperationException(_T("File operation failed"));
1027 * @brief Update results after running FileActionScript.
1028 * This functions is called after script is finished to update
1029 * results (including UI).
1030 * @param [in] actionlist Script that was run.
1032 void CDirView::UpdateAfterFileScript(FileActionScript & actionList)
1034 bool bItemsRemoved = false;
1035 int curSel = GetFirstSelectedInd();
1036 CDiffContext& ctxt = GetDiffContext();
1037 while (actionList.GetActionItemCount()>0)
1039 // Start handling from tail of list, so removing items
1040 // doesn't invalidate our item indexes.
1041 FileActionItem act = actionList.RemoveTailActionItem();
1043 // Update doc (difflist)
1044 UPDATEITEM_TYPE updatetype = UpdateDiffAfterOperation(act, ctxt, GetDiffItem(act.context));
1045 if (updatetype == UPDATEITEM_REMOVE)
1047 DeleteItem(act.context, true);
1048 bItemsRemoved = true;
1050 else if (updatetype == UPDATEITEM_UPDATE)
1051 UpdateDiffItemStatus(act.context);
1054 // Make sure selection is at sensible place if all selected items
1058 UINT selected = GetSelectedCount();
1063 MoveFocus(0, curSel - 1, selected);
1068 Counts CDirView::Count(DirActions::method_type2 func) const
1070 return ::Count(SelBegin(), SelEnd(), MakeDirActions(func));
1073 /// Should Copy to Left be enabled or disabled ? (both main menu & context menu use this)
1074 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
1075 void CDirView::DoUpdateDirCopy(CCmdUI* pCmdUI, eMenuType menuType)
1077 Counts counts = Count(&DirActions::IsItemCopyableOnTo<srctype, dsttype>);
1078 pCmdUI->Enable(counts.count > 0);
1079 if (menuType == eContext)
1080 pCmdUI->SetText(FormatMenuItemString(srctype, dsttype, counts.count, counts.total).c_str());
1083 /// Should Move to Left be enabled or disabled ? (both main menu & context menu use this)
1084 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
1085 void CDirView::DoUpdateDirMove(CCmdUI* pCmdUI, eMenuType menuType)
1087 Counts counts = Count(&DirActions::IsItemMovableOnTo<srctype, dsttype>);
1088 pCmdUI->Enable(counts.count > 0);
1089 if (menuType == eContext)
1090 pCmdUI->SetText(FormatMenuItemString(srctype, dsttype, counts.count, counts.total).c_str());
1094 * @brief Update any resources necessary after a GUI language change
1096 void CDirView::UpdateResources()
1098 UpdateColumnNames();
1099 GetParentFrame()->UpdateResources();
1103 * @brief User just clicked a column, so perform sort
1105 void CDirView::OnColumnClick(NMHDR *pNMHDR, LRESULT *pResult)
1107 // set sort parameters and handle ascending/descending
1108 NM_LISTVIEW* pNMListView = (NM_LISTVIEW*) pNMHDR;
1109 int oldSortColumn = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
1110 int sortcol = m_pColItems->ColPhysToLog(pNMListView->iSubItem);
1111 if (sortcol == oldSortColumn)
1114 bool bSortAscending = GetOptionsMgr()->GetBool(OPT_DIRVIEW_SORT_ASCENDING);
1115 GetOptionsMgr()->SaveOption(OPT_DIRVIEW_SORT_ASCENDING, !bSortAscending);
1119 GetOptionsMgr()->SaveOption((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3, sortcol);
1120 // most columns start off ascending, but not dates
1121 bool bSortAscending = m_pColItems->IsDefaultSortAscending(sortcol);
1122 GetOptionsMgr()->SaveOption(OPT_DIRVIEW_SORT_ASCENDING, bSortAscending);
1125 SortColumnsAppropriately();
1129 void CDirView::SortColumnsAppropriately()
1131 int sortCol = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
1132 if (sortCol < 0 || sortCol >= m_pColItems->GetColCount())
1135 bool bSortAscending = GetOptionsMgr()->GetBool(OPT_DIRVIEW_SORT_ASCENDING);
1136 m_ctlSortHeader.SetSortImage(m_pColItems->ColLogToPhys(sortCol), bSortAscending);
1137 //sort using static CompareFunc comparison function
1138 CompareState cs(&GetDiffContext(), m_pColItems.get(), sortCol, bSortAscending, m_bTreeMode);
1139 std::stable_sort(m_listViewItems.begin(), m_listViewItems.end(), [&cs](const ListViewOwnerDataItem& a, const ListViewOwnerDataItem& b)
1140 { return CompareState::CompareFunc(a.lParam, b.lParam, reinterpret_cast<LPARAM>(&cs)) < 0; });
1142 m_firstDiffItem.reset();
1143 m_lastDiffItem.reset();
1145 m_pList->Invalidate();
1148 /// Do any last minute work as view closes
1149 void CDirView::OnDestroy()
1151 DeleteAllDisplayItems();
1154 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_ORDERS : OPT_DIRVIEW3_COLUMN_ORDERS;
1155 GetOptionsMgr()->SaveOption(keyname, m_pColItems->SaveColumnOrders());
1158 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS;
1159 GetOptionsMgr()->SaveOption(keyname,
1160 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1)));
1163 __super::OnDestroy();
1167 * @brief Open selected item when user presses ENTER key.
1169 void CDirView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
1171 if (nChar == VK_RETURN)
1173 int sel = GetFocusedItem();
1176 const DIFFITEM& di = GetDiffItem(sel);
1177 if (m_bTreeMode && GetDiffContext().m_bRecursive && di.diffcode.isDirectory())
1179 if (di.customFlags & ViewCustomFlags::EXPANDED)
1180 CollapseSubdir(sel);
1190 __super::OnChar(nChar, nRepCnt, nFlags);
1194 * @brief Expand/collapse subfolder when "+/-" icon is clicked.
1196 void CDirView::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
1198 LPNMITEMACTIVATE pNM = (LPNMITEMACTIVATE)pNMHDR;
1199 LVHITTESTINFO lvhti;
1200 lvhti.pt = pNM->ptAction;
1201 m_pList->SubItemHitTest(&lvhti);
1202 if (lvhti.flags == LVHT_ONITEMSTATEICON)
1204 const DIFFITEM &di = GetDiffItem(pNM->iItem);
1205 if (di.customFlags & ViewCustomFlags::EXPANDED)
1206 CollapseSubdir(pNM->iItem);
1208 ExpandSubdir(pNM->iItem);
1215 * @brief Collapse subfolder
1216 * @param [in] sel Folder item index in listview.
1218 void CDirView::CollapseSubdir(int sel)
1220 DIFFITEM& dip = this->GetDiffItem(sel);
1221 if (!m_bTreeMode || !(dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren())
1224 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
1226 dip.customFlags &= ~ViewCustomFlags::EXPANDED;
1228 int count = m_pList->GetItemCount();
1229 for (int i = sel + 1; i < count; i++)
1231 const DIFFITEM& di = GetDiffItem(i);
1232 if (!di.IsAncestor(&dip))
1234 m_listViewItems.erase(m_listViewItems.begin() + i);
1235 m_pList->DeleteItem(i--);
1239 m_pList->SetRedraw(TRUE); // Turn updating back on
1240 m_pList->Invalidate();
1244 * @brief Expand subfolder
1245 * @param [in] sel Folder item index in listview.
1247 void CDirView::ExpandSubdir(int sel, bool bRecursive)
1249 DIFFITEM& dip = GetDiffItem(sel);
1250 if (!m_bTreeMode || (dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren())
1253 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
1255 CDiffContext &ctxt = GetDiffContext();
1256 dip.customFlags |= ViewCustomFlags::EXPANDED;
1258 ExpandSubdirs(ctxt, dip);
1260 DIFFITEM *diffpos = ctxt.GetFirstChildDiffPosition(GetItemKey(sel));
1261 UINT indext = sel + 1;
1263 RedisplayChildren(diffpos, dip.GetDepth() + 1, indext, alldiffs);
1265 SortColumnsAppropriately();
1267 m_pList->SetRedraw(TRUE); // Turn updating back on
1268 m_pList->SetItemCountEx(static_cast<int>(m_listViewItems.size()), LVSICF_NOSCROLL);
1269 m_pList->Invalidate();
1273 * @brief Open parent folder if possible.
1275 void CDirView::OpenParentDirectory(CDirDoc *pDocOpen)
1277 CDirDoc *pDoc = GetDocument();
1278 PathContext pathsParent;
1279 switch (CheckAllowUpwardDirectory(GetDiffContext(), pDoc->m_pTempPathContext, pathsParent))
1281 case AllowUpwardDirectory::ParentIsTempPath:
1282 pDoc->m_pTempPathContext = pDoc->m_pTempPathContext->DeleteHead();
1284 case AllowUpwardDirectory::ParentIsRegularPath:
1285 fileopenflags_t dwFlags[3];
1286 for (int nIndex = 0; nIndex < pathsParent.GetSize(); ++nIndex)
1287 dwFlags[nIndex] = FFILEOPEN_NOMRU | (pDoc->GetReadOnly(nIndex) ? FFILEOPEN_READONLY : 0);
1288 GetMainFrame()->DoFileOrFolderOpen(&pathsParent, dwFlags, nullptr, _T(""), GetDiffContext().m_bRecursive, (GetAsyncKeyState(VK_CONTROL) & 0x8000) ? nullptr : pDocOpen);
1290 case AllowUpwardDirectory::No:
1293 LangMessageBox(IDS_INVALID_DIRECTORY, MB_ICONSTOP);
1299 * @brief Get one or two selected items
1301 * Returns false if 0 or more than 3 items selecte
1303 bool CDirView::GetSelectedItems(int * sel1, int * sel2, int * sel3)
1307 *sel1 = m_pList->GetNextItem(-1, LVNI_SELECTED);
1310 *sel2 = m_pList->GetNextItem(*sel1, LVNI_SELECTED);
1313 *sel3 = m_pList->GetNextItem(*sel2, LVNI_SELECTED);
1316 int extra = m_pList->GetNextItem(*sel3, LVNI_SELECTED);
1317 return (extra == -1);
1321 * @brief Open special items (parent folders etc).
1322 * @param [in] pDoc Pointer to CDirDoc object.
1323 * @param [in] pos1 First item position.
1324 * @param [in] pos2 Second item position.
1326 void CDirView::OpenSpecialItems(CDirDoc *pDoc, DIFFITEM *pos1, DIFFITEM *pos2, DIFFITEM *pos3)
1328 if (pos2==nullptr && pos3==nullptr)
1330 // Browse to parent folder(s) selected
1331 // SPECIAL_ITEM_POS is position for
1332 // special items, but there is currenly
1333 // only one (parent folder)
1334 OpenParentDirectory(pDoc);
1338 // Parent directory & something else selected
1344 * @brief Creates a pairing folder for unique folder item.
1345 * This function creates a pairing folder for unique folder item in
1346 * folder compare. This way user can browse into unique folder's
1347 * contents and don't necessarily need to copy whole folder structure.
1348 * @return true if user agreed and folder was created.
1350 static bool CreateFoldersPair(const PathContext& paths)
1352 bool created = false;
1353 for (const auto& path : paths)
1355 if (!paths::DoesPathExist(path))
1358 strutils::format_string1(
1359 _("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?"),
1361 int res = AfxMessageBox(message.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN, IDS_CREATE_PAIR_FOLDER);
1363 created = paths::CreateIfNeeded(path);
1369 void CDirView::Open(CDirDoc *pDoc, const PathContext& paths, fileopenflags_t dwFlags[3], FileTextEncoding encoding[3], PackingInfo * infoUnpacker)
1372 for (auto path : paths)
1374 if (paths::DoesPathExist(path) == paths::IS_EXISTING_DIR)
1380 // Don't add folders to MRU
1381 GetMainFrame()->DoFileOrFolderOpen(&paths, dwFlags, nullptr, _T(""), GetDiffContext().m_bRecursive,
1382 ((GetAsyncKeyState(VK_CONTROL) & 0x8000) || GetDiffContext().m_bRecursive) ? nullptr : pDoc);
1384 else if (HasZipSupport() && std::count_if(paths.begin(), paths.end(), ArchiveGuessFormat) == paths.GetSize())
1386 // Open archives, not adding paths to MRU
1387 GetMainFrame()->DoFileOrFolderOpen(&paths, dwFlags, nullptr, _T(""), GetDiffContext().m_bRecursive, nullptr, infoUnpacker, nullptr);
1391 // Regular file case
1393 // Binary attributes are set after files are unpacked
1394 // so after plugins such as the MS-Office plugins have had a chance to make them textual
1395 // We haven't done unpacking yet in this diff, but if a binary flag is already set,
1396 // then it was set in a previous diff after unpacking, so we trust it
1398 // Open identical and different files
1399 PathContext filteredPaths;
1400 FileLocation fileloc[3];
1402 const String sUntitled[] = { _("Untitled left"), paths.GetSize() < 3 ? _("Untitled right") : _("Untitled middle"), _("Untitled right") };
1403 for (int i = 0; i < paths.GetSize(); ++i)
1405 if (paths::DoesPathExist(paths[i]) == paths::DOES_NOT_EXIST)
1407 strDesc[i] = sUntitled[i];
1408 filteredPaths.SetPath(i, _T("NUL"), false);
1412 fileloc[i].setPath(paths[i]);
1413 fileloc[i].encoding = encoding[i];
1414 filteredPaths.SetPath(i, paths[i], false);
1418 PackingInfo* tmpInfoUnpacker = nullptr;
1419 PrediffingInfo* infoPrediffer = nullptr;
1420 String filteredFilenames = CDiffContext::GetFilteredFilenames(filteredPaths);
1421 GetDiffContext().FetchPluginInfos(filteredFilenames, !infoUnpacker ? &infoUnpacker : &tmpInfoUnpacker, &infoPrediffer);
1423 GetMainFrame()->ShowAutoMergeDoc(0, GetDocument(), paths.GetSize(), fileloc,
1424 dwFlags, strDesc, _T(""), infoUnpacker, infoPrediffer);
1429 * @brief Open selected files or directories.
1431 * Opens selected files to file compare. If comparing
1432 * directories non-recursively, then subfolders and parent
1433 * folder are opened too.
1435 * This handles the case that one item is selected
1436 * and the case that two items are selected (one on each side)
1438 void CDirView::OpenSelection(CDirDoc *pDoc, SELECTIONTYPE selectionType /*= SELECTIONTYPE_NORMAL*/, PackingInfo * infoUnpacker /*= nullptr*/, bool openableForDir /*= true*/)
1440 Merge7zFormatMergePluginScope scope(infoUnpacker);
1441 const CDiffContext& ctxt = GetDiffContext();
1443 // First, figure out what was selected (store into pos1 & pos2)
1444 DIFFITEM *pos1 = nullptr, *pos2 = nullptr, *pos3 = nullptr;
1445 int sel1 = -1, sel2 = -1, sel3 = -1;
1446 if (!GetSelectedItems(&sel1, &sel2, &sel3))
1448 // Must have 1 or 2 or 3 items selected
1453 pos1 = GetItemKey(sel1);
1454 ASSERT(pos1 != nullptr);
1457 pos2 = GetItemKey(sel2);
1458 ASSERT(pos2 != nullptr);
1460 pos3 = GetItemKey(sel3);
1463 // Now handle the various cases of what was selected
1465 if (IsDiffItemSpecial(pos1))
1467 OpenSpecialItems(pDoc, pos1, pos2, pos3);
1471 // Common variables which both code paths below are responsible for setting
1473 const DIFFITEM *pdi[3] = {0}; // left & right items (di1==di2 if single selection)
1474 bool isdir = false; // set if we're comparing directories
1476 FileTextEncoding encoding[3];
1480 success = GetOpenTwoItems(ctxt, selectionType, pos1, pos2, pdi,
1481 paths, sel1, sel2, isdir, nPane, encoding, errmsg, openableForDir);
1482 else if (pos2 && pos3)
1483 success = GetOpenThreeItems(ctxt, pos1, pos2, pos3, pdi,
1484 paths, sel1, sel2, sel3, isdir, nPane, encoding, errmsg, openableForDir);
1487 // Only one item selected, so perform diff on its sides
1488 success = GetOpenOneItem(ctxt, pos1, pdi,
1489 paths, sel1, isdir, nPane, encoding, errmsg, openableForDir);
1491 CreateFoldersPair(paths);
1495 if (!errmsg.empty())
1496 AfxMessageBox(errmsg.c_str(), MB_ICONSTOP);
1500 // Now pathLeft, pathRight, di1, di2, and isdir are all set
1501 // We have two items to compare, no matter whether same or different underlying DirView item
1503 fileopenflags_t dwFlags[3];
1504 for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
1505 dwFlags[nIndex] = FFILEOPEN_NOMRU | (GetDocument()->GetReadOnly(nPane[nIndex]) ? FFILEOPEN_READONLY : 0);
1507 Open(pDoc, paths, dwFlags, encoding, infoUnpacker);
1510 void CDirView::OpenSelection(SELECTIONTYPE selectionType /*= SELECTIONTYPE_NORMAL*/, PackingInfo* infoUnpacker /*= nullptr*/, bool openableForDir /*= true*/)
1512 OpenSelection(GetDocument(), selectionType, infoUnpacker, openableForDir);
1515 void CDirView::OpenSelectionAs(UINT id)
1517 CDirDoc * pDoc = GetDocument();
1518 const CDiffContext& ctxt = GetDiffContext();
1520 // First, figure out what was selected (store into pos1 & pos2 & pos3)
1521 DIFFITEM *pos1 = nullptr, *pos2 = nullptr, *pos3 = nullptr;
1522 int sel1 = -1, sel2 = -1, sel3 = -1;
1523 if (!GetSelectedItems(&sel1, &sel2, &sel3))
1525 // Must have 1 or 2 or 3 items selected
1530 pos1 = GetItemKey(sel1);
1534 pos2 = GetItemKey(sel2);
1535 ASSERT(pos2 != nullptr);
1537 pos3 = GetItemKey(sel3);
1540 // Now handle the various cases of what was selected
1542 if (IsDiffItemSpecial(pos1))
1548 // Common variables which both code paths below are responsible for setting
1550 const DIFFITEM *pdi[3]; // left & right items (di1==di2 if single selection)
1551 bool isdir = false; // set if we're comparing directories
1553 FileTextEncoding encoding[3];
1557 success = GetOpenTwoItems(ctxt, SELECTIONTYPE_NORMAL, pos1, pos2, pdi,
1558 paths, sel1, sel2, isdir, nPane, encoding, errmsg, false);
1559 else if (pos2 && pos3)
1560 success = GetOpenThreeItems(ctxt, pos1, pos2, pos3, pdi,
1561 paths, sel1, sel2, sel3, isdir, nPane, encoding, errmsg, false);
1564 // Only one item selected, so perform diff on its sides
1565 success = GetOpenOneItem(ctxt, pos1, pdi,
1566 paths, sel1, isdir, nPane, encoding, errmsg, false);
1570 if (!errmsg.empty())
1571 AfxMessageBox(errmsg.c_str(), MB_ICONSTOP);
1575 // Open identical and different files
1576 const String sUntitled[] = { _("Untitled left"), paths.GetSize() < 3 ? _("Untitled right") : _("Untitled middle"), _("Untitled right") };
1577 fileopenflags_t dwFlags[3] = { 0 };
1579 PathContext filteredPaths;
1580 FileLocation fileloc[3];
1581 for (int pane = 0; pane < paths.GetSize(); pane++)
1583 if (paths::DoesPathExist(paths[pane]) == paths::DOES_NOT_EXIST)
1585 strDesc[pane] = sUntitled[pane];
1586 filteredPaths.SetPath(pane, _T("NUL"), false);
1590 fileloc[pane].setPath(paths[pane]);
1591 fileloc[pane].encoding = encoding[pane];
1592 filteredPaths.SetPath(pane, paths[pane]);
1594 dwFlags[pane] |= FFILEOPEN_NOMRU | (pDoc->GetReadOnly(nPane[pane]) ? FFILEOPEN_READONLY : 0);
1596 PackingInfo* infoUnpacker = nullptr;
1597 PrediffingInfo* infoPrediffer = nullptr;
1598 GetDiffContext().FetchPluginInfos(CDiffContext::GetFilteredFilenames(filteredPaths), &infoUnpacker, &infoPrediffer);
1599 if (ID_UNPACKERS_FIRST <= id && id <= ID_UNPACKERS_LAST)
1601 PackingInfo infoUnpackerAlt(
1602 CMainFrame::GetPluginPipelineByMenuId(id, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
1603 GetMainFrame()->DoFileOrFolderOpen(&paths, dwFlags, strDesc, _T(""),
1604 ctxt.m_bRecursive, nullptr, &infoUnpackerAlt, infoPrediffer, 0);
1608 GetMainFrame()->ShowMergeDoc(id, pDoc, paths.GetSize(), fileloc, dwFlags, strDesc, _T(""), infoUnpacker, infoPrediffer);
1612 /// User chose (context menu) delete left
1613 template<SIDE_TYPE stype>
1614 void CDirView::OnCtxtDirDel()
1616 DoDirAction(&DirActions::DeleteOn<stype>, _("Deleting files..."));
1619 /// User chose (context menu) delete both
1620 void CDirView::OnCtxtDirDelBoth()
1622 DoDirAction(&DirActions::DeleteOnBoth, _("Deleting files..."));
1625 /// Enable/disable Delete Left menu choice on context menu
1626 template<SIDE_TYPE stype>
1627 void CDirView::OnUpdateCtxtDirDel(CCmdUI* pCmdUI)
1629 Counts counts = Count(&DirActions::IsItemDeletableOn<stype>);
1630 pCmdUI->Enable(counts.count > 0);
1631 pCmdUI->SetText(FormatMenuItemString(stype, counts.count, counts.total).c_str());
1634 /// Enable/disable Delete Both menu choice on context menu
1635 void CDirView::OnUpdateCtxtDirDelBoth(CCmdUI* pCmdUI)
1637 Counts counts = Count(&DirActions::IsItemDeletableOnBoth);
1638 pCmdUI->Enable(counts.count > 0);
1639 pCmdUI->SetText(FormatMenuItemStringAll(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1643 * @brief Update "Copy | Right to..." item
1645 template<SIDE_TYPE stype>
1646 void CDirView::OnUpdateCtxtDirCopyTo(CCmdUI* pCmdUI)
1648 Counts counts = Count(&DirActions::IsItemCopyableToOn<stype>);
1649 pCmdUI->Enable(counts.count > 0);
1650 pCmdUI->SetText(FormatMenuItemStringTo(stype, counts.count, counts.total).c_str());
1653 void CDirView::OnUpdateCtxtDirCopyBothTo(CCmdUI* pCmdUI)
1655 Counts counts = Count(&DirActions::IsItemCopyableBothToOn);
1656 pCmdUI->Enable(counts.count > 0);
1657 pCmdUI->SetText(FormatMenuItemStringAllTo(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1660 void CDirView::OnUpdateCtxtDirCopyBothDiffsOnlyTo(CCmdUI* pCmdUI)
1662 Counts counts = Count(&DirActions::IsItemNavigableDiff);
1663 pCmdUI->Enable(counts.count > 0);
1664 pCmdUI->SetText(FormatMenuItemStringDifferencesTo(counts.count, counts.total).c_str());
1668 * @brief Update "Copy | Left/Right/Both " item
1670 template<SIDE_TYPE stype>
1671 void CDirView::OnUpdateCtxtDirCopy2(CCmdUI* pCmdUI)
1673 Counts counts = Count(&DirActions::IsItemCopyableToOn<stype>);
1674 pCmdUI->Enable(counts.count > 0);
1675 pCmdUI->SetText(FormatMenuItemString(stype, counts.count, counts.total).c_str());
1678 void CDirView::OnUpdateCtxtDirCopyBoth2(CCmdUI* pCmdUI)
1680 Counts counts = Count(&DirActions::IsItemCopyableBothToOn);
1681 pCmdUI->Enable(counts.count > 0);
1682 pCmdUI->SetText(FormatMenuItemStringAll(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1686 * @brief Get keydata associated with item in given index.
1687 * @param [in] idx Item's index to list in UI.
1688 * @return Key for item in given index.
1690 DIFFITEM *CDirView::GetItemKey(int idx) const
1692 ASSERT(idx >= 0 && idx < static_cast<int>(m_listViewItems.size()));
1693 return reinterpret_cast<DIFFITEM*>(m_listViewItems[idx].lParam);
1696 // SetItemKey & GetItemKey encapsulate how the display list items
1697 // are mapped to DiffItems, which in turn are DiffContext keys to the actual DIFFITEM data
1700 * @brief Get DIFFITEM data for item.
1701 * This function returns DIFFITEM data for item in given index in GUI.
1702 * @param [in] sel Item's index in folder compare GUI list.
1703 * @return DIFFITEM for item.
1705 const DIFFITEM &CDirView::GetDiffItem(int sel) const
1707 CDirView * pDirView = const_cast<CDirView *>(this);
1708 return pDirView->GetDiffItem(sel);
1712 * Given index in list control, get modifiable reference to its DIFFITEM data
1714 DIFFITEM &CDirView::GetDiffItem(int sel)
1716 DIFFITEM *diffpos = GetItemKey(sel);
1718 // If it is special item, return empty DIFFITEM
1719 if (IsDiffItemSpecial(diffpos))
1721 return *DIFFITEM::GetEmptyItem();
1723 return GetDiffContext().GetDiffRefAt(diffpos);
1726 void CDirView::DeleteItem(int sel, bool removeDIFFITEM)
1728 DIFFITEM *diffpos = GetItemKey(sel);
1729 if (IsDiffItemSpecial(diffpos))
1733 CollapseSubdir(sel);
1734 m_listViewItems.erase(m_listViewItems.begin() + sel);
1735 m_pList->DeleteItem(sel);
1737 else if (GetDiffContext().m_bRecursive || diffpos->HasChildren())
1740 for (it = RevBegin(); it != RevEnd(); )
1743 int cursel = it.m_sel;
1745 if (di.IsAncestor(diffpos) || diffpos == &di)
1747 m_listViewItems.erase(m_listViewItems.begin() + cursel);
1748 m_pList->DeleteItem(cursel);
1754 m_listViewItems.erase(m_listViewItems.begin() + sel);
1755 m_pList->DeleteItem(sel);
1759 if (diffpos->HasChildren())
1760 diffpos->RemoveChildren();
1761 diffpos->DelinkFromSiblings();
1765 m_firstDiffItem.reset();
1766 m_lastDiffItem.reset();
1769 void CDirView::DeleteAllDisplayItems()
1771 // item data are just positions (diffposes)
1772 // that is, they contain no memory needing to be freed
1773 m_pList->DeleteAllItems();
1774 m_listViewItems.clear();
1776 m_firstDiffItem.reset();
1777 m_lastDiffItem.reset();
1781 * @brief Given key, get index of item which has it stored.
1782 * This function searches from list in UI.
1784 int CDirView::GetItemIndex(DIFFITEM *key)
1786 for (size_t i = 0; i < m_listViewItems.size(); ++i)
1788 if (m_listViewItems[i].lParam == reinterpret_cast<LPARAM>(key))
1789 return static_cast<int>(i);
1795 * @brief Get the file names on both sides for specified item.
1796 * @note Return empty strings if item is special item.
1798 void CDirView::GetItemFileNames(int sel, String& strLeft, String& strRight) const
1800 DIFFITEM *diffpos = GetItemKey(sel);
1801 if (IsDiffItemSpecial(diffpos))
1808 const CDiffContext& ctxt = GetDiffContext();
1809 ::GetItemFileNames(ctxt, ctxt.GetDiffAt(diffpos), strLeft, strRight);
1814 * @brief Get the file names on both sides for specified item.
1815 * @note Return empty strings if item is special item.
1817 void CDirView::GetItemFileNames(int sel, PathContext * paths) const
1819 DIFFITEM *diffpos = GetItemKey(sel);
1820 if (IsDiffItemSpecial(diffpos))
1822 for (int nIndex = 0; nIndex < GetDocument()->m_nDirs; nIndex++)
1823 paths->SetPath(nIndex, _T(""));
1827 const CDiffContext& ctxt = GetDiffContext();
1828 *paths = ::GetItemFileNames(ctxt, ctxt.GetDiffAt(diffpos));
1833 * @brief Open selected file with registered application.
1834 * Uses shell file associations to open file with registered
1835 * application. We first try to use "Edit" action which should
1836 * open file to editor, since we are more interested editing
1837 * files than running them (scripts).
1838 * @param [in] stype Side of file to open.
1840 void CDirView::DoOpen(SIDE_TYPE stype)
1842 int sel = GetSingleSelectedItem();
1843 if (sel == -1) return;
1844 DirItemIterator dirBegin = SelBegin();
1845 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1846 if (file.empty()) return;
1847 shell::Edit(file.c_str());
1850 /// Open with dialog for file on selected side
1851 void CDirView::DoOpenWith(SIDE_TYPE stype)
1853 int sel = GetSingleSelectedItem();
1854 if (sel == -1) return;
1855 DirItemIterator dirBegin = SelBegin();
1856 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1857 if (file.empty()) return;
1858 shell::OpenWith(file.c_str());
1861 /// Open selected file on specified side to external editor
1862 void CDirView::DoOpenWithEditor(SIDE_TYPE stype)
1864 int sel = GetSingleSelectedItem();
1865 if (sel == -1) return;
1866 DirItemIterator dirBegin = SelBegin();
1867 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1868 if (file.empty()) return;
1870 CMergeApp::OpenFileToExternalEditor(file);
1873 void CDirView::DoOpenParentFolder(SIDE_TYPE stype)
1875 int sel = GetSingleSelectedItem();
1876 if (sel == -1) return;
1877 DirItemIterator dirBegin = SelBegin();
1878 String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1879 if (file.empty()) return;
1880 shell::OpenParentFolder(file.c_str());
1883 /// Update context menuitem "Open left | with editor"
1884 template<SIDE_TYPE stype>
1885 void CDirView::OnUpdateCtxtDirOpenWithEditor(CCmdUI* pCmdUI)
1887 Counts counts = Count(&DirActions::IsItemOpenableOnWith<stype>);
1888 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1891 // return selected item index, or -1 if none or multiple
1892 int CDirView::GetSingleSelectedItem() const
1894 int sel = -1, sel2 = -1;
1895 sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
1896 if (sel == -1) return -1;
1897 sel2 = m_pList->GetNextItem(sel, LVNI_SELECTED);
1898 if (sel2 != -1) return -1;
1901 // Enable/disable Open Left menu choice on context menu
1902 template<SIDE_TYPE stype>
1903 void CDirView::OnUpdateCtxtDirOpen(CCmdUI* pCmdUI)
1905 Counts counts = Count(&DirActions::IsItemOpenableOn<stype>);
1906 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1909 // Enable/disable Open Left With menu choice on context menu
1910 template<SIDE_TYPE stype>
1911 void CDirView::OnUpdateCtxtDirOpenWith(CCmdUI* pCmdUI)
1913 Counts counts = Count(&DirActions::IsItemOpenableOnWith<stype>);
1914 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1917 // Enable/disable Open Parent Folder menu choice on context menu
1918 template<SIDE_TYPE stype>
1919 void CDirView::OnUpdateCtxtDirOpenParentFolder(CCmdUI* pCmdUI)
1921 Counts counts = Count(&DirActions::IsParentFolderOpenable<stype>);
1922 pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1926 void CDirView::DoUpdateOpen(SELECTIONTYPE selectionType, CCmdUI* pCmdUI, bool openableForDir /*= true*/)
1928 int sel1 = -1, sel2 = -1, sel3 = -1;
1929 if (!GetSelectedItems(&sel1, &sel2, &sel3))
1931 // 0 items or more than 2 items seleted
1932 pCmdUI->Enable(FALSE);
1937 // One item selected
1938 if (selectionType != SELECTIONTYPE_NORMAL)
1940 pCmdUI->Enable(FALSE);
1943 if (!openableForDir)
1945 const DIFFITEM& di1 = GetDiffItem(sel1);
1946 if (di1.diffcode.isDirectory() || IsDiffItemSpecial(GetItemKey(sel1)))
1948 pCmdUI->Enable(FALSE);
1953 else if (sel3 == -1)
1955 // Two items selected
1956 const DIFFITEM& di1 = GetDiffItem(sel1);
1957 const DIFFITEM& di2 = GetDiffItem(sel2);
1958 if (!AreItemsOpenable(GetDiffContext(), selectionType, di1, di2, openableForDir))
1960 pCmdUI->Enable(FALSE);
1966 // Three items selected
1967 const DIFFITEM& di1 = GetDiffItem(sel1);
1968 const DIFFITEM& di2 = GetDiffItem(sel2);
1969 const DIFFITEM& di3 = GetDiffItem(sel3);
1970 if (selectionType != SELECTIONTYPE_NORMAL || !::AreItemsOpenable(GetDiffContext(), di1, di2, di3, openableForDir))
1972 pCmdUI->Enable(FALSE);
1976 pCmdUI->Enable(TRUE);
1980 * @brief Return count of selected items in folder compare.
1982 UINT CDirView::GetSelectedCount() const
1984 return m_pList->GetSelectedCount();
1988 * @brief Return index of first selected item in folder compare.
1990 int CDirView::GetFirstSelectedInd()
1992 return m_pList->GetNextItem(-1, LVNI_SELECTED);
1996 // If none or one item selected select found item
1997 // This is used for scrolling to first diff too
1998 void CDirView::OnFirstdiff()
2000 DirItemIterator it =
2001 std::find_if(Begin(), End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2003 MoveFocus(GetFirstSelectedInd(), it.m_sel, GetSelectedCount());
2006 void CDirView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
2008 pCmdUI->Enable(GetFirstDifferentItem() > -1);
2012 // If none or one item selected select found item
2013 void CDirView::OnLastdiff()
2015 DirItemIterator it =
2016 std::find_if(RevBegin(), RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2018 MoveFocus(GetFirstSelectedInd(), it.m_sel, GetSelectedCount());
2021 void CDirView::OnUpdateLastdiff(CCmdUI* pCmdUI)
2023 pCmdUI->Enable(GetFirstDifferentItem() > -1);
2026 bool CDirView::HasNextDiff()
2028 int lastDiff = GetLastDifferentItem();
2030 // Check if different files were found and
2031 // there is different item after focused item
2032 return (lastDiff > -1) && (GetFocusedItem() < lastDiff);
2035 bool CDirView::HasPrevDiff()
2037 int firstDiff = GetFirstDifferentItem();
2039 // Check if different files were found and
2040 // there is different item before focused item
2041 return (firstDiff > -1) && (firstDiff < GetFocusedItem());
2044 void CDirView::MoveToNextDiff()
2046 int currentInd = GetFocusedItem();
2047 DirItemIterator begin(m_pIList.get(), currentInd + 1);
2048 DirItemIterator it =
2049 std::find_if(begin, End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2051 MoveFocus(currentInd, it.m_sel, GetSelectedCount());
2054 void CDirView::MoveToPrevDiff()
2056 int currentInd = GetFocusedItem();
2057 if (currentInd <= 0)
2059 DirItemIterator begin(m_pIList.get(), currentInd - 1, false, true);
2060 DirItemIterator it =
2061 std::find_if(begin, RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2063 MoveFocus(currentInd, it.m_sel, GetSelectedCount());
2066 void CDirView::OpenNextDiff()
2069 int currentInd = GetFocusedItem();
2070 const DIFFITEM& dip = GetDiffItem(currentInd);
2071 if (!dip.diffcode.isDirectory())
2077 GetParentFrame()->ActivateFrame();
2081 void CDirView::OpenPrevDiff()
2084 int currentInd = GetFocusedItem();
2085 const DIFFITEM& dip = GetDiffItem(currentInd);
2086 if (!dip.diffcode.isDirectory())
2092 GetParentFrame()->ActivateFrame();
2096 void CDirView::OpenFirstFile()
2098 int currentInd = GetFocusedItem();
2099 int firstFileInd = 0;
2101 while (firstFileInd <= currentInd)
2103 DIFFITEM& dip = GetDiffItem(firstFileInd);
2104 if (!dip.diffcode.isDirectory())
2106 MoveFocus(currentInd, firstFileInd, 1);
2114 bool CDirView::IsFirstFile()
2116 int currentInd = GetFocusedItem();
2117 int firstFileInd = 0;
2118 while (firstFileInd <= currentInd)
2120 DIFFITEM& dip = GetDiffItem(firstFileInd);
2121 if (!dip.diffcode.isDirectory())
2123 if (currentInd == firstFileInd)
2133 void CDirView::OpenLastFile()
2135 const int count = m_pList->GetItemCount();
2136 int currentInd = GetFocusedItem();
2137 int lastFileInd = count - 1;
2139 while (lastFileInd >= 0)
2141 DIFFITEM& dip = GetDiffItem(lastFileInd);
2142 if (!dip.diffcode.isDirectory())
2144 MoveFocus(currentInd, lastFileInd, 1);
2152 bool CDirView::IsLastFile()
2154 const int count = m_pList->GetItemCount();
2155 int currentInd = GetFocusedItem();
2156 int lastFileInd = count - 1;
2157 while (lastFileInd >= currentInd)
2159 DIFFITEM& dip = GetDiffItem(lastFileInd);
2160 if (!dip.diffcode.isDirectory())
2162 if (currentInd == lastFileInd)
2172 void CDirView::OpenNextFile()
2174 const int count = m_pList->GetItemCount();
2175 int currentInd = GetFocusedItem();
2176 int nextInd = currentInd + 1;
2177 if (currentInd >= 0)
2179 while (nextInd < count)
2181 DIFFITEM& dip = GetDiffItem(nextInd);
2182 MoveFocus(nextInd - 1, nextInd, 1);
2183 if (!dip.diffcode.isDirectory())
2193 void CDirView::OpenPrevFile()
2195 int currentInd = GetFocusedItem();
2196 int prevInd = currentInd - 1;
2197 if (currentInd >= 0)
2199 while (prevInd >= 0)
2201 DIFFITEM& dip = GetDiffItem(prevInd);
2202 MoveFocus(prevInd + 1, prevInd, 1);
2203 if (!dip.diffcode.isDirectory())
2213 void CDirView::SetActivePane(int pane)
2215 if (m_nActivePane >= 0)
2216 GetParentFrame()->GetHeaderInterface()->SetActive(m_nActivePane, false);
2217 GetParentFrame()->GetHeaderInterface()->SetActive(pane, true);
2218 m_nActivePane = pane;
2222 // If none or one item selected select found item
2223 void CDirView::OnNextdiff()
2229 void CDirView::OnUpdateNextdiff(CCmdUI* pCmdUI)
2231 pCmdUI->Enable(HasNextDiff());
2235 // If none or one item selected select found item
2236 void CDirView::OnPrevdiff()
2242 void CDirView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
2244 pCmdUI->Enable(HasPrevDiff());
2247 void CDirView::OnCurdiff()
2249 const int count = m_pList->GetItemCount();
2251 int i = GetFirstSelectedInd();
2253 // No selection - no diff to go
2257 while (i < count && !found)
2259 UINT selected = m_pList->GetItemState(i, LVIS_SELECTED);
2260 UINT focused = m_pList->GetItemState(i, LVIS_FOCUSED);
2262 if (selected == LVIS_SELECTED && focused == LVIS_FOCUSED)
2264 m_pList->EnsureVisible(i, FALSE);
2271 void CDirView::OnUpdateCurdiff(CCmdUI* pCmdUI)
2273 pCmdUI->Enable(GetFirstSelectedInd() > -1);
2276 int CDirView::GetFocusedItem()
2278 return m_pList->GetNextItem(-1, LVNI_FOCUSED);
2281 int CDirView::GetFirstDifferentItem()
2283 if (!m_firstDiffItem.has_value())
2285 DirItemIterator it =
2286 std::find_if(Begin(), End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2287 m_firstDiffItem = it.m_sel;
2289 return m_firstDiffItem.value();
2292 int CDirView::GetLastDifferentItem()
2294 if (!m_lastDiffItem.has_value())
2296 DirItemIterator it =
2297 std::find_if(RevBegin(), RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2298 m_lastDiffItem = it.m_sel;
2300 return m_lastDiffItem.value();
2304 * @brief Move focus to specified item (and selection if multiple items not selected)
2306 * Moves the focus from item [currentInd] to item [i]
2307 * Additionally, if there are not multiple items selected,
2308 * deselects item [currentInd] and selects item [i]
2310 void CDirView::MoveFocus(int currentInd, int i, int selCount)
2314 // Not multiple items selected, so bring selection with us
2315 m_pList->SetItemState(currentInd, 0, LVIS_SELECTED);
2316 m_pList->SetItemState(currentInd, 0, LVIS_FOCUSED);
2317 m_pList->SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2318 m_pList->SetSelectionMark(i);
2321 // Move focus to specified item
2322 // (this automatically defocuses old item)
2323 m_pList->SetItemState(i, LVIS_FOCUSED, LVIS_FOCUSED);
2324 m_pList->EnsureVisible(i, FALSE);
2327 void CDirView::OnUpdateSave(CCmdUI* pCmdUI)
2329 pCmdUI->Enable(FALSE);
2332 CDirFrame * CDirView::GetParentFrame()
2334 // can't verify cast without introducing more coupling
2335 // (CDirView doesn't include DirFrame.h)
2336 return static_cast<CDirFrame *>(__super::GetParentFrame());
2339 void CDirView::OnRefresh()
2341 m_pSavedTreeState.reset(SaveTreeState(GetDiffContext()));
2342 GetDocument()->Rescan();
2345 BOOL CDirView::PreTranslateMessage(MSG* pMsg)
2347 // Handle special shortcuts here
2348 if (pMsg->message == WM_KEYDOWN)
2352 // Check if we got 'ESC pressed' -message
2353 if (pMsg->wParam == VK_ESCAPE)
2355 if (GetDocument()->m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPARING)
2357 GetDocument()->AbortCurrentScan();
2361 if (m_nEscCloses != 0)
2363 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
2367 // Check if we got 'DEL pressed' -message
2368 if (pMsg->wParam == VK_DELETE)
2370 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_MERGE_DELETE);
2373 int sel = GetFocusedItem();
2374 // Check if we got 'Backspace pressed' -message
2375 if (pMsg->wParam == VK_BACK)
2377 if (!GetDiffContext().m_bRecursive)
2379 OpenParentDirectory(GetDocument());
2382 else if (m_bTreeMode && sel >= 0)
2384 const DIFFITEM& di = GetDiffItem(sel);
2385 assert(di.HasParent());
2388 int i = GetItemIndex(di.GetParentLink());
2390 MoveFocus(sel, i, GetSelectedCount());
2396 DIFFITEM& dip = this->GetDiffItem(sel);
2397 if (pMsg->wParam == VK_LEFT)
2399 if (m_bTreeMode && GetDiffContext().m_bRecursive && (!(dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren()))
2400 PostMessage(WM_KEYDOWN, VK_BACK);
2402 CollapseSubdir(sel);
2405 if (pMsg->wParam == VK_SUBTRACT)
2407 CollapseSubdir(sel);
2410 if (pMsg->wParam == VK_RIGHT)
2412 if (m_bTreeMode && GetDiffContext().m_bRecursive && dip.customFlags & ViewCustomFlags::EXPANDED && dip.HasChildren())
2413 PostMessage(WM_KEYDOWN, VK_DOWN);
2418 if (pMsg->wParam == VK_ADD)
2423 if (pMsg->wParam == VK_MULTIPLY)
2425 ExpandSubdir(sel, true);
2432 // ESC doesn't close window when user is renaming an item.
2433 if (pMsg->wParam == VK_ESCAPE)
2435 m_bUserCancelEdit = true;
2437 // The edit control send LVN_ENDLABELEDIT when it loses focus,
2438 // so we use it to cancel the rename action.
2439 m_pList->SetFocus();
2441 // Stop the ESC before it reach the main frame which might
2442 // cause a program termination.
2447 return __super::PreTranslateMessage(pMsg);
2450 void CDirView::OnUpdateRefresh(CCmdUI* pCmdUI)
2452 UINT threadState = GetDocument()->m_diffThread.GetThreadState();
2453 pCmdUI->Enable(threadState != CDiffThread::THREAD_COMPARING);
2457 * @brief Called when compare thread asks UI update.
2458 * @note Currently thread asks update after compare is ready
2461 LRESULT CDirView::OnUpdateUIMessage(WPARAM wParam, LPARAM lParam)
2463 UNREFERENCED_PARAMETER(lParam);
2465 CDirDoc * pDoc = GetDocument();
2466 ASSERT(pDoc != nullptr);
2468 // Since the Collect thread deletes the DiffItems in the rescan by "Update selection",
2469 // the UI update process should not be executed until the Collect thread process is completed
2470 // to avoid accessing the deleted DiffItem.
2471 if (pDoc->m_diffThread.IsMarkedRescan() && pDoc->m_diffThread.GetCollectThreadState() != CDiffThread::THREAD_COMPLETED)
2474 return 0; // return value unused
2477 if (wParam == CDiffThread::EVENT_COMPARE_COMPLETED)
2479 if (!m_pSavedTreeState)
2481 if (m_nExpandSubdirs == EXPAND_DIFFERENT)
2482 OnViewExpandDifferentSubdirs();
2483 if (m_nExpandSubdirs == EXPAND_IDENTICAL)
2484 OnViewExpandIdenticalSubdirs();
2487 if (pDoc->GetDiffContext().m_pPropertySystem && pDoc->GetDiffContext().m_pPropertySystem->HasHashProperties())
2488 pDoc->GetDiffContext().CreateDuplicateValueMap();
2490 pDoc->CompareReady();
2492 if (!pDoc->GetGeneratingReport())
2495 if (!pDoc->GetReportFile().empty())
2497 OnToolsGenerateReport();
2498 pDoc->SetReportFile(_T(""));
2501 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
2506 // If compare took more than TimeToSignalCompare seconds, notify user
2507 m_elapsed = pDoc->GetElapsedTime();
2508 SetTimer(STATUSBAR_UPDATE, 150, nullptr);
2509 if (m_elapsed > TimeToSignalCompare * CLOCKS_PER_SEC)
2511 GetMainFrame()->StartFlashing();
2513 else if (wParam == CDiffThread::EVENT_COMPARE_PROGRESSED)
2515 InvalidateRect(nullptr, FALSE);
2517 else if (wParam == CDiffThread::EVENT_COLLECT_COMPLETED)
2519 if (m_pSavedTreeState != nullptr)
2521 RestoreTreeState(GetDiffContext(), m_pSavedTreeState.get());
2522 m_pSavedTreeState.reset();
2527 if (m_nExpandSubdirs == EXPAND_ALL)
2528 OnViewExpandAllSubdirs();
2533 HideItems(GetDiffContext().m_vCurrentlyHiddenItems);
2536 return 0; // return value unused
2539 BOOL CDirView::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
2541 NMHDR * hdr = reinterpret_cast<NMHDR *>(lParam);
2542 if (hdr->code == HDN_ENDDRAG)
2543 return OnHeaderEndDrag((LPNMHEADER)hdr, pResult);
2544 if (hdr->code == HDN_BEGINDRAG)
2545 return OnHeaderBeginDrag((LPNMHEADER)hdr, pResult);
2547 return __super::OnNotify(wParam, lParam, pResult);
2550 BOOL CDirView::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
2552 if (uMsg == WM_NOTIFY)
2554 NMHDR *pNMHDR = (NMHDR *)lParam;
2555 switch (pNMHDR->code)
2557 case LVN_GETDISPINFO:
2558 ReflectGetdispinfo((NMLVDISPINFO *)lParam);
2560 case LVN_GETINFOTIPW:
2561 case LVN_GETINFOTIPA:
2565 return __super::OnChildNotify(uMsg, wParam, lParam, pResult);
2569 * @brief User is starting to drag a column header
2571 bool CDirView::OnHeaderBeginDrag(LPNMHEADER hdr, LRESULT* pResult)
2573 // save column widths before user reorders them
2574 // so we can reload them on the end drag
2575 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS;
2576 GetOptionsMgr()->SaveOption(keyname,
2577 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1)).c_str());
2582 * @brief User just finished dragging a column header
2584 bool CDirView::OnHeaderEndDrag(LPNMHEADER hdr, LRESULT* pResult)
2586 int src = hdr->iItem;
2587 int dest = hdr->pitem->iOrder;
2588 bool allowDrop = true;
2589 *pResult = allowDrop ? FALSE : TRUE;
2590 if (allowDrop && src != dest && dest != -1)
2592 m_pColItems->MoveColumn(src, dest);
2599 * @brief Remove any windows reordering of columns
2601 void CDirView::FixReordering()
2604 lvcol.mask = LVCF_ORDER;
2607 lvcol.pszText = nullptr;
2609 for (int i = 0; i < m_pColItems->GetColCount(); ++i)
2612 GetListCtrl().SetColumn(i, &lvcol);
2616 /** @brief Add columns to display, loading width & order from registry. */
2617 void CDirView::LoadColumnHeaderItems()
2619 bool dummyflag = false;
2621 CHeaderCtrl * h = m_pList->GetHeaderCtrl();
2622 if (h->GetItemCount())
2625 while (m_pList->GetHeaderCtrl()->GetItemCount() > 1)
2626 m_pList->DeleteColumn(1);
2629 for (int i = 0; i < m_pColItems->GetDispColCount(); ++i)
2632 lvc.mask = LVCF_FMT + LVCF_SUBITEM + LVCF_TEXT;
2633 lvc.fmt = LVCFMT_LEFT;
2635 lvc.pszText = _T("text");
2637 m_pList->InsertColumn(i, &lvc);
2640 m_pList->DeleteColumn(1);
2644 void CDirView::SetFont(const LOGFONT & lf)
2646 m_font.DeleteObject();
2647 m_font.CreateFontIndirect(&lf);
2648 CWnd::SetFont(&m_font);
2651 /** @brief Fire off a resort of the data, to take place when things stabilize. */
2652 void CDirView::InitiateSort()
2654 PostMessage(WM_TIMER, COLUMN_REORDER);
2657 void CDirView::OnTimer(UINT_PTR nIDEvent)
2659 if (nIDEvent == COLUMN_REORDER)
2661 // Remove the windows reordering, as we're doing it ourselves
2663 // Now redraw screen
2664 UpdateColumnNames();
2665 m_pColItems->LoadColumnWidths(
2666 GetOptionsMgr()->GetString(GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS),
2667 std::bind(&CListCtrl::SetColumnWidth, m_pList, _1, _2), GetDefColumnWidth());
2670 else if (nIDEvent == STATUSBAR_UPDATE)
2672 KillTimer(STATUSBAR_UPDATE);
2673 int items = GetSelectedCount();
2677 msg = strutils::format(_("Elapsed time: %ld ms"), m_elapsed);
2682 msg = (items == 1) ? _("1 item selected") : strutils::format_string1(_("%1 items selected"), strutils::to_str(items));
2684 GetParentFrame()->SetStatus(msg.c_str());
2687 __super::OnTimer(nIDEvent);
2691 * @brief Change left-side readonly-status
2693 template<SIDE_TYPE stype>
2694 void CDirView::OnReadOnly()
2696 const int index = SideToIndex(GetDiffContext(), stype);
2697 bool bReadOnly = GetDocument()->GetReadOnly(index);
2698 GetDocument()->SetReadOnly(index, !bReadOnly);
2702 * @brief Update left-readonly menu item
2704 void CDirView::OnUpdateReadOnly(CCmdUI* pCmdUI, SIDE_TYPE stype)
2706 const int index = SideToIndex(GetDiffContext(), stype);
2707 bool bReadOnly = GetDocument()->GetReadOnly(index);
2708 if (stype != SIDE_MIDDLE)
2710 pCmdUI->Enable(TRUE);
2711 pCmdUI->SetCheck(bReadOnly);
2715 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
2716 pCmdUI->SetCheck(bReadOnly && GetDocument()->m_nDirs > 2);
2721 * @brief Update left-side readonly statusbar item
2723 void CDirView::OnUpdateStatusLeftRO(CCmdUI* pCmdUI)
2725 bool bROLeft = GetDocument()->GetReadOnly(0);
2726 pCmdUI->Enable(bROLeft);
2730 * @brief Update middle readonly statusbar item
2732 void CDirView::OnUpdateStatusMiddleRO(CCmdUI* pCmdUI)
2734 bool bROMiddle = GetDocument()->GetReadOnly(1);
2735 pCmdUI->Enable(bROMiddle && GetDocument()->m_nDirs > 2);
2739 * @brief Update right-side readonly statusbar item
2741 void CDirView::OnUpdateStatusRightRO(CCmdUI* pCmdUI)
2743 bool bRORight = GetDocument()->GetReadOnly(GetDocument()->m_nDirs - 1);
2744 pCmdUI->Enable(bRORight);
2748 * @brief Open dialog to customize dirview columns
2750 void CDirView::OnCustomizeColumns()
2752 // Located in DirViewColHandler.cpp
2754 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_ORDERS : OPT_DIRVIEW3_COLUMN_ORDERS;
2755 GetOptionsMgr()->SaveOption(keyname, m_pColItems->SaveColumnOrders());
2758 void CDirView::OnOpenWithUnpacker()
2761 sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
2764 PackingInfo* infoUnpacker = nullptr;
2765 PrediffingInfo* infoPrediffer = nullptr;
2766 CDiffContext& ctxt = GetDiffContext();
2767 String filteredFilenames = ctxt.GetFilteredFilenames(GetDiffItem(sel));
2768 ctxt.FetchPluginInfos(filteredFilenames, &infoUnpacker, &infoPrediffer);
2769 // let the user choose a handler
2770 CSelectPluginDlg dlg(infoUnpacker->GetPluginPipeline(), filteredFilenames,
2771 CSelectPluginDlg::PluginType::Unpacker, false, this);
2772 if (dlg.DoModal() == IDOK)
2774 PackingInfo infoUnpackerNew(dlg.GetPluginPipeline());
2775 OpenSelection(SELECTIONTYPE_NORMAL, &infoUnpackerNew, false);
2781 void CDirView::OnUpdateCtxtOpenWithUnpacker(CCmdUI* pCmdUI)
2783 if (!GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
2785 pCmdUI->Enable(FALSE);
2789 // we need one selected file, existing on both side
2790 if (m_pList->GetSelectedCount() != 1)
2791 pCmdUI->Enable(FALSE);
2795 sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
2796 const DIFFITEM& di = GetDiffItem(sel);
2797 if (di.diffcode.isDirectory())
2799 pCmdUI->Enable(FALSE);
2802 pCmdUI->Enable(IsItemDeletableOnBoth(GetDiffContext(), di));
2807 * @brief Fill string list with current dirview column registry key names
2809 std::vector<String> CDirView::GetCurrentColRegKeys()
2811 std::vector<String> colKeys;
2812 int nphyscols = GetListCtrl().GetHeaderCtrl()->GetItemCount();
2813 for (int col = 0; col < nphyscols; ++col)
2815 int logcol = m_pColItems->ColPhysToLog(col);
2816 colKeys.push_back(m_pColItems->GetColRegValueNameBase(logcol));
2821 struct FileCmpReport: public IFileCmpReport
2823 explicit FileCmpReport(CDirView *pDirView) : m_pDirView(pDirView) {}
2824 ~FileCmpReport() override {}
2825 bool operator()(REPORT_TYPE nReportType, IListCtrl *pList, int nIndex, const String &sDestDir, String &sLinkPath) override
2827 const CDiffContext& ctxt = m_pDirView->GetDiffContext();
2828 const DIFFITEM &di = m_pDirView->GetDiffItem(nIndex);
2830 String sLinkFullPath = paths::ConcatPath(ctxt.GetLeftPath(), di.diffFileInfo[0].GetFile());
2832 if (di.diffcode.isDirectory() || !IsItemNavigableDiff(ctxt, di) || IsArchiveFile(sLinkFullPath))
2838 sLinkPath = strutils::format(_T("%d_"), nIndex) + di.diffFileInfo[0].GetFile();
2840 strutils::replace(sLinkPath, _T("\\"), _T("_"));
2841 sLinkPath += _T(".html");
2842 String sReportPath = paths::ConcatPath(sDestDir, sLinkPath);
2843 bool completed = false;
2845 m_pDirView->MoveFocus(m_pDirView->GetFirstSelectedInd(), nIndex, m_pDirView->GetSelectedCount());
2846 m_pDirView->PostMessage(MSG_GENERATE_FLIE_COMPARE_REPORT,
2847 reinterpret_cast<WPARAM>(sReportPath.c_str()),
2848 reinterpret_cast<LPARAM>(&completed));
2850 CMainFrame::WaitAndDoMessageLoop(completed, 5);
2856 CDirView *m_pDirView;
2859 LRESULT CDirView::OnGenerateFileCmpReport(WPARAM wParam, LPARAM lParam)
2863 auto *pReportFileName = reinterpret_cast<const tchar_t *>(wParam);
2864 bool *pCompleted = reinterpret_cast<bool *>(lParam);
2865 if (IMergeDoc * pMergeDoc = GetMainFrame()->GetActiveIMergeDoc())
2867 pMergeDoc->GenerateReport(pReportFileName);
2868 pMergeDoc->CloseNow();
2871 while (::PeekMessage(&msg, nullptr, NULL, NULL, PM_NOREMOVE))
2872 if (!AfxGetApp()->PumpMessage())
2874 GetMainFrame()->OnUpdateFrameTitle(FALSE);
2880 * @brief Generate report from dir compare results.
2882 void CDirView::OnToolsGenerateReport()
2884 CDirDoc *pDoc = GetDocument();
2885 DirCmpReportDlg dlg;
2887 dlg.m_sReportFile = pDoc->GetReportFile();
2888 if (dlg.m_sReportFile.empty() && dlg.DoModal() != IDOK)
2891 pDoc->SetGeneratingReport(true);
2892 const CDiffContext& ctxt = GetDiffContext();
2894 PathContext paths = ctxt.GetNormalizedPaths();
2896 // If inside archive, convert paths
2897 if (pDoc->IsArchiveFolders())
2899 for (int i = 0; i < paths.GetSize(); i++)
2900 pDoc->ApplyDisplayRoot(i, paths[i]);
2903 DirCmpReport *pReport = new DirCmpReport(GetCurrentColRegKeys());
2904 pReport->SetRootPaths(paths);
2905 pReport->SetColumns(m_pColItems->GetDispColCount());
2906 pReport->SetFileCmpReport(new FileCmpReport(this));
2907 pReport->SetList(new IListCtrlImpl(m_pList->m_hWnd, m_listViewItems));
2908 pReport->SetReportType(dlg.m_nReportType);
2909 pReport->SetReportFile(dlg.m_sReportFile);
2910 pReport->SetCopyToClipboard(dlg.m_bCopyToClipboard);
2911 pReport->SetIncludeFileCmpReport(dlg.m_bIncludeFileCmpReport);
2912 pDoc->SetReport(pReport);
2917 * @brief Generate patch from files selected.
2919 * Creates a patch from selected files in active directory compare, or
2920 * active file compare. Files in file compare must be saved before
2923 void CDirView::OnToolsGeneratePatch()
2926 const CDiffContext& ctxt = GetDiffContext();
2928 // Get selected items from folder compare
2929 bool bValidFiles = true;
2930 for (DirItemIterator it = SelBegin(); bValidFiles && it != SelEnd(); ++it)
2932 const DIFFITEM &item = *it;
2933 if (item.diffcode.isBin())
2935 LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING |
2936 MB_DONT_DISPLAY_AGAIN, IDS_CANNOT_CREATE_BINARYPATCH);
2937 bValidFiles = false;
2942 // Format full paths to files (leftFile/rightFile)
2943 String leftFile = item.getFilepath(0, ctxt.GetNormalizedPath(0));
2944 if (!leftFile.empty())
2945 leftFile = paths::ConcatPath(leftFile, item.diffFileInfo[0].filename);
2946 String rightFile = item.getFilepath(1, ctxt.GetNormalizedPath(1));
2947 if (!rightFile.empty())
2948 rightFile = paths::ConcatPath(rightFile, item.diffFileInfo[1].filename);
2950 // Format relative paths to files in folder compare
2951 String leftpatch = item.diffFileInfo[0].path;
2952 if (!leftpatch.empty())
2953 leftpatch += _T("/");
2954 leftpatch += item.diffFileInfo[0].filename;
2955 String rightpatch = item.diffFileInfo[1].path;
2956 if (!rightpatch.empty())
2957 rightpatch += _T("/");
2958 rightpatch += item.diffFileInfo[1].filename;
2959 patcher.AddFiles(leftFile, leftpatch, rightFile, rightpatch);
2963 patcher.CreatePatch();
2967 * @brief Add special items for non-recursive compare
2968 * to directory view.
2970 * Currently only special item is ".." for browsing to
2972 * @return number of items added to view
2974 int CDirView::AddSpecialItems()
2976 CDirDoc *pDoc = GetDocument();
2978 bool bEnable = true;
2979 PathContext pathsParent;
2980 switch (CheckAllowUpwardDirectory(GetDiffContext(), pDoc->m_pTempPathContext, pathsParent))
2982 case AllowUpwardDirectory::No:
2986 AddParentFolderItem(bEnable);
2989 case AllowUpwardDirectory::Never:
2996 * @brief Add "Parent folder" ("..") item to directory view
2998 void CDirView::AddParentFolderItem(bool bEnable)
3000 AddNewItem(0, (DIFFITEM *)SPECIAL_ITEM_POS, bEnable ? DIFFIMG_DIRUP : DIFFIMG_DIRUP_DISABLE, 0);
3003 void CDirView::OnCtxtDirZip(int flag)
3005 if (!HasZipSupport())
3007 LangMessageBox(IDS_NO_ZIP_SUPPORT, MB_ICONINFORMATION);
3013 this, LVNI_SELECTED | flag
3014 ).CompressArchive();
3017 void CDirView::ShowShellContextMenu(UINT id)
3019 std::vector<SIDE_TYPE> stypes;
3020 CShellContextMenu *pContextMenu = nullptr;
3023 case ID_DIR_SHELL_CONTEXT_MENU_LEFT:
3024 if (m_pShellContextMenuLeft == nullptr)
3025 m_pShellContextMenuLeft.reset(new CShellContextMenu(LeftCmdFirst, LeftCmdLast));
3026 pContextMenu = m_pShellContextMenuLeft.get();
3027 stypes = { SIDE_LEFT };
3029 case ID_DIR_SHELL_CONTEXT_MENU_MIDDLE:
3030 if (m_pShellContextMenuMiddle == nullptr)
3031 m_pShellContextMenuMiddle.reset(new CShellContextMenu(MiddleCmdFirst, MiddleCmdLast));
3032 pContextMenu = m_pShellContextMenuMiddle.get();
3033 stypes = { SIDE_MIDDLE };
3035 case ID_DIR_SHELL_CONTEXT_MENU_RIGHT:
3036 if (m_pShellContextMenuRight == nullptr)
3037 m_pShellContextMenuRight.reset(new CShellContextMenu(RightCmdFirst, RightCmdLast));
3038 pContextMenu = m_pShellContextMenuRight.get();
3039 stypes = { SIDE_RIGHT };
3042 if (m_pShellContextMenuBoth == nullptr)
3043 m_pShellContextMenuBoth.reset(new CShellContextMenu(BothCmdFirst, BothCmdLast));
3044 pContextMenu = m_pShellContextMenuBoth.get();
3045 if (GetDocument()->m_nDirs < 3)
3046 stypes = { SIDE_LEFT, SIDE_RIGHT };
3048 stypes = { SIDE_LEFT, SIDE_MIDDLE, SIDE_RIGHT };
3055 pContextMenu->Initialize();
3056 for (const auto stype : stypes)
3058 ApplyFolderNameAndFileName(SelBegin(), SelEnd(), stype, GetDiffContext(),
3059 [&](const String& path, const String& filename) { pContextMenu->AddItem(path, filename); });
3061 if (pContextMenu->RequeryShellContextMenu())
3064 GetCursorPos(&point);
3065 HWND hWnd = GetSafeHwnd();
3066 CFrameWnd* pFrame = GetTopLevelFrame();
3067 ASSERT(pFrame != nullptr);
3068 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3069 pFrame->m_bAutoMenuEnable = FALSE;
3070 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3072 pContextMenu->InvokeCommand(nCmd, hWnd);
3073 pContextMenu->ReleaseShellContextMenu();
3074 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3079 * @brief Select all visible items in dir compare
3081 void CDirView::OnSelectAll()
3083 // While the user is renaming an item, select all the edited text.
3084 CEdit *pEdit = m_pList->GetEditControl();
3085 if (pEdit != nullptr)
3087 pEdit->SetSel(pEdit->GetWindowTextLength());
3091 int selCount = m_pList->GetItemCount();
3093 for (int i = 0; i < selCount; i++)
3095 // Don't select special items (SPECIAL_ITEM_POS)
3096 DIFFITEM *diffpos = GetItemKey(i);
3097 if (!IsDiffItemSpecial(diffpos))
3098 m_pList->SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
3104 * @brief Update "Select All" item
3106 void CDirView::OnUpdateSelectAll(CCmdUI* pCmdUI)
3108 bool bEnable = (!IsLabelEdit()) || (m_pList->GetItemCount() > 0);
3109 pCmdUI->Enable(bEnable);
3113 * @brief Handle clicks in plugin context view in list
3115 void CDirView::OnPluginSettings(UINT nID)
3117 bool unpacker = (ID_UNPACKER_SETTINGS_NONE <= nID && nID <= ID_UNPACKER_SETTINGS_SELECT);
3118 String pluginPipeline;
3121 case ID_PREDIFFER_SETTINGS_NONE:
3122 case ID_UNPACKER_SETTINGS_NONE:
3123 pluginPipeline.clear();
3125 case ID_PREDIFFER_SETTINGS_AUTO:
3126 case ID_UNPACKER_SETTINGS_AUTO:
3127 pluginPipeline = _T("<Automatic>");
3129 case ID_PREDIFFER_SETTINGS_SELECT:
3130 case ID_UNPACKER_SETTINGS_SELECT:
3131 int sel = m_pList->GetNextItem(-1, LVNI_SELECTED);
3132 PackingInfo* infoUnpacker = nullptr;
3133 PrediffingInfo* infoPrediffer = nullptr;
3134 CDiffContext& ctxt = GetDiffContext();
3135 String filteredFilenames = ctxt.GetFilteredFilenames(GetDiffItem(sel));
3136 ctxt.FetchPluginInfos(filteredFilenames, &infoUnpacker, &infoPrediffer);
3137 GetDiffContext().FetchPluginInfos(filteredFilenames, &infoUnpacker, &infoPrediffer);
3138 CSelectPluginDlg dlg(infoUnpacker->GetPluginPipeline(), filteredFilenames,
3139 unpacker ? CSelectPluginDlg::PluginType::Unpacker : CSelectPluginDlg::PluginType::Prediffer, false, this);
3140 if (dlg.DoModal() != IDOK)
3142 pluginPipeline = dlg.GetPluginPipeline();
3145 ApplyPluginPipeline(SelBegin(), SelEnd(), GetDiffContext(), unpacker, pluginPipeline);
3150 * @brief Updates just before displaying plugin context view in list
3152 void CDirView::OnUpdatePluginMode(CCmdUI* pCmdUI)
3154 // 2004-04-03, Perry
3155 // CMainFrame::OnUpdatePluginUnpackMode handles this for global unpacking
3156 // and is the template to copy, but here, this is a bit tricky
3157 // as a group of files may be selected
3158 // and they may not all have the same setting
3159 // so I'm not trying this right now
3161 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
3163 BCMenu *pPopup = static_cast<BCMenu*>(pCmdUI->m_pSubMenu);
3164 if (pPopup == nullptr)
3167 bool unpacker = (ID_UNPACKER_SETTINGS_NONE <= pCmdUI->m_nID && pCmdUI->m_nID <= ID_UNPACKER_SETTINGS_SELECT);
3168 auto counts = CountPluginNoneAutoOthers(SelBegin(), SelEnd(), GetDiffContext(), unpacker);
3170 CheckContextMenu(pPopup, unpacker ? ID_UNPACKER_SETTINGS_NONE : ID_PREDIFFER_SETTINGS_NONE, (std::get<0>(counts) > 0));
3171 CheckContextMenu(pPopup, unpacker ? ID_UNPACKER_SETTINGS_AUTO : ID_PREDIFFER_SETTINGS_AUTO, (std::get<1>(counts) > 0));
3172 CheckContextMenu(pPopup, unpacker ? ID_UNPACKER_SETTINGS_SELECT : ID_PREDIFFER_SETTINGS_SELECT, (std::get<2>(counts) > 0));
3176 * @brief Refresh cached options.
3178 void CDirView::RefreshOptions()
3180 m_nEscCloses = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
3181 m_nExpandSubdirs = static_cast<eExpandSubfoldersType>(GetOptionsMgr()->GetInt(OPT_DIRVIEW_EXPAND_SUBDIRS));
3182 Options::DirColors::Load(GetOptionsMgr(), m_cachedColors);
3183 m_bUseColors = GetOptionsMgr()->GetBool(OPT_DIRCLR_USE_COLORS);
3184 m_pList->SetBkColor(m_bUseColors ? m_cachedColors.clrDirMargin : GetSysColor(COLOR_WINDOW));
3189 * @brief Copy selected item left side paths (containing filenames) to clipboard.
3191 void CDirView::OnCopyPathnames(SIDE_TYPE stype)
3193 std::list<String> list;
3194 CopyPathnames(SelBegin(), SelEnd(), std::back_inserter(list), stype, GetDiffContext());
3195 PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
3198 void CDirView::OnCopyBothPathnames()
3200 std::list<String> list;
3201 CopyBothPathnames(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
3202 PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
3206 * @brief Copy selected item filenames to clipboard.
3208 void CDirView::OnCopyFilenames()
3210 std::list<String> list;
3211 CopyFilenames(SelBegin(), SelEnd(), std::back_inserter(list));
3212 PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
3216 * @brief Enable/Disable dirview Copy Filenames context menu item.
3218 void CDirView::OnUpdateCopyFilenames(CCmdUI* pCmdUI)
3220 pCmdUI->Enable(Count(&DirActions::IsItemFile).count > 0);
3224 * @brief Copy selected item left side to clipboard.
3226 void CDirView::OnCopyToClipboard(SIDE_TYPE stype)
3228 std::list<String> list;
3229 CopyPathnames(SelBegin(), SelEnd(), std::back_inserter(list), stype, GetDiffContext());
3230 PutFilesToClipboard(list, GetMainFrame()->GetSafeHwnd());
3234 * @brief Copy selected item both side to clipboard.
3236 void CDirView::OnCopyBothToClipboard()
3238 std::list<String> list;
3239 CopyBothPathnames(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
3240 PutFilesToClipboard(list, GetMainFrame()->GetSafeHwnd());
3244 * @brief Copy all displayed columns of selected items to clipboard.
3246 void CDirView::OnCopyAllDisplayedColumns()
3248 int ncols = m_pColItems->GetDispColCount();
3254 row = m_pList->GetNextItem(row, LVNI_SELECTED);
3257 for (int col = 0; col < ncols; col++)
3259 text += m_pList->GetItemText(row, col);
3260 if (col < ncols - 1)
3266 PutToClipboard(text, GetMainFrame()->GetSafeHwnd());
3270 * @brief Enable/Disable dirview Copy All Displayed Columns context menu item.
3271 * @param[in] pCmdUI UI component to update.
3273 void CDirView::OnUpdateCopyAllDisplayedColumns(CCmdUI* pCmdUI)
3275 pCmdUI->Enable(m_pList->GetSelectedCount() != 0);
3279 * @brief Rename a selected item on both sides.
3282 void CDirView::OnItemRename()
3284 ASSERT(1 == m_pList->GetSelectedCount());
3285 int nSelItem = m_pList->GetNextItem(-1, LVNI_SELECTED);
3286 ASSERT(-1 != nSelItem);
3287 m_pList->EditLabel(nSelItem);
3291 * @brief Enable/Disable dirview Rename context menu item.
3294 void CDirView::OnUpdateItemRename(CCmdUI* pCmdUI)
3296 bool bEnabled = (1 == m_pList->GetSelectedCount());
3299 Counts counts = Count(&DirActions::IsItemRenamable);
3300 bEnabled = (counts.count > 0 && counts.total == 1);
3302 pCmdUI->Enable(bEnabled);
3306 * @brief hide selected item filenames (removes them from the ListView)
3308 void CDirView::OnHideFilenames()
3310 CDiffContext& ctxt = GetDiffContext();
3311 int selection_index;
3312 String hiddden_item_path;
3314 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
3317 while ((it = SelRevBegin()) != SelRevEnd())
3320 selection_index = it.m_sel;
3321 hiddden_item_path = di.getItemRelativePath();
3322 SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
3323 DeleteItem(selection_index);
3324 ctxt.m_vCurrentlyHiddenItems.push_back(hiddden_item_path);
3326 m_pList->SetRedraw(TRUE); // Turn updating back on
3330 * @brief determine if an item-relative-path is contained in the list of items to hide
3332 bool CDirView::IsItemToHide(const String& currentItem, const std::vector<String>& ItemsToHide) const
3334 return std::find(ItemsToHide.begin(), ItemsToHide.end(), currentItem) != ItemsToHide.end();
3338 * @brief hides items specified in the .winmerge file
3341 void CDirView::HideItems(const std::vector<String>& ItemsToHide)
3343 CDiffContext& ctxt = GetDiffContext();
3344 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
3345 while (diffpos != nullptr)
3347 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
3348 if (IsItemToHide(di.getItemRelativePath(), ItemsToHide))
3349 SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
3352 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
3355 const size_t num_to_hide = ItemsToHide.size();
3356 DirItemIterator it = RevBegin();
3357 while((num_hidden < num_to_hide) && (it != RevEnd()))
3360 if (di.customFlags & ViewCustomFlags::HIDDEN)
3362 DeleteItem(it.m_sel);
3368 m_pList->SetRedraw(TRUE); // Turn updating back on
3372 * @brief update menu item
3374 void CDirView::OnUpdateHideFilenames(CCmdUI* pCmdUI)
3376 pCmdUI->Enable(m_pList->GetSelectedCount() != 0);
3379 /// User chose (context men) Move from right to left
3380 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
3381 void CDirView::OnCtxtDirMove()
3383 DoDirAction(&DirActions::Move<srctype, dsttype>, _("Moveing files..."));
3386 /// User chose (context menu) Move left to...
3387 template<SIDE_TYPE stype>
3388 void CDirView::OnCtxtDirMoveTo()
3390 DoDirActionTo(stype, &DirActions::MoveTo<stype>, _("Moving files..."));
3393 /// Update context menu Move Right to Left item
3394 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
3395 void CDirView::OnUpdateCtxtDirMove(CCmdUI* pCmdUI)
3397 DoUpdateDirMove<srctype, dsttype>(pCmdUI, eContext);
3401 * @brief Update "Move | Left to..." item
3403 template<SIDE_TYPE stype>
3404 void CDirView::OnUpdateCtxtDirMoveTo(CCmdUI* pCmdUI)
3406 Counts counts = Count(&DirActions::IsItemMovableToOn<stype>);
3407 pCmdUI->Enable(counts.count > 0);
3408 pCmdUI->SetText(FormatMenuItemStringTo(stype, counts.count, counts.total).c_str());
3412 * @brief Update title after window is resized.
3414 void CDirView::OnSize(UINT nType, int cx, int cy)
3416 __super::OnSize(nType, cx, cy);
3417 GetDocument()->SetTitle(nullptr);
3421 * @brief Called when user selects 'Delete' from 'Merge' menu.
3423 void CDirView::OnDelete()
3425 DoDirAction(&DirActions::DeleteOnEitherOrBoth, _("Deleting files..."));
3429 * @brief Enables/disables 'Delete' item in 'Merge' menu.
3431 void CDirView::OnUpdateDelete(CCmdUI* pCmdUI)
3433 pCmdUI->Enable(Count(&DirActions::IsItemDeletableOnEitherOrBoth).count > 0);
3437 * @brief Called when item state is changed.
3439 * Show count of selected items in statusbar.
3441 void CDirView::OnItemChanged(NMHDR* pNMHDR, LRESULT* pResult)
3443 NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
3445 // If item's selected state changed
3446 if ((pNMListView->uOldState & LVIS_SELECTED) !=
3447 (pNMListView->uNewState & LVIS_SELECTED))
3449 SetTimer(STATUSBAR_UPDATE, 100, nullptr);
3455 * @brief Called before user start to item label edit.
3457 * Disable label edit if initiated from a user double-click.
3459 afx_msg void CDirView::OnBeginLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
3461 Counts counts = Count(&DirActions::IsItemRenamable);
3462 *pResult = !(counts.count > 0 && counts.total == 1);
3464 // If label edit is allowed.
3465 if (*pResult == FALSE)
3467 const NMLVDISPINFO *pdi = (NMLVDISPINFO*)pNMHDR;
3468 ASSERT(pdi != nullptr);
3470 // Locate the edit box on the right column in case the user changed the
3472 const int nColPos = m_pColItems->ColLogToPhys(0);
3474 // Get text from the "File Name" column.
3475 CString sText = m_pList->GetItemText(pdi->item.iItem, nColPos);
3476 ASSERT(!sText.IsEmpty());
3478 // Keep only left file name (separated by '|'). This form occurs
3479 // when two files exists with same name but not in same case.
3480 int nPos = sText.Find('|');
3483 sText = sText.Left(nPos);
3486 // Set the edit control with the updated text.
3487 CEdit *pEdit = m_pList->GetEditControl();
3488 ASSERT(pEdit != nullptr);
3489 pEdit->SetWindowText(sText);
3490 // Select file name without file extension
3491 if (!GetDiffItem(pdi->item.iItem).diffcode.isDirectory())
3493 int nPosExt = sText.ReverseFind('.');
3495 pEdit->SetSel(0, nPosExt);
3498 m_bUserCancelEdit = false;
3503 * @brief Called when user done with item label edit.
3506 afx_msg void CDirView::OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
3510 // We can't use the normal condition of pszText==`nullptr` to know if the
3511 // user cancels editing when file names had different case (e.g.
3512 // "file.txt|FILE.txt"). The edit text was changed to "file.txt" and
3513 // if the user accept it as the new file name, pszText is `nullptr`.
3515 if (!m_bUserCancelEdit)
3517 CEdit *pEdit = m_pList->GetEditControl();
3518 ASSERT(pEdit != nullptr);
3521 pEdit->GetWindowText(sText);
3523 if (!sText.IsEmpty() && paths::IsValidName(String(sText)))
3526 DirItemIterator it(m_pIList.get(), reinterpret_cast<NMLVDISPINFO *>(pNMHDR)->item.iItem);
3528 unsigned sideFlags = (di.diffcode.diffcode & DIFFCODE::SIDEFLAGS);
3529 *pResult = DoItemRename(it, GetDiffContext(), String(sText));
3530 // Rescan the item if side flags change due to renaming.
3533 if ((di.diffcode.diffcode & DIFFCODE::SIDEFLAGS) != sideFlags)
3535 // Delete the item with the same file name as after renaming.
3538 for (DIFFITEM* pItem = di.GetParentLink()->GetFirstChild(); pItem != nullptr; pItem = pItem->GetFwdSiblingLink())
3540 if ((pItem != &di) && (pItem->diffcode.isDirectory() == di.diffcode.isDirectory()) && (collstr(pItem->diffFileInfo[0].filename, di.diffFileInfo[0].filename, false) == 0))
3542 pItem->DelinkFromSiblings();
3550 m_pSavedTreeState.reset(SaveTreeState(GetDiffContext()));
3551 GetDocument()->SetMarkedRescan();
3552 GetDocument()->Rescan();
3555 int nDirs = GetDiffContext().GetCompareDirs();
3556 assert(nDirs == 2 || nDirs == 3);
3557 UpdatePaths(nDirs, di);
3559 int nIdx = reinterpret_cast<NMLVDISPINFO*>(pNMHDR)->item.iItem;
3560 UpdateDiffItemStatus(nIdx);
3563 } catch (ContentsChangedException& e) {
3564 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
3569 LangMessageBox(IDS_ERROR_INVALID_DIR_FILE_NAME, MB_ICONWARNING);
3574 void CDirView::OnODFindItem(NMHDR* pNMHDR, LRESULT* pResult)
3576 NMLVFINDITEM* pFindItem = reinterpret_cast<NMLVFINDITEM*>(pNMHDR);
3577 if (pFindItem->lvfi.flags & LVFI_STRING)
3579 String text = strutils::makelower(pFindItem->lvfi.psz);
3580 for (size_t i = pFindItem->iStart; i < m_listViewItems.size(); ++i)
3582 DIFFITEM *di = GetItemKey(static_cast<int>(i));
3583 String filename = strutils::makelower(di->diffFileInfo[0].filename);
3584 if (di && tc::tcsncmp(text.c_str(), filename.c_str(), text.length()) == 0)
3595 * @brief Called when item is marked for rescan.
3596 * This function marks selected items for rescan and rescans them.
3598 void CDirView::OnMarkedRescan()
3600 std::for_each(SelBegin(), SelEnd(), MarkForRescan);
3601 if (std::distance(SelBegin(), SelEnd()) > 0)
3603 m_pSavedTreeState.reset(SaveTreeState(GetDiffContext()));
3604 GetDocument()->SetMarkedRescan();
3605 GetDocument()->Rescan();
3610 * @brief Called to update the item count in the status bar
3612 void CDirView::OnUpdateStatusNum(CCmdUI* pCmdUI)
3614 String s; // text to display
3616 int count = m_pList->GetItemCount();
3617 int focusItem = GetFocusedItem();
3619 if (focusItem == -1)
3621 // No item has focus
3623 s = strutils::format_string1(_("Items: %1"), strutils::to_str(count));
3627 // Don't show number to special items
3628 DIFFITEM *pos = GetItemKey(focusItem);
3629 if (!IsDiffItemSpecial(pos))
3631 // If compare is non-recursive reduce special items count
3632 bool bRecursive = GetDiffContext().m_bRecursive;
3639 s = strutils::format_string2(_("Item %1 of %2"),
3640 strutils::to_str(focusItem + 1), strutils::to_str(count));
3643 pCmdUI->SetText(s.c_str());
3647 * @brief Show all hidden items.
3649 void CDirView::OnViewShowHiddenItems()
3651 CDiffContext& ctxt = GetDiffContext();
3652 SetItemViewFlag(GetDiffContext(), ViewCustomFlags::VISIBLE, ViewCustomFlags::VISIBILITY);
3653 ctxt.m_vCurrentlyHiddenItems.clear();
3658 * @brief Enable/Disable 'Show hidden items' menuitem.
3660 void CDirView::OnUpdateViewShowHiddenItems(CCmdUI* pCmdUI)
3662 const CDiffContext& ctxt = GetDiffContext();
3663 pCmdUI->Enable(ctxt.m_vCurrentlyHiddenItems.size() > 0);
3667 * @brief Toggle Tree Mode
3669 void CDirView::OnViewTreeMode()
3671 m_bTreeMode = !m_bTreeMode;
3672 m_dirfilter.tree_mode = m_bTreeMode;
3673 GetOptionsMgr()->SaveOption(OPT_TREE_MODE, m_bTreeMode); // reverse
3678 * @brief Check/Uncheck 'Tree Mode' menuitem.
3680 void CDirView::OnUpdateViewTreeMode(CCmdUI* pCmdUI)
3682 // Don't show Tree Mode as 'checked' if the
3683 // menu item is greyed out (disabled). Its very confusing.
3684 if( GetDocument()->GetDiffContext().m_bRecursive ) {
3685 pCmdUI->SetCheck(m_bTreeMode);
3686 pCmdUI->Enable(TRUE);
3688 pCmdUI->SetCheck(FALSE);
3689 pCmdUI->Enable(FALSE);
3694 * @brief Expand all subfolders
3696 void CDirView::OnViewExpandAllSubdirs()
3698 ExpandAllSubdirs(GetDiffContext());
3703 * @brief Expand different subfolders
3705 void CDirView::OnViewExpandDifferentSubdirs()
3707 ExpandDifferentSubdirs(GetDiffContext());
3712 * @brief Expand identical subfolders
3714 void CDirView::OnViewExpandIdenticalSubdirs()
3716 ExpandIdenticalSubdirs(GetDiffContext());
3721 * @brief Update "Expand All Subfolders" item
3723 void CDirView::OnUpdateViewExpandSubdirs(CCmdUI* pCmdUI)
3725 pCmdUI->Enable(m_bTreeMode && GetDiffContext().m_bRecursive);
3729 * @brief Collapse all subfolders
3731 void CDirView::OnViewCollapseAllSubdirs()
3733 CollapseAllSubdirs(GetDiffContext());
3738 * @brief Update "Collapse All Subfolders" item
3740 void CDirView::OnUpdateViewCollapseAllSubdirs(CCmdUI* pCmdUI)
3742 pCmdUI->Enable(m_bTreeMode && GetDiffContext().m_bRecursive);
3745 void CDirView::OnViewSwapPanes(int pane1, int pane2)
3747 GetDocument()->Swap(pane1, pane2);
3751 void CDirView::OnUpdateViewSwapPanes(CCmdUI* pCmdUI, int pane1, int pane2)
3753 pCmdUI->Enable(pane2 < GetDocument()->m_nDirs &&
3754 GetDocument()->m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPLETED);
3758 * @brief Show/Hide different files/directories
3760 void CDirView::OnOptionsShowDifferent()
3762 m_dirfilter.show_different = !m_dirfilter.show_different;
3763 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT, m_dirfilter.show_different);
3768 * @brief Show/Hide identical files/directories
3770 void CDirView::OnOptionsShowIdentical()
3772 m_dirfilter.show_identical = !m_dirfilter.show_identical;
3773 GetOptionsMgr()->SaveOption(OPT_SHOW_IDENTICAL, m_dirfilter.show_identical);
3778 * @brief Show/Hide left-only files/directories
3780 void CDirView::OnOptionsShowUniqueLeft()
3782 m_dirfilter.show_unique_left = !m_dirfilter.show_unique_left;
3783 GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_LEFT, m_dirfilter.show_unique_left);
3788 * @brief Show/Hide middle-only files/directories
3790 void CDirView::OnOptionsShowUniqueMiddle()
3792 m_dirfilter.show_unique_middle = !m_dirfilter.show_unique_middle;
3793 GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_MIDDLE, m_dirfilter.show_unique_middle);
3798 * @brief Show/Hide right-only files/directories
3800 void CDirView::OnOptionsShowUniqueRight()
3802 m_dirfilter.show_unique_right = !m_dirfilter.show_unique_right;
3803 GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_RIGHT, m_dirfilter.show_unique_right);
3808 * @brief Show/Hide binary files
3810 void CDirView::OnOptionsShowBinaries()
3812 m_dirfilter.show_binaries = !m_dirfilter.show_binaries;
3813 GetOptionsMgr()->SaveOption(OPT_SHOW_BINARIES, m_dirfilter.show_binaries);
3818 * @brief Show/Hide skipped files/directories
3820 void CDirView::OnOptionsShowSkipped()
3822 m_dirfilter.show_skipped = !m_dirfilter.show_skipped;
3823 GetOptionsMgr()->SaveOption(OPT_SHOW_SKIPPED, m_dirfilter.show_skipped);
3828 * @brief Show/Hide different files/folders (Middle and right are identical)
3830 void CDirView::OnOptionsShowDifferentLeftOnly()
3832 m_dirfilter.show_different_left_only = !m_dirfilter.show_different_left_only;
3833 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_LEFT_ONLY, m_dirfilter.show_different_left_only);
3838 * @brief Show/Hide different files/folders (Left and right are identical)
3840 void CDirView::OnOptionsShowDifferentMiddleOnly()
3842 m_dirfilter.show_different_middle_only = !m_dirfilter.show_different_middle_only;
3843 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_MIDDLE_ONLY, m_dirfilter.show_different_middle_only);
3848 * @brief Show/Hide different files/folders (Left and middle are identical)
3850 void CDirView::OnOptionsShowDifferentRightOnly()
3852 m_dirfilter.show_different_right_only = !m_dirfilter.show_different_right_only;
3853 GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_RIGHT_ONLY, m_dirfilter.show_different_right_only);
3858 * @brief Show/Hide missing left only files/folders
3860 void CDirView::OnOptionsShowMissingLeftOnly()
3862 m_dirfilter.show_missing_left_only = !m_dirfilter.show_missing_left_only;
3863 GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_LEFT_ONLY, m_dirfilter.show_missing_left_only);
3868 * @brief Show/Hide missing middle only files/folders
3870 void CDirView::OnOptionsShowMissingMiddleOnly()
3872 m_dirfilter.show_missing_middle_only = !m_dirfilter.show_missing_middle_only;
3873 GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_MIDDLE_ONLY, m_dirfilter.show_missing_middle_only);
3878 * @brief Show/Hide missing right only files/folders
3880 void CDirView::OnOptionsShowMissingRightOnly()
3882 m_dirfilter.show_missing_right_only = !m_dirfilter.show_missing_right_only;
3883 GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_RIGHT_ONLY, m_dirfilter.show_missing_right_only);
3887 void CDirView::OnUpdateOptionsShowdifferent(CCmdUI* pCmdUI)
3889 pCmdUI->SetCheck(m_dirfilter.show_different);
3892 void CDirView::OnUpdateOptionsShowidentical(CCmdUI* pCmdUI)
3894 pCmdUI->SetCheck(m_dirfilter.show_identical);
3897 void CDirView::OnUpdateOptionsShowuniqueleft(CCmdUI* pCmdUI)
3899 pCmdUI->SetCheck(m_dirfilter.show_unique_left);
3902 void CDirView::OnUpdateOptionsShowuniquemiddle(CCmdUI* pCmdUI)
3904 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3905 pCmdUI->SetCheck(m_dirfilter.show_unique_middle);
3908 void CDirView::OnUpdateOptionsShowuniqueright(CCmdUI* pCmdUI)
3910 pCmdUI->SetCheck(m_dirfilter.show_unique_right);
3913 void CDirView::OnUpdateOptionsShowBinaries(CCmdUI* pCmdUI)
3915 pCmdUI->SetCheck(m_dirfilter.show_binaries);
3918 void CDirView::OnUpdateOptionsShowSkipped(CCmdUI* pCmdUI)
3920 pCmdUI->SetCheck(m_dirfilter.show_skipped);
3923 void CDirView::OnUpdateOptionsShowDifferentLeftOnly(CCmdUI* pCmdUI)
3925 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3926 pCmdUI->SetCheck(m_dirfilter.show_different_left_only);
3929 void CDirView::OnUpdateOptionsShowDifferentMiddleOnly(CCmdUI* pCmdUI)
3931 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3932 pCmdUI->SetCheck(m_dirfilter.show_different_middle_only);
3935 void CDirView::OnUpdateOptionsShowDifferentRightOnly(CCmdUI* pCmdUI)
3937 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3938 pCmdUI->SetCheck(m_dirfilter.show_different_right_only);
3941 void CDirView::OnUpdateOptionsShowMissingLeftOnly(CCmdUI* pCmdUI)
3943 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3944 pCmdUI->SetCheck(m_dirfilter.show_missing_left_only);
3947 void CDirView::OnUpdateOptionsShowMissingMiddleOnly(CCmdUI* pCmdUI)
3949 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3950 pCmdUI->SetCheck(m_dirfilter.show_missing_middle_only);
3953 void CDirView::OnUpdateOptionsShowMissingRightOnly(CCmdUI* pCmdUI)
3955 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3956 pCmdUI->SetCheck(m_dirfilter.show_missing_right_only);
3959 void CDirView::OnMergeCompare(UINT nID)
3961 OpenSelection(nID == ID_MERGE_COMPARE ? GetDocument() : nullptr);
3964 void CDirView::OnMergeCompareNonHorizontally()
3966 int sel1, sel2, sel3;
3967 if (!GetSelectedItems(&sel1, &sel2, &sel3))
3969 DirSelectFilesDlg dlg;
3971 dlg.m_pdi[0] = &GetDiffItem(sel1);
3973 dlg.m_pdi[1] = &GetDiffItem(sel2);
3975 dlg.m_pdi[2] = &GetDiffItem(sel3);
3976 if (dlg.DoModal() == IDOK && dlg.m_selectedButtons.size() > 0)
3978 CDirDoc *pDoc = GetDocument();
3979 FileTextEncoding encoding[3];
3980 fileopenflags_t dwFlags[3] = {};
3982 for (int nIndex = 0; nIndex < static_cast<int>(dlg.m_selectedButtons.size()); ++nIndex)
3984 int n = dlg.m_selectedButtons[nIndex];
3985 dwFlags[nIndex] = FFILEOPEN_NOMRU | (pDoc->GetReadOnly(n % 3) ? FFILEOPEN_READONLY : 0);
3986 if (dlg.m_pdi[n / 3])
3988 paths.SetPath(nIndex, GetItemFileName(pDoc->GetDiffContext(), *dlg.m_pdi[n / 3], n % 3));
3989 encoding[nIndex] = dlg.m_pdi[n / 3]->diffFileInfo[n % 3].encoding;
3992 if (paths.GetSize() == 1)
3993 paths.SetRight(_T(""));
3994 Open(GetDocument(), paths, dwFlags, encoding);
3998 void CDirView::OnMergeCompareAs(UINT nID)
4000 OpenSelectionAs(nID);
4003 void CDirView::OnUpdateMergeCompare(CCmdUI *pCmdUI)
4005 bool openableForDir = !((pCmdUI->m_nID >= ID_MERGE_COMPARE_TEXT && pCmdUI->m_nID <= ID_MERGE_COMPARE_WEBPAGE) ||
4006 (pCmdUI->m_nID >= ID_UNPACKERS_FIRST && pCmdUI->m_nID <= ID_UNPACKERS_LAST));
4008 DoUpdateOpen(SELECTIONTYPE_NORMAL, pCmdUI, openableForDir);
4011 void CDirView::OnUpdateNoUnpacker(CCmdUI *pCmdUI)
4014 pCmdUI->m_pMenu->DeleteMenu(pCmdUI->m_nID, MF_BYCOMMAND);
4016 int sel = GetSingleSelectedItem();
4017 if (sel == -1 || GetItemKey(sel) == reinterpret_cast<DIFFITEM *>(SPECIAL_ITEM_POS))
4020 String filteredFilenames = GetDiffContext().GetFilteredFilenames(*GetItemKey(sel));
4021 CMainFrame::AppendPluginMenus(pCmdUI->m_pMenu, filteredFilenames,
4022 FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
4025 void CDirView::OnViewCompareStatistics()
4027 CompareStatisticsDlg dlg(GetDocument()->GetCompareStats());
4032 * @brief Count left & right files, and number with editable text encoding
4033 * @param nLeft [out] #files on left side selected
4034 * @param nLeftAffected [out] #files on left side selected which can have text encoding changed
4035 * @param nRight [out] #files on right side selected
4036 * @param nRightAffected [out] #files on right side selected which can have text encoding changed
4038 * Affected files include all except unicode files
4040 void CDirView::FormatEncodingDialogDisplays(CLoadSaveCodepageDlg * dlg)
4042 IntToIntMap currentCodepages = CountCodepages(SelBegin(), SelEnd(), GetDiffContext());
4044 Counts left, middle, right;
4045 left = Count(&DirActions::IsItemEditableEncoding<SIDE_LEFT>);
4046 if (GetDocument()->m_nDirs > 2)
4047 middle = Count(&DirActions::IsItemEditableEncoding<SIDE_MIDDLE>);
4048 right = Count(&DirActions::IsItemEditableEncoding<SIDE_RIGHT>);
4050 // Format strings such as "25 of 30 Files Affected"
4051 String sLeftAffected = FormatFilesAffectedString(left.count, left.total);
4052 String sMiddleAffected = (GetDocument()->m_nDirs < 3) ? _T("") : FormatFilesAffectedString(middle.count, middle.total);
4053 String sRightAffected = FormatFilesAffectedString(right.count, right.total);
4054 dlg->SetLeftRightAffectStrings(sLeftAffected, sMiddleAffected, sRightAffected);
4055 int codepage = currentCodepages.FindMaxKey();
4056 dlg->SetCodepages(codepage);
4060 * @brief Display file encoding dialog to user & handle user's choices
4062 * This handles DirView invocation, so multiple files may be affected
4064 void CDirView::DoFileEncodingDialog()
4066 CLoadSaveCodepageDlg dlg(GetDocument()->m_nDirs);
4067 // set up labels about what will be affected
4068 FormatEncodingDialogDisplays(&dlg);
4069 dlg.EnableSaveCodepage(false); // disallow setting a separate codepage for saving
4072 if (dlg.DoModal() != IDOK)
4076 affected[0] = dlg.DoesAffectLeft();
4077 affected[1] = dlg.DoesAffectMiddle();
4078 affected[SideToIndex(GetDiffContext(), SIDE_RIGHT)] = dlg.DoesAffectRight();
4080 ApplyCodepage(SelBegin(), SelEnd(), GetDiffContext(), affected, dlg.GetLoadCodepage());
4082 m_pList->InvalidateRect(nullptr);
4083 m_pList->UpdateWindow();
4085 // TODO: We could loop through any active merge windows belonging to us
4086 // and see if any of their files are affected
4087 // but, if they've been edited, we cannot throw away the user's work?
4091 * @brief Display file encoding dialog & handle user's actions
4093 void CDirView::OnFileEncoding()
4095 DoFileEncodingDialog();
4098 /** @brief Open help from mainframe when user presses F1*/
4099 void CDirView::OnHelp()
4101 theApp.ShowHelp(DirViewHelpLocation);
4105 * @brief true while user is editing a file name.
4107 bool CDirView::IsLabelEdit() const
4109 return (m_pList->GetEditControl() != nullptr);
4113 * @brief Allow edit "Paste" when renaming an item.
4115 void CDirView::OnEditCopy()
4117 CEdit *pEdit = m_pList->GetEditControl();
4118 if (pEdit != nullptr)
4125 * @brief Allow edit "Cut" when renaming an item.
4127 void CDirView::OnEditCut()
4129 CEdit *pEdit = m_pList->GetEditControl();
4130 if (pEdit != nullptr)
4137 * @brief Allow edit "Paste" when renaming an item.
4139 void CDirView::OnEditPaste()
4141 CEdit *pEdit = m_pList->GetEditControl();
4142 if (pEdit != nullptr)
4149 * @brief Allow edit "Undo" when renaming an item.
4151 void CDirView::OnEditUndo()
4153 CEdit *pEdit = m_pList->GetEditControl();
4154 if (pEdit != nullptr)
4161 * @brief Update the tool bar's "Undo" icon. It should be enabled when
4162 * renaming an item and undo is possible.
4164 void CDirView::OnUpdateEditUndo(CCmdUI* pCmdUI)
4166 CEdit *pEdit = m_pList->GetEditControl();
4167 pCmdUI->Enable(pEdit && pEdit->CanUndo());
4170 * @brief Returns CShellContextMenu object that owns given HMENU.
4172 * @param [in] hMenu Handle to the menu to check ownership of.
4173 * @return Either m_pShellContextMenuLeft, m_pShellContextMenuRight
4174 * or `nullptr` if hMenu is not owned by these two.
4176 CShellContextMenu* CDirView::GetCorrespondingShellContextMenu(HMENU hMenu) const
4178 CShellContextMenu* pMenu = nullptr;
4179 if (m_pShellContextMenuLeft!=nullptr && hMenu == m_pShellContextMenuLeft->GetHMENU())
4180 pMenu = m_pShellContextMenuLeft.get();
4181 else if (m_pShellContextMenuMiddle!=nullptr && hMenu == m_pShellContextMenuMiddle->GetHMENU())
4182 pMenu = m_pShellContextMenuMiddle.get();
4183 else if (m_pShellContextMenuRight!=nullptr && hMenu == m_pShellContextMenuRight->GetHMENU())
4184 pMenu = m_pShellContextMenuRight.get();
4190 * @brief Handle messages related to correct menu working.
4192 * We need to requery shell context menu each time we switch from context menu
4193 * for one side to context menu for other side. Here we check whether we need to
4194 * requery and call ShellContextMenuHandleMenuMessage.
4196 LRESULT CDirView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
4198 while (message == WM_INITMENUPOPUP)
4200 HMENU hMenu = (HMENU)wParam;
4201 if (CShellContextMenu* pMenu = GetCorrespondingShellContextMenu(hMenu))
4203 if (m_hCurrentMenu != hMenu)
4205 // re-query context menu once more, because if context menu was queried for right
4206 // group of files and we are showing menu for left group (or vice versa) menu will
4207 // be shown incorrectly
4208 // also, if context menu was last queried for right group of files and we are
4209 // invoking command for left command will be executed for right group (the last
4210 // group that menu was requested for)
4211 // may be a "feature" of Shell
4213 pMenu->RequeryShellContextMenu();
4214 m_hCurrentMenu = hMenu;
4220 CShellContextMenu* pMenu = GetCorrespondingShellContextMenu(m_hCurrentMenu);
4222 if (pMenu != nullptr)
4225 pMenu->HandleMenuMessage(message, wParam, lParam, res);
4228 return __super::WindowProc(message, wParam, lParam);
4232 * @brief Implement background item coloring
4234 void CDirView::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
4236 if (!m_bUseColors) {
4240 LPNMLISTVIEW pNM = (LPNMLISTVIEW)pNMHDR;
4241 *pResult = CDRF_DODEFAULT;
4243 if (pNM->hdr.code == NM_CUSTOMDRAW)
4245 LPNMLVCUSTOMDRAW lpC = (LPNMLVCUSTOMDRAW)pNMHDR;
4247 if (lpC->nmcd.dwDrawStage == CDDS_PREPAINT)
4249 *pResult = CDRF_NOTIFYITEMDRAW;
4253 if (lpC->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)
4255 *pResult = CDRF_NOTIFYITEMDRAW;
4259 if (lpC->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM ))
4261 GetColors (static_cast<int>(lpC->nmcd.dwItemSpec), lpC->iSubItem, lpC->clrTextBk, lpC->clrText);
4267 * @brief Populate colors for items in view, depending on difference status
4269 void CDirView::GetColors (int nRow, int nCol, COLORREF& clrBk, COLORREF& clrText) const
4271 const DIFFITEM& di = GetDiffItem (nRow);
4275 clrText = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_NORMALTEXT);
4276 clrBk = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_BKGND);
4278 else if (di.diffcode.isResultFiltered())
4280 clrText = m_cachedColors.clrDirItemFilteredText;
4281 clrBk = m_cachedColors.clrDirItemFiltered;
4283 else if (!IsItemExistAll(GetDiffContext(), di))
4285 clrText = m_cachedColors.clrDirItemNotExistAllText;
4286 clrBk = m_cachedColors.clrDirItemNotExistAll;
4288 else if (di.diffcode.isResultDiff())
4290 clrText = m_cachedColors.clrDirItemDiffText;
4291 clrBk = m_cachedColors.clrDirItemDiff;
4293 else if (di.diffcode.isResultSame())
4295 clrText = m_cachedColors.clrDirItemEqualText;
4296 clrBk = m_cachedColors.clrDirItemEqual;
4300 clrText = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_NORMALTEXT);
4301 clrBk = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_BKGND);
4305 void CDirView::OnSearch()
4307 CDirDoc *pDoc = GetDocument();
4308 m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
4309 int nRows = m_pList->GetItemCount();
4310 CDiffContext& ctxt = GetDiffContext();
4312 for (int currRow = nRows - 1; currRow >= 0; currRow--)
4314 DIFFITEM *pos = GetItemKey(currRow);
4315 if (IsDiffItemSpecial(pos))
4318 bool bFound = false;
4319 DIFFITEM &di = GetDiffItem(currRow);
4322 for (int i = 0; i < pDoc->m_nDirs; i++)
4324 if (di.diffcode.exists(i) && !di.diffcode.isDirectory())
4326 GetItemFileNames(currRow, &paths);
4328 if (!ufile.OpenReadOnly(paths[i]))
4331 ufile.SetUnicoding(di.diffFileInfo[i].encoding.m_unicoding);
4332 ufile.SetBom(di.diffFileInfo[i].encoding.m_bom);
4333 ufile.SetCodepage(di.diffFileInfo[i].encoding.m_codepage);
4341 if (!ufile.ReadString(line, &lossy))
4344 if (tc::tcsstr(line.c_str(), _T("DirView")))
4358 String hiddden_item_path = di.getItemRelativePath();
4359 SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
4360 DeleteItem(currRow);
4361 ctxt.m_vCurrentlyHiddenItems.push_back(hiddden_item_path);
4364 m_pList->SetRedraw(TRUE); // Turn updating back on
4368 * @brief Drag files/directories from folder compare listing view.
4370 void CDirView::OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult)
4372 std::list<String> list;
4373 CopyPathnamesForDragAndDrop(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
4374 String filesForDroping = strutils::join(list.begin(), list.end(), _T("\n")) + _T("\n");
4376 CSharedFile file(GMEM_DDESHARE | GMEM_MOVEABLE | GMEM_ZEROINIT);
4377 file.Write(filesForDroping.data(), static_cast<unsigned>((filesForDroping.length() + 1) * sizeof(tchar_t)));
4379 HGLOBAL hMem = GlobalReAlloc(file.Detach(), (filesForDroping.length() + 1) * sizeof(tchar_t), 0);
4380 if (hMem != nullptr)
4382 COleDataSource* DropData = new COleDataSource();
4383 DropData->CacheGlobalData(CF_UNICODETEXT, hMem);
4384 DROPEFFECT de = DropData->DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE, nullptr);
4390 /// Assign column name, using string resource & current column ordering
4391 void CDirView::NameColumn(const DirColInfo *col, int subitem)
4393 int phys = m_pColItems->ColLogToPhys(subitem);
4396 String s = col->GetDisplayName();
4398 lvc.mask = LVCF_TEXT;
4399 lvc.pszText = const_cast<tchar_t*>(s.c_str());
4400 m_pList->SetColumn(phys, &lvc);
4404 /// Load column names from string table
4405 void CDirView::UpdateColumnNames()
4407 int ncols = m_pColItems->GetColCount();
4408 for (int i=0; i<ncols; ++i)
4410 const DirColInfo* col = m_pColItems->GetDirColInfo(i);
4417 * @brief Set alignment of columns.
4419 void CDirView::SetColAlignments()
4421 int ncols = m_pColItems->GetColCount();
4422 for (int i=0; i<ncols; ++i)
4424 const DirColInfo * col = m_pColItems->GetDirColInfo(i);
4428 lvc.mask = LVCF_FMT;
4429 lvc.fmt = col->alignment;
4430 m_pList->SetColumn(m_pColItems->ColLogToPhys(i), &lvc);
4435 CDirView::CompareState::CompareState(const CDiffContext *pCtxt, const DirViewColItems *pColItems, int sortCol, bool bSortAscending, bool bTreeMode)
4437 , pColItems(pColItems)
4439 , bSortAscending(bSortAscending)
4440 , bTreeMode(bTreeMode)
4444 /// Compare two specified rows during a sort operation (windows callback)
4445 int CALLBACK CDirView::CompareState::CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
4447 CompareState *pThis = reinterpret_cast<CompareState*>(lParamSort);
4448 // Sort special items always first in dir view
4454 DIFFITEM *diffposl = (DIFFITEM *)lParam1;
4455 DIFFITEM *diffposr = (DIFFITEM *)lParam2;
4456 const DIFFITEM &ldi = pThis->pCtxt->GetDiffAt(diffposl);
4457 const DIFFITEM &rdi = pThis->pCtxt->GetDiffAt(diffposr);
4458 // compare 'left' and 'right' parameters as appropriate
4459 int retVal = pThis->pColItems->ColSort(pThis->pCtxt, pThis->sortCol, ldi, rdi, pThis->bTreeMode);
4460 // return compare result, considering sort direction
4461 return pThis->bSortAscending ? retVal : -retVal;
4464 /// Add new item to list view
4465 void CDirView::AddNewItem(int i, DIFFITEM *diffpos, int iImage, int iIndent)
4467 ListViewOwnerDataItem lvItem;
4468 lvItem.iIndent = iIndent;
4469 lvItem.lParam = (LPARAM)diffpos;
4470 lvItem.iImage = iImage;
4471 if (i == static_cast<int>(m_listViewItems.size()))
4472 m_listViewItems.push_back(lvItem);
4474 m_listViewItems.insert(m_listViewItems.begin() + i, lvItem);
4478 * @brief Update listview display of details for specified row
4479 * @note Customising shownd data should be done here
4481 void CDirView::UpdateDiffItemStatus(UINT nIdx)
4483 GetListCtrl().RedrawItems(nIdx, nIdx);
4484 const DIFFITEM& di = GetDiffItem(nIdx);
4485 if (di.diffcode.isDirectory())
4488 for (it = RevBegin(); it != RevEnd(); )
4490 DIFFITEM& di2 = *it;
4491 int cursel = it.m_sel;
4493 if (di2.IsAncestor(&di))
4495 if ((di2.diffcode.diffcode & DIFFCODE::SIDEFLAGS) == 0)
4496 DeleteItem(cursel, true);
4498 GetListCtrl().RedrawItems(cursel, cursel);
4504 static String rgDispinfoText[2]; // used in function below
4507 * @brief Allocate a text buffer to assign to NMLVDISPINFO::item::pszText
4508 * Quoting from SDK Docs:
4509 * If the LVITEM structure is receiving item text, the pszText and cchTextMax
4510 * members specify the address and size of a buffer. You can either copy text to
4511 * the buffer or assign the address of a string to the pszText member. In the
4512 * latter case, you must not change or delete the string until the corresponding
4513 * item text is deleted or two additional LVN_GETDISPINFO messages have been sent.
4515 static tchar_t* NTAPI AllocDispinfoText(const String &s)
4518 const tchar_t* pszText = (rgDispinfoText[i] = s).c_str();
4520 return (tchar_t*)pszText;
4524 * @brief Respond to LVN_GETDISPINFO message
4526 void CDirView::ReflectGetdispinfo(NMLVDISPINFO *pParam)
4528 int nIdx = pParam->item.iItem;
4529 if (nIdx >= static_cast<int>(m_listViewItems.size()))
4531 DIFFITEM *key = reinterpret_cast<DIFFITEM*>(m_listViewItems[nIdx].lParam);
4532 int i = m_pColItems->ColPhysToLog(pParam->item.iSubItem);
4533 if (IsDiffItemSpecial(key))
4535 pParam->item.iImage = m_listViewItems[nIdx].iImage;
4536 if (m_pColItems->IsColName(i))
4538 pParam->item.pszText = _T("..");
4542 if (!GetDocument()->HasDiffs())
4544 const CDiffContext &ctxt = GetDiffContext();
4545 const DIFFITEM &di = ctxt.GetDiffAt(key);
4546 if (pParam->item.mask & LVIF_TEXT)
4548 String s = m_pColItems->ColGetTextToDisplay(&ctxt, i, di);
4549 pParam->item.pszText = AllocDispinfoText(s);
4551 if (pParam->item.mask & LVIF_IMAGE)
4553 pParam->item.iImage = GetColImage(di);
4554 if ((pParam->item.mask & LVIF_STATE) == 0)
4557 pParam->item.mask |= LVIF_STATE;
4558 pParam->item.state = m_pList->GetItemState(nIdx, static_cast<UINT>(~LVIS_STATEIMAGEMASK));
4561 if (pParam->item.mask & LVIF_INDENT)
4563 pParam->item.iIndent = m_listViewItems[nIdx].iIndent;
4565 if (pParam->item.mask & LVIF_STATE)
4567 pParam->item.stateMask |= LVIS_STATEIMAGEMASK;
4568 if (di.HasChildren())
4569 pParam->item.state |= INDEXTOSTATEIMAGEMASK((di.customFlags & ViewCustomFlags::EXPANDED) ? 2 : 1);
4574 * @brief User examines & edits which columns are displayed in dirview, and in which order
4576 void CDirView::OnEditColumns()
4578 bool bReset = false;
4579 CDirColsDlg::ColumnArray cols;
4584 // List all the currently displayed columns
4585 for (int col=0; col<GetListCtrl().GetHeaderCtrl()->GetItemCount(); ++col)
4587 int l = m_pColItems->ColPhysToLog(col);
4588 dlg.AddColumn(m_pColItems->GetColDisplayName(l), m_pColItems->GetColDescription(l), l, col);
4590 // Now add all the columns not currently displayed
4592 for (l=0; l<m_pColItems->GetColCount(); ++l)
4594 if (m_pColItems->ColLogToPhys(l)==-1)
4596 dlg.AddColumn(m_pColItems->GetColDisplayName(l), m_pColItems->GetColDescription(l), l);
4599 assert(m_pColItems->GetColCount() == dlg.GetColumns().size());
4601 // Add default order of columns for resetting to defaults
4602 for (l = 0; l < m_pColItems->GetColCount(); ++l)
4604 int phy = m_pColItems->GetColDefaultOrder(l);
4605 dlg.AddDefColumn(m_pColItems->GetColDisplayName(l), l, phy);
4608 if (dlg.DoModal() != IDOK)
4611 if (!dlg.GetShowAdditionalProperties())
4613 bReset = dlg.m_bReset;
4614 cols = dlg.GetColumns();
4618 CDirAdditionalPropertiesDlg dlgAdditionalProperties(m_pColItems->GetAdditionalPropertyNames());
4619 if (dlgAdditionalProperties.DoModal() == IDOK)
4621 auto& selectedCanonicalNames = dlgAdditionalProperties.GetSelectedCanonicalNames();
4622 GetOptionsMgr()->SaveOption(OPT_ADDITIONAL_PROPERTIES,
4623 strutils::join(selectedCanonicalNames.begin(), selectedCanonicalNames.end(), _T(" ")));
4624 m_pColItems->SetAdditionalPropertyNames(selectedCanonicalNames);
4625 m_pColItems->SaveColumnOrders();
4626 GetDiffContext().m_pPropertySystem.reset(new PropertySystem(m_pColItems->GetAdditionalPropertyNames()));
4627 GetDiffContext().ClearAllAdditionalProperties();
4628 auto* pDoc = GetDocument();
4633 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS;
4634 GetOptionsMgr()->SaveOption(keyname,
4635 (bReset ? m_pColItems->ResetColumnWidths(GetDefColumnWidth()) :
4636 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1))));
4638 // Reset our data to reflect the new data from the dialog
4639 m_pColItems->ClearColumnOrders();
4640 const int sortColumn = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
4641 std::vector<int> colorder(m_pColItems->GetColCount(), -1);
4642 for (CDirColsDlg::ColumnArray::const_iterator iter = cols.begin();
4643 iter != cols.end(); ++iter)
4645 int log = iter->log_col;
4646 int phy = iter->phy_col;
4647 colorder[log] = phy;
4649 // If sorted column was hidden, reset sorting
4650 if (log == sortColumn && phy < 0)
4652 GetOptionsMgr()->Reset((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
4653 GetOptionsMgr()->Reset(OPT_DIRVIEW_SORT_ASCENDING);
4657 m_pColItems->SetColumnOrdering(&colorder[0]);
4659 if (m_pColItems->GetDispColCount() < 1)
4661 // Set them back to default if they didn't leave a column showing
4662 // (However, if none of the items are checked, this process will not be executed because the "OK" button in the "Display Columns" dialog cannot be pressed.)
4663 m_pColItems->ResetColumnOrdering();
4669 DirActions CDirView::MakeDirActions(DirActions::method_type func) const
4671 const CDirDoc *pDoc = GetDocument();
4672 return DirActions(pDoc->GetDiffContext(), pDoc->GetReadOnly(), func);
4675 DirActions CDirView::MakeDirActions(DirActions::method_type2 func) const
4677 const CDirDoc *pDoc = GetDocument();
4678 return DirActions(pDoc->GetDiffContext(), pDoc->GetReadOnly(), nullptr, func);
4681 const CDiffContext& CDirView::GetDiffContext() const
4683 return GetDocument()->GetDiffContext();
4686 CDiffContext& CDirView::GetDiffContext()
4688 return GetDocument()->GetDiffContext();