OSDN Git Service

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