Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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)
Ejemplo n.º 7
0
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)