OSDN Git Service

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