# Find Files Example

The Find Files example shows how to use QProgressDialog to provide feedback on the progress of a slow operation. The example also shows how to use QFileDialog to facilitate browsing, how to use QTextStream to read a file, and how to use QTableWidget to provide standard table display facilities for applications.

With the Find Files application the user can search for files in a specified directory, matching a specified file name (using wild cards if appropiate) and containing a specified text.

The user is provided with a Browse option, and the result of the search is displayed in a table with the names of the files found and their sizes. In addition the application provides a total count of the files found.

## The FindFiles Class

The `FindFiles` class inherits QWidget, and is the main application widget. It shows the search options, and displays the search results. We first describe the API of the class and then review its methods. Data members are explained as we encounter them in the code.

We need two private slots: The `browse()` slot is called whenever the user wants to browse for a directory to search in, and the `find()` slot is called whenever the user requests a search to be performed by pressing the Find button.

In addition we declare several private methods: We use the `findFiles()` method to search for files matching the user's specifications, we call the `showFiles()` method to display the results, and we use `createButton()`, `createComboBox()` and `createFilesTable()` when we are constructing the widget.

In the constructor we first create the application's widgets.

            public FindFiles()
            {
                browseButton = createButton(tr("&Browse..."), "browse()");
                findButton = createButton(tr("&Find"), "find()");

                fileComboBox = createComboBox(tr("*"));
                textComboBox = createComboBox("");
                directoryComboBox = createComboBox(QDir.currentPath());

                fileLabel = new QLabel(tr("Named:"));
                textLabel = new QLabel(tr("Containing text:"));
                directoryLabel = new QLabel(tr("In directory:"));
                filesFoundLabel = new QLabel();

                createFilesTable();

We create the application's buttons using the private `createButton()` method. Then we create the comboboxes associated with the search specifications, using the private `createComboBox()` method. We also create the application's labels before we use the private `createFilesTable()` method to create the table displaying the search results.

                QHBoxLayout buttonsLayout = new QHBoxLayout();
                buttonsLayout.addStretch();
                buttonsLayout.addWidget(findButton);

                QGridLayout mainLayout = new QGridLayout();
                mainLayout.addWidget(fileLabel, 0, 0);
                mainLayout.addWidget(fileComboBox, 0, 1, 1, 2);
                mainLayout.addWidget(textLabel, 1, 0);
                mainLayout.addWidget(textComboBox, 1, 1, 1, 2);
                mainLayout.addWidget(directoryLabel, 2, 0);
                mainLayout.addWidget(directoryComboBox, 2, 1);
                mainLayout.addWidget(browseButton, 2, 2);
                mainLayout.addWidget(filesTable, 3, 0, 1, 3);
                mainLayout.addWidget(filesFoundLabel, 4, 0);
                mainLayout.addLayout(buttonsLayout, 5, 0, 1, 3);
                setLayout(mainLayout);

                setWindowTitle(tr("Find Files"));
                resize(700, 300);
            }

Then we add all the widgets to a main layout using QGridLayout. We have, however, put the `Find` and `Quit` buttons and a stretchable space in a separate QHBoxLayout first, to make the buttons appear in the `Window` widget's bottom right corner.

            private void browse()
            {
                String directory = QFileDialog.getExistingDirectory(this,
                                           tr("Find Files"), QDir.currentPath());
                if (!directory.equals("")) {
                    directoryComboBox.addItem(directory);
                    directoryComboBox.setCurrentIndex(directoryComboBox.currentIndex() + 1);
                }
            }

The `browse()` slot presents a file dialog to the user, using the QFileDialog class. QFileDialog enables a user to traverse the file system in order to select one or many files or a directory. The easiest way to create a QFileDialog is to use the convenience static methods.

Here we use the static QFileDialog::getExistingDirectory() method which returns an existing directory selected by the user. Then we display the directory in the directory combobox using the QComboBox::addItem() method, and updates the current index.

QComboBox::addItem() adds an item to the combobox with the given text, and containing the specified user data. The item is appended to the list of existing items. The current index holds the index of the current item in the combobox. So in order to display the item we just added, we need to update the index as well.

            private void find()
            {
                filesTable.setRowCount(0);

                String fileName = fileComboBox.currentText();
                String text = textComboBox.currentText();
                String path = directoryComboBox.currentText();

The `find()` slot is called whenever the user requests a new search by pressing the Find button.

First we eliminate any previous search results by setting the table widgets row count to zero. Then we retrieve the specified file name, text and directory path from the respective comboboxes.

                QDir directory = new QDir(path);
                List<String> files = new LinkedList<String>();

                if (fileName.equals(""))
                    fileName = "*";

                List<String> entries = new LinkedList<String>();
                entries.add(fileName);

                files = directory.entryList(entries,
                    new QDir.Filters(QDir.Filter.Files, QDir.Filter.NoSymLinks));

                if (!text.equals(""))
                    files = findFiles(directory, files, text);
                showFiles(directory, files);
            }

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.

Then we search through all the files in the list, using the private `findFiles()` method, eliminating the ones that don't contain the specified text. And finally, we display the results using the private `showFiles()` method.

If the user didn't specify any text, there is no reason to search through the files, and we display the results immediately.

            private List<String> findFiles(QDir directory, List<String> files,
                                           String text)
            {
                QProgressDialog progressDialog = new QProgressDialog(this);
                progressDialog.setCancelButtonText(tr("&Cancel"));
                progressDialog.setRange(0, files.size());
                progressDialog.setWindowTitle(tr("Find Files"));

In the private `findFiles()` method we search through a list of files, looking for the ones that contain a specified text. This can be a very slow operation depending on the number of files as well as their sizes. In case there are a large number of files, or there exists some large files on the list, we provide a QProgressDialog.

The QProgressDialog class provides feedback on the progress of a slow operation. It is used to give the user an indication of how long an operation is going to take, and to demonstrate that the application has not frozen. It can also give the user an opportunity to abort the operation.

                List<String> foundFiles = new LinkedList<String>();

                for (int i = 0; i < files.size(); ++i) {
                    progressDialog.setValue(i);
                    progressDialog.setLabelText(tr("Searching file number "+i+
                                                   " of "+files.size()+"..."));
                    QApplication.processEvents();

                    if (progressDialog.wasCanceled())
                        break;

We run through the files, one at a time, and for each file we update the QProgressDialog value. This property holds the current amount of progress made. We also update the progress dialog's label.

Then we call the QCoreApplication::processEvents() method using the QApplication object. In this way we interleave the display of the progress made with the process of searching through the files so the application doesn't appear to be frozen.

The QApplication class manages the GUI application's control flow and main settings. It contains the main event loop, where all events from the window system and other sources are processed and dispatched. QApplication inherits QCoreApplication. The QCoreApplication::processEvents() method processes all pending events according to the specified QEventLoop::ProcessEventFlags until there are no more events to process. The default flags are QEventLoop::AllEvents.

                    QFile file = new QFile(directory.absoluteFilePath(files.get(i)));

                    if (file.open(QIODevice.OpenModeFlag.ReadOnly)) {
                        String line = "";
                        QTextStream in = new QTextStream(file);
                        while (!in.atEnd()) {
                            if (progressDialog.wasCanceled())
                                break;
                            line = in.readLine();
                            if (line.contains(text)) {
                                foundFiles.add(files.get(i));
                                break;
                            }
                        }
                    }
                }
                return foundFiles;
            }

After updating the QProgressDialog, we create a QFile using the QDir::absoluteFilePath() method 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.

For each line we read we check if the QProgressDialog has been canceled. If it has, we abort the operation, otherwise we check if the line contains the specified text. When we find the text within one of the files, we add the file's name to a list of found files that contain the specified text, and start searching a new file.

Finally, we return the list of the files found.

            private void showFiles(QDir directory, List<String> files)
            {
                for (int i = 0; i < files.size(); ++i) {
                    QFile file = new QFile(directory.absoluteFilePath(files.get(i)));
                    long size = new QFileInfo(file).size();

                    QTableWidgetItem fileNameItem = new QTableWidgetItem(files.get(i));
                    fileNameItem.setFlags(Qt.ItemFlag.ItemIsEnabled);
                    QTableWidgetItem sizeItem =
                        new QTableWidgetItem("" + ((size + 1023) / 1024) + tr("KB"));
                    sizeItem.setTextAlignment(new Qt.Alignment(Qt.AlignmentFlag.AlignRight,
                                                               Qt.AlignmentFlag.AlignVCenter).value());
                    sizeItem.setFlags(Qt.ItemFlag.ItemIsEnabled);

                    int row = filesTable.rowCount();
                    filesTable.insertRow(row);
                    filesTable.setItem(row, 0, fileNameItem);
                    filesTable.setItem(row, 1, sizeItem);
                }
                filesFoundLabel.setText("" + files.size() + tr("file(s) found."));
            }

Both the `findFiles()` and `showFiles()` methods are called from the `find()` slot. In the `showFiles()` method we run through the provided list of file names, adding each file name to the first column in the table widget and retrieving the file's size using QFile and QFileInfo for the second column.

We also update the total number of files found.

            private QPushButton createButton(String text, String goldMember)
            {
                QPushButton button = new QPushButton(text);
                button.clicked.connect(this, goldMember);

                return button;
            }

The private `createButton()` method is called from the constructor. We create a QPushButton with the provided text, connect it to the provided slot, and return a pointer to the button.

            private QComboBox createComboBox(String text)
            {
                QComboBox comboBox = new QComboBox();
                comboBox.setEditable(true);
                comboBox.addItem(text);
                comboBox.setSizePolicy(QSizePolicy.Policy.Expanding,
                                       QSizePolicy.Policy.Preferred);

                return comboBox;
            }

The private `createComboBox()` method is also called from the contructor. We create a QComboBox with the given text, and make it editable.

When the user enters a new string in an editable combobox, the widget may or may not insert it, and it can insert it in several locations, depending on the QComboBox::InsertPolicy. The default policy is is QComboBox::InsertAtBottom.

Then we add the provided text to the combobox, and specify the widget's size policies, before we return a pointer to the combobox.

            private void createFilesTable()
            {
                filesTable = new QTableWidget(0, 2);

                List<String> labels = new LinkedList<String>();
                labels.add(tr("File Name"));
                labels.add(tr("Size"));

                filesTable.setHorizontalHeaderLabels(labels);
                filesTable.horizontalHeader().setResizeMode(0, QHeaderView.ResizeMode.Stretch);
                filesTable.verticalHeader().hide();
                filesTable.setShowGrid(false);
            }

The private `createFilesTable()` method is called from the constructor. In this method we create the QTableWidget that will display the search results. We set its horizontal headers and their resize mode.

QTableWidget inherits QTableView which provides a default model/view implementation of a table view. The QTableView::horizontalHeader() method returns the table view's horizontal header as a QHeaderView. The QHeaderView class provides a header row or header column for item views, and the QHeaderView::setResizeMode() method sets the constraints on how the section in the header can be resized.

Finally, we hide the QTableWidget's vertical headers using the QWidget::hide() method, and remove the default grid drawn for the table using the QTableView::setShowGrid() method.