OSDN Git Service

3278ae2e14ff443fb2de7d397c1d398280111c5a
[winmerge-jp/winmerge-jp.git] / Src / DirView.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //    SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
7 /**
8  * @file  DirView.cpp
9  *
10  * @brief Main implementation file for CDirView
11  */
12
13 #include "StdAfx.h"
14 #include "DirView.h"
15 #include "Constants.h"
16 #include "Merge.h"
17 #include "ClipBoard.h"
18 #include "DirActions.h"
19 #include "DirViewColItems.h"
20 #include "DirFrame.h"  // StatePane
21 #include "DirDoc.h"
22 #include "IMergeDoc.h"
23 #include "FileLocation.h"
24 #include "MainFrm.h"
25 #include "resource.h"
26 #include "FileTransform.h"
27 #include "SelectPluginDlg.h"
28 #include "paths.h"
29 #include "7zCommon.h"
30 #include "OptionsDef.h"
31 #include "OptionsMgr.h"
32 #include "BCMenu.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"
41 #include "UniFile.h"
42 #include "ShellContextMenu.h"
43 #include "DiffItem.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"
50 #include "Shell.h"
51 #include "DirTravel.h"
52 #include <numeric>
53 #include <functional>
54
55 #ifdef _DEBUG
56 #define new DEBUG_NEW
57 #endif
58
59 using std::swap;
60 using namespace std::placeholders;
61
62 /**
63  * @brief Location for folder compare specific help to open.
64  */
65 static TCHAR DirViewHelpLocation[] = _T("::/htmlhelp/Compare_dirs.html");
66
67 /**
68  * @brief Limit (in seconds) to signal compare is ready for user.
69  * If compare takes longer than this value (in seconds) we inform
70  * user about it. Current implementation uses MessageBeep(IDOK).
71  */
72 const int TimeToSignalCompare = 3;
73
74 // The resource ID constants/limits for the Shell context menu
75 const UINT LeftCmdFirst = 0x9000; // this should be greater than any of already defined command IDs
76 const UINT RightCmdLast = 0xffff; // maximum available value
77 const UINT LeftCmdLast = LeftCmdFirst + (RightCmdLast - LeftCmdFirst) / 3; // divide available range equally between two context menus
78 const UINT MiddleCmdFirst = LeftCmdLast + 1;
79 const UINT MiddleCmdLast = MiddleCmdFirst + (RightCmdLast - LeftCmdFirst) / 3;
80 const UINT RightCmdFirst = MiddleCmdLast + 1;
81
82 /////////////////////////////////////////////////////////////////////////////
83 // CDirView
84
85 enum { 
86         COLUMN_REORDER = 99,
87         STATUSBAR_UPDATE = 100
88 };
89
90 IMPLEMENT_DYNCREATE(CDirView, CListView)
91
92 CDirView::CDirView()
93                 : m_pList(nullptr)
94                 , m_compareStart(0)
95                 , m_elapsed(0)
96                 , m_bTreeMode(false)
97                 , m_dirfilter(std::bind(&COptionsMgr::GetBool, GetOptionsMgr(), _1))
98                 , m_pShellContextMenuLeft(nullptr)
99                 , m_pShellContextMenuMiddle(nullptr)
100                 , m_pShellContextMenuRight(nullptr)
101                 , m_hCurrentMenu(nullptr)
102                 , m_pSavedTreeState(nullptr)
103                 , m_pColItems(nullptr)
104                 , m_nActivePane(-1)
105 {
106         m_dwDefaultStyle &= ~LVS_TYPEMASK;
107         // Show selection all the time, so user can see current item even when
108         // focus is elsewhere (ie, on file edit window)
109         m_dwDefaultStyle |= LVS_REPORT | LVS_SHOWSELALWAYS | LVS_EDITLABELS | LVS_OWNERDATA;
110
111         m_bTreeMode =  GetOptionsMgr()->GetBool(OPT_TREE_MODE);
112         m_bExpandSubdirs = GetOptionsMgr()->GetBool(OPT_DIRVIEW_EXPAND_SUBDIRS);
113         m_nEscCloses = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
114         Options::DirColors::Load(GetOptionsMgr(), m_cachedColors);
115         m_bUseColors = GetOptionsMgr()->GetBool(OPT_DIRCLR_USE_COLORS);
116 }
117
118 CDirView::~CDirView()
119 {
120 }
121
122 BEGIN_MESSAGE_MAP(CDirView, CListView)
123         //{{AFX_MSG_MAP(CDirView)
124         ON_WM_CONTEXTMENU()
125         ON_WM_LBUTTONDBLCLK()
126         ON_WM_SIZE()
127         ON_WM_DESTROY()
128         ON_WM_CHAR()
129         ON_WM_KEYDOWN()
130         ON_WM_TIMER()
131         ON_MESSAGE(MSG_UI_UPDATE, OnUpdateUIMessage)
132         ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
133         ON_COMMAND(ID_EDIT_CUT, OnEditCut)
134         ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
135         ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
136         ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
137         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateSave)
138         ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnClick)
139         ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, OnItemChanged)
140         ON_NOTIFY_REFLECT(LVN_BEGINLABELEDIT, OnBeginLabelEdit)
141         ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, OnEndLabelEdit)
142         ON_NOTIFY_REFLECT(LVN_ODFINDITEM, OnODFindItem)
143         ON_NOTIFY_REFLECT(NM_CLICK, OnClick)
144         ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBeginDrag)
145         ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
146         // [File] menu
147         ON_COMMAND(ID_FILE_LEFT_READONLY, OnReadOnly<SIDE_LEFT>)
148         ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnReadOnly<SIDE_MIDDLE>)
149         ON_COMMAND(ID_FILE_RIGHT_READONLY, OnReadOnly<SIDE_RIGHT>)
150         ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateReadOnly<SIDE_LEFT>)
151         ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateReadOnly<SIDE_MIDDLE>)
152         ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateReadOnly<SIDE_RIGHT>)
153         ON_COMMAND(ID_FILE_ENCODING, OnFileEncoding)
154         // [Edit] menu
155         ON_COMMAND(ID_EDIT_SELECT_ALL, OnSelectAll)
156         ON_UPDATE_COMMAND_UI(ID_EDIT_SELECT_ALL, OnUpdateSelectAll)
157         // [View] menu
158         ON_COMMAND(ID_OPTIONS_SHOWDIFFERENT, OnOptionsShowDifferent)
159         ON_COMMAND(ID_OPTIONS_SHOWIDENTICAL, OnOptionsShowIdentical)
160         ON_COMMAND(ID_OPTIONS_SHOWUNIQUELEFT, OnOptionsShowUniqueLeft)
161         ON_COMMAND(ID_OPTIONS_SHOWUNIQUEMIDDLE, OnOptionsShowUniqueMiddle)
162         ON_COMMAND(ID_OPTIONS_SHOWUNIQUERIGHT, OnOptionsShowUniqueRight)
163         ON_COMMAND(ID_OPTIONS_SHOWBINARIES, OnOptionsShowBinaries)
164         ON_COMMAND(ID_OPTIONS_SHOWSKIPPED, OnOptionsShowSkipped)
165         ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTLEFTONLY, OnOptionsShowDifferentLeftOnly)
166         ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTMIDDLEONLY, OnOptionsShowDifferentMiddleOnly)
167         ON_COMMAND(ID_OPTIONS_SHOWDIFFERENTRIGHTONLY, OnOptionsShowDifferentRightOnly)
168         ON_COMMAND(ID_OPTIONS_SHOWMISSINGLEFTONLY, OnOptionsShowMissingLeftOnly)
169         ON_COMMAND(ID_OPTIONS_SHOWMISSINGMIDDLEONLY, OnOptionsShowMissingMiddleOnly)
170         ON_COMMAND(ID_OPTIONS_SHOWMISSINGRIGHTONLY, OnOptionsShowMissingRightOnly)
171         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENT, OnUpdateOptionsShowdifferent)
172         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWIDENTICAL, OnUpdateOptionsShowidentical)
173         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUELEFT, OnUpdateOptionsShowuniqueleft)
174         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUEMIDDLE, OnUpdateOptionsShowuniquemiddle)
175         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUERIGHT, OnUpdateOptionsShowuniqueright)
176         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWBINARIES, OnUpdateOptionsShowBinaries)
177         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWSKIPPED, OnUpdateOptionsShowSkipped)
178         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTLEFTONLY, OnUpdateOptionsShowDifferentLeftOnly)
179         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTMIDDLEONLY, OnUpdateOptionsShowDifferentMiddleOnly)
180         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENTRIGHTONLY, OnUpdateOptionsShowDifferentRightOnly)
181         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGLEFTONLY, OnUpdateOptionsShowMissingLeftOnly)
182         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGMIDDLEONLY, OnUpdateOptionsShowMissingMiddleOnly)
183         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWMISSINGRIGHTONLY, OnUpdateOptionsShowMissingRightOnly)
184         ON_COMMAND(ID_VIEW_SHOWHIDDENITEMS, OnViewShowHiddenItems)
185         ON_UPDATE_COMMAND_UI(ID_VIEW_SHOWHIDDENITEMS, OnUpdateViewShowHiddenItems)
186         ON_COMMAND(ID_VIEW_TREEMODE, OnViewTreeMode)
187         ON_COMMAND(ID_VIEW_EXPAND_ALLSUBDIRS, OnViewExpandAllSubdirs)
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, OnUpdateViewExpandAllSubdirs)
191         ON_UPDATE_COMMAND_UI(ID_VIEW_COLLAPSE_ALLSUBDIRS, OnUpdateViewCollapseAllSubdirs)
192         ON_COMMAND(ID_SWAPPANES_SWAP12, (OnViewSwapPanes<0, 1>))
193         ON_COMMAND(ID_SWAPPANES_SWAP23, (OnViewSwapPanes<1, 2>))
194         ON_COMMAND(ID_SWAPPANES_SWAP13, (OnViewSwapPanes<0, 2>))
195         ON_UPDATE_COMMAND_UI(ID_SWAPPANES_SWAP12, (OnUpdateViewSwapPanes<0, 1>))
196         ON_UPDATE_COMMAND_UI(ID_SWAPPANES_SWAP23, (OnUpdateViewSwapPanes<1, 2>))
197         ON_UPDATE_COMMAND_UI(ID_SWAPPANES_SWAP13, (OnUpdateViewSwapPanes<0, 2>))
198         ON_COMMAND(ID_VIEW_DIR_STATISTICS, OnViewCompareStatistics)
199         ON_COMMAND(ID_REFRESH, OnRefresh)
200         ON_UPDATE_COMMAND_UI(ID_REFRESH, OnUpdateRefresh)
201         ON_COMMAND(ID_RESCAN, OnMarkedRescan)
202         // [Merge] menu or Context menu
203         ON_COMMAND_RANGE(ID_MERGE_COMPARE, ID_MERGE_COMPARE_IN_NEW_WINDOW, OnMergeCompare)
204         ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE, ID_MERGE_COMPARE_IN_NEW_WINDOW, OnUpdateMergeCompare)
205         ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
206         ON_COMMAND(ID_LASTDIFF, OnLastdiff)
207         ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
208         ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
209         ON_COMMAND(ID_CURDIFF, OnCurdiff)
210         ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
211         ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
212         ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
213         ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
214         ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
215         ON_COMMAND_RANGE(ID_L2R, ID_R2L, OnDirCopy)
216         ON_UPDATE_COMMAND_UI_RANGE(ID_L2R, ID_R2L, OnUpdateDirCopy)
217         ON_COMMAND(ID_MERGE_DELETE, OnDelete)
218         ON_UPDATE_COMMAND_UI(ID_MERGE_DELETE, OnUpdateDelete)
219         // [Tools] menu
220         ON_COMMAND(ID_TOOLS_CUSTOMIZECOLUMNS, OnCustomizeColumns)
221         ON_COMMAND(ID_TOOLS_GENERATEREPORT, OnToolsGenerateReport)
222         ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
223         ON_MESSAGE(MSG_GENERATE_FLIE_COMPARE_REPORT, OnGenerateFileCmpReport)
224         // [Plugins] menu
225         ON_COMMAND(ID_OPEN_WITH_UNPACKER, OnOpenWithUnpacker)
226         ON_UPDATE_COMMAND_UI(ID_OPEN_WITH_UNPACKER, OnUpdateCtxtOpenWithUnpacker)
227         // [Help] menu
228         ON_COMMAND(ID_HELP, OnHelp)
229         // Context menu
230         // Context menu -> Compare Non-horizontally
231         ON_COMMAND(ID_MERGE_COMPARE_LEFT1_LEFT2, OnMergeCompare2<SELECTIONTYPE_LEFT1LEFT2>)
232         ON_COMMAND(ID_MERGE_COMPARE_RIGHT1_RIGHT2, OnMergeCompare2<SELECTIONTYPE_RIGHT1RIGHT2>)
233         ON_COMMAND(ID_MERGE_COMPARE_LEFT1_RIGHT2, OnMergeCompare2<SELECTIONTYPE_LEFT1RIGHT2>)
234         ON_COMMAND(ID_MERGE_COMPARE_LEFT2_RIGHT1, OnMergeCompare2<SELECTIONTYPE_LEFT2RIGHT1>)
235         ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT1_LEFT2, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT1LEFT2>)
236         ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_RIGHT1_RIGHT2, OnUpdateMergeCompare2<SELECTIONTYPE_RIGHT1RIGHT2>)
237         ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT1_RIGHT2, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT1RIGHT2>)
238         ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_LEFT2_RIGHT1, OnUpdateMergeCompare2<SELECTIONTYPE_LEFT2RIGHT1>)
239         ON_COMMAND(ID_MERGE_COMPARE_NONHORIZONTALLY, OnMergeCompareNonHorizontally)
240         // Context menu -> Compare As
241         ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnMergeCompareAs)
242         ON_COMMAND_RANGE(ID_UNPACKERS_FIRST, ID_UNPACKERS_LAST, OnMergeCompareAs)
243         ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnUpdateMergeCompare)
244         ON_UPDATE_COMMAND_UI(ID_NO_UNPACKER, OnUpdateNoUnpacker)
245         // Context menu -> Copy
246         ON_COMMAND(ID_DIR_COPY_LEFT_TO_RIGHT, (OnCtxtDirCopy<SIDE_LEFT, SIDE_RIGHT>))
247         ON_COMMAND(ID_DIR_COPY_LEFT_TO_MIDDLE, (OnCtxtDirCopy<SIDE_LEFT, SIDE_MIDDLE>))
248         ON_COMMAND(ID_DIR_COPY_RIGHT_TO_LEFT, (OnCtxtDirCopy<SIDE_RIGHT, SIDE_LEFT>))
249         ON_COMMAND(ID_DIR_COPY_RIGHT_TO_MIDDLE, (OnCtxtDirCopy<SIDE_RIGHT, SIDE_MIDDLE>))
250         ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_LEFT, (OnCtxtDirCopy<SIDE_MIDDLE, SIDE_LEFT>))
251         ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_RIGHT, (OnCtxtDirCopy<SIDE_MIDDLE, SIDE_RIGHT>))
252         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_RIGHT, (OnUpdateCtxtDirCopy<SIDE_LEFT, SIDE_RIGHT>))
253         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_MIDDLE, (OnUpdateCtxtDirCopy<SIDE_LEFT, SIDE_MIDDLE>))
254         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_LEFT, (OnUpdateCtxtDirCopy<SIDE_RIGHT, SIDE_LEFT>))
255         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_MIDDLE, (OnUpdateCtxtDirCopy<SIDE_RIGHT, SIDE_MIDDLE>))
256         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_LEFT, (OnUpdateCtxtDirCopy<SIDE_MIDDLE, SIDE_LEFT>))
257         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_RIGHT, (OnUpdateCtxtDirCopy<SIDE_MIDDLE, SIDE_RIGHT>))
258         ON_COMMAND(ID_DIR_COPY_LEFT_TO_BROWSE, OnCtxtDirCopyTo<SIDE_LEFT>)
259         ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_BROWSE, OnCtxtDirCopyTo<SIDE_MIDDLE>)
260         ON_COMMAND(ID_DIR_COPY_RIGHT_TO_BROWSE, OnCtxtDirCopyTo<SIDE_RIGHT>)
261         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_LEFT>)
262         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_MIDDLE>)
263         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_BROWSE, OnUpdateCtxtDirCopyTo<SIDE_RIGHT>)
264         // Context menu -> Move
265         ON_COMMAND(ID_DIR_MOVE_LEFT_TO_BROWSE, OnCtxtDirMoveTo<SIDE_LEFT>)
266         ON_COMMAND(ID_DIR_MOVE_MIDDLE_TO_BROWSE, OnCtxtDirMoveTo<SIDE_MIDDLE>)
267         ON_COMMAND(ID_DIR_MOVE_RIGHT_TO_BROWSE, OnCtxtDirMoveTo<SIDE_RIGHT>)
268         ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_LEFT_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_LEFT>)
269         ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_MIDDLE_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_MIDDLE>)
270         ON_UPDATE_COMMAND_UI(ID_DIR_MOVE_RIGHT_TO_BROWSE, OnUpdateCtxtDirMoveTo<SIDE_RIGHT>)
271         // Context menu -> Delete
272         ON_COMMAND(ID_DIR_DEL_LEFT, OnCtxtDirDel<SIDE_LEFT>)
273         ON_COMMAND(ID_DIR_DEL_RIGHT, OnCtxtDirDel<SIDE_RIGHT>)
274         ON_COMMAND(ID_DIR_DEL_MIDDLE, OnCtxtDirDel<SIDE_MIDDLE>)
275         ON_COMMAND(ID_DIR_DEL_BOTH, OnCtxtDirDelBoth)
276         ON_COMMAND(ID_DIR_DEL_ALL, OnCtxtDirDelBoth)
277         ON_UPDATE_COMMAND_UI(ID_DIR_DEL_LEFT, OnUpdateCtxtDirDel<SIDE_LEFT>)
278         ON_UPDATE_COMMAND_UI(ID_DIR_DEL_MIDDLE, OnUpdateCtxtDirDel<SIDE_MIDDLE>)
279         ON_UPDATE_COMMAND_UI(ID_DIR_DEL_RIGHT, OnUpdateCtxtDirDel<SIDE_RIGHT>)
280         ON_UPDATE_COMMAND_UI(ID_DIR_DEL_BOTH, OnUpdateCtxtDirDelBoth)
281         ON_UPDATE_COMMAND_UI(ID_DIR_DEL_ALL, OnUpdateCtxtDirDelBoth)
282         // Context menu -> Rename, Hide Items
283         ON_COMMAND(ID_DIR_ITEM_RENAME, OnItemRename)
284         ON_UPDATE_COMMAND_UI(ID_DIR_ITEM_RENAME, OnUpdateItemRename)
285         ON_COMMAND(ID_DIR_HIDE_FILENAMES, OnHideFilenames)
286         ON_UPDATE_COMMAND_UI(ID_DIR_HIDE_FILENAMES, OnUpdateHideFilenames)
287         // Context menu -> Open Left
288         ON_COMMAND(ID_DIR_OPEN_LEFT, OnCtxtDirOpen<SIDE_LEFT>)
289         ON_COMMAND(ID_DIR_OPEN_LEFT_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_LEFT>)
290         ON_COMMAND(ID_DIR_OPEN_LEFT_WITH, OnCtxtDirOpenWith<SIDE_LEFT>)
291         ON_COMMAND(ID_DIR_OPEN_LEFT_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_LEFT>)
292         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT, OnUpdateCtxtDirOpen<SIDE_LEFT>)
293         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_LEFT>)
294         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_WITH, OnUpdateCtxtDirOpenWith<SIDE_LEFT>)
295         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_LEFT_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_LEFT>)
296         // Context menu -> Open Middle
297         ON_COMMAND(ID_DIR_OPEN_MIDDLE, OnCtxtDirOpen<SIDE_MIDDLE>)
298         ON_COMMAND(ID_DIR_OPEN_MIDDLE_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_MIDDLE>)
299         ON_COMMAND(ID_DIR_OPEN_MIDDLE_WITH, OnCtxtDirOpenWith<SIDE_MIDDLE>)
300         ON_COMMAND(ID_DIR_OPEN_MIDDLE_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_MIDDLE>)
301         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE, OnUpdateCtxtDirOpen<SIDE_MIDDLE>)
302         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_MIDDLE>)
303         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_WITH, OnUpdateCtxtDirOpenWith<SIDE_MIDDLE>)
304         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_MIDDLE_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_MIDDLE>)
305         // Context menu -> Open Right
306         ON_COMMAND(ID_DIR_OPEN_RIGHT, OnCtxtDirOpen<SIDE_RIGHT>)
307         ON_COMMAND(ID_DIR_OPEN_RIGHT_WITHEDITOR, OnCtxtDirOpenWithEditor<SIDE_RIGHT>)
308         ON_COMMAND(ID_DIR_OPEN_RIGHT_WITH, OnCtxtDirOpenWith<SIDE_RIGHT>)
309         ON_COMMAND(ID_DIR_OPEN_RIGHT_PARENT_FOLDER, OnCtxtDirOpenParentFolder<SIDE_RIGHT>)
310         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT, OnUpdateCtxtDirOpen<SIDE_RIGHT>)
311         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_WITHEDITOR, OnUpdateCtxtDirOpenWithEditor<SIDE_RIGHT>)
312         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_WITH, OnUpdateCtxtDirOpenWith<SIDE_RIGHT>)
313         ON_UPDATE_COMMAND_UI(ID_DIR_OPEN_RIGHT_PARENT_FOLDER, OnUpdateCtxtDirOpenParentFolder<SIDE_RIGHT>)
314         // Context menu -> Copy Pathnames
315         ON_COMMAND(ID_DIR_COPY_PATHNAMES_LEFT, OnCopyPathnames<SIDE_LEFT>)
316         ON_COMMAND(ID_DIR_COPY_PATHNAMES_MIDDLE, OnCopyPathnames<SIDE_MIDDLE>)
317         ON_COMMAND(ID_DIR_COPY_PATHNAMES_RIGHT, OnCopyPathnames<SIDE_RIGHT>)
318         ON_COMMAND(ID_DIR_COPY_PATHNAMES_BOTH, OnCopyBothPathnames)
319         ON_COMMAND(ID_DIR_COPY_PATHNAMES_ALL, OnCopyBothPathnames)
320         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_LEFT, OnUpdateCtxtDirCopy2<SIDE_LEFT>)
321         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_MIDDLE, OnUpdateCtxtDirCopy2<SIDE_MIDDLE>)
322         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_RIGHT, OnUpdateCtxtDirCopy2<SIDE_RIGHT>)
323         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_BOTH, OnUpdateCtxtDirCopyBoth2)
324         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_PATHNAMES_ALL, OnUpdateCtxtDirCopyBoth2)
325         // Context menu -> Zip
326         ON_COMMAND(ID_DIR_ZIP_LEFT, OnCtxtDirZip<DirItemEnumerator::Left>)
327         ON_COMMAND(ID_DIR_ZIP_MIDDLE, OnCtxtDirZip<DirItemEnumerator::Middle>)
328         ON_COMMAND(ID_DIR_ZIP_RIGHT, OnCtxtDirZip<DirItemEnumerator::Right>)
329         ON_COMMAND(ID_DIR_ZIP_BOTH, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders>)
330         ON_COMMAND(ID_DIR_ZIP_ALL, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders>)
331         ON_COMMAND(ID_DIR_ZIP_BOTH_DIFFS_ONLY, OnCtxtDirZip<DirItemEnumerator::Original | DirItemEnumerator::Altered | DirItemEnumerator::BalanceFolders | DirItemEnumerator::DiffsOnly>)
332         ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_LEFT, OnUpdateCtxtDirCopyTo<SIDE_LEFT>)
333         ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_MIDDLE, OnUpdateCtxtDirCopyTo<SIDE_MIDDLE>)
334         ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_RIGHT, OnUpdateCtxtDirCopyTo<SIDE_RIGHT>)
335         ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_BOTH, OnUpdateCtxtDirCopyBothTo)
336         ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_ALL, OnUpdateCtxtDirCopyBothTo)
337         ON_UPDATE_COMMAND_UI(ID_DIR_ZIP_BOTH_DIFFS_ONLY, OnUpdateCtxtDirCopyBothDiffsOnlyTo)
338         // Context menu -> Left/Middle/Right Shell menu
339         ON_COMMAND(ID_DIR_SHELL_CONTEXT_MENU_LEFT, OnCtxtDirShellContextMenu<SIDE_LEFT>)
340         ON_COMMAND(ID_DIR_SHELL_CONTEXT_MENU_MIDDLE, OnCtxtDirShellContextMenu<SIDE_MIDDLE>)
341         ON_COMMAND(ID_DIR_SHELL_CONTEXT_MENU_RIGHT, OnCtxtDirShellContextMenu<SIDE_RIGHT>)
342         // Context menu -> Plugin settings
343         ON_COMMAND_RANGE(ID_PREDIFFER_SETTINGS_NONE, ID_PREDIFFER_SETTINGS_SELECT, OnPluginSettings)
344         ON_COMMAND_RANGE(ID_UNPACKER_SETTINGS_NONE, ID_UNPACKER_SETTINGS_SELECT, OnPluginSettings)
345         ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFER_SETTINGS_NONE, ID_PREDIFFER_SETTINGS_SELECT, OnUpdatePluginMode)
346         ON_UPDATE_COMMAND_UI_RANGE(ID_UNPACKER_SETTINGS_NONE, ID_UNPACKER_SETTINGS_SELECT, OnUpdatePluginMode)
347         // Context menu -> Copy Filenames
348         ON_COMMAND(ID_DIR_COPY_FILENAMES, OnCopyFilenames)
349         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_FILENAMES, OnUpdateCopyFilenames)
350         // Context menu -> Copy Items to Clipboard
351         ON_COMMAND(ID_DIR_COPY_LEFT_TO_CLIPBOARD, OnCopyToClipboard<SIDE_LEFT>)
352         ON_COMMAND(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, OnCopyToClipboard<SIDE_MIDDLE>)
353         ON_COMMAND(ID_DIR_COPY_RIGHT_TO_CLIPBOARD, OnCopyToClipboard<SIDE_RIGHT>)
354         ON_COMMAND(ID_DIR_COPY_BOTH_TO_CLIPBOARD, OnCopyBothToClipboard)
355         ON_COMMAND(ID_DIR_COPY_ALL_TO_CLIPBOARD, OnCopyBothToClipboard)
356         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_LEFT_TO_CLIPBOARD, OnUpdateCtxtDirCopy2<SIDE_LEFT>)
357         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, OnUpdateCtxtDirCopy2<SIDE_MIDDLE>)
358         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_RIGHT_TO_CLIPBOARD, OnUpdateCtxtDirCopy2<SIDE_RIGHT>)
359         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_BOTH_TO_CLIPBOARD, OnUpdateCtxtDirCopyBoth2)
360         ON_UPDATE_COMMAND_UI(ID_DIR_COPY_ALL_TO_CLIPBOARD, OnUpdateCtxtDirCopyBoth2)
361         // Status bar
362         ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
363         ON_UPDATE_COMMAND_UI(ID_STATUS_RIGHTDIR_RO, OnUpdateStatusRightRO)
364         ON_UPDATE_COMMAND_UI(ID_STATUS_MIDDLEDIR_RO, OnUpdateStatusMiddleRO)
365         ON_UPDATE_COMMAND_UI(ID_STATUS_LEFTDIR_RO, OnUpdateStatusLeftRO)
366         //}}AFX_MSG_MAP
367 END_MESSAGE_MAP()
368
369 /////////////////////////////////////////////////////////////////////////////
370 // CDirView diagnostics
371
372 #ifdef _DEBUG
373
374 CDirDoc* CDirView::GetDocument() // non-debug version is inline
375 {
376         ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDirDoc)));
377         return (CDirDoc*)m_pDocument;
378 }
379 #endif //_DEBUG
380
381 /////////////////////////////////////////////////////////////////////////////
382 // CDirView message handlers
383
384 void CDirView::OnInitialUpdate()
385 {
386         const int iconCX = []() {
387                 const int cx = GetSystemMetrics(SM_CXSMICON);
388                 if (cx < 24)
389                         return 16;
390                 if (cx < 32)
391                         return 24;
392                 if (cx < 48)
393                         return 32;
394                 return 48;
395         }();
396         const int iconCY = iconCX;
397         CListView::OnInitialUpdate();
398         m_pList = &GetListCtrl();
399         m_pIList.reset(new IListCtrlImpl(m_pList->m_hWnd, m_listViewItems));
400         CDirDoc* pDoc = GetDocument();
401         pDoc->SetDirView(this);
402
403         auto properties = strutils::split<std::vector<String>>(GetOptionsMgr()->GetString(OPT_ADDITIONAL_PROPERTIES), ' ');
404         m_pColItems.reset(new DirViewColItems(pDoc->m_nDirs, properties));
405
406         m_pList->SendMessage(CCM_SETUNICODEFORMAT, TRUE, 0);
407
408         // Load user-selected font
409         if (GetOptionsMgr()->GetBool(OPT_FONT_DIRCMP + OPT_FONT_USECUSTOM))
410         {
411                 m_font.CreateFontIndirect(&GetMainFrame()->m_lfDir);
412                 CWnd::SetFont(&m_font, TRUE);
413         }
414
415         if (m_bUseColors)
416                 m_pList->SetBkColor(m_cachedColors.clrDirMargin);
417
418         // Replace standard header with sort header
419         HWND hWnd = ListView_GetHeader(m_pList->m_hWnd);
420         if (hWnd != nullptr)
421                 m_ctlSortHeader.SubclassWindow(hWnd);
422
423         // Load the icons used for the list view (to reflect diff status)
424         // NOTE: these must be in the exactly the same order as in the `enum`
425         // definition in the DirActions.h file (ref: DIFFIMG_LUNIQUE)
426         VERIFY(m_imageList.Create(iconCX, iconCY, ILC_COLOR32 | ILC_MASK, 15, 1));
427         int icon_ids[] = {
428                 IDI_LFILE, IDI_MFILE, IDI_RFILE,
429                 IDI_MRFILE, IDI_LRFILE, IDI_LMFILE,
430                 IDI_NOTEQUALFILE, IDI_EQUALFILE, IDI_FILE, 
431                 IDI_EQUALBINARY, IDI_BINARYDIFF,
432                 IDI_LFOLDER, IDI_MFOLDER, IDI_RFOLDER,
433                 IDI_MRFOLDER, IDI_LRFOLDER, IDI_LMFOLDER,
434                 IDI_FILESKIP, IDI_FOLDERSKIP,
435                 IDI_NOTEQUALFOLDER, IDI_EQUALFOLDER, IDI_FOLDER,
436                 IDI_COMPARE_ERROR,
437                 IDI_FOLDERUP, IDI_FOLDERUP_DISABLE,
438                 IDI_COMPARE_ABORTED,
439                 IDI_NOTEQUALTEXTFILE, IDI_EQUALTEXTFILE,
440                 IDI_NOTEQUALIMAGE, IDI_EQUALIMAGE, 
441         };
442         for (auto id : icon_ids)
443                 VERIFY(-1 != m_imageList.Add((HICON)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(id), IMAGE_ICON, iconCX, iconCY, 0)));
444         m_pList->SetImageList(&m_imageList, LVSIL_SMALL);
445
446         // Load the icons used for the list view (expanded/collapsed state icons)
447         VERIFY(m_imageState.Create(iconCX, iconCY, ILC_COLOR32 | ILC_MASK, 15, 1));
448         for (auto id : { IDI_TREE_STATE_COLLAPSED, IDI_TREE_STATE_EXPANDED })
449                 VERIFY(-1 != m_imageState.Add((HICON)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(id), IMAGE_ICON, iconCX, iconCY, 0)));
450
451         // Restore column orders as they had them last time they ran
452         m_pColItems->LoadColumnOrders(
453                 GetOptionsMgr()->GetString(pDoc->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_ORDERS : OPT_DIRVIEW3_COLUMN_ORDERS));
454
455         // Display column headers (in appropriate order)
456         ReloadColumns();
457
458         // Show selection across entire row.u
459         // Also allow user to rearrange columns via drag&drop of headers.
460         // Also enable infotips.
461         DWORD exstyle = LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP | LVS_EX_DOUBLEBUFFER;
462         m_pList->SetExtendedStyle(exstyle);
463 }
464
465 BOOL CDirView::PreCreateWindow(CREATESTRUCT& cs)
466 {
467         CListView::PreCreateWindow(cs);
468         cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
469         return TRUE;
470 }
471
472 /**
473  * @brief Called before compare is started.
474  * CDirDoc calls this function before new compare is started, so this
475  * is good place to setup GUI for compare.
476  * @param [in] pCompareStats Pointer to class having current compare stats.
477  */
478 void CDirView::StartCompare(CompareStats *pCompareStats)
479 {
480         m_compareStart = clock();
481 }
482
483 /**
484  * @brief Called when folder compare row is double-clicked with mouse.
485  * Selected item is opened to folder or file compare.
486  */
487 void CDirView::OnLButtonDblClk(UINT nFlags, CPoint point)
488 {
489         LVHITTESTINFO lvhti;
490         lvhti.pt = point;
491         m_pList->SubItemHitTest(&lvhti);
492         if (lvhti.iItem >= 0)
493         {
494                 const DIFFITEM& di = GetDiffItem(lvhti.iItem);
495                 if (m_bTreeMode && GetDiffContext().m_bRecursive && di.diffcode.isDirectory())
496                 {
497                         if (di.customFlags & ViewCustomFlags::EXPANDED)
498                                 CollapseSubdir(lvhti.iItem);
499                         else
500                                 ExpandSubdir(lvhti.iItem);
501                 }
502                 else
503                 {
504                         OpenSelection();
505                 }
506         }
507         if (GetFocus() == this)
508                 CListView::OnLButtonDblClk(nFlags, point);
509 }
510
511 /**
512  * @brief Load or reload the columns (headers) of the list view
513  */
514 void CDirView::ReloadColumns()
515 {
516         LoadColumnHeaderItems();
517
518         UpdateColumnNames();
519         m_pColItems->LoadColumnWidths(
520                 GetOptionsMgr()->GetString(GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS),
521                 std::bind(&CListCtrl::SetColumnWidth, m_pList, _1, _2), GetDefColumnWidth());
522         SetColAlignments();
523 }
524
525 /**
526  * @brief Redisplay items in subfolder
527  * @param [in] diffpos First item position in subfolder.
528  * @param [in] level Indent level
529  * @param [in,out] index Index of the item to be inserted.
530  * @param [in,out] alldiffs Number of different items
531  */
532 void CDirView::RedisplayChildren(DIFFITEM *diffpos, int level, UINT &index, int &alldiffs)
533 {
534         const CDiffContext &ctxt = GetDiffContext();
535         while (diffpos != nullptr)
536         {
537                 DIFFITEM *curdiffpos = diffpos;
538                 const DIFFITEM &di = ctxt.GetNextSiblingDiffPosition(diffpos);
539
540                 if (di.diffcode.isResultDiff() || (!di.diffcode.existAll() && !di.diffcode.isResultFiltered()))
541                         ++alldiffs;
542
543                 bool bShowable = IsShowable(ctxt, di, m_dirfilter);
544                 if (bShowable)
545                 {
546                         if (m_bTreeMode)
547                         {
548                                 AddNewItem(index, curdiffpos, I_IMAGECALLBACK, level);
549                                 index++;
550                                 if (di.HasChildren())
551                                 {
552                                         if (di.customFlags & ViewCustomFlags::EXPANDED)
553                                                 RedisplayChildren(ctxt.GetFirstChildDiffPosition(curdiffpos), level + 1, index, alldiffs);
554                                 }
555                         }
556                         else
557                         {
558                                 if (!ctxt.m_bRecursive || !di.diffcode.isDirectory() || (!di.diffcode.existAll() && !di.HasChildren()))
559                                 {
560                                         AddNewItem(index, curdiffpos, I_IMAGECALLBACK, 0);
561                                         index++;
562                                 }
563                                 if (di.HasChildren())
564                                 {
565                                         RedisplayChildren(ctxt.GetFirstChildDiffPosition(curdiffpos), level + 1, index, alldiffs);
566                                 }
567                         }
568                 }
569         }
570         m_firstDiffItem.reset();
571         m_lastDiffItem.reset();
572 }
573
574 /**
575  * @brief Redisplay folder compare view.
576  * This function clears folder compare view and then adds
577  * items from current compare to it.
578  */
579 void CDirView::Redisplay()
580 {
581         const CDirDoc *pDoc = GetDocument();
582         const CDiffContext &ctxt = GetDiffContext();
583         PathContext pathsParent;
584         CImageList emptyImageList;
585
586         UINT cnt = 0;
587         // Disable redrawing while adding new items
588         SetRedraw(FALSE);
589
590         DeleteAllDisplayItems();
591
592         m_pList->SetImageList((m_bTreeMode && ctxt.m_bRecursive) ? &m_imageState : &emptyImageList, LVSIL_STATE);
593
594         // If non-recursive compare, add special item(s)
595         if (!ctxt.m_bRecursive ||
596                 CheckAllowUpwardDirectory(ctxt, pDoc->m_pTempPathContext, pathsParent) == AllowUpwardDirectory::ParentIsTempPath)
597         {
598                 cnt += AddSpecialItems();
599         }
600
601         int alldiffs = 0;
602         DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
603         RedisplayChildren(diffpos, 0, cnt, alldiffs);
604         if (pDoc->m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPLETED)
605                 GetParentFrame()->SetLastCompareResult(alldiffs);
606         SortColumnsAppropriately();
607         SetRedraw(TRUE);
608         m_pList->SetItemCount(static_cast<int>(m_listViewItems.size()));
609         m_pList->Invalidate();
610 }
611
612 /**
613  * @brief User right-clicked somewhere in this view
614  */
615 void CDirView::OnContextMenu(CWnd*, CPoint point)
616 {
617         if (GetListCtrl().GetItemCount() == 0)
618                 return;
619         // Make sure window is active
620         GetParentFrame()->ActivateFrame();
621
622         int i = 0;
623         if (point.x == -1 && point.y == -1)
624         {
625                 //keystroke invocation
626                 CRect rect;
627                 GetClientRect(rect);
628                 ClientToScreen(rect);
629
630                 point = rect.TopLeft();
631                 point.Offset(5, 5);
632         }
633         else
634         {
635                 // Check if user right-clicked on header
636                 // convert screen coordinates to client coordinates of listview
637                 CPoint insidePt = point;
638                 GetListCtrl().ScreenToClient(&insidePt);
639                 // TODO: correct for hscroll ?
640                 // Ask header control if click was on one of its header items
641                 HDHITTESTINFO hhti = { 0 };
642                 hhti.pt = insidePt;
643                 int col = static_cast<int>(GetListCtrl().GetHeaderCtrl()->SendMessage(HDM_HITTEST, 0, (LPARAM) & hhti));
644                 if (col >= 0)
645                 {
646                         // Presumably hhti.flags & HHT_ONHEADER is true
647                         HeaderContextMenu(point, m_pColItems->ColPhysToLog(col));
648                         return;
649                 }
650                 // bail out if point is not in any row
651                 LVHITTESTINFO lhti = { 0 };
652                 insidePt = point;
653                 ScreenToClient(&insidePt);
654                 lhti.pt = insidePt;
655                 i = GetListCtrl().HitTest(insidePt);
656                 TRACE(_T("i=%d\n"), i);
657                 if (i < 0)
658                         return;
659         }
660
661         ListContextMenu(point, i);
662 }
663
664 /**
665  * @brief Format context menu string and disable item if it cannot be applied.
666  */
667 static void NTAPI FormatContextMenu(BCMenu *pPopup, UINT uIDItem, int n1, int n2 = 0, int n3 = 0)
668 {
669         CString s1, s2;
670         pPopup->GetMenuText(uIDItem, s1, MF_BYCOMMAND);
671         s2.FormatMessage(s1, NumToStr(n1).c_str(), NumToStr(n2).c_str(), NumToStr(n3).c_str());
672         pPopup->SetMenuText(uIDItem, s2, MF_BYCOMMAND);
673         if (n1 == 0)
674         {
675                 pPopup->EnableMenuItem(uIDItem, MF_GRAYED);
676         }
677 }
678
679 /**
680  * @brief Toggle context menu item
681  */
682 static void NTAPI CheckContextMenu(BCMenu *pPopup, UINT uIDItem, BOOL bCheck)
683 {
684         if (bCheck)
685                 pPopup->CheckMenuItem(uIDItem, MF_CHECKED);
686         else
687                 pPopup->CheckMenuItem(uIDItem, MF_UNCHECKED);
688 }
689
690 /**
691  * @brief User right-clicked in listview rows
692  */
693 void CDirView::ListContextMenu(CPoint point, int /*i*/)
694 {
695         CDirDoc* pDoc = GetDocument();
696         BCMenu menu;
697         VERIFY(menu.LoadMenu(IDR_POPUP_DIRVIEW));
698         VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
699         theApp.TranslateMenu(menu.m_hMenu);
700
701         // 1st submenu of IDR_POPUP_DIRVIEW is for item popup
702         BCMenu *pPopup = static_cast<BCMenu*>(menu.GetSubMenu(0));
703         ASSERT(pPopup != nullptr);
704
705         int sel = GetFocusedItem();
706         if (sel == -1)
707                 sel = GetFirstSelectedInd();
708         if (sel == -1)
709                 return;
710         const DIFFITEM& di = GetDiffItem(sel);
711         if (GetDiffContext().m_bRecursive && di.diffcode.isDirectory())
712                 pPopup->RemoveMenu(ID_MERGE_COMPARE, MF_BYCOMMAND);
713         if (!di.diffcode.isDirectory())
714                 pPopup->RemoveMenu(ID_MERGE_COMPARE_IN_NEW_WINDOW, MF_BYCOMMAND);
715         if (pDoc->m_nDirs < 3)
716         {
717                 pPopup->RemoveMenu(ID_DIR_COPY_LEFT_TO_MIDDLE, MF_BYCOMMAND);
718                 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_LEFT, MF_BYCOMMAND);
719                 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_RIGHT, MF_BYCOMMAND);
720                 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_BROWSE, MF_BYCOMMAND);
721                 pPopup->RemoveMenu(ID_DIR_COPY_RIGHT_TO_MIDDLE, MF_BYCOMMAND);
722                 pPopup->RemoveMenu(ID_DIR_MOVE_MIDDLE_TO_BROWSE, MF_BYCOMMAND);
723                 pPopup->RemoveMenu(ID_DIR_DEL_MIDDLE, MF_BYCOMMAND);
724                 pPopup->RemoveMenu(ID_DIR_DEL_ALL, MF_BYCOMMAND);
725                 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE, MF_BYCOMMAND);
726
727                 for (int i = 0; i < pPopup->GetMenuItemCount(); ++i)
728                 {
729                         if (pPopup->GetMenuItemID(i) == ID_DIR_HIDE_FILENAMES)
730                                 pPopup->RemoveMenu(i + 3, MF_BYPOSITION);
731                 }
732
733                 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE_WITHEDITOR, MF_BYCOMMAND);
734                 pPopup->RemoveMenu(ID_DIR_OPEN_MIDDLE_WITH, MF_BYCOMMAND);
735                 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_MIDDLE, MF_BYCOMMAND);
736                 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_ALL, MF_BYCOMMAND);
737                 pPopup->RemoveMenu(ID_DIR_COPY_MIDDLE_TO_CLIPBOARD, MF_BYCOMMAND);
738                 pPopup->RemoveMenu(ID_DIR_COPY_ALL_TO_CLIPBOARD, MF_BYCOMMAND);
739                 pPopup->RemoveMenu(ID_DIR_ZIP_MIDDLE, MF_BYCOMMAND);
740                 pPopup->RemoveMenu(ID_DIR_ZIP_ALL, MF_BYCOMMAND);
741                 pPopup->RemoveMenu(ID_DIR_SHELL_CONTEXT_MENU_MIDDLE, MF_BYCOMMAND);
742                 pPopup->RemoveMenu(ID_MERGE_COMPARE_NONHORIZONTALLY, MF_BYCOMMAND);
743         }
744         else
745         {
746                 pPopup->RemoveMenu(ID_DIR_COPY_PATHNAMES_BOTH, MF_BYCOMMAND);
747                 pPopup->RemoveMenu(ID_DIR_COPY_BOTH_TO_CLIPBOARD, MF_BYCOMMAND);
748                 pPopup->RemoveMenu(ID_DIR_ZIP_BOTH, MF_BYCOMMAND);
749                 pPopup->RemoveMenu(ID_DIR_DEL_BOTH, MF_BYCOMMAND);
750                 pPopup->RemoveMenu(2, MF_BYPOSITION); // Compare Non-horizontally
751         }
752
753         CMenu menuPluginsHolder;
754         menuPluginsHolder.LoadMenu(IDR_POPUP_PLUGINS_SETTINGS);
755         theApp.TranslateMenu(menuPluginsHolder.m_hMenu);
756         String s = _("Plugin Settings");
757         pPopup->AppendMenu(MF_SEPARATOR);
758         pPopup->AppendMenu(MF_POPUP, static_cast<int>(reinterpret_cast<uintptr_t>(menuPluginsHolder.m_hMenu)), s.c_str());
759
760         CFrameWnd *pFrame = GetTopLevelFrame();
761         ASSERT(pFrame != nullptr);
762         pFrame->m_bAutoMenuEnable = FALSE;
763         // invoke context menu
764         // this will invoke all the OnUpdate methods to enable/disable the individual items
765         pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
766                         AfxGetMainWnd());
767
768         pFrame->m_bAutoMenuEnable = TRUE;
769 }
770
771 /**
772  * @brief User right-clicked on specified logical column
773  */
774 void CDirView::HeaderContextMenu(CPoint point, int /*i*/)
775 {
776         BCMenu menu;
777         VERIFY(menu.LoadMenu(IDR_POPUP_DIRVIEW));
778         VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
779         theApp.TranslateMenu(menu.m_hMenu);
780         // 2nd submenu of IDR_POPUP_DIRVIEW is for header popup
781         BCMenu* pPopup = static_cast<BCMenu *>(menu.GetSubMenu(1));
782         ASSERT(pPopup != nullptr);
783
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,
787                         AfxGetMainWnd());
788 }
789
790 /**
791  * @brief Gets Explorer's context menu for a group of selected files.
792  *
793  * @param [in] Side whether to get context menu for the files from the left or
794  *   right side.
795  * @retval true menu successfully retrieved.
796  * @retval falsea an error occurred while retrieving the menu.
797  */
798 bool CDirView::ListShellContextMenu(SIDE_TYPE stype)
799 {
800         CShellContextMenu* shellContextMenu;
801         switch (stype) {
802         case SIDE_MIDDLE:
803                 shellContextMenu = m_pShellContextMenuMiddle.get(); break;
804         case SIDE_RIGHT:
805                 shellContextMenu = m_pShellContextMenuRight.get(); break;
806         default:
807                 shellContextMenu = m_pShellContextMenuLeft.get(); break;
808         }
809         shellContextMenu->Initialize();
810         ApplyFolderNameAndFileName(SelBegin(), SelEnd(), stype, GetDiffContext(),
811                 [&](const String& path, const String& filename) { shellContextMenu->AddItem(path, filename); });
812         return shellContextMenu->RequeryShellContextMenu();
813 }
814
815 /**
816  * @brief User chose (main menu) Copy from right to left
817  */
818 void CDirView::OnDirCopy(UINT id)
819 {
820         bool to_right = (id == ID_L2R) ? true : false;
821         if (GetDocument()->m_nDirs < 3)
822         {
823                 if (to_right)
824                         DoDirAction(&DirActions::Copy<SIDE_LEFT, SIDE_RIGHT>, _("Copying files..."));
825                 else
826                         DoDirAction(&DirActions::Copy<SIDE_RIGHT, SIDE_LEFT>, _("Copying files..."));
827         }
828         else
829         {
830                 if (to_right)
831                 {
832                         switch (m_nActivePane)
833                         {
834                         case 0:
835                                 DoDirAction(&DirActions::Copy<SIDE_LEFT, SIDE_MIDDLE>, _("Copying files..."));
836                                 break;
837                         case 1:
838                         case 2:
839                                 DoDirAction(&DirActions::Copy<SIDE_MIDDLE, SIDE_RIGHT>, _("Copying files..."));
840                                 break;
841                         }
842                 }
843                 else
844                 {
845                         switch (m_nActivePane)
846                         {
847                         case 0:
848                         case 1:
849                                 DoDirAction(&DirActions::Copy<SIDE_MIDDLE, SIDE_LEFT>, _("Copying files..."));
850                                 break;
851                         case 2:
852                                 DoDirAction(&DirActions::Copy<SIDE_RIGHT, SIDE_MIDDLE>, _("Copying files..."));
853                                 break;
854                         }
855                 }
856         }
857 }
858
859 /// User chose (context men) Copy from right to left
860 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
861 void CDirView::OnCtxtDirCopy()
862 {
863         DoDirAction(&DirActions::Copy<srctype, dsttype>, _("Copying files..."));
864 }
865
866 /// User chose (context menu) Copy left to...
867 template<SIDE_TYPE stype>
868 void CDirView::OnCtxtDirCopyTo()
869 {
870         DoDirActionTo(stype, &DirActions::CopyTo<stype>, _("Copying files..."));
871 }
872
873 /// Update context menu Copy Right to Left item
874 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
875 void CDirView::OnUpdateCtxtDirCopy(CCmdUI* pCmdUI)
876 {
877         DoUpdateDirCopy<srctype, dsttype>(pCmdUI, eContext);
878 }
879
880 /// Update main menu Copy Right to Left item
881 void CDirView::OnUpdateDirCopy(CCmdUI* pCmdUI)
882 {
883         bool to_right = pCmdUI->m_nID == ID_L2R ? true : false;
884         if (GetDocument()->m_nDirs < 3)
885         {
886                 if (to_right)
887                         DoUpdateDirCopy<SIDE_LEFT, SIDE_RIGHT>(pCmdUI, eContext);
888                 else
889                         DoUpdateDirCopy<SIDE_RIGHT, SIDE_LEFT>(pCmdUI, eContext);
890         }
891         else
892         {
893                 if (to_right)
894                 {
895                         switch (m_nActivePane)
896                         {
897                         case 0:
898                                 DoUpdateDirCopy<SIDE_LEFT, SIDE_MIDDLE>(pCmdUI, eContext);
899                                 break;
900                         case 1:
901                         case 2:
902                                 DoUpdateDirCopy<SIDE_MIDDLE, SIDE_RIGHT>(pCmdUI, eContext);
903                                 break;
904                         }
905                 }
906                 else
907                 {
908                         switch (m_nActivePane)
909                         {
910                         case 0:
911                         case 1:
912                                 DoUpdateDirCopy<SIDE_MIDDLE, SIDE_LEFT>(pCmdUI, eContext);
913                                 break;
914                         case 2:
915                                 DoUpdateDirCopy<SIDE_RIGHT, SIDE_MIDDLE>(pCmdUI, eContext);
916                                 break;
917                         }
918                 }
919         }
920 }
921
922 void CDirView::DoDirAction(DirActions::method_type func, const String& status_message)
923 {
924         CWaitCursor waitstatus;
925
926         try {
927                 // First we build a list of desired actions
928                 FileActionScript actionScript;
929                 DirItemWithIndexIterator begin(m_pIList.get(), -1, true);
930                 DirItemWithIndexIterator end;
931                 FileActionScript *rsltScript;
932                 rsltScript = std::accumulate(begin, end, &actionScript, MakeDirActions(func));
933                 ASSERT(rsltScript == &actionScript);
934                 // Now we prompt, and execute actions
935                 ConfirmAndPerformActions(actionScript);
936         } catch (ContentsChangedException& e) {
937                 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
938         } catch (FileOperationException& e) {
939                 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
940         }
941 }
942
943 void CDirView::DoDirActionTo(SIDE_TYPE stype, DirActions::method_type func, const String& status_message)
944 {
945         String destPath;
946         String startPath(m_lastCopyFolder);
947         String selectfolder_title;
948
949         if (stype == SIDE_LEFT)
950                 selectfolder_title = _("Left side - select destination folder:");
951         else if (stype == SIDE_MIDDLE)
952                 selectfolder_title = _("Middle side - select destination folder:");
953         else if (stype == SIDE_RIGHT)
954                 selectfolder_title = _("Right side - select destination folder:");
955
956         if (!SelectFolder(destPath, startPath.c_str(), selectfolder_title))
957                 return;
958
959         m_lastCopyFolder = destPath;
960         CWaitCursor waitstatus;
961
962         try {
963                 // First we build a list of desired actions
964                 FileActionScript actionScript;
965                 actionScript.m_destBase = std::move(destPath);
966                 DirItemWithIndexIterator begin(m_pIList.get(), -1, true);
967                 DirItemWithIndexIterator end;
968                 FileActionScript *rsltScript;
969                 rsltScript = std::accumulate(begin, end, &actionScript, MakeDirActions(func));
970                 ASSERT(rsltScript == &actionScript);
971                 // Now we prompt, and execute actions
972                 ConfirmAndPerformActions(actionScript);
973         } catch (ContentsChangedException& e) {
974                 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
975         }
976 }
977
978 // Confirm with user, then perform the action list
979 void CDirView::ConfirmAndPerformActions(FileActionScript & actionList)
980 {
981         if (actionList.GetActionItemCount() == 0) // Not sure it is possible to get right-click menu without
982                 return;    // any selected items, but may as well be safe
983
984         ASSERT(actionList.GetActionItemCount()>0); // Or else the update handler got it wrong
985
986         // Set parent window so modality is correct and correct window gets focus
987         // after dialogs.
988         actionList.SetParentWindow(this->GetSafeHwnd());
989         
990         try {
991                 ConfirmActionList(GetDiffContext(), actionList);
992         } catch (ConfirmationNeededException& e) {
993                 ConfirmFolderCopyDlg dlg;
994                 dlg.m_caption = e.m_caption;
995                 dlg.m_question = e.m_question;
996                 dlg.m_fromText = e.m_fromText;
997                 dlg.m_toText = e.m_toText;
998                 dlg.m_fromPath = e.m_fromPath;
999                 dlg.m_toPath = e.m_toPath;
1000                 INT_PTR ans = dlg.DoModal();
1001                 if (ans != IDOK && ans != IDYES)
1002                         return;
1003         }
1004         PerformActionList(actionList);
1005 }
1006
1007 /**
1008  * @brief Perform an array of actions
1009  * @note There can be only COPY or DELETE actions, not both!
1010  */
1011 void CDirView::PerformActionList(FileActionScript & actionScript)
1012 {
1013         // Check option and enable putting deleted items to Recycle Bin
1014         if (GetOptionsMgr()->GetBool(OPT_USE_RECYCLE_BIN))
1015                 actionScript.UseRecycleBin(true);
1016         else
1017                 actionScript.UseRecycleBin(false);
1018
1019         actionScript.SetParentWindow(GetMainFrame()->GetSafeHwnd());
1020
1021         theApp.AddOperation();
1022         bool succeeded = actionScript.Run();
1023         if (succeeded)
1024                 UpdateAfterFileScript(actionScript);
1025         theApp.RemoveOperation();
1026         if (!succeeded && !actionScript.IsCanceled())
1027                 throw FileOperationException(_T("File operation failed"));
1028 }
1029
1030 /**
1031  * @brief Update results after running FileActionScript.
1032  * This functions is called after script is finished to update
1033  * results (including UI).
1034  * @param [in] actionlist Script that was run.
1035  */
1036 void CDirView::UpdateAfterFileScript(FileActionScript & actionList)
1037 {
1038         bool bItemsRemoved = false;
1039         int curSel = GetFirstSelectedInd();
1040         CDiffContext& ctxt = GetDiffContext();
1041         while (actionList.GetActionItemCount()>0)
1042         {
1043                 // Start handling from tail of list, so removing items
1044                 // doesn't invalidate our item indexes.
1045                 FileActionItem act = actionList.RemoveTailActionItem();
1046
1047                 // Update doc (difflist)
1048                 UPDATEITEM_TYPE updatetype = UpdateDiffAfterOperation(act, ctxt, GetDiffItem(act.context));
1049                 if (updatetype == UPDATEITEM_REMOVE)
1050                 {
1051                         DeleteItem(act.context, true);
1052                         bItemsRemoved = true;
1053                 }
1054                 else if (updatetype == UPDATEITEM_UPDATE)
1055                         UpdateDiffItemStatus(act.context);
1056         }
1057         
1058         // Make sure selection is at sensible place if all selected items
1059         // were removed.
1060         if (bItemsRemoved)
1061         {
1062                 UINT selected = GetSelectedCount();
1063                 if (selected == 0)
1064                 {
1065                         if (curSel < 1)
1066                                 ++curSel;
1067                         MoveFocus(0, curSel - 1, selected);
1068                 }
1069         }
1070 }
1071
1072 Counts CDirView::Count(DirActions::method_type2 func) const
1073 {
1074         return ::Count(SelBegin(), SelEnd(), MakeDirActions(func));
1075 }
1076
1077 /// Should Copy to Left be enabled or disabled ? (both main menu & context menu use this)
1078 template<SIDE_TYPE srctype, SIDE_TYPE dsttype>
1079 void CDirView::DoUpdateDirCopy(CCmdUI* pCmdUI, eMenuType menuType)
1080 {
1081         Counts counts = Count(&DirActions::IsItemCopyableOnTo<srctype, dsttype>);
1082         pCmdUI->Enable(counts.count > 0);
1083         if (menuType == eContext)
1084                 pCmdUI->SetText(FormatMenuItemString(srctype, dsttype, counts.count, counts.total).c_str());
1085 }
1086
1087 /**
1088  * @brief Update any resources necessary after a GUI language change
1089  */
1090 void CDirView::UpdateResources()
1091 {
1092         UpdateColumnNames();
1093         GetParentFrame()->UpdateResources();
1094 }
1095
1096 /**
1097  * @brief User just clicked a column, so perform sort
1098  */
1099 void CDirView::OnColumnClick(NMHDR *pNMHDR, LRESULT *pResult)
1100 {
1101         // set sort parameters and handle ascending/descending
1102         NM_LISTVIEW* pNMListView = (NM_LISTVIEW*) pNMHDR;
1103         int oldSortColumn = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
1104         int sortcol = m_pColItems->ColPhysToLog(pNMListView->iSubItem);
1105         if (sortcol == oldSortColumn)
1106         {
1107                 // Swap direction
1108                 bool bSortAscending = GetOptionsMgr()->GetBool(OPT_DIRVIEW_SORT_ASCENDING);
1109                 GetOptionsMgr()->SaveOption(OPT_DIRVIEW_SORT_ASCENDING, !bSortAscending);
1110         }
1111         else
1112         {
1113                 GetOptionsMgr()->SaveOption((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3, sortcol);
1114                 // most columns start off ascending, but not dates
1115                 bool bSortAscending = m_pColItems->IsDefaultSortAscending(sortcol);
1116                 GetOptionsMgr()->SaveOption(OPT_DIRVIEW_SORT_ASCENDING, bSortAscending);
1117         }
1118
1119         SortColumnsAppropriately();
1120         *pResult = 0;
1121 }
1122
1123 void CDirView::SortColumnsAppropriately()
1124 {
1125         int sortCol = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
1126         if (sortCol < 0 || sortCol >= m_pColItems->GetColCount())
1127                 return;
1128
1129         bool bSortAscending = GetOptionsMgr()->GetBool(OPT_DIRVIEW_SORT_ASCENDING);
1130         m_ctlSortHeader.SetSortImage(m_pColItems->ColLogToPhys(sortCol), bSortAscending);
1131         //sort using static CompareFunc comparison function
1132         CompareState cs(&GetDiffContext(), m_pColItems.get(), sortCol, bSortAscending, m_bTreeMode);
1133         std::stable_sort(m_listViewItems.begin(), m_listViewItems.end(), [&cs](const ListViewOwnerDataItem& a, const ListViewOwnerDataItem& b)
1134                 { return CompareState::CompareFunc(a.lParam, b.lParam, reinterpret_cast<LPARAM>(&cs)) < 0; });
1135
1136         m_firstDiffItem.reset();
1137         m_lastDiffItem.reset();
1138         
1139         m_pList->Invalidate();
1140 }
1141
1142 /// Do any last minute work as view closes
1143 void CDirView::OnDestroy()
1144 {
1145         DeleteAllDisplayItems();
1146
1147         {
1148                 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_ORDERS : OPT_DIRVIEW3_COLUMN_ORDERS;
1149                 GetOptionsMgr()->SaveOption(keyname, m_pColItems->SaveColumnOrders());
1150         }
1151         {
1152                 const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS;
1153                 GetOptionsMgr()->SaveOption(keyname,
1154                         m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1)));
1155         }
1156
1157         CListView::OnDestroy();
1158 }
1159
1160 /**
1161  * @brief Open selected item when user presses ENTER key.
1162  */
1163 void CDirView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
1164 {
1165         if (nChar == VK_RETURN)
1166         {
1167                 int sel = GetFocusedItem();
1168                 if (sel >= 0)
1169                 {
1170                         const DIFFITEM& di = GetDiffItem(sel);
1171                         if (m_bTreeMode && GetDiffContext().m_bRecursive && di.diffcode.isDirectory())
1172                         {
1173                                 if (di.customFlags & ViewCustomFlags::EXPANDED)
1174                                         CollapseSubdir(sel);
1175                                 else
1176                                         ExpandSubdir(sel);
1177                         }
1178                         else
1179                         {
1180                                 OpenSelection();
1181                         }
1182                 }
1183         }
1184         CListView::OnChar(nChar, nRepCnt, nFlags);
1185 }
1186
1187 /**
1188  * @brief Expand/collapse subfolder when "+/-" icon is clicked.
1189  */
1190 void CDirView::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
1191 {
1192         LPNMITEMACTIVATE pNM = (LPNMITEMACTIVATE)pNMHDR;
1193         LVHITTESTINFO lvhti;
1194         lvhti.pt = pNM->ptAction;
1195         m_pList->SubItemHitTest(&lvhti);
1196         if (lvhti.flags == LVHT_ONITEMSTATEICON)
1197         {
1198                 const DIFFITEM &di = GetDiffItem(pNM->iItem);
1199                 if (di.customFlags & ViewCustomFlags::EXPANDED)
1200                         CollapseSubdir(pNM->iItem);
1201                 else
1202                         ExpandSubdir(pNM->iItem);
1203         }
1204
1205         *pResult = 0;
1206 }
1207
1208 /**
1209  * @brief Collapse subfolder
1210  * @param [in] sel Folder item index in listview.
1211  */
1212 void CDirView::CollapseSubdir(int sel)
1213 {
1214         DIFFITEM& dip = this->GetDiffItem(sel);
1215         if (!m_bTreeMode || !(dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren())
1216                 return;
1217
1218         m_pList->SetRedraw(FALSE);      // Turn off updating (better performance)
1219
1220         dip.customFlags &= ~ViewCustomFlags::EXPANDED;
1221
1222         int count = m_pList->GetItemCount();
1223         for (int i = sel + 1; i < count; i++)
1224         {
1225                 const DIFFITEM& di = GetDiffItem(i);
1226                 if (!di.IsAncestor(&dip))
1227                         break;
1228                 m_listViewItems.erase(m_listViewItems.begin() + i);
1229                 m_pList->DeleteItem(i--);
1230                 count--;
1231         }
1232
1233         m_pList->SetRedraw(TRUE);       // Turn updating back on
1234 }
1235
1236 /**
1237  * @brief Expand subfolder
1238  * @param [in] sel Folder item index in listview.
1239  */
1240 void CDirView::ExpandSubdir(int sel, bool bRecursive)
1241 {
1242         DIFFITEM& dip = GetDiffItem(sel);
1243         if (!m_bTreeMode || (dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren())
1244                 return;
1245
1246         m_pList->SetRedraw(FALSE);      // Turn off updating (better performance)
1247
1248         const int top = m_pList->GetTopIndex();
1249         const size_t num = m_listViewItems.size();
1250
1251         CDiffContext &ctxt = GetDiffContext();
1252         dip.customFlags |= ViewCustomFlags::EXPANDED;
1253         if (bRecursive)
1254                 ExpandSubdirs(ctxt, dip);
1255
1256         DIFFITEM *diffpos = ctxt.GetFirstChildDiffPosition(GetItemKey(sel));
1257         UINT indext = sel + 1;
1258         int alldiffs;
1259         RedisplayChildren(diffpos, dip.GetDepth() + 1, indext, alldiffs);
1260
1261         for (size_t i = 0; i < m_listViewItems.size() - num; ++i)
1262                 m_pList->InsertItem(sel + 1, nullptr);
1263
1264         SortColumnsAppropriately();
1265
1266         m_pList->SetRedraw(TRUE);       // Turn updating back on
1267         m_pList->SetItemCount(static_cast<int>(m_listViewItems.size()));
1268         m_pList->EnsureVisible(top, TRUE);
1269         m_pList->Invalidate();
1270 }
1271
1272 /**
1273  * @brief Open parent folder if possible.
1274  */
1275 void CDirView::OpenParentDirectory(CDirDoc *pDocOpen)
1276 {
1277         CDirDoc *pDoc = GetDocument();
1278         PathContext pathsParent;
1279         switch (CheckAllowUpwardDirectory(GetDiffContext(), pDoc->m_pTempPathContext, pathsParent))
1280         {
1281         case AllowUpwardDirectory::ParentIsTempPath:
1282                 pDoc->m_pTempPathContext = pDoc->m_pTempPathContext->DeleteHead();
1283                 [[fallthrough]];
1284         case AllowUpwardDirectory::ParentIsRegularPath: 
1285                 DWORD 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);
1289                 [[fallthrough]];
1290         case AllowUpwardDirectory::No:
1291                 break;
1292         default:
1293                 LangMessageBox(IDS_INVALID_DIRECTORY, MB_ICONSTOP);
1294                 break;
1295         }
1296 }
1297
1298 /**
1299  * @brief Get one or two selected items
1300  *
1301  * Returns false if 0 or more than 3 items selecte
1302  */
1303 bool CDirView::GetSelectedItems(int * sel1, int * sel2, int * sel3)
1304 {
1305         *sel2 = -1;
1306         *sel3 = -1;
1307         *sel1 = m_pList->GetNextItem(-1, LVNI_SELECTED);
1308         if (*sel1 == -1)
1309                 return false;
1310         *sel2 = m_pList->GetNextItem(*sel1, LVNI_SELECTED);
1311         if (*sel2 == -1)
1312                 return true;
1313         *sel3 = m_pList->GetNextItem(*sel2, LVNI_SELECTED);
1314         if (*sel3 == -1)
1315                 return true;
1316         int extra = m_pList->GetNextItem(*sel3, LVNI_SELECTED);
1317         return (extra == -1);
1318 }
1319
1320 /**
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.
1325  */
1326 void CDirView::OpenSpecialItems(CDirDoc *pDoc, DIFFITEM *pos1, DIFFITEM *pos2, DIFFITEM *pos3)
1327 {
1328         if (pos2==nullptr && pos3==nullptr)
1329         {
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);
1335         }
1336         else
1337         {
1338                 // Parent directory & something else selected
1339                 // Not valid action
1340         }
1341 }
1342
1343 /**
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.
1349  */
1350 static bool CreateFoldersPair(const PathContext& paths)
1351 {
1352         bool created = false;
1353         for (const auto& path : paths)
1354         {
1355                 if (!paths::DoesPathExist(path))
1356                 {
1357                         String message =
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?"),
1360                                         path);
1361                         int res = AfxMessageBox(message.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN, IDS_CREATE_PAIR_FOLDER);
1362                         if (res == IDYES)
1363                                 created = paths::CreateIfNeeded(path);
1364                 }
1365         }
1366         return created;
1367 }
1368
1369 void CDirView::Open(CDirDoc *pDoc, const PathContext& paths, DWORD dwFlags[3], FileTextEncoding encoding[3], PackingInfo * infoUnpacker)
1370 {
1371         bool isdir = false;
1372         for (auto path : paths)
1373         {
1374                 if (paths::DoesPathExist(path) == paths::IS_EXISTING_DIR)
1375                         isdir = true;
1376         }
1377         if (isdir)
1378         {
1379                 // Open subfolders
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);
1383         }
1384         else if (HasZipSupport() && std::count_if(paths.begin(), paths.end(), ArchiveGuessFormat) == paths.GetSize())
1385         {
1386                 // Open archives, not adding paths to MRU
1387                 GetMainFrame()->DoFileOrFolderOpen(&paths, dwFlags, nullptr, _T(""), GetDiffContext().m_bRecursive, nullptr, infoUnpacker, nullptr);
1388         }
1389         else
1390         {
1391                 // Regular file case
1392
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
1397
1398                 // Open identical and different files
1399                 PathContext filteredPaths;
1400                 FileLocation fileloc[3];
1401                 String strDesc[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)
1404                 {
1405                         if (paths::DoesPathExist(paths[i]) == paths::DOES_NOT_EXIST)
1406                         {
1407                                 strDesc[i] = sUntitled[i];
1408                                 filteredPaths.SetPath(i, _T("NUL"), false);
1409                         }
1410                         else
1411                         {
1412                                 fileloc[i].setPath(paths[i]);
1413                                 fileloc[i].encoding = encoding[i];
1414                                 filteredPaths.SetPath(i, paths[i], false);
1415                         }
1416                 }
1417
1418                 if (!infoUnpacker)
1419                 {
1420                         PrediffingInfo* infoPrediffer = nullptr;
1421                         String filteredFilenames = CDiffContext::GetFilteredFilenames(filteredPaths);
1422                         GetDiffContext().FetchPluginInfos(filteredFilenames, &infoUnpacker, &infoPrediffer);
1423                 }
1424
1425                 GetMainFrame()->ShowAutoMergeDoc(0, GetDocument(), paths.GetSize(), fileloc,
1426                         dwFlags, strDesc, _T(""), infoUnpacker);
1427         }
1428 }
1429
1430 /**
1431  * @brief Open selected files or directories.
1432  *
1433  * Opens selected files to file compare. If comparing
1434  * directories non-recursively, then subfolders and parent
1435  * folder are opened too.
1436  *
1437  * This handles the case that one item is selected
1438  * and the case that two items are selected (one on each side)
1439  */
1440 void CDirView::OpenSelection(CDirDoc *pDoc, SELECTIONTYPE selectionType /*= SELECTIONTYPE_NORMAL*/, PackingInfo * infoUnpacker /*= nullptr*/, bool openableForDir /*= true*/)
1441 {
1442         Merge7zFormatMergePluginScope scope(infoUnpacker);
1443         const CDiffContext& ctxt = GetDiffContext();
1444
1445         // First, figure out what was selected (store into pos1 & pos2)
1446         DIFFITEM *pos1 = nullptr, *pos2 = nullptr, *pos3 = nullptr;
1447         int sel1 = -1, sel2 = -1, sel3 = -1;
1448         if (!GetSelectedItems(&sel1, &sel2, &sel3))
1449         {
1450                 // Must have 1 or 2 or 3 items selected
1451                 // Not valid action
1452                 return;
1453         }
1454
1455         pos1 = GetItemKey(sel1);
1456         ASSERT(pos1 != nullptr);
1457         if (sel2 != -1)
1458         {
1459                 pos2 = GetItemKey(sel2);
1460                 ASSERT(pos2 != nullptr);
1461                 if (sel3 != -1)
1462                         pos3 = GetItemKey(sel3);
1463         }
1464
1465         // Now handle the various cases of what was selected
1466
1467         if (IsDiffItemSpecial(pos1))
1468         {
1469                 OpenSpecialItems(pDoc, pos1, pos2, pos3);
1470                 return;
1471         }
1472
1473         // Common variables which both code paths below are responsible for setting
1474         PathContext paths;
1475         const DIFFITEM *pdi[3] = {0}; // left & right items (di1==di2 if single selection)
1476         bool isdir = false; // set if we're comparing directories
1477         int nPane[3];
1478         FileTextEncoding encoding[3];
1479         String errmsg;
1480         bool success;
1481         if (pos2 && !pos3)
1482                 success = GetOpenTwoItems(ctxt, selectionType, pos1, pos2, pdi,
1483                                 paths, sel1, sel2, isdir, nPane, encoding, errmsg, openableForDir);
1484         else if (pos2 && pos3)
1485                 success = GetOpenThreeItems(ctxt, pos1, pos2, pos3, pdi,
1486                                 paths, sel1, sel2, sel3, isdir, nPane, encoding, errmsg, openableForDir);
1487         else
1488         {
1489                 // Only one item selected, so perform diff on its sides
1490                 success = GetOpenOneItem(ctxt, pos1, pdi, 
1491                                 paths, sel1, isdir, nPane, encoding, errmsg, openableForDir);
1492                 if (isdir)
1493                         CreateFoldersPair(paths);
1494         }
1495         if (!success)
1496         {
1497                 if (!errmsg.empty())
1498                         AfxMessageBox(errmsg.c_str(), MB_ICONSTOP);
1499                 return;
1500         }
1501
1502         // Now pathLeft, pathRight, di1, di2, and isdir are all set
1503         // We have two items to compare, no matter whether same or different underlying DirView item
1504
1505         DWORD dwFlags[3];
1506         for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
1507                 dwFlags[nIndex] = FFILEOPEN_NOMRU | (GetDocument()->GetReadOnly(nPane[nIndex]) ? FFILEOPEN_READONLY : 0);
1508
1509         Open(pDoc, paths, dwFlags, encoding, infoUnpacker);
1510 }
1511
1512 void CDirView::OpenSelection(SELECTIONTYPE selectionType /*= SELECTIONTYPE_NORMAL*/, PackingInfo* infoUnpacker /*= nullptr*/, bool openableForDir /*= true*/)
1513 {
1514         OpenSelection(GetDocument(), selectionType, infoUnpacker, openableForDir);
1515 }
1516
1517 void CDirView::OpenSelectionAs(UINT id)
1518 {
1519         CDirDoc * pDoc = GetDocument();
1520         const CDiffContext& ctxt = GetDiffContext();
1521
1522         // First, figure out what was selected (store into pos1 & pos2 & pos3)
1523         DIFFITEM *pos1 = nullptr, *pos2 = nullptr, *pos3 = nullptr;
1524         int sel1 = -1, sel2 = -1, sel3 = -1;
1525         if (!GetSelectedItems(&sel1, &sel2, &sel3))
1526         {
1527                 // Must have 1 or 2 or 3 items selected
1528                 // Not valid action
1529                 return;
1530         }
1531
1532         pos1 = GetItemKey(sel1);
1533         ASSERT(pos1);
1534         if (sel2 != -1)
1535         {
1536                 pos2 = GetItemKey(sel2);
1537                 ASSERT(pos2 != nullptr);
1538                 if (sel3 != -1)
1539                         pos3 = GetItemKey(sel3);
1540         }
1541
1542         // Now handle the various cases of what was selected
1543
1544         if (IsDiffItemSpecial(pos1))
1545         {
1546                 ASSERT(false);
1547                 return;
1548         }
1549
1550         // Common variables which both code paths below are responsible for setting
1551         PathContext paths;
1552         const DIFFITEM *pdi[3]; // left & right items (di1==di2 if single selection)
1553         bool isdir = false; // set if we're comparing directories
1554         int nPane[3];
1555         FileTextEncoding encoding[3];
1556         String errmsg;
1557         bool success;
1558         if (pos2 && !pos3)
1559                 success = GetOpenTwoItems(ctxt, SELECTIONTYPE_NORMAL, pos1, pos2, pdi,
1560                                 paths, sel1, sel2, isdir, nPane, encoding, errmsg, false);
1561         else if (pos2 && pos3)
1562                 success = GetOpenThreeItems(ctxt, pos1, pos2, pos3, pdi,
1563                                 paths, sel1, sel2, sel3, isdir, nPane, encoding, errmsg, false);
1564         else
1565         {
1566                 // Only one item selected, so perform diff on its sides
1567                 success = GetOpenOneItem(ctxt, pos1, pdi,
1568                                 paths, sel1, isdir, nPane, encoding, errmsg, false);
1569         }
1570         if (!success)
1571         {
1572                 if (!errmsg.empty())
1573                         AfxMessageBox(errmsg.c_str(), MB_ICONSTOP);
1574                 return;
1575         }
1576
1577         // Open identical and different files
1578         const String sUntitled[] = { _("Untitled left"), paths.GetSize() < 3 ? _("Untitled right") : _("Untitled middle"), _("Untitled right") };
1579         DWORD dwFlags[3] = { 0 };
1580         String strDesc[3];
1581         PathContext filteredPaths;
1582         FileLocation fileloc[3];
1583         for (int pane = 0; pane < paths.GetSize(); pane++)
1584         {
1585                 if (paths::DoesPathExist(paths[pane]) == paths::DOES_NOT_EXIST)
1586                 {
1587                         strDesc[pane] = sUntitled[pane];
1588                         filteredPaths.SetPath(pane, _T("NUL"), false);
1589                 }
1590                 else
1591                 {
1592                         fileloc[pane].setPath(paths[pane]);
1593                         fileloc[pane].encoding = encoding[pane];
1594                         filteredPaths.SetPath(pane, paths[pane]);
1595                 }
1596                 dwFlags[pane] |= FFILEOPEN_NOMRU | (pDoc->GetReadOnly(nPane[pane]) ? FFILEOPEN_READONLY : 0);
1597         }
1598         if (ID_UNPACKERS_FIRST <= id && id <= ID_UNPACKERS_LAST)
1599         {
1600                 PackingInfo infoUnpacker(
1601                                 CMainFrame::GetPluginPipelineByMenuId(id, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
1602                 GetMainFrame()->DoFileOrFolderOpen(&paths, dwFlags, strDesc, _T(""),
1603                         ctxt.m_bRecursive, nullptr, &infoUnpacker, nullptr, 0);
1604         }
1605         else
1606         {
1607                 PackingInfo* infoUnpacker = nullptr;
1608                 PrediffingInfo* infoPrediffer = nullptr;
1609                 GetDiffContext().FetchPluginInfos(CDiffContext::GetFilteredFilenames(filteredPaths), &infoUnpacker, &infoPrediffer);
1610                 GetMainFrame()->ShowMergeDoc(id, pDoc, paths.GetSize(), fileloc, dwFlags, strDesc, _T(""), infoUnpacker);
1611         }
1612 }
1613
1614 /// User chose (context menu) delete left
1615 template<SIDE_TYPE stype>
1616 void CDirView::OnCtxtDirDel()
1617 {
1618         DoDirAction(&DirActions::DeleteOn<stype>, _("Deleting files..."));
1619 }
1620
1621 /// User chose (context menu) delete both
1622 void CDirView::OnCtxtDirDelBoth()
1623 {
1624         DoDirAction(&DirActions::DeleteOnBoth, _("Deleting files..."));
1625 }
1626
1627 /// Enable/disable Delete Left menu choice on context menu
1628 template<SIDE_TYPE stype>
1629 void CDirView::OnUpdateCtxtDirDel(CCmdUI* pCmdUI)
1630 {
1631         Counts counts = Count(&DirActions::IsItemDeletableOn<stype>);
1632         pCmdUI->Enable(counts.count > 0);
1633         pCmdUI->SetText(FormatMenuItemString(stype, counts.count, counts.total).c_str());
1634 }
1635
1636 /// Enable/disable Delete Both menu choice on context menu
1637 void CDirView::OnUpdateCtxtDirDelBoth(CCmdUI* pCmdUI)
1638 {
1639         Counts counts = Count(&DirActions::IsItemDeletableOnBoth);
1640         pCmdUI->Enable(counts.count > 0);
1641         pCmdUI->SetText(FormatMenuItemStringAll(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1642 }
1643
1644 /**
1645  * @brief Update "Copy | Right to..." item
1646  */
1647 template<SIDE_TYPE stype>
1648 void CDirView::OnUpdateCtxtDirCopyTo(CCmdUI* pCmdUI)
1649 {
1650         Counts counts = Count(&DirActions::IsItemCopyableToOn<stype>);
1651         pCmdUI->Enable(counts.count > 0);
1652         pCmdUI->SetText(FormatMenuItemStringTo(stype, counts.count, counts.total).c_str());
1653 }
1654
1655 void CDirView::OnUpdateCtxtDirCopyBothTo(CCmdUI* pCmdUI)
1656 {
1657         Counts counts = Count(&DirActions::IsItemCopyableBothToOn);
1658         pCmdUI->Enable(counts.count > 0);
1659         pCmdUI->SetText(FormatMenuItemStringAllTo(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1660 }
1661
1662 void CDirView::OnUpdateCtxtDirCopyBothDiffsOnlyTo(CCmdUI* pCmdUI)
1663 {
1664         Counts counts = Count(&DirActions::IsItemNavigableDiff);
1665         pCmdUI->Enable(counts.count > 0);
1666         pCmdUI->SetText(FormatMenuItemStringDifferencesTo(counts.count, counts.total).c_str());
1667 }
1668         
1669 /**
1670  * @brief Update "Copy | Left/Right/Both " item
1671  */
1672 template<SIDE_TYPE stype>
1673 void CDirView::OnUpdateCtxtDirCopy2(CCmdUI* pCmdUI)
1674 {
1675         Counts counts = Count(&DirActions::IsItemCopyableToOn<stype>);
1676         pCmdUI->Enable(counts.count > 0);
1677         pCmdUI->SetText(FormatMenuItemString(stype, counts.count, counts.total).c_str());
1678 }
1679
1680 void CDirView::OnUpdateCtxtDirCopyBoth2(CCmdUI* pCmdUI)
1681 {
1682         Counts counts = Count(&DirActions::IsItemCopyableBothToOn);
1683         pCmdUI->Enable(counts.count > 0);
1684         pCmdUI->SetText(FormatMenuItemStringAll(GetDocument()->m_nDirs, counts.count, counts.total).c_str());
1685 }
1686
1687 /**
1688  * @brief Get keydata associated with item in given index.
1689  * @param [in] idx Item's index to list in UI.
1690  * @return Key for item in given index.
1691  */
1692 DIFFITEM *CDirView::GetItemKey(int idx) const
1693 {
1694         ASSERT(idx >= 0 && idx < static_cast<int>(m_listViewItems.size()));
1695         return reinterpret_cast<DIFFITEM*>(m_listViewItems[idx].lParam);
1696 }
1697
1698 // SetItemKey & GetItemKey encapsulate how the display list items
1699 // are mapped to DiffItems, which in turn are DiffContext keys to the actual DIFFITEM data
1700
1701 /**
1702  * @brief Get DIFFITEM data for item.
1703  * This function returns DIFFITEM data for item in given index in GUI.
1704  * @param [in] sel Item's index in folder compare GUI list.
1705  * @return DIFFITEM for item.
1706  */
1707 const DIFFITEM &CDirView::GetDiffItem(int sel) const
1708 {
1709         CDirView * pDirView = const_cast<CDirView *>(this);
1710         return pDirView->GetDiffItem(sel);
1711 }
1712
1713 /**
1714  * Given index in list control, get modifiable reference to its DIFFITEM data
1715  */
1716 DIFFITEM &CDirView::GetDiffItem(int sel)
1717 {
1718         DIFFITEM *diffpos = GetItemKey(sel);
1719
1720         // If it is special item, return empty DIFFITEM
1721         if (IsDiffItemSpecial(diffpos))
1722         {
1723                 return *DIFFITEM::GetEmptyItem();
1724         }
1725         return GetDiffContext().GetDiffRefAt(diffpos);
1726 }
1727
1728 void CDirView::DeleteItem(int sel, bool removeDIFFITEM)
1729 {
1730         DIFFITEM *diffpos = GetItemKey(sel);
1731         if (IsDiffItemSpecial(diffpos))
1732                 return;
1733         if (m_bTreeMode)
1734         {
1735                 CollapseSubdir(sel);
1736                 m_listViewItems.erase(m_listViewItems.begin() + sel);
1737                 m_pList->DeleteItem(sel);
1738         }
1739         else if (GetDiffContext().m_bRecursive || diffpos->HasChildren())
1740         {
1741                 DirItemIterator it;
1742                 for (it = RevBegin(); it != RevEnd(); )
1743                 {
1744                         DIFFITEM& di = *it;
1745                         int cursel = it.m_sel;
1746                         ++it;
1747                         if (di.IsAncestor(diffpos) || diffpos == &di)
1748                         {
1749                                 m_listViewItems.erase(m_listViewItems.begin() + cursel);
1750                                 m_pList->DeleteItem(cursel);
1751                         }
1752                 }
1753         }
1754         else
1755         {
1756                 m_listViewItems.erase(m_listViewItems.begin() + sel);
1757                 m_pList->DeleteItem(sel);
1758         }
1759         if (removeDIFFITEM)
1760         {
1761                 if (diffpos->HasChildren())
1762                         diffpos->RemoveChildren();
1763                 diffpos->DelinkFromSiblings();
1764                 delete diffpos;
1765         }
1766
1767         m_firstDiffItem.reset();
1768         m_lastDiffItem.reset();
1769 }
1770
1771 void CDirView::DeleteAllDisplayItems()
1772 {
1773         // item data are just positions (diffposes)
1774         // that is, they contain no memory needing to be freed
1775         m_pList->DeleteAllItems();
1776         m_listViewItems.clear();
1777
1778         m_firstDiffItem.reset();
1779         m_lastDiffItem.reset();
1780 }
1781
1782 /**
1783  * @brief Given key, get index of item which has it stored.
1784  * This function searches from list in UI.
1785  */
1786 int CDirView::GetItemIndex(DIFFITEM *key)
1787 {
1788         for (size_t i = 0; i < m_listViewItems.size(); ++i)
1789         {
1790                 if (m_listViewItems[i].lParam == reinterpret_cast<LPARAM>(key))
1791                         return static_cast<int>(i);
1792         }
1793         return 0;
1794 }
1795
1796 /**
1797  * @brief Get the file names on both sides for specified item.
1798  * @note Return empty strings if item is special item.
1799  */
1800 void CDirView::GetItemFileNames(int sel, String& strLeft, String& strRight) const
1801 {
1802         DIFFITEM *diffpos = GetItemKey(sel);
1803         if (IsDiffItemSpecial(diffpos))
1804         {
1805                 strLeft.erase();
1806                 strRight.erase();
1807         }
1808         else
1809         {
1810                 const CDiffContext& ctxt = GetDiffContext();
1811                 ::GetItemFileNames(ctxt, ctxt.GetDiffAt(diffpos), strLeft, strRight);
1812         }
1813 }
1814
1815 /**
1816  * @brief Get the file names on both sides for specified item.
1817  * @note Return empty strings if item is special item.
1818  */
1819 void CDirView::GetItemFileNames(int sel, PathContext * paths) const
1820 {
1821         DIFFITEM *diffpos = GetItemKey(sel);
1822         if (IsDiffItemSpecial(diffpos))
1823         {
1824                 for (int nIndex = 0; nIndex < GetDocument()->m_nDirs; nIndex++)
1825                         paths->SetPath(nIndex, _T(""));
1826         }
1827         else
1828         {
1829                 const CDiffContext& ctxt = GetDiffContext();
1830                 *paths = ::GetItemFileNames(ctxt, ctxt.GetDiffAt(diffpos));
1831         }
1832 }
1833
1834 /**
1835  * @brief Open selected file with registered application.
1836  * Uses shell file associations to open file with registered
1837  * application. We first try to use "Edit" action which should
1838  * open file to editor, since we are more interested editing
1839  * files than running them (scripts).
1840  * @param [in] stype Side of file to open.
1841  */
1842 void CDirView::DoOpen(SIDE_TYPE stype)
1843 {
1844         int sel = GetSingleSelectedItem();
1845         if (sel == -1) return;
1846         DirItemIterator dirBegin = SelBegin();
1847         String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1848         if (file.empty()) return;
1849         shell::Edit(file.c_str());
1850 }
1851
1852 /// Open with dialog for file on selected side
1853 void CDirView::DoOpenWith(SIDE_TYPE stype)
1854 {
1855         int sel = GetSingleSelectedItem();
1856         if (sel == -1) return;
1857         DirItemIterator dirBegin = SelBegin();
1858         String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1859         if (file.empty()) return;
1860         shell::OpenWith(file.c_str());
1861 }
1862
1863 /// Open selected file  on specified side to external editor
1864 void CDirView::DoOpenWithEditor(SIDE_TYPE stype)
1865 {
1866         int sel = GetSingleSelectedItem();
1867         if (sel == -1) return;
1868         DirItemIterator dirBegin = SelBegin();
1869         String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1870         if (file.empty()) return;
1871
1872         CMergeApp::OpenFileToExternalEditor(file);
1873 }
1874
1875 void CDirView::DoOpenParentFolder(SIDE_TYPE stype)
1876 {
1877         int sel = GetSingleSelectedItem();
1878         if (sel == -1) return;
1879         DirItemIterator dirBegin = SelBegin();
1880         String file = GetSelectedFileName(dirBegin, stype, GetDiffContext());
1881         if (file.empty()) return;
1882         shell::OpenParentFolder(file.c_str());
1883 }
1884
1885 /// User chose (context menu) open left
1886 template<SIDE_TYPE stype>
1887 void CDirView::OnCtxtDirOpen()
1888 {
1889         DoOpen(stype);
1890 }
1891
1892 /// User chose (context menu) open left with
1893 template<SIDE_TYPE stype>
1894 void CDirView::OnCtxtDirOpenWith()
1895 {
1896         DoOpenWith(stype);
1897 }
1898
1899 /// User chose (context menu) open left with editor
1900 template<SIDE_TYPE stype>
1901 void CDirView::OnCtxtDirOpenWithEditor()
1902 {
1903         DoOpenWithEditor(stype);
1904 }
1905
1906 /// User chose (context menu) open left parent folder
1907 template<SIDE_TYPE stype>
1908 void CDirView::OnCtxtDirOpenParentFolder()
1909 {
1910         DoOpenParentFolder(stype);
1911 }
1912
1913 /// Update context menuitem "Open left | with editor"
1914 template<SIDE_TYPE stype>
1915 void CDirView::OnUpdateCtxtDirOpenWithEditor(CCmdUI* pCmdUI)
1916 {
1917         Counts counts = Count(&DirActions::IsItemOpenableOnWith<stype>);
1918         pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1919 }
1920
1921 // return selected item index, or -1 if none or multiple
1922 int CDirView::GetSingleSelectedItem() const
1923 {
1924         int sel = -1, sel2 = -1;
1925         sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
1926         if (sel == -1) return -1;
1927         sel2 = m_pList->GetNextItem(sel, LVNI_SELECTED);
1928         if (sel2 != -1) return -1;
1929         return sel;
1930 }
1931 // Enable/disable Open Left menu choice on context menu
1932 template<SIDE_TYPE stype>
1933 void CDirView::OnUpdateCtxtDirOpen(CCmdUI* pCmdUI)
1934 {
1935         Counts counts = Count(&DirActions::IsItemOpenableOn<stype>);
1936         pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1937 }
1938
1939 // Enable/disable Open Left With menu choice on context menu
1940 template<SIDE_TYPE stype>
1941 void CDirView::OnUpdateCtxtDirOpenWith(CCmdUI* pCmdUI)
1942 {
1943         Counts counts = Count(&DirActions::IsItemOpenableOnWith<stype>);
1944         pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1945 }
1946
1947 // Enable/disable Open Parent Folder menu choice on context menu
1948 template<SIDE_TYPE stype>
1949 void CDirView::OnUpdateCtxtDirOpenParentFolder(CCmdUI* pCmdUI)
1950 {
1951         Counts counts = Count(&DirActions::IsParentFolderOpenable<stype>);
1952         pCmdUI->Enable(counts.count > 0 && counts.total == 1);
1953 }
1954
1955 // Used for Open
1956 void CDirView::DoUpdateOpen(SELECTIONTYPE selectionType, CCmdUI* pCmdUI, bool openableForDir /*= true*/)
1957 {
1958         int sel1 = -1, sel2 = -1, sel3 = -1;
1959         if (!GetSelectedItems(&sel1, &sel2, &sel3))
1960         {
1961                 // 0 items or more than 2 items seleted
1962                 pCmdUI->Enable(FALSE);
1963                 return;
1964         }
1965         if (sel2 == -1)
1966         {
1967                 // One item selected
1968                 if (selectionType != SELECTIONTYPE_NORMAL)
1969                 {
1970                         pCmdUI->Enable(FALSE);
1971                         return;
1972                 }
1973                 if (!openableForDir)
1974                 {
1975                         const DIFFITEM& di1 = GetDiffItem(sel1);
1976                         if (di1.diffcode.isDirectory() || IsDiffItemSpecial(GetItemKey(sel1)))
1977                         {
1978                                 pCmdUI->Enable(FALSE);
1979                                 return;
1980                         }
1981                 }
1982         }
1983         else if (sel3 == -1)
1984         {
1985                 // Two items selected
1986                 const DIFFITEM& di1 = GetDiffItem(sel1);
1987                 const DIFFITEM& di2 = GetDiffItem(sel2);
1988                 if (!AreItemsOpenable(GetDiffContext(), selectionType, di1, di2, openableForDir))
1989                 {
1990                         pCmdUI->Enable(FALSE);
1991                         return;
1992                 }
1993         }
1994         else
1995         {
1996                 // Three items selected
1997                 const DIFFITEM& di1 = GetDiffItem(sel1);
1998                 const DIFFITEM& di2 = GetDiffItem(sel2);
1999                 const DIFFITEM& di3 = GetDiffItem(sel3);
2000                 if (selectionType != SELECTIONTYPE_NORMAL || !::AreItemsOpenable(GetDiffContext(), di1, di2, di3, openableForDir))
2001                 {
2002                         pCmdUI->Enable(FALSE);
2003                         return;
2004                 }
2005         }
2006         pCmdUI->Enable(TRUE);
2007 }
2008
2009 /**
2010  * @brief Return count of selected items in folder compare.
2011  */
2012 UINT CDirView::GetSelectedCount() const
2013 {
2014         return m_pList->GetSelectedCount();
2015 }
2016
2017 /**
2018  * @brief Return index of first selected item in folder compare.
2019  */
2020 int CDirView::GetFirstSelectedInd()
2021 {
2022         return m_pList->GetNextItem(-1, LVNI_SELECTED);
2023 }
2024
2025 // Go to first diff
2026 // If none or one item selected select found item
2027 // This is used for scrolling to first diff too
2028 void CDirView::OnFirstdiff()
2029 {
2030         DirItemIterator it =
2031                 std::find_if(Begin(), End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2032         if (it != End())
2033                 MoveFocus(GetFirstSelectedInd(), it.m_sel, GetSelectedCount());
2034 }
2035
2036 void CDirView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
2037 {
2038         pCmdUI->Enable(GetFirstDifferentItem() > -1);
2039 }
2040
2041 // Go to last diff
2042 // If none or one item selected select found item
2043 void CDirView::OnLastdiff()
2044 {
2045         DirItemIterator it =
2046                 std::find_if(RevBegin(), RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2047         if (it != RevEnd())
2048                 MoveFocus(GetFirstSelectedInd(), it.m_sel, GetSelectedCount());
2049 }
2050
2051 void CDirView::OnUpdateLastdiff(CCmdUI* pCmdUI)
2052 {
2053         pCmdUI->Enable(GetFirstDifferentItem() > -1);
2054 }
2055
2056 bool CDirView::HasNextDiff()
2057 {
2058         int lastDiff = GetLastDifferentItem();
2059
2060         // Check if different files were found and
2061         // there is different item after focused item
2062         return (lastDiff > -1) && (GetFocusedItem() < lastDiff);
2063 }
2064
2065 bool CDirView::HasPrevDiff()
2066 {
2067         int firstDiff = GetFirstDifferentItem();
2068
2069         // Check if different files were found and
2070         // there is different item before focused item
2071         return (firstDiff > -1) && (firstDiff < GetFocusedItem());
2072 }
2073
2074 void CDirView::MoveToNextDiff()
2075 {
2076         int currentInd = GetFocusedItem();
2077         DirItemIterator begin(m_pIList.get(), currentInd + 1);
2078         DirItemIterator it =
2079                 std::find_if(begin, End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2080         if (it != End())
2081                 MoveFocus(currentInd, it.m_sel, GetSelectedCount());
2082 }
2083
2084 void CDirView::MoveToPrevDiff()
2085 {
2086         int currentInd = GetFocusedItem();
2087         if (currentInd <= 0)
2088                 return;
2089         DirItemIterator begin(m_pIList.get(), currentInd - 1, false, true);
2090         DirItemIterator it =
2091                 std::find_if(begin, RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2092         if (it != RevEnd())
2093                 MoveFocus(currentInd, it.m_sel, GetSelectedCount());
2094 }
2095
2096 void CDirView::OpenNextDiff()
2097 {
2098         MoveToNextDiff();
2099         int currentInd = GetFocusedItem();
2100         const DIFFITEM& dip = GetDiffItem(currentInd);
2101         if (!dip.diffcode.isDirectory())
2102         {
2103                 OpenSelection();
2104         }
2105         else
2106         {
2107                 GetParentFrame()->ActivateFrame();
2108         }
2109 }
2110
2111 void CDirView::OpenPrevDiff()
2112 {
2113         MoveToPrevDiff();
2114         int currentInd = GetFocusedItem();
2115         const DIFFITEM& dip = GetDiffItem(currentInd);
2116         if (!dip.diffcode.isDirectory())
2117         {
2118                 OpenSelection();
2119         }
2120         else
2121         {
2122                 GetParentFrame()->ActivateFrame();
2123         }
2124 }
2125
2126 void CDirView::OpenFirstFile()
2127 {
2128         int currentInd = GetFocusedItem();
2129         int firstFileInd = 0;
2130         // Skip directories
2131         while (firstFileInd <= currentInd)
2132         {
2133                 DIFFITEM& dip = GetDiffItem(firstFileInd);
2134                 if (!dip.diffcode.isDirectory())
2135                 {
2136                         MoveFocus(currentInd, firstFileInd, 1);
2137                         OpenSelection();
2138                         break;
2139                 }               
2140                 firstFileInd++;
2141         }
2142 }
2143
2144 bool CDirView::IsFirstFile()
2145 {
2146         int currentInd = GetFocusedItem();
2147         int firstFileInd = 0;
2148         while (firstFileInd <= currentInd)
2149         {
2150                 DIFFITEM& dip = GetDiffItem(firstFileInd);
2151                 if (!dip.diffcode.isDirectory())
2152                 {
2153                         if (currentInd == firstFileInd)
2154                                 return true;
2155                         else
2156                                 return false;
2157                 }
2158                 firstFileInd++;
2159         }
2160         return false;
2161 }
2162
2163 void CDirView::OpenLastFile()
2164 {
2165         const int count = m_pList->GetItemCount();
2166         int currentInd = GetFocusedItem();
2167         int lastFileInd = count - 1;
2168         // Skip directories
2169         while (lastFileInd >= 0)
2170         {
2171                 DIFFITEM& dip = GetDiffItem(lastFileInd);
2172                 if (!dip.diffcode.isDirectory())
2173                 {
2174                         MoveFocus(currentInd, lastFileInd, 1);
2175                         OpenSelection();
2176                         break;
2177                 }
2178                 lastFileInd--;
2179         }
2180 }
2181
2182 bool CDirView::IsLastFile()
2183 {
2184         const int count = m_pList->GetItemCount();
2185         int currentInd = GetFocusedItem();
2186         int lastFileInd = count - 1;
2187         while (lastFileInd >= currentInd)
2188         {
2189                 DIFFITEM& dip = GetDiffItem(lastFileInd);
2190                 if (!dip.diffcode.isDirectory())
2191                 {
2192                         if (currentInd == lastFileInd)
2193                                 return true;
2194                         else
2195                                 return false;
2196                 }
2197                 lastFileInd--;
2198         }
2199         return false;
2200 }
2201
2202 void CDirView::OpenNextFile()
2203 {
2204         const int count = m_pList->GetItemCount();
2205         int currentInd = GetFocusedItem();
2206         int nextInd = currentInd + 1;
2207         if (currentInd >= 0)
2208         {
2209                 while (nextInd < count)
2210                 {
2211                         DIFFITEM& dip = GetDiffItem(nextInd);
2212                         MoveFocus(nextInd - 1, nextInd, 1);
2213                         if (!dip.diffcode.isDirectory())
2214                         {                               
2215                                 OpenSelection();
2216                                 break;
2217                         }
2218                         nextInd++;
2219                 }
2220         }
2221 }
2222
2223 void CDirView::OpenPrevFile()
2224 {
2225         int currentInd = GetFocusedItem();
2226         int prevInd = currentInd - 1;
2227         if (currentInd >= 0)
2228         {
2229                 while (prevInd >= 0)
2230                 {
2231                         DIFFITEM& dip = GetDiffItem(prevInd);
2232                         MoveFocus(prevInd + 1, prevInd, 1);
2233                         if (!dip.diffcode.isDirectory())
2234                         {
2235                                 OpenSelection();
2236                                 break;
2237                         }
2238                         prevInd--;
2239                 }
2240         }
2241 }
2242
2243 void CDirView::SetActivePane(int pane)
2244 {
2245         if (m_nActivePane >= 0)
2246                 GetParentFrame()->GetHeaderInterface()->SetActive(m_nActivePane, false);
2247         GetParentFrame()->GetHeaderInterface()->SetActive(pane, true);
2248         m_nActivePane = pane;
2249 }
2250
2251 // Go to next diff
2252 // If none or one item selected select found item
2253 void CDirView::OnNextdiff()
2254 {
2255         MoveToNextDiff();
2256 }
2257
2258
2259 void CDirView::OnUpdateNextdiff(CCmdUI* pCmdUI)
2260 {
2261         pCmdUI->Enable(HasNextDiff());
2262 }
2263
2264 // Go to prev diff
2265 // If none or one item selected select found item
2266 void CDirView::OnPrevdiff()
2267 {
2268         MoveToPrevDiff();
2269 }
2270
2271
2272 void CDirView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
2273 {
2274         pCmdUI->Enable(HasPrevDiff());
2275 }
2276
2277 void CDirView::OnCurdiff()
2278 {
2279         const int count = m_pList->GetItemCount();
2280         bool found = false;
2281         int i = GetFirstSelectedInd();
2282
2283         // No selection - no diff to go
2284         if (i == -1)
2285                 i = count;
2286
2287         while (i < count && !found)
2288         {
2289                 UINT selected = m_pList->GetItemState(i, LVIS_SELECTED);
2290                 UINT focused = m_pList->GetItemState(i, LVIS_FOCUSED);
2291
2292                 if (selected == LVIS_SELECTED && focused == LVIS_FOCUSED)
2293                 {
2294                         m_pList->EnsureVisible(i, FALSE);
2295                         found = true;
2296                 }
2297                 i++;
2298         }
2299 }
2300
2301 void CDirView::OnUpdateCurdiff(CCmdUI* pCmdUI)
2302 {
2303         pCmdUI->Enable(GetFirstSelectedInd() > -1);
2304 }
2305
2306 int CDirView::GetFocusedItem()
2307 {
2308         return m_pList->GetNextItem(-1, LVNI_FOCUSED);
2309 }
2310
2311 int CDirView::GetFirstDifferentItem()
2312 {
2313         if (!m_firstDiffItem.has_value())
2314         {
2315                 DirItemIterator it =
2316                         std::find_if(Begin(), End(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2317                 m_firstDiffItem = it.m_sel;
2318         }
2319         return m_firstDiffItem.value();
2320 }
2321
2322 int CDirView::GetLastDifferentItem()
2323 {
2324         if (!m_lastDiffItem.has_value())
2325         {
2326                 DirItemIterator it =
2327                         std::find_if(RevBegin(), RevEnd(), MakeDirActions(&DirActions::IsItemNavigableDiff));
2328                 m_lastDiffItem = it.m_sel;
2329         }
2330         return m_lastDiffItem.value();
2331 }
2332
2333 /**
2334  * @brief Move focus to specified item (and selection if multiple items not selected)
2335  *
2336  * Moves the focus from item [currentInd] to item [i]
2337  * Additionally, if there are not multiple items selected,
2338  *  deselects item [currentInd] and selects item [i]
2339  */
2340 void CDirView::MoveFocus(int currentInd, int i, int selCount)
2341 {
2342         if (selCount <= 1)
2343         {
2344                 // Not multiple items selected, so bring selection with us
2345                 m_pList->SetItemState(currentInd, 0, LVIS_SELECTED);
2346                 m_pList->SetItemState(currentInd, 0, LVIS_FOCUSED);
2347                 m_pList->SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2348                 m_pList->SetSelectionMark(i);
2349         }
2350
2351         // Move focus to specified item
2352         // (this automatically defocuses old item)
2353         m_pList->SetItemState(i, LVIS_FOCUSED, LVIS_FOCUSED);
2354         m_pList->EnsureVisible(i, FALSE);
2355 }
2356
2357 void CDirView::OnUpdateSave(CCmdUI* pCmdUI)
2358 {
2359         pCmdUI->Enable(FALSE);
2360 }
2361
2362 CDirFrame * CDirView::GetParentFrame()
2363 {
2364         // can't verify cast without introducing more coupling
2365         // (CDirView doesn't include DirFrame.h)
2366         return static_cast<CDirFrame *>(CListView::GetParentFrame());
2367 }
2368
2369 void CDirView::OnRefresh()
2370 {
2371         m_pSavedTreeState.reset(SaveTreeState(GetDiffContext()));
2372         GetDocument()->Rescan();
2373 }
2374
2375 BOOL CDirView::PreTranslateMessage(MSG* pMsg)
2376 {
2377         // Handle special shortcuts here
2378         if (pMsg->message == WM_KEYDOWN)
2379         {
2380                 if (!IsLabelEdit())
2381                 {
2382                         // Check if we got 'ESC pressed' -message
2383                         if (pMsg->wParam == VK_ESCAPE)
2384                         {
2385                                 if (GetDocument()->m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPARING)
2386                                 {
2387                                         GetDocument()->AbortCurrentScan();
2388                                         return TRUE;
2389                                 }
2390
2391                                 if (m_nEscCloses != 0)
2392                                 {
2393                                         AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
2394                                         return FALSE;
2395                                 }
2396                         }
2397                         // Check if we got 'DEL pressed' -message
2398                         if (pMsg->wParam == VK_DELETE)
2399                         {
2400                                 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_MERGE_DELETE);
2401                                 return FALSE;
2402                         }
2403                         int sel = GetFocusedItem();
2404                         // Check if we got 'Backspace pressed' -message
2405                         if (pMsg->wParam == VK_BACK)
2406                         {
2407                                 if (!GetDiffContext().m_bRecursive)
2408                                 {
2409                                         OpenParentDirectory(GetDocument());
2410                                         return FALSE;
2411                                 }
2412                                 else if (m_bTreeMode && sel >= 0)
2413                                 {
2414                                         const DIFFITEM& di = GetDiffItem(sel);
2415                                         assert(di.HasParent());
2416                                         if (di.HasParent())
2417                                         {
2418                                                 int i = GetItemIndex(di.GetParentLink());
2419                                                 if (i >= 0)
2420                                                         MoveFocus(sel, i, GetSelectedCount());
2421                                         }
2422                                 }
2423                         }
2424                         if (sel >= 0)
2425                         {
2426                                 DIFFITEM& dip = this->GetDiffItem(sel);
2427                                 if (pMsg->wParam == VK_LEFT)
2428                                 {
2429                                         if (m_bTreeMode && GetDiffContext().m_bRecursive && (!(dip.customFlags & ViewCustomFlags::EXPANDED) || !dip.HasChildren()))
2430                                                 PostMessage(WM_KEYDOWN, VK_BACK);
2431                                         else
2432                                                 CollapseSubdir(sel);
2433                                         return TRUE;
2434                                 }
2435                                 if (pMsg->wParam == VK_SUBTRACT)
2436                                 {
2437                                         CollapseSubdir(sel);
2438                                         return TRUE;
2439                                 }
2440                                 if (pMsg->wParam == VK_RIGHT)
2441                                 {
2442                                         if (m_bTreeMode && GetDiffContext().m_bRecursive && dip.customFlags & ViewCustomFlags::EXPANDED && dip.HasChildren())
2443                                                 PostMessage(WM_KEYDOWN, VK_DOWN);
2444                                         else
2445                                                 ExpandSubdir(sel);
2446                                         return TRUE;
2447                                 }
2448                                 if (pMsg->wParam == VK_ADD)
2449                                 {
2450                                         ExpandSubdir(sel);
2451                                         return TRUE;
2452                                 }
2453                                 if (pMsg->wParam == VK_MULTIPLY)
2454                                 {
2455                                         ExpandSubdir(sel, true);
2456                                         return TRUE;
2457                                 }
2458                         }
2459                 }
2460                 else
2461                 {
2462                         // ESC doesn't close window when user is renaming an item.
2463                         if (pMsg->wParam == VK_ESCAPE)
2464                         {
2465                                 m_bUserCancelEdit = true;
2466
2467                                 // The edit control send LVN_ENDLABELEDIT when it loses focus,
2468                                 // so we use it to cancel the rename action.
2469                                 m_pList->SetFocus();
2470
2471                                 // Stop the ESC before it reach the main frame which might
2472                                 // cause a program termination.
2473                                 return TRUE;
2474                         }
2475                 }
2476         }
2477         return CListView::PreTranslateMessage(pMsg);
2478 }
2479
2480 void CDirView::OnUpdateRefresh(CCmdUI* pCmdUI)
2481 {
2482         UINT threadState = GetDocument()->m_diffThread.GetThreadState();
2483         pCmdUI->Enable(threadState != CDiffThread::THREAD_COMPARING);
2484 }
2485
2486 /**
2487  * @brief Called when compare thread asks UI update.
2488  * @note Currently thread asks update after compare is ready
2489  * or aborted.
2490  */
2491 LRESULT CDirView::OnUpdateUIMessage(WPARAM wParam, LPARAM lParam)
2492 {
2493         UNREFERENCED_PARAMETER(lParam);
2494
2495         CDirDoc * pDoc = GetDocument();
2496         ASSERT(pDoc != nullptr);
2497
2498         // Since the Collect thread deletes the DiffItems in the rescan by "Update selection",
2499         // the UI update process should not be executed until the Collect thread process is completed 
2500         // to avoid accessing the deleted DiffItem.
2501         if (pDoc->m_diffThread.IsMarkedRescan() && pDoc->m_diffThread.GetCollectThreadState() != CDiffThread::THREAD_COMPLETED)
2502         {
2503                 ASSERT(0);
2504                 return 0;       // return value unused
2505         }
2506
2507         if (wParam == CDiffThread::EVENT_COMPARE_COMPLETED)
2508         {
2509                 if (pDoc->GetDiffContext().m_pPropertySystem && pDoc->GetDiffContext().m_pPropertySystem->HasHashProperties())
2510                         pDoc->GetDiffContext().CreateDuplicateValueMap();
2511
2512                 pDoc->CompareReady();
2513
2514                 if (!pDoc->GetGeneratingReport())
2515                         Redisplay();
2516
2517                 if (!pDoc->GetReportFile().empty())
2518                 {
2519                         OnToolsGenerateReport();
2520                         pDoc->SetReportFile(_T(""));
2521                 }
2522
2523                 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
2524                         OnFirstdiff();
2525                 else
2526                         MoveFocus(0, 0, 0);
2527
2528                 // If compare took more than TimeToSignalCompare seconds, notify user
2529                 m_elapsed = clock() - m_compareStart;
2530                 SetTimer(STATUSBAR_UPDATE, 150, nullptr);
2531                 if (m_elapsed > TimeToSignalCompare * CLOCKS_PER_SEC)
2532                         MessageBeep(IDOK);
2533                 GetMainFrame()->StartFlashing();
2534         }
2535         else if (wParam == CDiffThread::EVENT_COMPARE_PROGRESSED)
2536         {
2537                 InvalidateRect(nullptr, FALSE);
2538         }
2539         else if (wParam == CDiffThread::EVENT_COLLECT_COMPLETED)
2540         {
2541                 if (m_pSavedTreeState != nullptr)
2542                 {
2543                         RestoreTreeState(GetDiffContext(), m_pSavedTreeState.get());
2544                         m_pSavedTreeState.reset();
2545                         Redisplay();
2546                 }
2547                 else
2548                 {
2549                         if (m_bExpandSubdirs)
2550                                 OnViewExpandAllSubdirs();
2551                         else
2552                                 Redisplay();
2553                 }
2554
2555                 HideItems(GetDiffContext().m_vCurrentlyHiddenItems);
2556         }
2557
2558         return 0; // return value unused
2559 }
2560
2561 BOOL CDirView::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
2562 {
2563         NMHDR * hdr = reinterpret_cast<NMHDR *>(lParam);
2564         if (hdr->code == HDN_ENDDRAG)
2565                 return OnHeaderEndDrag((LPNMHEADER)hdr, pResult);
2566         if (hdr->code == HDN_BEGINDRAG)
2567                 return OnHeaderBeginDrag((LPNMHEADER)hdr, pResult);
2568
2569         return CListView::OnNotify(wParam, lParam, pResult);
2570 }
2571
2572 BOOL CDirView::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
2573 {
2574         if (uMsg == WM_NOTIFY)
2575         {
2576                 NMHDR *pNMHDR = (NMHDR *)lParam;
2577                 switch (pNMHDR->code)
2578                 {
2579                 case LVN_GETDISPINFO:
2580                         ReflectGetdispinfo((NMLVDISPINFO *)lParam);
2581                         return TRUE;
2582                 case LVN_GETINFOTIPW:
2583                 case LVN_GETINFOTIPA:
2584                         return TRUE;
2585                 }
2586         }
2587         return CListView::OnChildNotify(uMsg, wParam, lParam, pResult);
2588 }
2589
2590 /**
2591  * @brief User is starting to drag a column header
2592  */
2593 bool CDirView::OnHeaderBeginDrag(LPNMHEADER hdr, LRESULT* pResult)
2594 {
2595         // save column widths before user reorders them
2596         // so we can reload them on the end drag
2597         const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS;
2598         GetOptionsMgr()->SaveOption(keyname,
2599                 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1)).c_str());
2600         return true;
2601 }
2602
2603 /**
2604  * @brief User just finished dragging a column header
2605  */
2606 bool CDirView::OnHeaderEndDrag(LPNMHEADER hdr, LRESULT* pResult)
2607 {
2608         int src = hdr->iItem;
2609         int dest = hdr->pitem->iOrder;
2610         bool allowDrop = true;
2611         *pResult = allowDrop ? FALSE : TRUE;
2612         if (allowDrop && src != dest && dest != -1)
2613         {
2614                 m_pColItems->MoveColumn(src, dest);
2615                 InitiateSort();
2616         }
2617         return true;
2618 }
2619
2620 /**
2621  * @brief Remove any windows reordering of columns
2622  */
2623 void CDirView::FixReordering()
2624 {
2625         LVCOLUMN lvcol;
2626         lvcol.mask = LVCF_ORDER;
2627         lvcol.fmt = 0;
2628         lvcol.cx = 0;
2629         lvcol.pszText = nullptr;
2630         lvcol.iSubItem = 0;
2631         for (int i = 0; i < m_pColItems->GetColCount(); ++i)
2632         {
2633                 lvcol.iOrder = i;
2634                 GetListCtrl().SetColumn(i, &lvcol);
2635         }
2636 }
2637
2638 /** @brief Add columns to display, loading width & order from registry. */
2639 void CDirView::LoadColumnHeaderItems()
2640 {
2641         bool dummyflag = false;
2642
2643         CHeaderCtrl * h = m_pList->GetHeaderCtrl();
2644         if (h->GetItemCount())
2645         {
2646                 dummyflag = true;
2647                 while (m_pList->GetHeaderCtrl()->GetItemCount() > 1)
2648                         m_pList->DeleteColumn(1);
2649         }
2650
2651         for (int i = 0; i < m_pColItems->GetDispColCount(); ++i)
2652         {
2653                 LVCOLUMN lvc;
2654                 lvc.mask = LVCF_FMT + LVCF_SUBITEM + LVCF_TEXT;
2655                 lvc.fmt = LVCFMT_LEFT;
2656                 lvc.cx = 0;
2657                 lvc.pszText = _T("text");
2658                 lvc.iSubItem = i;
2659                 m_pList->InsertColumn(i, &lvc);
2660         }
2661         if (dummyflag)
2662                 m_pList->DeleteColumn(1);
2663
2664 }
2665
2666 void CDirView::SetFont(const LOGFONT & lf)
2667 {
2668         m_font.DeleteObject();
2669         m_font.CreateFontIndirect(&lf);
2670         CWnd::SetFont(&m_font);
2671 }
2672
2673 /** @brief Fire off a resort of the data, to take place when things stabilize. */
2674 void CDirView::InitiateSort()
2675 {
2676         PostMessage(WM_TIMER, COLUMN_REORDER);
2677 }
2678
2679 void CDirView::OnTimer(UINT_PTR nIDEvent)
2680 {
2681         if (nIDEvent == COLUMN_REORDER)
2682         {
2683                 // Remove the windows reordering, as we're doing it ourselves
2684                 FixReordering();
2685                 // Now redraw screen
2686                 UpdateColumnNames();
2687                 m_pColItems->LoadColumnWidths(
2688                         GetOptionsMgr()->GetString(GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS),
2689                         std::bind(&CListCtrl::SetColumnWidth, m_pList, _1, _2), GetDefColumnWidth());
2690                 Redisplay();
2691         }
2692         else if (nIDEvent == STATUSBAR_UPDATE)
2693         {
2694                 KillTimer(STATUSBAR_UPDATE);
2695                 int items = GetSelectedCount();
2696                 String msg;
2697                 if (m_elapsed != 0)
2698                 {
2699                         msg = strutils::format(_("Elapsed time: %ld ms"), m_elapsed);
2700                         m_elapsed = 0;
2701                 }
2702                 else
2703                 {
2704                         msg = (items == 1) ? _("1 item selected") : strutils::format_string1(_("%1 items selected"), strutils::to_str(items));
2705                 }
2706                 GetParentFrame()->SetStatus(msg.c_str());
2707         }
2708         
2709         CListView::OnTimer(nIDEvent);
2710 }
2711
2712 /**
2713  * @brief Change left-side readonly-status
2714  */
2715 template<SIDE_TYPE stype>
2716 void CDirView::OnReadOnly()
2717 {
2718         const int index = SideToIndex(GetDiffContext(), stype);
2719         bool bReadOnly = GetDocument()->GetReadOnly(index);
2720         GetDocument()->SetReadOnly(index, !bReadOnly);
2721 }
2722
2723 /**
2724  * @brief Update left-readonly menu item
2725  */
2726 template<SIDE_TYPE stype>
2727 void CDirView::OnUpdateReadOnly(CCmdUI* pCmdUI)
2728 {
2729         const int index = SideToIndex(GetDiffContext(), stype);
2730         bool bReadOnly = GetDocument()->GetReadOnly(index);
2731         if (stype != SIDE_MIDDLE)
2732         {
2733                 pCmdUI->Enable(TRUE);
2734                 pCmdUI->SetCheck(bReadOnly);
2735         }
2736         else
2737         {
2738                 pCmdUI->Enable(GetDocument()->m_nDirs > 2);
2739                 pCmdUI->SetCheck(bReadOnly && GetDocument()->m_nDirs > 2);
2740         }
2741 }
2742
2743 /**
2744  * @brief Update left-side readonly statusbar item
2745  */
2746 void CDirView::OnUpdateStatusLeftRO(CCmdUI* pCmdUI)
2747 {
2748         bool bROLeft = GetDocument()->GetReadOnly(0);
2749         pCmdUI->Enable(bROLeft);
2750 }
2751
2752 /**
2753  * @brief Update middle readonly statusbar item
2754  */
2755 void CDirView::OnUpdateStatusMiddleRO(CCmdUI* pCmdUI)
2756 {
2757         bool bROMiddle = GetDocument()->GetReadOnly(1);
2758         pCmdUI->Enable(bROMiddle && GetDocument()->m_nDirs > 2);
2759 }
2760
2761 /**
2762  * @brief Update right-side readonly statusbar item
2763  */
2764 void CDirView::OnUpdateStatusRightRO(CCmdUI* pCmdUI)
2765 {
2766         bool bRORight = GetDocument()->GetReadOnly(GetDocument()->m_nDirs - 1);
2767         pCmdUI->Enable(bRORight);
2768 }
2769
2770 /**
2771  * @brief Open dialog to customize dirview columns
2772  */
2773 void CDirView::OnCustomizeColumns()
2774 {
2775         // Located in DirViewColHandler.cpp
2776         OnEditColumns();
2777         const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_ORDERS : OPT_DIRVIEW3_COLUMN_ORDERS;
2778         GetOptionsMgr()->SaveOption(keyname, m_pColItems->SaveColumnOrders());
2779 }
2780
2781 void CDirView::OnOpenWithUnpacker()
2782 {
2783         int sel = -1;
2784         sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
2785         if (sel != -1)
2786         {
2787                 PackingInfo* infoUnpacker = nullptr;
2788                 PrediffingInfo* infoPrediffer = nullptr;
2789                 CDiffContext& ctxt = GetDiffContext();
2790                 String filteredFilenames = ctxt.GetFilteredFilenames(GetDiffItem(sel));
2791                 ctxt.FetchPluginInfos(filteredFilenames, &infoUnpacker, &infoPrediffer);
2792                 // let the user choose a handler
2793                 CSelectPluginDlg dlg(infoUnpacker->GetPluginPipeline(), filteredFilenames,
2794                         CSelectPluginDlg::PluginType::Unpacker, false, this);
2795                 if (dlg.DoModal() == IDOK)
2796                 {
2797                         PackingInfo infoUnpackerNew(dlg.GetPluginPipeline());
2798                         OpenSelection(SELECTIONTYPE_NORMAL, &infoUnpackerNew, false);
2799                 }
2800         }
2801
2802 }
2803
2804 void CDirView::OnUpdateCtxtOpenWithUnpacker(CCmdUI* pCmdUI)
2805 {
2806         if (!GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
2807         {
2808                 pCmdUI->Enable(FALSE);
2809                 return;
2810         }
2811
2812         // we need one selected file, existing on both side
2813         if (m_pList->GetSelectedCount() != 1)
2814                 pCmdUI->Enable(FALSE);
2815         else
2816         {
2817                 int sel = -1;
2818                 sel = m_pList->GetNextItem(sel, LVNI_SELECTED);
2819                 const DIFFITEM& di = GetDiffItem(sel);
2820                 if (di.diffcode.isDirectory())
2821                 {
2822                         pCmdUI->Enable(FALSE);
2823                         return;
2824                 }
2825                 pCmdUI->Enable(IsItemDeletableOnBoth(GetDiffContext(), di));
2826         }
2827 }
2828
2829 /**
2830  * @brief Fill string list with current dirview column registry key names
2831  */
2832 std::vector<String> CDirView::GetCurrentColRegKeys()
2833 {
2834         std::vector<String> colKeys;
2835         int nphyscols = GetListCtrl().GetHeaderCtrl()->GetItemCount();
2836         for (int col = 0; col < nphyscols; ++col)
2837         {
2838                 int logcol = m_pColItems->ColPhysToLog(col);
2839                 colKeys.push_back(m_pColItems->GetColRegValueNameBase(logcol));
2840         }
2841         return colKeys;
2842 }
2843
2844 struct FileCmpReport: public IFileCmpReport
2845 {
2846         explicit FileCmpReport(CDirView *pDirView) : m_pDirView(pDirView) {}
2847         ~FileCmpReport() override {}
2848         bool operator()(REPORT_TYPE nReportType, IListCtrl *pList, int nIndex, const String &sDestDir, String &sLinkPath) override
2849         {
2850                 const CDiffContext& ctxt = m_pDirView->GetDiffContext();
2851                 const DIFFITEM &di = m_pDirView->GetDiffItem(nIndex);
2852                 
2853                 String sLinkFullPath = paths::ConcatPath(ctxt.GetLeftPath(), di.diffFileInfo[0].GetFile());
2854
2855                 if (di.diffcode.isDirectory() || !IsItemNavigableDiff(ctxt, di) || IsArchiveFile(sLinkFullPath))
2856                 {
2857                         sLinkPath.clear();
2858                         return false;
2859                 }
2860
2861                 sLinkPath = strutils::format(_T("%d_"), nIndex) + di.diffFileInfo[0].GetFile();
2862
2863                 strutils::replace(sLinkPath, _T("\\"), _T("_"));
2864                 sLinkPath += _T(".html");
2865                 String sReportPath = paths::ConcatPath(sDestDir, sLinkPath);
2866                 bool completed = false;
2867
2868                 m_pDirView->MoveFocus(m_pDirView->GetFirstSelectedInd(), nIndex, m_pDirView->GetSelectedCount());
2869                 m_pDirView->PostMessage(MSG_GENERATE_FLIE_COMPARE_REPORT,
2870                         reinterpret_cast<WPARAM>(sReportPath.c_str()), 
2871                         reinterpret_cast<LPARAM>(&completed));
2872
2873                 while (!completed)
2874                 {
2875                         MSG msg;
2876                         while (::PeekMessage(&msg, nullptr, NULL, NULL, PM_NOREMOVE))
2877                         {
2878                                 if (!AfxGetApp()->PumpMessage())
2879                                         break;
2880                         }
2881                         Sleep(5);
2882                 }
2883
2884                 return true;
2885         }
2886 private:
2887         FileCmpReport();
2888         CDirView *m_pDirView;
2889 };
2890
2891 LRESULT CDirView::OnGenerateFileCmpReport(WPARAM wParam, LPARAM lParam)
2892 {
2893         OpenSelection();
2894
2895         auto *pReportFileName = reinterpret_cast<const TCHAR *>(wParam);
2896         bool *pCompleted = reinterpret_cast<bool *>(lParam);
2897         if (IMergeDoc * pMergeDoc = GetMainFrame()->GetActiveIMergeDoc())
2898         {
2899                 pMergeDoc->GenerateReport(pReportFileName);
2900                 pMergeDoc->CloseNow();
2901         }
2902         MSG msg;
2903         while (::PeekMessage(&msg, nullptr, NULL, NULL, PM_NOREMOVE))
2904                 if (!AfxGetApp()->PumpMessage())
2905                         break;
2906         GetMainFrame()->OnUpdateFrameTitle(FALSE);
2907         *pCompleted = true;
2908         return 0;
2909 }
2910
2911 /**
2912  * @brief Generate report from dir compare results.
2913  */
2914 void CDirView::OnToolsGenerateReport()
2915 {
2916         CDirDoc *pDoc = GetDocument();
2917         DirCmpReportDlg dlg;
2918         dlg.LoadSettings();
2919         dlg.m_sReportFile = pDoc->GetReportFile();
2920         if (dlg.m_sReportFile.empty() && dlg.DoModal() != IDOK)
2921                 return;
2922
2923         pDoc->SetGeneratingReport(true);
2924         const CDiffContext& ctxt = GetDiffContext();
2925
2926         PathContext paths = ctxt.GetNormalizedPaths();
2927
2928         // If inside archive, convert paths
2929         if (pDoc->IsArchiveFolders())
2930         {
2931                 for (int i = 0; i < paths.GetSize(); i++)
2932                         pDoc->ApplyDisplayRoot(i, paths[i]);
2933         }
2934
2935         DirCmpReport *pReport = new DirCmpReport(GetCurrentColRegKeys());
2936         pReport->SetRootPaths(paths);
2937         pReport->SetColumns(m_pColItems->GetDispColCount());
2938         pReport->SetFileCmpReport(new FileCmpReport(this));
2939         pReport->SetList(new IListCtrlImpl(m_pList->m_hWnd, m_listViewItems));
2940         pReport->SetReportType(dlg.m_nReportType);
2941         pReport->SetReportFile(dlg.m_sReportFile);
2942         pReport->SetCopyToClipboard(dlg.m_bCopyToClipboard);
2943         pReport->SetIncludeFileCmpReport(dlg.m_bIncludeFileCmpReport);
2944         pDoc->SetReport(pReport);
2945         pDoc->Rescan();
2946 }
2947
2948 /**
2949  * @brief Generate patch from files selected.
2950  *
2951  * Creates a patch from selected files in active directory compare, or
2952  * active file compare. Files in file compare must be saved before
2953  * creating a patch.
2954  */
2955 void CDirView::OnToolsGeneratePatch()
2956 {
2957         CPatchTool patcher;
2958         const CDiffContext& ctxt = GetDiffContext();
2959
2960         // Get selected items from folder compare
2961         bool bValidFiles = true;
2962         for (DirItemIterator it = SelBegin(); bValidFiles && it != SelEnd(); ++it)
2963         {
2964                 const DIFFITEM &item = *it;
2965                 if (item.diffcode.isBin())
2966                 {
2967                         LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING |
2968                                 MB_DONT_DISPLAY_AGAIN, IDS_CANNOT_CREATE_BINARYPATCH);
2969                         bValidFiles = false;
2970                 }
2971
2972                 if (bValidFiles)
2973                 {
2974                         // Format full paths to files (leftFile/rightFile)
2975                         String leftFile = item.getFilepath(0, ctxt.GetNormalizedPath(0));
2976                         if (!leftFile.empty())
2977                                 leftFile = paths::ConcatPath(leftFile, item.diffFileInfo[0].filename);
2978                         String rightFile = item.getFilepath(1, ctxt.GetNormalizedPath(1));
2979                         if (!rightFile.empty())
2980                                 rightFile = paths::ConcatPath(rightFile, item.diffFileInfo[1].filename);
2981
2982                         // Format relative paths to files in folder compare
2983                         String leftpatch = item.diffFileInfo[0].path;
2984                         if (!leftpatch.empty())
2985                                 leftpatch += _T("/");
2986                         leftpatch += item.diffFileInfo[0].filename;
2987                         String rightpatch = item.diffFileInfo[1].path;
2988                         if (!rightpatch.empty())
2989                                 rightpatch += _T("/");
2990                         rightpatch += item.diffFileInfo[1].filename;
2991                         patcher.AddFiles(leftFile, leftpatch, rightFile, rightpatch);
2992                 }
2993         }
2994
2995         patcher.CreatePatch();
2996 }
2997
2998 /**
2999  * @brief Add special items for non-recursive compare
3000  * to directory view.
3001  *
3002  * Currently only special item is ".." for browsing to
3003  * parent folders.
3004  * @return number of items added to view
3005  */
3006 int CDirView::AddSpecialItems()
3007 {
3008         CDirDoc *pDoc = GetDocument();
3009         int retVal = 0;
3010         bool bEnable = true;
3011         PathContext pathsParent;
3012         switch (CheckAllowUpwardDirectory(GetDiffContext(), pDoc->m_pTempPathContext, pathsParent))
3013         {
3014         case AllowUpwardDirectory::No:
3015                 bEnable = false;
3016                 [[fallthrough]];
3017         default:
3018                 AddParentFolderItem(bEnable);
3019                 retVal = 1;
3020                 [[fallthrough]];
3021         case AllowUpwardDirectory::Never:
3022                 break;
3023         }
3024         return retVal;
3025 }
3026
3027 /**
3028  * @brief Add "Parent folder" ("..") item to directory view
3029  */
3030 void CDirView::AddParentFolderItem(bool bEnable)
3031 {
3032         AddNewItem(0, (DIFFITEM *)SPECIAL_ITEM_POS, bEnable ? DIFFIMG_DIRUP : DIFFIMG_DIRUP_DISABLE, 0);
3033 }
3034
3035 template <int flag>
3036 void CDirView::OnCtxtDirZip()
3037 {
3038         if (!HasZipSupport())
3039         {
3040                 LangMessageBox(IDS_NO_ZIP_SUPPORT, MB_ICONINFORMATION);
3041                 return;
3042         }
3043
3044         DirItemEnumerator
3045         (
3046                 this, LVNI_SELECTED | flag
3047         ).CompressArchive();
3048 }
3049
3050 void CDirView::ShowShellContextMenu(SIDE_TYPE stype)
3051 {
3052         CShellContextMenu *pContextMenu = nullptr;
3053         switch (stype)
3054         {
3055         case SIDE_LEFT:
3056                 if (m_pShellContextMenuLeft == nullptr)
3057                         m_pShellContextMenuLeft.reset(new CShellContextMenu(LeftCmdFirst, LeftCmdLast));
3058                 pContextMenu = m_pShellContextMenuLeft.get();
3059                 break;
3060         case SIDE_MIDDLE:
3061                 if (m_pShellContextMenuMiddle == nullptr)
3062                         m_pShellContextMenuMiddle.reset(new CShellContextMenu(MiddleCmdFirst, MiddleCmdLast));
3063                 pContextMenu = m_pShellContextMenuMiddle.get();
3064                 break;
3065         case SIDE_RIGHT:
3066                 if (m_pShellContextMenuRight == nullptr)
3067                         m_pShellContextMenuRight.reset(new CShellContextMenu(RightCmdFirst, RightCmdLast));
3068                 pContextMenu = m_pShellContextMenuRight.get();
3069                 break;
3070         }
3071         if (pContextMenu!=nullptr && ListShellContextMenu(stype))
3072         {
3073                 CPoint point;
3074                 GetCursorPos(&point);
3075                 HWND hWnd = GetSafeHwnd();
3076                 CFrameWnd *pFrame = GetTopLevelFrame();
3077                 ASSERT(pFrame != nullptr);
3078                 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3079                 pFrame->m_bAutoMenuEnable = FALSE;
3080                 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3081                 if (nCmd)
3082                         pContextMenu->InvokeCommand(nCmd, hWnd);
3083                 pContextMenu->ReleaseShellContextMenu();
3084                 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3085         }
3086 }
3087
3088 template <SIDE_TYPE stype>
3089 void CDirView::OnCtxtDirShellContextMenu()
3090 {
3091         ShowShellContextMenu(stype);
3092 }
3093
3094 /**
3095  * @brief Select all visible items in dir compare
3096  */
3097 void CDirView::OnSelectAll()
3098 {
3099         // While the user is renaming an item, select all the edited text.
3100         CEdit *pEdit = m_pList->GetEditControl();
3101         if (pEdit != nullptr)
3102         {
3103                 pEdit->SetSel(pEdit->GetWindowTextLength());
3104         }
3105         else
3106         {
3107                 int selCount = m_pList->GetItemCount();
3108
3109                 for (int i = 0; i < selCount; i++)
3110                 {
3111                         // Don't select special items (SPECIAL_ITEM_POS)
3112                         DIFFITEM *diffpos = GetItemKey(i);
3113                         if (!IsDiffItemSpecial(diffpos))
3114                                 m_pList->SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
3115                 }
3116         }
3117 }
3118
3119 /**
3120  * @brief Update "Select All" item
3121  */
3122 void CDirView::OnUpdateSelectAll(CCmdUI* pCmdUI)
3123 {
3124         bool bEnable = (!IsLabelEdit()) || (m_pList->GetItemCount() > 0);
3125         pCmdUI->Enable(bEnable);
3126 }
3127
3128 /**
3129  * @brief Handle clicks in plugin context view in list
3130  */
3131 void CDirView::OnPluginSettings(UINT nID)
3132 {
3133         bool unpacker = (ID_UNPACKER_SETTINGS_NONE <= nID && nID <= ID_UNPACKER_SETTINGS_SELECT);
3134         String pluginPipeline;
3135         switch (nID)
3136         {
3137         case ID_PREDIFFER_SETTINGS_NONE:
3138         case ID_UNPACKER_SETTINGS_NONE:
3139                 pluginPipeline.clear();
3140                 break;
3141         case ID_PREDIFFER_SETTINGS_AUTO:
3142         case ID_UNPACKER_SETTINGS_AUTO:
3143                 pluginPipeline = _T("<Automatic>");
3144                 break;
3145         case ID_PREDIFFER_SETTINGS_SELECT:
3146         case ID_UNPACKER_SETTINGS_SELECT:
3147                 int sel = m_pList->GetNextItem(-1, LVNI_SELECTED);
3148                 PackingInfo* infoUnpacker = nullptr;
3149                 PrediffingInfo* infoPrediffer = nullptr;
3150                 CDiffContext& ctxt = GetDiffContext();
3151                 String filteredFilenames = ctxt.GetFilteredFilenames(GetDiffItem(sel));
3152                 ctxt.FetchPluginInfos(filteredFilenames, &infoUnpacker, &infoPrediffer);
3153                 GetDiffContext().FetchPluginInfos(filteredFilenames, &infoUnpacker, &infoPrediffer);
3154                 CSelectPluginDlg dlg(infoUnpacker->GetPluginPipeline(), filteredFilenames,
3155                         unpacker ? CSelectPluginDlg::PluginType::Unpacker : CSelectPluginDlg::PluginType::Prediffer, false, this);
3156                 if (dlg.DoModal() != IDOK)
3157                         return;
3158                 pluginPipeline = dlg.GetPluginPipeline();
3159                 break;
3160         }
3161         ApplyPluginPipeline(SelBegin(), SelEnd(), GetDiffContext(), unpacker, pluginPipeline);
3162         Invalidate();
3163 }
3164
3165 /**
3166  * @brief Updates just before displaying plugin context view in list
3167  */
3168 void CDirView::OnUpdatePluginMode(CCmdUI* pCmdUI)
3169 {
3170         // 2004-04-03, Perry
3171         // CMainFrame::OnUpdatePluginUnpackMode handles this for global unpacking
3172         // and is the template to copy, but here, this is a bit tricky
3173         // as a group of files may be selected
3174         // and they may not all have the same setting
3175         // so I'm not trying this right now
3176
3177         pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
3178
3179         BCMenu *pPopup = static_cast<BCMenu*>(pCmdUI->m_pSubMenu);
3180         if (pPopup == nullptr)
3181                 return;
3182
3183         bool unpacker = (ID_UNPACKER_SETTINGS_NONE <= pCmdUI->m_nID && pCmdUI->m_nID <= ID_UNPACKER_SETTINGS_SELECT);
3184         auto counts = CountPluginNoneAutoOthers(SelBegin(), SelEnd(), GetDiffContext(), unpacker);
3185
3186         CheckContextMenu(pPopup, unpacker ? ID_UNPACKER_SETTINGS_NONE   : ID_PREDIFFER_SETTINGS_NONE,   (std::get<0>(counts) > 0));
3187         CheckContextMenu(pPopup, unpacker ? ID_UNPACKER_SETTINGS_AUTO   : ID_PREDIFFER_SETTINGS_AUTO,   (std::get<1>(counts) > 0));
3188         CheckContextMenu(pPopup, unpacker ? ID_UNPACKER_SETTINGS_SELECT : ID_PREDIFFER_SETTINGS_SELECT, (std::get<2>(counts) > 0));
3189 }
3190
3191 /**
3192  * @brief Refresh cached options.
3193  */
3194 void CDirView::RefreshOptions()
3195 {
3196         m_nEscCloses = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
3197         m_bExpandSubdirs = GetOptionsMgr()->GetBool(OPT_DIRVIEW_EXPAND_SUBDIRS);
3198         Options::DirColors::Load(GetOptionsMgr(), m_cachedColors);
3199         m_bUseColors = GetOptionsMgr()->GetBool(OPT_DIRCLR_USE_COLORS);
3200         m_pList->SetBkColor(m_bUseColors ? m_cachedColors.clrDirMargin : GetSysColor(COLOR_WINDOW));
3201         Invalidate();
3202 }
3203
3204 /**
3205  * @brief Copy selected item left side paths (containing filenames) to clipboard.
3206  */
3207 template<SIDE_TYPE stype>
3208 void CDirView::OnCopyPathnames()
3209 {
3210         std::list<String> list;
3211         CopyPathnames(SelBegin(), SelEnd(), std::back_inserter(list), stype, GetDiffContext());
3212         PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
3213 }
3214
3215 void CDirView::OnCopyBothPathnames()
3216 {
3217         std::list<String> list;
3218         CopyBothPathnames(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
3219         PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
3220 }
3221
3222 /**
3223  * @brief Copy selected item filenames to clipboard.
3224  */
3225 void CDirView::OnCopyFilenames()
3226 {
3227         std::list<String> list;
3228         CopyFilenames(SelBegin(), SelEnd(), std::back_inserter(list));
3229         PutToClipboard(strutils::join(list.begin(), list.end(), _T("\r\n")), GetMainFrame()->GetSafeHwnd());
3230 }
3231
3232 /**
3233  * @brief Enable/Disable dirview Copy Filenames context menu item.
3234  */
3235 void CDirView::OnUpdateCopyFilenames(CCmdUI* pCmdUI)
3236 {
3237         pCmdUI->Enable(Count(&DirActions::IsItemFile).count > 0);
3238 }
3239
3240 /**
3241  * @brief Copy selected item left side to clipboard.
3242  */
3243 template<SIDE_TYPE stype>
3244 void CDirView::OnCopyToClipboard()
3245 {
3246         std::list<String> list;
3247         CopyPathnames(SelBegin(), SelEnd(), std::back_inserter(list), stype, GetDiffContext());
3248         PutFilesToClipboard(list, GetMainFrame()->GetSafeHwnd());
3249 }
3250
3251 /**
3252  * @brief Copy selected item both side to clipboard.
3253  */
3254 void CDirView::OnCopyBothToClipboard()
3255 {
3256         std::list<String> list;
3257         CopyBothPathnames(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
3258         PutFilesToClipboard(list, GetMainFrame()->GetSafeHwnd());
3259 }
3260
3261 /**
3262  * @brief Rename a selected item on both sides.
3263  *
3264  */
3265 void CDirView::OnItemRename()
3266 {
3267         ASSERT(1 == m_pList->GetSelectedCount());
3268         int nSelItem = m_pList->GetNextItem(-1, LVNI_SELECTED);
3269         ASSERT(-1 != nSelItem);
3270         m_pList->EditLabel(nSelItem);
3271 }
3272
3273 /**
3274  * @brief Enable/Disable dirview Rename context menu item.
3275  *
3276  */
3277 void CDirView::OnUpdateItemRename(CCmdUI* pCmdUI)
3278 {
3279         bool bEnabled = (1 == m_pList->GetSelectedCount());
3280         if (bEnabled)
3281         {
3282                 Counts counts = Count(&DirActions::IsItemRenamable);
3283                 bEnabled = (counts.count > 0 && counts.total == 1);
3284         }
3285         pCmdUI->Enable(bEnabled);
3286 }
3287
3288 /**
3289  * @brief hide selected item filenames (removes them from the ListView)
3290  */
3291 void CDirView::OnHideFilenames()
3292 {
3293         CDiffContext& ctxt = GetDiffContext();
3294         int selection_index;
3295         String hiddden_item_path;
3296         
3297         m_pList->SetRedraw(FALSE);      // Turn off updating (better performance)
3298         DirItemIterator it;
3299
3300         while ((it = SelRevBegin()) != SelRevEnd())
3301         {
3302                 DIFFITEM &di = *it;
3303                 selection_index = it.m_sel;
3304                 hiddden_item_path = di.getItemRelativePath();
3305                 SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
3306                 DeleteItem(selection_index);
3307                 ctxt.m_vCurrentlyHiddenItems.push_back(hiddden_item_path);
3308         }
3309         m_pList->SetRedraw(TRUE);       // Turn updating back on
3310 }
3311
3312 /**
3313  * @brief determine if an item-relative-path is contained in the list of items to hide
3314  */
3315 bool CDirView::IsItemToHide(const String& currentItem, const std::vector<String>& ItemsToHide) const
3316 {
3317         return std::find(ItemsToHide.begin(), ItemsToHide.end(), currentItem) != ItemsToHide.end();
3318 }
3319
3320 /**
3321  * @brief hides items specified in the .winmerge file
3322  */
3323
3324 void CDirView::HideItems(const std::vector<String>& ItemsToHide)
3325 {
3326         CDiffContext& ctxt = GetDiffContext();
3327         DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
3328         while (diffpos != nullptr)
3329         {
3330                 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
3331                 if (IsItemToHide(di.getItemRelativePath(), ItemsToHide))
3332                         SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
3333         }
3334
3335         m_pList->SetRedraw(FALSE);      // Turn off updating (better performance)
3336
3337         int num_hidden = 0;
3338         const size_t num_to_hide = ItemsToHide.size();
3339         DirItemIterator it = RevBegin();
3340         while((num_hidden < num_to_hide) && (it != RevEnd()))
3341         {
3342                 DIFFITEM& di = *it;
3343                 if (di.customFlags & ViewCustomFlags::HIDDEN)
3344                 {
3345                         DeleteItem(it.m_sel);
3346                         num_hidden++;
3347                 }
3348                 ++it;
3349         }
3350
3351         m_pList->SetRedraw(TRUE);       // Turn updating back on
3352 }
3353
3354 /**
3355  * @brief update menu item
3356  */
3357 void CDirView::OnUpdateHideFilenames(CCmdUI* pCmdUI)
3358 {
3359         pCmdUI->Enable(m_pList->GetSelectedCount() != 0);
3360 }
3361
3362 /// User chose (context menu) Move left to...
3363 template<SIDE_TYPE stype>
3364 void CDirView::OnCtxtDirMoveTo()
3365 {
3366         DoDirActionTo(stype, &DirActions::MoveTo<stype>, _("Moving files..."));
3367 }
3368
3369 /**
3370  * @brief Update "Move | Left to..." item
3371  */
3372 template<SIDE_TYPE stype>
3373 void CDirView::OnUpdateCtxtDirMoveTo(CCmdUI* pCmdUI)
3374 {
3375         Counts counts = Count(&DirActions::IsItemMovableToOn<stype>);
3376         pCmdUI->Enable(counts.count > 0);
3377         pCmdUI->SetText(FormatMenuItemStringTo(stype, counts.count, counts.total).c_str());
3378 }
3379
3380 /**
3381  * @brief Update title after window is resized.
3382  */
3383 void CDirView::OnSize(UINT nType, int cx, int cy)
3384 {
3385         CListView::OnSize(nType, cx, cy);
3386         GetDocument()->SetTitle(nullptr);
3387 }
3388
3389 /**
3390  * @brief Called when user selects 'Delete' from 'Merge' menu.
3391  */
3392 void CDirView::OnDelete()
3393 {
3394         DoDirAction(&DirActions::DeleteOnEitherOrBoth, _("Deleting files..."));
3395 }
3396
3397 /**
3398  * @brief Enables/disables 'Delete' item in 'Merge' menu.
3399  */
3400 void CDirView::OnUpdateDelete(CCmdUI* pCmdUI)
3401 {
3402         pCmdUI->Enable(Count(&DirActions::IsItemDeletableOnEitherOrBoth).count > 0);
3403 }
3404
3405 /**
3406  * @brief Called when item state is changed.
3407  *
3408  * Show count of selected items in statusbar.
3409  */
3410 void CDirView::OnItemChanged(NMHDR* pNMHDR, LRESULT* pResult)
3411 {
3412         NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
3413
3414         // If item's selected state changed
3415         if ((pNMListView->uOldState & LVIS_SELECTED) !=
3416                         (pNMListView->uNewState & LVIS_SELECTED))
3417         {
3418                 SetTimer(STATUSBAR_UPDATE, 100, nullptr);
3419         }
3420         *pResult = 0;
3421 }
3422
3423 /**
3424  * @brief Called before user start to item label edit.
3425  *
3426  * Disable label edit if initiated from a user double-click.
3427  */
3428 afx_msg void CDirView::OnBeginLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
3429 {
3430         Counts counts = Count(&DirActions::IsItemRenamable);
3431         *pResult = !(counts.count > 0 && counts.total == 1);
3432
3433         // If label edit is allowed.
3434         if (*pResult == FALSE)
3435         {
3436                 const NMLVDISPINFO *pdi = (NMLVDISPINFO*)pNMHDR;
3437                 ASSERT(pdi != nullptr);
3438
3439                 // Locate the edit box on the right column in case the user changed the
3440                 // column order.
3441                 const int nColPos = m_pColItems->ColLogToPhys(0);
3442
3443                 // Get text from the "File Name" column.
3444                 CString sText = m_pList->GetItemText(pdi->item.iItem, nColPos);
3445                 ASSERT(!sText.IsEmpty());
3446
3447                 // Keep only left file name (separated by '|'). This form occurs
3448                 // when two files exists with same name but not in same case.
3449                 int nPos = sText.Find('|');
3450                 if (-1 != nPos)
3451                 {
3452                         sText = sText.Left(nPos);
3453                 }
3454
3455                 // Set the edit control with the updated text.
3456                 CEdit *pEdit = m_pList->GetEditControl();
3457                 ASSERT(pEdit != nullptr);
3458                 pEdit->SetWindowText(sText);
3459
3460                 m_bUserCancelEdit = false;
3461         }
3462 }
3463
3464 /**
3465  * @brief Called when user done with item label edit.
3466  *
3467  */
3468 afx_msg void CDirView::OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
3469 {
3470         *pResult = FALSE;
3471
3472         // We can't use the normal condition of pszText==`nullptr` to know if the
3473         // user cancels editing when file names had different case (e.g.
3474         // "file.txt|FILE.txt"). The edit text was changed to "file.txt" and
3475         // if the user accept it as the new file name, pszText is `nullptr`.
3476
3477         if (!m_bUserCancelEdit)
3478         {
3479                 CEdit *pEdit = m_pList->GetEditControl();
3480                 ASSERT(pEdit != nullptr);
3481
3482                 CString sText;
3483                 pEdit->GetWindowText(sText);
3484
3485                 if (!sText.IsEmpty() && paths::IsValidName(String(sText)))
3486                 {
3487                         try {
3488                                 DirItemIterator it(m_pIList.get(), reinterpret_cast<NMLVDISPINFO *>(pNMHDR)->item.iItem);
3489                                 DIFFITEM& di = *it;
3490                                 unsigned sideFlags = (di.diffcode.diffcode & DIFFCODE::SIDEFLAGS);
3491                                 *pResult = DoItemRename(it, GetDiffContext(), String(sText));
3492                                 // Rescan the item if side flags change due to renaming.
3493                                 if (*pResult)
3494                                 {
3495                                         if ((di.diffcode.diffcode & DIFFCODE::SIDEFLAGS) != sideFlags)
3496                                         {
3497                                                 // Delete the item with the same file name as after renaming.
3498                                                 if (di.HasParent())
3499                                                 {
3500                                                         for (DIFFITEM* pItem = di.GetParentLink()->GetFirstChild(); pItem != nullptr; pItem = pItem->GetFwdSiblingLink())
3501                                                         {
3502                                                                 if ((pItem != &di) && (pItem->diffcode.isDirectory() == di.diffcode.isDirectory()) && (collstr(pItem->diffFileInfo[0].filename, di.diffFileInfo[0].filename, false) == 0))
3503                                                                 {
3504                                                                         pItem->DelinkFromSiblings();
3505                                                                         delete pItem;
3506                                                                         break;
3507                                                                 }
3508                                                         }
3509                                                 }
3510                                                 // Rescan the item.
3511                                                 MarkForRescan(di);
3512                                                 m_pSavedTreeState.reset(SaveTreeState(GetDiffContext()));
3513                                                 GetDocument()->SetMarkedRescan();
3514                                                 GetDocument()->Rescan();
3515                                         }
3516                                         else {
3517                                                 int nDirs = GetDiffContext().GetCompareDirs();
3518                                                 assert(nDirs == 2 || nDirs == 3);
3519                                                 UpdatePaths(nDirs, di);
3520
3521                                                 int nIdx = reinterpret_cast<NMLVDISPINFO*>(pNMHDR)->item.iItem;
3522                                                 UpdateDiffItemStatus(nIdx);
3523                                         }
3524                                 }
3525                         } catch (ContentsChangedException& e) {
3526                                 AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
3527                         }
3528                 }
3529                 else
3530                 {
3531                         LangMessageBox(IDS_ERROR_INVALID_DIR_FILE_NAME, MB_ICONWARNING);
3532                 }
3533         }
3534 }
3535
3536 void CDirView::OnODFindItem(NMHDR* pNMHDR, LRESULT* pResult)
3537 {
3538         NMLVFINDITEM* pFindItem = reinterpret_cast<NMLVFINDITEM*>(pNMHDR);
3539         if (pFindItem->lvfi.flags & LVFI_STRING)
3540         {
3541                 String text = strutils::makelower(pFindItem->lvfi.psz);
3542                 for (size_t i = pFindItem->iStart; i < m_listViewItems.size(); ++i)
3543                 {
3544                         DIFFITEM *di = GetItemKey(static_cast<int>(i));
3545                         String filename = strutils::makelower(di->diffFileInfo[0].filename);
3546                         if (di && _tcsncmp(text.c_str(), filename.c_str(), text.length()) == 0)
3547                         {
3548                                 *pResult = i;
3549                                 return;
3550                         }
3551                 }
3552         }
3553         *pResult = -1;
3554 }
3555
3556 /**
3557  * @brief Called when item is marked for rescan.
3558  * This function marks selected items for rescan and rescans them.
3559  */
3560 void CDirView::OnMarkedRescan()
3561 {
3562         std::for_each(SelBegin(), SelEnd(), MarkForRescan);
3563         if (std::distance(SelBegin(), SelEnd()) > 0)
3564         {
3565                 m_pSavedTreeState.reset(SaveTreeState(GetDiffContext()));
3566                 GetDocument()->SetMarkedRescan();
3567                 GetDocument()->Rescan();
3568         }
3569 }
3570
3571 /**
3572  * @brief Called to update the item count in the status bar
3573  */
3574 void CDirView::OnUpdateStatusNum(CCmdUI* pCmdUI)
3575 {
3576         String s; // text to display
3577
3578         int count = m_pList->GetItemCount();
3579         int focusItem = GetFocusedItem();
3580
3581         if (focusItem == -1)
3582         {
3583                 // No item has focus
3584                 // "Items: %1"
3585                 s = strutils::format_string1(_("Items: %1"), strutils::to_str(count));
3586         }
3587         else
3588         {
3589                 // Don't show number to special items
3590                 DIFFITEM *pos = GetItemKey(focusItem);
3591                 if (!IsDiffItemSpecial(pos))
3592                 {
3593                         // If compare is non-recursive reduce special items count
3594                         bool bRecursive = GetDiffContext().m_bRecursive;
3595                         if (!bRecursive)
3596                         {
3597                                 --focusItem;
3598                                 --count;
3599                         }
3600                         // "Item %1 of %2"
3601                         s = strutils::format_string2(_("Item %1 of %2"), 
3602                                         strutils::to_str(focusItem + 1), strutils::to_str(count));
3603                 }
3604         }
3605         pCmdUI->SetText(s.c_str());
3606 }
3607
3608 /**
3609  * @brief Show all hidden items.
3610  */
3611 void CDirView::OnViewShowHiddenItems()
3612 {
3613         CDiffContext& ctxt = GetDiffContext();
3614         SetItemViewFlag(GetDiffContext(), ViewCustomFlags::VISIBLE, ViewCustomFlags::VISIBILITY);
3615         ctxt.m_vCurrentlyHiddenItems.clear();
3616         Redisplay();
3617 }
3618
3619 /**
3620  * @brief Enable/Disable 'Show hidden items' menuitem.
3621  */
3622 void CDirView::OnUpdateViewShowHiddenItems(CCmdUI* pCmdUI)
3623 {
3624         const CDiffContext& ctxt = GetDiffContext();
3625         pCmdUI->Enable(ctxt.m_vCurrentlyHiddenItems.size() > 0);
3626 }
3627
3628 /**
3629  * @brief Toggle Tree Mode
3630  */
3631 void CDirView::OnViewTreeMode()
3632 {
3633         m_bTreeMode = !m_bTreeMode;
3634         m_dirfilter.tree_mode = m_bTreeMode;
3635         GetOptionsMgr()->SaveOption(OPT_TREE_MODE, m_bTreeMode); // reverse
3636         Redisplay();
3637 }
3638
3639 /**
3640  * @brief Check/Uncheck 'Tree Mode' menuitem.
3641  */
3642 void CDirView::OnUpdateViewTreeMode(CCmdUI* pCmdUI)
3643 {
3644         // Don't show Tree Mode as 'checked' if the
3645         // menu item is greyed out (disabled).  Its very confusing.
3646         if( GetDocument()->GetDiffContext().m_bRecursive ) {
3647                 pCmdUI->SetCheck(m_bTreeMode);
3648                 pCmdUI->Enable(TRUE);
3649         } else {
3650                 pCmdUI->SetCheck(FALSE);
3651                 pCmdUI->Enable(FALSE);
3652         }
3653 }
3654
3655 /**
3656  * @brief Expand all subfolders
3657  */
3658 void CDirView::OnViewExpandAllSubdirs()
3659 {
3660         ExpandAllSubdirs(GetDiffContext());
3661         Redisplay();
3662 }
3663
3664 /**
3665  * @brief Update "Expand All Subfolders" item
3666  */
3667 void CDirView::OnUpdateViewExpandAllSubdirs(CCmdUI* pCmdUI)
3668 {
3669         pCmdUI->Enable(m_bTreeMode && GetDiffContext().m_bRecursive);
3670 }
3671
3672 /**
3673  * @brief Collapse all subfolders
3674  */
3675 void CDirView::OnViewCollapseAllSubdirs()
3676 {
3677         CollapseAllSubdirs(GetDiffContext());
3678         Redisplay();
3679 }
3680
3681 /**
3682  * @brief Update "Collapse All Subfolders" item
3683  */
3684 void CDirView::OnUpdateViewCollapseAllSubdirs(CCmdUI* pCmdUI)
3685 {
3686         pCmdUI->Enable(m_bTreeMode && GetDiffContext().m_bRecursive);
3687 }
3688
3689 template <int pane1, int pane2>
3690 void CDirView::OnViewSwapPanes()
3691 {
3692         GetDocument()->Swap(pane1, pane2);
3693         Redisplay();
3694 }
3695
3696 template <int pane1, int pane2>
3697 void CDirView::OnUpdateViewSwapPanes(CCmdUI* pCmdUI)
3698 {
3699         pCmdUI->Enable(pane2 < GetDocument()->m_nDirs &&
3700                 GetDocument()->m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPLETED);
3701 }
3702
3703 /**
3704  * @brief Show/Hide different files/directories
3705  */
3706 void CDirView::OnOptionsShowDifferent() 
3707 {
3708         m_dirfilter.show_different = !m_dirfilter.show_different;
3709         GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT, m_dirfilter.show_different);
3710         Redisplay();
3711 }
3712
3713 /**
3714  * @brief Show/Hide identical files/directories
3715  */
3716 void CDirView::OnOptionsShowIdentical() 
3717 {
3718         m_dirfilter.show_identical = !m_dirfilter.show_identical;
3719         GetOptionsMgr()->SaveOption(OPT_SHOW_IDENTICAL, m_dirfilter.show_identical);
3720         Redisplay();
3721 }
3722
3723 /**
3724  * @brief Show/Hide left-only files/directories
3725  */
3726 void CDirView::OnOptionsShowUniqueLeft() 
3727 {
3728         m_dirfilter.show_unique_left = !m_dirfilter.show_unique_left;
3729         GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_LEFT, m_dirfilter.show_unique_left);
3730         Redisplay();
3731 }
3732
3733 /**
3734  * @brief Show/Hide middle-only files/directories
3735  */
3736 void CDirView::OnOptionsShowUniqueMiddle() 
3737 {
3738         m_dirfilter.show_unique_middle = !m_dirfilter.show_unique_middle;
3739         GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_MIDDLE, m_dirfilter.show_unique_middle);
3740         Redisplay();
3741 }
3742
3743 /**
3744  * @brief Show/Hide right-only files/directories
3745  */
3746 void CDirView::OnOptionsShowUniqueRight() 
3747 {
3748         m_dirfilter.show_unique_right = !m_dirfilter.show_unique_right;
3749         GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_RIGHT, m_dirfilter.show_unique_right);
3750         Redisplay();
3751 }
3752
3753 /**
3754  * @brief Show/Hide binary files
3755  */
3756 void CDirView::OnOptionsShowBinaries()
3757 {
3758         m_dirfilter.show_binaries = !m_dirfilter.show_binaries;
3759         GetOptionsMgr()->SaveOption(OPT_SHOW_BINARIES, m_dirfilter.show_binaries);
3760         Redisplay();
3761 }
3762
3763 /**
3764  * @brief Show/Hide skipped files/directories
3765  */
3766 void CDirView::OnOptionsShowSkipped()
3767 {
3768         m_dirfilter.show_skipped = !m_dirfilter.show_skipped;
3769         GetOptionsMgr()->SaveOption(OPT_SHOW_SKIPPED, m_dirfilter.show_skipped);
3770         Redisplay();
3771 }
3772
3773 /**
3774  * @brief Show/Hide different files/folders (Middle and right are identical)
3775  */
3776 void CDirView::OnOptionsShowDifferentLeftOnly() 
3777 {
3778         m_dirfilter.show_different_left_only = !m_dirfilter.show_different_left_only;
3779         GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_LEFT_ONLY, m_dirfilter.show_different_left_only);
3780         Redisplay();
3781 }
3782
3783 /**
3784  * @brief Show/Hide different files/folders (Left and right are identical)
3785  */
3786 void CDirView::OnOptionsShowDifferentMiddleOnly() 
3787 {
3788         m_dirfilter.show_different_middle_only = !m_dirfilter.show_different_middle_only;
3789         GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_MIDDLE_ONLY, m_dirfilter.show_different_middle_only);
3790         Redisplay();
3791 }
3792
3793 /**
3794  * @brief Show/Hide different files/folders (Left and middle are identical)
3795  */
3796 void CDirView::OnOptionsShowDifferentRightOnly() 
3797 {
3798         m_dirfilter.show_different_right_only = !m_dirfilter.show_different_right_only;
3799         GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT_RIGHT_ONLY, m_dirfilter.show_different_right_only);
3800         Redisplay();
3801 }
3802
3803 /**
3804  * @brief Show/Hide missing left only files/folders
3805  */
3806 void CDirView::OnOptionsShowMissingLeftOnly() 
3807 {
3808         m_dirfilter.show_missing_left_only = !m_dirfilter.show_missing_left_only;
3809         GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_LEFT_ONLY, m_dirfilter.show_missing_left_only);
3810         Redisplay();
3811 }
3812
3813 /**
3814  * @brief Show/Hide missing middle only files/folders
3815  */
3816 void CDirView::OnOptionsShowMissingMiddleOnly() 
3817 {
3818         m_dirfilter.show_missing_middle_only = !m_dirfilter.show_missing_middle_only;
3819         GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_MIDDLE_ONLY, m_dirfilter.show_missing_middle_only);
3820         Redisplay();
3821 }
3822
3823 /**
3824  * @brief Show/Hide missing right only files/folders
3825  */
3826 void CDirView::OnOptionsShowMissingRightOnly() 
3827 {
3828         m_dirfilter.show_missing_right_only = !m_dirfilter.show_missing_right_only;
3829         GetOptionsMgr()->SaveOption(OPT_SHOW_MISSING_RIGHT_ONLY, m_dirfilter.show_missing_right_only);
3830         Redisplay();
3831 }
3832
3833 void CDirView::OnUpdateOptionsShowdifferent(CCmdUI* pCmdUI) 
3834 {
3835         pCmdUI->SetCheck(m_dirfilter.show_different);
3836 }
3837
3838 void CDirView::OnUpdateOptionsShowidentical(CCmdUI* pCmdUI) 
3839 {
3840         pCmdUI->SetCheck(m_dirfilter.show_identical);
3841 }
3842
3843 void CDirView::OnUpdateOptionsShowuniqueleft(CCmdUI* pCmdUI) 
3844 {
3845         pCmdUI->SetCheck(m_dirfilter.show_unique_left);
3846 }
3847
3848 void CDirView::OnUpdateOptionsShowuniquemiddle(CCmdUI* pCmdUI) 
3849 {
3850         pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3851         pCmdUI->SetCheck(m_dirfilter.show_unique_middle);
3852 }
3853
3854 void CDirView::OnUpdateOptionsShowuniqueright(CCmdUI* pCmdUI) 
3855 {
3856         pCmdUI->SetCheck(m_dirfilter.show_unique_right);
3857 }
3858
3859 void CDirView::OnUpdateOptionsShowBinaries(CCmdUI* pCmdUI) 
3860 {
3861         pCmdUI->SetCheck(m_dirfilter.show_binaries);
3862 }
3863
3864 void CDirView::OnUpdateOptionsShowSkipped(CCmdUI* pCmdUI)
3865 {
3866         pCmdUI->SetCheck(m_dirfilter.show_skipped);
3867 }
3868
3869 void CDirView::OnUpdateOptionsShowDifferentLeftOnly(CCmdUI* pCmdUI) 
3870 {
3871         pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3872         pCmdUI->SetCheck(m_dirfilter.show_different_left_only);
3873 }
3874
3875 void CDirView::OnUpdateOptionsShowDifferentMiddleOnly(CCmdUI* pCmdUI) 
3876 {
3877         pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3878         pCmdUI->SetCheck(m_dirfilter.show_different_middle_only);
3879 }
3880
3881 void CDirView::OnUpdateOptionsShowDifferentRightOnly(CCmdUI* pCmdUI) 
3882 {
3883         pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3884         pCmdUI->SetCheck(m_dirfilter.show_different_right_only);
3885 }
3886
3887 void CDirView::OnUpdateOptionsShowMissingLeftOnly(CCmdUI* pCmdUI) 
3888 {
3889         pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3890         pCmdUI->SetCheck(m_dirfilter.show_missing_left_only);
3891 }
3892
3893 void CDirView::OnUpdateOptionsShowMissingMiddleOnly(CCmdUI* pCmdUI) 
3894 {
3895         pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3896         pCmdUI->SetCheck(m_dirfilter.show_missing_middle_only);
3897 }
3898
3899 void CDirView::OnUpdateOptionsShowMissingRightOnly(CCmdUI* pCmdUI) 
3900 {
3901         pCmdUI->Enable(GetDocument()->m_nDirs > 2);
3902         pCmdUI->SetCheck(m_dirfilter.show_missing_right_only);
3903 }
3904
3905 void CDirView::OnMergeCompare(UINT nID)
3906 {
3907         OpenSelection(nID == ID_MERGE_COMPARE ? GetDocument() : nullptr);
3908 }
3909
3910 template<SELECTIONTYPE seltype>
3911 void CDirView::OnMergeCompare2()
3912 {
3913         OpenSelection(seltype);
3914 }
3915
3916 void CDirView::OnMergeCompareNonHorizontally()
3917 {
3918         int sel1, sel2, sel3;
3919         if (!GetSelectedItems(&sel1, &sel2, &sel3))
3920                 return;
3921         DirSelectFilesDlg dlg;
3922         if (sel1 != -1)
3923                 dlg.m_pdi[0] = &GetDiffItem(sel1);
3924         if (sel2 != -1)
3925                 dlg.m_pdi[1] = &GetDiffItem(sel2);
3926         if (sel3 != -1)
3927                 dlg.m_pdi[2] = &GetDiffItem(sel3);
3928         if (dlg.DoModal() == IDOK && dlg.m_selectedButtons.size() > 0)
3929         {
3930                 CDirDoc *pDoc = GetDocument();
3931                 FileTextEncoding encoding[3];
3932                 DWORD dwFlags[3] = {};
3933                 PathContext paths;
3934                 for (int nIndex = 0; nIndex < static_cast<int>(dlg.m_selectedButtons.size()); ++nIndex)
3935                 {
3936                         int n = dlg.m_selectedButtons[nIndex];
3937                         dwFlags[nIndex] = FFILEOPEN_NOMRU | (pDoc->GetReadOnly(n % 3) ? FFILEOPEN_READONLY : 0);
3938                         if (dlg.m_pdi[n / 3])
3939                         {
3940                                 paths.SetPath(nIndex, GetItemFileName(pDoc->GetDiffContext(), *dlg.m_pdi[n / 3], n % 3));
3941                                 encoding[nIndex] = dlg.m_pdi[n / 3]->diffFileInfo[n % 3].encoding;
3942                         }
3943                 }
3944                 if (paths.GetSize() == 1)
3945                         paths.SetRight(_T(""));
3946                 Open(GetDocument(), paths, dwFlags, encoding);
3947         }
3948 }
3949
3950 void CDirView::OnMergeCompareAs(UINT nID)
3951 {
3952         OpenSelectionAs(nID);
3953 }
3954
3955 void CDirView::OnUpdateMergeCompare(CCmdUI *pCmdUI)
3956 {
3957         bool openableForDir = !((pCmdUI->m_nID >= ID_MERGE_COMPARE_TEXT && pCmdUI->m_nID <= ID_MERGE_COMPARE_WEBPAGE) ||
3958                 (pCmdUI->m_nID >= ID_UNPACKERS_FIRST && pCmdUI->m_nID <= ID_UNPACKERS_LAST));
3959
3960         DoUpdateOpen(SELECTIONTYPE_NORMAL, pCmdUI, openableForDir);
3961 }
3962
3963 template<SELECTIONTYPE seltype>
3964 void CDirView::OnUpdateMergeCompare2(CCmdUI *pCmdUI)
3965 {
3966         DoUpdateOpen(seltype, pCmdUI);
3967 }
3968
3969 void CDirView::OnUpdateNoUnpacker(CCmdUI *pCmdUI)
3970 {
3971         pCmdUI->Enable();
3972         pCmdUI->m_pMenu->DeleteMenu(pCmdUI->m_nID, MF_BYCOMMAND);
3973
3974         int sel = GetSingleSelectedItem();
3975         if (sel == -1 || GetItemKey(sel) == reinterpret_cast<DIFFITEM *>(SPECIAL_ITEM_POS))
3976                 return;
3977
3978         String filteredFilenames = GetDiffContext().GetFilteredFilenames(*GetItemKey(sel));
3979         CMainFrame::AppendPluginMenus(pCmdUI->m_pMenu, filteredFilenames,
3980                 FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
3981 }
3982
3983 void CDirView::OnViewCompareStatistics()
3984 {
3985         CompareStatisticsDlg dlg(GetDocument()->GetCompareStats());
3986         dlg.DoModal();
3987 }
3988
3989 /**
3990  * @brief Count left & right files, and number with editable text encoding
3991  * @param nLeft [out]  #files on left side selected
3992  * @param nLeftAffected [out]  #files on left side selected which can have text encoding changed
3993  * @param nRight [out]  #files on right side selected
3994  * @param nRightAffected [out]  #files on right side selected which can have text encoding changed
3995  *
3996  * Affected files include all except unicode files
3997  */
3998 void CDirView::FormatEncodingDialogDisplays(CLoadSaveCodepageDlg * dlg)
3999 {
4000         IntToIntMap currentCodepages = CountCodepages(SelBegin(), SelEnd(), GetDiffContext());
4001
4002         Counts left, middle, right;
4003         left = Count(&DirActions::IsItemEditableEncoding<SIDE_LEFT>);
4004         if (GetDocument()->m_nDirs > 2)
4005                 middle = Count(&DirActions::IsItemEditableEncoding<SIDE_MIDDLE>);
4006         right = Count(&DirActions::IsItemEditableEncoding<SIDE_RIGHT>);
4007
4008         // Format strings such as "25 of 30 Files Affected"
4009         String sLeftAffected = FormatFilesAffectedString(left.count, left.total);
4010         String sMiddleAffected = (GetDocument()->m_nDirs < 3) ? _T("") : FormatFilesAffectedString(middle.count, middle.total);
4011         String sRightAffected = FormatFilesAffectedString(right.count, right.total);
4012         dlg->SetLeftRightAffectStrings(sLeftAffected, sMiddleAffected, sRightAffected);
4013         int codepage = currentCodepages.FindMaxKey();
4014         dlg->SetCodepages(codepage);
4015 }
4016
4017 /**
4018  * @brief Display file encoding dialog to user & handle user's choices
4019  *
4020  * This handles DirView invocation, so multiple files may be affected
4021  */
4022 void CDirView::DoFileEncodingDialog()
4023 {
4024         CLoadSaveCodepageDlg dlg(GetDocument()->m_nDirs);
4025         // set up labels about what will be affected
4026         FormatEncodingDialogDisplays(&dlg);
4027         dlg.EnableSaveCodepage(false); // disallow setting a separate codepage for saving
4028
4029         // Invoke dialog
4030         if (dlg.DoModal() != IDOK)
4031                 return;
4032
4033         bool affected[3];
4034         affected[0] = dlg.DoesAffectLeft();
4035         affected[1] = dlg.DoesAffectMiddle();
4036         affected[SideToIndex(GetDiffContext(), SIDE_RIGHT)] = dlg.DoesAffectRight();
4037
4038         ApplyCodepage(SelBegin(), SelEnd(), GetDiffContext(), affected, dlg.GetLoadCodepage());
4039
4040         m_pList->InvalidateRect(nullptr);
4041         m_pList->UpdateWindow();
4042
4043         // TODO: We could loop through any active merge windows belonging to us
4044         // and see if any of their files are affected
4045         // but, if they've been edited, we cannot throw away the user's work?
4046 }
4047
4048 /**
4049  * @brief Display file encoding dialog & handle user's actions
4050  */
4051 void CDirView::OnFileEncoding()
4052 {
4053         DoFileEncodingDialog();
4054 }
4055
4056 /** @brief Open help from mainframe when user presses F1*/
4057 void CDirView::OnHelp()
4058 {
4059         theApp.ShowHelp(DirViewHelpLocation);
4060 }
4061
4062 /**
4063  * @brief true while user is editing a file name.
4064  */
4065 bool CDirView::IsLabelEdit() const
4066 {
4067         return (m_pList->GetEditControl() != nullptr);
4068 }
4069
4070 /**
4071  * @brief Allow edit "Paste" when renaming an item.
4072  */
4073 void CDirView::OnEditCopy()
4074 {
4075         CEdit *pEdit = m_pList->GetEditControl();
4076         if (pEdit != nullptr)
4077         {
4078                 pEdit->Copy();
4079         }
4080 }
4081
4082 /**
4083  * @brief Allow edit "Cut" when renaming an item.
4084  */
4085 void CDirView::OnEditCut()
4086 {
4087         CEdit *pEdit = m_pList->GetEditControl();
4088         if (pEdit != nullptr)
4089         {
4090                 pEdit->Cut();
4091         }
4092 }
4093
4094 /**
4095 * @brief Allow edit "Paste" when renaming an item.
4096  */
4097 void CDirView::OnEditPaste()
4098 {
4099         CEdit *pEdit = m_pList->GetEditControl();
4100         if (pEdit != nullptr)
4101         {
4102                 pEdit->Paste();
4103         }
4104 }
4105
4106 /**
4107  * @brief Allow edit "Undo" when renaming an item.
4108  */
4109 void CDirView::OnEditUndo()
4110 {
4111         CEdit *pEdit = m_pList->GetEditControl();
4112         if (pEdit != nullptr)
4113         {
4114                 pEdit->Undo();
4115         }
4116 }
4117
4118 /**
4119  * @brief Update the tool bar's "Undo" icon. It should be enabled when
4120  * renaming an item and undo is possible.
4121  */
4122 void CDirView::OnUpdateEditUndo(CCmdUI* pCmdUI)
4123 {
4124         CEdit *pEdit = m_pList->GetEditControl();
4125         pCmdUI->Enable(pEdit && pEdit->CanUndo());
4126 }
4127 /**
4128  * @brief Returns CShellContextMenu object that owns given HMENU.
4129  *
4130  * @param [in] hMenu Handle to the menu to check ownership of.
4131  * @return Either m_pShellContextMenuLeft, m_pShellContextMenuRight
4132  *   or `nullptr` if hMenu is not owned by these two.
4133  */
4134 CShellContextMenu* CDirView::GetCorrespondingShellContextMenu(HMENU hMenu) const
4135 {
4136         CShellContextMenu* pMenu = nullptr;
4137         if (m_pShellContextMenuLeft!=nullptr && hMenu == m_pShellContextMenuLeft->GetHMENU())
4138                 pMenu = m_pShellContextMenuLeft.get();
4139         else if (m_pShellContextMenuMiddle!=nullptr && hMenu == m_pShellContextMenuMiddle->GetHMENU())
4140                 pMenu = m_pShellContextMenuMiddle.get();
4141         else if (m_pShellContextMenuRight!=nullptr && hMenu == m_pShellContextMenuRight->GetHMENU())
4142                 pMenu = m_pShellContextMenuRight.get();
4143
4144         return pMenu;
4145 }
4146
4147 /**
4148  * @brief Handle messages related to correct menu working.
4149  *
4150  * We need to requery shell context menu each time we switch from context menu
4151  * for one side to context menu for other side. Here we check whether we need to
4152  * requery and call ShellContextMenuHandleMenuMessage.
4153  */
4154 LRESULT CDirView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
4155 {
4156         while (message == WM_INITMENUPOPUP)
4157         {
4158                 HMENU hMenu = (HMENU)wParam;
4159                 if (CShellContextMenu* pMenu = GetCorrespondingShellContextMenu(hMenu))
4160                 {
4161                         if (m_hCurrentMenu != hMenu)
4162                         {
4163                                 // re-query context menu once more, because if context menu was queried for right
4164                                 // group of files and we are showing menu for left group (or vice versa) menu will
4165                                 // be shown incorrectly
4166                                 // also, if context menu was last queried for right group of files and we are
4167                                 // invoking command for left command will be executed for right group (the last
4168                                 // group that menu was requested for)
4169                                 // may be a "feature" of Shell
4170
4171                                 pMenu->RequeryShellContextMenu();
4172                                 m_hCurrentMenu = hMenu;
4173                         }
4174                 }
4175                 break;
4176         }
4177
4178         CShellContextMenu* pMenu = GetCorrespondingShellContextMenu(m_hCurrentMenu);
4179
4180         if (pMenu != nullptr)
4181         {
4182                 LRESULT res = 0;
4183                 pMenu->HandleMenuMessage(message, wParam, lParam, res);
4184         }
4185
4186         return CListView::WindowProc(message, wParam, lParam);
4187 }
4188
4189 /**
4190  * @brief Implement background item coloring
4191  */
4192 void CDirView::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult) 
4193 {
4194         if (!m_bUseColors) {
4195                 return;
4196         }
4197
4198         LPNMLISTVIEW pNM = (LPNMLISTVIEW)pNMHDR;
4199         *pResult = CDRF_DODEFAULT;
4200
4201         if (pNM->hdr.code == NM_CUSTOMDRAW)
4202         {
4203                 LPNMLVCUSTOMDRAW lpC = (LPNMLVCUSTOMDRAW)pNMHDR;
4204
4205                 if (lpC->nmcd.dwDrawStage == CDDS_PREPAINT)
4206                 {
4207                         *pResult =  CDRF_NOTIFYITEMDRAW;
4208                         return;
4209                 }
4210
4211                 if (lpC->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)
4212                 {
4213                         *pResult = CDRF_NOTIFYITEMDRAW;
4214                         return;
4215                 }
4216
4217                 if (lpC->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM ))
4218                 {
4219                         GetColors (static_cast<int>(lpC->nmcd.dwItemSpec), lpC->iSubItem, lpC->clrTextBk, lpC->clrText);
4220                 }
4221         }
4222 }
4223
4224 /**
4225  * @brief Populate colors for items in view, depending on difference status
4226  */
4227 void CDirView::GetColors (int nRow, int nCol, COLORREF& clrBk, COLORREF& clrText) const
4228 {
4229         const DIFFITEM& di = GetDiffItem (nRow);
4230
4231         if (di.isEmpty())
4232         {
4233                 clrText = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_NORMALTEXT);
4234                 clrBk = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_BKGND);
4235         }
4236         else if (di.diffcode.isResultFiltered())
4237         {
4238                 clrText = m_cachedColors.clrDirItemFilteredText;
4239                 clrBk = m_cachedColors.clrDirItemFiltered;
4240         }
4241         else if (!IsItemExistAll(GetDiffContext(), di))
4242         {
4243                 clrText = m_cachedColors.clrDirItemNotExistAllText;
4244                 clrBk = m_cachedColors.clrDirItemNotExistAll;
4245         }
4246         else if (di.diffcode.isResultDiff())
4247         {
4248                 clrText = m_cachedColors.clrDirItemDiffText;
4249                 clrBk = m_cachedColors.clrDirItemDiff;
4250         }
4251         else if (di.diffcode.isResultSame())
4252         {
4253                 clrText = m_cachedColors.clrDirItemEqualText;
4254                 clrBk = m_cachedColors.clrDirItemEqual;
4255         }
4256         else
4257         {
4258                 clrText = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_NORMALTEXT);
4259                 clrBk = theApp.GetMainSyntaxColors()->GetColor(COLORINDEX_BKGND);
4260         }
4261 }
4262
4263 void CDirView::OnSearch()
4264 {
4265         CDirDoc *pDoc = GetDocument();
4266         m_pList->SetRedraw(FALSE);      // Turn off updating (better performance)
4267         int nRows = m_pList->GetItemCount();
4268         CDiffContext& ctxt = GetDiffContext();
4269
4270         for (int currRow = nRows - 1; currRow >= 0; currRow--)
4271         {
4272                 DIFFITEM *pos = GetItemKey(currRow);
4273                 if (IsDiffItemSpecial(pos))
4274                         continue;
4275
4276                 bool bFound = false;
4277                 DIFFITEM &di = GetDiffItem(currRow);
4278                 PathContext paths;
4279
4280                 for (int i = 0; i < pDoc->m_nDirs; i++)
4281                 {
4282                         if (di.diffcode.exists(i) && !di.diffcode.isDirectory())
4283                         {
4284                                 GetItemFileNames(currRow, &paths);
4285                                 UniMemFile ufile;
4286                                 if (!ufile.OpenReadOnly(paths[i]))
4287                                         continue;
4288
4289                                 ufile.SetUnicoding(di.diffFileInfo[i].encoding.m_unicoding);
4290                                 ufile.SetBom(di.diffFileInfo[i].encoding.m_bom);
4291                                 ufile.SetCodepage(di.diffFileInfo[i].encoding.m_codepage);
4292
4293                                 ufile.ReadBom();
4294
4295                                 String line;
4296                                 for (;;)
4297                                 {
4298                                         bool lossy = false;
4299                                         if (!ufile.ReadString(line, &lossy))
4300                                                 break;
4301                                         
4302                                         if (_tcsstr(line.c_str(), _T("DirView")))
4303                                         {
4304                                                 bFound = true;
4305                                                 break;
4306                                         }
4307                                 }
4308
4309                                 ufile.Close();
4310                                 if (bFound)
4311                                         break;
4312                         }
4313                 }
4314                 if (!bFound)
4315                 {
4316                         String hiddden_item_path = di.getItemRelativePath();
4317                         SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
4318                         DeleteItem(currRow);
4319                         ctxt.m_vCurrentlyHiddenItems.push_back(hiddden_item_path);
4320                 }
4321         }
4322         m_pList->SetRedraw(TRUE);       // Turn updating back on
4323 }
4324
4325 /**
4326  * @brief Drag files/directories from folder compare listing view.
4327  */
4328 void CDirView::OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult) 
4329 {
4330         std::list<String> list;
4331         CopyPathnamesForDragAndDrop(SelBegin(), SelEnd(), std::back_inserter(list), GetDiffContext());
4332         String filesForDroping = strutils::join(list.begin(), list.end(), _T("\n")) + _T("\n");
4333
4334         CSharedFile file(GMEM_DDESHARE | GMEM_MOVEABLE | GMEM_ZEROINIT);
4335         file.Write(filesForDroping.data(), static_cast<unsigned>((filesForDroping.length() + 1) * sizeof(TCHAR)));
4336         
4337         HGLOBAL hMem = GlobalReAlloc(file.Detach(), (filesForDroping.length() + 1) * sizeof(TCHAR), 0);
4338         if (hMem != nullptr) 
4339         {
4340                 COleDataSource* DropData = new COleDataSource();
4341                 DropData->CacheGlobalData(CF_UNICODETEXT, hMem);
4342                 DROPEFFECT de = DropData->DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE, nullptr);
4343         }
4344
4345         *pResult = 0;
4346 }
4347
4348 /// Assign column name, using string resource & current column ordering
4349 void CDirView::NameColumn(const DirColInfo *col, int subitem)
4350 {
4351         int phys = m_pColItems->ColLogToPhys(subitem);
4352         if (phys>=0)
4353         {
4354                 String s = col->GetDisplayName();
4355                 LV_COLUMN lvc;
4356                 lvc.mask = LVCF_TEXT;
4357                 lvc.pszText = const_cast<LPTSTR>(s.c_str());
4358                 m_pList->SetColumn(phys, &lvc);
4359         }
4360 }
4361
4362 /// Load column names from string table
4363 void CDirView::UpdateColumnNames()
4364 {
4365         int ncols = m_pColItems->GetColCount();
4366         for (int i=0; i<ncols; ++i)
4367         {
4368                 const DirColInfo* col = m_pColItems->GetDirColInfo(i);
4369                 if (col)
4370                         NameColumn(col, i);
4371         }
4372 }
4373
4374 /**
4375  * @brief Set alignment of columns.
4376  */
4377 void CDirView::SetColAlignments()
4378 {
4379         int ncols = m_pColItems->GetColCount();
4380         for (int i=0; i<ncols; ++i)
4381         {
4382                 const DirColInfo * col = m_pColItems->GetDirColInfo(i);
4383                 if (col)
4384                 {
4385                         LVCOLUMN lvc;
4386                         lvc.mask = LVCF_FMT;
4387                         lvc.fmt = col->alignment;
4388                         m_pList->SetColumn(m_pColItems->ColLogToPhys(i), &lvc);
4389                 }
4390         }
4391 }
4392
4393 CDirView::CompareState::CompareState(const CDiffContext *pCtxt, const DirViewColItems *pColItems, int sortCol, bool bSortAscending, bool bTreeMode)
4394 : pCtxt(pCtxt)
4395 , pColItems(pColItems)
4396 , sortCol(sortCol)
4397 , bSortAscending(bSortAscending)
4398 , bTreeMode(bTreeMode)
4399 {
4400 }
4401
4402 /// Compare two specified rows during a sort operation (windows callback)
4403 int CALLBACK CDirView::CompareState::CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
4404 {
4405         CompareState *pThis = reinterpret_cast<CompareState*>(lParamSort);
4406         // Sort special items always first in dir view
4407         if (lParam1 == -1)
4408                 return -1;
4409         if (lParam2 == -1)
4410                 return 1;
4411
4412         DIFFITEM *diffposl = (DIFFITEM *)lParam1;
4413         DIFFITEM *diffposr = (DIFFITEM *)lParam2;
4414         const DIFFITEM &ldi = pThis->pCtxt->GetDiffAt(diffposl);
4415         const DIFFITEM &rdi = pThis->pCtxt->GetDiffAt(diffposr);
4416         // compare 'left' and 'right' parameters as appropriate
4417         int retVal = pThis->pColItems->ColSort(pThis->pCtxt, pThis->sortCol, ldi, rdi, pThis->bTreeMode);
4418         // return compare result, considering sort direction
4419         return pThis->bSortAscending ? retVal : -retVal;
4420 }
4421
4422 /// Add new item to list view
4423 void CDirView::AddNewItem(int i, DIFFITEM *diffpos, int iImage, int iIndent)
4424 {
4425         ListViewOwnerDataItem lvItem;
4426         lvItem.iIndent = iIndent;
4427         lvItem.lParam = (LPARAM)diffpos;
4428         lvItem.iImage = iImage;
4429         if (i == static_cast<int>(m_listViewItems.size()))
4430                 m_listViewItems.push_back(lvItem);
4431         else
4432                 m_listViewItems.insert(m_listViewItems.begin() + i, lvItem);
4433 }
4434
4435 /**
4436  * @brief Update listview display of details for specified row
4437  * @note Customising shownd data should be done here
4438  */
4439 void CDirView::UpdateDiffItemStatus(UINT nIdx)
4440 {
4441         GetListCtrl().RedrawItems(nIdx, nIdx);
4442         const DIFFITEM& di = GetDiffItem(nIdx);
4443         if (di.diffcode.isDirectory())
4444         {
4445                 DirItemIterator it;
4446                 for (it = RevBegin(); it != RevEnd(); )
4447                 {
4448                         DIFFITEM& di2 = *it;
4449                         int cursel = it.m_sel;
4450                         ++it;
4451                         if (di2.IsAncestor(&di))
4452                         {
4453                                 if ((di2.diffcode.diffcode & DIFFCODE::SIDEFLAGS) == 0)
4454                                         DeleteItem(cursel, true);
4455                                 else
4456                                         GetListCtrl().RedrawItems(cursel, cursel);
4457                         }
4458                 }
4459         }
4460 }
4461
4462 static String rgDispinfoText[2]; // used in function below
4463
4464 /**
4465  * @brief Allocate a text buffer to assign to NMLVDISPINFO::item::pszText
4466  * Quoting from SDK Docs:
4467  *      If the LVITEM structure is receiving item text, the pszText and cchTextMax
4468  *      members specify the address and size of a buffer. You can either copy text to
4469  *      the buffer or assign the address of a string to the pszText member. In the
4470  *      latter case, you must not change or delete the string until the corresponding
4471  *      item text is deleted or two additional LVN_GETDISPINFO messages have been sent.
4472  */
4473 static LPTSTR NTAPI AllocDispinfoText(const String &s)
4474 {
4475         static int i = 0;
4476         LPCTSTR pszText = (rgDispinfoText[i] = s).c_str();
4477         i ^= 1;
4478         return (LPTSTR)pszText;
4479 }
4480
4481 /**
4482  * @brief Respond to LVN_GETDISPINFO message
4483  */
4484 void CDirView::ReflectGetdispinfo(NMLVDISPINFO *pParam)
4485 {
4486         int nIdx = pParam->item.iItem;
4487         if (nIdx >= static_cast<int>(m_listViewItems.size()))
4488                 return;
4489         DIFFITEM *key = reinterpret_cast<DIFFITEM*>(m_listViewItems[nIdx].lParam);
4490         int i = m_pColItems->ColPhysToLog(pParam->item.iSubItem);
4491         if (IsDiffItemSpecial(key))
4492         {
4493                 pParam->item.iImage = m_listViewItems[nIdx].iImage;
4494                 if (m_pColItems->IsColName(i))
4495                 {
4496                         pParam->item.pszText = _T("..");
4497                 }
4498                 return;
4499         }
4500         if (!GetDocument()->HasDiffs())
4501                 return;
4502         const CDiffContext &ctxt = GetDiffContext();
4503         const DIFFITEM &di = ctxt.GetDiffAt(key);
4504         if (pParam->item.mask & LVIF_TEXT)
4505         {
4506                 String s = m_pColItems->ColGetTextToDisplay(&ctxt, i, di);
4507                 pParam->item.pszText = AllocDispinfoText(s);
4508         }
4509         if (pParam->item.mask & LVIF_IMAGE)
4510         {
4511                 pParam->item.iImage = GetColImage(di);
4512                 if ((pParam->item.mask & LVIF_STATE) == 0)
4513                 {
4514                         // for WinXP
4515                         pParam->item.mask |= LVIF_STATE;
4516                         pParam->item.state = m_pList->GetItemState(nIdx, static_cast<UINT>(~LVIS_STATEIMAGEMASK));
4517                 }
4518         }
4519         if (pParam->item.mask & LVIF_INDENT)
4520         {
4521                 pParam->item.iIndent = m_listViewItems[nIdx].iIndent;
4522         }
4523         if (pParam->item.mask & LVIF_STATE)
4524         {
4525                 pParam->item.stateMask |= LVIS_STATEIMAGEMASK;
4526                 if (di.HasChildren())
4527                         pParam->item.state |= INDEXTOSTATEIMAGEMASK((di.customFlags & ViewCustomFlags::EXPANDED) ? 2 : 1);
4528         }
4529 }
4530
4531 /**
4532  * @brief User examines & edits which columns are displayed in dirview, and in which order
4533  */
4534 void CDirView::OnEditColumns()
4535 {
4536         bool bReset = false;
4537         CDirColsDlg::ColumnArray cols;
4538
4539         for (;;)
4540         {
4541                 CDirColsDlg dlg;
4542                 // List all the currently displayed columns
4543                 for (int col=0; col<GetListCtrl().GetHeaderCtrl()->GetItemCount(); ++col)
4544                 {
4545                         int l = m_pColItems->ColPhysToLog(col);
4546                         dlg.AddColumn(m_pColItems->GetColDisplayName(l), m_pColItems->GetColDescription(l), l, col);
4547                 }
4548                 // Now add all the columns not currently displayed
4549                 int l=0;
4550                 for (l=0; l<m_pColItems->GetColCount(); ++l)
4551                 {
4552                         if (m_pColItems->ColLogToPhys(l)==-1)
4553                         {
4554                                 dlg.AddColumn(m_pColItems->GetColDisplayName(l), m_pColItems->GetColDescription(l), l);
4555                         }
4556                 }
4557                 assert(m_pColItems->GetColCount() == dlg.GetColumns().size());
4558
4559                 // Add default order of columns for resetting to defaults
4560                 for (l = 0; l < m_pColItems->GetColCount(); ++l)
4561                 {
4562                         int phy = m_pColItems->GetColDefaultOrder(l);
4563                         dlg.AddDefColumn(m_pColItems->GetColDisplayName(l), l, phy);
4564                 }
4565
4566                 if (dlg.DoModal() != IDOK)
4567                         return;
4568
4569                 if (!dlg.GetShowAdditionalProperties())
4570                 {
4571                         bReset = dlg.m_bReset;
4572                         cols = dlg.GetColumns();
4573                         break;
4574                 }
4575                 
4576                 CDirAdditionalPropertiesDlg dlgAdditionalProperties(m_pColItems->GetAdditionalPropertyNames());
4577                 if (dlgAdditionalProperties.DoModal() == IDOK)
4578                 {
4579                         auto& selectedCanonicalNames = dlgAdditionalProperties.GetSelectedCanonicalNames();
4580                         GetOptionsMgr()->SaveOption(OPT_ADDITIONAL_PROPERTIES,
4581                                 strutils::join(selectedCanonicalNames.begin(), selectedCanonicalNames.end(), _T(" ")));
4582                         m_pColItems->SetAdditionalPropertyNames(selectedCanonicalNames);
4583                         m_pColItems->SaveColumnOrders();
4584                         GetDiffContext().m_pPropertySystem.reset(new PropertySystem(m_pColItems->GetAdditionalPropertyNames()));
4585                         GetDiffContext().ClearAllAdditionalProperties();
4586                         auto* pDoc = GetDocument();
4587                         ReloadColumns();
4588                 }
4589         } 
4590
4591         const String keyname = GetDocument()->m_nDirs < 3 ? OPT_DIRVIEW_COLUMN_WIDTHS : OPT_DIRVIEW3_COLUMN_WIDTHS;
4592         GetOptionsMgr()->SaveOption(keyname,
4593                 (bReset ? m_pColItems->ResetColumnWidths(GetDefColumnWidth()) :
4594                                 m_pColItems->SaveColumnWidths(std::bind(&CListCtrl::GetColumnWidth, m_pList, _1))));
4595
4596         // Reset our data to reflect the new data from the dialog
4597         m_pColItems->ClearColumnOrders();
4598         const int sortColumn = GetOptionsMgr()->GetInt((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
4599         std::vector<int> colorder(m_pColItems->GetColCount(), -1);
4600         for (CDirColsDlg::ColumnArray::const_iterator iter = cols.begin();
4601                 iter != cols.end(); ++iter)
4602         {
4603                 int log = iter->log_col; 
4604                 int phy = iter->phy_col;
4605                 colorder[log] = phy;
4606
4607                 // If sorted column was hidden, reset sorting
4608                 if (log == sortColumn && phy < 0)
4609                 {
4610                         GetOptionsMgr()->Reset((GetDocument()->m_nDirs < 3) ? OPT_DIRVIEW_SORT_COLUMN : OPT_DIRVIEW_SORT_COLUMN3);
4611                         GetOptionsMgr()->Reset(OPT_DIRVIEW_SORT_ASCENDING);
4612                 }
4613         }
4614
4615         m_pColItems->SetColumnOrdering(&colorder[0]);
4616
4617         if (m_pColItems->GetDispColCount() < 1)
4618         {
4619                 // Set them back to default if they didn't leave a column showing
4620                 // (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.)
4621                 m_pColItems->ResetColumnOrdering();
4622         }
4623         ReloadColumns();
4624         Redisplay();
4625 }
4626
4627 DirActions CDirView::MakeDirActions(DirActions::method_type func) const
4628 {
4629         const CDirDoc *pDoc = GetDocument();
4630         return DirActions(pDoc->GetDiffContext(), pDoc->GetReadOnly(), func);
4631 }
4632
4633 DirActions CDirView::MakeDirActions(DirActions::method_type2 func) const
4634 {
4635         const CDirDoc *pDoc = GetDocument();
4636         return DirActions(pDoc->GetDiffContext(), pDoc->GetReadOnly(), nullptr, func);
4637 }
4638
4639 const CDiffContext& CDirView::GetDiffContext() const
4640 {
4641         return GetDocument()->GetDiffContext();
4642 }
4643
4644 CDiffContext& CDirView::GetDiffContext()
4645 {
4646         return GetDocument()->GetDiffContext();
4647 }