программа с моделью 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 шт):
Я считаю, что тут бессмыслица:
// Если 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>
Ваша основная ошибка в том, что вы забываете, что в обоих 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
