"Fossies" - the Fresh Open Source Software Archive

Member "filezilla-3.48.1/src/interface/filelistctrl.cpp" (8 May 2020, 28074 Bytes) of package /linux/misc/FileZilla_3.48.1_src.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "filelistctrl.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.48.0_vs_3.48.1.

    1 #ifndef FILELISTCTRL_INCLUDE_TEMPLATE_DEFINITION
    2 // This works around a bug in GCC, appears to be [Bug pch/12707]
    3 #include <filezilla.h>
    4 #endif
    5 #include "filelistctrl.h"
    6 #include "filezillaapp.h"
    7 #include "Options.h"
    8 #include "conditionaldialog.h"
    9 #include <algorithm>
   10 #include "filelist_statusbar.h"
   11 #include "themeprovider.h"
   12 
   13 #ifndef __WXMSW__
   14 #include <wx/mimetype.h>
   15 #endif
   16 
   17 #if defined(__WXGTK__) && !defined(__WXGTK3__)
   18 #include <gtk/gtk.h>
   19 #endif
   20 
   21 #ifndef __WXMSW__
   22 DECLARE_EVENT_TYPE(fz_EVT_FILELIST_FOCUSCHANGE, -1)
   23 DECLARE_EVENT_TYPE(fz_EVT_DEFERRED_MOUSEEVENT, -1)
   24 #ifndef FILELISTCTRL_INCLUDE_TEMPLATE_DEFINITION
   25 DEFINE_EVENT_TYPE(fz_EVT_FILELIST_FOCUSCHANGE)
   26 DEFINE_EVENT_TYPE(fz_EVT_DEFERRED_MOUSEEVENT)
   27 #endif
   28 #endif
   29 
   30 BEGIN_EVENT_TABLE_TEMPLATE1(CFileListCtrl, wxListCtrlEx, CFileData)
   31 EVT_LIST_COL_CLICK(wxID_ANY, CFileListCtrl<CFileData>::OnColumnClicked)
   32 EVT_LIST_COL_RIGHT_CLICK(wxID_ANY, CFileListCtrl<CFileData>::OnColumnRightClicked)
   33 EVT_LIST_ITEM_SELECTED(wxID_ANY, CFileListCtrl<CFileData>::OnItemSelected)
   34 EVT_LIST_ITEM_DESELECTED(wxID_ANY, CFileListCtrl<CFileData>::OnItemDeselected)
   35 #ifndef __WXMSW__
   36 EVT_LIST_ITEM_FOCUSED(wxID_ANY, CFileListCtrl<CFileData>::OnFocusChanged)
   37 EVT_COMMAND(wxID_ANY, fz_EVT_FILELIST_FOCUSCHANGE, CFileListCtrl<CFileData>::OnProcessFocusChange)
   38 EVT_LEFT_DOWN(CFileListCtrl<CFileData>::OnLeftDown)
   39 EVT_COMMAND(wxID_ANY, fz_EVT_DEFERRED_MOUSEEVENT, CFileListCtrl<CFileData>::OnProcessMouseEvent)
   40 #endif
   41 EVT_KEY_DOWN(CFileListCtrl<CFileData>::OnKeyDown)
   42 END_EVENT_TABLE()
   43 
   44 #ifdef __WXMSW__
   45 // wxWidgets does not handle LVN_ODSTATECHANGED, work around it
   46 
   47 #pragma pack(push, 1)
   48 typedef struct fz_tagNMLVODSTATECHANGE
   49 {
   50     NMHDR hdr;
   51     int iFrom;
   52     int iTo;
   53     UINT uNewState;
   54     UINT uOldState;
   55 } fzNMLVODSTATECHANGE;
   56 #pragma pack(pop)
   57 
   58 // MinGW lacks these constants and macros
   59 #ifndef LVN_MARQUEEBEGIN
   60 #define LVN_MARQUEEBEGIN (LVN_FIRST-56)
   61 #endif
   62 #ifndef APPCOMMAND_BROWSER_FORWARD
   63 #define APPCOMMAND_BROWSER_FORWARD 2
   64 #endif
   65 #ifndef APPCOMMAND_BROWSER_BACKWARD
   66 #define APPCOMMAND_BROWSER_BACKWARD 1
   67 #endif
   68 #ifndef GET_APPCOMMAND_LPARAM
   69 #define GET_APPCOMMAND_LPARAM(x) (HIWORD(x)&~0xF000)
   70 #endif
   71 
   72 template<class CFileData> WXLRESULT CFileListCtrl<CFileData>::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
   73 {
   74     if (nMsg == WM_APPCOMMAND) {
   75         DWORD cmd = GET_APPCOMMAND_LPARAM(lParam);
   76         if (cmd == APPCOMMAND_BROWSER_FORWARD) {
   77             OnNavigationEvent(true);
   78             return true;
   79         }
   80         else if (cmd == APPCOMMAND_BROWSER_BACKWARD) {
   81             OnNavigationEvent(false);
   82             return true;
   83         }
   84 
   85         return wxListCtrlEx::MSWWindowProc(nMsg, wParam, lParam);
   86     }
   87 
   88     return wxListCtrlEx::MSWWindowProc(nMsg, wParam, lParam);
   89 }
   90 
   91 template<class CFileData> bool CFileListCtrl<CFileData>::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
   92 {
   93     if (!m_pFilelistStatusBar) {
   94         return wxListCtrlEx::MSWOnNotify(idCtrl, lParam, result);
   95     }
   96 
   97     *result = 0;
   98 
   99     NMHDR* pNmhdr = (NMHDR*)lParam;
  100     if (pNmhdr->code == LVN_ODSTATECHANGED) {
  101         // A range of items got (de)selected
  102 
  103         if (m_insideSetSelection) {
  104             return true;
  105         }
  106 
  107         if (!m_pFilelistStatusBar) {
  108             return true;
  109         }
  110 
  111         if (wxGetKeyState(WXK_CONTROL) && wxGetKeyState(WXK_SHIFT)) {
  112             // The behavior of Ctrl+Shift+Click is highly erratic.
  113             // Even though it is very slow, we need to manually recount.
  114             m_pFilelistStatusBar->UnselectAll();
  115             int item = -1;
  116             while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
  117                 if (m_hasParent && !item) {
  118                     continue;
  119                 }
  120 
  121                 const int index = m_indexMapping[item];
  122                 const CFileData& data = m_fileData[index];
  123                 if (data.comparison_flags == fill) {
  124                     continue;
  125                 }
  126 
  127                 if (ItemIsDir(index)) {
  128                     m_pFilelistStatusBar->SelectDirectory();
  129                 }
  130                 else {
  131                     m_pFilelistStatusBar->SelectFile(ItemGetSize(index));
  132                 }
  133             }
  134         }
  135         else {
  136             fzNMLVODSTATECHANGE* pNmOdStateChange = (fzNMLVODSTATECHANGE*)lParam;
  137 
  138             wxASSERT(pNmOdStateChange->iFrom <= pNmOdStateChange->iTo);
  139             for (int i = pNmOdStateChange->iFrom; i <= pNmOdStateChange->iTo; ++i) {
  140                 if (m_hasParent && !i) {
  141                     continue;
  142                 }
  143 
  144                 const int index = m_indexMapping[i];
  145                 const CFileData& data = m_fileData[index];
  146                 if (data.comparison_flags == fill) {
  147                     continue;
  148                 }
  149 
  150                 if (ItemIsDir(index)) {
  151                     m_pFilelistStatusBar->SelectDirectory();
  152                 }
  153                 else {
  154                     m_pFilelistStatusBar->SelectFile(ItemGetSize(index));
  155                 }
  156             }
  157         }
  158         return true;
  159     }
  160     else if (pNmhdr->code == LVN_ITEMCHANGED) {
  161         if (m_insideSetSelection) {
  162             return true;
  163         }
  164 
  165         NMLISTVIEW* pNmListView = (NMLISTVIEW*)lParam;
  166 
  167         // Item of -1 means change applied to all items
  168         if (pNmListView->iItem == -1 && !(pNmListView->uNewState & LVIS_SELECTED)) {
  169             m_pFilelistStatusBar->UnselectAll();
  170         }
  171     }
  172     else if (pNmhdr->code == LVN_MARQUEEBEGIN) {
  173         SetFocus();
  174     }
  175     else if (pNmhdr->code == LVN_GETDISPINFO) {
  176         // Handle this manually instead of using wx for it
  177         // so that we can set the overlay image
  178         LV_DISPINFO *info = (LV_DISPINFO *)lParam;
  179 
  180         LV_ITEM& lvi = info->item;
  181         long item = lvi.iItem;
  182 
  183         int column = m_pVisibleColumnMapping[lvi.iSubItem];
  184 
  185         if (lvi.mask & LVIF_TEXT) {
  186             wxString text = GetItemText(item, column);
  187             wxStrncpy(lvi.pszText, text, lvi.cchTextMax - 1);
  188             lvi.pszText[lvi.cchTextMax - 1] = 0;
  189         }
  190 
  191         if (lvi.mask & LVIF_IMAGE) {
  192             if (!lvi.iSubItem) {
  193                 lvi.iImage = OnGetItemImage(item);
  194             }
  195             else {
  196                 lvi.iImage = -1;
  197             }
  198         }
  199 
  200         if (!lvi.iSubItem) {
  201             lvi.state = INDEXTOOVERLAYMASK(GetOverlayIndex(lvi.iItem));
  202         }
  203 
  204         return true;
  205     }
  206 
  207     return wxListCtrlEx::MSWOnNotify(idCtrl, lParam, result);
  208 }
  209 #endif
  210 
  211 #if defined(__WXGTK__) && !defined(__WXGTK3__)
  212 // Need to call a member function of a C++ template class
  213 // from a C function.
  214 // Sadly template functions with C linkage aren't possible,
  215 // so use some proxy object.
  216 class CGtkEventCallbackProxyBase
  217 {
  218 public:
  219     virtual void OnNavigationEvent(bool forward) = 0;
  220     virtual ~CGtkEventCallbackProxyBase() {}
  221 };
  222 
  223 template <class CFileData> class CGtkEventCallbackProxy : public CGtkEventCallbackProxyBase
  224 {
  225 public:
  226     CGtkEventCallbackProxy(CFileListCtrl<CFileData> *pData) : m_pData(pData) {}
  227 
  228     virtual void OnNavigationEvent(bool forward)
  229     {
  230         m_pData->OnNavigationEvent(forward);
  231     }
  232 protected:
  233     CFileListCtrl<CFileData> *m_pData;
  234 };
  235 
  236 extern "C" {
  237 static gboolean gtk_button_release_event(GtkWidget*, void *gdk_event, CGtkEventCallbackProxyBase *proxy)
  238 {
  239     GdkEventButton* button_event = (GdkEventButton*)gdk_event;
  240 
  241     // 8 is back, 9 is forward.
  242     if (button_event->button != 8 && button_event->button != 9) {
  243         return FALSE;
  244     }
  245 
  246     proxy->OnNavigationEvent(button_event->button == 9);
  247 
  248     return FALSE;
  249 }
  250 }
  251 #endif
  252 
  253 template<class CFileData> CFileListCtrl<CFileData>::CFileListCtrl(wxWindow* pParent, CQueueView* pQueue, bool border)
  254     : wxListCtrlEx(pParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxLC_VIRTUAL | wxLC_REPORT | wxLC_EDIT_LABELS | (border ? wxBORDER_SUNKEN : wxNO_BORDER))
  255     , CComparableListing(this)
  256     , m_pQueue(pQueue)
  257 {
  258     CreateSystemImageList(CThemeProvider::GetIconSize(iconSizeSmall).x);
  259 
  260 #ifndef __WXMSW__
  261     m_dropHighlightAttribute.SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW));
  262 #endif
  263 
  264 #ifdef __WXMSW__
  265     // Enable use of overlay images
  266     DWORD mask = ListView_GetCallbackMask((HWND)GetHandle()) | LVIS_OVERLAYMASK;
  267     ListView_SetCallbackMask((HWND)GetHandle(), mask);
  268 #endif
  269 
  270 #if defined(__WXGTK__) && !defined(__WXGTK3__)
  271     m_gtkEventCallbackProxy.reset(new CGtkEventCallbackProxy<CFileData>(this));
  272 
  273     GtkWidget* widget = GetMainWindow()->GetConnectWidget();
  274     g_signal_connect(widget, "button_release_event", G_CALLBACK(gtk_button_release_event), m_gtkEventCallbackProxy.get());
  275 #endif
  276 
  277     m_genericTypes[genericTypes::file] = _("File").ToStdWstring();
  278     m_genericTypes[genericTypes::directory] = _("Directory").ToStdWstring();
  279 
  280     SetBackgroundStyle(wxBG_STYLE_SYSTEM);
  281 #ifndef __WXMSW__
  282     GetMainWindow()->SetBackgroundStyle(wxBG_STYLE_SYSTEM);
  283 #endif
  284 }
  285 
  286 template<class CFileData> void CFileListCtrl<CFileData>::SortList(int column /*=-1*/, int direction /*=-1*/, bool updateSelections /*=true*/)
  287 {
  288     CancelLabelEdit();
  289 
  290     if (column != -1) {
  291         if (column != m_sortColumn) {
  292             const int oldVisibleColumn = GetColumnVisibleIndex(m_sortColumn);
  293             if (oldVisibleColumn != -1) {
  294                 SetHeaderSortIconIndex(oldVisibleColumn, -1);
  295             }
  296         }
  297     }
  298     else {
  299         if (m_sortColumn != -1) {
  300             column = m_sortColumn;
  301         }
  302         else {
  303             column = 0;
  304         }
  305     }
  306 
  307     if (direction == -1) {
  308         direction = m_sortDirection;
  309     }
  310 
  311 
  312     if (column != m_sortColumn || direction != m_sortDirection) {
  313         int newVisibleColumn = GetColumnVisibleIndex(column);
  314         if (newVisibleColumn == -1) {
  315             newVisibleColumn = 0;
  316             column = 0;
  317         }
  318         SetHeaderSortIconIndex(newVisibleColumn, direction);
  319     }
  320 
  321     // Remember which files are selected
  322     bool *selected = 0;
  323     int focused_item = -1;
  324     unsigned int focused_index{};
  325 
  326     if (updateSelections) {
  327 #ifndef __WXMSW__
  328         // GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
  329         if (GetSelectedItemCount())
  330 #endif
  331         {
  332             int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
  333             if (item != -1) {
  334                 selected = new bool[m_fileData.size()];
  335                 memset(selected, 0, sizeof(bool) * m_fileData.size());
  336 
  337                 do {
  338                     selected[m_indexMapping[item]] = 1;
  339                     item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
  340                 } while (item != -1);
  341             }
  342         }
  343         focused_item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
  344         if (focused_item >= 0 && static_cast<size_t>(focused_item) < m_indexMapping.size()) {
  345             focused_index = m_indexMapping[focused_item];
  346         }
  347     }
  348 
  349     const int dirSortOption = COptions::Get()->GetOptionVal(OPTION_FILELIST_DIRSORT);
  350 
  351     if (column == m_sortColumn && direction != m_sortDirection && !m_indexMapping.empty() &&
  352         dirSortOption != 1)
  353     {
  354         // Simply reverse everything
  355         m_sortDirection = direction;
  356         m_sortColumn = column;
  357         std::vector<unsigned int>::iterator start = m_indexMapping.begin();
  358         if (m_hasParent) {
  359             ++start;
  360         }
  361         std::reverse(start, m_indexMapping.end());
  362 
  363         if (updateSelections) {
  364             SortList_UpdateSelections(selected, focused_item, focused_index);
  365             delete [] selected;
  366         }
  367 
  368         return;
  369     }
  370 
  371     m_sortDirection = direction;
  372     m_sortColumn = column;
  373 
  374     const unsigned int minsize = m_hasParent ? 3 : 2;
  375     if (m_indexMapping.size() < minsize) {
  376         delete [] selected;
  377         return;
  378     }
  379 
  380     std::vector<unsigned int>::iterator start = m_indexMapping.begin();
  381     if (m_hasParent) {
  382         ++start;
  383     }
  384     std::unique_ptr<CFileListCtrlSortBase> object = GetSortComparisonObject();
  385     std::sort(start, m_indexMapping.end(), SortPredicate(object));
  386 
  387     if (updateSelections) {
  388         SortList_UpdateSelections(selected, focused_item, focused_index);
  389         delete [] selected;
  390     }
  391 }
  392 
  393 template<class CFileData> void CFileListCtrl<CFileData>::SortList_UpdateSelections(bool* selections, int focused_item, unsigned int focused_index)
  394 {
  395     if (focused_item >= 0) {
  396         if (m_indexMapping[focused_item] != focused_index) {
  397             SetItemState(focused_item, 0, wxLIST_STATE_FOCUSED);
  398 
  399             for (unsigned int i = m_hasParent ? 1 : 0; i < m_indexMapping.size(); ++i) {
  400                 if (m_indexMapping[i] == focused_index) {
  401                     SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
  402                 }
  403             }
  404         }
  405     }
  406 
  407     if (selections) {
  408         for (unsigned int i = m_hasParent ? 1 : 0; i < m_indexMapping.size(); ++i) {
  409             const int state = GetItemState(i, wxLIST_STATE_SELECTED);
  410             const bool selected = (state & wxLIST_STATE_SELECTED) != 0;
  411 
  412             int item = m_indexMapping[i];
  413             if (selections[item] != selected) {
  414                 SetSelection(i, selections[item]);
  415             }
  416         }
  417     }
  418 }
  419 
  420 template<class CFileData> CFileListCtrlSortBase::DirSortMode CFileListCtrl<CFileData>::GetDirSortMode()
  421 {
  422     const int dirSortOption = COptions::Get()->GetOptionVal(OPTION_FILELIST_DIRSORT);
  423 
  424     CFileListCtrlSortBase::DirSortMode dirSortMode;
  425     switch (dirSortOption)
  426     {
  427     case 0:
  428     default:
  429         dirSortMode = CFileListCtrlSortBase::dirsort_ontop;
  430         break;
  431     case 1:
  432         if (m_sortDirection) {
  433             dirSortMode = CFileListCtrlSortBase::dirsort_onbottom;
  434         }
  435         else {
  436             dirSortMode = CFileListCtrlSortBase::dirsort_ontop;
  437         }
  438         break;
  439     case 2:
  440         dirSortMode = CFileListCtrlSortBase::dirsort_inline;
  441         break;
  442     }
  443 
  444     return dirSortMode;
  445 }
  446 
  447 template<class CFileData> CFileListCtrlSortBase::NameSortMode CFileListCtrl<CFileData>::GetNameSortMode()
  448 {
  449     const int nameSortOption = COptions::Get()->GetOptionVal(OPTION_FILELIST_NAMESORT);
  450 
  451     CFileListCtrlSortBase::NameSortMode nameSortMode;
  452     switch (nameSortOption)
  453     {
  454     case 0:
  455     default:
  456         nameSortMode = CFileListCtrlSortBase::namesort_caseinsensitive;
  457         break;
  458     case 1:
  459         nameSortMode = CFileListCtrlSortBase::namesort_casesensitive;
  460         break;
  461     case 2:
  462         nameSortMode = CFileListCtrlSortBase::namesort_natural;
  463         break;
  464     }
  465 
  466     return nameSortMode;
  467 }
  468 
  469 template<class CFileData> void CFileListCtrl<CFileData>::OnColumnClicked(wxListEvent &event)
  470 {
  471     int col = m_pVisibleColumnMapping[event.GetColumn()];
  472     if (col == -1) {
  473         return;
  474     }
  475 
  476     if (IsComparing()) {
  477 #ifdef __WXMSW__
  478         ReleaseCapture();
  479         Refresh();
  480 #endif
  481         CConditionalDialog dlg(this, CConditionalDialog::compare_changesorting, CConditionalDialog::yesno);
  482         dlg.SetTitle(_("Directory comparison"));
  483         dlg.AddText(_("Sort order cannot be changed if comparing directories."));
  484         dlg.AddText(_("End comparison and change sorting order?"));
  485         if (!dlg.Run()) {
  486             return;
  487         }
  488         ExitComparisonMode();
  489     }
  490 
  491     int dir;
  492     if (col == m_sortColumn) {
  493         dir = m_sortDirection ? 0 : 1;
  494     }
  495     else {
  496         dir = m_sortDirection;
  497     }
  498 
  499     SortList(col, dir);
  500     RefreshListOnly(false);
  501 }
  502 
  503 #ifdef __WXMSW__
  504 namespace {
  505 wxString GetExt(const wxString& file)
  506 {
  507     wxString ret;
  508 
  509     int pos = file.Find('.', true);
  510     if (pos > 0 && (static_cast<size_t>(pos) + 1) < file.size()) { // Does neither starts nor end with dot
  511         ret = file.Mid(pos + 1);
  512     }
  513 
  514     return ret;
  515 }
  516 }
  517 #endif
  518 
  519 template<class CFileData> std::wstring CFileListCtrl<CFileData>::GetType(std::wstring name, bool dir, std::wstring const& path)
  520 {
  521 #ifdef __WXMSW__
  522     wxString ext;
  523     if (dir) {
  524         if (!path.empty()) {
  525             ext = '/';
  526         }
  527     }
  528     else {
  529         ext = GetExt(name);
  530         ext.MakeLower();
  531     }
  532     auto typeIter = m_fileTypeMap.find(ext);
  533     if (typeIter != m_fileTypeMap.end()) {
  534         return typeIter->second;
  535     }
  536 
  537     wxString type;
  538     int flags = SHGFI_TYPENAME;
  539     if (path.empty()) {
  540         flags |= SHGFI_USEFILEATTRIBUTES;
  541     }
  542     else if (path == _T("\\")) {
  543         name += _T("\\");
  544     }
  545     else {
  546         name = path + name;
  547     }
  548 
  549     SHFILEINFO shFinfo;
  550     memset(&shFinfo, 0, sizeof(SHFILEINFO));
  551     if (SHGetFileInfo(name.c_str(),
  552         dir ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL,
  553         &shFinfo,
  554         sizeof(shFinfo),
  555         flags))
  556     {
  557         if (!*shFinfo.szTypeName) {
  558             if (!ext.empty()) {
  559                 type = ext;
  560                 type.MakeUpper();
  561                 type += _T("-");
  562                 type += _("file");
  563             }
  564             else {
  565                 type = m_genericTypes[genericTypes::file];
  566             }
  567         }
  568         else {
  569             type = shFinfo.szTypeName;
  570             if (!ext.empty()) {
  571                 m_fileTypeMap[ext.MakeLower()] = type.ToStdWstring();
  572             }
  573         }
  574     }
  575     else {
  576         if (!ext.empty()) {
  577             type = ext;
  578             type.MakeUpper();
  579             type += _T("-");
  580             type += _("file");
  581         }
  582         else {
  583             type = m_genericTypes[genericTypes::file];
  584         }
  585     }
  586     return type.ToStdWstring();
  587 #else
  588     (void)path;
  589 
  590     if (dir) {
  591         return m_genericTypes[genericTypes::directory];
  592     }
  593 
  594     size_t pos = name.rfind('.');
  595     if (pos == std::wstring::npos || pos < 1 || !name[pos + 1]) { // No dot or starts or ends with dot
  596         return m_genericTypes[genericTypes::file];
  597     }
  598     wxString ext = name.substr(pos + 1);
  599     wxString lower_ext = ext.Lower();
  600 
  601     auto typeIter = m_fileTypeMap.find(lower_ext);
  602     if (typeIter != m_fileTypeMap.end()) {
  603         return typeIter->second;
  604     }
  605 
  606     wxFileType *pType = wxTheMimeTypesManager->GetFileTypeFromExtension(ext);
  607     if (!pType) {
  608         wxString desc = ext;
  609         desc += _T("-");
  610         desc += _("file");
  611         m_fileTypeMap[ext] = desc.ToStdWstring();
  612         return desc.ToStdWstring();
  613     }
  614 
  615     wxString desc;
  616     if (pType->GetDescription(&desc) && !desc.empty()) {
  617         delete pType;
  618         m_fileTypeMap[ext] = desc.ToStdWstring();
  619         return desc.ToStdWstring();
  620     }
  621     delete pType;
  622 
  623     desc = ext;
  624     desc += _T("-");
  625     desc += _("file");
  626     m_fileTypeMap[lower_ext] = desc.ToStdWstring();
  627     return desc.ToStdWstring();
  628 #endif
  629 }
  630 
  631 template<class CFileData> void CFileListCtrl<CFileData>::ScrollTopItem(int item)
  632 {
  633     wxListCtrlEx::ScrollTopItem(item);
  634 }
  635 
  636 template<class CFileData> void CFileListCtrl<CFileData>::OnPostScroll()
  637 {
  638     if (!IsComparing()) {
  639         return;
  640     }
  641 
  642     CComparableListing* pOther = GetOther();
  643     if (!pOther) {
  644         return;
  645     }
  646 
  647     pOther->ScrollTopItem(GetTopItem());
  648 }
  649 
  650 template<class CFileData> void CFileListCtrl<CFileData>::OnExitComparisonMode()
  651 {
  652     if (m_originalIndexMapping.empty()) {
  653         return;
  654     }
  655 
  656     ComparisonRememberSelections();
  657 
  658     m_indexMapping.clear();
  659     m_indexMapping.swap(m_originalIndexMapping);
  660 
  661     for (unsigned int i = 0; i < m_fileData.size() - 1; ++i) {
  662         m_fileData[i].comparison_flags = normal;
  663     }
  664 
  665     SetItemCount(m_indexMapping.size());
  666 
  667     ComparisonRestoreSelections();
  668 
  669     RefreshListOnly();
  670 }
  671 
  672 template<class CFileData> void CFileListCtrl<CFileData>::CompareAddFile(t_fileEntryFlags flags)
  673 {
  674     if (flags == fill) {
  675         m_indexMapping.push_back(m_fileData.size() - 1);
  676         return;
  677     }
  678 
  679     int index = m_originalIndexMapping[m_comparisonIndex];
  680     m_fileData[index].comparison_flags = flags;
  681 
  682     m_indexMapping.push_back(index);
  683 }
  684 
  685 template<class CFileData> void CFileListCtrl<CFileData>::ComparisonRememberSelections()
  686 {
  687     m_comparisonSelections.clear();
  688 
  689     if (GetItemCount() != (int)m_indexMapping.size()) {
  690         return;
  691     }
  692 
  693     int focus = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
  694     if (focus != -1) {
  695         SetItemState(focus, 0, wxLIST_STATE_FOCUSED);
  696         int index = m_indexMapping[focus];
  697         if (m_fileData[index].comparison_flags == fill) {
  698             focus = -1;
  699         }
  700         else {
  701             focus = index;
  702         }
  703     }
  704     m_comparisonSelections.push_back(focus);
  705 
  706 #ifndef __WXMSW__
  707     // GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
  708     if (GetSelectedItemCount())
  709 #endif
  710     {
  711         int item = -1;
  712         while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
  713             int index = m_indexMapping[item];
  714             if (m_fileData[index].comparison_flags == fill) {
  715                 continue;
  716             }
  717             m_comparisonSelections.push_back(index);
  718         }
  719     }
  720 }
  721 
  722 template<class CFileData> void CFileListCtrl<CFileData>::ComparisonRestoreSelections()
  723 {
  724     if (m_comparisonSelections.empty()) {
  725         return;
  726     }
  727 
  728     int focus = m_comparisonSelections.front();
  729     m_comparisonSelections.pop_front();
  730 
  731     int item = -1;
  732     if (!m_comparisonSelections.empty()) {
  733         item = m_comparisonSelections.front();
  734         m_comparisonSelections.pop_front();
  735     }
  736     if (focus == -1) {
  737         focus = item;
  738     }
  739 
  740     for (unsigned int i = 0; i < m_indexMapping.size(); ++i) {
  741         int index = m_indexMapping[i];
  742         if (focus == index) {
  743             SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
  744             focus = -1;
  745         }
  746 
  747         bool isSelected = GetItemState(i, wxLIST_STATE_SELECTED) == wxLIST_STATE_SELECTED;
  748         bool shouldSelected = item == index;
  749         if (isSelected != shouldSelected) {
  750             SetSelection(i, shouldSelected);
  751         }
  752 
  753         if (shouldSelected) {
  754             if (m_comparisonSelections.empty()) {
  755                 item = -1;
  756             }
  757             else {
  758                 item = m_comparisonSelections.front();
  759                 m_comparisonSelections.pop_front();
  760             }
  761         }
  762     }
  763 }
  764 
  765 template<class CFileData> void CFileListCtrl<CFileData>::OnColumnRightClicked(wxListEvent&)
  766 {
  767     ShowColumnEditor();
  768 }
  769 
  770 template<class CFileData> void CFileListCtrl<CFileData>::InitSort(int optionID)
  771 {
  772     wxString sortInfo = COptions::Get()->GetOption(optionID);
  773     if (!sortInfo.empty()) {
  774         m_sortDirection = sortInfo[0] - '0';
  775     }
  776     else {
  777         m_sortDirection = 0;
  778     }
  779     if (m_sortDirection < 0 || m_sortDirection > 1) {
  780         m_sortDirection = 0;
  781     }
  782 
  783     if (sortInfo.Len() == 3) {
  784         m_sortColumn = sortInfo[2] - '0';
  785         if (GetColumnVisibleIndex(m_sortColumn) == -1) {
  786             m_sortColumn = 0;
  787         }
  788     }
  789     else {
  790         m_sortColumn = 0;
  791     }
  792 
  793     SetHeaderSortIconIndex(GetColumnVisibleIndex(m_sortColumn), m_sortDirection);
  794 }
  795 
  796 template<class CFileData> void CFileListCtrl<CFileData>::OnItemSelected(wxListEvent& event)
  797 {
  798 #ifndef __WXMSW__
  799     // On MSW this is done in the subclassed window proc
  800     if (m_insideSetSelection) {
  801         return;
  802     }
  803     if (m_pending_focus_processing) {
  804         return;
  805     }
  806 #endif
  807 
  808     const int item = event.GetIndex();
  809     if (item < 0 || item >= (int)m_indexMapping.size()) {
  810         return;
  811     }
  812 
  813 #ifndef __WXMSW__
  814     if (m_selections[item]) {
  815         // Need to re-do all selections
  816         // Unfortunately the focus change event comes before the selection change event
  817         // in the generic list ctrl if multiple items are already selected
  818         // and selecting one of those a second time, causing all others to be deselected
  819         UpdateSelections(0, GetItemCount() - 1);
  820         return;
  821     }
  822     m_selections[item] = true;
  823 #endif
  824 
  825     if (!m_pFilelistStatusBar) {
  826         return;
  827     }
  828 
  829 
  830     if (m_hasParent && !item) {
  831         return;
  832     }
  833 
  834     const int index = m_indexMapping[item];
  835     const CFileData& data = m_fileData[index];
  836     if (data.comparison_flags == fill) {
  837         return;
  838     }
  839 
  840     if (ItemIsDir(index)) {
  841         m_pFilelistStatusBar->SelectDirectory();
  842     }
  843     else {
  844         m_pFilelistStatusBar->SelectFile(ItemGetSize(index));
  845     }
  846 }
  847 
  848 template<class CFileData> void CFileListCtrl<CFileData>::OnItemDeselected(wxListEvent& event)
  849 {
  850 #ifndef __WXMSW__
  851     // On MSW this is done in the subclassed window proc
  852     if (m_insideSetSelection) {
  853         return;
  854     }
  855 #endif
  856 
  857     const int item = event.GetIndex();
  858     if (item < 0 || item >= (int)m_indexMapping.size()) {
  859         return;
  860     }
  861 
  862 #ifndef __WXMSW__
  863     if (!m_selections[item]) {
  864         return;
  865     }
  866     m_selections[item] = false;
  867 #endif
  868 
  869     if (!m_pFilelistStatusBar) {
  870         return;
  871     }
  872 
  873     if (m_hasParent && !item) {
  874         return;
  875     }
  876 
  877     const int index = m_indexMapping[item];
  878     const CFileData& data = m_fileData[index];
  879     if (data.comparison_flags == fill) {
  880         return;
  881     }
  882 
  883     if (ItemIsDir(index)) {
  884         m_pFilelistStatusBar->UnselectDirectory();
  885     }
  886     else {
  887         m_pFilelistStatusBar->UnselectFile(ItemGetSize(index));
  888     }
  889 }
  890 
  891 template<class CFileData> void CFileListCtrl<CFileData>::SetSelection(int item, bool select)
  892 {
  893     m_insideSetSelection = true;
  894     SetItemState(item, select ? wxLIST_STATE_SELECTED : 0, wxLIST_STATE_SELECTED);
  895     m_insideSetSelection = false;
  896 #ifndef __WXMSW__
  897     m_selections[item] = select;
  898 #endif
  899 }
  900 
  901 #ifndef __WXMSW__
  902 template<class CFileData> void CFileListCtrl<CFileData>::OnFocusChanged(wxListEvent& event)
  903 {
  904     const int focusItem = event.GetIndex();
  905 
  906     // Need to defer processing, as focus it set before selection by wxWidgets internally
  907     wxCommandEvent *evt = new wxCommandEvent();
  908     evt->SetEventType(fz_EVT_FILELIST_FOCUSCHANGE);
  909     evt->SetInt(m_focusItem);
  910     evt->SetExtraLong((long)focusItem);
  911     m_pending_focus_processing++;
  912     QueueEvent(evt);
  913 
  914     m_focusItem = focusItem;
  915 }
  916 
  917 template<class CFileData> void CFileListCtrl<CFileData>::SetItemCount(int count)
  918 {
  919     m_selections.resize(count, false);
  920     if (m_focusItem >= count) {
  921         m_focusItem = -1;
  922     }
  923     wxListCtrlEx::SetItemCount(count);
  924 }
  925 
  926 template<class CFileData> void CFileListCtrl<CFileData>::OnProcessFocusChange(wxCommandEvent& event)
  927 {
  928     m_pending_focus_processing--;
  929     int old_focus = event.GetInt();
  930     int new_focus = (int)event.GetExtraLong();
  931 
  932     if (old_focus >= GetItemCount()) {
  933         return;
  934     }
  935 
  936     if (old_focus >= 0) {
  937         bool selected = GetItemState(old_focus, wxLIST_STATE_SELECTED) == wxLIST_STATE_SELECTED;
  938         if (!selected && old_focus < m_selections.size() && m_selections[old_focus]) {
  939             // Need to deselect all
  940             if (m_pFilelistStatusBar) {
  941                 m_pFilelistStatusBar->UnselectAll();
  942             }
  943             for (unsigned int i = 0; i < m_selections.size(); ++i) {
  944                 m_selections[i] = 0;
  945             }
  946         }
  947     }
  948 
  949     int min;
  950     int max;
  951     if (new_focus > old_focus) {
  952         min = old_focus;
  953         max = new_focus;
  954     }
  955     else {
  956         min = new_focus;
  957         max = old_focus;
  958     }
  959     if (min == -1) {
  960         min++;
  961     }
  962     if (max == -1) {
  963         return;
  964     }
  965 
  966     if (max >= GetItemCount()) {
  967         return;
  968     }
  969 
  970     UpdateSelections(min, max);
  971 }
  972 
  973 template <class CFileData> void CFileListCtrl<CFileData>::UpdateSelections(int min, int max)
  974 {
  975     if (min < 0 || min > max) {
  976         return;
  977     }
  978 
  979     size_t smax = static_cast<size_t>(max);
  980     if (m_selections.size() <= smax || m_indexMapping.size() <= smax || m_fileData.size() <= smax) {
  981         return;
  982     }
  983 
  984     for (int i = min; i <= max; ++i) {
  985         bool selected = GetItemState(i, wxLIST_STATE_SELECTED) == wxLIST_STATE_SELECTED;
  986         if (selected == m_selections[i]) {
  987             continue;
  988         }
  989 
  990         m_selections[i] = selected;
  991 
  992         if (!m_pFilelistStatusBar) {
  993             continue;
  994         }
  995 
  996         if (m_hasParent && !i) {
  997             continue;
  998         }
  999 
 1000         const int index = m_indexMapping[i];
 1001         const CFileData& data = m_fileData[index];
 1002         if (data.comparison_flags == fill) {
 1003             continue;
 1004         }
 1005 
 1006         if (selected) {
 1007             if (ItemIsDir(index)) {
 1008                 m_pFilelistStatusBar->SelectDirectory();
 1009             }
 1010             else {
 1011                 m_pFilelistStatusBar->SelectFile(ItemGetSize(index));
 1012             }
 1013         }
 1014         else {
 1015             if (ItemIsDir(index)) {
 1016                 m_pFilelistStatusBar->UnselectDirectory();
 1017             }
 1018             else {
 1019                 m_pFilelistStatusBar->UnselectFile(ItemGetSize(index));
 1020             }
 1021         }
 1022     }
 1023 }
 1024 
 1025 template<class CFileData> void CFileListCtrl<CFileData>::OnLeftDown(wxMouseEvent& event)
 1026 {
 1027     // Left clicks in the whitespace around the items deselect everything
 1028     // but does not change focus. Defer event.
 1029     event.Skip();
 1030     wxCommandEvent *evt = new wxCommandEvent();
 1031     evt->SetEventType(fz_EVT_DEFERRED_MOUSEEVENT);
 1032     QueueEvent(evt);
 1033 }
 1034 
 1035 template<class CFileData> void CFileListCtrl<CFileData>::OnProcessMouseEvent(wxCommandEvent&)
 1036 {
 1037     if (m_pending_focus_processing) {
 1038         return;
 1039     }
 1040 
 1041     if (m_focusItem >= GetItemCount()) {
 1042         return;
 1043     }
 1044     if (m_focusItem == -1) {
 1045         return;
 1046     }
 1047 
 1048     bool selected = GetItemState(m_focusItem, wxLIST_STATE_SELECTED) == wxLIST_STATE_SELECTED;
 1049     if (!selected && m_selections[m_focusItem]) {
 1050         // Need to deselect all
 1051         if (m_pFilelistStatusBar) {
 1052             m_pFilelistStatusBar->UnselectAll();
 1053         }
 1054         for (unsigned int i = 0; i < m_selections.size(); ++i) {
 1055             m_selections[i] = 0;
 1056         }
 1057     }
 1058 }
 1059 #endif
 1060 
 1061 template<class CFileData> void CFileListCtrl<CFileData>::ClearSelection()
 1062 {
 1063     // Clear selection
 1064 #ifndef __WXMSW__
 1065     // GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
 1066     if (GetSelectedItemCount())
 1067 #endif
 1068     {
 1069         int item = -1;
 1070         while ((item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
 1071             SetSelection(item, false);
 1072         }
 1073     }
 1074 
 1075     int item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
 1076     if (item != -1) {
 1077         SetItemState(item, 0, wxLIST_STATE_FOCUSED);
 1078     }
 1079 }
 1080 
 1081 template<class CFileData> void CFileListCtrl<CFileData>::OnKeyDown(wxKeyEvent& event)
 1082 {
 1083 #ifdef __WXMAC__
 1084 #define CursorModifierKey wxMOD_CMD
 1085 #else
 1086 #define CursorModifierKey wxMOD_ALT
 1087 #endif
 1088 
 1089     const int code = event.GetKeyCode();
 1090     const int mods = event.GetModifiers();
 1091     if (code == 'A' && (mods & wxMOD_CMD || (mods & (wxMOD_CONTROL | wxMOD_META)) == (wxMOD_CONTROL | wxMOD_META))) {
 1092         int mask = fill;
 1093         if (mods & wxMOD_SHIFT) {
 1094             mask |= normal;
 1095         }
 1096 
 1097         for (unsigned int i = m_hasParent ? 1 : 0; i < m_indexMapping.size(); ++i) {
 1098             const CFileData& data = m_fileData[m_indexMapping[i]];
 1099             if (data.comparison_flags & mask) {
 1100                 SetSelection(i, false);
 1101             }
 1102             else {
 1103                 SetSelection(i, true);
 1104             }
 1105         }
 1106         if (m_hasParent && GetItemCount()) {
 1107             SetSelection(0, false);
 1108         }
 1109         if (m_pFilelistStatusBar) {
 1110             m_pFilelistStatusBar->SelectAll();
 1111         }
 1112     }
 1113     else if (code == WXK_BACK ||
 1114             (code == WXK_UP && event.GetModifiers() == CursorModifierKey) ||
 1115             (code == WXK_LEFT && event.GetModifiers() == CursorModifierKey)
 1116         )
 1117     {
 1118         OnNavigationEvent(false);
 1119     }
 1120     else {
 1121         event.Skip();
 1122     }
 1123 }
 1124 
 1125 template<class CFileData> void CFileListCtrl<CFileData>::UpdateSelections_ItemsAdded(std::vector<int> const& added_indexes)
 1126 {
 1127     if (added_indexes.empty()) {
 1128         return;
 1129     }
 1130 
 1131     auto added_index = added_indexes.cbegin();
 1132     std::deque<bool> selected;
 1133     // This is O(n) in number of items. I think it's possible to make it O(n) in number of selections
 1134     for (unsigned int i = *added_index; i < m_indexMapping.size(); ++i) {
 1135         if (added_index != added_indexes.end() && i == static_cast<unsigned int>(*added_index)) {
 1136             selected.push_front(false);
 1137             ++added_index;
 1138         }
 1139         bool is_selected = GetItemState(i, wxLIST_STATE_SELECTED) != 0;
 1140         selected.push_back(is_selected);
 1141 
 1142         bool should_selected = selected.front();
 1143         selected.pop_front();
 1144         if (is_selected != should_selected) {
 1145             SetSelection(i, should_selected);
 1146         }
 1147     }
 1148 }