OSDN Git Service

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