#include "FileZilla.h" #include "LocalListView.h" #include "queue.h" #include "filezillaapp.h" #include "filter.h" #include "inputdialog.h" #include #include #include "dndobjects.h" #include "Options.h" #ifdef __WXMSW__ #include "lm.h" #include #else #include #endif #ifdef _DEBUG #define new DEBUG_NEW #endif class CLocalListViewDropTarget : public wxDropTarget { public: CLocalListViewDropTarget(CLocalListView* pLocalListView) : m_pLocalListView(pLocalListView), m_pFileDataObject(new wxFileDataObject()), m_pRemoteDataObject(new CRemoteDataObject()) { m_pDataObject = new wxDataObjectComposite; m_pDataObject->Add(m_pRemoteDataObject, true); m_pDataObject->Add(m_pFileDataObject, false); SetDataObject(m_pDataObject); } void ClearDropHighlight() { const int dropTarget = m_pLocalListView->m_dropTarget; if (dropTarget != -1) { m_pLocalListView->SetItemState(dropTarget, 0, wxLIST_STATE_DROPHILITED); m_pLocalListView->m_dropTarget = -1; } } virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def) { if (def == wxDragError || def == wxDragNone || def == wxDragCancel) return def; if (m_pLocalListView->m_fileData.empty()) return wxDragError; if (def != wxDragCopy && def != wxDragMove) return wxDragError; wxString subdir; int flags; int hit = m_pLocalListView->HitTest(wxPoint(x, y), flags, 0); if (hit != -1 && (flags & wxLIST_HITTEST_ONITEM)) { const CLocalListView::t_fileData* const data = m_pLocalListView->GetData(hit); if (data && data->dir) subdir = data->name; } wxString dir; if (subdir != _T("")) { dir = CState::Canonicalize(m_pLocalListView->m_dir, subdir); if (dir == _T("")) return wxDragError; } else dir = m_pLocalListView->m_dir; if (!CState::LocalDirIsWriteable(dir)) return wxDragError; if (!GetData()) return wxDragError; if (m_pDataObject->GetReceivedFormat() == m_pFileDataObject->GetFormat()) m_pLocalListView->m_pState->HandleDroppedFiles(m_pFileDataObject, dir, def == wxDragCopy); else { if (m_pRemoteDataObject->GetProcessId() != (int)wxGetProcessId()) { wxMessageBox(_("Drag&drop between different instances of FileZilla has not been implemented yet.")); return wxDragNone; } if (!m_pLocalListView->m_pState->GetServer() || m_pRemoteDataObject->GetServer() != *m_pLocalListView->m_pState->GetServer()) { wxMessageBox(_("Drag&drop between different servers has not been implemented yet.")); return wxDragNone; } if (!m_pLocalListView->m_pState->DownloadDroppedFiles(m_pRemoteDataObject, dir)) return wxDragNone; } return def; } virtual bool OnDrop(wxCoord x, wxCoord y) { ClearDropHighlight(); if (m_pLocalListView->m_fileData.empty()) return false; return true; } wxString DisplayDropHighlight(wxPoint point) { wxString subDir; int flags; int hit = m_pLocalListView->HitTest(point, flags, 0); if (!(flags & wxLIST_HITTEST_ONITEM)) hit = -1; if (hit != -1) { const CLocalListView::t_fileData* const data = m_pLocalListView->GetData(hit); if (!data || !data->dir) hit = -1; else subDir = data->name; } if (hit != m_pLocalListView->m_dropTarget) { ClearDropHighlight(); if (hit != -1) { m_pLocalListView->SetItemState(hit, wxLIST_STATE_DROPHILITED, wxLIST_STATE_DROPHILITED); m_pLocalListView->m_dropTarget = hit; } } return subDir; } virtual wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult def) { if (def == wxDragError || def == wxDragNone || def == wxDragCancel) { ClearDropHighlight(); return def; } if (m_pLocalListView->m_fileData.empty()) { ClearDropHighlight(); return wxDragNone; } const wxString& subdir = DisplayDropHighlight(wxPoint(x, y)); if (subdir == _T("")) { if (!CState::LocalDirIsWriteable(m_pLocalListView->m_dir)) return wxDragNone; } else { wxString dir = CState::Canonicalize(m_pLocalListView->m_dir, subdir); if (dir == _T("") || !CState::LocalDirIsWriteable(dir)) return wxDragNone; } if (def == wxDragLink) def = wxDragCopy; return def; } virtual void OnLeave() { ClearDropHighlight(); } virtual wxDragResult OnEnter(wxCoord x, wxCoord y, wxDragResult def) { return OnDragOver(x, y, def); } protected: CLocalListView *m_pLocalListView; wxFileDataObject* m_pFileDataObject; CRemoteDataObject* m_pRemoteDataObject; wxDataObjectComposite* m_pDataObject; }; BEGIN_EVENT_TABLE(CLocalListView, wxListCtrl) EVT_LIST_ITEM_ACTIVATED(wxID_ANY, CLocalListView::OnItemActivated) EVT_LIST_COL_CLICK(wxID_ANY, CLocalListView::OnColumnClicked) EVT_CONTEXT_MENU(CLocalListView::OnContextMenu) // Map both ID_UPLOAD and ID_ADDTOQUEUE to OnMenuUpload, code is identical EVT_MENU(XRCID("ID_UPLOAD"), CLocalListView::OnMenuUpload) EVT_MENU(XRCID("ID_ADDTOQUEUE"), CLocalListView::OnMenuUpload) EVT_MENU(XRCID("ID_MKDIR"), CLocalListView::OnMenuMkdir) EVT_MENU(XRCID("ID_DELETE"), CLocalListView::OnMenuDelete) EVT_MENU(XRCID("ID_RENAME"), CLocalListView::OnMenuRename) EVT_KEY_DOWN(CLocalListView::OnKeyDown) EVT_LIST_BEGIN_LABEL_EDIT(wxID_ANY, CLocalListView::OnBeginLabelEdit) EVT_LIST_END_LABEL_EDIT(wxID_ANY, CLocalListView::OnEndLabelEdit) EVT_LIST_BEGIN_DRAG(wxID_ANY, CLocalListView::OnBeginDrag) END_EVENT_TABLE() CLocalListView::CLocalListView(wxWindow* parent, wxWindowID id, CState *pState, CQueueView *pQueue) : wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxLC_VIRTUAL | wxLC_REPORT | wxNO_BORDER | wxLC_EDIT_LABELS), CSystemImageList(16), CStateEventHandler(pState, STATECHANGE_LOCAL_DIR | STATECHANGE_APPLYFILTER | STATECHANGE_LOCAL_REFRESH_FILE) { m_dropTarget = -1; m_hasParent = true; m_pQueue = pQueue; unsigned long widths[4] = { 120, 80, 100, 120 }; if (!wxGetKeyState(WXK_SHIFT) || !wxGetKeyState(WXK_ALT) || !wxGetKeyState(WXK_CONTROL)) COptions::Get()->ReadColumnWidths(OPTION_LOCALFILELIST_COLUMN_WIDTHS, 4, widths); InsertColumn(0, _("Filename"), wxLIST_FORMAT_LEFT, widths[0]); InsertColumn(1, _("Filesize"), wxLIST_FORMAT_RIGHT, widths[1]); InsertColumn(2, _("Filetype"), wxLIST_FORMAT_LEFT, widths[2]); InsertColumn(3, _("Last modified"), wxLIST_FORMAT_LEFT, widths[3]); wxString sortInfo = COptions::Get()->GetOption(OPTION_LOCALFILELIST_SORTORDER); m_sortDirection = sortInfo[0] - '0'; if (m_sortDirection < 0 || m_sortDirection > 1) m_sortDirection = 0; if (sortInfo.Len() == 3) { m_sortColumn = sortInfo[2] - '0'; if (m_sortColumn < 0 || m_sortColumn > 3) m_sortColumn = 0; } else m_sortColumn = 0; SetImageList(GetSystemImageList(), wxIMAGE_LIST_SMALL); #ifdef __WXMSW__ // Initialize imagelist for list header m_pHeaderImageList = new wxImageListEx(8, 8, true, 3); wxBitmap bmp; bmp.LoadFile(wxGetApp().GetResourceDir() + _T("empty.png"), wxBITMAP_TYPE_PNG); m_pHeaderImageList->Add(bmp); bmp.LoadFile(wxGetApp().GetResourceDir() + _T("up.png"), wxBITMAP_TYPE_PNG); m_pHeaderImageList->Add(bmp); bmp.LoadFile(wxGetApp().GetResourceDir() + _T("down.png"), wxBITMAP_TYPE_PNG); m_pHeaderImageList->Add(bmp); HWND hWnd = (HWND)GetHandle(); if (!hWnd) { delete m_pHeaderImageList; m_pHeaderImageList = 0; return; } HWND header = (HWND)SendMessage(hWnd, LVM_GETHEADER, 0, 0); if (!header) { delete m_pHeaderImageList; m_pHeaderImageList = 0; return; } TCHAR buffer[1000] = {0}; HDITEM item; item.mask = HDI_TEXT; item.pszText = buffer; item.cchTextMax = 999; SendMessage(header, HDM_GETITEM, 0, (LPARAM)&item); SendMessage(header, HDM_SETIMAGELIST, 0, (LPARAM)m_pHeaderImageList->GetHandle()); #endif SetDropTarget(new CLocalListViewDropTarget(this)); #if (!defined(__WIN32__) && !defined(__WXMAC__)) || defined(__WXUNIVERSAL__) // The generic list control a scrolled child window. In order to receive // scroll events, we have to connect the event handler to it. ((wxWindow*)m_mainWin)->Connect(-1, wxEVT_KEY_DOWN, wxKeyEventHandler(CLocalListView::OnKeyDown), 0, this); #endif InitDateFormat(); } CLocalListView::~CLocalListView() { wxString str = wxString::Format(_T("%d %d"), m_sortDirection, m_sortColumn); COptions::Get()->SetOption(OPTION_LOCALFILELIST_SORTORDER, str); #ifdef __WXMSW__ delete m_pHeaderImageList; #endif } bool CLocalListView::DisplayDir(wxString dirname) { wxString focused; std::list selectedNames; if (m_dir != dirname) { // Clear selection int item = -1; while (true) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); if (item == -1) break; SetItemState(item, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); } focused = _T(".."); if (GetItemCount()) EnsureVisible(0); m_dir = dirname; } else { // Remember which items were selected selectedNames = RememberSelectedItems(focused); } const int oldItemCount = m_indexMapping.size(); m_fileData.clear(); m_indexMapping.clear(); m_hasParent = CState::LocalDirHasParent(dirname); if (m_hasParent) { t_fileData data; data.dir = true; data.icon = -2; data.name = _T(".."); data.size = -1; data.hasTime = 0; m_fileData.push_back(data); m_indexMapping.push_back(0); } #ifdef __WXMSW__ if (dirname == _T("\\")) { DisplayDrives(); } else if (dirname.Left(2) == _T("\\\\")) { int pos = dirname.Mid(2).Find('\\'); if (pos != -1 && pos + 3 != (int)dirname.Len()) goto regular_dir; // UNC path without shares DisplayShares(dirname); } else #endif { #ifdef __WXMSW__ regular_dir: #endif CFilterDialog filter; wxDir dir(dirname); if (!dir.IsOpened()) { SetItemCount(1); return false; } wxString file; bool found = dir.GetFirst(&file); int num = m_fileData.size(); while (found) { if (file == _T("")) { wxGetApp().DisplayEncodingWarning(); found = dir.GetNext(&file); continue; } t_fileData data; data.dir = wxFileName::DirExists(dirname + file); data.icon = -2; data.name = file; wxStructStat buf; int result; result = wxStat(dirname + file, &buf); if (!result) { data.hasTime = true; data.lastModified = wxDateTime(buf.st_mtime); } else data.hasTime = false; if (data.dir) data.size = -1; else data.size = result ? -1 : buf.st_size; m_fileData.push_back(data); if (!filter.FilenameFiltered(data.name, data.dir, data.size, true)) m_indexMapping.push_back(num); num++; found = dir.GetNext(&file); } } if (m_dropTarget != -1) { t_fileData* data = GetData(m_dropTarget); if (!data || !data->dir) { SetItemState(m_dropTarget, 0, wxLIST_STATE_DROPHILITED); m_dropTarget = -1; } } const int count = m_indexMapping.size(); if (oldItemCount != count) SetItemCount(count); SortList(); ReselectItems(selectedNames, focused); Refresh(); return true; } wxString FormatSize(const wxLongLong& size) { COptions* const pOptions = COptions::Get(); const int format = pOptions->GetOptionVal(OPTION_SIZE_FORMAT); if (!format) { if (!pOptions->GetOptionVal(OPTION_SIZE_USETHOUSANDSEP)) return size.ToString(); #ifdef __WXMSW__ wxChar sep[5]; int count = ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, sep, 5); if (!count) return size.ToString(); #else char* chr = nl_langinfo(THOUSEP); if (!chr || !*chr) return size.ToString(); wxChar sep = chr[0]; #endif wxString tmp = size.ToString(); const int len = tmp.Len(); if (len <= 3) return tmp; wxString result; int i = (len - 1) % 3 + 1; result = tmp.Left(i); while (i < len) { result += sep + tmp.Mid(i, 3); i += 3; } return result; } int divider; if (format == 3) divider = 1000; else divider = 1024; bool r2 = false; int p = 0; wxLongLong r = size; while (r > divider || p == 6) { const wxLongLong rr = r / divider; if (rr * divider != r) r2 = true; r = rr; p++; } if (r2) r++; wxString result = r.ToString(); result += ' '; if (!p) return result + _T("B"); // We stop at Exa. If someone has files bigger than that, he can afford to // make a donation to have this changed ;) wxChar prefix[] = { ' ', 'K', 'M', 'G', 'T', 'P', 'E' }; result += prefix[p]; if (format == 1) result += 'i'; result += 'B'; return result; } // Declared const due to design error in wxWidgets. // Won't be fixed since a fix would break backwards compatibility // Both functions use a const_cast(this) and modify // the instance. wxString CLocalListView::OnGetItemText(long item, long column) const { CLocalListView *pThis = const_cast(this); t_fileData *data = pThis->GetData(item); if (!data) return _T(""); if (!column) return data->name; else if (column == 1) { if (data->size < 0) return _T(""); else return FormatSize(data->size); } else if (column == 2) { if (!item && m_hasParent) return _T(""); if (data->fileType == _T("")) data->fileType = pThis->GetType(data->name, data->dir); return data->fileType; } else if (column == 3) { if (!data->hasTime) return _T(""); return data->lastModified.Format(m_dateFormat); } return _T(""); } // See comment to OnGetItemText int CLocalListView::OnGetItemImage(long item) const { CLocalListView *pThis = const_cast(this); t_fileData *data = pThis->GetData(item); if (!data) return -1; int &icon = data->icon; if (icon == -2) { wxString path = _T(""); if (data->name != _T("..")) { #ifdef __WXMSW__ if (m_dir == _T("\\")) path = data->name + _T("\\"); else #endif path = m_dir + data->name; } icon = pThis->GetIconIndex(data->dir ? dir : file, path); } return icon; } void CLocalListView::OnItemActivated(wxListEvent &event) { int count = 0; bool back = false; int item = -1; while (true) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) break; count++; if (!item && m_hasParent) back = true; } if (count > 1) { if (back) { wxBell(); return; } wxCommandEvent cmdEvent; OnMenuUpload(cmdEvent); return; } item = event.GetIndex(); t_fileData *data = GetData(item); if (!data) return; if (data->dir) { wxString error; if (!m_pState->SetLocalDir(data->name, &error)) { if (error != _T("")) wxMessageBox(error, _("Failed to change directory"), wxICON_INFORMATION); else wxBell(); } return; } const CServer* pServer = m_pState->GetServer(); if (!pServer) { wxBell(); return; } CServerPath path = m_pState->GetRemotePath(); if (path.IsEmpty()) { wxBell(); return; } wxFileName fn(m_dir, data->name); m_pQueue->QueueFile(false, false, fn.GetFullPath(), data->name, path, *pServer, data->size); } #ifdef __WXMSW__ void CLocalListView::DisplayDrives() { long drivesToHide = 0; // Adhere to the NODRIVES group policy wxRegKey key(_T("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer")); if (key.Exists()) { if (!key.HasValue(_T("NoDrives")) || !key.QueryValue(_T("NoDrives"), &drivesToHide)) drivesToHide = 0; } int len = GetLogicalDriveStrings(0, 0); if (!len) return; wxChar* drives = new wxChar[len + 1]; if (!GetLogicalDriveStrings(len, drives)) { delete [] drives; return; } const wxChar* pDrive = drives; int count = m_fileData.size(); while(*pDrive) { // Check if drive should be hidden by default if (pDrive[0] != 0 && pDrive[1] == ':') { int bit = -1; char letter = pDrive[0]; if (letter >= 'A' && letter <= 'Z') bit = 1 << (letter - 'A'); if (letter >= 'a' && letter <= 'z') bit = 1 << (letter - 'a'); if (bit != -1 && drivesToHide & bit) { pDrive += wxStrlen(pDrive) + 1; continue; } } wxString path = pDrive; if (path.Right(1) == _T("\\")) path.Truncate(path.Length() - 1); t_fileData data; data.name = path; data.dir = true; data.icon = -2; data.size = -1; data.hasTime = false; m_fileData.push_back(data); m_indexMapping.push_back(count); pDrive += wxStrlen(pDrive) + 1; count++; } delete [] drives; } void CLocalListView::DisplayShares(wxString computer) { SHARE_INFO_1* pShareInfo = 0; DWORD read, total; DWORD resume_handle = 0; if (computer.Last() == '\\') computer.RemoveLast(); int j = m_fileData.size(); int res = 0; do { const wxWX2WCbuf buf = computer.wc_str(wxConvLocal); res = NetShareEnum((wchar_t*)(const wchar_t*)buf, 1, (LPBYTE*)&pShareInfo, MAX_PREFERRED_LENGTH, &read, &total, &resume_handle); if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA) break; SHARE_INFO_1* p = pShareInfo; for (unsigned int i = 0; i < read; i++, p++) { if (p->shi1_type != STYPE_DISKTREE) continue; t_fileData data; data.name = p->shi1_netname; data.dir = true; data.icon = -2; data.size = -1; data.hasTime = false; m_fileData.push_back(data); m_indexMapping.push_back(j++); } NetApiBufferFree(pShareInfo); } while (res == ERROR_MORE_DATA); } #endif wxString CLocalListView::GetType(wxString name, bool dir) { #ifdef __WXMSW__ wxString ext = wxFileName(name).GetExt(); ext.MakeLower(); std::map::iterator typeIter = m_fileTypeMap.find(ext); if (typeIter != m_fileTypeMap.end()) return typeIter->second; wxString type; wxString path; if (m_dir == _T("\\")) path = name + _T("\\"); else path = m_dir + name; SHFILEINFO shFinfo; memset(&shFinfo, 0, sizeof(SHFILEINFO)); if (SHGetFileInfo(path, dir ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL, &shFinfo, sizeof(shFinfo), SHGFI_TYPENAME)) { type = shFinfo.szTypeName; if (type == _T("")) { type = ext; type.MakeUpper(); if (!type.IsEmpty()) { type += _T("-"); type += _("file"); } else type = _("File"); } else { if (!dir && ext != _T("")) m_fileTypeMap[ext.MakeLower()] = type; } } else { type = ext; type.MakeUpper(); if (!type.IsEmpty()) { type += _T("-"); type += _("file"); } else type = _("File"); } return type; #else if (dir) return _("Folder"); wxFileName fn(name); wxString ext = fn.GetExt(); if (ext == _T("")) return _("File"); std::map::iterator typeIter = m_fileTypeMap.find(ext); if (typeIter != m_fileTypeMap.end()) return typeIter->second; wxString desc; wxFileType *pType = wxTheMimeTypesManager->GetFileTypeFromExtension(ext); if (!pType) { m_fileTypeMap[ext] = desc; return desc; } if (pType->GetDescription(&desc) && desc != _T("")) { delete pType; m_fileTypeMap[ext] = desc; return desc; } delete pType; desc = _("File"); m_fileTypeMap[ext.MakeLower()] = desc; return desc; #endif } CLocalListView::t_fileData* CLocalListView::GetData(unsigned int item) { if (!IsItemValid(item)) return 0; return &m_fileData[m_indexMapping[item]]; } bool CLocalListView::IsItemValid(unsigned int item) const { if (item >= m_indexMapping.size()) return false; unsigned int index = m_indexMapping[item]; if (index >= m_fileData.size()) return false; return true; } // Helper classes for fast sorting using std::sort // ----------------------------------------------- class CLocalListViewSort //: public std::binary_function { public: enum DirSortMode { dirsort_ontop, dirsort_onbottom, dirsort_inline }; CLocalListViewSort(std::vector& fileData, enum DirSortMode dirSortMode) : m_fileData(fileData), m_dirSortMode(dirSortMode) { } virtual ~CLocalListViewSort() { } virtual bool operator()(int a, int b) const = 0; #define CMP(f, data1, data2) \ {\ int res = f(data1, data2);\ if (res == -1)\ return true;\ else if (res == 1)\ return false;\ } #define CMP_LESS(f, data1, data2) \ {\ int res = f(data1, data2);\ if (res == -1)\ return true;\ else\ return false;\ } inline int CmpDir(const CLocalListView::t_fileData &data1, const CLocalListView::t_fileData &data2) const { switch (m_dirSortMode) { default: case dirsort_ontop: if (data1.dir) { if (!data2.dir) return -1; else return 0; } else { if (data2.dir) return 1; else return 0; } case dirsort_onbottom: if (data1.dir) { if (!data2.dir) return 1; else return 0; } else { if (data2.dir) return -1; else return 0; } case dirsort_inline: return 0; } } inline int CmpName(const CLocalListView::t_fileData &data1, const CLocalListView::t_fileData &data2) const { #ifdef __WXMSW__ return data1.name.CmpNoCase(data2.name); #else return data1.name.Cmp(data2.name); #endif } inline int CmpSize(const CLocalListView::t_fileData &data1, const CLocalListView::t_fileData &data2) const { const wxLongLong diff = data1.size - data2.size; if (diff < 0) return -1; else if (diff > 0) return 1; else return 0; } inline int CmpType(const CLocalListView::t_fileData &data1, const CLocalListView::t_fileData &data2) const { return data1.fileType.CmpNoCase(data2.fileType); } inline int CmpTime(const CLocalListView::t_fileData &data1, const CLocalListView::t_fileData &data2) const { if (!data1.hasTime) { if (data2.hasTime) return -1; else return 0; } else { if (!data2.hasTime) return 1; if (data1.lastModified < data2.lastModified) return -1; else if (data1.lastModified > data2.lastModified) return 1; else return 0; } } protected: std::vector& m_fileData; DirSortMode m_dirSortMode; }; class CLocalListViewSortObject : public std::binary_function { public: CLocalListViewSortObject(CLocalListViewSort* pObject) : m_pObject(pObject) { } void Destroy() { delete m_pObject; } inline bool operator()(int a, int b) { return m_pObject->operator ()(a, b); } protected: CLocalListViewSort* m_pObject; }; template class CReverseSort : public T { public: CReverseSort(std::vector& fileData, enum CLocalListViewSort::DirSortMode dirSortMode, CLocalListView* pListView) : T(fileData, dirSortMode, pListView) { } inline bool operator()(int a, int b) const { return T::operator()(b, a); } }; class CLocalListViewSortName : public CLocalListViewSort { public: CLocalListViewSortName(std::vector& fileData, enum DirSortMode dirSortMode, CLocalListView* pListView) : CLocalListViewSort(fileData, dirSortMode) { } bool operator()(int a, int b) const { const CLocalListView::t_fileData &data1 = m_fileData[a]; const CLocalListView::t_fileData &data2 = m_fileData[b]; CMP(CmpDir, data1, data2); CMP_LESS(CmpName, data1, data2); } }; typedef CReverseSort CLocalListViewSortName_Reverse; class CLocalListViewSortSize : public CLocalListViewSort { public: CLocalListViewSortSize(std::vector& fileData, enum DirSortMode dirSortMode, CLocalListView* pListView) : CLocalListViewSort(fileData, dirSortMode) { } bool operator()(int a, int b) const { const CLocalListView::t_fileData &data1 = m_fileData[a]; const CLocalListView::t_fileData &data2 = m_fileData[b]; CMP(CmpDir, data1, data2); if (!data1.dir) CMP(CmpSize, data1, data2) CMP_LESS(CmpName, data1, data2); } }; typedef CReverseSort CLocalListViewSortSize_Reverse; class CLocalListViewSortType : public CLocalListViewSort { public: CLocalListViewSortType(std::vector& fileData, enum DirSortMode dirSortMode, CLocalListView* pListView) : CLocalListViewSort(fileData, dirSortMode) { m_pListView = pListView; } bool operator()(int a, int b) const { CLocalListView::t_fileData &data1 = m_fileData[a]; CLocalListView::t_fileData &data2 = m_fileData[b]; if (data1.fileType == _T("")) data1.fileType = m_pListView->GetType(data1.name, data1.dir); if (data2.fileType == _T("")) data2.fileType = m_pListView->GetType(data2.name, data2.dir); CMP(CmpDir, data1, data2); CMP(CmpType, data1, data2) CMP_LESS(CmpName, data1, data2); } protected: CLocalListView* m_pListView; }; typedef CReverseSort CLocalListViewSortType_Reverse; class CLocalListViewSortTime : public CLocalListViewSort { public: CLocalListViewSortTime(std::vector& fileData, enum DirSortMode dirSortMode, CLocalListView* pListView) : CLocalListViewSort(fileData, dirSortMode) { } bool operator()(int a, int b) const { const CLocalListView::t_fileData &data1 = m_fileData[a]; const CLocalListView::t_fileData &data2 = m_fileData[b]; CMP(CmpDir, data1, data2); CMP(CmpTime, data1, data2) CMP_LESS(CmpName, data1, data2); } }; typedef CReverseSort CLocalListViewSortTime_Reverse; CLocalListViewSortObject CLocalListView::GetComparisonObject() { const int dirSortOption = COptions::Get()->GetOptionVal(OPTION_FILELIST_DIRSORT); CLocalListViewSort::DirSortMode dirSortMode; switch (dirSortOption) { case 0: default: dirSortMode = CLocalListViewSort::dirsort_ontop; break; case 1: if (m_sortDirection) dirSortMode = CLocalListViewSort::dirsort_onbottom; else dirSortMode = CLocalListViewSort::dirsort_ontop; break; case 2: dirSortMode = CLocalListViewSort::dirsort_inline; break; } if (!m_sortDirection) { if (m_sortColumn == 1) return CLocalListViewSortObject(new CLocalListViewSortSize(m_fileData, dirSortMode, this)); else if (m_sortColumn == 2) return CLocalListViewSortObject(new CLocalListViewSortType(m_fileData, dirSortMode, this)); else if (m_sortColumn == 3) return CLocalListViewSortObject(new CLocalListViewSortTime(m_fileData, dirSortMode, this)); else return CLocalListViewSortObject(new CLocalListViewSortName(m_fileData, dirSortMode, this)); } else { if (m_sortColumn == 1) return CLocalListViewSortObject(new CLocalListViewSortSize_Reverse(m_fileData, dirSortMode, this)); else if (m_sortColumn == 2) return CLocalListViewSortObject(new CLocalListViewSortType_Reverse(m_fileData, dirSortMode, this)); else if (m_sortColumn == 3) return CLocalListViewSortObject(new CLocalListViewSortTime_Reverse(m_fileData, dirSortMode, this)); else return CLocalListViewSortObject(new CLocalListViewSortName_Reverse(m_fileData, dirSortMode, this)); } } void CLocalListView::SortList(int column /*=-1*/, int direction /*=-1*/) { if (column != -1) { #ifdef __WXMSW__ if (column != m_sortColumn && m_pHeaderImageList) { HWND hWnd = (HWND)GetHandle(); HWND header = (HWND)SendMessage(hWnd, LVM_GETHEADER, 0, 0); wxChar buffer[100]; HDITEM item; item.mask = HDI_TEXT | HDI_FORMAT; item.pszText = buffer; item.cchTextMax = 99; SendMessage(header, HDM_GETITEM, m_sortColumn, (LPARAM)&item); item.mask |= HDI_IMAGE; item.fmt |= HDF_IMAGE | HDF_BITMAP_ON_RIGHT; item.iImage = 0; SendMessage(header, HDM_SETITEM, m_sortColumn, (LPARAM)&item); } #endif m_sortColumn = column; } else column = m_sortColumn; if (direction == -1) direction = m_sortDirection; #ifdef __WXMSW__ if (m_pHeaderImageList) { HWND hWnd = (HWND)GetHandle(); HWND header = (HWND)SendMessage(hWnd, LVM_GETHEADER, 0, 0); wxChar buffer[100]; HDITEM item; item.mask = HDI_TEXT | HDI_FORMAT; item.pszText = buffer; item.cchTextMax = 99; SendMessage(header, HDM_GETITEM, column, (LPARAM)&item); item.mask |= HDI_IMAGE; item.fmt |= HDF_IMAGE | HDF_BITMAP_ON_RIGHT; item.iImage = direction ? 2 : 1; SendMessage(header, HDM_SETITEM, column, (LPARAM)&item); } #endif // Remember which files are selected bool *selected = new bool[m_fileData.size()]; memset(selected, 0, sizeof(bool) * m_fileData.size()); int item = -1; while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) selected[m_indexMapping[item]] = 1; int focused = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED); if (focused != -1) focused = m_indexMapping[focused]; const int dirSortOption = COptions::Get()->GetOptionVal(OPTION_FILELIST_DIRSORT); if (column == m_sortColumn && direction != m_sortDirection && !m_indexMapping.empty() && dirSortOption != 1) { // Simply reverse everything m_sortDirection = direction; m_sortColumn = column; std::vector::iterator start = m_indexMapping.begin(); if (m_hasParent) start++; std::reverse(start, m_indexMapping.end()); SortList_UpdateSelections(selected, focused); delete [] selected; return; } m_sortDirection = direction; m_sortColumn = column; const unsigned int minsize = m_hasParent ? 3 : 2; if (m_indexMapping.size() < minsize) { delete [] selected; return; } std::vector::iterator start = m_indexMapping.begin(); if (m_hasParent) start++; CLocalListViewSortObject object = GetComparisonObject(); std::sort(start, m_indexMapping.end(), object); object.Destroy(); SortList_UpdateSelections(selected, focused); delete [] selected; } void CLocalListView::SortList_UpdateSelections(bool* selections, int focus) { for (int i = m_hasParent ? 1 : 0; i < GetItemCount(); i++) { const int state = GetItemState(i, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); const bool selected = (state & wxLIST_STATE_SELECTED) != 0; const bool focused = (state & wxLIST_STATE_FOCUSED) != 0; int item = m_indexMapping[i]; if (selections[item] != selected) SetItemState(i, selections[item] ? wxLIST_STATE_SELECTED : 0, wxLIST_STATE_SELECTED); if (focused) { if (item != focus) SetItemState(i, 0, wxLIST_STATE_FOCUSED); } else { if (item == focus) SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); } } } void CLocalListView::OnColumnClicked(wxListEvent &event) { int col = event.GetColumn(); if (col == -1) return; int dir; if (col == m_sortColumn) dir = m_sortDirection ? 0 : 1; else dir = m_sortDirection; SortList(col, dir); Refresh(false); } void CLocalListView::OnContextMenu(wxContextMenuEvent& event) { wxMenu* pMenu = wxXmlResource::Get()->LoadMenu(_T("ID_MENU_LOCALFILELIST")); if (!pMenu) return; int index = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); int count = 0; while (index != -1) { count++; if (!index && m_hasParent) { pMenu->Enable(XRCID("ID_UPLOAD"), false); pMenu->Enable(XRCID("ID_ADDTOQUEUE"), false); pMenu->Enable(XRCID("ID_DELETE"), false); pMenu->Enable(XRCID("ID_RENAME"), false); } index = GetNextItem(index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); } if (!count) { pMenu->Enable(XRCID("ID_UPLOAD"), false); pMenu->Enable(XRCID("ID_ADDTOQUEUE"), false); pMenu->Enable(XRCID("ID_DELETE"), false); pMenu->Enable(XRCID("ID_RENAME"), false); } else if (count > 1) pMenu->Enable(XRCID("ID_RENAME"), false); PopupMenu(pMenu); delete pMenu; } void CLocalListView::OnMenuUpload(wxCommandEvent& event) { long item = -1; while (true) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) break; if (!item && m_hasParent) return; } item = -1; while (true) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) break; t_fileData *data = GetData(item); if (!data) return; if (!item && m_hasParent) m_pState->SetLocalDir(data->name); else { const CServer* pServer = m_pState->GetServer(); if (!pServer) { wxBell(); return; } CServerPath path = m_pState->GetRemotePath(); if (path.IsEmpty()) { wxBell(); return; } if (data->dir) { path.ChangePath(data->name); wxFileName fn(m_dir, _T("")); fn.AppendDir(data->name); m_pQueue->QueueFolder(event.GetId() == XRCID("ID_ADDTOQUEUE"), false, fn.GetPath(), path, *pServer); } else { wxFileName fn(m_dir, data->name); m_pQueue->QueueFile(event.GetId() == XRCID("ID_ADDTOQUEUE"), false, fn.GetFullPath(), data->name, path, *pServer, data->size); } } } } void CLocalListView::OnMenuMkdir(wxCommandEvent& event) { CInputDialog dlg; if (!dlg.Create(this, _("Create directory"), _("Please enter the name of the directory which should be created:"))) return; if (dlg.ShowModal() != wxID_OK) return; if (dlg.GetValue() == _T("")) { wxBell(); return; } wxFileName fn(dlg.GetValue(), _T("")); fn.Normalize(wxPATH_NORM_ALL, m_dir); bool res; { wxLogNull log; res = fn.Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL); } if (!res) wxBell(); DisplayDir(m_dir); } void CLocalListView::OnMenuDelete(wxCommandEvent& event) { // Under Windows use SHFileOperation to delete files and directories. // Under other systems, we have to recurse into subdirectories manually // to delete all contents. #ifdef __WXMSW__ // SHFileOperation accepts a list of null-terminated strings. Go through all // items to get the required buffer length wxString dir = m_dir; if (dir.Right(1) != _T("\\") && dir.Right(1) != _T("/")) dir += _T("\\"); int dirLen = dir.Length(); int len = 1; // String list terminated by empty string long item = -1; while (true) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) break; if (!item && m_hasParent) continue; t_fileData *data = GetData(item); if (!data) continue; len += dirLen + data->name.Length() + 1; } // Allocate memory wxChar* pBuffer = new wxChar[len]; wxChar* p = pBuffer; // Loop through all selected items again and fill buffer item = -1; while (true) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) break; if (!item && m_hasParent) continue; t_fileData *data = GetData(item); if (!data) continue; _tcscpy(p, dir); p += dirLen; _tcscpy(p, data->name); p += data->name.Length() + 1; } *p = 0; // Now we can delete the files in the buffer SHFILEOPSTRUCT op; memset(&op, 0, sizeof(op)); op.hwnd = (HWND)GetHandle(); op.wFunc = FO_DELETE; op.pFrom = pBuffer; // Move to trash if shift is not pressed, else delete op.fFlags = wxGetKeyState(WXK_SHIFT) ? 0 : FOF_ALLOWUNDO; SHFileOperation(&op); delete [] pBuffer; #else if (wxMessageBox(_("Really delete all selected files and/or directories?"), _("Confirmation needed"), wxICON_QUESTION | wxYES_NO, this) != wxYES) return; // Remember the directories to delete and the directories to visit std::list dirsToDelete; std::list dirsToVisit; wxString dir = m_dir; if (dir.Right(1) != _T("\\") && dir.Right(1) != _T("/")) dir += _T("/"); // First process selected items long item = -1; while (true) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) break; if (!item && m_hasParent) continue; t_fileData *data = GetData(item); if (!data) continue; if (data->dir) { wxString subDir = dir + data->name + _T("/"); dirsToVisit.push_back(subDir); dirsToDelete.push_front(subDir); } else wxRemoveFile(dir + data->name); } // Process any subdirs which still have to be visited while (!dirsToVisit.empty()) { wxString filename = dirsToVisit.front(); dirsToVisit.pop_front(); wxDir dir; if (!dir.Open(filename)) continue; wxString file; for (bool found = dir.GetFirst(&file); found; found = dir.GetNext(&file)) { if (file == _T("")) { wxGetApp().DisplayEncodingWarning(); continue; } if (wxFileName::DirExists(filename + file)) { wxString subDir = filename + file + _T("/"); dirsToVisit.push_back(subDir); dirsToDelete.push_front(subDir); } else wxRemoveFile(filename + file); } } // Delete the now empty directories for (std::list::const_iterator iter = dirsToDelete.begin(); iter != dirsToDelete.end(); iter++) wxRmdir(*iter); #endif m_pState->SetLocalDir(m_dir); } void CLocalListView::OnMenuRename(wxCommandEvent& event) { int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item < 0 || (!item && m_hasParent)) { wxBell(); return; } if (GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1) { wxBell(); return; } EditLabel(item); } void CLocalListView::OnChar(wxKeyEvent& event) { int code = event.GetKeyCode(); if (code == WXK_DELETE) { if (GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == -1) { wxBell(); return; } wxCommandEvent tmp; OnMenuDelete(tmp); } else if (code == WXK_F2) { wxCommandEvent tmp; OnMenuRename(tmp); } else if (code == WXK_BACK) { if (!m_hasParent) { wxBell(); return; } wxString error; if (!m_pState->SetLocalDir(_T(".."), &error)) { if (error != _T("")) wxMessageBox(error, _("Failed to change directory"), wxICON_INFORMATION); else wxBell(); } } else if (code > 32 && code < 300 && !event.HasModifiers()) { // Keyboard navigation within items wxDateTime now = wxDateTime::UNow(); if (m_lastKeyPress.IsValid()) { wxTimeSpan span = now - m_lastKeyPress; if (span.GetSeconds() >= 1) m_prefix = _T(""); } m_lastKeyPress = now; wxChar tmp[2]; #if wxUSE_UNICODE tmp[0] = event.GetUnicodeKey(); #else tmp[0] = code; #endif tmp[1] = 0; wxString newPrefix = m_prefix + tmp; bool beep = false; int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item != -1) { wxString text = GetData(item)->name; if (text.Length() >= m_prefix.Length() && !m_prefix.CmpNoCase(text.Left(m_prefix.Length()))) beep = true; } else if (m_prefix == _T("")) beep = true; int start = item; if (start < 0) start = 0; int newPos = FindItemWithPrefix(newPrefix, start); if (newPos == -1 && m_prefix == tmp && item != -1 && beep) { // Search the next item that starts with the same letter newPrefix = m_prefix; newPos = FindItemWithPrefix(newPrefix, item + 1); } m_prefix = newPrefix; if (newPos == -1) { if (beep) wxBell(); return; } item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); while (item != -1) { SetItemState(item, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); } SetItemState(newPos, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); EnsureVisible(newPos); } else event.Skip(); } void CLocalListView::OnKeyDown(wxKeyEvent& event) { const int code = event.GetKeyCode(); if (code == 'A' && event.GetModifiers() == wxMOD_CMD) { for (int i = m_hasParent ? 1 : 0; i < GetItemCount(); i++) { SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } } else OnChar(event); //event.Skip(); } int CLocalListView::FindItemWithPrefix(const wxString& prefix, int start) { const int itemCount = m_fileData.size(); for (int i = start; i < (itemCount + start); i++) { int item = i % itemCount; wxString fn; t_fileData* data = GetData(item); if (!data) continue; fn = data->name.Left(prefix.Length()); if (!fn.CmpNoCase(prefix)) return i % itemCount; } return -1; } void CLocalListView::OnBeginLabelEdit(wxListEvent& event) { if (!m_hasParent) return; if (event.GetIndex() == 0) { event.Veto(); return; } } void CLocalListView::OnEndLabelEdit(wxListEvent& event) { if (event.IsEditCancelled()) return; int index = event.GetIndex(); if (!index && m_hasParent) { event.Veto(); return; } if (event.GetLabel() == _T("")) { event.Veto(); return; } wxString newname = event.GetLabel(); #ifdef __WXMSW__ newname = newname.Left(255); if ((newname.Find('/') != -1) || (newname.Find('\\') != -1) || (newname.Find(':') != -1) || (newname.Find('*') != -1) || (newname.Find('?') != -1) || (newname.Find('"') != -1) || (newname.Find('<') != -1) || (newname.Find('>') != -1) || (newname.Find('|') != -1)) { wxMessageBox(_("Filenames may not contain any of the following characters: / \\ : * ? \" < > |"), _("Invalid filename"), wxICON_EXCLAMATION); event.Veto(); return; } SHFILEOPSTRUCT op; memset(&op, 0, sizeof(op)); wxString dir = m_dir; if (dir.Right(1) != _T("\\") && dir.Right(1) != _T("/")) dir += _T("\\"); wxString from = dir + m_fileData[m_indexMapping[index]].name + _T(" "); from.SetChar(from.Length() - 1, '\0'); op.pFrom = from; wxString to = dir + newname + _T(" "); to.SetChar(to.Length()-1, '\0'); op.pTo = to; op.hwnd = (HWND)GetHandle(); op.wFunc = FO_RENAME; op.fFlags = FOF_ALLOWUNDO; if (SHFileOperation(&op)) event.Veto(); else { m_fileData[m_indexMapping[index]].name = newname; return; } #else if ((newname.Find('/') != -1) || (newname.Find('*') != -1) || (newname.Find('?') != -1) || (newname.Find('<') != -1) || (newname.Find('>') != -1) || (newname.Find('|') != -1)) { wxMessageBox(_("Filenames may not contain any of the following characters: / * ? < > |"), _("Invalid filename"), wxICON_EXCLAMATION); event.Veto(); return; } wxString dir = m_dir; if (dir.Right(1) != _T("\\") && dir.Right(1) != _T("/")) dir += _T("\\"); if (wxRename(dir + m_fileData[m_indexMapping[index]].name, dir + newname)) m_fileData[m_indexMapping[index]].name = newname; else event.Veto(); #endif } void CLocalListView::ApplyCurrentFilter() { unsigned int min = m_hasParent ? 1 : 0; if (m_fileData.size() <= min) return; wxString focused; const std::list& selectedNames = RememberSelectedItems(focused); CFilterDialog filter; m_indexMapping.clear(); m_indexMapping.push_back(0); for (unsigned int i = min; i < m_fileData.size(); i++) { const t_fileData& data = m_fileData[i]; if (!filter.FilenameFiltered(data.name, data.dir, data.size, true)) m_indexMapping.push_back(i); } SetItemCount(m_indexMapping.size()); SortList(); ReselectItems(selectedNames, focused); } std::list CLocalListView::RememberSelectedItems(wxString& focused) { std::list selectedNames; // Remember which items were selected int item = -1; while (true) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) break; const t_fileData &data = m_fileData[m_indexMapping[item]]; if (data.dir) selectedNames.push_back(_T("d") + data.name); else selectedNames.push_back(_T("-") + data.name); SetItemState(item, 0, wxLIST_STATE_SELECTED); } item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED); if (item != -1) { const t_fileData &data = m_fileData[m_indexMapping[item]]; focused = data.name; SetItemState(item, 0, wxLIST_STATE_FOCUSED); } return selectedNames; } void CLocalListView::ReselectItems(const std::list& selectedNames, wxString focused) { // Reselect previous items if neccessary. // Sorting direction did not change. We just have to scan through items once if (selectedNames.empty()) { if (focused == _T("")) return; for (unsigned int i = 0; i < m_indexMapping.size(); i++) { const t_fileData &data = m_fileData[m_indexMapping[i]]; if (data.name == focused) { SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); return; } } return; } int firstSelected = -1; unsigned i = 0; for (std::list::const_iterator iter = selectedNames.begin(); iter != selectedNames.end(); iter++) { while (i < m_indexMapping.size()) { const t_fileData &data = m_fileData[m_indexMapping[i]]; if (data.name == focused) { SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); focused = _T(""); } if (data.dir && *iter == (_T("d") + data.name)) { if (firstSelected == -1) firstSelected = i; SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); i++; break; } else if (*iter == (_T("-") + data.name)) { if (firstSelected == -1) firstSelected = i; SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); i++; break; } else i++; } if (i == m_indexMapping.size()) break; } if (focused != _T("")) { if (firstSelected != -1) SetItemState(firstSelected, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); else SetItemState(0, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); } } void CLocalListView::OnStateChange(unsigned int event, const wxString& data) { if (event == STATECHANGE_LOCAL_DIR) DisplayDir(m_pState->GetLocalDir()); else if (event == STATECHANGE_APPLYFILTER) ApplyCurrentFilter(); else if (event == STATECHANGE_LOCAL_REFRESH_FILE) RefreshFile(data); } void CLocalListView::OnBeginDrag(wxListEvent& event) { long item = -1; while (true) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) break; if (!item && m_hasParent) return; } wxFileDataObject obj; item = -1; while (true) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == -1) break; t_fileData *data = GetData(item); if (!data) return; obj.AddFile(m_dir + data->name); } wxDropSource source(this); source.SetData(obj); int res = source.DoDragDrop(wxDrag_AllowMove); if (res == wxDragCopy || res == wxDragMove) m_pState->RefreshLocal(); } void CLocalListView::RefreshFile(const wxString& file) { bool fileExists; if (!(fileExists = wxFileName::FileExists(m_dir + file)) && !wxFileName::DirExists(m_dir + file)) return; t_fileData data; data.dir = !fileExists; data.icon = -2; data.name = file; wxStructStat buf; int result = wxStat(m_dir + file, &buf); if (!result) { data.hasTime = true; data.lastModified = wxDateTime(buf.st_mtime); } else data.hasTime = false; if (data.dir) data.size = -1; else data.size = result ? -1 : buf.st_size; // Look if file data already exists for (std::vector::iterator iter = m_fileData.begin(); iter != m_fileData.end(); iter++) { const t_fileData& oldData = *iter; if (oldData.name != file) continue; // It exists, update entry data.fileType = oldData.fileType; *iter = data; if (m_sortColumn) SortList(); Refresh(false); return; } // Insert new entry int index = m_fileData.size(); m_fileData.push_back(data); // Find correct position in index mapping std::vector::iterator start = m_indexMapping.begin(); if (m_hasParent) start++; CLocalListViewSortObject compare = GetComparisonObject(); std::vector::iterator insertPos = std::lower_bound(start, m_indexMapping.end(), index, compare); compare.Destroy(); const int item = insertPos - m_indexMapping.begin(); m_indexMapping.insert(insertPos, index); SetItemCount(m_indexMapping.size()); // Move selections int prevState = 0; for (unsigned int i = item; i < m_indexMapping.size(); i++) { int state = GetItemState(i, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); if (state != prevState) { SetItemState(i, prevState, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); prevState = state; } } Refresh(false); } void CLocalListView::InitDateFormat() { const wxString& dateFormat = COptions::Get()->GetOption(OPTION_DATE_FORMAT); const wxString& timeFormat = COptions::Get()->GetOption(OPTION_TIME_FORMAT); if (dateFormat == _T("1")) m_dateFormat = _T("%Y-%m-%d"); else if (dateFormat[0] == '2') m_dateFormat = dateFormat.Mid(1); else m_dateFormat = _T("%x"); m_dateFormat += ' '; if (timeFormat == _T("1")) m_dateFormat += _T("%H:%M"); else if (timeFormat[0] == '2') m_dateFormat += timeFormat.Mid(1); else m_dateFormat += _T("%X"); }