1 #include "filezilla.h"
2
3 #define FILELISTCTRL_INCLUDE_TEMPLATE_DEFINITION
4
5 #include "view.h"
6 #include "LocalListView.h"
7 #include "queue.h"
8 #include "filezillaapp.h"
9 #include "filter_manager.h"
10 #include "file_utils.h"
11 #include "infotext.h"
12 #include "inputdialog.h"
13 #include <algorithm>
14 #include "dndobjects.h"
15 #include "Options.h"
16 #ifdef __WXMSW__
17 #include "lm.h"
18 #include <wx/msw/registry.h>
19 #include "volume_enumerator.h"
20 #endif
21 #include "dragdropmanager.h"
22 #include "drop_target_ex.h"
23 #include "edithandler.h"
24 #include "filelist_statusbar.h"
25 #include "graphics.h"
26 #include "local_recursive_operation.h"
27 #include "sizeformatting.h"
28 #include "timeformatting.h"
29
30 #include <libfilezilla/local_filesys.hpp>
31 #include <libfilezilla/process.hpp>
32 #include <libfilezilla/recursive_remove.hpp>
33
34 #include <wx/menu.h>
35
36 class CLocalListViewDropTarget final : public CFileDropTarget<wxListCtrlEx>
37 {
38 public:
CLocalListViewDropTarget(CLocalListView * pLocalListView)39 CLocalListViewDropTarget(CLocalListView* pLocalListView)
40 : CFileDropTarget<wxListCtrlEx>(pLocalListView)
41 , m_pLocalListView(pLocalListView)
42 {
43 }
44
ClearDropHighlight()45 void ClearDropHighlight()
46 {
47 const int dropTarget = m_pLocalListView->m_dropTarget;
48 if (dropTarget != -1)
49 {
50 m_pLocalListView->m_dropTarget = -1;
51 #ifdef __WXMSW__
52 m_pLocalListView->SetItemState(dropTarget, 0, wxLIST_STATE_DROPHILITED);
53 #else
54 m_pLocalListView->RefreshItem(dropTarget);
55 #endif
56 }
57 }
58
OnData(wxCoord x,wxCoord y,wxDragResult def)59 virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def)
60 {
61 def = CScrollableDropTarget<wxListCtrlEx>::FixupDragResult(def);
62
63 if (def == wxDragError ||
64 def == wxDragNone ||
65 def == wxDragCancel)
66 {
67 return def;
68 }
69
70 if (m_pLocalListView->m_fileData.empty()) {
71 return wxDragError;
72 }
73
74 if (def != wxDragCopy && def != wxDragMove) {
75 return wxDragError;
76 }
77
78 CDragDropManager* pDragDropManager = CDragDropManager::Get();
79 if (pDragDropManager) {
80 pDragDropManager->pDropTarget = m_pLocalListView;
81 }
82
83 std::wstring subdir;
84 int flags;
85 int hit = m_pLocalListView->HitTest(wxPoint(x, y), flags, 0);
86 if (hit != -1 && (flags & wxLIST_HITTEST_ONITEM)) {
87 const CLocalFileData* const data = m_pLocalListView->GetData(hit);
88 if (data && data->dir) {
89 subdir = data->name;
90 }
91 }
92
93 CLocalPath dir = m_pLocalListView->m_state.GetLocalDir();
94 if (!subdir.empty()) {
95 if (!dir.ChangePath(subdir)) {
96 return wxDragError;
97 }
98 }
99
100 if (!dir.IsWriteable()) {
101 return wxDragError;
102 }
103
104 if (!GetData()) {
105 return wxDragError;
106 }
107
108 auto const format = m_pDataObject->GetReceivedFormat();
109 if (format == m_pFileDataObject->GetFormat()) {
110 m_pLocalListView->m_state.HandleDroppedFiles(m_pFileDataObject, dir, def == wxDragCopy);
111 }
112 else if (format == m_pLocalDataObject->GetFormat()) {
113 m_pLocalListView->m_state.HandleDroppedFiles(m_pLocalDataObject, dir, def == wxDragCopy);
114 }
115 else {
116 if (m_pRemoteDataObject->GetProcessId() != (int)wxGetProcessId()) {
117 wxMessageBoxEx(_("Drag&drop between different instances of FileZilla has not been implemented yet."));
118 return wxDragNone;
119 }
120
121 if (!m_pLocalListView->m_state.GetSite() || m_pRemoteDataObject->GetSite().server != m_pLocalListView->m_state.GetSite().server) {
122 wxMessageBoxEx(_("Drag&drop between different servers has not been implemented yet."));
123 return wxDragNone;
124 }
125
126 if (!m_pLocalListView->m_state.DownloadDroppedFiles(m_pRemoteDataObject, dir)) {
127 return wxDragNone;
128 }
129 }
130
131 return def;
132 }
133
OnDrop(wxCoord x,wxCoord y)134 virtual bool OnDrop(wxCoord x, wxCoord y)
135 {
136 CScrollableDropTarget<wxListCtrlEx>::OnDrop(x, y);
137 ClearDropHighlight();
138
139 if (m_pLocalListView->m_fileData.empty()) {
140 return false;
141 }
142
143 return true;
144 }
145
DisplayDropHighlight(wxPoint const & point)146 virtual int DisplayDropHighlight(wxPoint const& point) override
147 {
148 DoDisplayDropHighlight(point);
149 return -1;
150 }
151
DoDisplayDropHighlight(wxPoint point)152 virtual wxString DoDisplayDropHighlight(wxPoint point)
153 {
154 wxString subDir;
155
156 int flags;
157 int hit = m_pLocalListView->HitTest(point, flags, 0);
158 if (!(flags & wxLIST_HITTEST_ONITEM)) {
159 hit = -1;
160 }
161
162 if (hit != -1) {
163 const CLocalFileData* const data = m_pLocalListView->GetData(hit);
164 if (!data || !data->dir) {
165 hit = -1;
166 }
167 else {
168 const CDragDropManager* pDragDropManager = CDragDropManager::Get();
169 if (pDragDropManager && pDragDropManager->pDragSource == m_pLocalListView) {
170 if (m_pLocalListView->GetItemState(hit, wxLIST_STATE_SELECTED)) {
171 hit = -1;
172 }
173 else {
174 subDir = data->name;
175 }
176 }
177 else {
178 subDir = data->name;
179 }
180 }
181 }
182 if (hit != m_pLocalListView->m_dropTarget) {
183 ClearDropHighlight();
184 if (hit != -1) {
185 m_pLocalListView->m_dropTarget = hit;
186 #ifdef __WXMSW__
187 m_pLocalListView->SetItemState(hit, wxLIST_STATE_DROPHILITED, wxLIST_STATE_DROPHILITED);
188 #else
189 m_pLocalListView->RefreshItem(hit);
190 #endif
191 }
192 }
193
194 return subDir;
195 }
196
OnDragOver(wxCoord x,wxCoord y,wxDragResult def)197 virtual wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult def)
198 {
199 def = CScrollableDropTarget<wxListCtrlEx>::OnDragOver(x, y, def);
200
201 if (def == wxDragError ||
202 def == wxDragNone ||
203 def == wxDragCancel)
204 {
205 ClearDropHighlight();
206 return def;
207 }
208
209 if (m_pLocalListView->m_fileData.empty()) {
210 ClearDropHighlight();
211 return wxDragNone;
212 }
213
214 std::wstring const subdir = DoDisplayDropHighlight(wxPoint(x, y)).ToStdWstring();
215
216 CLocalPath dir = m_pLocalListView->m_state.GetLocalDir();
217 if (subdir.empty()) {
218 const CDragDropManager* pDragDropManager = CDragDropManager::Get();
219 if (pDragDropManager && pDragDropManager->localParent == m_pLocalListView->m_dir) {
220 return wxDragNone;
221 }
222 }
223 else {
224 if (!dir.ChangePath(subdir)) {
225 return wxDragNone;
226 }
227 }
228
229 if (!dir.IsWriteable()) {
230 return wxDragNone;
231 }
232
233 return def;
234 }
235
OnLeave()236 virtual void OnLeave()
237 {
238 CScrollableDropTarget<wxListCtrlEx>::OnLeave();
239 ClearDropHighlight();
240 }
241
OnEnter(wxCoord x,wxCoord y,wxDragResult def)242 virtual wxDragResult OnEnter(wxCoord x, wxCoord y, wxDragResult def)
243 {
244 def = CScrollableDropTarget<wxListCtrlEx>::OnEnter(x, y, def);
245 return OnDragOver(x, y, def);
246 }
247
248 protected:
249 CLocalListView *m_pLocalListView{};
250 };
251
BEGIN_EVENT_TABLE(CLocalListView,CFileListCtrl<CLocalFileData>)252 BEGIN_EVENT_TABLE(CLocalListView, CFileListCtrl<CLocalFileData>)
253 EVT_LIST_ITEM_ACTIVATED(wxID_ANY, CLocalListView::OnItemActivated)
254 EVT_CONTEXT_MENU(CLocalListView::OnContextMenu)
255 // Map both ID_UPLOAD and ID_ADDTOQUEUE to OnMenuUpload, code is identical
256 EVT_MENU(XRCID("ID_UPLOAD"), CLocalListView::OnMenuUpload)
257 EVT_MENU(XRCID("ID_ADDTOQUEUE"), CLocalListView::OnMenuUpload)
258 EVT_MENU(XRCID("ID_MKDIR"), CLocalListView::OnMenuMkdir)
259 EVT_MENU(XRCID("ID_MKDIR_CHGDIR"), CLocalListView::OnMenuMkdirChgDir)
260 EVT_MENU(XRCID("ID_DELETE"), CLocalListView::OnMenuDelete)
261 EVT_MENU(XRCID("ID_RENAME"), CLocalListView::OnMenuRename)
262 EVT_KEY_DOWN(CLocalListView::OnKeyDown)
263 EVT_SIZE(CLocalListView::OnSize)
264 EVT_LIST_BEGIN_DRAG(wxID_ANY, CLocalListView::OnBeginDrag)
265 EVT_MENU(XRCID("ID_OPEN"), CLocalListView::OnMenuOpen)
266 EVT_MENU(XRCID("ID_EDIT"), CLocalListView::OnMenuEdit)
267 EVT_MENU(XRCID("ID_ENTER"), CLocalListView::OnMenuEnter)
268 #ifdef __WXMSW__
269 EVT_COMMAND(-1, fzEVT_VOLUMESENUMERATED, CLocalListView::OnVolumesEnumerated)
270 EVT_COMMAND(-1, fzEVT_VOLUMEENUMERATED, CLocalListView::OnVolumesEnumerated)
271 #endif
272 EVT_MENU(XRCID("ID_CONTEXT_REFRESH"), CLocalListView::OnMenuRefresh)
273 END_EVENT_TABLE()
274
275 CLocalListView::CLocalListView(CView* pParent, CState& state, CQueueView *pQueue)
276 : CFileListCtrl<CLocalFileData>(pParent, pQueue),
277 CStateEventHandler(state),
278 m_parentView(pParent)
279 {
280 wxGetApp().AddStartupProfileRecord("CLocalListView::CLocalListView");
281 m_state.RegisterHandler(this, STATECHANGE_LOCAL_DIR);
282 m_state.RegisterHandler(this, STATECHANGE_APPLYFILTER);
283 m_state.RegisterHandler(this, STATECHANGE_LOCAL_REFRESH_FILE);
284 m_state.RegisterHandler(this, STATECHANGE_SERVER);
285
286 const unsigned long widths[4] = { 170, 80, 120, 120 };
287
288 AddColumn(_("Filename"), wxLIST_FORMAT_LEFT, widths[0], true);
289 AddColumn(_("Filesize"), wxLIST_FORMAT_RIGHT, widths[1]);
290 AddColumn(_("Filetype"), wxLIST_FORMAT_LEFT, widths[2]);
291 AddColumn(_("Last modified"), wxLIST_FORMAT_LEFT, widths[3]);
292 LoadColumnSettings(OPTION_LOCALFILELIST_COLUMN_WIDTHS, OPTION_LOCALFILELIST_COLUMN_SHOWN, OPTION_LOCALFILELIST_COLUMN_ORDER);
293
294 SetImageList(GetSystemImageList(), wxIMAGE_LIST_SMALL);
295
296 InitHeaderSortImageList();
297
298 InitSort(OPTION_LOCALFILELIST_SORTORDER);
299
300 SetDropTarget(new CLocalListViewDropTarget(this));
301
302 EnablePrefixSearch(true);
303
304 m_windowTinter = std::make_unique<CWindowTinter>(*GetMainWindow());
305
306 m_pInfoText = new CInfoText(*this);
307 }
308
~CLocalListView()309 CLocalListView::~CLocalListView()
310 {
311 wxString str = wxString::Format(_T("%d %d"), m_sortDirection, m_sortColumn);
312 COptions::Get()->set(OPTION_LOCALFILELIST_SORTORDER, str.ToStdWstring());
313
314 #ifdef __WXMSW__
315 volumeEnumeratorThread_.reset();
316 #endif
317 }
318
DisplayDir(CLocalPath const & dirname)319 bool CLocalListView::DisplayDir(CLocalPath const& dirname)
320 {
321 CancelLabelEdit();
322
323 std::wstring focused;
324 int focusedItem = -1;
325 std::vector<std::wstring> selectedNames;
326 bool ensureVisible = false;
327 if (m_dir != dirname) {
328 ResetSearchPrefix();
329
330 if (IsComparing()) {
331 ExitComparisonMode();
332 }
333
334 ClearSelection();
335 focused = m_state.GetPreviouslyVisitedLocalSubdir();
336 ensureVisible = !focused.empty();
337 if (focused.empty()) {
338 focused = _T("..");
339 }
340
341 if (GetItemCount()) {
342 EnsureVisible(0);
343 }
344 m_dir = dirname;
345 }
346 else {
347 // Remember which items were selected
348 selectedNames = RememberSelectedItems(focused, focusedItem);
349 }
350
351 if (m_pFilelistStatusBar) {
352 m_pFilelistStatusBar->UnselectAll();
353 }
354
355 const int oldItemCount = m_indexMapping.size();
356
357 m_fileData.clear();
358 m_indexMapping.clear();
359
360 m_hasParent = m_dir.HasLogicalParent();
361
362 if (m_hasParent) {
363 CLocalFileData data;
364 data.dir = true;
365 data.name = _T("..");
366 data.size = -1;
367 m_fileData.push_back(data);
368 m_indexMapping.push_back(0);
369 }
370
371 #ifdef __WXMSW__
372 if (m_dir.GetPath() == _T("\\")) {
373 DisplayDrives();
374 }
375 else if (m_dir.GetPath().substr(0, 2) == _T("\\\\")) {
376 auto pos = m_dir.GetPath().find('\\', 2);
377 if (pos != std::wstring::npos && pos + 1 < m_dir.GetPath().size()) {
378 goto regular_dir;
379 }
380
381 // UNC path without shares
382 DisplayShares(m_dir.GetPath());
383 }
384 else
385 #endif
386 {
387 #ifdef __WXMSW__
388 regular_dir:
389 #endif
390 CStateFilterManager const& filter = m_state.GetStateFilterManager();
391 fz::local_filesys local_filesys;
392
393 auto result = local_filesys.begin_find_files(fz::to_native(m_dir.GetPath()), false);
394 if (!result) {
395
396 if (result.error_ == fz::result::noperm) {
397 SetInfoText(_("You do not have permission to list this directory"));
398 }
399 else {
400 SetInfoText(_("Could not list directory contents"));
401 }
402
403 SetItemCount(1);
404 if (m_pFilelistStatusBar) {
405 m_pFilelistStatusBar->SetDirectoryContents(0, 0, 0, 0, 0);
406 }
407
408 return false;
409 }
410
411 SetInfoText(wxString());
412
413 int64_t totalSize{};
414 int unknown_sizes = 0;
415 int totalFileCount = 0;
416 int totalDirCount = 0;
417 int hidden = 0;
418
419 int num = m_fileData.size();
420 CLocalFileData data;
421 bool wasLink{};
422 fz::local_filesys::type t{};
423 fz::native_string name;
424 while (local_filesys.get_next_file(name, wasLink, t, &data.size, &data.time, &data.attributes)) {
425 data.name = fz::to_wstring(name);
426 data.dir = t == fz::local_filesys::dir;
427 if (name.empty() || data.name.empty()) {
428 wxGetApp().DisplayEncodingWarning();
429 continue;
430 }
431
432 m_fileData.push_back(data);
433 if (!filter.FilenameFiltered(data.name, m_dir.GetPath(), data.dir, data.size, true, data.attributes, data.time)) {
434 if (data.dir) {
435 ++totalDirCount;
436 }
437 else {
438 if (data.size != -1) {
439 totalSize += data.size;
440 }
441 else {
442 ++unknown_sizes;
443 }
444 ++totalFileCount;
445 }
446 m_indexMapping.push_back(num);
447 }
448 else {
449 ++hidden;
450 }
451 ++num;
452 }
453
454 if (m_pFilelistStatusBar) {
455 m_pFilelistStatusBar->SetDirectoryContents(totalFileCount, totalDirCount, totalSize, unknown_sizes, hidden);
456 }
457 }
458
459 if (m_dropTarget != -1) {
460 CLocalFileData* data = GetData(m_dropTarget);
461 if (!data || !data->dir) {
462 SetItemState(m_dropTarget, 0, wxLIST_STATE_DROPHILITED);
463 m_dropTarget = -1;
464 }
465 }
466
467 const int count = m_indexMapping.size();
468 if (oldItemCount != count) {
469 SetItemCount(count);
470 }
471
472 SortList(-1, -1, false);
473
474 if (IsComparing()) {
475 m_originalIndexMapping.clear();
476 RefreshComparison();
477 }
478
479 ReselectItems(selectedNames, focused, focusedItem, ensureVisible);
480
481 RefreshListOnly();
482
483 return true;
484 }
485
486 // See comment to OnGetItemText
OnGetItemImage(long item) const487 int CLocalListView::OnGetItemImage(long item) const
488 {
489 CLocalListView *pThis = const_cast<CLocalListView *>(this);
490 CLocalFileData *data = pThis->GetData(item);
491 if (!data) {
492 return -1;
493 }
494 int &icon = data->icon;
495
496 if (icon == -2) {
497 std::wstring path;
498 if (data->name != L"..") {
499 #ifdef __WXMSW__
500 if (m_dir.GetPath() == L"\\") {
501 path = data->name + L"\\";
502 }
503 else
504 #endif
505 {
506 path = m_dir.GetPath() + data->name;
507 }
508 }
509
510 icon = pThis->GetIconIndex(data->dir ? iconType::dir : iconType::file, path);
511 }
512 return icon;
513 }
514
OnItemActivated(wxListEvent & event)515 void CLocalListView::OnItemActivated(wxListEvent &event)
516 {
517 int count = 0;
518 bool back = false;
519
520 int item = -1;
521 for (;;) {
522 item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
523 if (item == -1) {
524 break;
525 }
526
527 count++;
528
529 if (!item && m_hasParent) {
530 back = true;
531 }
532 }
533 if (count > 1) {
534 if (back) {
535 wxBell();
536 return;
537 }
538
539 wxCommandEvent cmdEvent;
540 OnMenuUpload(cmdEvent);
541 return;
542 }
543
544 item = event.GetIndex();
545
546 CLocalFileData *data = GetData(item);
547 if (!data) {
548 return;
549 }
550
551 if (data->dir) {
552 const int action = COptions::Get()->get_int(OPTION_DOUBLECLICK_ACTION_DIRECTORY);
553 if (action == 3) {
554 // No action
555 wxBell();
556 return;
557 }
558
559 if (!action || data->name == _T("..")) {
560 // Enter action
561
562 std::wstring error;
563 if (!m_state.SetLocalDir(data->name, &error)) {
564 if (!error.empty()) {
565 wxMessageBoxEx(error, _("Failed to change directory"), wxICON_INFORMATION);
566 }
567 else {
568 wxBell();
569 }
570 }
571 return;
572 }
573
574 wxCommandEvent evt(0, action == 1 ? XRCID("ID_UPLOAD") : XRCID("ID_ADDTOQUEUE"));
575 OnMenuUpload(evt);
576 return;
577 }
578
579 if (data->comparison_flags == fill) {
580 wxBell();
581 return;
582 }
583
584 const int action = COptions::Get()->get_int(OPTION_DOUBLECLICK_ACTION_FILE);
585 if (action == 3) {
586 // No action
587 wxBell();
588 return;
589 }
590
591 if (action == 2) {
592 // View / Edit action
593 wxCommandEvent evt;
594 OnMenuEdit(evt);
595 return;
596 }
597
598 Site const& site = m_state.GetSite();
599 if (!site) {
600 wxBell();
601 return;
602 }
603
604 CServerPath path = m_state.GetRemotePath();
605 if (path.empty()) {
606 wxBell();
607 return;
608 }
609
610 const bool queue_only = action == 1;
611
612 m_pQueue->QueueFile(queue_only, false, data->name, wxEmptyString, m_dir, path, site, data->size);
613 m_pQueue->QueueFile_Finish(true);
614 }
615
OnMenuEnter(wxCommandEvent &)616 void CLocalListView::OnMenuEnter(wxCommandEvent &)
617 {
618 int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
619 if (item == -1) {
620 wxBell();
621 return;
622 }
623
624 if (GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1) {
625 wxBell();
626 return;
627 }
628
629 CLocalFileData *data = GetData(item);
630 if (!data || !data->dir) {
631 wxBell();
632 return;
633 }
634
635 std::wstring error;
636 if (!m_state.SetLocalDir(data->name, &error)) {
637 if (!error.empty()) {
638 wxMessageBoxEx(error, _("Failed to change directory"), wxICON_INFORMATION);
639 }
640 else {
641 wxBell();
642 }
643 }
644 }
645
646 #ifdef __WXMSW__
DisplayDrives()647 void CLocalListView::DisplayDrives()
648 {
649 SetInfoText(wxString());
650 int count = m_fileData.size();
651
652 std::vector<std::wstring> drives = CVolumeDescriptionEnumeratorThread::GetDrives();
653 for (auto it = drives.begin(); it != drives.end(); ++it) {
654 std::wstring drive = *it;
655 if (!drive.empty() && drive.back() == '\\') {
656 drive.pop_back();
657 }
658
659 CLocalFileData data;
660 data.name = drive;
661 data.label = fz::sparse_optional<std::wstring>(data.name);
662 data.dir = true;
663 data.size = -1;
664 data.icon = GetIconIndex(iconType::dir, std::wstring(), false);
665
666 m_fileData.push_back(data);
667 m_indexMapping.push_back(count);
668 ++count;
669 }
670
671 if (m_pFilelistStatusBar) {
672 m_pFilelistStatusBar->SetDirectoryContents(0, drives.size(), 0, false, 0);
673 }
674
675 if (!volumeEnumeratorThread_) {
676 volumeEnumeratorThread_ = std::make_unique<CVolumeDescriptionEnumeratorThread>(this, m_state.pool_);
677 if (volumeEnumeratorThread_->Failed()) {
678 volumeEnumeratorThread_.reset();
679 }
680 }
681 }
682
DisplayShares(wxString computer)683 void CLocalListView::DisplayShares(wxString computer)
684 {
685 SetInfoText(wxString());
686
687 // Cast through a union to avoid warning about breaking strict aliasing rule
688 union
689 {
690 SHARE_INFO_1* pShareInfo;
691 LPBYTE pShareInfoBlob;
692 } si;
693
694 DWORD read, total;
695 DWORD resume_handle = 0;
696
697 if (!computer.empty() && computer.Last() == '\\') {
698 computer.RemoveLast();
699 }
700
701 int j = m_fileData.size();
702 int share_count = 0;
703 int res = 0;
704 do {
705 const wxWX2WCbuf buf = computer.wc_str(wxConvLocal);
706 res = NetShareEnum((wchar_t*)(const wchar_t*)buf, 1, &si.pShareInfoBlob, MAX_PREFERRED_LENGTH, &read, &total, &resume_handle);
707
708 if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA) {
709 break;
710 }
711
712 SHARE_INFO_1* p = si.pShareInfo;
713 for (unsigned int i = 0; i < read; ++i, ++p) {
714 if (p->shi1_type != STYPE_DISKTREE) {
715 continue;
716 }
717
718 CLocalFileData data;
719 data.name = p->shi1_netname;
720 #ifdef __WXMSW__
721 data.label = fz::sparse_optional<std::wstring>(data.name);
722 #endif
723 data.dir = true;
724 data.size = -1;
725
726 m_fileData.push_back(data);
727 m_indexMapping.push_back(j++);
728
729 share_count++;
730 }
731
732 NetApiBufferFree(si.pShareInfo);
733 }
734 while (res == ERROR_MORE_DATA);
735
736 if (m_pFilelistStatusBar) {
737 m_pFilelistStatusBar->SetDirectoryContents(0, share_count, 0, false, 0);
738 }
739 }
740
741 #endif //__WXMSW__
742
GetData(unsigned int item)743 CLocalFileData* CLocalListView::GetData(unsigned int item)
744 {
745 if (!IsItemValid(item)) {
746 return 0;
747 }
748
749 return &m_fileData[m_indexMapping[item]];
750 }
751
IsItemValid(unsigned int item) const752 bool CLocalListView::IsItemValid(unsigned int item) const
753 {
754 if (item >= m_indexMapping.size()) {
755 return false;
756 }
757
758 unsigned int index = m_indexMapping[item];
759 if (index >= m_fileData.size()) {
760 return false;
761 }
762
763 return true;
764 }
765
GetSortComparisonObject()766 std::unique_ptr<CFileListCtrlSortBase> CLocalListView::GetSortComparisonObject()
767 {
768 CFileListCtrlSortBase::DirSortMode dirSortMode = GetDirSortMode();
769 NameSortMode nameSortMode = GetNameSortMode();
770
771 if (!m_sortDirection) {
772 if (m_sortColumn == 1) {
773 return std::make_unique<CFileListCtrlSortSize<std::vector<CLocalFileData>, CLocalFileData>>(m_fileData, m_fileData, dirSortMode, nameSortMode, this);
774 }
775 else if (m_sortColumn == 2) {
776 return std::make_unique<CFileListCtrlSortType<std::vector<CLocalFileData>, CLocalFileData>>(m_fileData, m_fileData, dirSortMode, nameSortMode, this);
777 }
778 else if (m_sortColumn == 3) {
779 return std::make_unique<CFileListCtrlSortTime<std::vector<CLocalFileData>, CLocalFileData>>(m_fileData, m_fileData, dirSortMode, nameSortMode, this);
780 }
781 else {
782 return std::make_unique<CFileListCtrlSortName<std::vector<CLocalFileData>, CLocalFileData>>(m_fileData, m_fileData, dirSortMode, nameSortMode, this);
783 }
784 }
785 else {
786 if (m_sortColumn == 1) {
787 return std::make_unique<CReverseSort<CFileListCtrlSortSize<std::vector<CLocalFileData>, CLocalFileData>, CLocalFileData>>(m_fileData, m_fileData, dirSortMode, nameSortMode, this);
788 }
789 else if (m_sortColumn == 2) {
790 return std::make_unique<CReverseSort<CFileListCtrlSortType<std::vector<CLocalFileData>, CLocalFileData>, CLocalFileData>>(m_fileData, m_fileData, dirSortMode, nameSortMode, this);
791 }
792 else if (m_sortColumn == 3) {
793 return std::make_unique<CReverseSort<CFileListCtrlSortTime<std::vector<CLocalFileData>, CLocalFileData>, CLocalFileData>>(m_fileData, m_fileData, dirSortMode, nameSortMode, this);
794 }
795 else {
796 return std::make_unique<CReverseSort<CFileListCtrlSortName<std::vector<CLocalFileData>, CLocalFileData>, CLocalFileData>>(m_fileData, m_fileData, dirSortMode, nameSortMode, this);
797 }
798 }
799 }
800
OnContextMenu(wxContextMenuEvent & event)801 void CLocalListView::OnContextMenu(wxContextMenuEvent& event)
802 {
803 if (GetEditControl()) {
804 event.Skip();
805 return;
806 }
807
808 wxMenu menu;
809
810 auto item = new wxMenuItem(&menu, XRCID("ID_UPLOAD"), _("&Upload"), _("Upload selected files and directories"));
811 item->SetBitmap(wxArtProvider::GetBitmap(_T("ART_UPLOAD"), wxART_MENU));
812 menu.Append(item);
813 item = new wxMenuItem(&menu, XRCID("ID_ADDTOQUEUE"), _("&Add files to queue"), _("Add selected files and folders to the transfer queue"));
814 item->SetBitmap(wxArtProvider::GetBitmap(_T("ART_UPLOADADD"), wxART_MENU));
815 menu.Append(item);
816 menu.Append(XRCID("ID_ENTER"), _("E&nter directory"), _("Enter selected directory"));
817
818 menu.AppendSeparator();
819 menu.Append(XRCID("ID_OPEN"), _("&Open"), _("Open the file."));
820 menu.Append(XRCID("ID_EDIT"), _("&Edit"), _("Edit the file with the configured editor and upload changes to the server."));
821
822 menu.AppendSeparator();
823 menu.Append(XRCID("ID_MKDIR"), _("&Create directory"), _("Create a new subdirectory in the current directory"));
824 menu.Append(XRCID("ID_MKDIR_CHGDIR"), _("Create director&y and enter it"), _("Create a new subdirectory in the current directory and change into it"));
825 menu.Append(XRCID("ID_CONTEXT_REFRESH"), _("Re&fresh"));
826
827 menu.AppendSeparator();
828 menu.Append(XRCID("ID_DELETE"),_("&Delete"), _("Delete selected files and directories"));
829 menu.Append(XRCID("ID_RENAME"), _("&Rename"), _("Rename selected files and directories"));
830
831 const bool connected = m_state.IsRemoteConnected();
832 if (!connected) {
833 menu.Enable(XRCID("ID_EDIT"), COptions::Get()->get_int(OPTION_EDIT_TRACK_LOCAL) == 0);
834 menu.Enable(XRCID("ID_UPLOAD"), false);
835 menu.Enable(XRCID("ID_ADDTOQUEUE"), false);
836 }
837
838 int index = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
839 int count = 0;
840 int fillCount = 0;
841 bool selectedDir = false;
842 while (index != -1) {
843 ++count;
844 const CLocalFileData* const data = GetData(index);
845 if (!data || (!index && m_hasParent)) {
846 menu.Enable(XRCID("ID_OPEN"), false);
847 menu.Enable(XRCID("ID_RENAME"), false);
848 menu.Enable(XRCID("ID_EDIT"), false);
849 }
850 if ((data && data->comparison_flags == fill) || (!index && m_hasParent)) {
851 fillCount++;
852 }
853 if (data && data->dir) {
854 selectedDir = true;
855 }
856 index = GetNextItem(index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
857 }
858 if (!count || fillCount == count) {
859 menu.Delete(XRCID("ID_ENTER"));
860 menu.Enable(XRCID("ID_UPLOAD"), false);
861 menu.Enable(XRCID("ID_ADDTOQUEUE"), false);
862 menu.Enable(XRCID("ID_DELETE"), false);
863 menu.Enable(XRCID("ID_RENAME"), false);
864 menu.Enable(XRCID("ID_EDIT"), false);
865 }
866 else if (count > 1) {
867 menu.Delete(XRCID("ID_ENTER"));
868 menu.Enable(XRCID("ID_RENAME"), false);
869 }
870 else {
871 // Exactly one item selected
872 if (!selectedDir) {
873 menu.Delete(XRCID("ID_ENTER"));
874 }
875 }
876 if (selectedDir) {
877 menu.Enable(XRCID("ID_EDIT"), false);
878 if (m_state.GetLocalRecursiveOperation() && m_state.GetLocalRecursiveOperation()->IsActive()) {
879 menu.Enable(XRCID("ID_UPLOAD"), false);
880 menu.Enable(XRCID("ID_ADDTOQUEUE"), false);
881 }
882 }
883
884 PopupMenu(&menu);
885 }
886
OnMenuUpload(wxCommandEvent & event)887 void CLocalListView::OnMenuUpload(wxCommandEvent& event)
888 {
889 Site const& site = m_state.GetSite();
890 if (!site) {
891 wxBell();
892 return;
893 }
894
895 bool added = false;
896
897 bool queue_only = event.GetId() == XRCID("ID_ADDTOQUEUE");
898
899 auto recursiveOperation = m_state.GetLocalRecursiveOperation();
900 if (!recursiveOperation || recursiveOperation->IsActive()) {
901 wxBell();
902 return;
903 }
904
905 local_recursion_root root;
906
907 long item = -1;
908 for (;;) {
909 item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
910 if (!item && m_hasParent) {
911 continue;
912 }
913 if (item == -1) {
914 break;
915 }
916
917 const CLocalFileData *data = GetData(item);
918 if (!data) {
919 break;
920 }
921
922 if (data->comparison_flags == fill) {
923 continue;
924 }
925
926 CServerPath remotePath = m_state.GetRemotePath();
927 if (remotePath.empty()) {
928 wxBell();
929 break;
930 }
931
932 if (data->dir) {
933 CLocalPath localPath = m_dir;
934 if (!localPath.ChangePath(data->name)) {
935 continue;
936 }
937 if (!remotePath.ChangePath(data->name)) {
938 continue;
939 }
940
941 root.add_dir_to_visit(localPath, remotePath);
942 }
943 else {
944 m_pQueue->QueueFile(queue_only, false, data->name, wxEmptyString, m_dir, remotePath, site, data->size);
945 added = true;
946 }
947 }
948
949 if (added) {
950 m_pQueue->QueueFile_Finish(!queue_only);
951 }
952
953 if (!root.empty()) {
954 recursiveOperation->AddRecursionRoot(std::move(root));
955 CFilterManager filter;
956 recursiveOperation->StartRecursiveOperation(recursive_operation::recursive_transfer, filter.GetActiveFilters(), !queue_only);
957 }
958 }
959
960 // Create a new Directory
OnMenuMkdir(wxCommandEvent &)961 void CLocalListView::OnMenuMkdir(wxCommandEvent&)
962 {
963 wxString newdir = MenuMkdir();
964 if (!newdir.empty()) {
965 m_state.RefreshLocal();
966 }
967 }
968
969 // Create a new Directory and enter the new Directory
OnMenuMkdirChgDir(wxCommandEvent &)970 void CLocalListView::OnMenuMkdirChgDir(wxCommandEvent&)
971 {
972 std::wstring newdir = MenuMkdir().ToStdWstring();
973 if (newdir.empty()) {
974 return;
975 }
976
977 // OnMenuEnter
978 std::wstring error;
979 if (!m_state.SetLocalDir(newdir, &error)) {
980 if (!error.empty()) {
981 wxMessageBoxEx(error, _("Failed to change directory"), wxICON_INFORMATION);
982 }
983 else {
984 wxBell();
985 }
986 }
987 }
988
989 // Helper-Function to create a new Directory
990 // Returns the name of the new directory
MenuMkdir()991 wxString CLocalListView::MenuMkdir()
992 {
993 CInputDialog dlg;
994 if (!dlg.Create(this, _("Create directory"), _("Please enter the name of the directory which should be created:"))) {
995 return wxString();
996 }
997
998 if (dlg.ShowModal() != wxID_OK) {
999 return wxString();
1000 }
1001
1002 if (dlg.GetValue().empty()) {
1003 wxBell();
1004 return wxString();
1005 }
1006
1007 wxFileName fn(dlg.GetValue(), wxString());
1008 fn.Normalize(wxPATH_NORM_ALL, m_dir.GetPath());
1009
1010 bool res;
1011
1012 {
1013 wxLogNull log;
1014 res = fn.Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL);
1015 }
1016
1017 if (!res) {
1018 wxBell();
1019 return wxString();
1020 }
1021
1022 // Return name of the New Directory
1023 return fn.GetPath();
1024 }
1025
OnMenuDelete(wxCommandEvent &)1026 void CLocalListView::OnMenuDelete(wxCommandEvent&)
1027 {
1028 std::list<fz::native_string> pathsToDelete;
1029 long item = -1;
1030 while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
1031 if (!item && m_hasParent) {
1032 continue;
1033 }
1034
1035 CLocalFileData *data = GetData(item);
1036 if (!data) {
1037 continue;
1038 }
1039
1040 if (data->comparison_flags == fill) {
1041 continue;
1042 }
1043
1044 pathsToDelete.push_back(fz::to_native(m_dir.GetPath() + data->name));
1045 }
1046 gui_recursive_remove rmd(this);
1047 rmd.remove(pathsToDelete);
1048
1049 m_state.SetLocalDir(m_dir);
1050 }
1051
OnMenuRename(wxCommandEvent &)1052 void CLocalListView::OnMenuRename(wxCommandEvent&)
1053 {
1054 if (GetEditControl()) {
1055 GetEditControl()->SetFocus();
1056 return;
1057 }
1058
1059 int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1060 if (item < 0 || (!item && m_hasParent)) {
1061 wxBell();
1062 return;
1063 }
1064
1065 if (GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1) {
1066 wxBell();
1067 return;
1068 }
1069
1070 CLocalFileData *data = GetData(item);
1071 if (!data || data->comparison_flags == fill) {
1072 wxBell();
1073 return;
1074 }
1075
1076 EditLabel(item);
1077 }
1078
OnKeyDown(wxKeyEvent & event)1079 void CLocalListView::OnKeyDown(wxKeyEvent& event)
1080 {
1081 #ifdef __WXMAC__
1082 #define CursorModifierKey wxMOD_CMD
1083 #else
1084 #define CursorModifierKey wxMOD_ALT
1085 #endif
1086
1087 const int code = event.GetKeyCode();
1088 if (code == WXK_DELETE || code == WXK_NUMPAD_DELETE) {
1089 if (GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == -1) {
1090 wxBell();
1091 return;
1092 }
1093
1094 wxCommandEvent tmp;
1095 OnMenuDelete(tmp);
1096 }
1097 else if (code == WXK_F2) {
1098 wxCommandEvent tmp;
1099 OnMenuRename(tmp);
1100 }
1101 else if (code == WXK_RIGHT && event.GetModifiers() == CursorModifierKey) {
1102 wxListEvent evt;
1103 evt.m_itemIndex = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
1104 OnItemActivated(evt);
1105 }
1106 else if (code == WXK_DOWN && event.GetModifiers() == CursorModifierKey) {
1107 wxCommandEvent cmdEvent;
1108 OnMenuUpload(cmdEvent);
1109 }
1110 else if (code == 'N' && event.GetModifiers() == (wxMOD_CONTROL | wxMOD_SHIFT)) {
1111 wxCommandEvent cmdEvent;
1112 OnMenuMkdir(cmdEvent);
1113 }
1114 else if (code == 'F' && event.GetModifiers() == wxMOD_CONTROL) {
1115 if (m_parentView) {
1116 m_parentView->ShowSearchPanel();
1117 }
1118 }
1119 else {
1120 event.Skip();
1121 }
1122 }
1123
OnBeginRename(const wxListEvent & event)1124 bool CLocalListView::OnBeginRename(const wxListEvent& event)
1125 {
1126 if (!m_state.GetLocalDir().IsWriteable()) {
1127 return false;
1128 }
1129
1130 if (event.GetIndex() == 0 && m_hasParent) {
1131 return false;
1132 }
1133
1134 const CLocalFileData * const data = GetData(event.GetIndex());
1135 if (!data || data->comparison_flags == fill) {
1136 return false;
1137 }
1138
1139 return true;
1140 }
1141
OnAcceptRename(const wxListEvent & event)1142 bool CLocalListView::OnAcceptRename(const wxListEvent& event)
1143 {
1144 const int index = event.GetIndex();
1145 if (!index && m_hasParent) {
1146 return false;
1147 }
1148
1149 if (event.GetLabel().empty()) {
1150 return false;
1151 }
1152
1153 if (!m_state.GetLocalDir().IsWriteable()) {
1154 return false;
1155 }
1156
1157 CLocalFileData *const data = GetData(event.GetIndex());
1158 if (!data || data->comparison_flags == fill) {
1159 return false;
1160 }
1161
1162 std::wstring newname = event.GetLabel().ToStdWstring();
1163 #ifdef __WXMSW__
1164 newname = newname.substr(0, 255);
1165 #endif
1166
1167 if (newname == data->name) {
1168 return false;
1169 }
1170
1171 if (!RenameFile(this, m_dir.GetPath(), data->name, newname)) {
1172 return false;
1173 }
1174
1175 data->name = newname;
1176 #ifdef __WXMSW__
1177 data->label.clear();
1178 #endif
1179 CallAfter([&](){m_state.RefreshLocal();});
1180
1181 return true;
1182 }
1183
ApplyCurrentFilter()1184 void CLocalListView::ApplyCurrentFilter()
1185 {
1186 CStateFilterManager const& filter = m_state.GetStateFilterManager();
1187
1188 if (!filter.HasSameLocalAndRemoteFilters() && IsComparing()) {
1189 ExitComparisonMode();
1190 }
1191
1192 unsigned int min = m_hasParent ? 1 : 0;
1193 if (m_fileData.size() <= min) {
1194 return;
1195 }
1196
1197 int focusedItem = -1;
1198 std::wstring focused;
1199 std::vector<std::wstring> const& selectedNames = RememberSelectedItems(focused, focusedItem);
1200
1201 if (m_pFilelistStatusBar) {
1202 m_pFilelistStatusBar->UnselectAll();
1203 }
1204
1205 int64_t totalSize{};
1206 int unknown_sizes = 0;
1207 int totalFileCount = 0;
1208 int totalDirCount = 0;
1209 int hidden = 0;
1210
1211 m_indexMapping.clear();
1212 if (m_hasParent) {
1213 m_indexMapping.push_back(0);
1214 }
1215 for (unsigned int i = min; i < m_fileData.size(); ++i) {
1216 const CLocalFileData& data = m_fileData[i];
1217 if (data.comparison_flags == fill) {
1218 continue;
1219 }
1220 if (filter.FilenameFiltered(data.name, m_dir.GetPath(), data.dir, data.size, true, data.attributes, data.time)) {
1221 ++hidden;
1222 continue;
1223 }
1224
1225 if (data.dir) {
1226 ++totalDirCount;
1227 }
1228 else {
1229 if (data.size != -1) {
1230 totalSize += data.size;
1231 }
1232 else {
1233 ++unknown_sizes;
1234 }
1235 ++totalFileCount;
1236 }
1237
1238 m_indexMapping.push_back(i);
1239 }
1240 SetItemCount(m_indexMapping.size());
1241
1242 if (m_pFilelistStatusBar) {
1243 m_pFilelistStatusBar->SetDirectoryContents(totalFileCount, totalDirCount, totalSize, unknown_sizes, hidden);
1244 }
1245
1246 SortList(-1, -1, false);
1247
1248 if (IsComparing()) {
1249 m_originalIndexMapping.clear();
1250 RefreshComparison();
1251 }
1252
1253 ReselectItems(selectedNames, focused, focusedItem);
1254
1255 if (!IsComparing()) {
1256 RefreshListOnly();
1257 }
1258 }
1259
RememberSelectedItems(std::wstring & focused,int & focusedItem)1260 std::vector<std::wstring> CLocalListView::RememberSelectedItems(std::wstring& focused, int & focusedItem)
1261 {
1262 std::vector<std::wstring> selectedNames;
1263 // Remember which items were selected
1264 #ifndef __WXMSW__
1265 // GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
1266 if (GetSelectedItemCount())
1267 #endif
1268 {
1269 int item = -1;
1270 for (;;) {
1271 item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1272 if (item < 0 || static_cast<size_t>(item) >= m_indexMapping.size()) {
1273 break;
1274 }
1275 const CLocalFileData &data = m_fileData[m_indexMapping[item]];
1276 if (data.comparison_flags != fill) {
1277 if (data.dir) {
1278 selectedNames.push_back(L"d" + data.name);
1279 }
1280 else {
1281 selectedNames.push_back(L"-" + data.name);
1282 }
1283 }
1284 SetSelection(item, false);
1285 }
1286 }
1287
1288 focusedItem = -1;
1289 int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
1290 if (item >= 0 && static_cast<size_t>(item) < m_indexMapping.size()) {
1291 const CLocalFileData &data = m_fileData[m_indexMapping[item]];
1292 if (data.comparison_flags != fill) {
1293 focused = data.name;
1294 }
1295 focusedItem = item;
1296 }
1297
1298 return selectedNames;
1299 }
1300
ReselectItems(const std::vector<std::wstring> & selectedNames,std::wstring focused,int focusedItem,bool ensureVisible)1301 void CLocalListView::ReselectItems(const std::vector<std::wstring>& selectedNames, std::wstring focused, int focusedItem, bool ensureVisible)
1302 {
1303 if (!GetItemCount()) {
1304 return;
1305 }
1306
1307 // Reselect previous items if neccessary.
1308 // Sorting direction did not change. We just have to scan through items once
1309
1310 if (selectedNames.empty()) {
1311 if (focused.empty()) {
1312 return;
1313 }
1314 for (unsigned int i = 0; i < m_indexMapping.size(); ++i) {
1315 const CLocalFileData &data = m_fileData[m_indexMapping[i]];
1316 if (data.name == focused) {
1317 SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1318 if (ensureVisible) {
1319 EnsureVisible(i);
1320 }
1321 return;
1322 }
1323 }
1324
1325 if (focusedItem != -1 && GetItemCount() != 0) {
1326 if (focusedItem >= GetItemCount()) {
1327 focusedItem = GetItemCount() - 1;
1328 }
1329 SetItemState(focusedItem, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1330 }
1331 return;
1332 }
1333
1334 int firstSelected = -1;
1335
1336 int i = -1;
1337 for (auto const& selectedName : selectedNames) {
1338 while (++i < (int)m_indexMapping.size()) {
1339 const CLocalFileData &data = m_fileData[m_indexMapping[i]];
1340 if (data.name == focused) {
1341 SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1342 if (ensureVisible) {
1343 EnsureVisible(i);
1344 }
1345 focused.clear();
1346 focusedItem = -1;
1347 }
1348 if (data.dir && selectedName == (_T("d") + data.name)) {
1349 if (firstSelected == -1) {
1350 firstSelected = i;
1351 }
1352 if (m_pFilelistStatusBar) {
1353 m_pFilelistStatusBar->SelectDirectory();
1354 }
1355 SetSelection(i, true);
1356 break;
1357 }
1358 else if (selectedName == (_T("-") + data.name)) {
1359 if (firstSelected == -1) {
1360 firstSelected = i;
1361 }
1362 if (m_pFilelistStatusBar) {
1363 m_pFilelistStatusBar->SelectFile(data.size);
1364 }
1365 SetSelection(i, true);
1366 break;
1367 }
1368 }
1369 if (i == (int)m_indexMapping.size()) {
1370 break;
1371 }
1372 }
1373 if (!focused.empty()) {
1374 if (firstSelected != -1) {
1375 SetItemState(firstSelected, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1376 }
1377 else {
1378 if (GetItemCount() != 0) {
1379 if (focusedItem == -1) {
1380 focusedItem = 0;
1381 }
1382 else if (focusedItem >= GetItemCount()) {
1383 focusedItem = GetItemCount() - 1;
1384 }
1385 SetItemState(focusedItem, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
1386 }
1387 }
1388 }
1389 }
1390
OnSize(wxSizeEvent & event)1391 void CLocalListView::OnSize(wxSizeEvent& event)
1392 {
1393 event.Skip();
1394 if (m_pInfoText) {
1395 m_pInfoText->Reposition();
1396 }
1397 }
1398
OnStateChange(t_statechange_notifications notification,std::wstring const & data,const void *)1399 void CLocalListView::OnStateChange(t_statechange_notifications notification, std::wstring const& data, const void*)
1400 {
1401 if (notification == STATECHANGE_LOCAL_DIR) {
1402 DisplayDir(m_state.GetLocalDir());
1403 }
1404 else if (notification == STATECHANGE_APPLYFILTER) {
1405 ApplyCurrentFilter();
1406 }
1407 else if (notification == STATECHANGE_SERVER) {
1408 if (m_windowTinter) {
1409 m_windowTinter->SetBackgroundTint(site_colour_to_wx(m_state.GetSite().m_colour));
1410 }
1411 if (m_pInfoText) {
1412 m_pInfoText->SetBackgroundTint(site_colour_to_wx(m_state.GetSite().m_colour));
1413 }
1414 }
1415 else {
1416 wxASSERT(notification == STATECHANGE_LOCAL_REFRESH_FILE);
1417 RefreshFile(data);
1418 }
1419 }
1420
SetInfoText(wxString const & text)1421 void CLocalListView::SetInfoText(wxString const& text)
1422 {
1423 if (!m_pInfoText) {
1424 return;
1425 }
1426
1427 if (IsComparing() || text.empty()) {
1428 m_pInfoText->Hide();
1429 }
1430 else {
1431 m_pInfoText->SetText(text);
1432 m_pInfoText->Reposition();
1433 m_pInfoText->Show();
1434 }
1435 }
1436
OnBeginDrag(wxListEvent &)1437 void CLocalListView::OnBeginDrag(wxListEvent&)
1438 {
1439 if (COptions::Get()->get_int(OPTION_DND_DISABLED) != 0) {
1440 return;
1441 }
1442
1443 long item = -1;
1444 for (;;) {
1445 item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1446 if (item == -1) {
1447 break;
1448 }
1449
1450 if (!item && m_hasParent) {
1451 return;
1452 }
1453 }
1454
1455 #ifdef __WXMAC__
1456 // Don't use wxFileDataObject on Mac, crashes on Mojave, wx bug #18232
1457 CLocalDataObject obj;
1458 #else
1459 wxFileDataObject obj;
1460 #endif
1461
1462 CDragDropManager* pDragDropManager = CDragDropManager::Init();
1463 pDragDropManager->pDragSource = this;
1464 pDragDropManager->localParent = m_dir;
1465
1466 auto const path = m_dir.GetPath();
1467
1468 bool added = false;
1469
1470 item = -1;
1471 for (;;) {
1472 item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1473 if (item == -1) {
1474 break;
1475 }
1476
1477 CLocalFileData *data = GetData(item);
1478 if (!data) {
1479 continue;
1480 }
1481
1482 if (data->comparison_flags == fill) {
1483 continue;
1484 }
1485
1486 std::wstring name = path + data->name;
1487 obj.AddFile(name);
1488 added = true;
1489 }
1490
1491 if (!added) {
1492 pDragDropManager->Release();
1493 return;
1494 }
1495
1496 CLabelEditBlocker b(*this);
1497
1498 wxDropSource source(this);
1499 source.SetData(obj);
1500 int res = source.DoDragDrop(wxDrag_AllowMove);
1501
1502 bool handled_internally = pDragDropManager->pDropTarget != 0;
1503
1504 pDragDropManager->Release();
1505
1506 if (!handled_internally && (res == wxDragCopy || res == wxDragMove)) {
1507 // We only need to refresh local side if the operation got handled
1508 // externally, the internal handlers do this for us already
1509 m_state.RefreshLocal();
1510 }
1511 }
1512
RefreshFile(std::wstring const & file)1513 void CLocalListView::RefreshFile(std::wstring const& file)
1514 {
1515 CLocalFileData data;
1516
1517 bool wasLink;
1518 fz::local_filesys::type type = fz::local_filesys::get_file_info(fz::to_native(m_dir.GetPath() + file), wasLink, &data.size, &data.time, &data.attributes);
1519 if (type == fz::local_filesys::unknown) {
1520 return;
1521 }
1522
1523 data.name = file;
1524 data.dir = type == fz::local_filesys::dir;
1525
1526 CStateFilterManager const& filter = m_state.GetStateFilterManager();
1527 if (filter.FilenameFiltered(data.name, m_dir.GetPath(), data.dir, data.size, true, data.attributes, data.time)) {
1528 return;
1529 }
1530
1531 CancelLabelEdit();
1532
1533 // Look if file data already exists
1534 unsigned int i = 0;
1535 for (auto iter = m_fileData.begin(); iter != m_fileData.end(); ++iter, ++i) {
1536 const CLocalFileData& oldData = *iter;
1537 if (oldData.name != file) {
1538 continue;
1539 }
1540
1541 // Update file list status bar
1542 if (m_pFilelistStatusBar) {
1543 #ifndef __WXMSW__
1544 // GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
1545 if (GetSelectedItemCount())
1546 #endif
1547 {
1548 int item = -1;
1549 for (;;) {
1550 item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1551 if (item == -1) {
1552 break;
1553 }
1554 if (m_indexMapping[item] != i) {
1555 continue;
1556 }
1557
1558 if (oldData.dir) {
1559 m_pFilelistStatusBar->UnselectDirectory();
1560 }
1561 else {
1562 m_pFilelistStatusBar->UnselectFile(oldData.size);
1563 }
1564 if (data.dir) {
1565 m_pFilelistStatusBar->SelectDirectory();
1566 }
1567 else {
1568 m_pFilelistStatusBar->SelectFile(data.size);
1569 }
1570 break;
1571 }
1572 }
1573
1574 if (oldData.dir) {
1575 m_pFilelistStatusBar->RemoveDirectory();
1576 }
1577 else {
1578 m_pFilelistStatusBar->RemoveFile(oldData.size);
1579 }
1580 if (data.dir) {
1581 m_pFilelistStatusBar->AddDirectory();
1582 }
1583 else {
1584 m_pFilelistStatusBar->AddFile(data.size);
1585 }
1586 }
1587
1588 // Update the data
1589 data.fileType = oldData.fileType;
1590
1591 *iter = data;
1592 if (IsComparing()) {
1593 // Sort order doesn't change
1594 RefreshComparison();
1595 }
1596 else {
1597 if (m_sortColumn) {
1598 SortList();
1599 }
1600 RefreshListOnly(false);
1601 }
1602 return;
1603 }
1604
1605 if (data.dir) {
1606 m_pFilelistStatusBar->AddDirectory();
1607 }
1608 else {
1609 m_pFilelistStatusBar->AddFile(data.size);
1610 }
1611
1612 std::wstring focused;
1613 int focusedItem = -1;
1614 std::vector<std::wstring> selectedNames;
1615 if (IsComparing()) {
1616 selectedNames = RememberSelectedItems(focused, focusedItem);
1617 if (!m_originalIndexMapping.empty()) {
1618 m_indexMapping.clear();
1619 m_originalIndexMapping.swap(m_indexMapping);
1620 }
1621 }
1622
1623 // Insert new entry
1624 int index = m_fileData.size();
1625 m_fileData.push_back(data);
1626
1627 // Find correct position in index mapping
1628 std::vector<unsigned int>::iterator start = m_indexMapping.begin();
1629 if (m_hasParent) {
1630 ++start;
1631 }
1632 std::unique_ptr<CFileListCtrlSortBase> compare = GetSortComparisonObject();
1633 std::vector<unsigned int>::iterator insertPos = std::lower_bound(start, m_indexMapping.end(), index, SortPredicate(compare));
1634
1635 const int item = insertPos - m_indexMapping.begin();
1636 m_indexMapping.insert(insertPos, index);
1637
1638 if (!IsComparing()) {
1639 SetItemCount(m_indexMapping.size());
1640
1641 // Move selections
1642 int prevState = 0;
1643 for (unsigned int j = item; j < m_indexMapping.size(); ++j) {
1644 int state = GetItemState(j, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
1645 if (state != prevState) {
1646 SetItemState(j, prevState, wxLIST_STATE_FOCUSED);
1647 SetSelection(j, (prevState & wxLIST_STATE_SELECTED) != 0);
1648 prevState = state;
1649 }
1650 }
1651 RefreshListOnly();
1652 }
1653 else {
1654 RefreshComparison();
1655 if (m_pFilelistStatusBar) {
1656 m_pFilelistStatusBar->UnselectAll();
1657 }
1658 ReselectItems(selectedNames, focused, focusedItem);
1659 }
1660 }
1661
OnGetItemAttr(long item) const1662 wxListItemAttr* CLocalListView::OnGetItemAttr(long item) const
1663 {
1664 CLocalListView *pThis = const_cast<CLocalListView *>(this);
1665 const CLocalFileData* const data = pThis->GetData((unsigned int)item);
1666
1667 if (!data) {
1668 return 0;
1669 }
1670
1671 #ifndef __WXMSW__
1672 if (item == m_dropTarget) {
1673 return &pThis->m_dropHighlightAttribute;
1674 }
1675 #endif
1676
1677 switch (data->comparison_flags)
1678 {
1679 case different:
1680 return &pThis->m_comparisonBackgrounds[0];
1681 case lonely:
1682 return &pThis->m_comparisonBackgrounds[1];
1683 case newer:
1684 return &pThis->m_comparisonBackgrounds[2];
1685 default:
1686 return 0;
1687 }
1688 }
1689
StartComparison()1690 void CLocalListView::StartComparison()
1691 {
1692 if (m_sortDirection || m_sortColumn != 0) {
1693 wxASSERT(m_originalIndexMapping.empty());
1694 SortList(0, 0);
1695 }
1696
1697 ComparisonRememberSelections();
1698
1699 if (m_originalIndexMapping.empty()) {
1700 m_originalIndexMapping.swap(m_indexMapping);
1701 }
1702 else {
1703 m_indexMapping.clear();
1704 }
1705
1706 m_comparisonIndex = -1;
1707
1708 if (m_fileData.empty() || m_fileData.back().comparison_flags != fill) {
1709 CLocalFileData data;
1710 data.dir = false;
1711 data.icon = -1;
1712 data.size = -1;
1713 data.comparison_flags = fill;
1714 m_fileData.push_back(data);
1715 }
1716 }
1717
get_next_file(std::wstring_view & name,std::wstring &,bool & dir,int64_t & size,fz::datetime & date)1718 bool CLocalListView::get_next_file(std::wstring_view & name, std::wstring &, bool& dir, int64_t& size, fz::datetime& date)
1719 {
1720 if (++m_comparisonIndex >= (int)m_originalIndexMapping.size()) {
1721 return false;
1722 }
1723
1724 const unsigned int index = m_originalIndexMapping[m_comparisonIndex];
1725 if (index >= m_fileData.size()) {
1726 return false;
1727 }
1728
1729 CLocalFileData const& data = m_fileData[index];
1730
1731 name = data.name;
1732 dir = data.dir;
1733 size = data.size;
1734 date = data.time;
1735
1736 return true;
1737 }
1738
FinishComparison()1739 void CLocalListView::FinishComparison()
1740 {
1741 SetInfoText(wxString());
1742
1743 SetItemCount(m_indexMapping.size());
1744
1745 ComparisonRestoreSelections();
1746
1747 RefreshListOnly();
1748
1749 CComparableListing* pOther = GetOther();
1750 if (pOther) {
1751 pOther->ScrollTopItem(GetTopItem());
1752 }
1753 }
1754
CanStartComparison()1755 bool CLocalListView::CanStartComparison()
1756 {
1757 return true;
1758 }
1759
GetItemText(int item,unsigned int column)1760 wxString CLocalListView::GetItemText(int item, unsigned int column)
1761 {
1762 CLocalFileData *data = GetData(item);
1763 if (!data) {
1764 return wxString();
1765 }
1766
1767 if (!column) {
1768 #ifdef __WXMSW__
1769 return data->label ? *data->label : data->name;
1770 #else
1771 return data->name;
1772 #endif
1773 }
1774 else if (column == 1) {
1775 if (data->size < 0) {
1776 return wxString();
1777 }
1778 else {
1779 return CSizeFormat::Format(data->size);
1780 }
1781 }
1782 else if (column == 2) {
1783 if (!item && m_hasParent) {
1784 return wxString();
1785 }
1786
1787 if (data->comparison_flags == fill) {
1788 return wxString();
1789 }
1790
1791 if (data->fileType.empty()) {
1792 data->fileType = GetType(data->name, data->dir, m_dir.GetPath());
1793 }
1794
1795 return data->fileType;
1796 }
1797 else if (column == 3) {
1798 return CTimeFormat::Format(data->time);
1799 }
1800 return wxString();
1801 }
1802
OnMenuEdit(wxCommandEvent &)1803 void CLocalListView::OnMenuEdit(wxCommandEvent&)
1804 {
1805 Site site;
1806 CServerPath path;
1807
1808 if (!m_state.GetSite()) {
1809 if (COptions::Get()->get_int(OPTION_EDIT_TRACK_LOCAL)) {
1810 wxMessageBoxEx(_("Cannot edit file, not connected to any server."), _("Editing failed"), wxICON_EXCLAMATION);
1811 return;
1812 }
1813 }
1814 else {
1815 site = m_state.GetSite();
1816
1817 path = m_state.GetRemotePath();
1818 if (path.empty()) {
1819 wxMessageBoxEx(_("Cannot edit file, remote path unknown."), _("Editing failed"), wxICON_EXCLAMATION);
1820 return;
1821 }
1822 }
1823
1824 std::vector<CEditHandler::FileData> selected_item;
1825
1826 long item = -1;
1827 while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
1828 if (!item && m_hasParent) {
1829 wxBell();
1830 return;
1831 }
1832
1833 const CLocalFileData *data = GetData(item);
1834 if (!data) {
1835 continue;
1836 }
1837
1838 if (data->dir) {
1839 wxBell();
1840 return;
1841 }
1842
1843 if (data->comparison_flags == fill) {
1844 continue;
1845 }
1846
1847 selected_item.push_back({m_dir.GetPath() + data->name, data->size});
1848 }
1849
1850 CEditHandler* pEditHandler = CEditHandler::Get();
1851 pEditHandler->Edit(CEditHandler::local, selected_item, path, site, this);
1852 }
1853
OnMenuOpen(wxCommandEvent &)1854 void CLocalListView::OnMenuOpen(wxCommandEvent&)
1855 {
1856 long item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1857 if (item == -1) {
1858 OpenInFileManager(m_dir.GetPath());
1859 return;
1860 }
1861
1862 std::list<CLocalFileData> selected_item_list;
1863
1864 item = -1;
1865 while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
1866 if (!item && m_hasParent) {
1867 wxBell();
1868 return;
1869 }
1870
1871 const CLocalFileData *data = GetData(item);
1872 if (!data) {
1873 continue;
1874 }
1875
1876 if (data->comparison_flags == fill) {
1877 continue;
1878 }
1879
1880 selected_item_list.push_back(*data);
1881 }
1882
1883 CEditHandler* pEditHandler = CEditHandler::Get();
1884 if (!pEditHandler) {
1885 wxBell();
1886 return;
1887 }
1888
1889 if (selected_item_list.empty()) {
1890 wxBell();
1891 return;
1892 }
1893
1894 if (selected_item_list.size() > 10) {
1895 CConditionalDialog dlg(this, CConditionalDialog::many_selected_for_edit, CConditionalDialog::yesno);
1896 dlg.SetTitle(_("Confirmation needed"));
1897 dlg.AddText(_("You have selected more than 10 files or directories to open, do you really want to continue?"));
1898
1899 if (!dlg.Run()) {
1900 return;
1901 }
1902 }
1903
1904 for (auto const& data : selected_item_list) {
1905 if (data.dir) {
1906 CLocalPath path(m_dir);
1907 if (!path.ChangePath(data.name)) {
1908 wxBell();
1909 continue;
1910 }
1911
1912 OpenInFileManager(path.GetPath());
1913 continue;
1914 }
1915
1916
1917 wxFileName fn(m_dir.GetPath(), data.name);
1918 if (wxLaunchDefaultApplication(fn.GetFullPath(), 0)) {
1919 continue;
1920 }
1921 auto cmd_with_args = GetSystemAssociation(fn.GetFullPath().ToStdWstring());
1922 if (cmd_with_args.empty()) {
1923 cmd_with_args = pEditHandler->GetAssociation(fn.GetFullPath().ToStdWstring());
1924 }
1925 if (cmd_with_args.empty()) {
1926 wxMessageBoxEx(wxString::Format(_("The file '%s' could not be opened:\nNo program has been associated on your system with this file type."), fn.GetFullPath()), _("Opening failed"), wxICON_EXCLAMATION);
1927 continue;
1928 }
1929 if (!ProgramExists(cmd_with_args.front())) {
1930 wxString msg = wxString::Format(_("The file '%s' cannot be opened:\nThe associated program (%s) could not be found.\nPlease check your filetype associations."), fn.GetFullPath(), cmd_with_args.front());
1931 wxMessageBoxEx(msg, _("Cannot edit file"), wxICON_EXCLAMATION);
1932 continue;
1933 }
1934
1935 if (fz::spawn_detached_process(AssociationToCommand(cmd_with_args, fn.GetFullPath().ToStdWstring()))) {
1936 continue;
1937 }
1938 wxMessageBoxEx(wxString::Format(_("The file '%s' could not be opened:\nThe associated command failed"), fn.GetFullPath()), _("Opening failed"), wxICON_EXCLAMATION);
1939
1940 }
1941 }
1942
ItemIsDir(int index) const1943 bool CLocalListView::ItemIsDir(int index) const
1944 {
1945 return m_fileData[index].dir;
1946 }
1947
ItemGetSize(int index) const1948 int64_t CLocalListView::ItemGetSize(int index) const
1949 {
1950 return m_fileData[index].size;
1951 }
1952
1953 #ifdef __WXMSW__
1954
OnVolumesEnumerated(wxCommandEvent & event)1955 void CLocalListView::OnVolumesEnumerated(wxCommandEvent& event)
1956 {
1957 if (!volumeEnumeratorThread_) {
1958 return;
1959 }
1960
1961 std::vector<CVolumeDescriptionEnumeratorThread::t_VolumeInfo> volumeInfo = volumeEnumeratorThread_->GetVolumes();
1962
1963 if (event.GetEventType() == fzEVT_VOLUMESENUMERATED) {
1964 volumeEnumeratorThread_.reset();
1965 }
1966
1967 if (m_dir.GetPath() != _T("\\")) {
1968 return;
1969 }
1970
1971 for (auto const& info : volumeInfo) {
1972 std::wstring const& drive = info.volume;
1973
1974 unsigned int item, index;
1975 for (item = m_hasParent ? 1 : 0; item < m_indexMapping.size(); ++item) {
1976 index = m_indexMapping[item];
1977 if (m_fileData[index].name == drive || m_fileData[index].name.substr(0, drive.size() + 1) == drive + _T(" ")) {
1978 break;
1979 }
1980 }
1981 if (item >= m_indexMapping.size()) {
1982 continue;
1983 }
1984
1985 if (!info.volumeName.empty()) {
1986 m_fileData[index].label = fz::sparse_optional<std::wstring>(drive + _T(" (") + info.volumeName + _T(")"));
1987 }
1988 if (info.icon != -1) {
1989 m_fileData[index].icon = info.icon;
1990 }
1991
1992 RefreshItem(item);
1993 }
1994 }
1995
1996 #endif
1997
OnMenuRefresh(wxCommandEvent &)1998 void CLocalListView::OnMenuRefresh(wxCommandEvent&)
1999 {
2000 m_state.RefreshLocal();
2001 }
2002
OnNavigationEvent(bool forward)2003 void CLocalListView::OnNavigationEvent(bool forward)
2004 {
2005 if (!forward) {
2006 if (!m_hasParent) {
2007 wxBell();
2008 return;
2009 }
2010
2011 std::wstring error;
2012 if (!m_state.SetLocalDir(_T(".."), &error)) {
2013 if (!error.empty()) {
2014 wxMessageBoxEx(error, _("Failed to change directory"), wxICON_INFORMATION);
2015 }
2016 else {
2017 wxBell();
2018 }
2019 }
2020 }
2021 }
2022