class FindOptions(QWidget): """Find widget with options""" REGEX_INVALID = "background-color:rgb(255, 175, 90);" find = Signal() stop = Signal() redirect_stdio = Signal(bool) def __init__(self, parent, search_text, search_text_regexp, search_path, exclude, exclude_idx, exclude_regexp, supported_encodings, in_python_path, more_options, case_sensitive, external_path_history): QWidget.__init__(self, parent) if search_path is None: search_path = getcwd() self.path = '' self.project_path = None self.file_path = None self.external_path = None self.external_path_history = external_path_history if not isinstance(search_text, (list, tuple)): search_text = [search_text] if not isinstance(search_path, (list, tuple)): search_path = [search_path] if not isinstance(exclude, (list, tuple)): exclude = [exclude] if not isinstance(external_path_history, (list, tuple)): external_path_history = [external_path_history] self.supported_encodings = supported_encodings # Layout 1 hlayout1 = QHBoxLayout() self.search_text = PatternComboBox(self, search_text, _("Search pattern")) self.edit_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.setChecked(case_sensitive) self.edit_regexp.setCheckable(True) self.edit_regexp.setChecked(search_text_regexp) self.more_widgets = () self.more_options = create_toolbutton(self, toggled=self.toggle_more_options) self.more_options.setCheckable(True) self.more_options.setChecked(more_options) self.ok_button = create_toolbutton(self, text=_("Search"), icon=ima.icon('find'), triggered=lambda: self.find.emit(), tip=_("Start search"), text_beside_icon=True) self.ok_button.clicked.connect(self.update_combos) self.stop_button = create_toolbutton(self, text=_("Stop"), icon=ima.icon('editclear'), triggered=lambda: self.stop.emit(), tip=_("Stop search"), text_beside_icon=True) self.stop_button.setEnabled(False) for widget in [self.search_text, self.edit_regexp, self.case_button, self.ok_button, self.stop_button, self.more_options]: hlayout1.addWidget(widget) # Layout 2 hlayout2 = QHBoxLayout() self.exclude_pattern = PatternComboBox(self, exclude, _("Excluded filenames pattern")) if exclude_idx is not None and exclude_idx >= 0 \ and exclude_idx < self.exclude_pattern.count(): self.exclude_pattern.setCurrentIndex(exclude_idx) self.exclude_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.exclude_regexp.setCheckable(True) self.exclude_regexp.setChecked(exclude_regexp) exclude_label = QLabel(_("Exclude:")) exclude_label.setBuddy(self.exclude_pattern) for widget in [exclude_label, self.exclude_pattern, self.exclude_regexp]: hlayout2.addWidget(widget) # Layout 3 hlayout3 = QHBoxLayout() search_on_label = QLabel(_("Search in:")) self.path_selection_combo = PatternComboBox(self, exclude, _('Search directory')) self.path_selection_combo.setEditable(False) self.path_selection_contents = QListWidget(self.path_selection_combo) self.path_selection_contents.hide() self.path_selection_combo.setModel( self.path_selection_contents.model()) self.path_selection_contents.addItem(_("Current working directory")) item = self.path_selection_contents.item(0) item.setToolTip(_("Search in all files and " "directories present on the" "current Spyder path")) self.path_selection_contents.addItem(_("Project")) item = self.path_selection_contents.item(1) item.setToolTip(_("Search in all files and " "directories present on the" "current project path " "(If opened)")) item.setFlags(item.flags() & ~Qt.ItemIsEnabled) self.path_selection_contents.addItem(_("File").replace('&', '')) item = self.path_selection_contents.item(2) item.setToolTip(_("Search in current opened file")) self.path_selection_contents.addItem(_("Select other directory")) item = self.path_selection_contents.item(3) item.setToolTip(_("Search in other folder present on the file system")) self.path_selection_combo.insertSeparator(3) self.path_selection_combo.insertSeparator(5) for path in external_path_history: item = ExternalPathItem(None, path) self.path_selection_contents.addItem(item) self.path_selection_combo.currentIndexChanged.connect( self.path_selection_changed) hlayout3.addWidget(search_on_label) hlayout3.addWidget(self.path_selection_combo) self.search_text.valid.connect(lambda valid: self.find.emit()) self.exclude_pattern.valid.connect(lambda valid: self.find.emit()) vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addLayout(hlayout1) vlayout.addLayout(hlayout2) vlayout.addLayout(hlayout3) self.more_widgets = (hlayout2,) self.toggle_more_options(more_options) self.setLayout(vlayout) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) @Slot(bool) def toggle_more_options(self, state): for layout in self.more_widgets: for index in range(layout.count()): if state and self.isVisible() or not state: layout.itemAt(index).widget().setVisible(state) if state: icon = ima.icon('options_less') tip = _('Hide advanced options') else: icon = ima.icon('options_more') tip = _('Show advanced options') self.more_options.setIcon(icon) self.more_options.setToolTip(tip) @Slot() def path_selection_changed(self): idx = self.path_selection_combo.currentIndex() if idx == EXTERNAL_PATH: external_path = self.select_directory() if len(external_path) > 0: item = ExternalPathItem(None, external_path) self.path_selection_contents.addItem(item) total_items = (self.path_selection_combo.count() - MAX_PATH_HISTORY) for i in range(6, total_items): self.path_selection_contents.takeItem(i) self.path_selection_combo.setCurrentIndex( self.path_selection_combo.count() - 1) else: self.path_selection_combo.setCurrentIndex(CWD) elif idx > EXTERNAL_PATH: item = self.path_selection_contents.item(idx) self.external_path = item.path def update_combos(self): self.search_text.lineEdit().returnPressed.emit() self.exclude_pattern.lineEdit().returnPressed.emit() def set_search_text(self, text): if text: self.search_text.add_text(text) self.search_text.lineEdit().selectAll() self.search_text.setFocus() def get_options(self, all=False): # Getting options self.search_text.lineEdit().setStyleSheet("") self.exclude_pattern.lineEdit().setStyleSheet("") utext = to_text_string(self.search_text.currentText()) if not utext: return try: texts = [(utext.encode('utf-8'), 'utf-8')] except UnicodeEncodeError: texts = [] for enc in self.supported_encodings: try: texts.append((utext.encode(enc), enc)) except UnicodeDecodeError: pass text_re = self.edit_regexp.isChecked() exclude = to_text_string(self.exclude_pattern.currentText()) exclude_re = self.exclude_regexp.isChecked() case_sensitive = self.case_button.isChecked() python_path = False if not case_sensitive: texts = [(text[0].lower(), text[1]) for text in texts] file_search = False selection_idx = self.path_selection_combo.currentIndex() if selection_idx == CWD: path = self.path elif selection_idx == PROJECT: path = self.project_path elif selection_idx == FILE_PATH: path = self.file_path file_search = True else: path = self.external_path # Finding text occurrences if not exclude_re: exclude = fnmatch.translate(exclude) else: try: exclude = re.compile(exclude) except Exception: exclude_edit = self.exclude_pattern.lineEdit() exclude_edit.setStyleSheet(self.REGEX_INVALID) return None if text_re: try: texts = [(re.compile(x[0]), x[1]) for x in texts] except Exception: self.search_text.lineEdit().setStyleSheet(self.REGEX_INVALID) return None if all: search_text = [to_text_string(self.search_text.itemText(index)) for index in range(self.search_text.count())] exclude = [to_text_string(self.exclude_pattern.itemText(index)) for index in range(self.exclude_pattern.count())] path_history = [to_text_string( self.path_selection_contents.item(index)) for index in range( 6, self.path_selection_combo.count())] exclude_idx = self.exclude_pattern.currentIndex() more_options = self.more_options.isChecked() return (search_text, text_re, [], exclude, exclude_idx, exclude_re, python_path, more_options, case_sensitive, path_history) else: return (path, file_search, exclude, texts, text_re, case_sensitive) @Slot() def select_directory(self): """Select directory""" self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), self.path) if directory: directory = to_text_string(osp.abspath(to_text_string(directory))) self.redirect_stdio.emit(True) return directory def set_directory(self, directory): self.path = to_text_string(osp.abspath(to_text_string(directory))) def set_project_path(self, path): self.project_path = to_text_string(osp.abspath(to_text_string(path))) item = self.path_selection_contents.item(PROJECT) item.setFlags(item.flags() | Qt.ItemIsEnabled) def disable_project_search(self): item = self.path_selection_contents.item(PROJECT) item.setFlags(item.flags() & ~Qt.ItemIsEnabled) self.project_path = None def set_file_path(self, path): self.file_path = path def keyPressEvent(self, event): """Reimplemented to handle key events""" ctrl = event.modifiers() & Qt.ControlModifier shift = event.modifiers() & Qt.ShiftModifier if event.key() in (Qt.Key_Enter, Qt.Key_Return): self.find.emit() elif event.key() == Qt.Key_F and ctrl and shift: # Toggle find widgets self.parent().toggle_visibility.emit(not self.isVisible()) else: QWidget.keyPressEvent(self, event)
class FindInFilesWidget(PluginMainWidget): """ Find in files widget. """ DEFAULT_OPTIONS = { 'case_sensitive': False, 'exclude_case_sensitive': False, 'exclude': EXCLUDE_PATTERNS[0], 'exclude_index': None, 'exclude_regexp': False, 'path_history': [], 'max_results': 1000, 'hist_limit': MAX_PATH_HISTORY, 'more_options': False, 'search_in_index': None, 'search_text': '', 'search_text_regexp': False, 'supported_encodings': ("utf-8", "iso-8859-1", "cp1252"), 'text_color': MAIN_TEXT_COLOR, } ENABLE_SPINNER = True REGEX_INVALID = "background-color:rgb(255, 80, 80);" REGEX_ERROR = _("Regular expression error") # Signals sig_edit_goto_requested = Signal(str, int, str, int, int) """ This signal will request to open a file in a given row and column using a code editor. Parameters ---------- path: str Path to file. row: int Cursor starting row position. word: str Word to select on given row. start_column: int Starting column of found word. end_column: Ending column of found word. """ sig_finished = Signal() """ This signal is emitted to inform the search process has finished. """ sig_max_results_reached = Signal() """ This signal is emitted to inform the search process has finished due to reaching the maximum number of results. """ def __init__(self, name=None, plugin=None, parent=None, options=DEFAULT_OPTIONS): super().__init__(name, plugin, parent=parent, options=options) # Attributes self.text_color = self.get_option('text_color') self.supported_encodings = self.get_option('supported_encodings') self.search_thread = None self.running = False self.more_options_action = None self.extras_toolbar = None search_text = self.get_option('search_text') path_history = self.get_option('path_history') exclude = self.get_option('exclude') if not isinstance(search_text, (list, tuple)): search_text = [search_text] if not isinstance(exclude, (list, tuple)): exclude = [exclude] if not isinstance(path_history, (list, tuple)): path_history = [path_history] # Widgets self.search_text_edit = PatternComboBox( self, search_text, _("Search pattern"), ) self.search_in_label = QLabel(_('Search in:')) self.exclude_label = QLabel(_('Exclude:')) self.path_selection_combo = SearchInComboBox(path_history, self) self.exclude_pattern_edit = PatternComboBox( self, exclude, _("Exclude pattern"), ) self.result_browser = ResultsBrowser( self, text_color=self.text_color, max_results=self.get_option('max_results'), ) # Setup self.exclude_label.setBuddy(self.exclude_pattern_edit) exclude_idx = self.get_option('exclude_index') if (exclude_idx is not None and exclude_idx >= 0 and exclude_idx < self.exclude_pattern_edit.count()): self.exclude_pattern_edit.setCurrentIndex(exclude_idx) search_in_index = self.get_option('search_in_index') self.path_selection_combo.set_current_searchpath_index(search_in_index) # Layout layout = QHBoxLayout() layout.addWidget(self.result_browser) self.setLayout(layout) # Signals self.path_selection_combo.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) self.search_text_edit.valid.connect(lambda valid: self.find()) self.exclude_pattern_edit.valid.connect(lambda valid: self.find()) self.result_browser.sig_edit_goto_requested.connect( self.sig_edit_goto_requested) self.result_browser.sig_max_results_reached.connect( self.sig_max_results_reached) self.result_browser.sig_max_results_reached.connect( self._stop_and_reset_thread) self.search_text_edit.sig_resized.connect(self._update_size) # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _("Find") def get_focus_widget(self): return self.search_text_edit def setup(self, options=DEFAULT_OPTIONS): self.search_regexp_action = self.create_action( FindInFilesWidgetActions.ToggleSearchRegex, text=_('Regular expression'), tip=_('Regular expression'), icon=self.create_icon('regex'), toggled=lambda val: self.set_option('search_text_regexp', val), initial=self.get_option('search_text_regexp'), ) self.case_action = self.create_action( FindInFilesWidgetActions.ToggleExcludeCase, text=_("Case sensitive"), tip=_("Case sensitive"), icon=self.create_icon("format_letter_case"), toggled=lambda val: self.set_option('case_sensitive', val), initial=self.get_option('case_sensitive'), ) self.find_action = self.create_action( FindInFilesWidgetActions.Find, icon_text=_('Search'), text=_("&Find in files"), tip=_("Search text in multiple files"), icon=self.create_icon('find'), triggered=self.find, register_shortcut=False, ) self.exclude_regexp_action = self.create_action( FindInFilesWidgetActions.ToggleExcludeRegex, text=_('Regular expression'), tip=_('Regular expression'), icon=self.create_icon('regex'), toggled=lambda val: self.set_option('exclude_regexp', val), initial=self.get_option('exclude_regexp'), ) self.exclude_case_action = self.create_action( FindInFilesWidgetActions.ToggleCase, text=_("Exclude case sensitive"), tip=_("Exclude case sensitive"), icon=self.create_icon("format_letter_case"), toggled=lambda val: self.set_option('exclude_case_sensitive', val), initial=self.get_option('exclude_case_sensitive'), ) self.more_options_action = self.create_action( FindInFilesWidgetActions.ToggleMoreOptions, text=_('Show advanced options'), tip=_('Show advanced options'), icon=self.create_icon("options_more"), toggled=lambda val: self.set_option('more_options', val), initial=self.get_option('more_options'), ) self.set_max_results_action = self.create_action( FindInFilesWidgetActions.MaxResults, text=_('Set maximum number of results'), tip=_('Set maximum number of results'), triggered=lambda x=None: self.set_max_results(), ) # Toolbar toolbar = self.get_main_toolbar() for item in [ self.search_text_edit, self.search_regexp_action, self.case_action, self.more_options_action, self.find_action ]: self.add_item_to_toolbar( item, toolbar=toolbar, section=FindInFilesWidgetMainToolbarSections.Main, ) # Exclude Toolbar self.extras_toolbar = self.create_toolbar( FindInFilesWidgetToolbars.Exclude) for item in [ self.exclude_label, self.exclude_pattern_edit, self.exclude_regexp_action, self.create_stretcher() ]: self.add_item_to_toolbar( item, toolbar=self.extras_toolbar, section=FindInFilesWidgetExcludeToolbarSections.Main, ) # Location toolbar location_toolbar = self.create_toolbar( FindInFilesWidgetToolbars.Location) for item in [self.search_in_label, self.path_selection_combo]: self.add_item_to_toolbar( item, toolbar=location_toolbar, section=FindInFilesWidgetLocationToolbarSections.Main, ) menu = self.get_options_menu() self.add_item_to_menu( self.set_max_results_action, menu=menu, ) def update_actions(self): stop_text = _('Stop') search_text = _('Search') if self.running: icon_text = stop_text icon = self.create_icon('stop') else: icon_text = search_text icon = self.create_icon('find') self.find_action.setIconText(icon_text) self.find_action.setIcon(icon) widget = self.get_main_toolbar().widgetForAction(self.find_action) if widget: w1 = widget.fontMetrics().width(stop_text) w2 = widget.fontMetrics().width(search_text) # Ensure the search/stop button has the same size independent on # the length of the words. width = (self.get_options_menu_button().width() + max([w1, w2]) + EXTRA_BUTTON_PADDING) widget.setMinimumWidth(width) if self.extras_toolbar and self.more_options_action: self.extras_toolbar.setVisible( self.more_options_action.isChecked()) def on_option_update(self, option, value): if option == 'more_options': self.exclude_pattern_edit.setMinimumWidth( self.search_text_edit.width()) if value: icon = self.create_icon('options_less') tip = _('Hide advanced options') else: icon = self.create_icon('options_more') tip = _('Show advanced options') if self.extras_toolbar: self.extras_toolbar.setVisible(value) if self.more_options_action: self.more_options_action.setIcon(icon) self.more_options_action.setToolTip(tip) elif option == 'max_results': self.result_browser.set_max_results(value) # --- Private API # ------------------------------------------------------------------------ def _update_size(self, size, old_size): self.exclude_pattern_edit.setMinimumWidth(size.width()) def _get_options(self): """ Get search options. """ text_re = self.search_regexp_action.isChecked() exclude_re = self.exclude_regexp_action.isChecked() case_sensitive = self.case_action.isChecked() # Clear fields self.search_text_edit.lineEdit().setStyleSheet("") self.exclude_pattern_edit.lineEdit().setStyleSheet("") self.exclude_pattern_edit.setToolTip("") self.search_text_edit.setToolTip("") utext = str(self.search_text_edit.currentText()) if not utext: return try: texts = [(utext.encode('utf-8'), 'utf-8')] except UnicodeEncodeError: texts = [] for enc in self.supported_encodings: try: texts.append((utext.encode(enc), enc)) except UnicodeDecodeError: pass exclude = str(self.exclude_pattern_edit.currentText()) if not case_sensitive: texts = [(text[0].lower(), text[1]) for text in texts] file_search = self.path_selection_combo.is_file_search() path = self.path_selection_combo.get_current_searchpath() if not exclude_re: items = [ fnmatch.translate(item.strip()) for item in exclude.split(",") if item.strip() != '' ] exclude = '|'.join(items) # Validate exclude regular expression if exclude: error_msg = regexp_error_msg(exclude) if error_msg: exclude_edit = self.exclude_pattern_edit.lineEdit() exclude_edit.setStyleSheet(self.REGEX_INVALID) tooltip = self.REGEX_ERROR + ': ' + str(error_msg) self.exclude_pattern_edit.setToolTip(tooltip) return None else: exclude = re.compile(exclude) # Validate text regular expression if text_re: error_msg = regexp_error_msg(texts[0][0]) if error_msg: self.search_text_edit.lineEdit().setStyleSheet( self.REGEX_INVALID) tooltip = self.REGEX_ERROR + ': ' + str(error_msg) self.search_text_edit.setToolTip(tooltip) return None else: texts = [(re.compile(x[0]), x[1]) for x in texts] return (path, file_search, exclude, texts, text_re, case_sensitive) def _update_options(self): """ Extract search options from widgets and set the corresponding option. """ hist_limit = self.get_option('hist_limit') search_texts = [ str(self.search_text_edit.itemText(index)) for index in range(self.search_text_edit.count()) ] excludes = [ str(self.search_text_edit.itemText(index)) for index in range(self.exclude_pattern_edit.count()) ] path_history = self.path_selection_combo.get_external_paths() self.set_option('path_history', path_history) self.set_option('search_text', search_texts[:hist_limit]) self.set_option('exclude', excludes[:hist_limit]) self.set_option('path_history', path_history[-hist_limit:]) self.set_option('exclude_index', self.exclude_pattern_edit.currentIndex()) self.set_option('search_in_index', self.path_selection_combo.currentIndex()) def _handle_search_complete(self, completed): """ Current search thread has finished. """ self.result_browser.set_sorting(ON) self.result_browser.expandAll() if self.search_thread is None: return self.sig_finished.emit() found = self.search_thread.get_results() self._stop_and_reset_thread() if found is not None: self.result_browser.show() self.stop_spinner() self.update_actions() def _stop_and_reset_thread(self, ignore_results=False): """Stop current search thread and clean-up.""" if self.search_thread is not None: if self.search_thread.isRunning(): if ignore_results: self.search_thread.sig_finished.disconnect( self.search_complete) self.search_thread.stop() self.search_thread.wait() self.search_thread.setParent(None) self.search_thread = None self.running = False self.stop_spinner() self.update_actions() # --- Public API # ------------------------------------------------------------------------ @property def path(self): """Return the current path.""" return self.path_selection_combo.path @property def project_path(self): """Return the current project path.""" return self.path_selection_combo.project_path @property def file_path(self): """Return the current file path.""" return self.path_selection_combo.file_path def set_directory(self, directory): """ Set directory as current path. Parameters ---------- directory: str Directory path string. """ self.path_selection_combo.path = osp.abspath(directory) def set_project_path(self, path): """ Set path as current project path. Parameters ---------- path: str Project path string. """ self.path_selection_combo.set_project_path(path) def disable_project_search(self): """Disable project search path in combobox.""" self.path_selection_combo.set_project_path(None) def set_file_path(self, path): """ Set path as current file path. Parameters ---------- path: str File path string. """ self.path_selection_combo.file_path = path def set_search_text(self, text): """ Set current search text. Parameters ---------- text: str Search string. Notes ----- If `text` is empty, focus will be given to the search lineedit and no search will be performed. """ if text: self.search_text_edit.add_text(text) self.search_text_edit.lineEdit().selectAll() self.search_text_edit.setFocus() def find(self): """ Start/stop find action. Notes ----- If there is no search running, this will start the search. If there is a search running, this will stop it. """ if self.running: self.stop() else: self.start() def stop(self): """Stop find thread.""" self._stop_and_reset_thread() def start(self): """Start find thread.""" options = self._get_options() if options is None: return self._stop_and_reset_thread(ignore_results=True) search_text = self.search_text_edit.currentText() # Update and set options self._update_options() # Start self.running = True self.start_spinner() self.search_thread = SearchThread(self, search_text, self.text_color) self.search_thread.sig_finished.connect(self._handle_search_complete) self.search_thread.sig_file_match.connect( self.result_browser.append_file_result) self.search_thread.sig_line_match.connect( self.result_browser.append_result) self.result_browser.clear_title(search_text) self.search_thread.initialize(*self._get_options()) self.search_thread.start() self.update_actions() def add_external_path(self, path): """ Parameters ---------- path: str Path to add to combobox. """ self.path_selection_combo.add_external_path(path) def set_max_results(self, value=None): """ Set maximum amount of results to add to the result browser. Parameters ---------- value: int, optional Number of results. If None an input dialog will be used. Default is None. """ if value is None: # Create dialog dialog = QInputDialog(self) # Set dialog properties dialog.setModal(False) dialog.setWindowTitle(self.get_name()) dialog.setLabelText(_('Set maximum number of results: ')) dialog.setInputMode(QInputDialog.IntInput) dialog.setIntRange(1, 10000) dialog.setIntStep(1) dialog.setIntValue(self.get_option('max_results')) # Connect slot dialog.intValueSelected.connect( lambda value: self.set_option('max_results', value)) dialog.show() else: self.set_option('max_results', value)
class FindOptions(QWidget): """Find widget with options""" REGEX_INVALID = "background-color:rgb(255, 80, 80);" REGEX_ERROR = _("Regular expression error") find = Signal() stop = Signal() redirect_stdio = Signal(bool) def __init__(self, parent, search_text, search_text_regexp, exclude, exclude_idx, exclude_regexp, supported_encodings, more_options, case_sensitive, external_path_history, options_button=None): QWidget.__init__(self, parent) if not isinstance(search_text, (list, tuple)): search_text = [search_text] if not isinstance(exclude, (list, tuple)): exclude = [exclude] if not isinstance(external_path_history, (list, tuple)): external_path_history = [external_path_history] self.supported_encodings = supported_encodings # Layout 1 hlayout1 = QHBoxLayout() self.search_text = PatternComboBox(self, search_text, _("Search pattern")) self.edit_regexp = create_toolbutton(self, icon=ima.icon('regex'), tip=_('Regular expression')) self.case_button = create_toolbutton(self, icon=ima.icon( "format_letter_case"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.setChecked(case_sensitive) self.edit_regexp.setCheckable(True) self.edit_regexp.setChecked(search_text_regexp) self.more_widgets = () self.more_options = create_toolbutton(self, toggled=self.toggle_more_options) self.more_options.setCheckable(True) self.more_options.setChecked(more_options) self.ok_button = create_toolbutton(self, text=_("Search"), icon=ima.icon('find'), triggered=lambda: self.find.emit(), tip=_("Start search"), text_beside_icon=True) self.ok_button.clicked.connect(self.update_combos) self.stop_button = create_toolbutton(self, text=_("Stop"), icon=ima.icon('stop'), triggered=lambda: self.stop.emit(), tip=_("Stop search"), text_beside_icon=True) self.stop_button.setEnabled(False) for widget in [self.search_text, self.edit_regexp, self.case_button, self.ok_button, self.stop_button, self.more_options]: hlayout1.addWidget(widget) if options_button: hlayout1.addWidget(options_button) # Layout 2 hlayout2 = QHBoxLayout() self.exclude_pattern = PatternComboBox(self, exclude, _("Exclude pattern")) if exclude_idx is not None and exclude_idx >= 0 \ and exclude_idx < self.exclude_pattern.count(): self.exclude_pattern.setCurrentIndex(exclude_idx) self.exclude_regexp = create_toolbutton(self, icon=ima.icon('regex'), tip=_('Regular expression')) self.exclude_regexp.setCheckable(True) self.exclude_regexp.setChecked(exclude_regexp) exclude_label = QLabel(_("Exclude:")) exclude_label.setBuddy(self.exclude_pattern) for widget in [exclude_label, self.exclude_pattern, self.exclude_regexp]: hlayout2.addWidget(widget) # Layout 3 hlayout3 = QHBoxLayout() search_on_label = QLabel(_("Search in:")) self.path_selection_combo = SearchInComboBox( external_path_history, parent) hlayout3.addWidget(search_on_label) hlayout3.addWidget(self.path_selection_combo) self.search_text.valid.connect(lambda valid: self.find.emit()) self.exclude_pattern.valid.connect(lambda valid: self.find.emit()) vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addLayout(hlayout1) vlayout.addLayout(hlayout2) vlayout.addLayout(hlayout3) self.more_widgets = (hlayout2,) self.toggle_more_options(more_options) self.setLayout(vlayout) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) @Slot(bool) def toggle_more_options(self, state): for layout in self.more_widgets: for index in range(layout.count()): if state and self.isVisible() or not state: layout.itemAt(index).widget().setVisible(state) if state: icon = ima.icon('options_less') tip = _('Hide advanced options') else: icon = ima.icon('options_more') tip = _('Show advanced options') self.more_options.setIcon(icon) self.more_options.setToolTip(tip) def update_combos(self): self.search_text.lineEdit().returnPressed.emit() self.exclude_pattern.lineEdit().returnPressed.emit() def set_search_text(self, text): if text: self.search_text.add_text(text) self.search_text.lineEdit().selectAll() self.search_text.setFocus() def get_options(self, to_save=False): """Get options""" text_re = self.edit_regexp.isChecked() exclude_re = self.exclude_regexp.isChecked() case_sensitive = self.case_button.isChecked() # Return current options for them to be saved when closing # Spyder. if to_save: search_text = [to_text_string(self.search_text.itemText(index)) for index in range(self.search_text.count())] exclude = [to_text_string(self.exclude_pattern.itemText(index)) for index in range(self.exclude_pattern.count())] exclude_idx = self.exclude_pattern.currentIndex() path_history = self.path_selection_combo.get_external_paths() more_options = self.more_options.isChecked() return (search_text, text_re, exclude, exclude_idx, exclude_re, more_options, case_sensitive, path_history) # Clear fields self.search_text.lineEdit().setStyleSheet("") self.exclude_pattern.lineEdit().setStyleSheet("") self.search_text.setToolTip("") self.exclude_pattern.setToolTip("") utext = to_text_string(self.search_text.currentText()) if not utext: return try: texts = [(utext.encode('utf-8'), 'utf-8')] except UnicodeEncodeError: texts = [] for enc in self.supported_encodings: try: texts.append((utext.encode(enc), enc)) except UnicodeDecodeError: pass exclude = to_text_string(self.exclude_pattern.currentText()) if not case_sensitive: texts = [(text[0].lower(), text[1]) for text in texts] file_search = self.path_selection_combo.is_file_search() path = self.path_selection_combo.get_current_searchpath() if not exclude_re: items = [fnmatch.translate(item.strip()) for item in exclude.split(",") if item.strip() != ''] exclude = '|'.join(items) # Validate exclude regular expression if exclude: error_msg = regexp_error_msg(exclude) if error_msg: exclude_edit = self.exclude_pattern.lineEdit() exclude_edit.setStyleSheet(self.REGEX_INVALID) tooltip = self.REGEX_ERROR + u': ' + to_text_string(error_msg) self.exclude_pattern.setToolTip(tooltip) return None else: exclude = re.compile(exclude) # Validate text regular expression if text_re: error_msg = regexp_error_msg(texts[0][0]) if error_msg: self.search_text.lineEdit().setStyleSheet(self.REGEX_INVALID) tooltip = self.REGEX_ERROR + u': ' + to_text_string(error_msg) self.search_text.setToolTip(tooltip) return None else: texts = [(re.compile(x[0]), x[1]) for x in texts] return (path, file_search, exclude, texts, text_re, case_sensitive) @property def path(self): return self.path_selection_combo.path def set_directory(self, directory): self.path_selection_combo.path = osp.abspath(directory) @property def project_path(self): return self.path_selection_combo.project_path def set_project_path(self, path): self.path_selection_combo.set_project_path(path) def disable_project_search(self): self.path_selection_combo.set_project_path(None) @property def file_path(self): return self.path_selection_combo.file_path def set_file_path(self, path): self.path_selection_combo.file_path = path def keyPressEvent(self, event): """Reimplemented to handle key events""" ctrl = event.modifiers() & Qt.ControlModifier shift = event.modifiers() & Qt.ShiftModifier if event.key() in (Qt.Key_Enter, Qt.Key_Return): self.find.emit() elif event.key() == Qt.Key_F and ctrl and shift: # Toggle find widgets self.parent().toggle_visibility.emit(not self.isVisible()) else: QWidget.keyPressEvent(self, event)
class FindOptions(QWidget): """Find widget with options""" REGEX_INVALID = "background-color:rgb(255, 175, 90);" find = Signal() stop = Signal() redirect_stdio = Signal(bool) def __init__(self, parent, search_text, search_text_regexp, search_path, exclude, exclude_idx, exclude_regexp, supported_encodings, in_python_path, more_options): QWidget.__init__(self, parent) if search_path is None: search_path = getcwd() self.path = '' self.project_path = None self.file_path = None if not isinstance(search_text, (list, tuple)): search_text = [search_text] if not isinstance(search_path, (list, tuple)): search_path = [search_path] if not isinstance(exclude, (list, tuple)): exclude = [exclude] self.supported_encodings = supported_encodings # Layout 1 hlayout1 = QHBoxLayout() self.search_text = PatternComboBox(self, search_text, _("Search pattern")) self.edit_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.edit_regexp.setCheckable(True) self.edit_regexp.setChecked(search_text_regexp) self.more_widgets = () self.more_options = create_toolbutton(self, toggled=self.toggle_more_options) self.more_options.setCheckable(True) self.more_options.setChecked(more_options) self.ok_button = create_toolbutton(self, text=_("Search"), icon=ima.icon('find'), triggered=lambda: self.find.emit(), tip=_("Start search"), text_beside_icon=True) self.ok_button.clicked.connect(self.update_combos) self.stop_button = create_toolbutton( self, text=_("Stop"), icon=ima.icon('editclear'), triggered=lambda: self.stop.emit(), tip=_("Stop search"), text_beside_icon=True) self.stop_button.setEnabled(False) for widget in [ self.search_text, self.edit_regexp, self.ok_button, self.stop_button, self.more_options ]: hlayout1.addWidget(widget) # Layout 2 hlayout2 = QHBoxLayout() self.exclude_pattern = PatternComboBox(self, exclude, _("Excluded filenames pattern")) if exclude_idx is not None and exclude_idx >= 0 \ and exclude_idx < self.exclude_pattern.count(): self.exclude_pattern.setCurrentIndex(exclude_idx) self.exclude_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.exclude_regexp.setCheckable(True) self.exclude_regexp.setChecked(exclude_regexp) exclude_label = QLabel(_("Exclude:")) exclude_label.setBuddy(self.exclude_pattern) for widget in [ exclude_label, self.exclude_pattern, self.exclude_regexp ]: hlayout2.addWidget(widget) # Layout 3 hlayout3 = QHBoxLayout() self.global_path_search = QRadioButton( _("Current working " "directory"), self) self.global_path_search.setChecked(True) self.global_path_search.setToolTip( _("Search in all files and " "directories present on the" "current Spyder path")) self.project_search = QRadioButton(_("Project"), self) self.project_search.setToolTip( _("Search in all files and " "directories present on the" "current project path (If opened)")) self.project_search.setEnabled(False) self.file_search = QRadioButton(_("File"), self) self.file_search.setToolTip(_("Search in current opened file")) for wid in [ self.global_path_search, self.project_search, self.file_search ]: hlayout3.addWidget(wid) hlayout3.addStretch(1) self.search_text.valid.connect(lambda valid: self.find.emit()) self.exclude_pattern.valid.connect(lambda valid: self.find.emit()) vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addLayout(hlayout1) vlayout.addLayout(hlayout2) vlayout.addLayout(hlayout3) self.more_widgets = (hlayout2, ) self.toggle_more_options(more_options) self.setLayout(vlayout) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) @Slot(bool) def toggle_more_options(self, state): for layout in self.more_widgets: for index in range(layout.count()): if state and self.isVisible() or not state: layout.itemAt(index).widget().setVisible(state) if state: icon = ima.icon('options_less') tip = _('Hide advanced options') else: icon = ima.icon('options_more') tip = _('Show advanced options') self.more_options.setIcon(icon) self.more_options.setToolTip(tip) def update_combos(self): self.search_text.lineEdit().returnPressed.emit() self.exclude_pattern.lineEdit().returnPressed.emit() def set_search_text(self, text): if text: self.search_text.add_text(text) self.search_text.lineEdit().selectAll() self.search_text.setFocus() def get_options(self, all=False): # Getting options self.search_text.lineEdit().setStyleSheet("") self.exclude_pattern.lineEdit().setStyleSheet("") utext = to_text_string(self.search_text.currentText()) if not utext: return try: texts = [(utext.encode('utf-8'), 'utf-8')] except UnicodeEncodeError: texts = [] for enc in self.supported_encodings: try: texts.append((utext.encode(enc), enc)) except UnicodeDecodeError: pass text_re = self.edit_regexp.isChecked() exclude = to_text_string(self.exclude_pattern.currentText()) exclude_re = self.exclude_regexp.isChecked() python_path = False global_path_search = self.global_path_search.isChecked() project_search = self.project_search.isChecked() file_search = self.file_search.isChecked() if global_path_search: path = self.path elif project_search: path = self.project_path else: path = self.file_path # Finding text occurrences if not exclude_re: exclude = fnmatch.translate(exclude) else: try: exclude = re.compile(exclude) except Exception: exclude_edit = self.exclude_pattern.lineEdit() exclude_edit.setStyleSheet(self.REGEX_INVALID) return None if text_re: try: texts = [(re.compile(x[0]), x[1]) for x in texts] except Exception: self.search_text.lineEdit().setStyleSheet(self.REGEX_INVALID) return None if all: search_text = [ to_text_string(self.search_text.itemText(index)) for index in range(self.search_text.count()) ] exclude = [ to_text_string(self.exclude_pattern.itemText(index)) for index in range(self.exclude_pattern.count()) ] exclude_idx = self.exclude_pattern.currentIndex() more_options = self.more_options.isChecked() return (search_text, text_re, [], exclude, exclude_idx, exclude_re, python_path, more_options) else: return (path, file_search, exclude, texts, text_re) @Slot() def select_directory(self): """Select directory""" self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), self.dir_combo.currentText()) if directory: self.set_directory(directory) self.redirect_stdio.emit(True) def set_directory(self, directory): self.path = to_text_string(osp.abspath(to_text_string(directory))) def set_project_path(self, path): self.project_path = to_text_string(osp.abspath(to_text_string(path))) self.project_search.setEnabled(True) def disable_project_search(self): self.project_search.setEnabled(False) self.project_search.setChecked(False) self.project_path = None def set_file_path(self, path): self.file_path = path def keyPressEvent(self, event): """Reimplemented to handle key events""" ctrl = event.modifiers() & Qt.ControlModifier shift = event.modifiers() & Qt.ShiftModifier if event.key() in (Qt.Key_Enter, Qt.Key_Return): self.find.emit() elif event.key() == Qt.Key_F and ctrl and shift: # Toggle find widgets self.parent().toggle_visibility.emit(not self.isVisible()) else: QWidget.keyPressEvent(self, event)
class FindOptions(QWidget): """Find widget with options""" REGEX_INVALID = "background-color:rgb(255, 175, 90);" find = Signal() stop = Signal() redirect_stdio = Signal(bool) def __init__(self, parent, search_text, search_text_regexp, search_path, exclude, exclude_idx, exclude_regexp, supported_encodings, in_python_path, more_options, case_sensitive, external_path_history, options_button=None): QWidget.__init__(self, parent) if search_path is None: search_path = getcwd_or_home() if not isinstance(search_text, (list, tuple)): search_text = [search_text] if not isinstance(search_path, (list, tuple)): search_path = [search_path] if not isinstance(exclude, (list, tuple)): exclude = [exclude] if not isinstance(external_path_history, (list, tuple)): external_path_history = [external_path_history] self.supported_encodings = supported_encodings # Layout 1 hlayout1 = QHBoxLayout() self.search_text = PatternComboBox(self, search_text, _("Search pattern")) self.edit_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.setChecked(case_sensitive) self.edit_regexp.setCheckable(True) self.edit_regexp.setChecked(search_text_regexp) self.more_widgets = () self.more_options = create_toolbutton(self, toggled=self.toggle_more_options) self.more_options.setCheckable(True) self.more_options.setChecked(more_options) self.ok_button = create_toolbutton(self, text=_("Search"), icon=ima.icon('find'), triggered=lambda: self.find.emit(), tip=_("Start search"), text_beside_icon=True) self.ok_button.clicked.connect(self.update_combos) self.stop_button = create_toolbutton(self, text=_("Stop"), icon=ima.icon('editclear'), triggered=lambda: self.stop.emit(), tip=_("Stop search"), text_beside_icon=True) self.stop_button.setEnabled(False) for widget in [self.search_text, self.edit_regexp, self.case_button, self.ok_button, self.stop_button, self.more_options]: hlayout1.addWidget(widget) if options_button: hlayout1.addWidget(options_button) # Layout 2 hlayout2 = QHBoxLayout() self.exclude_pattern = PatternComboBox(self, exclude, _("Excluded filenames pattern")) if exclude_idx is not None and exclude_idx >= 0 \ and exclude_idx < self.exclude_pattern.count(): self.exclude_pattern.setCurrentIndex(exclude_idx) self.exclude_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.exclude_regexp.setCheckable(True) self.exclude_regexp.setChecked(exclude_regexp) exclude_label = QLabel(_("Exclude:")) exclude_label.setBuddy(self.exclude_pattern) for widget in [exclude_label, self.exclude_pattern, self.exclude_regexp]: hlayout2.addWidget(widget) # Layout 3 hlayout3 = QHBoxLayout() search_on_label = QLabel(_("Search in:")) self.path_selection_combo = SearchInComboBox( external_path_history, parent) hlayout3.addWidget(search_on_label) hlayout3.addWidget(self.path_selection_combo) self.search_text.valid.connect(lambda valid: self.find.emit()) self.exclude_pattern.valid.connect(lambda valid: self.find.emit()) vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addLayout(hlayout1) vlayout.addLayout(hlayout2) vlayout.addLayout(hlayout3) self.more_widgets = (hlayout2,) self.toggle_more_options(more_options) self.setLayout(vlayout) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) @Slot(bool) def toggle_more_options(self, state): for layout in self.more_widgets: for index in range(layout.count()): if state and self.isVisible() or not state: layout.itemAt(index).widget().setVisible(state) if state: icon = ima.icon('options_less') tip = _('Hide advanced options') else: icon = ima.icon('options_more') tip = _('Show advanced options') self.more_options.setIcon(icon) self.more_options.setToolTip(tip) def update_combos(self): self.search_text.lineEdit().returnPressed.emit() self.exclude_pattern.lineEdit().returnPressed.emit() def set_search_text(self, text): if text: self.search_text.add_text(text) self.search_text.lineEdit().selectAll() self.search_text.setFocus() def get_options(self, all=False): # Getting options self.search_text.lineEdit().setStyleSheet("") self.exclude_pattern.lineEdit().setStyleSheet("") utext = to_text_string(self.search_text.currentText()) if not utext: return try: texts = [(utext.encode('utf-8'), 'utf-8')] except UnicodeEncodeError: texts = [] for enc in self.supported_encodings: try: texts.append((utext.encode(enc), enc)) except UnicodeDecodeError: pass text_re = self.edit_regexp.isChecked() exclude = to_text_string(self.exclude_pattern.currentText()) exclude_re = self.exclude_regexp.isChecked() case_sensitive = self.case_button.isChecked() python_path = False if not case_sensitive: texts = [(text[0].lower(), text[1]) for text in texts] file_search = self.path_selection_combo.is_file_search() path = self.path_selection_combo.get_current_searchpath() # Finding text occurrences if not exclude_re: exclude = fnmatch.translate(exclude) else: try: exclude = re.compile(exclude) except Exception: exclude_edit = self.exclude_pattern.lineEdit() exclude_edit.setStyleSheet(self.REGEX_INVALID) return None if text_re: try: texts = [(re.compile(x[0]), x[1]) for x in texts] except Exception: self.search_text.lineEdit().setStyleSheet(self.REGEX_INVALID) return None if all: search_text = [to_text_string(self.search_text.itemText(index)) for index in range(self.search_text.count())] exclude = [to_text_string(self.exclude_pattern.itemText(index)) for index in range(self.exclude_pattern.count())] path_history = self.path_selection_combo.get_external_paths() exclude_idx = self.exclude_pattern.currentIndex() more_options = self.more_options.isChecked() return (search_text, text_re, [], exclude, exclude_idx, exclude_re, python_path, more_options, case_sensitive, path_history) else: return (path, file_search, exclude, texts, text_re, case_sensitive) @property def path(self): return self.path_selection_combo.path def set_directory(self, directory): self.path_selection_combo.path = osp.abspath(directory) @property def project_path(self): return self.path_selection_combo.project_path def set_project_path(self, path): self.path_selection_combo.set_project_path(path) def disable_project_search(self): self.path_selection_combo.set_project_path(None) @property def file_path(self): return self.path_selection_combo.file_path def set_file_path(self, path): self.path_selection_combo.file_path = path def keyPressEvent(self, event): """Reimplemented to handle key events""" ctrl = event.modifiers() & Qt.ControlModifier shift = event.modifiers() & Qt.ShiftModifier if event.key() in (Qt.Key_Enter, Qt.Key_Return): self.find.emit() elif event.key() == Qt.Key_F and ctrl and shift: # Toggle find widgets self.parent().toggle_visibility.emit(not self.isVisible()) else: QWidget.keyPressEvent(self, event)
class FindOptions(QWidget): """Find widget with options""" REGEX_INVALID = "background-color:rgb(255, 175, 90);" find = Signal() stop = Signal() def __init__(self, parent, search_text, search_text_regexp, search_path, exclude, exclude_idx, exclude_regexp, supported_encodings, in_python_path, more_options, case_sensitive, external_path_history): QWidget.__init__(self, parent) if search_path is None: search_path = getcwd_or_home() self.path = '' self.project_path = None self.file_path = None self.external_path = None self.external_path_history = external_path_history if not isinstance(search_text, (list, tuple)): search_text = [search_text] if not isinstance(search_path, (list, tuple)): search_path = [search_path] if not isinstance(exclude, (list, tuple)): exclude = [exclude] if not isinstance(external_path_history, (list, tuple)): external_path_history = [external_path_history] self.supported_encodings = supported_encodings # Layout 1 hlayout1 = QHBoxLayout() self.search_text = PatternComboBox(self, search_text, _("Search pattern")) self.edit_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.setChecked(case_sensitive) self.edit_regexp.setCheckable(True) self.edit_regexp.setChecked(search_text_regexp) self.more_widgets = () self.more_options = create_toolbutton(self, toggled=self.toggle_more_options) self.more_options.setCheckable(True) self.more_options.setChecked(more_options) self.ok_button = create_toolbutton(self, text=_("Search"), icon=ima.icon('find'), triggered=lambda: self.find.emit(), tip=_("Start search"), text_beside_icon=True) self.ok_button.clicked.connect(self.update_combos) self.stop_button = create_toolbutton( self, text=_("Stop"), icon=ima.icon('editclear'), triggered=lambda: self.stop.emit(), tip=_("Stop search"), text_beside_icon=True) self.stop_button.setEnabled(False) for widget in [ self.search_text, self.edit_regexp, self.case_button, self.ok_button, self.stop_button, self.more_options ]: hlayout1.addWidget(widget) # Layout 2 hlayout2 = QHBoxLayout() self.exclude_pattern = PatternComboBox(self, exclude, _("Excluded filenames pattern")) if exclude_idx is not None and exclude_idx >= 0 \ and exclude_idx < self.exclude_pattern.count(): self.exclude_pattern.setCurrentIndex(exclude_idx) self.exclude_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.exclude_regexp.setCheckable(True) self.exclude_regexp.setChecked(exclude_regexp) exclude_label = QLabel(_("Exclude:")) exclude_label.setBuddy(self.exclude_pattern) for widget in [ exclude_label, self.exclude_pattern, self.exclude_regexp ]: hlayout2.addWidget(widget) # Layout 3 hlayout3 = QHBoxLayout() search_on_label = QLabel(_("Search in:")) self.path_selection_combo = PatternComboBox(self, exclude, _('Search directory')) self.path_selection_combo.setEditable(False) self.path_selection_contents = QListWidget(self.path_selection_combo) self.path_selection_contents.hide() self.path_selection_combo.setModel( self.path_selection_contents.model()) self.path_selection_contents.addItem(_("Current working directory")) item = self.path_selection_contents.item(0) item.setToolTip( _("Search in all files and " "directories present on the" "current Spyder path")) self.path_selection_contents.addItem(_("Project")) item = self.path_selection_contents.item(1) item.setToolTip( _("Search in all files and " "directories present on the" "current project path " "(If opened)")) item.setFlags(item.flags() & ~Qt.ItemIsEnabled) self.path_selection_contents.addItem(_("File").replace('&', '')) item = self.path_selection_contents.item(2) item.setToolTip(_("Search in current opened file")) self.path_selection_contents.addItem(_("Select other directory")) item = self.path_selection_contents.item(3) item.setToolTip(_("Search in other folder present on the file system")) self.path_selection_combo.insertSeparator(3) self.path_selection_combo.insertSeparator(5) for path in external_path_history: item = ExternalPathItem(None, path) self.path_selection_contents.addItem(item) self.path_selection_combo.currentIndexChanged.connect( self.path_selection_changed) hlayout3.addWidget(search_on_label) hlayout3.addWidget(self.path_selection_combo) self.search_text.valid.connect(lambda valid: self.find.emit()) self.exclude_pattern.valid.connect(lambda valid: self.find.emit()) vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addLayout(hlayout1) vlayout.addLayout(hlayout2) vlayout.addLayout(hlayout3) self.more_widgets = (hlayout2, ) self.toggle_more_options(more_options) self.setLayout(vlayout) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) @Slot(bool) def toggle_more_options(self, state): for layout in self.more_widgets: for index in range(layout.count()): if state and self.isVisible() or not state: layout.itemAt(index).widget().setVisible(state) if state: icon = ima.icon('options_less') tip = _('Hide advanced options') else: icon = ima.icon('options_more') tip = _('Show advanced options') self.more_options.setIcon(icon) self.more_options.setToolTip(tip) @Slot() def path_selection_changed(self): idx = self.path_selection_combo.currentIndex() if idx == EXTERNAL_PATH: external_path = self.select_directory() if len(external_path) > 0: item = ExternalPathItem(None, external_path) self.path_selection_contents.addItem(item) total_items = (self.path_selection_combo.count() - MAX_PATH_HISTORY) for i in range(6, total_items): self.path_selection_contents.takeItem(i) self.path_selection_combo.setCurrentIndex( self.path_selection_combo.count() - 1) else: self.path_selection_combo.setCurrentIndex(CWD) elif idx > EXTERNAL_PATH: item = self.path_selection_contents.item(idx) self.external_path = item.path def update_combos(self): self.search_text.lineEdit().returnPressed.emit() self.exclude_pattern.lineEdit().returnPressed.emit() def set_search_text(self, text): if text: self.search_text.add_text(text) self.search_text.lineEdit().selectAll() self.search_text.setFocus() def get_options(self, all=False): # Getting options self.search_text.lineEdit().setStyleSheet("") self.exclude_pattern.lineEdit().setStyleSheet("") utext = to_text_string(self.search_text.currentText()) if not utext: return try: texts = [(utext.encode('utf-8'), 'utf-8')] except UnicodeEncodeError: texts = [] for enc in self.supported_encodings: try: texts.append((utext.encode(enc), enc)) except UnicodeDecodeError: pass text_re = self.edit_regexp.isChecked() exclude = to_text_string(self.exclude_pattern.currentText()) exclude_re = self.exclude_regexp.isChecked() case_sensitive = self.case_button.isChecked() python_path = False if not case_sensitive: texts = [(text[0].lower(), text[1]) for text in texts] file_search = False selection_idx = self.path_selection_combo.currentIndex() if selection_idx == CWD: path = self.path elif selection_idx == PROJECT: path = self.project_path elif selection_idx == FILE_PATH: path = self.file_path file_search = True else: path = self.external_path # Finding text occurrences if not exclude_re: exclude = fnmatch.translate(exclude) else: try: exclude = re.compile(exclude) except Exception: exclude_edit = self.exclude_pattern.lineEdit() exclude_edit.setStyleSheet(self.REGEX_INVALID) return None if text_re: try: texts = [(re.compile(x[0]), x[1]) for x in texts] except Exception: self.search_text.lineEdit().setStyleSheet(self.REGEX_INVALID) return None if all: search_text = [ to_text_string(self.search_text.itemText(index)) for index in range(self.search_text.count()) ] exclude = [ to_text_string(self.exclude_pattern.itemText(index)) for index in range(self.exclude_pattern.count()) ] path_history = [ to_text_string(self.path_selection_contents.item(index)) for index in range(6, self.path_selection_combo.count()) ] exclude_idx = self.exclude_pattern.currentIndex() more_options = self.more_options.isChecked() return (search_text, text_re, [], exclude, exclude_idx, exclude_re, python_path, more_options, case_sensitive, path_history) else: return (path, file_search, exclude, texts, text_re, case_sensitive) @Slot() def select_directory(self): """Select directory""" self.parent().redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), self.path) if directory: directory = to_text_string(osp.abspath(to_text_string(directory))) self.parent().redirect_stdio.emit(True) return directory def set_directory(self, directory): self.path = to_text_string(osp.abspath(to_text_string(directory))) def set_project_path(self, path): self.project_path = to_text_string(osp.abspath(to_text_string(path))) item = self.path_selection_contents.item(PROJECT) item.setFlags(item.flags() | Qt.ItemIsEnabled) def disable_project_search(self): item = self.path_selection_contents.item(PROJECT) item.setFlags(item.flags() & ~Qt.ItemIsEnabled) self.project_path = None def set_file_path(self, path): self.file_path = path def keyPressEvent(self, event): """Reimplemented to handle key events""" ctrl = event.modifiers() & Qt.ControlModifier shift = event.modifiers() & Qt.ShiftModifier if event.key() in (Qt.Key_Enter, Qt.Key_Return): self.find.emit() elif event.key() == Qt.Key_F and ctrl and shift: # Toggle find widgets self.parent().toggle_visibility.emit(not self.isVisible()) else: QWidget.keyPressEvent(self, event)
class FindOptions(QWidget): """Find widget with options""" find = Signal() stop = Signal() redirect_stdio = Signal(bool) def __init__(self, parent, search_text, search_text_regexp, search_path, include, include_idx, include_regexp, exclude, exclude_idx, exclude_regexp, supported_encodings, in_python_path, more_options): QWidget.__init__(self, parent) if search_path is None: search_path = getcwd() if not isinstance(search_text, (list, tuple)): search_text = [search_text] if not isinstance(search_path, (list, tuple)): search_path = [search_path] if not isinstance(include, (list, tuple)): include = [include] if not isinstance(exclude, (list, tuple)): exclude = [exclude] self.supported_encodings = supported_encodings # Layout 1 hlayout1 = QHBoxLayout() self.search_text = PatternComboBox(self, search_text, _("Search pattern")) self.edit_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.edit_regexp.setCheckable(True) self.edit_regexp.setChecked(search_text_regexp) self.more_widgets = () self.more_options = create_toolbutton(self, toggled=self.toggle_more_options) self.more_options.setCheckable(True) self.more_options.setChecked(more_options) self.ok_button = create_toolbutton(self, text=_("Search"), icon=ima.icon('DialogApplyButton'), triggered=lambda: self.find.emit(), tip=_("Start search"), text_beside_icon=True) self.ok_button.clicked.connect(self.update_combos) self.stop_button = create_toolbutton(self, text=_("Stop"), icon=ima.icon('stop'), triggered=lambda: self.stop.emit(), tip=_("Stop search"), text_beside_icon=True) self.stop_button.setEnabled(False) for widget in [self.search_text, self.edit_regexp, self.ok_button, self.stop_button, self.more_options]: hlayout1.addWidget(widget) # Layout 2 hlayout2 = QHBoxLayout() self.include_pattern = PatternComboBox(self, include, _("Included filenames pattern")) if include_idx is not None and include_idx >= 0 \ and include_idx < self.include_pattern.count(): self.include_pattern.setCurrentIndex(include_idx) self.include_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.include_regexp.setCheckable(True) self.include_regexp.setChecked(include_regexp) include_label = QLabel(_("Include:")) include_label.setBuddy(self.include_pattern) self.exclude_pattern = PatternComboBox(self, exclude, _("Excluded filenames pattern")) if exclude_idx is not None and exclude_idx >= 0 \ and exclude_idx < self.exclude_pattern.count(): self.exclude_pattern.setCurrentIndex(exclude_idx) self.exclude_regexp = create_toolbutton(self, icon=ima.icon('advanced'), tip=_('Regular expression')) self.exclude_regexp.setCheckable(True) self.exclude_regexp.setChecked(exclude_regexp) exclude_label = QLabel(_("Exclude:")) exclude_label.setBuddy(self.exclude_pattern) for widget in [include_label, self.include_pattern, self.include_regexp, exclude_label, self.exclude_pattern, self.exclude_regexp]: hlayout2.addWidget(widget) # Layout 3 hlayout3 = QHBoxLayout() self.python_path = QRadioButton(_("PYTHONPATH"), self) self.python_path.setChecked(in_python_path) self.python_path.setToolTip(_( "Search in all directories listed in sys.path which" " are outside the Python installation directory")) self.hg_manifest = QRadioButton(_("Hg repository"), self) self.detect_hg_repository() self.hg_manifest.setToolTip( _("Search in current directory hg repository")) self.custom_dir = QRadioButton(_("Here:"), self) self.custom_dir.setChecked(not in_python_path) self.dir_combo = PathComboBox(self) self.dir_combo.addItems(search_path) self.dir_combo.setToolTip(_("Search recursively in this directory")) self.dir_combo.open_dir.connect(self.set_directory) self.python_path.toggled.connect(self.dir_combo.setDisabled) self.hg_manifest.toggled.connect(self.dir_combo.setDisabled) browse = create_toolbutton(self, icon=ima.icon('DirOpenIcon'), tip=_('Browse a search directory'), triggered=self.select_directory) for widget in [self.python_path, self.hg_manifest, self.custom_dir, self.dir_combo, browse]: hlayout3.addWidget(widget) self.search_text.valid.connect(lambda valid: self.find.emit()) self.include_pattern.valid.connect(lambda valid: self.find.emit()) self.exclude_pattern.valid.connect(lambda valid: self.find.emit()) self.dir_combo.valid.connect(lambda valid: self.find.emit()) vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addLayout(hlayout1) vlayout.addLayout(hlayout2) vlayout.addLayout(hlayout3) self.more_widgets = (hlayout2, hlayout3) self.toggle_more_options(more_options) self.setLayout(vlayout) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) @Slot(bool) def toggle_more_options(self, state): for layout in self.more_widgets: for index in range(layout.count()): if state and self.isVisible() or not state: layout.itemAt(index).widget().setVisible(state) if state: icon = ima.icon('options_less') tip = _('Hide advanced options') else: icon = ima.icon('options_more') tip = _('Show advanced options') self.more_options.setIcon(icon) self.more_options.setToolTip(tip) def update_combos(self): self.search_text.lineEdit().returnPressed.emit() self.include_pattern.lineEdit().returnPressed.emit() self.exclude_pattern.lineEdit().returnPressed.emit() def detect_hg_repository(self, path=None): if path is None: path = getcwd() hg_repository = is_hg_installed() and get_vcs_root(path) is not None self.hg_manifest.setEnabled(hg_repository) if not hg_repository and self.hg_manifest.isChecked(): self.custom_dir.setChecked(True) def set_search_text(self, text): if text: self.search_text.add_text(text) self.search_text.lineEdit().selectAll() self.search_text.setFocus() def get_options(self, all=False): # Getting options utext = to_text_string(self.search_text.currentText()) if not utext: return try: texts = [(utext.encode('ascii'), 'ascii')] except UnicodeEncodeError: texts = [] for enc in self.supported_encodings: try: texts.append((utext.encode(enc), enc)) except UnicodeDecodeError: pass text_re = self.edit_regexp.isChecked() include = to_text_string(self.include_pattern.currentText()) include_re = self.include_regexp.isChecked() exclude = to_text_string(self.exclude_pattern.currentText()) exclude_re = self.exclude_regexp.isChecked() python_path = self.python_path.isChecked() hg_manifest = self.hg_manifest.isChecked() path = osp.abspath( to_text_string( self.dir_combo.currentText() ) ) # Finding text occurrences if not include_re: include = fnmatch.translate(include) if not exclude_re: exclude = fnmatch.translate(exclude) if all: search_text = [to_text_string(self.search_text.itemText(index)) \ for index in range(self.search_text.count())] search_path = [to_text_string(self.dir_combo.itemText(index)) \ for index in range(self.dir_combo.count())] include = [to_text_string(self.include_pattern.itemText(index)) \ for index in range(self.include_pattern.count())] include_idx = self.include_pattern.currentIndex() exclude = [to_text_string(self.exclude_pattern.itemText(index)) \ for index in range(self.exclude_pattern.count())] exclude_idx = self.exclude_pattern.currentIndex() more_options = self.more_options.isChecked() return (search_text, text_re, search_path, include, include_idx, include_re, exclude, exclude_idx, exclude_re, python_path, more_options) else: return (path, python_path, hg_manifest, include, exclude, texts, text_re) @Slot() def select_directory(self): """Select directory""" self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), self.dir_combo.currentText()) if directory: self.set_directory(directory) self.redirect_stdio.emit(True) def set_directory(self, directory): path = to_text_string(osp.abspath(to_text_string(directory))) self.dir_combo.setEditText(path) self.detect_hg_repository(path) def keyPressEvent(self, event): """Reimplemented to handle key events""" ctrl = event.modifiers() & Qt.ControlModifier shift = event.modifiers() & Qt.ShiftModifier if event.key() in (Qt.Key_Enter, Qt.Key_Return): self.find.emit() elif event.key() == Qt.Key_F and ctrl and shift: # Toggle find widgets self.parent().toggle_visibility.emit(not self.isVisible()) else: QWidget.keyPressEvent(self, event)