source: filezilla/trunk/fuentes/src/interface/RemoteListView.cpp @ 3185

Last change on this file since 3185 was 3185, checked in by jrpelegrina, 3 years ago

Update new version: 3.15.02

File size: 67.3 KB
Line 
1#include <filezilla.h>
2
3#define FILELISTCTRL_INCLUDE_TEMPLATE_DEFINITION
4
5#include "RemoteListView.h"
6#include "commandqueue.h"
7#include "queue.h"
8#include "filezillaapp.h"
9#include "inputdialog.h"
10#include "chmoddialog.h"
11#include "filter.h"
12#include <algorithm>
13#include <wx/dcclient.h>
14#include <wx/dnd.h>
15#include "dndobjects.h"
16#include "Options.h"
17#include "recursive_operation.h"
18#include "edithandler.h"
19#include "dragdropmanager.h"
20#include "drop_target_ex.h"
21#include <wx/clipbrd.h>
22#include "sizeformatting.h"
23#include "timeformatting.h"
24#ifdef __WXMSW__
25#include "shellapi.h"
26#include "commctrl.h"
27#endif
28
29class CRemoteListViewDropTarget : public CScrollableDropTarget<wxListCtrlEx>
30{
31public:
32        CRemoteListViewDropTarget(CRemoteListView* pRemoteListView)
33                : CScrollableDropTarget<wxListCtrlEx>(pRemoteListView)
34                , m_pRemoteListView(pRemoteListView),
35                  m_pFileDataObject(new wxFileDataObject()),
36                  m_pRemoteDataObject(new CRemoteDataObject()),
37                  m_pDataObject(new wxDataObjectComposite())
38        {
39                m_pDataObject->Add(m_pRemoteDataObject, true);
40                m_pDataObject->Add(m_pFileDataObject);
41                SetDataObject(m_pDataObject);
42        }
43
44        void ClearDropHighlight()
45        {
46                const int dropTarget = m_pRemoteListView->m_dropTarget;
47                if (dropTarget != -1)
48                {
49                        m_pRemoteListView->m_dropTarget = -1;
50#ifdef __WXMSW__
51                        m_pRemoteListView->SetItemState(dropTarget, 0, wxLIST_STATE_DROPHILITED);
52#else
53                        m_pRemoteListView->RefreshItem(dropTarget);
54#endif
55                }
56        }
57
58        virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def)
59        {
60                def = FixupDragResult(def);
61
62                if (def == wxDragError ||
63                        def == wxDragNone ||
64                        def == wxDragCancel)
65                        return def;
66
67                if (!m_pRemoteListView->m_pDirectoryListing)
68                        return wxDragError;
69
70                if (!GetData())
71                        return wxDragError;
72
73                CDragDropManager* pDragDropManager = CDragDropManager::Get();
74                if (pDragDropManager)
75                        pDragDropManager->pDropTarget = m_pRemoteListView;
76
77                if (m_pDataObject->GetReceivedFormat() == m_pFileDataObject->GetFormat())
78                {
79                        wxString subdir;
80                        int flags = 0;
81                        int hit = m_pRemoteListView->HitTest(wxPoint(x, y), flags, 0);
82                        if (hit != -1 && (flags & wxLIST_HITTEST_ONITEM))
83                        {
84                                int index = m_pRemoteListView->GetItemIndex(hit);
85                                if (index != -1 && m_pRemoteListView->m_fileData[index].comparison_flags != CComparableListing::fill)
86                                {
87                                        if (index == (int)m_pRemoteListView->m_pDirectoryListing->GetCount())
88                                                subdir = _T("..");
89                                        else if ((*m_pRemoteListView->m_pDirectoryListing)[index].is_dir())
90                                                subdir = (*m_pRemoteListView->m_pDirectoryListing)[index].name;
91                                }
92                        }
93
94                        m_pRemoteListView->m_pState->UploadDroppedFiles(m_pFileDataObject, subdir, false);
95                        return wxDragCopy;
96                }
97
98                // At this point it's the remote data object.
99
100                if (m_pRemoteDataObject->GetProcessId() != (int)wxGetProcessId())
101                {
102                        wxMessageBoxEx(_("Drag&drop between different instances of FileZilla has not been implemented yet."));
103                        return wxDragNone;
104                }
105
106                if (!m_pRemoteDataObject->GetServer().EqualsNoPass(*m_pRemoteListView->m_pState->GetServer()))
107                {
108                        wxMessageBoxEx(_("Drag&drop between different servers has not been implemented yet."));
109                        return wxDragNone;
110                }
111
112                // Find drop directory (if it exists)
113                wxString subdir;
114                int flags = 0;
115                int hit = m_pRemoteListView->HitTest(wxPoint(x, y), flags, 0);
116                if (hit != -1 && (flags & wxLIST_HITTEST_ONITEM))
117                {
118                        int index = m_pRemoteListView->GetItemIndex(hit);
119                        if (index != -1 && m_pRemoteListView->m_fileData[index].comparison_flags != CComparableListing::fill)
120                        {
121                                if (index == (int)m_pRemoteListView->m_pDirectoryListing->GetCount())
122                                        subdir = _T("..");
123                                else if ((*m_pRemoteListView->m_pDirectoryListing)[index].is_dir())
124                                        subdir = (*m_pRemoteListView->m_pDirectoryListing)[index].name;
125                        }
126                }
127
128                // Get target path
129                CServerPath target = m_pRemoteListView->m_pDirectoryListing->path;
130                if (subdir == _T(".."))
131                {
132                        if (target.HasParent())
133                                target = target.GetParent();
134                }
135                else if (!subdir.empty())
136                        target.AddSegment(subdir);
137
138                // Make sure target path is valid
139                if (target == m_pRemoteDataObject->GetServerPath())
140                {
141                        wxMessageBoxEx(_("Source and target of the drop operation are identical"));
142                        return wxDragNone;
143                }
144
145                const std::list<CRemoteDataObject::t_fileInfo>& files = m_pRemoteDataObject->GetFiles();
146                for (std::list<CRemoteDataObject::t_fileInfo>::const_iterator iter = files.begin(); iter != files.end(); ++iter)
147                {
148                        const CRemoteDataObject::t_fileInfo& info = *iter;
149                        if (info.dir)
150                        {
151                                CServerPath dir = m_pRemoteDataObject->GetServerPath();
152                                dir.AddSegment(info.name);
153                                if (dir == target)
154                                        return wxDragNone;
155                                else if (dir.IsParentOf(target, false))
156                                {
157                                        wxMessageBoxEx(_("A directory cannot be dragged into one of its subdirectories."));
158                                        return wxDragNone;
159                                }
160                        }
161                }
162
163                for (std::list<CRemoteDataObject::t_fileInfo>::const_iterator iter = files.begin(); iter != files.end(); ++iter)
164                {
165                        const CRemoteDataObject::t_fileInfo& info = *iter;
166                        m_pRemoteListView->m_pState->m_pCommandQueue->ProcessCommand(
167                                new CRenameCommand(m_pRemoteDataObject->GetServerPath(), info.name, target, info.name)
168                                );
169                }
170
171                // Refresh remote listing
172                m_pRemoteListView->m_pState->m_pCommandQueue->ProcessCommand(
173                        new CListCommand()
174                        );
175
176                return wxDragNone;
177        }
178
179        virtual bool OnDrop(wxCoord x, wxCoord y)
180        {
181                CScrollableDropTarget<wxListCtrlEx>::OnDrop(x, y);
182                ClearDropHighlight();
183
184                if (!m_pRemoteListView->m_pDirectoryListing)
185                        return false;
186
187                return true;
188        }
189
190        virtual int DisplayDropHighlight(wxPoint point)
191        {
192                DoDisplayDropHighlight(point);
193                return -1;
194        }
195
196        int DoDisplayDropHighlight(wxPoint point)
197        {
198                int flags;
199                int hit = m_pRemoteListView->HitTest(point, flags, 0);
200                if (!(flags & wxLIST_HITTEST_ONITEM))
201                        hit = -1;
202
203                if (hit != -1)
204                {
205                        int index = m_pRemoteListView->GetItemIndex(hit);
206                        if (index == -1 || m_pRemoteListView->m_fileData[index].comparison_flags == CComparableListing::fill)
207                                hit = -1;
208                        else if (index != (int)m_pRemoteListView->m_pDirectoryListing->GetCount())
209                        {
210                                if (!(*m_pRemoteListView->m_pDirectoryListing)[index].is_dir())
211                                        hit = -1;
212                                else
213                                {
214                                        const CDragDropManager* pDragDropManager = CDragDropManager::Get();
215                                        if (pDragDropManager && pDragDropManager->pDragSource == m_pRemoteListView)
216                                        {
217                                                if (m_pRemoteListView->GetItemState(hit, wxLIST_STATE_SELECTED))
218                                                        hit = -1;
219                                        }
220                                }
221                        }
222                }
223                if (hit != m_pRemoteListView->m_dropTarget)
224                {
225                        ClearDropHighlight();
226                        if (hit != -1)
227                        {
228                                m_pRemoteListView->m_dropTarget = hit;
229#ifdef __WXMSW__
230                                m_pRemoteListView->SetItemState(hit, wxLIST_STATE_DROPHILITED, wxLIST_STATE_DROPHILITED);
231#else
232                                m_pRemoteListView->RefreshItem(hit);
233#endif
234                        }
235                }
236                return hit;
237        }
238
239        virtual wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult def)
240        {
241                def = CScrollableDropTarget<wxListCtrlEx>::OnDragOver(x, y, def);
242
243                if (def == wxDragError ||
244                        def == wxDragNone ||
245                        def == wxDragCancel)
246                {
247                        ClearDropHighlight();
248                        return def;
249                }
250
251                if (!m_pRemoteListView->m_pDirectoryListing) {
252                        ClearDropHighlight();
253                        return wxDragNone;
254                }
255
256                const CServer* const pServer = m_pRemoteListView->m_pState->GetServer();
257                wxASSERT(pServer);
258
259                int hit = DoDisplayDropHighlight(wxPoint(x, y));
260                const CDragDropManager* pDragDropManager = CDragDropManager::Get();
261
262                if (hit == -1 && pDragDropManager &&
263                        pDragDropManager->remoteParent == m_pRemoteListView->m_pDirectoryListing->path &&
264                        *pServer == pDragDropManager->server)
265                        return wxDragNone;
266
267                return wxDragCopy;
268        }
269
270        virtual void OnLeave()
271        {
272                CScrollableDropTarget<wxListCtrlEx>::OnLeave();
273                ClearDropHighlight();
274        }
275
276        virtual wxDragResult OnEnter(wxCoord x, wxCoord y, wxDragResult def)
277        {
278                def = CScrollableDropTarget<wxListCtrlEx>::OnEnter(x, y, def);
279                return OnDragOver(x, y, def);
280        }
281
282protected:
283        CRemoteListView *m_pRemoteListView;
284        wxFileDataObject* m_pFileDataObject;
285        CRemoteDataObject* m_pRemoteDataObject;
286
287        wxDataObjectComposite* m_pDataObject;
288};
289
290class CInfoText : public wxWindow
291{
292public:
293        CInfoText(wxWindow* parent, const wxString& text)
294                : wxWindow(parent, wxID_ANY, wxPoint(0, 60), wxDefaultSize),
295                m_text(text)
296        {
297                SetForegroundColour(parent->GetForegroundColour());
298                SetBackgroundColour(parent->GetBackgroundColour());
299                GetTextExtent(m_text, &m_textSize.x, &m_textSize.y);
300        }
301
302        void SetText(const wxString &text)
303        {
304                if (text == m_text)
305                        return;
306
307                m_text = text;
308
309                GetTextExtent(m_text, &m_textSize.x, &m_textSize.y);
310        }
311
312        wxSize GetTextSize() const { return m_textSize; }
313
314        bool AcceptsFocus() const { return false; }
315protected:
316        wxString m_text;
317
318        void OnPaint(wxPaintEvent&)
319        {
320                wxPaintDC paintDc(this);
321
322                paintDc.SetFont(GetFont());
323                paintDc.SetTextForeground(GetForegroundColour());
324
325                paintDc.DrawText(m_text, 0, 0);
326        };
327
328        wxSize m_textSize;
329
330        DECLARE_EVENT_TABLE()
331};
332
333BEGIN_EVENT_TABLE(CInfoText, wxWindow)
334EVT_PAINT(CInfoText::OnPaint)
335END_EVENT_TABLE()
336
337BEGIN_EVENT_TABLE(CRemoteListView, CFileListCtrl<CGenericFileData>)
338        EVT_LIST_ITEM_ACTIVATED(wxID_ANY, CRemoteListView::OnItemActivated)
339        EVT_CONTEXT_MENU(CRemoteListView::OnContextMenu)
340        // Map both ID_DOWNLOAD and ID_ADDTOQUEUE to OnMenuDownload, code is identical
341        EVT_MENU(XRCID("ID_DOWNLOAD"), CRemoteListView::OnMenuDownload)
342        EVT_MENU(XRCID("ID_ADDTOQUEUE"), CRemoteListView::OnMenuDownload)
343        EVT_MENU(XRCID("ID_MKDIR"), CRemoteListView::OnMenuMkdir)
344        EVT_MENU(XRCID("ID_MKDIR_CHGDIR"), CRemoteListView::OnMenuMkdirChgDir)
345        EVT_MENU(XRCID("ID_NEW_FILE"), CRemoteListView::OnMenuNewfile)
346        EVT_MENU(XRCID("ID_DELETE"), CRemoteListView::OnMenuDelete)
347        EVT_MENU(XRCID("ID_RENAME"), CRemoteListView::OnMenuRename)
348        EVT_MENU(XRCID("ID_CHMOD"), CRemoteListView::OnMenuChmod)
349        EVT_KEY_DOWN(CRemoteListView::OnKeyDown)
350        EVT_SIZE(CRemoteListView::OnSize)
351        EVT_LIST_BEGIN_DRAG(wxID_ANY, CRemoteListView::OnBeginDrag)
352        EVT_MENU(XRCID("ID_EDIT"), CRemoteListView::OnMenuEdit)
353        EVT_MENU(XRCID("ID_ENTER"), CRemoteListView::OnMenuEnter)
354        EVT_MENU(XRCID("ID_GETURL"), CRemoteListView::OnMenuGeturl)
355        EVT_MENU(XRCID("ID_CONTEXT_REFRESH"), CRemoteListView::OnMenuRefresh)
356END_EVENT_TABLE()
357
358CRemoteListView::CRemoteListView(wxWindow* pParent, CState *pState, CQueueView* pQueue)
359        : CFileListCtrl<CGenericFileData>(pParent, pState, pQueue),
360        CStateEventHandler(pState)
361{
362        pState->RegisterHandler(this, STATECHANGE_REMOTE_DIR);
363        pState->RegisterHandler(this, STATECHANGE_APPLYFILTER);
364        pState->RegisterHandler(this, STATECHANGE_REMOTE_LINKNOTDIR);
365
366        m_dropTarget = -1;
367
368        m_pInfoText = 0;
369        m_pDirectoryListing = 0;
370
371        const unsigned long widths[6] = { 80, 75, 80, 100, 80, 80 };
372
373        AddColumn(_("Filename"), wxLIST_FORMAT_LEFT, widths[0], true);
374        AddColumn(_("Filesize"), wxLIST_FORMAT_RIGHT, widths[1]);
375        AddColumn(_("Filetype"), wxLIST_FORMAT_LEFT, widths[2]);
376        AddColumn(_("Last modified"), wxLIST_FORMAT_LEFT, widths[3]);
377        AddColumn(_("Permissions"), wxLIST_FORMAT_LEFT, widths[4]);
378        AddColumn(_("Owner/Group"), wxLIST_FORMAT_LEFT, widths[5]);
379        LoadColumnSettings(OPTION_REMOTEFILELIST_COLUMN_WIDTHS, OPTION_REMOTEFILELIST_COLUMN_SHOWN, OPTION_REMOTEFILELIST_COLUMN_ORDER);
380
381        InitSort(OPTION_REMOTEFILELIST_SORTORDER);
382
383        m_dirIcon = GetIconIndex(iconType::dir);
384        SetImageList(GetSystemImageList(), wxIMAGE_LIST_SMALL);
385
386        InitHeaderSortImageList();
387
388        SetDirectoryListing(0);
389
390        SetDropTarget(new CRemoteListViewDropTarget(this));
391
392        EnablePrefixSearch(true);
393}
394
395CRemoteListView::~CRemoteListView()
396{
397        wxString str = wxString::Format(_T("%d %d"), m_sortDirection, m_sortColumn);
398        COptions::Get()->SetOption(OPTION_REMOTEFILELIST_SORTORDER, str);
399}
400
401// See comment to OnGetItemText
402int CRemoteListView::OnGetItemImage(long item) const
403{
404        CRemoteListView *pThis = const_cast<CRemoteListView *>(this);
405        int index = GetItemIndex(item);
406        if (index == -1)
407                return -1;
408
409        int &icon = pThis->m_fileData[index].icon;
410
411        if (icon != -2)
412                return icon;
413
414        icon = pThis->GetIconIndex(iconType::file, (*m_pDirectoryListing)[index].name, false, (*m_pDirectoryListing)[index].is_dir());
415        return icon;
416}
417
418int CRemoteListView::GetItemIndex(unsigned int item) const
419{
420        if (item >= m_indexMapping.size())
421                return -1;
422
423        unsigned int index = m_indexMapping[item];
424        if (index >= m_fileData.size())
425                return -1;
426
427        return index;
428}
429
430bool CRemoteListView::IsItemValid(unsigned int item) const
431{
432        if (item >= m_indexMapping.size())
433                return false;
434
435        unsigned int index = m_indexMapping[item];
436        if (index >= m_fileData.size())
437                return false;
438
439        return true;
440}
441
442void CRemoteListView::UpdateDirectoryListing_Added(std::shared_ptr<CDirectoryListing> const& pDirectoryListing)
443{
444        const unsigned int to_add = pDirectoryListing->GetCount() - m_pDirectoryListing->GetCount();
445        m_pDirectoryListing = pDirectoryListing;
446
447        m_indexMapping[0] = pDirectoryListing->GetCount();
448
449        std::list<unsigned int> added;
450
451        CFilterManager filter;
452        const wxString path = m_pDirectoryListing->path.GetPath();
453
454        CGenericFileData last = m_fileData.back();
455        m_fileData.pop_back();
456
457        for (unsigned int i = pDirectoryListing->GetCount() - to_add; i < pDirectoryListing->GetCount(); ++i) {
458                const CDirentry& entry = (*pDirectoryListing)[i];
459                CGenericFileData data;
460                if (entry.is_dir()) {
461                        data.icon = m_dirIcon;
462#ifndef __WXMSW__
463                        if (entry.is_link())
464                                data.icon += 3;
465#endif
466                }
467                m_fileData.push_back(data);
468
469                if (filter.FilenameFiltered(entry.name, path, entry.is_dir(), entry.size, false, 0, entry.time))
470                        continue;
471
472                if (m_pFilelistStatusBar) {
473                        if (entry.is_dir())
474                                m_pFilelistStatusBar->AddDirectory();
475                        else
476                                m_pFilelistStatusBar->AddFile(entry.size);
477                }
478
479                // Find correct position in index mapping
480                std::vector<unsigned int>::iterator start = m_indexMapping.begin();
481                if (m_hasParent)
482                        ++start;
483                CFileListCtrl<CGenericFileData>::CSortComparisonObject compare = GetSortComparisonObject();
484                std::vector<unsigned int>::iterator insertPos = std::lower_bound(start, m_indexMapping.end(), i, compare);
485                compare.Destroy();
486
487                const int item = insertPos - m_indexMapping.begin();
488                m_indexMapping.insert(insertPos, i);
489
490                for (auto iter = added.begin(); iter != added.end(); ++iter) {
491                        unsigned int &pos = *iter;
492                        if (pos >= (unsigned int)item)
493                                ++pos;
494                }
495                added.push_back(item);
496        }
497
498        m_fileData.push_back(last);
499
500        std::list<bool> selected;
501        unsigned int start;
502        added.push_back(m_indexMapping.size());
503        start = added.front();
504
505        SetItemCount(m_indexMapping.size());
506
507        for (unsigned int i = start; i < m_indexMapping.size(); ++i) {
508                if (i == added.front()) {
509                        selected.push_front(false);
510                        added.pop_front();
511                }
512                bool is_selected = GetItemState(i, wxLIST_STATE_SELECTED) != 0;
513                selected.push_back(is_selected);
514
515                bool should_selected = selected.front();
516                selected.pop_front();
517                if (is_selected != should_selected)
518                        SetSelection(i, should_selected);
519        }
520
521        if (m_pFilelistStatusBar)
522                m_pFilelistStatusBar->SetHidden(m_pDirectoryListing->GetCount() + 1 - m_indexMapping.size());
523
524        wxASSERT(m_indexMapping.size() <= pDirectoryListing->GetCount() + 1);
525}
526
527void CRemoteListView::UpdateDirectoryListing_Removed(std::shared_ptr<CDirectoryListing> const& pDirectoryListing)
528{
529        unsigned int const countRemoved = m_pDirectoryListing->GetCount() - pDirectoryListing->GetCount();
530        if (!countRemoved) {
531                m_pDirectoryListing = pDirectoryListing;
532                return;
533        }
534        wxASSERT(!IsComparing());
535
536        std::list<unsigned int> removedItems;
537        {
538                // Get indexes of the removed items in the listing
539                unsigned int j = 0;
540                unsigned int i = 0;
541                while (i < pDirectoryListing->GetCount() && j < m_pDirectoryListing->GetCount()) {
542                        const CDirentry& oldEntry = (*m_pDirectoryListing)[j];
543                        const wxString& oldName = oldEntry.name;
544                        const wxString& newName = (*pDirectoryListing)[i].name;
545                        if (oldName == newName) {
546                                ++i;
547                                ++j;
548                                continue;
549                        }
550
551                        removedItems.push_back(j++);
552                }
553                for (; j < m_pDirectoryListing->GetCount(); ++j)
554                        removedItems.push_back(j);
555
556                wxASSERT(removedItems.size() == countRemoved);
557        }
558
559        std::list<int> selectedItems;
560
561        // Number of items left to remove
562        unsigned int toRemove = countRemoved;
563
564        std::list<int> removedIndexes;
565
566        const int size = m_indexMapping.size();
567        for (int i = size - 1; i >= 0; --i) {
568                bool removed = false;
569
570                unsigned int& index = m_indexMapping[i];
571
572                // j is the offset the index has to be adjusted
573                int j = 0;
574                for (std::list<unsigned int>::const_iterator iter = removedItems.begin(); iter != removedItems.end(); ++iter, ++j) {
575                        if (*iter > index)
576                                break;
577
578                        if (*iter == index) {
579                                removedIndexes.push_back(i);
580                                removed = true;
581                                --toRemove;
582                                break;
583                        }
584                }
585
586                // Get old selection
587                bool isSelected = GetItemState(i, wxLIST_STATE_SELECTED) != 0;
588
589                // Update statusbar info
590                if (removed && m_pFilelistStatusBar) {
591                        const CDirentry& oldEntry = (*m_pDirectoryListing)[index];
592                        if (isSelected) {
593                                if (oldEntry.is_dir())
594                                        m_pFilelistStatusBar->UnselectDirectory();
595                                else
596                                        m_pFilelistStatusBar->UnselectFile(oldEntry.size);
597                        }
598                        if (oldEntry.is_dir())
599                                m_pFilelistStatusBar->RemoveDirectory();
600                        else
601                                m_pFilelistStatusBar->RemoveFile(oldEntry.size);
602                }
603
604                // Update index
605                index -= j;
606
607                // Update selections
608                bool needSelection;
609                if (selectedItems.empty())
610                        needSelection = false;
611                else if (selectedItems.front() == i) {
612                        needSelection = true;
613                        selectedItems.pop_front();
614                }
615                else
616                        needSelection = false;
617
618                if (isSelected) {
619                        if (!needSelection && (toRemove || removed))
620                                SetSelection(i, false);
621
622                        if (!removed)
623                                selectedItems.push_back(i - toRemove);
624                }
625                else if (needSelection)
626                        SetSelection(i, true);
627        }
628
629        // Erase file data
630        for (std::list<unsigned int>::reverse_iterator iter = removedItems.rbegin(); iter != removedItems.rend(); ++iter) {
631                m_fileData.erase(m_fileData.begin() + *iter);
632        }
633
634        // Erase indexes
635        wxASSERT(!toRemove);
636        wxASSERT(removedIndexes.size() == countRemoved);
637        for (auto const& removedIndex : removedIndexes) {
638                m_indexMapping.erase(m_indexMapping.begin() + removedIndex);
639        }
640
641        wxASSERT(m_indexMapping.size() == pDirectoryListing->GetCount() + 1);
642
643        m_pDirectoryListing = pDirectoryListing;
644
645        if (m_pFilelistStatusBar)
646                m_pFilelistStatusBar->SetHidden(m_pDirectoryListing->GetCount() + 1 - m_indexMapping.size());
647
648        SaveSetItemCount(m_indexMapping.size());
649}
650
651bool CRemoteListView::UpdateDirectoryListing(std::shared_ptr<CDirectoryListing> const& pDirectoryListing)
652{
653        wxASSERT(!IsComparing());
654
655        const int unsure = pDirectoryListing->get_unsure_flags() & ~(CDirectoryListing::unsure_unknown);
656
657        if (!unsure)
658                return false;
659
660        if (unsure & CDirectoryListing::unsure_invalid)
661                return false;
662
663        if (!(unsure & ~(CDirectoryListing::unsure_dir_changed | CDirectoryListing::unsure_file_changed)))
664        {
665                if (m_sortColumn && m_sortColumn != 2)
666                {
667                        // If not sorted by file or type, changing file attributes can influence
668                        // sort order.
669                        return false;
670                }
671
672                if (CFilterManager::HasActiveFilters())
673                        return false;
674
675                wxASSERT(pDirectoryListing->GetCount() == m_pDirectoryListing->GetCount());
676                if (pDirectoryListing->GetCount() != m_pDirectoryListing->GetCount())
677                        return false;
678
679                m_pDirectoryListing = pDirectoryListing;
680
681                // We don't have to do anything
682                return true;
683        }
684
685        if (unsure & (CDirectoryListing::unsure_dir_added | CDirectoryListing::unsure_file_added))
686        {
687                if (unsure & (CDirectoryListing::unsure_dir_removed | CDirectoryListing::unsure_file_removed))
688                        return false; // Cannot handle both at the same time unfortunately
689                UpdateDirectoryListing_Added(pDirectoryListing);
690                return true;
691        }
692
693        wxASSERT(pDirectoryListing->GetCount() <= m_pDirectoryListing->GetCount());
694        UpdateDirectoryListing_Removed(pDirectoryListing);
695        return true;
696}
697
698void CRemoteListView::SetDirectoryListing(std::shared_ptr<CDirectoryListing> const& pDirectoryListing)
699{
700        CancelLabelEdit();
701
702        bool reset = false;
703        if (!pDirectoryListing || !m_pDirectoryListing)
704                reset = true;
705        else if (m_pDirectoryListing->path != pDirectoryListing->path)
706                reset = true;
707        else if (m_pDirectoryListing->m_firstListTime == pDirectoryListing->m_firstListTime && !IsComparing()
708                && m_pDirectoryListing->GetCount() > 200)
709        {
710                // Updated directory listing. Check if we can use process it in a different,
711                // more efficient way.
712                // Makes only sense for big listings though.
713                if (UpdateDirectoryListing(pDirectoryListing)) {
714                        wxASSERT(GetItemCount() == (int)m_indexMapping.size());
715                        wxASSERT(GetItemCount() <= (int)m_fileData.size());
716                        wxASSERT(GetItemCount() == (int)m_fileData.size() || CFilterManager::HasActiveFilters());
717                        wxASSERT(m_pDirectoryListing->GetCount() + 1 >= (unsigned int)GetItemCount());
718                        wxASSERT(m_indexMapping[0] == m_pDirectoryListing->GetCount());
719
720                        RefreshListOnly();
721
722                        return;
723                }
724        }
725
726        wxString prevFocused;
727        std::list<wxString> selectedNames;
728        bool ensureVisible = false;
729        if (reset) {
730                ResetSearchPrefix();
731
732                if (IsComparing() && m_pDirectoryListing)
733                        ExitComparisonMode();
734
735                ClearSelection();
736
737                prevFocused = m_pState->GetPreviouslyVisitedRemoteSubdir();
738                ensureVisible = !prevFocused.empty();
739        }
740        else {
741                // Remember which items were selected
742                selectedNames = RememberSelectedItems(prevFocused);
743        }
744
745        if (m_pFilelistStatusBar) {
746                m_pFilelistStatusBar->UnselectAll();
747                m_pFilelistStatusBar->SetConnected(pDirectoryListing != 0);
748        }
749
750        m_pDirectoryListing = pDirectoryListing;
751
752        m_fileData.clear();
753        m_indexMapping.clear();
754
755        int64_t totalSize{};
756        int unknown_sizes = 0;
757        int totalFileCount = 0;
758        int totalDirCount = 0;
759        int hidden = 0;
760
761        bool eraseBackground = false;
762        if (m_pDirectoryListing) {
763                SetInfoText();
764
765                m_indexMapping.push_back(m_pDirectoryListing->GetCount());
766
767                const wxString path = m_pDirectoryListing->path.GetPath();
768
769                CFilterManager filter;
770                for (unsigned int i = 0; i < m_pDirectoryListing->GetCount(); ++i) {
771                        const CDirentry& entry = (*m_pDirectoryListing)[i];
772                        CGenericFileData data;
773                        if (entry.is_dir()) {
774                                data.icon = m_dirIcon;
775#ifndef __WXMSW__
776                                if (entry.is_link())
777                                        data.icon += 3;
778#endif
779                        }
780                        m_fileData.push_back(data);
781
782                        if (filter.FilenameFiltered(entry.name, path, entry.is_dir(), entry.size, false, 0, entry.time)) {
783                                ++hidden;
784                                continue;
785                        }
786
787                        if (entry.is_dir())
788                                ++totalDirCount;
789                        else {
790                                if (entry.size == -1)
791                                        ++unknown_sizes;
792                                else
793                                        totalSize += entry.size;
794                                ++totalFileCount;
795                        }
796
797                        m_indexMapping.push_back(i);
798                }
799
800                CGenericFileData data;
801                data.icon = m_dirIcon;
802                m_fileData.push_back(data);
803        }
804        else {
805                eraseBackground = true;
806                SetInfoText();
807        }
808
809        if (m_pFilelistStatusBar)
810                m_pFilelistStatusBar->SetDirectoryContents(totalFileCount, totalDirCount, totalSize, unknown_sizes, hidden);
811
812        if (m_dropTarget != -1) {
813                bool resetDropTarget = false;
814                int index = GetItemIndex(m_dropTarget);
815                if (index == -1)
816                        resetDropTarget = true;
817                else if (index != (int)m_pDirectoryListing->GetCount())
818                        if (!(*m_pDirectoryListing)[index].is_dir())
819                                resetDropTarget = true;
820
821                if (resetDropTarget) {
822                        SetItemState(m_dropTarget, 0, wxLIST_STATE_DROPHILITED);
823                        m_dropTarget = -1;
824                }
825        }
826
827        if (!IsComparing()) {
828                if ((unsigned int)GetItemCount() > m_indexMapping.size())
829                        eraseBackground = true;
830                if ((unsigned int)GetItemCount() != m_indexMapping.size())
831                        SetItemCount(m_indexMapping.size());
832
833                if (GetItemCount() && reset) {
834                        EnsureVisible(0);
835                        SetItemState(0, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
836                }
837        }
838
839        SortList(-1, -1, false);
840
841        if (IsComparing()) {
842                m_originalIndexMapping.clear();
843                RefreshComparison();
844                ReselectItems(selectedNames, prevFocused, ensureVisible);
845        }
846        else {
847                ReselectItems(selectedNames, prevFocused, ensureVisible);
848                RefreshListOnly(eraseBackground);
849        }
850}
851
852// Filenames on VMS systems have a revision suffix, e.g.
853// foo.bar;1
854// foo.bar;2
855// foo.bar;3
856wxString StripVMSRevision(const wxString& name)
857{
858        int pos = name.Find(';', true);
859        if (pos < 1)
860                return name;
861
862        const int len = name.Len();
863        if (pos == len - 1)
864                return name;
865
866        int p = pos;
867        while (++p < len)
868        {
869                const wxChar& c = name[p];
870                if (c < '0' || c > '9')
871                        return name;
872        }
873
874        return name.Left(pos);
875}
876
877
878void CRemoteListView::OnItemActivated(wxListEvent &event)
879{
880        int const action = COptions::Get()->GetOptionVal(OPTION_DOUBLECLICK_ACTION_DIRECTORY);
881        if (!m_pState->IsRemoteIdle(action ? false : true)) {
882                wxBell();
883                return;
884        }
885
886        int count = 0;
887        bool back = false;
888
889        int item = -1;
890        for (;;) {
891                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
892                if (item == -1)
893                        break;
894
895                int index = GetItemIndex(item);
896                if (index == -1 || m_fileData[index].comparison_flags == fill)
897                        continue;
898
899                count++;
900
901                if (!item)
902                        back = true;
903        }
904        if (!count) {
905                wxBell();
906                return;
907        }
908        if (count > 1) {
909                if (back) {
910                        wxBell();
911                        return;
912                }
913
914                wxCommandEvent cmdEvent;
915                OnMenuDownload(cmdEvent);
916                return;
917        }
918
919        item = event.GetIndex();
920
921        if (item) {
922                int index = GetItemIndex(item);
923                if (index == -1)
924                        return;
925                if (m_fileData[index].comparison_flags == fill)
926                        return;
927
928                const CDirentry& entry = (*m_pDirectoryListing)[index];
929                const wxString& name = entry.name;
930
931                const CServer* pServer = m_pState->GetServer();
932                if (!pServer) {
933                        wxBell();
934                        return;
935                }
936
937                if (entry.is_dir()) {
938                        if (action == 3) {
939                                // No action
940                                wxBell();
941                                return;
942                        }
943
944                        if (!action) {
945                                if (entry.is_link()) {
946                                        m_pLinkResolveState.reset(new t_linkResolveState);
947                                        m_pLinkResolveState->remote_path = m_pDirectoryListing->path;
948                                        m_pLinkResolveState->link = name;
949                                        m_pLinkResolveState->local_path = m_pState->GetLocalDir();
950                                        m_pLinkResolveState->server = *pServer;
951                                }
952                                m_pState->ChangeRemoteDir(m_pDirectoryListing->path, name, entry.is_link() ? LIST_FLAG_LINK : 0);
953                        }
954                        else {
955                                wxCommandEvent evt(0, action == 1 ? XRCID("ID_DOWNLOAD") : XRCID("ID_ADDTOQUEUE"));
956                                OnMenuDownload(evt);
957                        }
958                }
959                else {
960                        const int action = COptions::Get()->GetOptionVal(OPTION_DOUBLECLICK_ACTION_FILE);
961                        if (action == 3) {
962                                // No action
963                                wxBell();
964                                return;
965                        }
966
967                        if (action == 2) {
968                                // View / Edit action
969                                wxCommandEvent evt;
970                                OnMenuEdit(evt);
971                                return;
972                        }
973
974                        const bool queue_only = action == 1;
975
976                        const CLocalPath local_path = m_pState->GetLocalDir();
977                        if (!local_path.IsWriteable()) {
978                                wxBell();
979                                return;
980                        }
981
982                        wxString localFile = CQueueView::ReplaceInvalidCharacters(name);
983                        if (m_pDirectoryListing->path.GetType() == VMS && COptions::Get()->GetOptionVal(OPTION_STRIP_VMS_REVISION))
984                                localFile = StripVMSRevision(localFile);
985                        m_pQueue->QueueFile(queue_only, true, name,
986                                (name == localFile) ? wxString() : localFile,
987                                local_path, m_pDirectoryListing->path, *pServer, entry.size);
988                        m_pQueue->QueueFile_Finish(true);
989                }
990        }
991        else {
992                m_pState->ChangeRemoteDir(m_pDirectoryListing->path, _T(".."));
993        }
994}
995
996void CRemoteListView::OnMenuEnter(wxCommandEvent &)
997{
998        if (!m_pState->IsRemoteIdle(true)) {
999                wxBell();
1000                return;
1001        }
1002
1003        int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1004
1005        if (GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1) {
1006                wxBell();
1007                return;
1008        }
1009
1010        if (item) {
1011                int index = GetItemIndex(item);
1012                if (index == -1) {
1013                        wxBell();
1014                        return;
1015                }
1016                if (m_fileData[index].comparison_flags == fill) {
1017                        wxBell();
1018                        return;
1019                }
1020
1021                const CDirentry& entry = (*m_pDirectoryListing)[index];
1022                const wxString& name = entry.name;
1023
1024                const CServer* pServer = m_pState->GetServer();
1025                if (!pServer) {
1026                        wxBell();
1027                        return;
1028                }
1029
1030                if (!entry.is_dir()) {
1031                        wxBell();
1032                        return;
1033                }
1034
1035                if (entry.is_link()) {
1036                        m_pLinkResolveState.reset(new t_linkResolveState);
1037                        m_pLinkResolveState->remote_path = m_pDirectoryListing->path;
1038                        m_pLinkResolveState->link = name;
1039                        m_pLinkResolveState->local_path = m_pState->GetLocalDir();
1040                        m_pLinkResolveState->server = *pServer;
1041                }
1042                m_pState->ChangeRemoteDir(m_pDirectoryListing->path, name, entry.is_link() ? LIST_FLAG_LINK : 0);
1043        }
1044        else {
1045                m_pState->ChangeRemoteDir(m_pDirectoryListing->path, _T(".."));
1046        }
1047}
1048
1049void CRemoteListView::OnContextMenu(wxContextMenuEvent& event)
1050{
1051        if (GetEditControl()) {
1052                event.Skip();
1053                return;
1054        }
1055
1056        wxMenu* pMenu = wxXmlResource::Get()->LoadMenu(_T("ID_MENU_REMOTEFILELIST"));
1057        if (!pMenu)
1058                return;
1059
1060        bool const idle = m_pState->IsRemoteIdle();
1061        bool const userIdle = m_pState->IsRemoteIdle(true);
1062        if (!m_pState->IsRemoteConnected() || !idle) {
1063                bool canEnter = false;
1064                if (userIdle) {
1065                        int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1066                        if (item > 0 && GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == -1) {
1067                                int index = GetItemIndex(item);
1068                                if (index != -1 && m_fileData[index].comparison_flags != fill) {
1069                                        if ((*m_pDirectoryListing)[index].is_dir()) {
1070                                                canEnter = true;
1071                                        }
1072                                }
1073                                canEnter = true;
1074                        }
1075                }
1076                else {
1077                        pMenu->Enable(XRCID("ID_ENTER"), false);
1078                }
1079                if (!canEnter) {
1080                        pMenu->Delete(XRCID("ID_ENTER"));
1081                }
1082                pMenu->Enable(XRCID("ID_DOWNLOAD"), false);
1083                pMenu->Enable(XRCID("ID_ADDTOQUEUE"), false);
1084                pMenu->Enable(XRCID("ID_MKDIR"), false);
1085                pMenu->Enable(XRCID("ID_MKDIR_CHGDIR"), false);
1086                pMenu->Enable(XRCID("ID_DELETE"), false);
1087                pMenu->Enable(XRCID("ID_RENAME"), false);
1088                pMenu->Enable(XRCID("ID_CHMOD"), false);
1089                pMenu->Enable(XRCID("ID_EDIT"), false);
1090                pMenu->Enable(XRCID("ID_GETURL"), false);
1091                pMenu->Enable(XRCID("ID_CONTEXT_REFRESH"), false);
1092                pMenu->Enable(XRCID("ID_NEW_FILE"), false);
1093        }
1094        else if (GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == -1) {
1095                pMenu->Delete(XRCID("ID_ENTER"));
1096                pMenu->Enable(XRCID("ID_DOWNLOAD"), false);
1097                pMenu->Enable(XRCID("ID_ADDTOQUEUE"), false);
1098                pMenu->Enable(XRCID("ID_DELETE"), false);
1099                pMenu->Enable(XRCID("ID_RENAME"), false);
1100                pMenu->Enable(XRCID("ID_CHMOD"), false);
1101                pMenu->Enable(XRCID("ID_EDIT"), false);
1102                pMenu->Enable(XRCID("ID_GETURL"), false);
1103        }
1104        else {
1105                if ((GetItemCount() && GetItemState(0, wxLIST_STATE_SELECTED))) {
1106                        pMenu->Enable(XRCID("ID_RENAME"), false);
1107                        pMenu->Enable(XRCID("ID_CHMOD"), false);
1108                        pMenu->Enable(XRCID("ID_EDIT"), false);
1109                        pMenu->Enable(XRCID("ID_GETURL"), false);
1110                }
1111
1112                int count = 0;
1113                int fillCount = 0;
1114                bool selectedDir = false;
1115                int item = -1;
1116                while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
1117                        if (!item) {
1118                                ++count;
1119                                ++fillCount;
1120                                continue;
1121                        }
1122
1123                        int index = GetItemIndex(item);
1124                        if (index == -1)
1125                                continue;
1126                        count++;
1127                        if (m_fileData[index].comparison_flags == fill) {
1128                                fillCount++;
1129                                continue;
1130                        }
1131                        if ((*m_pDirectoryListing)[index].is_dir())
1132                                selectedDir = true;
1133                }
1134                if (!count || fillCount == count) {
1135                        pMenu->Delete(XRCID("ID_ENTER"));
1136                        pMenu->Enable(XRCID("ID_DOWNLOAD"), false);
1137                        pMenu->Enable(XRCID("ID_ADDTOQUEUE"), false);
1138                        pMenu->Enable(XRCID("ID_DELETE"), false);
1139                        pMenu->Enable(XRCID("ID_RENAME"), false);
1140                        pMenu->Enable(XRCID("ID_CHMOD"), false);
1141                        pMenu->Enable(XRCID("ID_EDIT"), false);
1142                        pMenu->Enable(XRCID("ID_GETURL"), false);
1143                }
1144                else {
1145                        if (selectedDir)
1146                                pMenu->Enable(XRCID("ID_EDIT"), false);
1147                        else
1148                                pMenu->Delete(XRCID("ID_ENTER"));
1149                        if (count > 1) {
1150                                if (selectedDir)
1151                                        pMenu->Delete(XRCID("ID_ENTER"));
1152                                pMenu->Enable(XRCID("ID_RENAME"), false);
1153                        }
1154
1155                        if (!m_pState->GetLocalDir().IsWriteable()) {
1156                                pMenu->Enable(XRCID("ID_DOWNLOAD"), false);
1157                                pMenu->Enable(XRCID("ID_ADDTOQUEUE"), false);
1158                        }
1159                }
1160        }
1161
1162        PopupMenu(pMenu);
1163        delete pMenu;
1164}
1165
1166void CRemoteListView::OnMenuDownload(wxCommandEvent& event)
1167{
1168        // Make sure selection is valid
1169        bool idle = m_pState->IsRemoteIdle();
1170
1171        const CLocalPath localDir = m_pState->GetLocalDir();
1172        if (!localDir.IsWriteable()) {
1173                wxBell();
1174                return;
1175        }
1176
1177        long item = -1;
1178        for (;;) {
1179                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1180                if (item == -1)
1181                        break;
1182
1183                if (!item)
1184                        continue;
1185
1186                int index = GetItemIndex(item);
1187                if (index == -1)
1188                        continue;
1189                if (m_fileData[index].comparison_flags == fill)
1190                        continue;
1191                if ((*m_pDirectoryListing)[index].is_dir() && !idle) {
1192                        wxBell();
1193                        return;
1194                }
1195        }
1196
1197        TransferSelectedFiles(localDir, event.GetId() == XRCID("ID_ADDTOQUEUE"));
1198}
1199
1200void CRemoteListView::TransferSelectedFiles(const CLocalPath& local_parent, bool queueOnly)
1201{
1202        bool idle = m_pState->IsRemoteIdle();
1203
1204        CRecursiveOperation* pRecursiveOperation = m_pState->GetRecursiveOperationHandler();
1205        wxASSERT(pRecursiveOperation);
1206
1207        wxASSERT(local_parent.IsWriteable());
1208
1209        const CServer* pServer = m_pState->GetServer();
1210        if (!pServer) {
1211                wxBell();
1212                return;
1213        }
1214
1215        bool added = false;
1216        long item = -1;
1217
1218        recursion_root root(m_pDirectoryListing->path, false);
1219        for (;;) {
1220                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1221                if (item == -1)
1222                        break;
1223                if (!item)
1224                        continue;
1225
1226                int index = GetItemIndex(item);
1227                if (index == -1)
1228                        continue;
1229                if (m_fileData[index].comparison_flags == fill)
1230                        continue;
1231
1232                const CDirentry& entry = (*m_pDirectoryListing)[index];
1233                const wxString& name = entry.name;
1234
1235                if (entry.is_dir()) {
1236                        if (!idle)
1237                                continue;
1238                        CLocalPath local_path(local_parent);
1239                        local_path.AddSegment(CQueueView::ReplaceInvalidCharacters(name));
1240                        CServerPath remotePath = m_pDirectoryListing->path;
1241                        if (remotePath.AddSegment(name)) {
1242                                root.add_dir_to_visit(m_pDirectoryListing->path, name, local_path, entry.is_link());
1243                        }
1244                }
1245                else {
1246                        wxString localFile = CQueueView::ReplaceInvalidCharacters(name);
1247                        if (m_pDirectoryListing->path.GetType() == VMS && COptions::Get()->GetOptionVal(OPTION_STRIP_VMS_REVISION))
1248                                localFile = StripVMSRevision(localFile);
1249                        m_pQueue->QueueFile(queueOnly, true,
1250                                name, (name == localFile) ? wxString() : localFile,
1251                                local_parent, m_pDirectoryListing->path, *pServer, entry.size);
1252                        added = true;
1253                }
1254        }
1255        if (added)
1256                m_pQueue->QueueFile_Finish(!queueOnly);
1257
1258        if (!root.empty()) {
1259                pRecursiveOperation->AddRecursionRoot(std::move(root));
1260                if (IsComparing())
1261                        ExitComparisonMode();
1262                CFilterManager filter;
1263                pRecursiveOperation->StartRecursiveOperation(queueOnly ? CRecursiveOperation::recursive_addtoqueue : CRecursiveOperation::recursive_download,
1264                                                                                                         filter.GetActiveFilters(false), m_pDirectoryListing->path);
1265        }
1266}
1267
1268// Create a new Directory
1269void CRemoteListView::OnMenuMkdir(wxCommandEvent&)
1270{
1271        MenuMkdir();
1272}
1273
1274// Create a new Directory and enter the new Directory
1275void CRemoteListView::OnMenuMkdirChgDir(wxCommandEvent&)
1276{
1277        CServerPath newdir = MenuMkdir();
1278        if (!newdir.empty()) {
1279                m_pState->ChangeRemoteDir(newdir, wxString(), 0, true);
1280        }
1281}
1282
1283// Help-Function to create a new Directory
1284// Returns the name of the new directory
1285CServerPath CRemoteListView::MenuMkdir()
1286{
1287        if (!m_pDirectoryListing || !m_pState->IsRemoteIdle()) {
1288                wxBell();
1289                return CServerPath();
1290        }
1291
1292        CInputDialog dlg;
1293        if (!dlg.Create(this, _("Create directory"), _("Please enter the name of the directory which should be created:")))
1294                return CServerPath();
1295
1296        CServerPath path = m_pDirectoryListing->path;
1297
1298        // Append a long segment which does (most likely) not exist in the path and
1299        // replace it with "New directory" later. This way we get the exact position of
1300        // "New directory" and can preselect it in the dialog.
1301        wxString tmpName = _T("25CF809E56B343b5A12D1F0466E3B37A49A9087FDCF8412AA9AF8D1E849D01CF");
1302        if (path.AddSegment(tmpName)) {
1303                wxString pathName = path.GetPath();
1304                int pos = pathName.Find(tmpName);
1305                wxASSERT(pos != -1);
1306                wxString newName = _("New directory");
1307                pathName.Replace(tmpName, newName);
1308                dlg.SetValue(pathName);
1309                dlg.SelectText(pos, pos + newName.Length());
1310        }
1311
1312        const CServerPath oldPath = m_pDirectoryListing->path;
1313
1314        if (dlg.ShowModal() != wxID_OK)
1315                return CServerPath();
1316
1317        if (!m_pDirectoryListing || oldPath != m_pDirectoryListing->path ||
1318                !m_pState->IsRemoteIdle())
1319        {
1320                wxBell();
1321                return CServerPath();
1322        }
1323
1324        path = m_pDirectoryListing->path;
1325        if (!path.ChangePath(dlg.GetValue())) {
1326                wxBell();
1327                return CServerPath();
1328        }
1329
1330        m_pState->m_pCommandQueue->ProcessCommand(new CMkdirCommand(path));
1331
1332        // Return name of the New Directory
1333        return path;
1334}
1335
1336void CRemoteListView::OnMenuDelete(wxCommandEvent&)
1337{
1338        if (!m_pState->IsRemoteIdle()) {
1339                wxBell();
1340                return;
1341        }
1342
1343        int count_dirs = 0;
1344        int count_files = 0;
1345        bool selected_link = false;
1346
1347        long item = -1;
1348        for (;;) {
1349                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1350                if (!item)
1351                        continue;
1352                if (item == -1)
1353                        break;
1354
1355                if (!IsItemValid(item))
1356                {
1357                        wxBell();
1358                        return;
1359                }
1360
1361                int index = GetItemIndex(item);
1362                if (index == -1)
1363                        continue;
1364                if (m_fileData[index].comparison_flags == fill)
1365                        continue;
1366
1367                const CDirentry& entry = (*m_pDirectoryListing)[index];
1368                if (entry.is_dir())
1369                {
1370                        count_dirs++;
1371                        if (entry.is_link())
1372                                selected_link = true;
1373                }
1374                else
1375                        count_files++;
1376        }
1377
1378        wxString question;
1379        if (!count_dirs)
1380                question.Printf(wxPLURAL("Really delete %d file from the server?", "Really delete %d files from the server?", count_files), count_files);
1381        else if (!count_files)
1382                question.Printf(wxPLURAL("Really delete %d directory with its contents from the server?", "Really delete %d directories with their contents from the server?", count_dirs), count_dirs);
1383        else
1384        {
1385                wxString files = wxString::Format(wxPLURAL("%d file", "%d files", count_files), count_files);
1386                wxString dirs = wxString::Format(wxPLURAL("%d directory with its contents", "%d directories with their contents", count_dirs), count_dirs);
1387                question.Printf(_("Really delete %s and %s from the server?"), files, dirs);
1388        }
1389
1390        if (wxMessageBoxEx(question, _("Confirmation needed"), wxICON_QUESTION | wxYES_NO, this) != wxYES)
1391                return;
1392
1393        bool follow_symlink = false;
1394        if (selected_link)
1395        {
1396                wxDialogEx dlg;
1397                if (!dlg.Load(this, _T("ID_DELETE_SYMLINK")))
1398                {
1399                        wxBell();
1400                        return;
1401                }
1402                if (dlg.ShowModal() != wxID_OK)
1403                        return;
1404
1405                follow_symlink = XRCCTRL(dlg, "ID_RECURSE", wxRadioButton)->GetValue();
1406        }
1407
1408        CRecursiveOperation* pRecursiveOperation = m_pState->GetRecursiveOperationHandler();
1409        wxASSERT(pRecursiveOperation);
1410
1411        std::deque<wxString> filesToDelete;
1412
1413        recursion_root root(m_pDirectoryListing->path, false);
1414        for (item = -1; ;) {
1415                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1416                if (!item)
1417                        continue;
1418                if (item == -1)
1419                        break;
1420
1421                int index = GetItemIndex(item);
1422                if (index == -1)
1423                        continue;
1424                if (m_fileData[index].comparison_flags == fill)
1425                        continue;
1426
1427                const CDirentry& entry = (*m_pDirectoryListing)[index];
1428                const wxString& name = entry.name;
1429
1430                if (entry.is_dir() && (follow_symlink || !entry.is_link())) {
1431                        CServerPath remotePath = m_pDirectoryListing->path;
1432                        if (remotePath.AddSegment(name)) {
1433                                root.add_dir_to_visit(m_pDirectoryListing->path, name, CLocalPath(), true);;
1434                        }
1435                }
1436                else
1437                        filesToDelete.push_back(name);
1438        }
1439
1440        if (!filesToDelete.empty())
1441                m_pState->m_pCommandQueue->ProcessCommand(new CDeleteCommand(m_pDirectoryListing->path, std::move(filesToDelete)));
1442
1443        if (!root.empty()) {
1444                if (IsComparing())
1445                        ExitComparisonMode();
1446                pRecursiveOperation->AddRecursionRoot(std::move(root));
1447               
1448                CFilterManager filter;
1449                pRecursiveOperation->StartRecursiveOperation(CRecursiveOperation::recursive_delete,
1450                                                                                                         filter.GetActiveFilters(false), m_pDirectoryListing->path);
1451        }
1452}
1453
1454void CRemoteListView::OnMenuRename(wxCommandEvent&)
1455{
1456        if (GetEditControl()) {
1457                GetEditControl()->SetFocus();
1458                return;
1459        }
1460
1461        if (!m_pState->IsRemoteIdle()) {
1462                wxBell();
1463                return;
1464        }
1465
1466        int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1467        if (item <= 0) {
1468                wxBell();
1469                return;
1470        }
1471
1472        if (GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1) {
1473                wxBell();
1474                return;
1475        }
1476
1477        int index = GetItemIndex(item);
1478        if (index == -1 || m_fileData[index].comparison_flags == fill) {
1479                wxBell();
1480                return;
1481        }
1482
1483        EditLabel(item);
1484}
1485
1486void CRemoteListView::OnKeyDown(wxKeyEvent& event)
1487{
1488#ifdef __WXMAC__
1489#define CursorModifierKey wxMOD_CMD
1490#else
1491#define CursorModifierKey wxMOD_ALT
1492#endif
1493
1494        int code = event.GetKeyCode();
1495        if (code == WXK_DELETE || code == WXK_NUMPAD_DELETE) {
1496                if (GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == -1) {
1497                        wxBell();
1498                        return;
1499                }
1500
1501                wxCommandEvent tmp;
1502                OnMenuDelete(tmp);
1503                return;
1504        }
1505        else if (code == WXK_F2) {
1506                wxCommandEvent tmp;
1507                OnMenuRename(tmp);
1508        }
1509        else if (code == WXK_RIGHT && event.GetModifiers() == CursorModifierKey) {
1510                wxListEvent evt;
1511                evt.m_itemIndex = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
1512                OnItemActivated(evt);
1513        }
1514        else if (code == WXK_DOWN && event.GetModifiers() == CursorModifierKey) {
1515                wxCommandEvent cmdEvent;
1516                OnMenuDownload(cmdEvent);
1517        }
1518        else if (code == 'N' && event.GetModifiers() == (wxMOD_CONTROL | wxMOD_SHIFT)) {
1519                MenuMkdir();
1520        }
1521        else
1522                event.Skip();
1523}
1524
1525bool CRemoteListView::OnBeginRename(const wxListEvent& event)
1526{
1527        if (!m_pState->IsRemoteIdle())
1528        {
1529                wxBell();
1530                return false;
1531        }
1532
1533        if (!m_pDirectoryListing)
1534        {
1535                wxBell();
1536                return false;
1537        }
1538
1539        int item = event.GetIndex();
1540        if (!item)
1541                return false;
1542
1543        int index = GetItemIndex(item);
1544        if (index == -1 || m_fileData[index].comparison_flags == fill)
1545                return false;
1546
1547        return true;
1548}
1549
1550bool CRemoteListView::OnAcceptRename(const wxListEvent& event)
1551{
1552        if (!m_pState->IsRemoteIdle())
1553        {
1554                wxBell();
1555                return false;
1556        }
1557
1558        if (!m_pDirectoryListing)
1559        {
1560                wxBell();
1561                return false;
1562        }
1563
1564        int item = event.GetIndex();
1565        if (!item)
1566                return false;
1567
1568        int index = GetItemIndex(item);
1569        if (index == -1 || m_fileData[index].comparison_flags == fill)
1570        {
1571                wxBell();
1572                return false;
1573        }
1574
1575        const CDirentry& entry = (*m_pDirectoryListing)[index];
1576
1577        wxString newFile = event.GetLabel();
1578
1579        CServerPath newPath = m_pDirectoryListing->path;
1580        if (!newPath.ChangePath(newFile, true))
1581        {
1582                wxMessageBoxEx(_("Filename invalid"), _("Cannot rename file"), wxICON_EXCLAMATION);
1583                return false;
1584        }
1585
1586        if (newPath == m_pDirectoryListing->path)
1587        {
1588                if (entry.name == newFile)
1589                        return false;
1590
1591                // Check if target file already exists
1592                for (unsigned int i = 0; i < m_pDirectoryListing->GetCount(); i++)
1593                {
1594                        if (newFile == (*m_pDirectoryListing)[i].name)
1595                        {
1596                                if (wxMessageBoxEx(_("Target filename already exists, really continue?"), _("File exists"), wxICON_QUESTION | wxYES_NO) != wxYES)
1597                                        return false;
1598
1599                                break;
1600                        }
1601                }
1602        }
1603
1604        m_pState->m_pCommandQueue->ProcessCommand(new CRenameCommand(m_pDirectoryListing->path, entry.name, newPath, newFile));
1605
1606        return true;
1607}
1608
1609void CRemoteListView::OnMenuChmod(wxCommandEvent&)
1610{
1611        if (!m_pState->IsRemoteConnected() || !m_pState->IsRemoteIdle()) {
1612                wxBell();
1613                return;
1614        }
1615
1616        int fileCount = 0;
1617        int dirCount = 0;
1618        wxString name;
1619
1620        char permissions[9] = {};
1621
1622        long item = -1;
1623        for (;;) {
1624                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1625                if (item == -1)
1626                        break;
1627
1628                if (!item)
1629                        return;
1630
1631                int index = GetItemIndex(item);
1632                if (index == -1)
1633                        return;
1634                if (m_fileData[index].comparison_flags == fill)
1635                        continue;
1636
1637                const CDirentry& entry = (*m_pDirectoryListing)[index];
1638
1639                if (entry.is_dir())
1640                        dirCount++;
1641                else
1642                        fileCount++;
1643                name = entry.name;
1644
1645                char file_perms[9];
1646                if (CChmodDialog::ConvertPermissions(*entry.permissions, file_perms)) {
1647                        for (int i = 0; i < 9; i++) {
1648                                if (!permissions[i] || permissions[i] == file_perms[i])
1649                                        permissions[i] = file_perms[i];
1650                                else
1651                                        permissions[i] = -1;
1652                        }
1653                }
1654        }
1655        if (!dirCount && !fileCount) {
1656                wxBell();
1657                return;
1658        }
1659
1660        for (int i = 0; i < 9; i++)
1661                if (permissions[i] == -1)
1662                        permissions[i] = 0;
1663
1664        CChmodDialog* pChmodDlg = new CChmodDialog;
1665        if (!pChmodDlg->Create(this, fileCount, dirCount, name, permissions)) {
1666                pChmodDlg->Destroy();
1667                return;
1668        }
1669
1670        if (pChmodDlg->ShowModal() != wxID_OK) {
1671                pChmodDlg->Destroy();
1672                return;
1673        }
1674
1675        // State may have changed while chmod dialog was shown
1676        if (!m_pState->IsRemoteConnected() || !m_pState->IsRemoteIdle()) {
1677                pChmodDlg->Destroy();
1678                wxBell();
1679                return;
1680        }
1681
1682        const int applyType = pChmodDlg->GetApplyType();
1683
1684        CRecursiveOperation* pRecursiveOperation = m_pState->GetRecursiveOperationHandler();
1685        wxASSERT(pRecursiveOperation);
1686        recursion_root root(m_pDirectoryListing->path, false);
1687
1688        item = -1;
1689        for (;;) {
1690                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1691                if (item == -1)
1692                        break;
1693
1694                if (!item) {
1695                        pChmodDlg->Destroy();
1696                        pChmodDlg = 0;
1697                        return;
1698                }
1699
1700                int index = GetItemIndex(item);
1701                if (index == -1) {
1702                        pChmodDlg->Destroy();
1703                        pChmodDlg = 0;
1704                        return;
1705                }
1706                if (m_fileData[index].comparison_flags == fill)
1707                        continue;
1708
1709                const CDirentry& entry = (*m_pDirectoryListing)[index];
1710
1711                if (!applyType ||
1712                        (!entry.is_dir() && applyType == 1) ||
1713                        (entry.is_dir() && applyType == 2))
1714                {
1715                        char newPermissions[9]{};
1716                        bool res = pChmodDlg->ConvertPermissions(*entry.permissions, newPermissions);
1717                        wxString newPerms = pChmodDlg->GetPermissions(res ? newPermissions : 0, entry.is_dir());
1718
1719                        m_pState->m_pCommandQueue->ProcessCommand(new CChmodCommand(m_pDirectoryListing->path, entry.name, newPerms));
1720                }
1721
1722                if (pChmodDlg->Recursive() && entry.is_dir())
1723                        root.add_dir_to_visit(m_pDirectoryListing->path, entry.name);
1724        }
1725
1726        if (pChmodDlg->Recursive()) {
1727                if (IsComparing())
1728                        ExitComparisonMode();
1729
1730                pRecursiveOperation->SetChmodDialog(pChmodDlg);
1731                pRecursiveOperation->AddRecursionRoot(std::move(root));
1732                CFilterManager filter;
1733                pRecursiveOperation->StartRecursiveOperation(CRecursiveOperation::recursive_chmod,
1734                                                                                                         filter.GetActiveFilters(false), m_pDirectoryListing->path);
1735
1736                // Refresh listing. This gets done implicitely by the recursive operation, so
1737                // only it if not recursing.
1738                if (pRecursiveOperation->GetOperationMode() != CRecursiveOperation::recursive_chmod)
1739                        m_pState->ChangeRemoteDir(m_pDirectoryListing->path);
1740        }
1741        else {
1742                pChmodDlg->Destroy();
1743                m_pState->ChangeRemoteDir(m_pDirectoryListing->path, _T(""), 0, true);
1744        }
1745
1746}
1747
1748void CRemoteListView::ApplyCurrentFilter()
1749{
1750        CFilterManager filter;
1751
1752        if (!filter.HasSameLocalAndRemoteFilters() && IsComparing())
1753                ExitComparisonMode();
1754
1755        if (m_fileData.size() <= 1)
1756                return;
1757
1758        wxString focused;
1759        std::list<wxString> selectedNames = RememberSelectedItems(focused);
1760
1761        if (m_pFilelistStatusBar)
1762                m_pFilelistStatusBar->UnselectAll();
1763
1764        int64_t totalSize{};
1765        int unknown_sizes = 0;
1766        int totalFileCount = 0;
1767        int totalDirCount = 0;
1768        int hidden = 0;
1769
1770        const wxString path = m_pDirectoryListing->path.GetPath();
1771
1772        m_indexMapping.clear();
1773        const unsigned int count = m_pDirectoryListing->GetCount();
1774        m_indexMapping.push_back(count);
1775        for (unsigned int i = 0; i < count; ++i) {
1776                const CDirentry& entry = (*m_pDirectoryListing)[i];
1777                if (filter.FilenameFiltered(entry.name, path, entry.is_dir(), entry.size, false, 0, entry.time)) {
1778                        ++hidden;
1779                        continue;
1780                }
1781
1782                if (entry.is_dir())
1783                        ++totalDirCount;
1784                else {
1785                        if (entry.size == -1)
1786                                ++unknown_sizes;
1787                        else
1788                                totalSize += entry.size;
1789                        ++totalFileCount;
1790                }
1791
1792                m_indexMapping.push_back(i);
1793        }
1794
1795        if (m_pFilelistStatusBar)
1796                m_pFilelistStatusBar->SetDirectoryContents(totalFileCount, totalDirCount, totalSize, unknown_sizes, hidden);
1797
1798        SetItemCount(m_indexMapping.size());
1799
1800        SortList(-1, -1, false);
1801
1802        if (IsComparing()) {
1803                m_originalIndexMapping.clear();
1804                RefreshComparison();
1805        }
1806
1807        ReselectItems(selectedNames, focused);
1808        if (!IsComparing())
1809                RefreshListOnly();
1810}
1811
1812std::list<wxString> CRemoteListView::RememberSelectedItems(wxString& focused)
1813{
1814        wxASSERT(GetItemCount() == (int)m_indexMapping.size());
1815        std::list<wxString> selectedNames;
1816        // Remember which items were selected
1817#ifndef __WXMSW__
1818        // GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
1819        if (GetSelectedItemCount())
1820#endif
1821        {
1822                int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1823                while (item != -1)
1824                {
1825                        SetSelection(item, false);
1826                        if (!item)
1827                        {
1828                                selectedNames.push_back(_T(".."));
1829                                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1830                                continue;
1831                        }
1832                        int index = GetItemIndex(item);
1833                        if (index == -1 || m_fileData[index].comparison_flags == fill)
1834                        {
1835                                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1836                                continue;
1837                        }
1838                        const CDirentry& entry = (*m_pDirectoryListing)[index];
1839
1840                        if (entry.is_dir())
1841                                selectedNames.push_back(_T("d") + entry.name);
1842                        else
1843                                selectedNames.push_back(_T("-") + entry.name);
1844
1845                        item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1846                }
1847        }
1848
1849        int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
1850        if (item != -1) {
1851                int index = GetItemIndex(item);
1852                if (index != -1 && m_fileData[index].comparison_flags != fill)
1853                {
1854                        if (!item)
1855                                focused = _T("..");
1856                        else
1857                                focused = (*m_pDirectoryListing)[index].name;
1858                }
1859
1860                SetItemState(item, 0, wxLIST_STATE_FOCUSED);
1861        }
1862
1863        return selectedNames;
1864}
1865
1866void CRemoteListView::ReselectItems(std::list<wxString>& selectedNames, wxString focused, bool ensureVisible)
1867{
1868        if (!GetItemCount())
1869                return;
1870
1871        if (focused == _T("..")) {
1872                focused = _T("");
1873                SetItemState(0, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1874        }
1875
1876        if (selectedNames.empty()) {
1877                if (focused.empty())
1878                        return;
1879
1880                for (unsigned int i = 1; i < m_indexMapping.size(); ++i) {
1881                        const int index = m_indexMapping[i];
1882                        if (m_fileData[index].comparison_flags == fill)
1883                                continue;
1884
1885                        if ((*m_pDirectoryListing)[index].name == focused) {
1886                                SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1887                                if (ensureVisible)
1888                                        EnsureVisible(i);
1889                                return;
1890                        }
1891                }
1892                return;
1893        }
1894
1895        if (selectedNames.front() == _T("..")) {
1896                selectedNames.pop_front();
1897                SetItemState(0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1898        }
1899
1900        int firstSelected = -1;
1901
1902        // Reselect previous items if neccessary.
1903        // Sorting direction did not change. We just have to scan through items once
1904        unsigned int i = 0;
1905        for (std::list<wxString>::const_iterator iter = selectedNames.begin(); iter != selectedNames.end(); ++iter)
1906        {
1907                while (++i < m_indexMapping.size())
1908                {
1909                        int index = GetItemIndex(i);
1910                        if (index == -1 || m_fileData[index].comparison_flags == fill)
1911                                continue;
1912                        const CDirentry& entry = (*m_pDirectoryListing)[index];
1913                        if (entry.name == focused)
1914                        {
1915                                SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1916                                if (ensureVisible)
1917                                        EnsureVisible(i);
1918                                focused = _T("");
1919                        }
1920                        if (entry.is_dir() && *iter == (_T("d") + entry.name))
1921                        {
1922                                if (firstSelected == -1)
1923                                        firstSelected = i;
1924                                if (m_pFilelistStatusBar)
1925                                        m_pFilelistStatusBar->SelectDirectory();
1926                                SetSelection(i, true);
1927                                break;
1928                        }
1929                        else if (*iter == (_T("-") + entry.name))
1930                        {
1931                                if (firstSelected == -1)
1932                                        firstSelected = i;
1933                                if (m_pFilelistStatusBar)
1934                                        m_pFilelistStatusBar->SelectFile(entry.size);
1935                                SetSelection(i, true);
1936                                break;
1937                        }
1938                }
1939                if (i == m_indexMapping.size())
1940                        break;
1941        }
1942        if (!focused.empty())
1943        {
1944                if (firstSelected != -1)
1945                        SetItemState(firstSelected, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1946                else
1947                        SetItemState(0, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1948        }
1949}
1950
1951void CRemoteListView::OnSize(wxSizeEvent& event)
1952{
1953        event.Skip();
1954        RepositionInfoText();
1955}
1956
1957void CRemoteListView::RepositionInfoText()
1958{
1959        if (!m_pInfoText)
1960                return;
1961
1962        wxRect rect = GetClientRect();
1963
1964        wxSize size = m_pInfoText->GetTextSize();
1965
1966        if (m_indexMapping.empty())
1967                rect.y = 60;
1968        else
1969        {
1970                wxRect itemRect;
1971                GetItemRect(0, itemRect);
1972                rect.y = wxMax(60, itemRect.GetBottom() + 1);
1973        }
1974        rect.x = rect.x + (rect.width - size.x) / 2;
1975        rect.width = size.x;
1976        rect.height = size.y;
1977
1978        m_pInfoText->SetSize(rect);
1979#ifdef __WXMSW__
1980        if (GetLayoutDirection() != wxLayout_RightToLeft)
1981        {
1982                m_pInfoText->Refresh(true);
1983                m_pInfoText->Update();
1984        }
1985        else
1986#endif
1987                m_pInfoText->Refresh(false);
1988
1989}
1990
1991void CRemoteListView::OnStateChange(CState* pState, enum t_statechange_notifications notification, const wxString& data, const void* data2)
1992{
1993        wxASSERT(pState);
1994        if (notification == STATECHANGE_REMOTE_DIR)
1995                SetDirectoryListing(pState->GetRemoteDir());
1996        else if (notification == STATECHANGE_REMOTE_LINKNOTDIR) {
1997                wxASSERT(data2);
1998                LinkIsNotDir(*(CServerPath*)data2, data);
1999        }
2000        else {
2001                wxASSERT(notification == STATECHANGE_APPLYFILTER);
2002                ApplyCurrentFilter();
2003        }
2004}
2005
2006void CRemoteListView::SetInfoText()
2007{
2008        wxString text;
2009        if (!IsComparing()) {
2010                if (!m_pDirectoryListing)
2011                        text = _("Not connected to any server");
2012                else if (m_pDirectoryListing->failed())
2013                        text = _("Directory listing failed");
2014                else if (!m_pDirectoryListing->GetCount())
2015                        text = _("Empty directory listing");
2016        }
2017
2018        if (text.empty()) {
2019                delete m_pInfoText;
2020                m_pInfoText = 0;
2021                return;
2022        }
2023
2024        if (!m_pInfoText) {
2025                m_pInfoText = new CInfoText(this, text);
2026#ifdef __WXMSW__
2027                if (GetLayoutDirection() != wxLayout_RightToLeft)
2028                        m_pInfoText->SetDoubleBuffered(true);
2029#endif
2030
2031                RepositionInfoText();
2032                return;
2033        }
2034
2035        m_pInfoText->SetText(text);
2036        RepositionInfoText();
2037}
2038
2039void CRemoteListView::OnBeginDrag(wxListEvent&)
2040{
2041        if (!m_pState->IsRemoteIdle()) {
2042                wxBell();
2043                return;
2044        }
2045
2046        if (GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == -1) {
2047                // Nothing selected
2048                return;
2049        }
2050
2051        bool idle = m_pState->m_pCommandQueue->Idle();
2052
2053        long item = -1;
2054        int count = 0;
2055        for (;;) {
2056                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2057                if (item == -1)
2058                        break;
2059
2060                if (!item) {
2061                        // Can't drag ".."
2062                        wxBell();
2063                        return;
2064                }
2065
2066                int index = GetItemIndex(item);
2067                if (index == -1 || m_fileData[index].comparison_flags == fill)
2068                        continue;
2069                if ((*m_pDirectoryListing)[index].is_dir() && !idle) {
2070                        // Drag could result in recursive operation, don't allow at this point
2071                        wxBell();
2072                        return;
2073                }
2074                ++count;
2075        }
2076        if (!count) {
2077                wxBell();
2078                return;
2079        }
2080
2081        wxDataObjectComposite object;
2082
2083        CServer const* pServer = m_pState->GetServer();
2084        if (!pServer)
2085                return;
2086        CServer const server = *pServer;
2087        CServerPath const path = m_pDirectoryListing->path;
2088
2089        CRemoteDataObject *pRemoteDataObject = new CRemoteDataObject(*pServer, m_pDirectoryListing->path);
2090
2091        CDragDropManager* pDragDropManager = CDragDropManager::Init();
2092        pDragDropManager->pDragSource = this;
2093        pDragDropManager->server = server;
2094        pDragDropManager->remoteParent = m_pDirectoryListing->path;
2095
2096        // Add files to remote data object
2097        item = -1;
2098        for (;;) {
2099                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2100                if (item == -1)
2101                        break;
2102
2103                int index = GetItemIndex(item);
2104                if (index == -1 || m_fileData[index].comparison_flags == fill)
2105                        continue;
2106                const CDirentry& entry = (*m_pDirectoryListing)[index];
2107
2108                pRemoteDataObject->AddFile(entry.name, entry.is_dir(), entry.size, entry.is_link());
2109        }
2110
2111        pRemoteDataObject->Finalize();
2112
2113        object.Add(pRemoteDataObject, true);
2114
2115#if FZ3_USESHELLEXT
2116        std::unique_ptr<CShellExtensionInterface> ext = CShellExtensionInterface::CreateInitialized();
2117        if (ext) {
2118                const wxString& file = ext->GetDragDirectory();
2119
2120                wxASSERT(!file.empty());
2121
2122                wxFileDataObject *pFileDataObject = new wxFileDataObject;
2123                pFileDataObject->AddFile(file);
2124
2125                object.Add(pFileDataObject);
2126        }
2127#endif
2128
2129        CLabelEditBlocker b(*this);
2130
2131        wxDropSource source(this);
2132        source.SetData(object);
2133
2134        int res = source.DoDragDrop();
2135
2136        pDragDropManager->Release();
2137
2138        if (res != wxDragCopy) {
2139                return;
2140        }
2141
2142#if FZ3_USESHELLEXT
2143        if (ext) {
2144                if (!pRemoteDataObject->DidSendData()) {
2145                        pServer = m_pState->GetServer();
2146                        if (!m_pState->IsRemoteIdle() ||
2147                                !pServer || *pServer != server ||
2148                                !m_pDirectoryListing || m_pDirectoryListing->path != path)
2149                        {
2150                                // Remote listing has changed since drag started
2151                                wxBell();
2152                                return;
2153                        }
2154
2155                        // Same checks as before
2156                        idle = m_pState->m_pCommandQueue->Idle();
2157
2158                        item = -1;
2159                        for (;;) {
2160                                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2161                                if (item == -1)
2162                                        break;
2163
2164                                if (!item) {
2165                                        // Can't drag ".."
2166                                        wxBell();
2167                                        return;
2168                                }
2169
2170                                int index = GetItemIndex(item);
2171                                if (index == -1 || m_fileData[index].comparison_flags == fill)
2172                                        continue;
2173                                if ((*m_pDirectoryListing)[index].is_dir() && !idle) {
2174                                        // Drag could result in recursive operation, don't allow at this point
2175                                        wxBell();
2176                                        return;
2177                                }
2178                        }
2179
2180                        CLocalPath target(ext->GetTarget());
2181                        if (target.empty()) {
2182                                ext.reset(); // Release extension before the modal message box
2183                                wxMessageBoxEx(_("Could not determine the target of the Drag&Drop operation.\nEither the shell extension is not installed properly or you didn't drop the files into an Explorer window."));
2184                                return;
2185                        }
2186
2187                        TransferSelectedFiles(target, false);
2188                }
2189        }
2190#endif
2191}
2192
2193void CRemoteListView::OnMenuEdit(wxCommandEvent&)
2194{
2195        if (!m_pState->IsRemoteConnected() || !m_pDirectoryListing) {
2196                wxBell();
2197                return;
2198        }
2199
2200        long item = -1;
2201
2202        std::vector<CEditHandler::FileData> selected_items;
2203        while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
2204                if (!item) {
2205                        wxBell();
2206                        return;
2207                }
2208
2209                int index = GetItemIndex(item);
2210                if (index == -1 || m_fileData[index].comparison_flags == fill)
2211                        continue;
2212
2213                const CDirentry& entry = (*m_pDirectoryListing)[index];
2214                if (entry.is_dir()) {
2215                        wxBell();
2216                        return;
2217                }
2218
2219                selected_items.push_back({entry.name, entry.size});
2220        }
2221
2222        CEditHandler* pEditHandler = CEditHandler::Get();
2223        if (!pEditHandler) {
2224                wxBell();
2225                return;
2226        }
2227
2228        const CServerPath path = m_pDirectoryListing->path;
2229        const CServer server = *m_pState->GetServer();
2230        pEditHandler->Edit(CEditHandler::remote, selected_items, path, server, this);
2231}
2232
2233#ifdef __WXDEBUG__
2234void CRemoteListView::ValidateIndexMapping()
2235{
2236        // This ensures that the index mapping is a bijection.
2237        // Beware:
2238        // - NO filter may be used!
2239        // - Doesn't work in comparison mode
2240
2241        char* buffer = new char[m_pDirectoryListing->GetCount() + 1];
2242        memset(buffer, 0, m_pDirectoryListing->GetCount() + 1);
2243
2244        // Injectivity
2245        for (auto const& item : m_indexMapping) {
2246                if (item > m_pDirectoryListing->GetCount()) {
2247                        abort();
2248                }
2249                else if (buffer[item]) {
2250                        abort();
2251                }
2252
2253                buffer[item] = 1;
2254        }
2255
2256        // Surjectivity
2257        for (unsigned int i = 0; i < m_pDirectoryListing->GetCount() + 1; i++) {
2258                wxASSERT(buffer[i] != 0);
2259        }
2260
2261        delete [] buffer;
2262}
2263#endif
2264
2265bool CRemoteListView::CanStartComparison()
2266{
2267        return m_pDirectoryListing != 0;
2268}
2269
2270void CRemoteListView::StartComparison()
2271{
2272        if (m_sortDirection || m_sortColumn)
2273        {
2274                wxASSERT(m_originalIndexMapping.empty());
2275                SortList(0, 0);
2276        }
2277
2278        ComparisonRememberSelections();
2279
2280        if (m_originalIndexMapping.empty())
2281                m_originalIndexMapping.swap(m_indexMapping);
2282        else
2283                m_indexMapping.clear();
2284
2285        m_comparisonIndex = -1;
2286
2287        const CGenericFileData& last = m_fileData[m_fileData.size() - 1];
2288        if (last.comparison_flags != fill)
2289        {
2290                CGenericFileData data;
2291                data.icon = -1;
2292                data.comparison_flags = fill;
2293                m_fileData.push_back(data);
2294        }
2295}
2296
2297bool CRemoteListView::get_next_file(wxString& name, bool& dir, int64_t& size, fz::datetime& date)
2298{
2299        if (++m_comparisonIndex >= (int)m_originalIndexMapping.size())
2300                return false;
2301
2302        const unsigned int index = m_originalIndexMapping[m_comparisonIndex];
2303        if (index >= m_fileData.size())
2304                return false;
2305
2306        if (index == m_pDirectoryListing->GetCount()) {
2307                name = _T("..");
2308                dir = true;
2309                size = -1;
2310                return true;
2311        }
2312
2313        const CDirentry& entry = (*m_pDirectoryListing)[index];
2314
2315        name = entry.name;
2316        dir = entry.is_dir();
2317        size = entry.size;
2318        date = entry.time;
2319
2320        return true;
2321}
2322
2323void CRemoteListView::FinishComparison()
2324{
2325        SetInfoText();
2326
2327        SetItemCount(m_indexMapping.size());
2328
2329        ComparisonRestoreSelections();
2330
2331        RefreshListOnly();
2332}
2333
2334wxListItemAttr* CRemoteListView::OnGetItemAttr(long item) const
2335{
2336        CRemoteListView *pThis = const_cast<CRemoteListView *>(this);
2337        int index = GetItemIndex(item);
2338
2339        if (index == -1)
2340                return 0;
2341
2342#ifndef __WXMSW__
2343        if (item == m_dropTarget)
2344                return &pThis->m_dropHighlightAttribute;
2345#endif
2346
2347        const CGenericFileData& data = m_fileData[index];
2348
2349        switch (data.comparison_flags)
2350        {
2351        case different:
2352                return &pThis->m_comparisonBackgrounds[0];
2353        case lonely:
2354                return &pThis->m_comparisonBackgrounds[1];
2355        case newer:
2356                return &pThis->m_comparisonBackgrounds[2];
2357        default:
2358                return 0;
2359        }
2360}
2361
2362wxString CRemoteListView::GetItemText(int item, unsigned int column)
2363{
2364        int index = GetItemIndex(item);
2365        if (index == -1)
2366                return wxString();
2367
2368        if (!column)
2369        {
2370                if ((unsigned int)index == m_pDirectoryListing->GetCount())
2371                        return _T("..");
2372                else if ((unsigned int)index < m_pDirectoryListing->GetCount())
2373                        return (*m_pDirectoryListing)[index].name;
2374                else
2375                        return wxString();
2376        }
2377        if (!item)
2378                return wxString(); //.. has no attributes
2379
2380        if ((unsigned int)index >= m_pDirectoryListing->GetCount())
2381                return wxString();
2382
2383        if (column == 1)
2384        {
2385                const CDirentry& entry = (*m_pDirectoryListing)[index];
2386                if (entry.is_dir() || entry.size < 0)
2387                        return wxString();
2388                else
2389                        return CSizeFormat::Format(entry.size);
2390        }
2391        else if (column == 2)
2392        {
2393                CGenericFileData& data = m_fileData[index];
2394                if (data.fileType.empty())
2395                {
2396                        const CDirentry& entry = (*m_pDirectoryListing)[index];
2397                        if (m_pDirectoryListing->path.GetType() == VMS)
2398                                data.fileType = GetType(StripVMSRevision(entry.name), entry.is_dir());
2399                        else
2400                                data.fileType = GetType(entry.name, entry.is_dir());
2401                }
2402
2403                return data.fileType;
2404        }
2405        else if (column == 3)
2406        {
2407                const CDirentry& entry = (*m_pDirectoryListing)[index];
2408                return CTimeFormat::Format(entry.time);
2409        }
2410        else if (column == 4)
2411                return *(*m_pDirectoryListing)[index].permissions;
2412        else if (column == 5)
2413                return *(*m_pDirectoryListing)[index].ownerGroup;
2414        return wxString();
2415}
2416
2417CFileListCtrl<CGenericFileData>::CSortComparisonObject CRemoteListView::GetSortComparisonObject()
2418{
2419        CFileListCtrlSort<CDirectoryListing>::DirSortMode dirSortMode = GetDirSortMode();
2420        CFileListCtrlSort<CDirectoryListing>::NameSortMode nameSortMode = GetNameSortMode();
2421
2422        CDirectoryListing const& directoryListing = *m_pDirectoryListing;
2423        if (!m_sortDirection)
2424        {
2425                if (m_sortColumn == 1)
2426                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortSize<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2427                else if (m_sortColumn == 2)
2428                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortType<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2429                else if (m_sortColumn == 3)
2430                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortTime<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2431                else if (m_sortColumn == 4)
2432                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortPermissions<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2433                else if (m_sortColumn == 5)
2434                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortOwnerGroup<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2435                else
2436                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortName<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2437        }
2438        else
2439        {
2440                if (m_sortColumn == 1)
2441                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortSize<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2442                else if (m_sortColumn == 2)
2443                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortType<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2444                else if (m_sortColumn == 3)
2445                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortTime<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2446                else if (m_sortColumn == 4)
2447                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortPermissions<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2448                else if (m_sortColumn == 5)
2449                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortOwnerGroup<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2450                else
2451                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortName<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2452        }
2453}
2454
2455void CRemoteListView::OnExitComparisonMode()
2456{
2457        CFileListCtrl<CGenericFileData>::OnExitComparisonMode();
2458        SetInfoText();
2459}
2460
2461bool CRemoteListView::ItemIsDir(int index) const
2462{
2463        return (*m_pDirectoryListing)[index].is_dir();
2464}
2465
2466int64_t CRemoteListView::ItemGetSize(int index) const
2467{
2468        return (*m_pDirectoryListing)[index].size;
2469}
2470
2471void CRemoteListView::LinkIsNotDir(const CServerPath& path, const wxString& link)
2472{
2473        if (m_pLinkResolveState && m_pLinkResolveState->remote_path == path && m_pLinkResolveState->link == link) {
2474                wxString localFile = CQueueView::ReplaceInvalidCharacters(link);
2475                if (m_pDirectoryListing->path.GetType() == VMS && COptions::Get()->GetOptionVal(OPTION_STRIP_VMS_REVISION))
2476                        localFile = StripVMSRevision(localFile);
2477                m_pQueue->QueueFile(false, true,
2478                        link, (link != localFile) ? localFile : wxString(),
2479                        m_pLinkResolveState->local_path, m_pLinkResolveState->remote_path, m_pLinkResolveState->server, -1);
2480                m_pQueue->QueueFile_Finish(true);
2481        }
2482
2483        m_pLinkResolveState.reset();
2484}
2485
2486void CRemoteListView::OnMenuGeturl(wxCommandEvent&)
2487{
2488        if (!m_pDirectoryListing)
2489                return;
2490
2491        const CServer* pServer = m_pState->GetServer();
2492        if (!pServer)
2493                return;
2494
2495        long item = -1;
2496
2497        std::list<CDirentry> selected_item_list;
2498        while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1)
2499        {
2500                if (!item)
2501                {
2502                        wxBell();
2503                        return;
2504                }
2505
2506                int index = GetItemIndex(item);
2507                if (index == -1 || m_fileData[index].comparison_flags == fill)
2508                        continue;
2509
2510                const CDirentry& entry = (*m_pDirectoryListing)[index];
2511
2512                selected_item_list.push_back(entry);
2513        }
2514        if (selected_item_list.empty())
2515        {
2516                wxBell();
2517                return;
2518        }
2519
2520        if (!wxTheClipboard->Open())
2521        {
2522                wxMessageBoxEx(_("Could not open clipboard"), _("Could not copy URLs"), wxICON_EXCLAMATION);
2523                return;
2524        }
2525
2526        const CServerPath& path = m_pDirectoryListing->path;
2527        const wxString server = pServer->FormatServer(true);
2528        if (selected_item_list.size() == 1)
2529        {
2530                wxString url = server;
2531                url += path.FormatFilename(selected_item_list.front().name, false);
2532
2533                // Poor mans URLencode
2534                url.Replace(_T(" "), _T("%20"));
2535
2536                wxTheClipboard->SetData(new wxURLDataObject(url));
2537        }
2538        else
2539        {
2540                wxString urls;
2541                for (std::list<CDirentry>::const_iterator iter = selected_item_list.begin(); iter != selected_item_list.end(); ++iter)
2542                {
2543                        urls += server;
2544                        urls += path.FormatFilename(iter->name, false);
2545#ifdef __WXMSW__
2546                        urls += _T("\r\n");
2547#else
2548                        urls += _T("\n");
2549#endif
2550                }
2551
2552                // Poor mans URLencode
2553                urls.Replace(_T(" "), _T("%20"));
2554
2555                wxTheClipboard->SetData(new wxTextDataObject(urls));
2556        }
2557
2558        wxTheClipboard->Flush();
2559        wxTheClipboard->Close();
2560}
2561
2562#ifdef __WXMSW__
2563int CRemoteListView::GetOverlayIndex(int item)
2564{
2565        int index = GetItemIndex(item);
2566        if (index == -1)
2567                return 0;
2568        if ((unsigned int)index >= m_pDirectoryListing->GetCount())
2569                return 0;
2570
2571        if ((*m_pDirectoryListing)[index].is_link())
2572                return GetLinkOverlayIndex();
2573
2574        return 0;
2575}
2576#endif
2577
2578void CRemoteListView::OnMenuRefresh(wxCommandEvent&)
2579{
2580        if (m_pState)
2581                m_pState->RefreshRemote();
2582}
2583
2584void CRemoteListView::OnNavigationEvent(bool forward)
2585{
2586        if (!forward) {
2587                if (!m_pState->IsRemoteIdle(true)) {
2588                        wxBell();
2589                        return;
2590                }
2591
2592                if (!m_pDirectoryListing) {
2593                        wxBell();
2594                        return;
2595                }
2596
2597                m_pState->ChangeRemoteDir(m_pDirectoryListing->path, _T(".."));
2598        }
2599}
2600
2601void CRemoteListView::OnMenuNewfile(wxCommandEvent&)
2602{
2603        if (!m_pState->IsRemoteIdle() || !m_pDirectoryListing) {
2604                wxBell();
2605                return;
2606        }
2607
2608        CInputDialog dlg;
2609        if (!dlg.Create(this, _("Create empty file"), _("Please enter the name of the file which should be created:")))
2610                return;
2611
2612        if (dlg.ShowModal() != wxID_OK)
2613                return;
2614
2615        if (dlg.GetValue().empty()) {
2616                wxBell();
2617                return;
2618        }
2619
2620        wxString newFileName = dlg.GetValue();
2621
2622        // Check if target file already exists
2623        for (unsigned int i = 0; i < m_pDirectoryListing->GetCount(); ++i) {
2624                if (newFileName == (*m_pDirectoryListing)[i].name) {
2625                        wxMessageBoxEx(_("Target filename already exists!"));
2626                        return;
2627                }
2628        }
2629
2630        CEditHandler* edithandler = CEditHandler::Get(); // Used to get the temporary folder
2631
2632        wxString emptyfile_name = _T("empty_file_yq744zm");
2633        wxString emptyfile = edithandler->GetLocalDirectory() + emptyfile_name;
2634
2635        // Create the empty temporary file
2636        {
2637                wxFile file;
2638                wxLogNull log;
2639                file.Create(emptyfile);
2640        }
2641
2642        const CServer* pServer = m_pState->GetServer();
2643        if (!pServer) {
2644                wxBell();
2645                return;
2646        }
2647
2648        CFileTransferCommand *cmd = new CFileTransferCommand(emptyfile, m_pDirectoryListing->path, newFileName, false, CFileTransferCommand::t_transferSettings());
2649        m_pState->m_pCommandQueue->ProcessCommand(cmd);
2650}
Note: See TracBrowser for help on using the repository browser.