1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2004 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing a dialog to search for files.
8"""
9
10import os
11import sys
12
13from PyQt5.QtCore import pyqtSignal, pyqtSlot
14from PyQt5.QtWidgets import (
15    QWidget, QHeaderView, QApplication, QDialogButtonBox, QTreeWidgetItem
16)
17
18from E5Gui.E5PathPicker import E5PathPickerModes
19
20from .Ui_FindFileNameDialog import Ui_FindFileNameDialog
21
22from Utilities import direntries
23import Utilities
24
25
26class FindFileNameDialog(QWidget, Ui_FindFileNameDialog):
27    """
28    Class implementing a dialog to search for files.
29
30    The occurrences found are displayed in a QTreeWidget showing the
31    filename and the pathname. The file will be opened upon a double click
32    onto the respective entry of the list.
33
34    @signal sourceFile(str) emitted to open a file in the editor
35    @signal designerFile(str) emitted to open a Qt-Designer file
36    """
37    sourceFile = pyqtSignal(str)
38    designerFile = pyqtSignal(str)
39
40    def __init__(self, project, parent=None):
41        """
42        Constructor
43
44        @param project reference to the project object
45        @param parent parent widget of this dialog (QWidget)
46        """
47        super().__init__(parent)
48        self.setupUi(self)
49
50        self.searchDirPicker.setMode(E5PathPickerModes.DirectoryMode)
51
52        self.fileList.headerItem().setText(self.fileList.columnCount(), "")
53
54        self.stopButton = self.buttonBox.addButton(
55            self.tr("Stop"), QDialogButtonBox.ButtonRole.ActionRole)
56        self.stopButton.setToolTip(self.tr("Press to stop the search"))
57        self.stopButton.setEnabled(False)
58        self.buttonBox.button(
59            QDialogButtonBox.StandardButton.Open).setToolTip(
60                self.tr("Opens the selected file"))
61        self.buttonBox.button(
62            QDialogButtonBox.StandardButton.Open).setEnabled(False)
63
64        self.project = project
65        self.extsepLabel.setText(os.extsep)
66
67        self.shouldStop = False
68
69    def on_buttonBox_clicked(self, button):
70        """
71        Private slot called by a button of the button box clicked.
72
73        @param button button that was clicked (QAbstractButton)
74        """
75        if button == self.stopButton:
76            self.shouldStop = True
77        elif button == self.buttonBox.button(
78            QDialogButtonBox.StandardButton.Open
79        ):
80            self.__openFile()
81
82    def __openFile(self, itm=None):
83        """
84        Private slot to open a file.
85
86        It emits the signal sourceFile or designerFile depending on the
87        file extension.
88
89        @param itm item to be opened (QTreeWidgetItem)
90        """
91        if itm is None:
92            itm = self.fileList.currentItem()
93        if itm is not None:
94            fileName = itm.text(0)
95            filePath = itm.text(1)
96
97            if fileName.endswith('.ui'):
98                self.designerFile.emit(os.path.join(filePath, fileName))
99            else:
100                self.sourceFile.emit(os.path.join(filePath, fileName))
101
102    def __searchFile(self):
103        """
104        Private slot to handle the search.
105        """
106        fileName = self.fileNameEdit.text()
107        if not fileName:
108            self.fileList.clear()
109            return
110        fileExt = self.fileExtEdit.text()
111        if not fileExt and Utilities.isWindowsPlatform():
112            self.fileList.clear()
113            return
114
115        patternFormat = fileExt and "{0}{1}{2}" or "{0}*{1}{2}"
116        fileNamePattern = patternFormat.format(
117            fileName, os.extsep, fileExt and fileExt or '*')
118
119        searchPaths = []
120        if (
121            self.searchDirCheckBox.isChecked() and
122            self.searchDirPicker.text() != ""
123        ):
124            searchPaths.append(self.searchDirPicker.text())
125        if self.projectCheckBox.isChecked():
126            searchPaths.append(self.project.ppath)
127        if self.syspathCheckBox.isChecked():
128            searchPaths.extend(sys.path)
129
130        found = False
131        self.fileList.clear()
132        locations = {}
133        self.shouldStop = False
134        self.stopButton.setEnabled(True)
135        QApplication.processEvents()
136
137        for path in searchPaths:
138            if os.path.isdir(path):
139                files = direntries(path, True, fileNamePattern,
140                                   False, self.checkStop)
141                if files:
142                    found = True
143                    for file in files:
144                        fp, fn = os.path.split(file)
145                        if fn in locations:
146                            if fp in locations[fn]:
147                                continue
148                            else:
149                                locations[fn].append(fp)
150                        else:
151                            locations[fn] = [fp]
152                        QTreeWidgetItem(self.fileList, [fn, fp])
153                    QApplication.processEvents()
154
155        del locations
156        self.stopButton.setEnabled(False)
157        self.fileList.header().resizeSections(
158            QHeaderView.ResizeMode.ResizeToContents)
159        self.fileList.header().setStretchLastSection(True)
160
161        if found:
162            self.fileList.setCurrentItem(self.fileList.topLevelItem(0))
163
164    def checkStop(self):
165        """
166        Public method to check, if the search should be stopped.
167
168        @return flag indicating the search should be stopped (boolean)
169        """
170        QApplication.processEvents()
171        return self.shouldStop
172
173    def on_fileNameEdit_textChanged(self, text):
174        """
175        Private slot to handle the textChanged signal of the file name edit.
176
177        @param text (ignored)
178        """
179        self.__searchFile()
180
181    def on_fileExtEdit_textChanged(self, text):
182        """
183        Private slot to handle the textChanged signal of the file extension
184        edit.
185
186        @param text (ignored)
187        """
188        self.__searchFile()
189
190    def on_searchDirPicker_textChanged(self, text):
191        """
192        Private slot to handle the textChanged signal of the search directory
193        edit.
194
195        @param text text of the search dir edit (string)
196        """
197        self.searchDirCheckBox.setEnabled(text != "")
198        if self.searchDirCheckBox.isChecked():
199            self.__searchFile()
200
201    def on_searchDirCheckBox_toggled(self, checked):
202        """
203        Private slot to handle the toggled signal of the search directory
204        checkbox.
205
206        @param checked flag indicating the state of the checkbox (boolean)
207        """
208        if self.searchDirPicker.text():
209            self.__searchFile()
210
211    def on_projectCheckBox_toggled(self, checked):
212        """
213        Private slot to handle the toggled signal of the project checkbox.
214
215        @param checked flag indicating the state of the checkbox (boolean)
216        """
217        self.__searchFile()
218
219    def on_syspathCheckBox_toggled(self, checked):
220        """
221        Private slot to handle the toggled signal of the sys.path checkbox.
222
223        @param checked flag indicating the state of the checkbox (boolean)
224        """
225        self.__searchFile()
226
227    def on_fileList_itemActivated(self, itm, column):
228        """
229        Private slot to handle the double click on a file item.
230
231        It emits the signal sourceFile or designerFile depending on the
232        file extension.
233
234        @param itm the double clicked listview item (QTreeWidgetItem)
235        @param column column that was double clicked (integer) (ignored)
236        """
237        self.__openFile(itm)
238
239    @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
240    def on_fileList_currentItemChanged(self, current, previous):
241        """
242        Private slot handling a change of the current item.
243
244        @param current current item (QTreeWidgetItem)
245        @param previous prevoius current item (QTreeWidgetItem)
246        """
247        self.buttonBox.button(QDialogButtonBox.StandardButton.Open).setEnabled(
248            current is not None)
249
250    def show(self):
251        """
252        Public method to enable/disable the project checkbox.
253        """
254        if self.project and self.project.isOpen():
255            self.projectCheckBox.setEnabled(True)
256            self.projectCheckBox.setChecked(True)
257        else:
258            self.projectCheckBox.setEnabled(False)
259            self.projectCheckBox.setChecked(False)
260
261        self.fileNameEdit.selectAll()
262        self.fileNameEdit.setFocus()
263
264        super().show()
265