OSDN Git Service

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