OSDN Git Service

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