class FindInFilesDialog(QDialog): """find in files dialog implementation""" IN_PROJECT = 0 IN_DIRECTORY = 1 IN_OPEN_FILES = 2 def __init__(self, where=None, what=None, dirPath=None, params=None): QDialog.__init__(self, GlobalData().mainWindow) # If parans is not None it means it is a repeated search mainWindow = GlobalData().mainWindow self.editorsManager = mainWindow.editorsManagerWidget.editorsManager self.__cancelRequest = False self.__inProgress = False self.searchRegexp = None self.searchResults = [] # Avoid pylint complains self.findCombo = None self.caseCheckBox = None self.wordCheckBox = None self.regexpCheckBox = None self.projectRButton = None self.openFilesRButton = None self.dirRButton = None self.dirEditCombo = None self.dirSelectButton = None self.filterCombo = None self.fileLabel = None self.progressBar = None self.findButton = None self.__createLayout() self.__maxEntries = Settings()['maxSearchEntries'] if params is None: self.__newSearch = True self.setWindowTitle("Find in files") # Restore the combo box values # [ {'term': ., 'dir': ., 'filters': ., # 'cbCase': ., 'cbWord': ., 'cbRegexp': ., # 'rbProject': ., 'rbOpen': ., 'rbDir': .}, ... ] self.__history = getFindInFilesHistory() self.__populateHistory() self.findCombo.setEditText('') self.dirEditCombo.setEditText('') self.filterCombo.setEditText('') if where == self.IN_PROJECT: self.setSearchInProject(what) elif where == self.IN_DIRECTORY: self.setSearchInDirectory(what, dirPath) else: self.setSearchInOpenFiles(what) else: self.__newSearch = False self.setWindowTitle("Find in files: search again") self.__populateSearchAgain(params) def exec_(self): """Execute the dialog""" if not self.__newSearch: QTimer.singleShot(1, self.__process) QDialog.exec_(self) def __serialize(self): """Serializes the current search parameters""" termText = self.findCombo.currentText() filtText = self.__normalizeFilters(self.filterCombo.currentText()) dirText = '' if self.dirRButton.isChecked(): dirText = self.dirEditCombo.currentText().strip() return {'term': termText, 'dir': dirText, 'filters': filtText, 'cbCase': self.caseCheckBox.isChecked(), 'cbWord': self.wordCheckBox.isChecked(), 'cbRegexp': self.regexpCheckBox.isChecked(), 'rbProject': self.projectRButton.isChecked(), 'rbOpen': self.openFilesRButton.isChecked(), 'rbDir': self.dirRButton.isChecked()} def __deserialize(self, item): """Deserializes the history item""" self.findCombo.setEditText(item['term']) self.dirEditCombo.setEditText(item['dir']) self.filterCombo.setEditText(item['filters']) self.caseCheckBox.setChecked(item['cbCase']) self.wordCheckBox.setChecked(item['cbWord']) self.regexpCheckBox.setChecked(item['cbRegexp']) self.projectRButton.setChecked(item['rbProject']) self.openFilesRButton.setChecked(item['rbOpen']) self.dirRButton.setChecked(item['rbDir']) self.dirEditCombo.setEnabled(item['rbDir']) self.dirSelectButton.setEnabled(item['rbDir']) def __populateHistory(self): """Populates the search history in the combo boxes""" # No need to react to the change of the current index self.findCombo.currentIndexChanged[int].disconnect( self.__whatIndexChanged) index = 0 for props in self.__history: self.findCombo.addItem(props['term'], index) directory = props['dir'] if directory: self.dirEditCombo.addItem(directory) filt = props['filters'] if filt: self.filterCombo.addItem(filt) index += 1 # Restore the handler self.findCombo.currentIndexChanged[int].connect( self.__whatIndexChanged) def __createLayout(self): """Creates the dialog layout""" self.resize(600, 300) self.setSizeGripEnabled(True) verticalLayout = QVBoxLayout(self) gridLayout = QGridLayout() # Combo box for the text to search findLabel = QLabel(self) findLabel.setText("Find text:") self.findCombo = QComboBox(self) self.__tuneCombo(self.findCombo) self.findCombo.lineEdit().setToolTip( "Regular expression to search for") self.findCombo.editTextChanged.connect(self.__someTextChanged) self.findCombo.currentIndexChanged[int].connect( self.__whatIndexChanged) gridLayout.addWidget(findLabel, 0, 0, 1, 1) gridLayout.addWidget(self.findCombo, 0, 1, 1, 1) verticalLayout.addLayout(gridLayout) # Check boxes horizontalCBLayout = QHBoxLayout() self.caseCheckBox = QCheckBox(self) self.caseCheckBox.setText("Match &case") horizontalCBLayout.addWidget(self.caseCheckBox) self.wordCheckBox = QCheckBox(self) self.wordCheckBox.setText("Match whole &word") horizontalCBLayout.addWidget(self.wordCheckBox) self.regexpCheckBox = QCheckBox(self) self.regexpCheckBox.setText("Regular &expression") horizontalCBLayout.addWidget(self.regexpCheckBox) verticalLayout.addLayout(horizontalCBLayout) # Files groupbox filesGroupbox = QGroupBox(self) filesGroupbox.setTitle("Find in") sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( filesGroupbox.sizePolicy().hasHeightForWidth()) filesGroupbox.setSizePolicy(sizePolicy) gridLayoutFG = QGridLayout(filesGroupbox) self.projectRButton = QRadioButton(filesGroupbox) self.projectRButton.setText("&Project") gridLayoutFG.addWidget(self.projectRButton, 0, 0) self.projectRButton.clicked.connect(self.__projectClicked) self.openFilesRButton = QRadioButton(filesGroupbox) self.openFilesRButton.setText("&Opened files only") gridLayoutFG.addWidget(self.openFilesRButton, 1, 0) self.openFilesRButton.clicked.connect(self.__openFilesOnlyClicked) self.dirRButton = QRadioButton(filesGroupbox) self.dirRButton.setText("&Directory tree") gridLayoutFG.addWidget(self.dirRButton, 2, 0) self.dirRButton.clicked.connect(self.__dirClicked) self.dirEditCombo = QComboBox(filesGroupbox) self.__tuneCombo(self.dirEditCombo) self.dirEditCombo.lineEdit().setToolTip("Directory to search in") gridLayoutFG.addWidget(self.dirEditCombo, 2, 1) self.dirEditCombo.editTextChanged.connect(self.__someTextChanged) self.dirSelectButton = QPushButton(filesGroupbox) self.dirSelectButton.setText("...") gridLayoutFG.addWidget(self.dirSelectButton, 2, 2) self.dirSelectButton.clicked.connect(self.__selectDirClicked) filterLabel = QLabel(filesGroupbox) filterLabel.setText("Files filter:") gridLayoutFG.addWidget(filterLabel, 3, 0) self.filterCombo = QComboBox(filesGroupbox) self.__tuneCombo(self.filterCombo) self.filterCombo.lineEdit().setToolTip("File names regular expression") gridLayoutFG.addWidget(self.filterCombo, 3, 1) self.filterCombo.editTextChanged.connect(self.__someTextChanged) verticalLayout.addWidget(filesGroupbox) # File label self.fileLabel = FitPathLabel(parent=self) self.fileLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) verticalLayout.addWidget(self.fileLabel) # Progress bar self.progressBar = QProgressBar(self) self.progressBar.setValue(0) self.progressBar.setOrientation(Qt.Horizontal) verticalLayout.addWidget(self.progressBar) # Buttons at the bottom buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.findButton = buttonBox.addButton("Find", QDialogButtonBox.AcceptRole) self.findButton.setDefault(True) self.findButton.clicked.connect(self.__process) verticalLayout.addWidget(buttonBox) buttonBox.rejected.connect(self.__onClose) @staticmethod def __tuneCombo(comboBox): """Sets the common settings for a combo box""" sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( comboBox.sizePolicy().hasHeightForWidth()) comboBox.setSizePolicy(sizePolicy) comboBox.setEditable(True) comboBox.setInsertPolicy(QComboBox.InsertAtTop) comboBox.setCompleter(None) comboBox.setDuplicatesEnabled(False) def __onClose(self): """Triggered when the close button is clicked""" self.__cancelRequest = True if not self.__inProgress: self.close() def __historyIndexByWhat(self, what): """Provides the history index by 'what' value""" if what: for index in range(self.findCombo.count()): if self.findCombo.itemText(index) == what: return index, self.findCombo.itemData(index) return None, None @staticmethod def __indexByText(combo, text): """Provides the text entry index""" index = 0 for index in range(combo.count()): if combo.itemText(index) == text: return index index += 1 return -1 def setSearchInProject(self, what=None): """Set search ready for the whole project""" if not GlobalData().project.isLoaded(): # No project loaded, fallback to opened files self.setSearchInOpenFiles(what) return # Select the project radio button self.projectRButton.setEnabled(True) self.projectRButton.setChecked(True) self.dirEditCombo.setEnabled(False) self.dirSelectButton.setEnabled(False) openedFiles = self.editorsManager.getTextEditors() self.openFilesRButton.setEnabled(len(openedFiles) != 0) if what: # Pick up the history values if so comboIndex, historyIndex = self.__historyIndexByWhat(what) if historyIndex is not None: self.__deserialize(self.__history[historyIndex]) self.findCombo.setCurrentIndex(comboIndex) else: self.findCombo.setCurrentText(what) self.findCombo.lineEdit().selectAll() self.findCombo.setFocus() # Check searchability self.__testSearchability() def setSearchInOpenFiles(self, what=None): """Sets search ready for the opened files""" openedFiles = self.editorsManager.getTextEditors() if not openedFiles: # No opened files, fallback to search in dir self.setSearchInDirectory(what, None) return # Select the radio buttons self.projectRButton.setEnabled(GlobalData().project.isLoaded()) self.openFilesRButton.setEnabled(True) self.openFilesRButton.setChecked(True) self.dirEditCombo.setEnabled(False) self.dirSelectButton.setEnabled(False) if what: # Pick up the history values if so comboIndex, historyIndex = self.__historyIndexByWhat(what) if historyIndex is not None: self.__deserialize(self.__history[historyIndex]) self.findCombo.setCurrentIndex(comboIndex) else: self.findCombo.setCurrentText(what) self.findCombo.lineEdit().selectAll() self.findCombo.setFocus() # Check searchability self.__testSearchability() def setSearchInDirectory(self, what=None, dirPath=None): """Sets search ready for the given directory""" # Select radio buttons self.projectRButton.setEnabled(GlobalData().project.isLoaded()) openedFiles = self.editorsManager.getTextEditors() self.openFilesRButton.setEnabled(len(openedFiles) != 0) self.dirRButton.setEnabled(True) self.dirRButton.setChecked(True) self.dirEditCombo.setEnabled(True) self.dirSelectButton.setEnabled(True) if what: # Pick up the history values if so comboIndex, historyIndex = self.__historyIndexByWhat(what) if historyIndex is not None: self.__deserialize(self.__history[historyIndex]) self.findCombo.setCurrentIndex(comboIndex) else: self.findCombo.setCurrentText(what) self.findCombo.lineEdit().selectAll() if dirPath: self.dirEditCombo.setEditText(dirPath) self.findCombo.setFocus() # Check searchability self.__testSearchability() def getParameters(self): """Provides a dictionary with the search parameters""" parameters = {'term': self.findCombo.currentText(), 'case': self.caseCheckBox.isChecked(), 'whole': self.wordCheckBox.isChecked(), 'regexp': self.regexpCheckBox.isChecked()} if self.projectRButton.isChecked(): parameters['in-project'] = True parameters['in-opened'] = False parameters['in-dir'] = '' elif self.openFilesRButton.isChecked(): parameters['in-project'] = False parameters['in-opened'] = True parameters['in-dir'] = '' else: parameters['in-project'] = False parameters['in-opened'] = False parameters['in-dir'] = realpath( self.dirEditCombo.currentText().strip()) parameters['file-filter'] = self.filterCombo.currentText().strip() return parameters def __populateSearchAgain(self, params): """Populates the search parameters to do the same search""" self.findCombo.setEditText(params['term']) self.findCombo.setEnabled(False) self.caseCheckBox.setChecked(params['case']) self.caseCheckBox.setEnabled(False) self.wordCheckBox.setChecked(params['whole']) self.wordCheckBox.setEnabled(False) self.regexpCheckBox.setChecked(params['regexp']) self.regexpCheckBox.setEnabled(False) if params['in-project']: self.projectRButton.setChecked(True) elif params['in-opened']: self.openFilesRButton.setChecked(True) else: self.dirRButton.setChecked(True) self.projectRButton.setEnabled(False) self.openFilesRButton.setEnabled(False) self.dirRButton.setEnabled(False) self.dirEditCombo.setEditText(params['in-dir']) self.dirEditCombo.setEnabled(False) self.dirSelectButton.setEnabled(False) self.filterCombo.setEditText(params['file-filter']) self.filterCombo.setEnabled(False) @staticmethod def __normalizeFilters(text): """Normalizes the filters string""" normParts = [] for part in text.strip().split(';'): part = part.strip() if part: normParts.append(part) return '; '.join(normParts) def __testSearchability(self): """Tests the searchability and sets the Find button status""" startTime = time.time() if self.findCombo.currentText().strip() == "": self.findButton.setEnabled(False) self.findButton.setToolTip("No text to search") return if self.dirRButton.isChecked(): dirname = self.dirEditCombo.currentText().strip() if dirname == "": self.findButton.setEnabled(False) self.findButton.setToolTip("No directory path") return if not isdir(dirname): self.findButton.setEnabled(False) self.findButton.setToolTip("Path is not a directory") return # Now we need to match file names if there is a filter filtersText = self.filterCombo.currentText().strip() if filtersText == "": self.findButton.setEnabled(True) self.findButton.setToolTip("Find in files") return # Need to check the files match try: filters = self.__compileFilters() except: self.findButton.setEnabled(False) self.findButton.setToolTip("Incorrect files " "filter regular expression") return matched = False tooLong = False if self.projectRButton.isChecked(): # Whole project for fname in GlobalData().project.filesList: if fname.endswith(sep): continue matched = self.__filterMatch(filters, fname) if matched: break # Check the time, it might took too long if time.time() - startTime > 0.1: tooLong = True break elif self.openFilesRButton.isChecked(): # Opened files openedFiles = self.editorsManager.getTextEditors() for record in openedFiles: matched = self.__filterMatch(filters, record[1]) if matched: break # Check the time, it might took too long if time.time() - startTime > 0.1: tooLong = True break else: # Search in the dir if not dirname.endswith(sep): dirname += sep matched, tooLong = self.__matchInDir(dirname, filters, startTime) if matched: self.findButton.setEnabled(True) self.findButton.setToolTip("Find in files") else: if tooLong: self.findButton.setEnabled(True) self.findButton.setToolTip("Find in files") else: self.findButton.setEnabled(False) self.findButton.setToolTip("No files matched to search in") @staticmethod def __matchInDir(path, filters, startTime): """Provides the 'match' and 'too long' statuses""" matched = False tooLong = False for item in listdir(path): if time.time() - startTime > 0.1: tooLong = True return matched, tooLong if isdir(path + item): dname = path + item + sep matched, tooLong = FindInFilesDialog.__matchInDir(dname, filters, startTime) if matched or tooLong: return matched, tooLong continue if FindInFilesDialog.__filterMatch(filters, path + item): matched = True return matched, tooLong return matched, tooLong def __projectClicked(self): """project radio button clicked""" self.dirEditCombo.setEnabled(False) self.dirSelectButton.setEnabled(False) self.__testSearchability() def __openFilesOnlyClicked(self): """open files only radio button clicked""" self.dirEditCombo.setEnabled(False) self.dirSelectButton.setEnabled(False) self.__testSearchability() def __dirClicked(self): """dir radio button clicked""" self.dirEditCombo.setEnabled(True) self.dirSelectButton.setEnabled(True) self.dirEditCombo.setFocus() self.__testSearchability() def __someTextChanged(self, text): """Text to search, filter or dir name has been changed""" del text # unused argument self.__testSearchability() def __whatIndexChanged(self, index): """Index in history has changed""" if index != -1: historyIndex = self.findCombo.itemData(index) if historyIndex is not None: self.__deserialize(self.__history[historyIndex]) self.__testSearchability() def __selectDirClicked(self): """The user selects a directory""" options = QFileDialog.Options() options |= QFileDialog.ShowDirsOnly | QFileDialog.DontUseNativeDialog dirName = QFileDialog.getExistingDirectory( self, 'Select directory to search in', self.dirEditCombo.currentText(), options) if dirName: self.dirEditCombo.setEditText(normpath(dirName)) self.__testSearchability() @staticmethod def __filterMatch(filters, fname): """True if the file should be taken into consideration""" if filters: for filt in filters: if filt.match(fname): return True return False return True def __projectFiles(self, filters): """Project files list respecting the mask""" mainWindow = GlobalData().mainWindow files = [] for fname in GlobalData().project.filesList: if fname.endswith(sep): continue if self.__filterMatch(filters, fname): widget = mainWindow.getWidgetForFileName(fname) if widget is None: # Do not check for broken symlinks if isFileSearchable(fname, False): files.append(ItemToSearchIn(fname, "")) else: if widget.getType() in \ [MainWindowTabWidgetBase.PlainTextEditor]: files.append(ItemToSearchIn(fname, widget.getUUID())) QApplication.processEvents() if self.__cancelRequest: raise Exception("Cancel request") return files def __openedFiles(self, filters): """Currently opened editor buffers""" files = [] openedFiles = self.editorsManager.getTextEditors() for record in openedFiles: uuid = record[0] fname = record[1] if self.__filterMatch(filters, fname): files.append(ItemToSearchIn(fname, uuid)) QApplication.processEvents() if self.__cancelRequest: raise Exception("Cancel request") return files def __dirFiles(self, path, filters, files): """Files recursively for the dir""" for item in listdir(path): QApplication.processEvents() if self.__cancelRequest: raise Exception("Cancel request") if isdir(path + item): if item in ['.svn', '.cvs', '.git', '.hg']: # It does not make sense to search in revision control dirs continue anotherDir, isLoop = resolveLink(path + item) if not isLoop: self.__dirFiles(anotherDir + sep, filters, files) continue if not isfile(path + item): continue realItem, isLoop = resolveLink(path + item) if isLoop: continue if self.__filterMatch(filters, realItem): found = False for itm in files: if itm.fileName == realItem: found = True break if not found: mainWindow = GlobalData().mainWindow widget = mainWindow.getWidgetForFileName(realItem) if widget is None: if isFileSearchable(realItem): files.append(ItemToSearchIn(realItem, "")) else: if widget.getType() in \ [MainWindowTabWidgetBase.PlainTextEditor]: files.append(ItemToSearchIn(realItem, widget.getUUID())) def __compileFilters(self): """Compiles the filters""" filtersText = self.filterCombo.currentText().strip() filtersRe = [] if filtersText != "": for filt in filtersText.split(';'): filtersRe.append(re.compile(filt.strip(), re.IGNORECASE)) return filtersRe def __buildFilesList(self): """Builds the list of files to search in""" filtersRe = self.__compileFilters() if self.projectRButton.isChecked(): return self.__projectFiles(filtersRe) if self.openFilesRButton.isChecked(): return self.__openedFiles(filtersRe) dirname = realpath(self.dirEditCombo.currentText().strip()) files = [] self.__dirFiles(dirname + sep, filtersRe, files) return files def __updateHistory(self): """Updates history if needed""" if not self.__newSearch: return # Add entries to the combo box if required historyItem = self.__serialize() _, historyIndex = self.__historyIndexByWhat( self.findCombo.currentText()) if historyIndex is not None: self.__history[historyIndex] = historyItem else: historyIndex = 0 self.__history.insert(0, historyItem) if len(self.__history) > self.__maxEntries: self.__history = self.__history[:self.__maxEntries] self.findCombo.clear() self.filterCombo.clear() self.dirEditCombo.clear() self.__populateHistory() self.findCombo.setCurrentIndex(historyIndex) self.findCombo.setCurrentText(historyItem['term']) fltValue = historyItem['filters'] if fltValue: index = self.__indexByText(self.filterCombo, fltValue) self.filterCombo.setCurrentIndex(index) self.filterCombo.setCurrentText(fltValue) dirValue = historyItem['dir'] if dirValue: index = self.__indexByText(self.dirEditCombo, dirValue) self.dirEditCombo.setCurrentIndex(index) self.dirEditCombo.setCurrentText(dirValue) # Save the combo values for further usage setFindInFilesHistory(self.__history) def __process(self): """Search process""" self.__updateHistory() self.__inProgress = True numberOfMatches = 0 self.searchResults = [] self.searchRegexp = None # Form the regexp to search regexpText = self.findCombo.currentText() if not self.regexpCheckBox.isChecked(): regexpText = re.escape(regexpText) if self.wordCheckBox.isChecked(): regexpText = "\\b%s\\b" % regexpText flags = re.UNICODE if not self.caseCheckBox.isChecked(): flags |= re.IGNORECASE try: self.searchRegexp = re.compile(regexpText, flags) except Exception as exc: logging.error("Invalid search expression: %s", str(exc)) self.close() return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.fileLabel.setPath('Building list of files to search in...') QApplication.processEvents() try: files = self.__buildFilesList() except Exception as exc: QApplication.restoreOverrideCursor() if 'Cancel request' not in str(exc): logging.error(str(exc)) self.close() return QApplication.restoreOverrideCursor() QApplication.processEvents() if not files: self.fileLabel.setPath('No files to search in') return self.progressBar.setRange(0, len(files)) index = 1 for item in files: if self.__cancelRequest: self.__inProgress = False self.close() return self.fileLabel.setPath('Matches: ' + str(numberOfMatches) + ' Processing: ' + item.fileName) item.search(self.searchRegexp) found = len(item.matches) if found > 0: numberOfMatches += found self.searchResults.append(item) self.progressBar.setValue(index) index += 1 QApplication.processEvents() if numberOfMatches > 0 or self.__newSearch == False: # If it is redo then close the dialog anyway self.close() else: msg = 'No matches in ' + str(len(files)) + ' file' if len(files) > 1: msg += 's' self.fileLabel.setPath(msg) self.__inProgress = False
class DocLinkAnchorDialog(QDialog): """Replace text input dialog""" def __init__(self, windowTitle, cmlDocComment, fileName, parent): QDialog.__init__(self, parent) # Name of a file from which the doc link is created/edited self.__fileName = fileName self.setWindowTitle(windowTitle + ' documentation link and/or anchor') self.__createLayout() self.__invalidInputColor = GlobalData().skin['invalidInputPaper'] self.__validInputColor = self.linkEdit.palette().color( self.linkEdit.backgroundRole()) if cmlDocComment is not None: self.__populate(cmlDocComment) def __createLayout(self): """Creates the dialog layout""" self.resize(450, 150) self.setSizeGripEnabled(True) verticalLayout = QVBoxLayout(self) gridLayout = QGridLayout() # Link gridLayout.addWidget(QLabel('Link', self), 0, 0, 1, 1) self.linkEdit = QLineEdit(self) self.linkEdit.setClearButtonEnabled(True) self.linkEdit.setToolTip( 'A link to a file or to an external web resource') gridLayout.addWidget(self.linkEdit, 0, 1, 1, 1) self.linkEdit.textChanged.connect(self.__validate) self.fileButton = QPushButton(self) self.fileButton.setText('...') self.fileButton.setToolTip('Select an existing or non existing file') gridLayout.addWidget(self.fileButton, 0, 2, 1, 1) self.fileButton.clicked.connect(self.__onSelectPath) self.createCheckBox = QCheckBox( 'Create a markdown file if does not exist', self) self.createCheckBox.setChecked(False) gridLayout.addWidget(self.createCheckBox, 1, 1, 1, 1) self.createCheckBox.stateChanged.connect(self.__validate) # Anchor gridLayout.addWidget(QLabel('Anchor', self), 2, 0, 1, 1) self.anchorEdit = QLineEdit(self) self.anchorEdit.setClearButtonEnabled(True) gridLayout.addWidget(self.anchorEdit, 2, 1, 1, 1) self.anchorEdit.textChanged.connect(self.__validate) # Title titleLabel = QLabel('Title', self) titleLabel.setAlignment(Qt.AlignTop) gridLayout.addWidget(titleLabel, 3, 0, 1, 1) self.titleEdit = QTextEdit() self.titleEdit.setTabChangesFocus(True) self.titleEdit.setAcceptRichText(False) self.titleEdit.setToolTip( 'If provided then will be displayed in the rectangle') gridLayout.addWidget(self.titleEdit, 3, 1, 1, 1) # Buttons at the bottom buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Ok) self.__OKButton = buttonBox.button(QDialogButtonBox.Ok) self.__OKButton.setDefault(True) buttonBox.accepted.connect(self.accept) buttonBox.rejected.connect(self.close) verticalLayout.addLayout(gridLayout) verticalLayout.addWidget(buttonBox) self.linkEdit.setFocus() def setTitle(self, txt): """Sets the title text to be edited""" self.titleEdit.setPlainText(txt) def title(self): """Provides the new title text""" return self.titleEdit.toPlainText() def needToCreate(self): return self.createCheckBox.isEnabled( ) and self.createCheckBox.isChecked() def __populate(self, cmlDocComment): """Populates the fields from the comment""" if cmlDocComment.link: self.linkEdit.setText(cmlDocComment.link) if cmlDocComment.anchor: self.anchorEdit.setText(cmlDocComment.anchor) if cmlDocComment.title: self.setTitle(cmlDocComment.getTitle()) def __onSelectPath(self): """Select file or directory""" options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog selectedPath = QFileDialog.getOpenFileName(self, 'Select documentation file', self.linkEdit.text(), options=options) if isinstance(selectedPath, tuple): selectedPath = selectedPath[0] if selectedPath: self.linkEdit.setText(os.path.normpath(selectedPath)) def __setLinkValid(self): """Sets the link edit valid""" self.linkEdit.setToolTip( 'A link to a file or to an external web resource') setLineEditBackground(self.linkEdit, self.__validInputColor, self.__validInputColor) def __setLinkInvalid(self, msg): """Sets the link edit invalid""" self.linkEdit.setToolTip(msg) setLineEditBackground(self.linkEdit, self.__invalidInputColor, self.__invalidInputColor) def __validateLink(self): """Validates the link field content""" txt = self.linkEdit.text().strip() if txt == '' or txt.startswith('http://') or txt.startswith( 'https://'): self.__setLinkValid() self.createCheckBox.setEnabled(False) return True if txt.endswith(os.path.sep): self.__setLinkInvalid('A link must be a file, not a directory') return False # Not a link; it is supposed to be a file or a creatable file # However the invalid values will also be acceptable self.createCheckBox.setEnabled(True) fromFile = None if self.__fileName: if os.path.isabs(self.__fileName): fromFile = self.__fileName fName, anchor, errMsg = preResolveLinkPath( txt, fromFile, self.createCheckBox.isChecked()) del anchor if fName: self.__setLinkValid() return True self.__setLinkInvalid(errMsg) return not self.createCheckBox.isChecked() def __setAnchorValid(self): """Sets the anchor edit valid""" self.anchorEdit.setToolTip( 'Anchor may not contain neither spaces nor tabs') setLineEditBackground(self.anchorEdit, self.__invalidInputColor, self.__validInputColor) def __setAnchorInvalid(self): """Sets the anchor edit invalid""" self.anchorEdit.setToolTip( 'Anchor is used to refer to it from the other files') setLineEditBackground(self.anchorEdit, self.__validInputColor, self.__validInputColor) def __validateAnchor(self): """Validates the anchor field""" txt = self.anchorEdit.text().strip() if ' ' in txt or '\t' in txt: self.__setAnchorValid() return False self.__setAnchorInvalid() return True def __validate(self, _=None): """Validates the input fields and sets the OK button enable""" self.__OKButton.setToolTip('') valid = self.__validateAnchor() and self.__validateLink() if valid: if not self.linkEdit.text().strip() and not self.anchorEdit.text( ).strip(): valid = False self.__OKButton.setToolTip( 'At least one of the items: link or anchor must be provided' ) self.__OKButton.setEnabled(valid) return valid