Polish the findfiles example to be actually useful

- Simplify the code, remove unused members
- Fix the translations of plurals to use %n
- Add tooltip displaying full paths in list
- Add context menu allowing to copy the name and  open
- Display the correct slashes on Windows
- Connect the returnPressed() signals of the line edits
- Make the search recursive
- Do not search binary files by checking the mime type

Change-Id: I3663799c88931db1f58c03ea35211e7ab03737ec
Reviewed-by: Topi Reiniö <topi.reinio@theqtcompany.com>
This commit is contained in:
Friedemann Kleint 2016-07-05 13:26:53 +02:00
parent d5be0d3058
commit d1a30be5ab
3 changed files with 135 additions and 48 deletions

View File

@ -42,51 +42,72 @@
#include "window.h"
//! [17]
enum { absoluteFileNameRole = Qt::UserRole + 1 };
//! [17]
//! [18]
static inline QString fileNameOfItem(const QTableWidgetItem *item)
{
return item->data(absoluteFileNameRole).toString();
}
//! [18]
//! [14]
static inline void openFile(const QString &fileName)
{
QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
}
//! [14]
//! [0]
Window::Window(QWidget *parent)
: QWidget(parent)
{
browseButton = new QPushButton(tr("&Browse..."), this);
QPushButton *browseButton = new QPushButton(tr("&Browse..."), this);
connect(browseButton, &QAbstractButton::clicked, this, &Window::browse);
findButton = new QPushButton(tr("&Find"), this);
connect(findButton, &QAbstractButton::clicked, this, &Window::find);
fileComboBox = createComboBox(tr("*"));
connect(fileComboBox->lineEdit(), &QLineEdit::returnPressed,
this, &Window::animateFindClick);
textComboBox = createComboBox();
directoryComboBox = createComboBox(QDir::currentPath());
connect(textComboBox->lineEdit(), &QLineEdit::returnPressed,
this, &Window::animateFindClick);
directoryComboBox = createComboBox(QDir::toNativeSeparators(QDir::currentPath()));
connect(directoryComboBox->lineEdit(), &QLineEdit::returnPressed,
this, &Window::animateFindClick);
fileLabel = new QLabel(tr("Named:"));
textLabel = new QLabel(tr("Containing text:"));
directoryLabel = new QLabel(tr("In directory:"));
filesFoundLabel = new QLabel;
createFilesTable();
//! [0]
//! [1]
QGridLayout *mainLayout = new QGridLayout;
mainLayout->addWidget(fileLabel, 0, 0);
QGridLayout *mainLayout = new QGridLayout(this);
mainLayout->addWidget(new QLabel(tr("Named:")), 0, 0);
mainLayout->addWidget(fileComboBox, 0, 1, 1, 2);
mainLayout->addWidget(textLabel, 1, 0);
mainLayout->addWidget(new QLabel(tr("Containing text:")), 1, 0);
mainLayout->addWidget(textComboBox, 1, 1, 1, 2);
mainLayout->addWidget(directoryLabel, 2, 0);
mainLayout->addWidget(new QLabel(tr("In directory:")), 2, 0);
mainLayout->addWidget(directoryComboBox, 2, 1);
mainLayout->addWidget(browseButton, 2, 2);
mainLayout->addWidget(filesTable, 3, 0, 1, 3);
mainLayout->addWidget(filesFoundLabel, 4, 0, 1, 2);
mainLayout->addWidget(findButton, 4, 2);
setLayout(mainLayout);
setWindowTitle(tr("Find Files"));
resize(700, 300);
const QRect screenGeometry = QApplication::desktop()->screenGeometry(this);
resize(screenGeometry.width() / 2, screenGeometry.height() / 3);
}
//! [1]
//! [2]
void Window::browse()
{
QString directory = QFileDialog::getExistingDirectory(this,
tr("Find Files"), QDir::currentPath());
QString directory =
QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Find Files"), QDir::currentPath()));
if (!directory.isEmpty()) {
if (directoryComboBox->findText(directory) == -1)
@ -102,14 +123,28 @@ static void updateComboBox(QComboBox *comboBox)
comboBox->addItem(comboBox->currentText());
}
//! [13]
static void findRecursion(const QString &path, const QString &pattern, QStringList *result)
{
QDir currentDir(path);
const QString prefix = path + QLatin1Char('/');
foreach (const QString &match, currentDir.entryList(QStringList(pattern), QDir::Files | QDir::NoSymLinks))
result->append(prefix + match);
foreach (const QString &dir, currentDir.entryList(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot))
findRecursion(prefix + dir, pattern, result);
}
//! [13]
//! [3]
void Window::find()
{
filesTable->setRowCount(0);
QString fileName = fileComboBox->currentText();
QString text = textComboBox->currentText();
QString path = directoryComboBox->currentText();
QString path = QDir::cleanPath(directoryComboBox->currentText());
//! [3]
updateComboBox(fileComboBox);
@ -117,19 +152,21 @@ void Window::find()
updateComboBox(directoryComboBox);
//! [4]
currentDir = QDir(path);
QStringList files;
if (fileName.isEmpty())
fileName = "*";
files = currentDir.entryList(QStringList(fileName),
QDir::Files | QDir::NoSymLinks);
findRecursion(path, fileName.isEmpty() ? QStringLiteral("*") : fileName, &files);
if (!text.isEmpty())
files = findFiles(files, text);
showFiles(files);
}
//! [4]
void Window::animateFindClick()
{
findButton->animateClick();
}
//! [5]
QStringList Window::findFiles(const QStringList &files, const QString &text)
{
@ -139,21 +176,26 @@ QStringList Window::findFiles(const QStringList &files, const QString &text)
progressDialog.setWindowTitle(tr("Find Files"));
//! [5] //! [6]
QMimeDatabase mimeDatabase;
QStringList foundFiles;
for (int i = 0; i < files.size(); ++i) {
progressDialog.setValue(i);
progressDialog.setLabelText(tr("Searching file number %1 of %2...")
.arg(i).arg(files.size()));
qApp->processEvents();
progressDialog.setLabelText(tr("Searching file number %1 of %n...", 0, files.size()).arg(i));
QCoreApplication::processEvents();
//! [6]
if (progressDialog.wasCanceled())
break;
//! [7]
QFile file(currentDir.absoluteFilePath(files[i]));
const QString fileName = files.at(i);
const QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileName);
if (mimeType.isValid() && !mimeType.inherits(QStringLiteral("text/plain"))) {
qWarning() << "Not searching binary file " << QDir::toNativeSeparators(fileName);
continue;
}
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
QString line;
QTextStream in(&file);
@ -161,7 +203,7 @@ QStringList Window::findFiles(const QStringList &files, const QString &text)
if (progressDialog.wasCanceled())
break;
line = in.readLine();
if (line.contains(text)) {
if (line.contains(text, Qt::CaseInsensitive)) {
foundFiles << files[i];
break;
}
@ -176,13 +218,18 @@ QStringList Window::findFiles(const QStringList &files, const QString &text)
void Window::showFiles(const QStringList &files)
{
for (int i = 0; i < files.size(); ++i) {
QFile file(currentDir.absoluteFilePath(files[i]));
qint64 size = QFileInfo(file).size();
QTableWidgetItem *fileNameItem = new QTableWidgetItem(files[i]);
const QString &fileName = files.at(i);
const QString toolTip = QDir::toNativeSeparators(fileName);
const QString relativePath = QDir::toNativeSeparators(currentDir.relativeFilePath(fileName));
const qint64 size = QFileInfo(fileName).size();
QTableWidgetItem *fileNameItem = new QTableWidgetItem(relativePath);
fileNameItem->setData(absoluteFileNameRole, QVariant(fileName));
fileNameItem->setToolTip(toolTip);
fileNameItem->setFlags(fileNameItem->flags() ^ Qt::ItemIsEditable);
QTableWidgetItem *sizeItem = new QTableWidgetItem(tr("%1 KB")
.arg(int((size + 1023) / 1024)));
sizeItem->setData(absoluteFileNameRole, QVariant(fileName));
sizeItem->setToolTip(toolTip);
sizeItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
sizeItem->setFlags(sizeItem->flags() ^ Qt::ItemIsEditable);
@ -191,8 +238,7 @@ void Window::showFiles(const QStringList &files)
filesTable->setItem(row, 0, fileNameItem);
filesTable->setItem(row, 1, sizeItem);
}
filesFoundLabel->setText(tr("%1 file(s) found").arg(files.size()) +
(" (Double click on a file to open it)"));
filesFoundLabel->setText(tr("%n file(s) found (Double click on a file to open it)", 0, files.size()));
filesFoundLabel->setWordWrap(true);
}
//! [8]
@ -220,20 +266,43 @@ void Window::createFilesTable()
filesTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
filesTable->verticalHeader()->hide();
filesTable->setShowGrid(false);
//! [15]
filesTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(filesTable, &QTableWidget::customContextMenuRequested,
this, &Window::contextMenu);
connect(filesTable, &QTableWidget::cellActivated,
this, &Window::openFileOfItem);
//! [15]
}
//! [11]
//! [12]
void Window::openFileOfItem(int row, int /* column */)
{
QTableWidgetItem *item = filesTable->item(row, 0);
QDesktopServices::openUrl(QUrl::fromLocalFile(currentDir.absoluteFilePath(item->text())));
const QTableWidgetItem *item = filesTable->item(row, 0);
openFile(fileNameOfItem(item));
}
//! [12]
//! [16]
void Window::contextMenu(const QPoint &pos)
{
const QTableWidgetItem *item = filesTable->itemAt(pos);
if (!item)
return;
QMenu menu;
QAction *copyAction = menu.addAction("Copy Name");
QAction *openAction = menu.addAction("Open");
QAction *action = menu.exec(filesTable->mapToGlobal(pos));
if (!action)
return;
const QString fileName = fileNameOfItem(item);
if (action == copyAction)
QGuiApplication::clipboard()->setText(QDir::toNativeSeparators(fileName));
else if (action == openAction)
openFile(fileName);
}
//! [16]

View File

@ -63,7 +63,9 @@ public:
private slots:
void browse();
void find();
void animateFindClick();
void openFileOfItem(int row, int column);
void contextMenu(const QPoint &pos);
private:
QStringList findFiles(const QStringList &files, const QString &text);
@ -74,11 +76,7 @@ private:
QComboBox *fileComboBox;
QComboBox *textComboBox;
QComboBox *directoryComboBox;
QLabel *fileLabel;
QLabel *textLabel;
QLabel *directoryLabel;
QLabel *filesFoundLabel;
QPushButton *browseButton;
QPushButton *findButton;
QTableWidget *filesTable;

View File

@ -120,10 +120,12 @@
\snippet dialogs/findfiles/window.cpp 4
We use the directory's path to create a QDir; the QDir class
provides access to directory structures and their contents. We
create a list of the files (contained in the newly created QDir)
that match the specified file name. If the file name is empty
the list will contain all the files in the directory.
provides access to directory structures and their contents.
\snippet dialogs/findfiles/window.cpp 13
We recursively create a list of the files (contained in the newl
created QDir) that match the specified file name.
Then we search through all the files in the list, using the private
\c findFiles() function, eliminating the ones that don't contain
@ -173,9 +175,7 @@
\snippet dialogs/findfiles/window.cpp 7
After updating the QProgressDialog, we create a QFile using the
QDir::absoluteFilePath() function which returns the absolute path
name of a file in the directory. We open the file in read-only
After updating the QProgressDialog, we open the file in read-only
mode, and read one line at a time using QTextStream.
The QTextStream class provides a convenient interface for reading
@ -194,9 +194,18 @@
Both the \c findFiles() and \c showFiles() functions are called from
the \c find() slot. In the \c showFiles() function we run through
the provided list of file names, adding each file name to the
the provided list of file names, adding each relative file name to the
first column in the table widget and retrieving the file's size using
QFile and QFileInfo for the second column.
QFileInfo for the second column. For later use, we set
the absolute path as a data on the QTableWidget using the
the role absoluteFileNameRole defined to be Qt::UserRole + 1.
\snippet dialogs/findfiles/window.cpp 17
This allows for retrieving the name of an item using a
convenience function:
\snippet dialogs/findfiles/window.cpp 18
We also update the total number of files found.
@ -236,8 +245,19 @@
\snippet dialogs/findfiles/window.cpp 12
\snippet dialogs/findfiles/window.cpp 14
The \c openFileOfItem() slot is invoked when the user double
clicks on a cell in the table. The QDesktopServices::openUrl()
knows how to open a file given the file name.
\snippet dialogs/findfiles/window.cpp 15
\snippet dialogs/findfiles/window.cpp 16
We set the context menu policy to of the table view to Qt::CustomContextMenu
and connect a slot contextMenu() to its signal
customContextMenuRequested(). We retrieve the absolute file name
from the data of the QTableWidgetItem and populate the context menu
with actions offering to copy the file name and to open the file.
*/