OSDN Git Service

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