Hi to all.
I'm quite a newbie; I've been learning and developing in Qt 4.8.4 for just three weeks now. I have to say that I'm very impressed with the power of this platform!
I'm developing a very simple widget that must show a list of image files. To do it, I have used derivatives of Qt classes QTableView, QAbstractTableModel as well as a QSortFilterProxyModel.
The widget is composed of a single toolbar at the top and the table view just below.
The toolbar has two buttons: add and delete. The add button opens a file selector (with multiple selection enabled); the files thus selected are added to the table view below. Obviously, the delete button is enabled only when a row in the table view is selected. When clicked, removes the corresponding entry (row) from the table view.
Since I want to provide the ability to sort the rows in the view, I have used a QSortFilterProxyModel.
The problem arises when I want to delete a row from the table view. Since I'm using a filter, I translate the row number returned by the selection event from indices valid for the filter to those valid to my backend structure (a simple QStringList).
Normally, this translation works pretty well, so deletion works also fine.
But I have noticed that, depending on how are the names of the files added to the list, the index translation goes crazy!
For instance, if all the files loaded in the table view adhere to the pattern "DSC999999" (where '9' stands for a digit) my software works flawlessly. But if other type of file names enter the game, then the index translation procedure fails miserably. For instance, with filenames as "h02_0698_08-15_mar200.tif" this strange behaviour appears. The index translation fails and the row removed is not the correct one.
I wonder if this can be produced by different string codification / locales (UTF, Latin-1...). Since these filenames are obtained using a QFileDialog object that scans my disk, and my Windows 7 is an spanish version... could this explain that the sort filter would behave in such a strange way? If not, could any one take a look to my code and point to the offending / bad code?
I have included the .cpp files that are involved in this problem. I don't include the .hpp(s) because otherwise this message becomes to long and the forum system does not accept it.
Thank you very much in advance for your help!
THE FILES
ImageListModel.cpp
#include "ImageListModel.hpp"
bool
ImageListModel::
appendRow
(QString& filename)
{
{
int position = filenames_->size();
filenames_->append(filename);
endInsertRows();
return true;
}
}
ImageListModel::
ImageListModel
{
{
filenames_ = filenames;
}
}
bool
ImageListModel::
removeRow
(int position)
{
{
filenames_->removeAt(position);
endRemoveRows();
return true;
}
}
int
ImageListModel::
{
{
Q_UNUSED(parent);
return filenames_->count();
}
}
int
ImageListModel::
{
{
Q_UNUSED(parent);
return 2;
}
}
ImageListModel::
headerData(int section,
Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal)
{
if (role == Qt::DisplayRole)
{
switch (section)
{
case 0:
return tr("Image name");
case 1:
return tr("Path");
default:
}
}
}
}
ImageListModel::
int role) const
{
if (!index.
isValid()) return QVariant();
if (index.row() >= filenames_->size() || index.row() < 0)
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
switch (index.column())
{
case 0:
return fi.fileName(); // File name.
case 1:
return fi.path(); // File path.
default:
}
}
}
#include "ImageListModel.hpp"
bool
ImageListModel::
appendRow
(QString& filename)
{
{
int position = filenames_->size();
beginInsertRows(QModelIndex(), position, position);
filenames_->append(filename);
endInsertRows();
return true;
}
}
ImageListModel::
ImageListModel
(QStringList *filenames,
QObject *parent) : QAbstractTableModel(parent)
{
{
filenames_ = filenames;
}
}
bool
ImageListModel::
removeRow
(int position)
{
{
beginRemoveRows(QModelIndex(), position, position);
filenames_->removeAt(position);
endRemoveRows();
return true;
}
}
int
ImageListModel::
rowCount(const QModelIndex &parent ) const
{
{
Q_UNUSED(parent);
return filenames_->count();
}
}
int
ImageListModel::
columnCount(const QModelIndex &parent ) const
{
{
Q_UNUSED(parent);
return 2;
}
}
QVariant
ImageListModel::
headerData(int section,
Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal)
{
if (role == Qt::DisplayRole)
{
switch (section)
{
case 0:
return tr("Image name");
case 1:
return tr("Path");
default:
return QVariant();
}
}
}
return QAbstractTableModel::headerData(section, orientation, role);
}
QVariant
ImageListModel::
data(const QModelIndex &index,
int role) const
{
if (!index.isValid()) return QVariant();
if (index.row() >= filenames_->size() || index.row() < 0)
return QVariant();
QFileInfo fi = filenames_->at(index.row());
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
switch (index.column())
{
case 0:
return fi.fileName(); // File name.
case 1:
return fi.path(); // File path.
default:
return QVariant();
}
}
return QVariant();
}
To copy to clipboard, switch view to plain text mode
ImageTableView.cpp
#include "ImageTableView.hpp"
ImageTableView::
ImageTableView
{
{
}
}
void
ImageTableView::
clickedSlot
{
{
emit rowSelected(qi.row());
}
}
#include "ImageTableView.hpp"
ImageTableView::
ImageTableView
(QWidget *parent) :
QTableView(parent)
{
{
connect(this,SIGNAL(clicked(QModelIndex)),this,SLOT(clickedSlot(QModelIndex)));
}
}
void
ImageTableView::
clickedSlot
(QModelIndex qi)
{
{
emit rowSelected(qi.row());
}
}
To copy to clipboard, switch view to plain text mode
ImageListManager.hpp
#ifndef IMAGELISTMANAGER_HPP
#define IMAGELISTMANAGER_HPP
#include <QWidget>
#include <QStringList>
#include <QAction>
#include <QSortFilterProxyModel>
#include <QVBoxLayout>
#include <QToolBar>
#include <QHeaderView>
#include <QFileDialog>
#include "ImageListModel.hpp"
#include "ImageTableView.hpp"
class ImageListManager
: public QWidget{
Q_OBJECT
public:
ImageListManager
(QWidget *parent
= 0);
protected slots:
void addImages(void);
void deleteSelectedRow (void);
void rowSelected(int rowIndex);
protected:
int cur_row_;
ImageListModel* model_;
ImageTableView* tableView_;
};
#endif // IMAGELISTMANAGER_HPP
#ifndef IMAGELISTMANAGER_HPP
#define IMAGELISTMANAGER_HPP
#include <QWidget>
#include <QStringList>
#include <QAction>
#include <QSortFilterProxyModel>
#include <QVBoxLayout>
#include <QToolBar>
#include <QHeaderView>
#include <QFileDialog>
#include "ImageListModel.hpp"
#include "ImageTableView.hpp"
class ImageListManager : public QWidget
{
Q_OBJECT
public:
ImageListManager(QWidget *parent = 0);
protected slots:
void addImages(void);
void deleteSelectedRow (void);
void rowSelected(int rowIndex);
protected:
QAction* actionAddImages_;
QAction* actionRemoveImage_;
int cur_row_;
QStringList filenames_;
ImageListModel* model_;
QSortFilterProxyModel* proxyModel_;
ImageTableView* tableView_;
};
#endif // IMAGELISTMANAGER_HPP
To copy to clipboard, switch view to plain text mode
ImageListManager.cpp
#include "ImageListManager.hpp"
void
ImageListManager::
addImages
(void)
{
{
dialog.setNameFilter(trUtf8( "Image Files (*.bmp *.jpg *.jpeg *.png *.tif);;Any Files (*.*)"));
if (dialog.exec())
{
int i;
for (i = 0; i < fileNames.size(); i++)
{
model_->appendRow(*file);
delete file;
}
tableView_->resizeColumnsToContents();
}
}
}
void
ImageListManager::
deleteSelectedRow
(void)
{
{
if (cur_row_ < 0) return;
if (cur_row_ >= filenames_.size()) return;
QModelIndex sorted
= proxyModel_
->mapFromSource
(source
);
//
// Now we may delete the filename from the list, since we know
// the real position affected.
//
model_->removeRow(sorted.row());
// Reset things.
tableView_->clearSelection();
cur_row_ = -1;
actionRemoveImage_->setEnabled(false);
}
}
ImageListManager::
ImageListManager
{
{
// Initialize some members.
cur_row_ = -1;
// Create the toolbar, including its actions.
imageListToolBar->setMovable(false);
actionRemoveImage_
= new QAction(removeImageIcon, tr
("Remove image"),
this);
actionRemoveImage_->setEnabled(false);
connect(actionRemoveImage_, SIGNAL(triggered()), this, SLOT(deleteSelectedRow()));
imageListToolBar->addAction(actionRemoveImage_);
actionAddImages_
= new QAction(newImagesIcon, tr
("Add images"),
this);
actionAddImages_->setEnabled(true);
connect(actionAddImages_, SIGNAL(triggered()), this, SLOT(addImages()));
imageListToolBar->addAction(actionAddImages_);
// Create the files model that will backing the table view.
model_ = new ImageListModel(&filenames_, this);
// Create the proxy model to sort data.
proxyModel_->setSortCaseSensitivity(Qt::CaseInsensitive);
proxyModel_->setDynamicSortFilter(true);
proxyModel_->setSortLocaleAware(true);
proxyModel_->setSourceModel(model_); // Sort the data in model_
// Set up the table view for the points.
tableView_ = new ImageTableView(this);
tableView_->setModel(proxyModel_); // proxyModel_ instead of model_!!!
tableView_->setSortingEnabled(true);
tableView_->verticalHeader()->hide();
//proxyModel_->sort(0, Qt::DescendingOrder);
// Lay out the toolbar and table view.
mainLayout->addWidget(imageListToolBar);
mainLayout->addWidget(tableView_);
setLayout(mainLayout);
// Connect events coming from the table view.
connect(tableView_, SIGNAL(rowSelected(int)), this, SLOT(rowSelected(int)));
// Set the window title.
setWindowTitle(tr("Images in project"));
}
}
void
ImageListManager::
rowSelected
(int rowIndex)
{
{
actionRemoveImage_->setEnabled(true);
cur_row_ = rowIndex;
}
}
#include "ImageListManager.hpp"
void
ImageListManager::
addImages
(void)
{
{
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFiles);
dialog.setNameFilter(trUtf8( "Image Files (*.bmp *.jpg *.jpeg *.png *.tif);;Any Files (*.*)"));
if (dialog.exec())
{
int i;
QStringList fileNames = dialog.selectedFiles();
for (i = 0; i < fileNames.size(); i++)
{
QString* file = new QString(fileNames.at(i));
model_->appendRow(*file);
delete file;
}
tableView_->resizeColumnsToContents();
}
}
}
void
ImageListManager::
deleteSelectedRow
(void)
{
{
if (cur_row_ < 0) return;
if (cur_row_ >= filenames_.size()) return;
QModelIndex source = model_->index(cur_row_,0);
QModelIndex sorted = proxyModel_->mapFromSource(source);
//
// Now we may delete the filename from the list, since we know
// the real position affected.
//
model_->removeRow(sorted.row());
// Reset things.
tableView_->clearSelection();
cur_row_ = -1;
actionRemoveImage_->setEnabled(false);
}
}
ImageListManager::
ImageListManager
(QWidget *parent) : QWidget(parent)
{
{
// Initialize some members.
cur_row_ = -1;
// Create the toolbar, including its actions.
QToolBar* imageListToolBar = new QToolBar(tr("Image list toolbar"), this);
imageListToolBar->setMovable(false);
QIcon removeImageIcon;
removeImageIcon.addFile(QString::fromUtf8(":/resources/delete.png"), QSize(), QIcon::Normal, QIcon::Off);
actionRemoveImage_ = new QAction(removeImageIcon, tr("Remove image"), this);
actionRemoveImage_->setEnabled(false);
connect(actionRemoveImage_, SIGNAL(triggered()), this, SLOT(deleteSelectedRow()));
imageListToolBar->addAction(actionRemoveImage_);
QIcon newImagesIcon;
newImagesIcon.addFile(QString::fromUtf8(":/resources/add.png"), QSize(), QIcon::Normal, QIcon::Off);
actionAddImages_ = new QAction(newImagesIcon, tr("Add images"), this);
actionAddImages_->setEnabled(true);
connect(actionAddImages_, SIGNAL(triggered()), this, SLOT(addImages()));
imageListToolBar->addAction(actionAddImages_);
// Create the files model that will backing the table view.
model_ = new ImageListModel(&filenames_, this);
// Create the proxy model to sort data.
proxyModel_ = new QSortFilterProxyModel(this);
proxyModel_->setSortCaseSensitivity(Qt::CaseInsensitive);
proxyModel_->setDynamicSortFilter(true);
proxyModel_->setSortLocaleAware(true);
proxyModel_->setSourceModel(model_); // Sort the data in model_
// Set up the table view for the points.
tableView_ = new ImageTableView(this);
tableView_->setModel(proxyModel_); // proxyModel_ instead of model_!!!
tableView_->setSortingEnabled(true);
tableView_->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView_->setSelectionMode(QAbstractItemView::SingleSelection);
tableView_->verticalHeader()->hide();
//proxyModel_->sort(0, Qt::DescendingOrder);
// Lay out the toolbar and table view.
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(imageListToolBar);
mainLayout->addWidget(tableView_);
setLayout(mainLayout);
// Connect events coming from the table view.
connect(tableView_, SIGNAL(rowSelected(int)), this, SLOT(rowSelected(int)));
// Set the window title.
setWindowTitle(tr("Images in project"));
}
}
void
ImageListManager::
rowSelected
(int rowIndex)
{
{
actionRemoveImage_->setEnabled(true);
cur_row_ = rowIndex;
}
}
To copy to clipboard, switch view to plain text mode
Just for the sake of completeness, I post here the .hpp files that wouldn't fit in the previous post.
ImageListModel.hpp
#ifndef IMAGELISTMODEL_HPP
#define IMAGELISTMODEL_HPP
#include <QAbstractTableModel>
#include <QStringList>
#include <QFileInfo>
{
public:
bool appendRow (QString& filename);
QVariant headerData
(int section, Qt
::Orientation orientation,
int role
) const;
bool removeRow (int position);
protected:
};
#endif // IMAGELISTMODEL_HPP
#ifndef IMAGELISTMODEL_HPP
#define IMAGELISTMODEL_HPP
#include <QAbstractTableModel>
#include <QStringList>
#include <QFileInfo>
class ImageListModel : public QAbstractTableModel
{
public:
bool appendRow (QString& filename);
int columnCount(const QModelIndex &parent ) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
ImageListModel(QStringList* filenames, QObject *parent=0);
bool removeRow (int position);
int rowCount(const QModelIndex &parent ) const;
protected:
QStringList* filenames_;
};
#endif // IMAGELISTMODEL_HPP
To copy to clipboard, switch view to plain text mode
ImageTableView.hpp
#ifndef IMAGETABLEVIEW_HPP
#define IMAGETABLEVIEW_HPP
#include <QTableView>
{
Q_OBJECT
public:
explicit ImageTableView
(QWidget *parent
= 0);
signals:
void rowSelected(int rowIndex);
public slots:
};
#endif // IMAGETABLEVIEW_HPP
#ifndef IMAGETABLEVIEW_HPP
#define IMAGETABLEVIEW_HPP
#include <QTableView>
class ImageTableView : public QTableView
{
Q_OBJECT
public:
explicit ImageTableView(QWidget *parent = 0);
signals:
void rowSelected(int rowIndex);
public slots:
void clickedSlot(QModelIndex qi);
};
#endif // IMAGETABLEVIEW_HPP
To copy to clipboard, switch view to plain text mode
ImageListManager.hpp
#ifndef IMAGELISTMANAGER_HPP
#define IMAGELISTMANAGER_HPP
#include <QWidget>
#include <QStringList>
#include <QAction>
#include <QSortFilterProxyModel>
#include <QVBoxLayout>
#include <QToolBar>
#include <QHeaderView>
#include <QFileDialog>
#include "ImageListModel.hpp"
#include "ImageTableView.hpp"
class ImageListManager
: public QWidget{
Q_OBJECT
public:
ImageListManager
(QWidget *parent
= 0);
protected slots:
void addImages(void);
void deleteSelectedRow (void);
void rowSelected(int rowIndex);
protected:
int cur_row_;
ImageListModel* model_;
ImageTableView* tableView_;
};
#endif // IMAGELISTMANAGER_HPP
#ifndef IMAGELISTMANAGER_HPP
#define IMAGELISTMANAGER_HPP
#include <QWidget>
#include <QStringList>
#include <QAction>
#include <QSortFilterProxyModel>
#include <QVBoxLayout>
#include <QToolBar>
#include <QHeaderView>
#include <QFileDialog>
#include "ImageListModel.hpp"
#include "ImageTableView.hpp"
class ImageListManager : public QWidget
{
Q_OBJECT
public:
ImageListManager(QWidget *parent = 0);
protected slots:
void addImages(void);
void deleteSelectedRow (void);
void rowSelected(int rowIndex);
protected:
QAction* actionAddImages_;
QAction* actionRemoveImage_;
int cur_row_;
QStringList filenames_;
ImageListModel* model_;
QSortFilterProxyModel* proxyModel_;
ImageTableView* tableView_;
};
#endif // IMAGELISTMANAGER_HPP
To copy to clipboard, switch view to plain text mode
Bookmarks