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

Last change on this file since 130 was 130, checked in by jrpelegrina, 4 years ago

First release to xenial

File size: 67.1 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        {
1212                wxBell();
1213                return;
1214        }
1215
1216        bool added = false;
1217        bool startRecursive = false;
1218        long item = -1;
1219        for (;;)
1220        {
1221                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1222                if (item == -1)
1223                        break;
1224                if (!item)
1225                        continue;
1226
1227                int index = GetItemIndex(item);
1228                if (index == -1)
1229                        continue;
1230                if (m_fileData[index].comparison_flags == fill)
1231                        continue;
1232
1233                const CDirentry& entry = (*m_pDirectoryListing)[index];
1234                const wxString& name = entry.name;
1235
1236                if (entry.is_dir())
1237                {
1238                        if (!idle)
1239                                continue;
1240                        CLocalPath local_path(local_parent);
1241                        local_path.AddSegment(CQueueView::ReplaceInvalidCharacters(name));
1242                        CServerPath remotePath = m_pDirectoryListing->path;
1243                        if (remotePath.AddSegment(name))
1244                        {
1245                                pRecursiveOperation->AddDirectoryToVisit(m_pDirectoryListing->path, name, local_path, entry.is_link());
1246                                startRecursive = true;
1247                        }
1248                }
1249                else
1250                {
1251                        wxString localFile = CQueueView::ReplaceInvalidCharacters(name);
1252                        if (m_pDirectoryListing->path.GetType() == VMS && COptions::Get()->GetOptionVal(OPTION_STRIP_VMS_REVISION))
1253                                localFile = StripVMSRevision(localFile);
1254                        m_pQueue->QueueFile(queueOnly, true,
1255                                name, (name == localFile) ? wxString() : localFile,
1256                                local_parent, m_pDirectoryListing->path, *pServer, entry.size);
1257                        added = true;
1258                }
1259        }
1260        if (added)
1261                m_pQueue->QueueFile_Finish(!queueOnly);
1262
1263        if (startRecursive)
1264        {
1265                if (IsComparing())
1266                        ExitComparisonMode();
1267                CFilterManager filter;
1268                pRecursiveOperation->StartRecursiveOperation(queueOnly ? CRecursiveOperation::recursive_addtoqueue : CRecursiveOperation::recursive_download, m_pDirectoryListing->path, filter.GetActiveFilters(false));
1269        }
1270}
1271
1272// Create a new Directory
1273void CRemoteListView::OnMenuMkdir(wxCommandEvent&)
1274{
1275        MenuMkdir();
1276}
1277
1278// Create a new Directory and enter the new Directory
1279void CRemoteListView::OnMenuMkdirChgDir(wxCommandEvent&)
1280{
1281        CServerPath newdir = MenuMkdir();
1282        if (!newdir.empty()) {
1283                m_pState->ChangeRemoteDir(newdir, wxString(), 0, true);
1284        }
1285}
1286
1287// Help-Function to create a new Directory
1288// Returns the name of the new directory
1289CServerPath CRemoteListView::MenuMkdir()
1290{
1291        if (!m_pDirectoryListing || !m_pState->IsRemoteIdle()) {
1292                wxBell();
1293                return CServerPath();
1294        }
1295
1296        CInputDialog dlg;
1297        if (!dlg.Create(this, _("Create directory"), _("Please enter the name of the directory which should be created:")))
1298                return CServerPath();
1299
1300        CServerPath path = m_pDirectoryListing->path;
1301
1302        // Append a long segment which does (most likely) not exist in the path and
1303        // replace it with "New directory" later. This way we get the exact position of
1304        // "New directory" and can preselect it in the dialog.
1305        wxString tmpName = _T("25CF809E56B343b5A12D1F0466E3B37A49A9087FDCF8412AA9AF8D1E849D01CF");
1306        if (path.AddSegment(tmpName)) {
1307                wxString pathName = path.GetPath();
1308                int pos = pathName.Find(tmpName);
1309                wxASSERT(pos != -1);
1310                wxString newName = _("New directory");
1311                pathName.Replace(tmpName, newName);
1312                dlg.SetValue(pathName);
1313                dlg.SelectText(pos, pos + newName.Length());
1314        }
1315
1316        const CServerPath oldPath = m_pDirectoryListing->path;
1317
1318        if (dlg.ShowModal() != wxID_OK)
1319                return CServerPath();
1320
1321        if (!m_pDirectoryListing || oldPath != m_pDirectoryListing->path ||
1322                !m_pState->IsRemoteIdle())
1323        {
1324                wxBell();
1325                return CServerPath();
1326        }
1327
1328        path = m_pDirectoryListing->path;
1329        if (!path.ChangePath(dlg.GetValue())) {
1330                wxBell();
1331                return CServerPath();
1332        }
1333
1334        m_pState->m_pCommandQueue->ProcessCommand(new CMkdirCommand(path));
1335
1336        // Return name of the New Directory
1337        return path;
1338}
1339
1340void CRemoteListView::OnMenuDelete(wxCommandEvent&)
1341{
1342        if (!m_pState->IsRemoteIdle()) {
1343                wxBell();
1344                return;
1345        }
1346
1347        int count_dirs = 0;
1348        int count_files = 0;
1349        bool selected_link = false;
1350
1351        long item = -1;
1352        for (;;) {
1353                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1354                if (!item)
1355                        continue;
1356                if (item == -1)
1357                        break;
1358
1359                if (!IsItemValid(item))
1360                {
1361                        wxBell();
1362                        return;
1363                }
1364
1365                int index = GetItemIndex(item);
1366                if (index == -1)
1367                        continue;
1368                if (m_fileData[index].comparison_flags == fill)
1369                        continue;
1370
1371                const CDirentry& entry = (*m_pDirectoryListing)[index];
1372                if (entry.is_dir())
1373                {
1374                        count_dirs++;
1375                        if (entry.is_link())
1376                                selected_link = true;
1377                }
1378                else
1379                        count_files++;
1380        }
1381
1382        wxString question;
1383        if (!count_dirs)
1384                question.Printf(wxPLURAL("Really delete %d file from the server?", "Really delete %d files from the server?", count_files), count_files);
1385        else if (!count_files)
1386                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);
1387        else
1388        {
1389                wxString files = wxString::Format(wxPLURAL("%d file", "%d files", count_files), count_files);
1390                wxString dirs = wxString::Format(wxPLURAL("%d directory with its contents", "%d directories with their contents", count_dirs), count_dirs);
1391                question.Printf(_("Really delete %s and %s from the server?"), files, dirs);
1392        }
1393
1394        if (wxMessageBoxEx(question, _("Confirmation needed"), wxICON_QUESTION | wxYES_NO, this) != wxYES)
1395                return;
1396
1397        bool follow_symlink = false;
1398        if (selected_link)
1399        {
1400                wxDialogEx dlg;
1401                if (!dlg.Load(this, _T("ID_DELETE_SYMLINK")))
1402                {
1403                        wxBell();
1404                        return;
1405                }
1406                if (dlg.ShowModal() != wxID_OK)
1407                        return;
1408
1409                follow_symlink = XRCCTRL(dlg, "ID_RECURSE", wxRadioButton)->GetValue();
1410        }
1411
1412        CRecursiveOperation* pRecursiveOperation = m_pState->GetRecursiveOperationHandler();
1413        wxASSERT(pRecursiveOperation);
1414
1415        std::deque<wxString> filesToDelete;
1416
1417        bool startRecursive = false;
1418        item = -1;
1419        for (;;)
1420        {
1421                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1422                if (!item)
1423                        continue;
1424                if (item == -1)
1425                        break;
1426
1427                int index = GetItemIndex(item);
1428                if (index == -1)
1429                        continue;
1430                if (m_fileData[index].comparison_flags == fill)
1431                        continue;
1432
1433                const CDirentry& entry = (*m_pDirectoryListing)[index];
1434                const wxString& name = entry.name;
1435
1436                if (entry.is_dir() && (follow_symlink || !entry.is_link()))
1437                {
1438                        CServerPath remotePath = m_pDirectoryListing->path;
1439                        if (remotePath.AddSegment(name))
1440                        {
1441                                pRecursiveOperation->AddDirectoryToVisit(m_pDirectoryListing->path, name, CLocalPath(), true);
1442                                startRecursive = true;
1443                        }
1444                }
1445                else
1446                        filesToDelete.push_back(name);
1447        }
1448
1449        if (!filesToDelete.empty())
1450                m_pState->m_pCommandQueue->ProcessCommand(new CDeleteCommand(m_pDirectoryListing->path, std::move(filesToDelete)));
1451
1452        if (startRecursive)
1453        {
1454                if (IsComparing())
1455                        ExitComparisonMode();
1456                CFilterManager filter;
1457                pRecursiveOperation->StartRecursiveOperation(CRecursiveOperation::recursive_delete, m_pDirectoryListing->path, filter.GetActiveFilters(false));
1458        }
1459}
1460
1461void CRemoteListView::OnMenuRename(wxCommandEvent&)
1462{
1463        if (GetEditControl()) {
1464                GetEditControl()->SetFocus();
1465                return;
1466        }
1467
1468        if (!m_pState->IsRemoteIdle()) {
1469                wxBell();
1470                return;
1471        }
1472
1473        int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1474        if (item <= 0) {
1475                wxBell();
1476                return;
1477        }
1478
1479        if (GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1) {
1480                wxBell();
1481                return;
1482        }
1483
1484        int index = GetItemIndex(item);
1485        if (index == -1 || m_fileData[index].comparison_flags == fill) {
1486                wxBell();
1487                return;
1488        }
1489
1490        EditLabel(item);
1491}
1492
1493void CRemoteListView::OnKeyDown(wxKeyEvent& event)
1494{
1495#ifdef __WXMAC__
1496#define CursorModifierKey wxMOD_CMD
1497#else
1498#define CursorModifierKey wxMOD_ALT
1499#endif
1500
1501        int code = event.GetKeyCode();
1502        if (code == WXK_DELETE || code == WXK_NUMPAD_DELETE) {
1503                if (GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == -1) {
1504                        wxBell();
1505                        return;
1506                }
1507
1508                wxCommandEvent tmp;
1509                OnMenuDelete(tmp);
1510                return;
1511        }
1512        else if (code == WXK_F2) {
1513                wxCommandEvent tmp;
1514                OnMenuRename(tmp);
1515        }
1516        else if (code == WXK_RIGHT && event.GetModifiers() == CursorModifierKey) {
1517                wxListEvent evt;
1518                evt.m_itemIndex = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
1519                OnItemActivated(evt);
1520        }
1521        else if (code == WXK_DOWN && event.GetModifiers() == CursorModifierKey) {
1522                wxCommandEvent cmdEvent;
1523                OnMenuDownload(cmdEvent);
1524        }
1525        else if (code == 'N' && event.GetModifiers() == (wxMOD_CONTROL | wxMOD_SHIFT)) {
1526                MenuMkdir();
1527        }
1528        else
1529                event.Skip();
1530}
1531
1532bool CRemoteListView::OnBeginRename(const wxListEvent& event)
1533{
1534        if (!m_pState->IsRemoteIdle())
1535        {
1536                wxBell();
1537                return false;
1538        }
1539
1540        if (!m_pDirectoryListing)
1541        {
1542                wxBell();
1543                return false;
1544        }
1545
1546        int item = event.GetIndex();
1547        if (!item)
1548                return false;
1549
1550        int index = GetItemIndex(item);
1551        if (index == -1 || m_fileData[index].comparison_flags == fill)
1552                return false;
1553
1554        return true;
1555}
1556
1557bool CRemoteListView::OnAcceptRename(const wxListEvent& event)
1558{
1559        if (!m_pState->IsRemoteIdle())
1560        {
1561                wxBell();
1562                return false;
1563        }
1564
1565        if (!m_pDirectoryListing)
1566        {
1567                wxBell();
1568                return false;
1569        }
1570
1571        int item = event.GetIndex();
1572        if (!item)
1573                return false;
1574
1575        int index = GetItemIndex(item);
1576        if (index == -1 || m_fileData[index].comparison_flags == fill)
1577        {
1578                wxBell();
1579                return false;
1580        }
1581
1582        const CDirentry& entry = (*m_pDirectoryListing)[index];
1583
1584        wxString newFile = event.GetLabel();
1585
1586        CServerPath newPath = m_pDirectoryListing->path;
1587        if (!newPath.ChangePath(newFile, true))
1588        {
1589                wxMessageBoxEx(_("Filename invalid"), _("Cannot rename file"), wxICON_EXCLAMATION);
1590                return false;
1591        }
1592
1593        if (newPath == m_pDirectoryListing->path)
1594        {
1595                if (entry.name == newFile)
1596                        return false;
1597
1598                // Check if target file already exists
1599                for (unsigned int i = 0; i < m_pDirectoryListing->GetCount(); i++)
1600                {
1601                        if (newFile == (*m_pDirectoryListing)[i].name)
1602                        {
1603                                if (wxMessageBoxEx(_("Target filename already exists, really continue?"), _("File exists"), wxICON_QUESTION | wxYES_NO) != wxYES)
1604                                        return false;
1605
1606                                break;
1607                        }
1608                }
1609        }
1610
1611        m_pState->m_pCommandQueue->ProcessCommand(new CRenameCommand(m_pDirectoryListing->path, entry.name, newPath, newFile));
1612
1613        return true;
1614}
1615
1616void CRemoteListView::OnMenuChmod(wxCommandEvent&)
1617{
1618        if (!m_pState->IsRemoteConnected() || !m_pState->IsRemoteIdle()) {
1619                wxBell();
1620                return;
1621        }
1622
1623        int fileCount = 0;
1624        int dirCount = 0;
1625        wxString name;
1626
1627        char permissions[9] = {};
1628
1629        long item = -1;
1630        for (;;) {
1631                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1632                if (item == -1)
1633                        break;
1634
1635                if (!item)
1636                        return;
1637
1638                int index = GetItemIndex(item);
1639                if (index == -1)
1640                        return;
1641                if (m_fileData[index].comparison_flags == fill)
1642                        continue;
1643
1644                const CDirentry& entry = (*m_pDirectoryListing)[index];
1645
1646                if (entry.is_dir())
1647                        dirCount++;
1648                else
1649                        fileCount++;
1650                name = entry.name;
1651
1652                char file_perms[9];
1653                if (CChmodDialog::ConvertPermissions(*entry.permissions, file_perms)) {
1654                        for (int i = 0; i < 9; i++) {
1655                                if (!permissions[i] || permissions[i] == file_perms[i])
1656                                        permissions[i] = file_perms[i];
1657                                else
1658                                        permissions[i] = -1;
1659                        }
1660                }
1661        }
1662        if (!dirCount && !fileCount) {
1663                wxBell();
1664                return;
1665        }
1666
1667        for (int i = 0; i < 9; i++)
1668                if (permissions[i] == -1)
1669                        permissions[i] = 0;
1670
1671        CChmodDialog* pChmodDlg = new CChmodDialog;
1672        if (!pChmodDlg->Create(this, fileCount, dirCount, name, permissions)) {
1673                pChmodDlg->Destroy();
1674                return;
1675        }
1676
1677        if (pChmodDlg->ShowModal() != wxID_OK) {
1678                pChmodDlg->Destroy();
1679                return;
1680        }
1681
1682        // State may have changed while chmod dialog was shown
1683        if (!m_pState->IsRemoteConnected() || !m_pState->IsRemoteIdle()) {
1684                pChmodDlg->Destroy();
1685                wxBell();
1686                return;
1687        }
1688
1689        const int applyType = pChmodDlg->GetApplyType();
1690
1691        CRecursiveOperation* pRecursiveOperation = m_pState->GetRecursiveOperationHandler();
1692        wxASSERT(pRecursiveOperation);
1693
1694        item = -1;
1695        for (;;) {
1696                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1697                if (item == -1)
1698                        break;
1699
1700                if (!item) {
1701                        pChmodDlg->Destroy();
1702                        pChmodDlg = 0;
1703                        return;
1704                }
1705
1706                int index = GetItemIndex(item);
1707                if (index == -1) {
1708                        pChmodDlg->Destroy();
1709                        pChmodDlg = 0;
1710                        return;
1711                }
1712                if (m_fileData[index].comparison_flags == fill)
1713                        continue;
1714
1715                const CDirentry& entry = (*m_pDirectoryListing)[index];
1716
1717                if (!applyType ||
1718                        (!entry.is_dir() && applyType == 1) ||
1719                        (entry.is_dir() && applyType == 2))
1720                {
1721                        char newPermissions[9]{};
1722                        bool res = pChmodDlg->ConvertPermissions(*entry.permissions, newPermissions);
1723                        wxString newPerms = pChmodDlg->GetPermissions(res ? newPermissions : 0, entry.is_dir());
1724
1725                        m_pState->m_pCommandQueue->ProcessCommand(new CChmodCommand(m_pDirectoryListing->path, entry.name, newPerms));
1726                }
1727
1728                if (pChmodDlg->Recursive() && entry.is_dir())
1729                        pRecursiveOperation->AddDirectoryToVisit(m_pDirectoryListing->path, entry.name);
1730        }
1731
1732        if (pChmodDlg->Recursive()) {
1733                if (IsComparing())
1734                        ExitComparisonMode();
1735
1736                pRecursiveOperation->SetChmodDialog(pChmodDlg);
1737                CFilterManager filter;
1738                pRecursiveOperation->StartRecursiveOperation(CRecursiveOperation::recursive_chmod, m_pDirectoryListing->path, filter.GetActiveFilters(false));
1739
1740                // Refresh listing. This gets done implicitely by the recursive operation, so
1741                // only it if not recursing.
1742                if (pRecursiveOperation->GetOperationMode() != CRecursiveOperation::recursive_chmod)
1743                        m_pState->ChangeRemoteDir(m_pDirectoryListing->path);
1744        }
1745        else {
1746                pChmodDlg->Destroy();
1747                m_pState->ChangeRemoteDir(m_pDirectoryListing->path, _T(""), 0, true);
1748        }
1749
1750}
1751
1752void CRemoteListView::ApplyCurrentFilter()
1753{
1754        CFilterManager filter;
1755
1756        if (!filter.HasSameLocalAndRemoteFilters() && IsComparing())
1757                ExitComparisonMode();
1758
1759        if (m_fileData.size() <= 1)
1760                return;
1761
1762        wxString focused;
1763        std::list<wxString> selectedNames = RememberSelectedItems(focused);
1764
1765        if (m_pFilelistStatusBar)
1766                m_pFilelistStatusBar->UnselectAll();
1767
1768        int64_t totalSize{};
1769        int unknown_sizes = 0;
1770        int totalFileCount = 0;
1771        int totalDirCount = 0;
1772        int hidden = 0;
1773
1774        const wxString path = m_pDirectoryListing->path.GetPath();
1775
1776        m_indexMapping.clear();
1777        const unsigned int count = m_pDirectoryListing->GetCount();
1778        m_indexMapping.push_back(count);
1779        for (unsigned int i = 0; i < count; ++i) {
1780                const CDirentry& entry = (*m_pDirectoryListing)[i];
1781                if (filter.FilenameFiltered(entry.name, path, entry.is_dir(), entry.size, false, 0, entry.time)) {
1782                        ++hidden;
1783                        continue;
1784                }
1785
1786                if (entry.is_dir())
1787                        ++totalDirCount;
1788                else {
1789                        if (entry.size == -1)
1790                                ++unknown_sizes;
1791                        else
1792                                totalSize += entry.size;
1793                        ++totalFileCount;
1794                }
1795
1796                m_indexMapping.push_back(i);
1797        }
1798
1799        if (m_pFilelistStatusBar)
1800                m_pFilelistStatusBar->SetDirectoryContents(totalFileCount, totalDirCount, totalSize, unknown_sizes, hidden);
1801
1802        SetItemCount(m_indexMapping.size());
1803
1804        SortList(-1, -1, false);
1805
1806        if (IsComparing()) {
1807                m_originalIndexMapping.clear();
1808                RefreshComparison();
1809        }
1810
1811        ReselectItems(selectedNames, focused);
1812        if (!IsComparing())
1813                RefreshListOnly();
1814}
1815
1816std::list<wxString> CRemoteListView::RememberSelectedItems(wxString& focused)
1817{
1818        wxASSERT(GetItemCount() == (int)m_indexMapping.size());
1819        std::list<wxString> selectedNames;
1820        // Remember which items were selected
1821#ifndef __WXMSW__
1822        // GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
1823        if (GetSelectedItemCount())
1824#endif
1825        {
1826                int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1827                while (item != -1)
1828                {
1829                        SetSelection(item, false);
1830                        if (!item)
1831                        {
1832                                selectedNames.push_back(_T(".."));
1833                                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1834                                continue;
1835                        }
1836                        int index = GetItemIndex(item);
1837                        if (index == -1 || m_fileData[index].comparison_flags == fill)
1838                        {
1839                                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1840                                continue;
1841                        }
1842                        const CDirentry& entry = (*m_pDirectoryListing)[index];
1843
1844                        if (entry.is_dir())
1845                                selectedNames.push_back(_T("d") + entry.name);
1846                        else
1847                                selectedNames.push_back(_T("-") + entry.name);
1848
1849                        item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1850                }
1851        }
1852
1853        int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
1854        if (item != -1)
1855        {
1856                int index = GetItemIndex(item);
1857                if (index != -1 && m_fileData[index].comparison_flags != fill)
1858                {
1859                        if (!item)
1860                                focused = _T("..");
1861                        else
1862                                focused = (*m_pDirectoryListing)[index].name;
1863                }
1864
1865                SetItemState(item, 0, wxLIST_STATE_FOCUSED);
1866        }
1867
1868        return selectedNames;
1869}
1870
1871void CRemoteListView::ReselectItems(std::list<wxString>& selectedNames, wxString focused, bool ensureVisible)
1872{
1873        if (!GetItemCount())
1874                return;
1875
1876        if (focused == _T(".."))
1877        {
1878                focused = _T("");
1879                SetItemState(0, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1880        }
1881
1882        if (selectedNames.empty())
1883        {
1884                if (focused.empty())
1885                        return;
1886
1887                for (unsigned int i = 1; i < m_indexMapping.size(); i++)
1888                {
1889                        const int index = m_indexMapping[i];
1890                        if (m_fileData[index].comparison_flags == fill)
1891                                continue;
1892
1893                        if ((*m_pDirectoryListing)[index].name == focused)
1894                        {
1895                                SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1896                                if (ensureVisible)
1897                                        EnsureVisible(i);
1898                                return;
1899                        }
1900                }
1901                return;
1902        }
1903
1904        if (selectedNames.front() == _T(".."))
1905        {
1906                selectedNames.pop_front();
1907                SetItemState(0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1908        }
1909
1910        int firstSelected = -1;
1911
1912        // Reselect previous items if neccessary.
1913        // Sorting direction did not change. We just have to scan through items once
1914        unsigned int i = 0;
1915        for (std::list<wxString>::const_iterator iter = selectedNames.begin(); iter != selectedNames.end(); ++iter)
1916        {
1917                while (++i < m_indexMapping.size())
1918                {
1919                        int index = GetItemIndex(i);
1920                        if (index == -1 || m_fileData[index].comparison_flags == fill)
1921                                continue;
1922                        const CDirentry& entry = (*m_pDirectoryListing)[index];
1923                        if (entry.name == focused)
1924                        {
1925                                SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1926                                if (ensureVisible)
1927                                        EnsureVisible(i);
1928                                focused = _T("");
1929                        }
1930                        if (entry.is_dir() && *iter == (_T("d") + entry.name))
1931                        {
1932                                if (firstSelected == -1)
1933                                        firstSelected = i;
1934                                if (m_pFilelistStatusBar)
1935                                        m_pFilelistStatusBar->SelectDirectory();
1936                                SetSelection(i, true);
1937                                break;
1938                        }
1939                        else if (*iter == (_T("-") + entry.name))
1940                        {
1941                                if (firstSelected == -1)
1942                                        firstSelected = i;
1943                                if (m_pFilelistStatusBar)
1944                                        m_pFilelistStatusBar->SelectFile(entry.size);
1945                                SetSelection(i, true);
1946                                break;
1947                        }
1948                }
1949                if (i == m_indexMapping.size())
1950                        break;
1951        }
1952        if (!focused.empty())
1953        {
1954                if (firstSelected != -1)
1955                        SetItemState(firstSelected, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1956                else
1957                        SetItemState(0, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1958        }
1959}
1960
1961void CRemoteListView::OnSize(wxSizeEvent& event)
1962{
1963        event.Skip();
1964        RepositionInfoText();
1965}
1966
1967void CRemoteListView::RepositionInfoText()
1968{
1969        if (!m_pInfoText)
1970                return;
1971
1972        wxRect rect = GetClientRect();
1973
1974        wxSize size = m_pInfoText->GetTextSize();
1975
1976        if (m_indexMapping.empty())
1977                rect.y = 60;
1978        else
1979        {
1980                wxRect itemRect;
1981                GetItemRect(0, itemRect);
1982                rect.y = wxMax(60, itemRect.GetBottom() + 1);
1983        }
1984        rect.x = rect.x + (rect.width - size.x) / 2;
1985        rect.width = size.x;
1986        rect.height = size.y;
1987
1988        m_pInfoText->SetSize(rect);
1989#ifdef __WXMSW__
1990        if (GetLayoutDirection() != wxLayout_RightToLeft)
1991        {
1992                m_pInfoText->Refresh(true);
1993                m_pInfoText->Update();
1994        }
1995        else
1996#endif
1997                m_pInfoText->Refresh(false);
1998
1999}
2000
2001void CRemoteListView::OnStateChange(CState* pState, enum t_statechange_notifications notification, const wxString& data, const void* data2)
2002{
2003        wxASSERT(pState);
2004        if (notification == STATECHANGE_REMOTE_DIR)
2005                SetDirectoryListing(pState->GetRemoteDir());
2006        else if (notification == STATECHANGE_REMOTE_LINKNOTDIR) {
2007                wxASSERT(data2);
2008                LinkIsNotDir(*(CServerPath*)data2, data);
2009        }
2010        else {
2011                wxASSERT(notification == STATECHANGE_APPLYFILTER);
2012                ApplyCurrentFilter();
2013        }
2014}
2015
2016void CRemoteListView::SetInfoText()
2017{
2018        wxString text;
2019        if (!IsComparing()) {
2020                if (!m_pDirectoryListing)
2021                        text = _("Not connected to any server");
2022                else if (m_pDirectoryListing->failed())
2023                        text = _("Directory listing failed");
2024                else if (!m_pDirectoryListing->GetCount())
2025                        text = _("Empty directory listing");
2026        }
2027
2028        if (text.empty()) {
2029                delete m_pInfoText;
2030                m_pInfoText = 0;
2031                return;
2032        }
2033
2034        if (!m_pInfoText) {
2035                m_pInfoText = new CInfoText(this, text);
2036#ifdef __WXMSW__
2037                if (GetLayoutDirection() != wxLayout_RightToLeft)
2038                        m_pInfoText->SetDoubleBuffered(true);
2039#endif
2040
2041                RepositionInfoText();
2042                return;
2043        }
2044
2045        m_pInfoText->SetText(text);
2046        RepositionInfoText();
2047}
2048
2049void CRemoteListView::OnBeginDrag(wxListEvent&)
2050{
2051        if (!m_pState->IsRemoteIdle()) {
2052                wxBell();
2053                return;
2054        }
2055
2056        if (GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == -1) {
2057                // Nothing selected
2058                return;
2059        }
2060
2061        bool idle = m_pState->m_pCommandQueue->Idle();
2062
2063        long item = -1;
2064        int count = 0;
2065        for (;;) {
2066                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2067                if (item == -1)
2068                        break;
2069
2070                if (!item) {
2071                        // Can't drag ".."
2072                        wxBell();
2073                        return;
2074                }
2075
2076                int index = GetItemIndex(item);
2077                if (index == -1 || m_fileData[index].comparison_flags == fill)
2078                        continue;
2079                if ((*m_pDirectoryListing)[index].is_dir() && !idle) {
2080                        // Drag could result in recursive operation, don't allow at this point
2081                        wxBell();
2082                        return;
2083                }
2084                ++count;
2085        }
2086        if (!count) {
2087                wxBell();
2088                return;
2089        }
2090
2091        wxDataObjectComposite object;
2092
2093        CServer const* pServer = m_pState->GetServer();
2094        if (!pServer)
2095                return;
2096        CServer const server = *pServer;
2097        CServerPath const path = m_pDirectoryListing->path;
2098
2099        CRemoteDataObject *pRemoteDataObject = new CRemoteDataObject(*pServer, m_pDirectoryListing->path);
2100
2101        CDragDropManager* pDragDropManager = CDragDropManager::Init();
2102        pDragDropManager->pDragSource = this;
2103        pDragDropManager->server = server;
2104        pDragDropManager->remoteParent = m_pDirectoryListing->path;
2105
2106        // Add files to remote data object
2107        item = -1;
2108        for (;;) {
2109                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2110                if (item == -1)
2111                        break;
2112
2113                int index = GetItemIndex(item);
2114                if (index == -1 || m_fileData[index].comparison_flags == fill)
2115                        continue;
2116                const CDirentry& entry = (*m_pDirectoryListing)[index];
2117
2118                pRemoteDataObject->AddFile(entry.name, entry.is_dir(), entry.size, entry.is_link());
2119        }
2120
2121        pRemoteDataObject->Finalize();
2122
2123        object.Add(pRemoteDataObject, true);
2124
2125#if FZ3_USESHELLEXT
2126        std::unique_ptr<CShellExtensionInterface> ext = CShellExtensionInterface::CreateInitialized();
2127        if (ext) {
2128                const wxString& file = ext->GetDragDirectory();
2129
2130                wxASSERT(!file.empty());
2131
2132                wxFileDataObject *pFileDataObject = new wxFileDataObject;
2133                pFileDataObject->AddFile(file);
2134
2135                object.Add(pFileDataObject);
2136        }
2137#endif
2138
2139        CLabelEditBlocker b(*this);
2140
2141        wxDropSource source(this);
2142        source.SetData(object);
2143
2144        int res = source.DoDragDrop();
2145
2146        pDragDropManager->Release();
2147
2148        if (res != wxDragCopy) {
2149                return;
2150        }
2151
2152#if FZ3_USESHELLEXT
2153        if (ext) {
2154                if (!pRemoteDataObject->DidSendData()) {
2155                        pServer = m_pState->GetServer();
2156                        if (!m_pState->IsRemoteIdle() ||
2157                                !pServer || *pServer != server ||
2158                                !m_pDirectoryListing || m_pDirectoryListing->path != path)
2159                        {
2160                                // Remote listing has changed since drag started
2161                                wxBell();
2162                                return;
2163                        }
2164
2165                        // Same checks as before
2166                        idle = m_pState->m_pCommandQueue->Idle();
2167
2168                        item = -1;
2169                        for (;;) {
2170                                item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2171                                if (item == -1)
2172                                        break;
2173
2174                                if (!item) {
2175                                        // Can't drag ".."
2176                                        wxBell();
2177                                        return;
2178                                }
2179
2180                                int index = GetItemIndex(item);
2181                                if (index == -1 || m_fileData[index].comparison_flags == fill)
2182                                        continue;
2183                                if ((*m_pDirectoryListing)[index].is_dir() && !idle) {
2184                                        // Drag could result in recursive operation, don't allow at this point
2185                                        wxBell();
2186                                        return;
2187                                }
2188                        }
2189
2190                        CLocalPath target(ext->GetTarget());
2191                        if (target.empty()) {
2192                                ext.reset(); // Release extension before the modal message box
2193                                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."));
2194                                return;
2195                        }
2196
2197                        TransferSelectedFiles(target, false);
2198                }
2199        }
2200#endif
2201}
2202
2203void CRemoteListView::OnMenuEdit(wxCommandEvent&)
2204{
2205        if (!m_pState->IsRemoteConnected() || !m_pDirectoryListing) {
2206                wxBell();
2207                return;
2208        }
2209
2210        long item = -1;
2211
2212        std::vector<CEditHandler::FileData> selected_items;
2213        while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
2214                if (!item) {
2215                        wxBell();
2216                        return;
2217                }
2218
2219                int index = GetItemIndex(item);
2220                if (index == -1 || m_fileData[index].comparison_flags == fill)
2221                        continue;
2222
2223                const CDirentry& entry = (*m_pDirectoryListing)[index];
2224                if (entry.is_dir()) {
2225                        wxBell();
2226                        return;
2227                }
2228
2229                selected_items.push_back({entry.name, entry.size});
2230        }
2231
2232        CEditHandler* pEditHandler = CEditHandler::Get();
2233        if (!pEditHandler) {
2234                wxBell();
2235                return;
2236        }
2237
2238        const CServerPath path = m_pDirectoryListing->path;
2239        const CServer server = *m_pState->GetServer();
2240        pEditHandler->Edit(CEditHandler::remote, selected_items, path, server, this);
2241}
2242
2243#ifdef __WXDEBUG__
2244void CRemoteListView::ValidateIndexMapping()
2245{
2246        // This ensures that the index mapping is a bijection.
2247        // Beware:
2248        // - NO filter may be used!
2249        // - Doesn't work in comparison mode
2250
2251        char* buffer = new char[m_pDirectoryListing->GetCount() + 1];
2252        memset(buffer, 0, m_pDirectoryListing->GetCount() + 1);
2253
2254        // Injectivity
2255        for (auto const& item : m_indexMapping) {
2256                if (item > m_pDirectoryListing->GetCount()) {
2257                        abort();
2258                }
2259                else if (buffer[item]) {
2260                        abort();
2261                }
2262
2263                buffer[item] = 1;
2264        }
2265
2266        // Surjectivity
2267        for (unsigned int i = 0; i < m_pDirectoryListing->GetCount() + 1; i++) {
2268                wxASSERT(buffer[i] != 0);
2269        }
2270
2271        delete [] buffer;
2272}
2273#endif
2274
2275bool CRemoteListView::CanStartComparison()
2276{
2277        return m_pDirectoryListing != 0;
2278}
2279
2280void CRemoteListView::StartComparison()
2281{
2282        if (m_sortDirection || m_sortColumn)
2283        {
2284                wxASSERT(m_originalIndexMapping.empty());
2285                SortList(0, 0);
2286        }
2287
2288        ComparisonRememberSelections();
2289
2290        if (m_originalIndexMapping.empty())
2291                m_originalIndexMapping.swap(m_indexMapping);
2292        else
2293                m_indexMapping.clear();
2294
2295        m_comparisonIndex = -1;
2296
2297        const CGenericFileData& last = m_fileData[m_fileData.size() - 1];
2298        if (last.comparison_flags != fill)
2299        {
2300                CGenericFileData data;
2301                data.icon = -1;
2302                data.comparison_flags = fill;
2303                m_fileData.push_back(data);
2304        }
2305}
2306
2307bool CRemoteListView::GetNextFile(wxString& name, bool& dir, int64_t& size, CDateTime& date)
2308{
2309        if (++m_comparisonIndex >= (int)m_originalIndexMapping.size())
2310                return false;
2311
2312        const unsigned int index = m_originalIndexMapping[m_comparisonIndex];
2313        if (index >= m_fileData.size())
2314                return false;
2315
2316        if (index == m_pDirectoryListing->GetCount()) {
2317                name = _T("..");
2318                dir = true;
2319                size = -1;
2320                return true;
2321        }
2322
2323        const CDirentry& entry = (*m_pDirectoryListing)[index];
2324
2325        name = entry.name;
2326        dir = entry.is_dir();
2327        size = entry.size;
2328        date = entry.time;
2329
2330        return true;
2331}
2332
2333void CRemoteListView::FinishComparison()
2334{
2335        SetInfoText();
2336
2337        SetItemCount(m_indexMapping.size());
2338
2339        ComparisonRestoreSelections();
2340
2341        RefreshListOnly();
2342}
2343
2344wxListItemAttr* CRemoteListView::OnGetItemAttr(long item) const
2345{
2346        CRemoteListView *pThis = const_cast<CRemoteListView *>(this);
2347        int index = GetItemIndex(item);
2348
2349        if (index == -1)
2350                return 0;
2351
2352#ifndef __WXMSW__
2353        if (item == m_dropTarget)
2354                return &pThis->m_dropHighlightAttribute;
2355#endif
2356
2357        const CGenericFileData& data = m_fileData[index];
2358
2359        switch (data.comparison_flags)
2360        {
2361        case different:
2362                return &pThis->m_comparisonBackgrounds[0];
2363        case lonely:
2364                return &pThis->m_comparisonBackgrounds[1];
2365        case newer:
2366                return &pThis->m_comparisonBackgrounds[2];
2367        default:
2368                return 0;
2369        }
2370}
2371
2372wxString CRemoteListView::GetItemText(int item, unsigned int column)
2373{
2374        int index = GetItemIndex(item);
2375        if (index == -1)
2376                return wxString();
2377
2378        if (!column)
2379        {
2380                if ((unsigned int)index == m_pDirectoryListing->GetCount())
2381                        return _T("..");
2382                else if ((unsigned int)index < m_pDirectoryListing->GetCount())
2383                        return (*m_pDirectoryListing)[index].name;
2384                else
2385                        return wxString();
2386        }
2387        if (!item)
2388                return wxString(); //.. has no attributes
2389
2390        if ((unsigned int)index >= m_pDirectoryListing->GetCount())
2391                return wxString();
2392
2393        if (column == 1)
2394        {
2395                const CDirentry& entry = (*m_pDirectoryListing)[index];
2396                if (entry.is_dir() || entry.size < 0)
2397                        return wxString();
2398                else
2399                        return CSizeFormat::Format(entry.size);
2400        }
2401        else if (column == 2)
2402        {
2403                CGenericFileData& data = m_fileData[index];
2404                if (data.fileType.empty())
2405                {
2406                        const CDirentry& entry = (*m_pDirectoryListing)[index];
2407                        if (m_pDirectoryListing->path.GetType() == VMS)
2408                                data.fileType = GetType(StripVMSRevision(entry.name), entry.is_dir());
2409                        else
2410                                data.fileType = GetType(entry.name, entry.is_dir());
2411                }
2412
2413                return data.fileType;
2414        }
2415        else if (column == 3)
2416        {
2417                const CDirentry& entry = (*m_pDirectoryListing)[index];
2418                return CTimeFormat::Format(entry.time);
2419        }
2420        else if (column == 4)
2421                return *(*m_pDirectoryListing)[index].permissions;
2422        else if (column == 5)
2423                return *(*m_pDirectoryListing)[index].ownerGroup;
2424        return wxString();
2425}
2426
2427CFileListCtrl<CGenericFileData>::CSortComparisonObject CRemoteListView::GetSortComparisonObject()
2428{
2429        CFileListCtrlSort<CDirectoryListing>::DirSortMode dirSortMode = GetDirSortMode();
2430        CFileListCtrlSort<CDirectoryListing>::NameSortMode nameSortMode = GetNameSortMode();
2431
2432        CDirectoryListing const& directoryListing = *m_pDirectoryListing;
2433        if (!m_sortDirection)
2434        {
2435                if (m_sortColumn == 1)
2436                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortSize<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2437                else if (m_sortColumn == 2)
2438                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortType<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2439                else if (m_sortColumn == 3)
2440                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortTime<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2441                else if (m_sortColumn == 4)
2442                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortPermissions<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2443                else if (m_sortColumn == 5)
2444                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortOwnerGroup<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2445                else
2446                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CFileListCtrlSortName<CDirectoryListing, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2447        }
2448        else
2449        {
2450                if (m_sortColumn == 1)
2451                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortSize<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2452                else if (m_sortColumn == 2)
2453                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortType<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2454                else if (m_sortColumn == 3)
2455                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortTime<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2456                else if (m_sortColumn == 4)
2457                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortPermissions<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2458                else if (m_sortColumn == 5)
2459                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortOwnerGroup<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2460                else
2461                        return CFileListCtrl<CGenericFileData>::CSortComparisonObject(new CReverseSort<CFileListCtrlSortName<CDirectoryListing, CGenericFileData>, CGenericFileData>(directoryListing, m_fileData, dirSortMode, nameSortMode, this));
2462        }
2463}
2464
2465void CRemoteListView::OnExitComparisonMode()
2466{
2467        CFileListCtrl<CGenericFileData>::OnExitComparisonMode();
2468        SetInfoText();
2469}
2470
2471bool CRemoteListView::ItemIsDir(int index) const
2472{
2473        return (*m_pDirectoryListing)[index].is_dir();
2474}
2475
2476int64_t CRemoteListView::ItemGetSize(int index) const
2477{
2478        return (*m_pDirectoryListing)[index].size;
2479}
2480
2481void CRemoteListView::LinkIsNotDir(const CServerPath& path, const wxString& link)
2482{
2483        if (m_pLinkResolveState && m_pLinkResolveState->remote_path == path && m_pLinkResolveState->link == link) {
2484                wxString localFile = CQueueView::ReplaceInvalidCharacters(link);
2485                if (m_pDirectoryListing->path.GetType() == VMS && COptions::Get()->GetOptionVal(OPTION_STRIP_VMS_REVISION))
2486                        localFile = StripVMSRevision(localFile);
2487                m_pQueue->QueueFile(false, true,
2488                        link, (link != localFile) ? localFile : wxString(),
2489                        m_pLinkResolveState->local_path, m_pLinkResolveState->remote_path, m_pLinkResolveState->server, -1);
2490                m_pQueue->QueueFile_Finish(true);
2491        }
2492
2493        m_pLinkResolveState.reset();
2494}
2495
2496void CRemoteListView::OnMenuGeturl(wxCommandEvent&)
2497{
2498        if (!m_pDirectoryListing)
2499                return;
2500
2501        const CServer* pServer = m_pState->GetServer();
2502        if (!pServer)
2503                return;
2504
2505        long item = -1;
2506
2507        std::list<CDirentry> selected_item_list;
2508        while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1)
2509        {
2510                if (!item)
2511                {
2512                        wxBell();
2513                        return;
2514                }
2515
2516                int index = GetItemIndex(item);
2517                if (index == -1 || m_fileData[index].comparison_flags == fill)
2518                        continue;
2519
2520                const CDirentry& entry = (*m_pDirectoryListing)[index];
2521
2522                selected_item_list.push_back(entry);
2523        }
2524        if (selected_item_list.empty())
2525        {
2526                wxBell();
2527                return;
2528        }
2529
2530        if (!wxTheClipboard->Open())
2531        {
2532                wxMessageBoxEx(_("Could not open clipboard"), _("Could not copy URLs"), wxICON_EXCLAMATION);
2533                return;
2534        }
2535
2536        const CServerPath& path = m_pDirectoryListing->path;
2537        const wxString server = pServer->FormatServer(true);
2538        if (selected_item_list.size() == 1)
2539        {
2540                wxString url = server;
2541                url += path.FormatFilename(selected_item_list.front().name, false);
2542
2543                // Poor mans URLencode
2544                url.Replace(_T(" "), _T("%20"));
2545
2546                wxTheClipboard->SetData(new wxURLDataObject(url));
2547        }
2548        else
2549        {
2550                wxString urls;
2551                for (std::list<CDirentry>::const_iterator iter = selected_item_list.begin(); iter != selected_item_list.end(); ++iter)
2552                {
2553                        urls += server;
2554                        urls += path.FormatFilename(iter->name, false);
2555#ifdef __WXMSW__
2556                        urls += _T("\r\n");
2557#else
2558                        urls += _T("\n");
2559#endif
2560                }
2561
2562                // Poor mans URLencode
2563                urls.Replace(_T(" "), _T("%20"));
2564
2565                wxTheClipboard->SetData(new wxTextDataObject(urls));
2566        }
2567
2568        wxTheClipboard->Flush();
2569        wxTheClipboard->Close();
2570}
2571
2572#ifdef __WXMSW__
2573int CRemoteListView::GetOverlayIndex(int item)
2574{
2575        int index = GetItemIndex(item);
2576        if (index == -1)
2577                return 0;
2578        if ((unsigned int)index >= m_pDirectoryListing->GetCount())
2579                return 0;
2580
2581        if ((*m_pDirectoryListing)[index].is_link())
2582                return GetLinkOverlayIndex();
2583
2584        return 0;
2585}
2586#endif
2587
2588void CRemoteListView::OnMenuRefresh(wxCommandEvent&)
2589{
2590        if (m_pState)
2591                m_pState->RefreshRemote();
2592}
2593
2594void CRemoteListView::OnNavigationEvent(bool forward)
2595{
2596        if (!forward) {
2597                if (!m_pState->IsRemoteIdle(true)) {
2598                        wxBell();
2599                        return;
2600                }
2601
2602                if (!m_pDirectoryListing) {
2603                        wxBell();
2604                        return;
2605                }
2606
2607                m_pState->ChangeRemoteDir(m_pDirectoryListing->path, _T(".."));
2608        }
2609}
2610
2611void CRemoteListView::OnMenuNewfile(wxCommandEvent&)
2612{
2613        if (!m_pState->IsRemoteIdle() || !m_pDirectoryListing) {
2614                wxBell();
2615                return;
2616        }
2617
2618        CInputDialog dlg;
2619        if (!dlg.Create(this, _("Create empty file"), _("Please enter the name of the file which should be created:")))
2620                return;
2621
2622        if (dlg.ShowModal() != wxID_OK)
2623                return;
2624
2625        if (dlg.GetValue().empty()) {
2626                wxBell();
2627                return;
2628        }
2629
2630        wxString newFileName = dlg.GetValue();
2631
2632        // Check if target file already exists
2633        for (unsigned int i = 0; i < m_pDirectoryListing->GetCount(); ++i) {
2634                if (newFileName == (*m_pDirectoryListing)[i].name) {
2635                        wxMessageBoxEx(_("Target filename already exists!"));
2636                        return;
2637                }
2638        }
2639
2640        CEditHandler* edithandler = CEditHandler::Get(); // Used to get the temporary folder
2641
2642        wxString emptyfile_name = _T("empty_file_yq744zm");
2643        wxString emptyfile = edithandler->GetLocalDirectory() + emptyfile_name;
2644
2645        // Create the empty temporary file
2646        {
2647                wxFile file;
2648                wxLogNull log;
2649                file.Create(emptyfile);
2650        }
2651
2652        const CServer* pServer = m_pState->GetServer();
2653        if (!pServer) {
2654                wxBell();
2655                return;
2656        }
2657
2658        CFileTransferCommand *cmd = new CFileTransferCommand(emptyfile, m_pDirectoryListing->path, newFileName, false, CFileTransferCommand::t_transferSettings());
2659        m_pState->m_pCommandQueue->ProcessCommand(cmd);
2660}
Note: See TracBrowser for help on using the repository browser.