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" #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] //! [0]
Window::Window(QWidget *parent) Window::Window(QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
browseButton = new QPushButton(tr("&Browse..."), this); QPushButton *browseButton = new QPushButton(tr("&Browse..."), this);
connect(browseButton, &QAbstractButton::clicked, this, &Window::browse); connect(browseButton, &QAbstractButton::clicked, this, &Window::browse);
findButton = new QPushButton(tr("&Find"), this); findButton = new QPushButton(tr("&Find"), this);
connect(findButton, &QAbstractButton::clicked, this, &Window::find); connect(findButton, &QAbstractButton::clicked, this, &Window::find);
fileComboBox = createComboBox(tr("*")); fileComboBox = createComboBox(tr("*"));
connect(fileComboBox->lineEdit(), &QLineEdit::returnPressed,
this, &Window::animateFindClick);
textComboBox = createComboBox(); 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; filesFoundLabel = new QLabel;
createFilesTable(); createFilesTable();
//! [0] //! [0]
//! [1] //! [1]
QGridLayout *mainLayout = new QGridLayout; QGridLayout *mainLayout = new QGridLayout(this);
mainLayout->addWidget(fileLabel, 0, 0); mainLayout->addWidget(new QLabel(tr("Named:")), 0, 0);
mainLayout->addWidget(fileComboBox, 0, 1, 1, 2); 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(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(directoryComboBox, 2, 1);
mainLayout->addWidget(browseButton, 2, 2); mainLayout->addWidget(browseButton, 2, 2);
mainLayout->addWidget(filesTable, 3, 0, 1, 3); mainLayout->addWidget(filesTable, 3, 0, 1, 3);
mainLayout->addWidget(filesFoundLabel, 4, 0, 1, 2); mainLayout->addWidget(filesFoundLabel, 4, 0, 1, 2);
mainLayout->addWidget(findButton, 4, 2); mainLayout->addWidget(findButton, 4, 2);
setLayout(mainLayout);
setWindowTitle(tr("Find Files")); setWindowTitle(tr("Find Files"));
resize(700, 300); const QRect screenGeometry = QApplication::desktop()->screenGeometry(this);
resize(screenGeometry.width() / 2, screenGeometry.height() / 3);
} }
//! [1] //! [1]
//! [2] //! [2]
void Window::browse() void Window::browse()
{ {
QString directory = QFileDialog::getExistingDirectory(this, QString directory =
tr("Find Files"), QDir::currentPath()); QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Find Files"), QDir::currentPath()));
if (!directory.isEmpty()) { if (!directory.isEmpty()) {
if (directoryComboBox->findText(directory) == -1) if (directoryComboBox->findText(directory) == -1)
@ -102,14 +123,28 @@ static void updateComboBox(QComboBox *comboBox)
comboBox->addItem(comboBox->currentText()); 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] //! [3]
void Window::find() void Window::find()
{ {
filesTable->setRowCount(0); filesTable->setRowCount(0);
QString fileName = fileComboBox->currentText(); QString fileName = fileComboBox->currentText();
QString text = textComboBox->currentText(); QString text = textComboBox->currentText();
QString path = directoryComboBox->currentText(); QString path = QDir::cleanPath(directoryComboBox->currentText());
//! [3] //! [3]
updateComboBox(fileComboBox); updateComboBox(fileComboBox);
@ -117,19 +152,21 @@ void Window::find()
updateComboBox(directoryComboBox); updateComboBox(directoryComboBox);
//! [4] //! [4]
currentDir = QDir(path); currentDir = QDir(path);
QStringList files; QStringList files;
if (fileName.isEmpty()) findRecursion(path, fileName.isEmpty() ? QStringLiteral("*") : fileName, &files);
fileName = "*";
files = currentDir.entryList(QStringList(fileName),
QDir::Files | QDir::NoSymLinks);
if (!text.isEmpty()) if (!text.isEmpty())
files = findFiles(files, text); files = findFiles(files, text);
showFiles(files); showFiles(files);
} }
//! [4] //! [4]
void Window::animateFindClick()
{
findButton->animateClick();
}
//! [5] //! [5]
QStringList Window::findFiles(const QStringList &files, const QString &text) 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")); progressDialog.setWindowTitle(tr("Find Files"));
//! [5] //! [6] //! [5] //! [6]
QMimeDatabase mimeDatabase;
QStringList foundFiles; QStringList foundFiles;
for (int i = 0; i < files.size(); ++i) { for (int i = 0; i < files.size(); ++i) {
progressDialog.setValue(i); progressDialog.setValue(i);
progressDialog.setLabelText(tr("Searching file number %1 of %2...") progressDialog.setLabelText(tr("Searching file number %1 of %n...", 0, files.size()).arg(i));
.arg(i).arg(files.size())); QCoreApplication::processEvents();
qApp->processEvents();
//! [6] //! [6]
if (progressDialog.wasCanceled()) if (progressDialog.wasCanceled())
break; break;
//! [7] //! [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)) { if (file.open(QIODevice::ReadOnly)) {
QString line; QString line;
QTextStream in(&file); QTextStream in(&file);
@ -161,7 +203,7 @@ QStringList Window::findFiles(const QStringList &files, const QString &text)
if (progressDialog.wasCanceled()) if (progressDialog.wasCanceled())
break; break;
line = in.readLine(); line = in.readLine();
if (line.contains(text)) { if (line.contains(text, Qt::CaseInsensitive)) {
foundFiles << files[i]; foundFiles << files[i];
break; break;
} }
@ -176,13 +218,18 @@ QStringList Window::findFiles(const QStringList &files, const QString &text)
void Window::showFiles(const QStringList &files) void Window::showFiles(const QStringList &files)
{ {
for (int i = 0; i < files.size(); ++i) { for (int i = 0; i < files.size(); ++i) {
QFile file(currentDir.absoluteFilePath(files[i])); const QString &fileName = files.at(i);
qint64 size = QFileInfo(file).size(); const QString toolTip = QDir::toNativeSeparators(fileName);
const QString relativePath = QDir::toNativeSeparators(currentDir.relativeFilePath(fileName));
QTableWidgetItem *fileNameItem = new QTableWidgetItem(files[i]); 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); fileNameItem->setFlags(fileNameItem->flags() ^ Qt::ItemIsEditable);
QTableWidgetItem *sizeItem = new QTableWidgetItem(tr("%1 KB") QTableWidgetItem *sizeItem = new QTableWidgetItem(tr("%1 KB")
.arg(int((size + 1023) / 1024))); .arg(int((size + 1023) / 1024)));
sizeItem->setData(absoluteFileNameRole, QVariant(fileName));
sizeItem->setToolTip(toolTip);
sizeItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); sizeItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
sizeItem->setFlags(sizeItem->flags() ^ Qt::ItemIsEditable); sizeItem->setFlags(sizeItem->flags() ^ Qt::ItemIsEditable);
@ -191,8 +238,7 @@ void Window::showFiles(const QStringList &files)
filesTable->setItem(row, 0, fileNameItem); filesTable->setItem(row, 0, fileNameItem);
filesTable->setItem(row, 1, sizeItem); filesTable->setItem(row, 1, sizeItem);
} }
filesFoundLabel->setText(tr("%1 file(s) found").arg(files.size()) + filesFoundLabel->setText(tr("%n file(s) found (Double click on a file to open it)", 0, files.size()));
(" (Double click on a file to open it)"));
filesFoundLabel->setWordWrap(true); filesFoundLabel->setWordWrap(true);
} }
//! [8] //! [8]
@ -220,20 +266,43 @@ void Window::createFilesTable()
filesTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); filesTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
filesTable->verticalHeader()->hide(); filesTable->verticalHeader()->hide();
filesTable->setShowGrid(false); filesTable->setShowGrid(false);
//! [15]
filesTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(filesTable, &QTableWidget::customContextMenuRequested,
this, &Window::contextMenu);
connect(filesTable, &QTableWidget::cellActivated, connect(filesTable, &QTableWidget::cellActivated,
this, &Window::openFileOfItem); this, &Window::openFileOfItem);
//! [15]
} }
//! [11] //! [11]
//! [12] //! [12]
void Window::openFileOfItem(int row, int /* column */) void Window::openFileOfItem(int row, int /* column */)
{ {
QTableWidgetItem *item = filesTable->item(row, 0); const QTableWidgetItem *item = filesTable->item(row, 0);
openFile(fileNameOfItem(item));
QDesktopServices::openUrl(QUrl::fromLocalFile(currentDir.absoluteFilePath(item->text())));
} }
//! [12] //! [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: private slots:
void browse(); void browse();
void find(); void find();
void animateFindClick();
void openFileOfItem(int row, int column); void openFileOfItem(int row, int column);
void contextMenu(const QPoint &pos);
private: private:
QStringList findFiles(const QStringList &files, const QString &text); QStringList findFiles(const QStringList &files, const QString &text);
@ -74,11 +76,7 @@ private:
QComboBox *fileComboBox; QComboBox *fileComboBox;
QComboBox *textComboBox; QComboBox *textComboBox;
QComboBox *directoryComboBox; QComboBox *directoryComboBox;
QLabel *fileLabel;
QLabel *textLabel;
QLabel *directoryLabel;
QLabel *filesFoundLabel; QLabel *filesFoundLabel;
QPushButton *browseButton;
QPushButton *findButton; QPushButton *findButton;
QTableWidget *filesTable; QTableWidget *filesTable;

View File

@ -120,10 +120,12 @@
\snippet dialogs/findfiles/window.cpp 4 \snippet dialogs/findfiles/window.cpp 4
We use the directory's path to create a QDir; the QDir class We use the directory's path to create a QDir; the QDir class
provides access to directory structures and their contents. We provides access to directory structures and their contents.
create a list of the files (contained in the newly created QDir)
that match the specified file name. If the file name is empty \snippet dialogs/findfiles/window.cpp 13
the list will contain all the files in the directory.
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 Then we search through all the files in the list, using the private
\c findFiles() function, eliminating the ones that don't contain \c findFiles() function, eliminating the ones that don't contain
@ -173,9 +175,7 @@
\snippet dialogs/findfiles/window.cpp 7 \snippet dialogs/findfiles/window.cpp 7
After updating the QProgressDialog, we create a QFile using the After updating the QProgressDialog, we open the file in read-only
QDir::absoluteFilePath() function which returns the absolute path
name of a file in the directory. We open the file in read-only
mode, and read one line at a time using QTextStream. mode, and read one line at a time using QTextStream.
The QTextStream class provides a convenient interface for reading The QTextStream class provides a convenient interface for reading
@ -194,9 +194,18 @@
Both the \c findFiles() and \c showFiles() functions are called from Both the \c findFiles() and \c showFiles() functions are called from
the \c find() slot. In the \c showFiles() function we run through 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 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. We also update the total number of files found.
@ -236,8 +245,19 @@
\snippet dialogs/findfiles/window.cpp 12 \snippet dialogs/findfiles/window.cpp 12
\snippet dialogs/findfiles/window.cpp 14
The \c openFileOfItem() slot is invoked when the user double The \c openFileOfItem() slot is invoked when the user double
clicks on a cell in the table. The QDesktopServices::openUrl() clicks on a cell in the table. The QDesktopServices::openUrl()
knows how to open a file given the file name. 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.
*/ */