программа с моделью QFileSystemModel начала вылетать после добавления сортировки путей

Проблема, пути .. и . оказываются где угодно, то в центре то сверху то снизу. Хотелось бы, чтобы данные пути всегда были сверху. Попытался добавить QSortFilterProxyModel и переопределить метод 'bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right)', но это не помогло. Программа начала вылетать, когда начинаешь переходить в другую папку.

#ifndef USBCONNECT_H
#define USBCONNECT_H

#include <QWidget>
#include <QDir>
#include <QFileSystemModel>
#include <QDebug>
#include <QDateTime>
#include <QSortFilterProxyModel>
#include <QFontDatabase>


class MySort : public QSortFilterProxyModel {
protected:
    bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
    {
        QString left = sourceModel()->data(source_left).toString();
        QString right = sourceModel()->data(source_right).toString();

        // Если left, или right соответствуют "." и "..", или наоборот
        if( (left == "." && right == "..") || (right == "." && left == "..") ) {
            return false;
        } else if( left == "." || right == "." || left == ".." || right == ".." ) {
            // Любое из значений либо ".", или ".."
            return false;
        }
        return QSortFilterProxyModel::lessThan(source_left, source_right);
    }
};


namespace Ui {
class UsbConnect;
}

class UsbConnect : public QWidget
{
    Q_OBJECT

public:
    explicit UsbConnect(QWidget *parent = 0);
    ~UsbConnect();

signals:
    void signalHideUsbConnect();

private slots:
    void on_pushButton_6_clicked();

    void on_listView_clicked(const QModelIndex &index);

    void on_pushButton_clicked();

public slots:
    void slotConnectUsbFlash();

private:
    Ui::UsbConnect *ui;
    QFileSystemModel *model;
    QString sda1;
    QString sdb1;
};


// Ищет отсутствующие или неактуальные элементы резервной директории
void contentDifference(QDir &sDir, QDir &dDir, QFileInfoList &diffList);
// Наполняет список всех вложенных директорий и файлов
void recursiveContentList(QDir &dir, QFileInfoList &contentList);

#endif // USBCONNECT_H


#include "usbconnect.h"
#include "ui_usbconnect.h"

UsbConnect::UsbConnect(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::UsbConnect)
{
    ui->setupUi(this);

    sda1 = "/media/usbhd-sda1/";
    sdb1 = "/media/usbhd-sdb1/";
    model = new QFileSystemModel(this);
    model->setFilter(QDir::AllEntries);
    model->setRootPath(sda1);

    MySort* proxy = new MySort();
    proxy->setSourceModel(model);
    proxy->sort(0, Qt::AscendingOrder); // или Qt::DescendingOrder

    int id = QFontDatabase::addApplicationFont("/usr/share/fonts/dejavu/DejaVuSansMono.ttf");
    QFont font = QFont(QFontDatabase::applicationFontFamilies(id).first());
    font.setPointSize(12);

    ui->listView->setModel(proxy);
    ui->listView->setFont(font);
    ui->listView->setGridSize( QSize( 64, 64 ) );
    ui->listView->setIconSize( QSize( 64, 64 ) );
    ui->listView->setUniformItemSizes( true );
    ui->listView->setRootIndex(model->index(sda1)); // авто-переход на usb накопитель

    ui->listView_2->setModel(proxy);
    ui->listView_2->setFont(font);
    ui->listView_2->setGridSize( QSize( 64, 64) );
    ui->listView_2->setIconSize( QSize( 64, 64 ) );
    ui->listView_2->setUniformItemSizes( true );
    connect(ui->listView_2, SIGNAL(clicked(QModelIndex)), this, SLOT(on_listView_clicked(QModelIndex)) ); // один слот(обработчик) для двух listView
}

void contentDifference(QDir &sDir, QDir &dDir, QFileInfoList &diffList)
{
    foreach(QFileInfo sInfo, sDir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name | QDir::DirsFirst)){
     bool fileExists = false;
      foreach(QFileInfo dInfo, dDir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name | QDir::DirsFirst)){
          if(sInfo.fileName() == dInfo.fileName()){
              if(sInfo.isDir() || sInfo.lastModified() <= dInfo.lastModified())
                  fileExists = true;
                  break;
          }
    }
    if(!fileExists)
       diffList.append(sInfo);
    if(sInfo.isFile())
        continue;
    if(fileExists){
        sDir.cd(sInfo.fileName());
        dDir.cd(sInfo.fileName());
        contentDifference(sDir, dDir, diffList);
        sDir.cdUp();
        dDir.cdUp();
    }
    else{
        sDir.cd(sInfo.fileName());
        recursiveContentList(sDir, diffList);
    }

    }
}

void recursiveContentList(QDir &dir, QFileInfoList &contentList)
{
    foreach(QFileInfo info, dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name | QDir::DirsFirst)){
     contentList.append(info);
     if(info.isDir() && dir.cd(info.fileName())){
         recursiveContentList(dir, contentList);
         dir.cdUp();
     }
    }
}

void UsbConnect::on_listView_clicked(const QModelIndex &index) // переход по пути по одному клику
{

    MySort* proxy = (MySort*)(ui->listView->model());

    if( proxy ) {
        Qt::SortOrder order = proxy->sortOrder() == Qt::AscendingOrder? Qt::DescendingOrder: Qt::AscendingOrder;
        proxy->sort(0, order);
    }

    QListView* listView = (QListView*)sender(); // получается у нас один слот на два listView по этой причине создаю дополнительную модель listView и работаю уже с ней
    QFileInfo fileInfo = model->fileInfo(index);
    if(fileInfo.fileName() == ".."){ // если нажали на папку с .. то переход на один уровень вниз
        QDir dir = fileInfo.dir();
        dir.cdUp();
        listView->setRootIndex(model->index(dir.absolutePath()));
    }
    else if(fileInfo.fileName() == "."){ // если нажали на папку с . то переход в корень
        listView->setRootIndex(model->index(""));
    }
    else if(fileInfo.isDir()){ // если нажали на папку с . то переход по указоному пути
       listView->setRootIndex(index);
    }
}

Дополнение:

    void UsbConnect::on_pushButton_clicked() // слот для копирования данных
{
   MySort* proxy = (MySort*)(ui->listView->model());
   MySort* proxy1 = (MySort*)(ui->listView_2->model());

   // Нужно получить пути от куда копировать и куда копировать

   QDir sDir = QDir(model->fileInfo(proxy->mapToSource(ui->listView->rootIndex())).dir()); // от куда копировать
   QDir dDir = QDir(model->fileInfo(proxy1->mapToSource(ui->listView_2->rootIndex())).dir()); // куда копировать

   qDebug() << sDir << dDir;
   /* получил
    * QDir( "/media" , nameFilters = { * },  QDir::SortFlags( Name | IgnoreCase ) , QDir::Filters( Dirs|Files|Drives|AllEntries ) )
    * QDir( "/media" , nameFilters = { * },  QDir::SortFlags( Name | IgnoreCase ) , QDir::Filters( Dirs|Files|Drives|AllEntries ) )
    а должны быть пути вида /media/usbhd-sda1 , /media/usbhd-sdb1
   */

   QFileInfoList diffList = QFileInfoList();
   contentDifference(sDir, dDir, diffList);
   foreach(QFileInfo diffInfo, diffList) {
   QString backupPath = diffInfo.filePath().replace(sDir.absolutePath(), dDir.absolutePath());
   if(diffInfo.isFile()){
   QFile::remove(backupPath);
   QFile::copy(diffInfo.absoluteFilePath(), backupPath);
   }
   if(diffInfo.isDir()){
       dDir.mkdir(backupPath);
   }
   }
}

Ответы (2 шт):

Автор решения: Majestio

Я считаю, что тут бессмыслица:

    // Если left, или right соответствуют "." и "..", или наоборот

    if( (left == "." && right == "..") || (right == "." && left == "..") ) {
        return false;
    } else if( left == "." || right == "." || left == ".." || right == ".." ) {
        // Любое из значений либо ".", или ".."
        return false;
    }

"Если левое или правое, и наоборот = . или .. , то меньше". Не мудрено, что программа с ума сходит. Надо четко определить, что "меньше" - ссылка на свой же каталог, или ссылка на каталог выше. А пока каша.

Вот так правильно будет:

bool MySortFilterProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
  QString DataLeft = sourceModel()->data(left).toString();
  QString DataRight = sourceModel()->data(right).toString();
  bool True = (sortOrder() == Qt::AscendingOrder) ? true : false;
  bool False = (sortOrder() == Qt::AscendingOrder) ? false : true;
  // . всегда меньше
  if (DataLeft == ".") return True;
  // любое значение слева всегда больше если . справа
  if (DataRight == ".") return False;
  // .. справа всегда меньше любого справа, кроме .
  if (DataLeft == ".." && DataRight != ".") return True;
  // .. справа всегда больше .
  if (DataLeft == ".." && DataRight == ".") return False;
  // каталоги выше файлов
  QFileInfo InfoLeft = dynamic_cast<QFileSystemModel *>(sourceModel())->fileInfo(left);
  QFileInfo InfoRight = dynamic_cast<QFileSystemModel *>(sourceModel())->fileInfo(right);
  if (InfoLeft.isDir() && !InfoRight.isDir()) return True;
  if (!InfoLeft.isDir() && InfoRight.isDir()) return False;
  // сравнения без учета регистра
  if (DataLeft.toUpper() < DataRight.toUpper()) return true;
  return false;
}

Тестовый проект прикрепляю!

SimpleFileBrowser.qbs

import qbs.FileInfo

QtApplication {
    Depends { name: "Qt.widgets" }
    cpp.defines: [
        "QT_DEPRECATED_WARNINGS",
    ]
    files: [
        "main.cpp",
        "dialog.cpp",
        "dialog.h",
        "SimpleFileBrowser.qrc",
    ]
    install: true
    installDir: qbs.targetOS.contains("qnx") ? FileInfo.joinPaths("/tmp", name, "bin") : base
}

SimpleFileBrowser.qrc

<RCC>
    <qresource prefix="/">
        <file>img/Arrow-down-white.svg</file>
        <file>img/Arrow-up-white.svg</file>
    </qresource>
    <qresource prefix="/"/>
</RCC>

main.cpp

#include "dialog.h"

#include <QtWidgets>

int main(int argc, char *argv[]) {
  QApplication a(argc, argv);
  Dialog w;
  w.show();
  return a.exec();
}

dialog.h

#ifndef DIALOG_H
#define DIALOG_H

#include <QtWidgets>

class Style_tweaks : public QProxyStyle {
  public:
    void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
                       QPainter *painter, const QWidget *widget) const {
      if (element == QStyle::PE_FrameFocusRect)
        return;
      QProxyStyle::drawPrimitive(element, option, painter, widget);
    }
};

class MySortFilterProxyModel: public QSortFilterProxyModel {
    Q_OBJECT
  public: MySortFilterProxyModel(QObject *parent = 0): QSortFilterProxyModel(parent) {};
  protected:
    bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
};

class Dialog : public QDialog {
    Q_OBJECT
    QTableView *Table;
    QFileSystemModel *Model;
    MySortFilterProxyModel *Proxy;
    Style_tweaks Tweak;
    int SortColumn = 0;
    void TuneTable();
  public:
    Dialog(QWidget *parent = nullptr);
    ~Dialog();
  public slots:
    void SlotDoubleClick(const QModelIndex& iIndex);
};
#endif // DIALOG_H

dialog.cpp

#include "dialog.h"

void Dialog::TuneTable() {
  Table->setAlternatingRowColors(true);
  Table->verticalHeader()->setHidden(true);
  Table->verticalHeader()->setDefaultSectionSize(20);
  Table->setSelectionBehavior(QAbstractItemView::SelectRows);
  Table->setSelectionMode(QAbstractItemView::SingleSelection);
  Table->setEditTriggers(QAbstractItemView::NoEditTriggers);
  Table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
  Table->horizontalHeader()->setHighlightSections(false);
  Table->horizontalHeader()->setSectionsClickable(true);
  Table->verticalHeader()->setHighlightSections(false);
  QFont Head = Table->horizontalHeader()->font();
  Head.setBold(true);
  Table->horizontalHeader()->setFont(Head);
  Table->setWordWrap(false);
  Table->setSortingEnabled(true);
  QString StyleSet =
    "QHeaderView::down-arrow { image: url(:/img/Arrow-down-white.svg); width: 13px; height:9px; subcontrol-position: center right; margin-right: 3px;} "
    "QHeaderView::up-arrow { image: url(:/img/Arrow-up-white.svg); width: 13px; height:9px; subcontrol-position: center right; margin-right: 3px;} "
    "QHeaderView::section:horizontal { color: #204f1e; background-color: #5bc169; padding-left: 13px; padding-right: 15px; border-style: solid; border-left-width: 1px; border-left-color: #5bc169; border-right-color: lightgray; border-right-width: 1px; }"
    "QHeaderView::section:horizontal:text { padding-top: 8px; padding-bottom: 0px;}"
    "QHeaderView::section:horizontal:first { border-left-color: #5bc169;}"
    "QHeaderView::section:horizontal:last { border-right-color: #5bc169;}"
    "QHeaderView::section:horizontal:hover { background-color: #80ce89;}";
  Table->setStyleSheet(StyleSet);
  Table->setStyle(&Tweak);
  Table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
  Table->horizontalHeader()->setSortIndicator(0, Qt::AscendingOrder);
}

Dialog::Dialog(QWidget *parent)
  : QDialog(parent) {
  QVBoxLayout *V = new QVBoxLayout();
  QHBoxLayout *H = new QHBoxLayout();
  Table = new QTableView();
  QPushButton *B = new QPushButton("Close");
  V->addWidget(Table, 1);
  H->addStretch();
  H->addWidget(B);
  V->addLayout(H);
  setLayout(V);
  Model = new QFileSystemModel(this);
  Proxy =  new MySortFilterProxyModel(this);
  Model->setRootPath("C:/");
  Model->setFilter((QDir::Filters)(Model->filter() - (QDir::NoDotAndDotDot)));
  Proxy->setSourceModel(Model);
  Table->setModel(Proxy);
  Table->setRootIndex(Proxy->mapFromSource(Model->index("C:/")));
  TuneTable();
  resize(600, 480);
  setMinimumSize(640, 480);
  connect(B, &QPushButton::clicked, this, &Dialog::close);
  connect(Table, &QTableView::doubleClicked, this, &Dialog::SlotDoubleClick);
}

Dialog::~Dialog() {
}

void Dialog::SlotDoubleClick(const QModelIndex& iIndex) {
  QFileInfo Info = Model->fileInfo(Proxy->mapToSource(iIndex));
  if (Info.isDir()) {
    QString Path = Info.canonicalFilePath();
    if (Info.fileName() == "..") {
      QDir Chg(Path);
      Chg.cdUp();
      Table->setRootIndex(Proxy->mapFromSource(Model->index(Chg.path())));
    } else
      Table->setRootIndex(Proxy->mapFromSource(Model->index(Path)));
  }
}

bool MySortFilterProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
  QString DataLeft = sourceModel()->data(left).toString();
  QString DataRight = sourceModel()->data(right).toString();
  bool True = (sortOrder() == Qt::AscendingOrder) ? true : false;
  bool False = (sortOrder() == Qt::AscendingOrder) ? false : true;
  // . всегда меньше
  if (DataLeft == ".") return True;
  // любое значение слева всегда больше если . справа
  if (DataRight == ".") return False;
  // .. справа всегда меньше любого справа, кроме .
  if (DataLeft == ".." && DataRight != ".") return True;
  // .. справа всегда больше .
  if (DataLeft == ".." && DataRight == ".") return False;
  // каталоги выше файлов
  QFileInfo InfoLeft = dynamic_cast<QFileSystemModel *>(sourceModel())->fileInfo(left);
  QFileInfo InfoRight = dynamic_cast<QFileSystemModel *>(sourceModel())->fileInfo(right);
  if (InfoLeft.isDir() && !InfoRight.isDir()) return True;
  if (!InfoLeft.isDir() && InfoRight.isDir()) return False;
  // сравнения без учета регистра
  if (DataLeft.toUpper() < DataRight.toUpper()) return true;
  return false;
}

img/Arrow-down-white.svg

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Arrow-Down" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
   viewBox="0 0 255 255" style="enable-background:new 0 0 255 255;" xml:space="preserve">
<style type="text/css">
  .st0{fill:#FFFFFF;}
</style>
<g>
  <g id="arrow-drop-down">
    <polygon class="st0" points="0,63.7 127.5,191.2 255,63.7        "/>
  </g>
</g>
</svg>

img/Arrow-up-white.svg

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Arrow-Up" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
   viewBox="0 0 213.3 213.3" style="enable-background:new 0 0 213.3 213.3;" xml:space="preserve">
<style type="text/css">
  .st0{fill:#FFFFFF;}
</style>
<g>
  <g>
    <polygon class="st0" points="106.7,53.3 0,160 213.3,160         "/>
  </g>
</g>
</svg>

Скриншот

→ Ссылка
Автор решения: Alexander Chernin

Ваша основная ошибка в том, что вы забываете, что в обоих listView у вас не сама модель (model), а посредник (proxy). Если этот факт учесть, то все работает нормально (с точки зрения кода).

Для отладки в вашем коде я оставил только конструктор и выбор строки в listView_2, остальное не проверял и просто закоментировал.

Метод UsbConnect::on_listView_clicked целиком:

void UsbConnect::on_listView_clicked(const QModelIndex &index) {
    // Здесь index это объект из модели MySort, а не QFileSystemModel

    MySort* proxy = (MySort*)(ui->listView->model());

    // Получаем QModelIndex, соответствующий модели из прокси
    QFileInfo fileInfo =  model->fileInfo(proxy->mapToSource(index));
    if(fileInfo.fileName() == ".."){ // если нажали на папку с .. то переход на один уровень вверх
        QDir dir = fileInfo.dir();
        dir.cdUp();

        // Снова получаем значение из основной модели, используя посредник
        ui->listView->setRootIndex( proxy->mapFromSource(model->index(dir.absolutePath())) );
    }
    else if(fileInfo.fileName() == "."){ // если нажали на папку с . то переход в корень
        ui->listView->setRootIndex( proxy->mapFromSource(model->index("")) );
    }
    else if(fileInfo.isDir()){
       ui->listView->setRootIndex(index);
    }
}

Кроме того, в конструкторе надо заменить строку ui->listView->setRootIndex(model->index(sda1)); на ui->listView->setRootIndex(proxy->mapFromSource(model->index(sda1))); по той же самой причине.

Дополнение. Если вам нужно установить вид сортировки один раз - делайте это в конструкторе главного окна:

model = new QFileSystemModel(this);
model->setFilter(QDir::AllEntries);
//...

MySort* proxy = new MySort();
proxy->setSourceModel(model);

ui->listView->setModel(proxy);
ui->listView->setRootIndex(proxy->mapFromSource(model->index(sda1))); 

ui->listView_2->setModel(proxy);

// Сортируем раз и навсегда по возрастанию
proxy->sort(0, Qt::AscendingOrder); // или Qt::DescendingOrder
→ Ссылка