OSDN Git Service

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