コード例 #1
0
ファイル: searchslave.py プロジェクト: Guillon88/stoq
    def _create_basic_search(self):
        # This hbox is here so we can have a padding on the filters
        # from the left window edge
        filters_container = gtk.HBox()
        filters_container.show()

        filters_box = gtk.VBox(spacing=6)
        filters_container.pack_start(filters_box, False, False, 6)
        filters_box.show()

        self.vbox.pack_start(filters_container, False, True, 6)

        hbox = gtk.HBox()
        filters_box.pack_start(hbox, False, False)
        hbox.show()
        self.hbox = hbox

        widget = self._primary_filter
        self.hbox.pack_start(widget, False, False)
        widget.show()

        self.search_entry = self._primary_filter.entry

        self.search_button = SearchFilterButton(stock=gtk.STOCK_FIND)
        hbox.pack_start(self.search_button, False, False)
        self.search_button.show()

        self.filters_box = filters_box
コード例 #2
0
ファイル: searchslave.py プロジェクト: hackedbellini/stoq
    def _create_basic_search(self):
        # This hbox is here so we can have a padding on the filters
        # from the left window edge
        filters_container = Gtk.HBox()
        filters_container.show()

        filters_box = Gtk.VBox(spacing=6)
        filters_container.pack_start(filters_box, False, False, 6)
        filters_box.show()

        self.vbox.pack_start(filters_container, False, True, 6)

        hbox = Gtk.HBox()
        filters_box.pack_start(hbox, False, False, 0)
        hbox.show()
        self.hbox = hbox

        widget = self._primary_filter
        self.hbox.pack_start(widget, False, False, 0)
        widget.show()

        self.search_entry = self._primary_filter.entry

        self.search_button = SearchFilterButton(_('Find'), icon='edit-find-symbolic')
        hbox.pack_start(self.search_button, False, False, 0)
        self.search_button.show()

        self.filters_box = filters_box
コード例 #3
0
 def _add_remove_button(self):
     self._remove_button = SearchFilterButton(stock=gtk.STOCK_REMOVE)
     self._remove_button.set_relief(gtk.RELIEF_NONE)
     self._remove_button.set_label_visible(False)
     self._remove_button.connect('clicked', self._on_remove_clicked)
     self._remove_button.show()
     self.pack_start(self._remove_button, False, False)
コード例 #4
0
ファイル: searchslave.py プロジェクト: pkaislan/stoq
    def _create_basic_search(self):
        filters_box = gtk.VBox()
        filters_box.show()
        self.vbox.pack_start(filters_box, expand=False)

        hbox = gtk.HBox()
        hbox.set_border_width(3)
        filters_box.pack_start(hbox, False, False)
        hbox.show()
        self.hbox = hbox

        widget = self._primary_filter
        self.hbox.pack_start(widget, False, False)
        widget.show()

        self.search_entry = self._primary_filter.entry

        self.search_button = SearchFilterButton(stock=gtk.STOCK_FIND)
        hbox.pack_start(self.search_button, False, False)
        self.search_button.show()

        self.filters_box = filters_box
コード例 #5
0
ファイル: searchslave.py プロジェクト: Guillon88/stoq
class SearchSlave(SlaveDelegate):
    """
    A search container is a widget which consists of:
    - search entry (w/ a label) (:class:`StringSearchFilter`)
    - search button
    - result view (:class:`SearchResultListView` or class:`SearchResultTreeView`)
    - a query executer (:class:`stoqlib.database.queryexecuter.QueryExecuter`)

    Additionally you can add a number of search filters to the SearchContainer.
    You can chose if you want to add the filter in the top-left corner
    of bottom, see :class:`SearchFilterPosition`
    """
    result_view_class = SearchResultListView

    gsignal("search-completed", object, object)
    gsignal("result-item-activated", object)
    gsignal("result-item-popup-menu", object, object)
    gsignal("result-selection-changed")

    def __init__(self, columns=None,
                 tree=False,
                 restore_name=None,
                 chars=25,
                 store=None,
                 search_spec=None,
                 fast_iter=False,
                 result_view_class=None):
        """
        Create a new SearchContainer object.
        :param columns: a list of :class:`kiwi.ui.objectlist.Column`
        :param tree: if we should list the results as a tree
        :param restore_name:
        :param chars: maximum number of chars used by the search entry
        :param store: a database store
        :param search_spec: a search spec for store to find on
        """
        if tree:
            self.result_view_class = SearchResultTreeView

        if result_view_class:
            self.result_view_class = result_view_class

        self._auto_search = True
        self._lazy_search = False
        self._last_results = None
        self._model = None
        self._query_executer = None
        self._restore_name = restore_name
        self._search_filters = []
        self._selected_item = None
        self._summary_label = None
        self._search_spec = search_spec
        self._fast_iter = fast_iter
        self.store = store
        self.menu = None
        self.result_view = None
        self._settings_key = 'search-columns-%s' % (
            api.get_current_user(self.store).username, )
        self.columns = self.restore_columns(columns)

        self.vbox = gtk.VBox()
        SlaveDelegate.__init__(self, toplevel=self.vbox)
        self.vbox.show()

        search_filter = StringSearchFilter(_('Search:'), chars=chars,
                                           container=self)
        search_filter.connect('changed', self._on_search_filter__changed)
        self._search_filters.append(search_filter)
        self._primary_filter = search_filter

        self._create_ui()
        self.focus_search_entry()

    #
    # Private API
    #

    def _create_ui(self):
        self._create_basic_search()

        self.set_result_view(self.result_view_class, refresh=False)

    def _create_basic_search(self):
        # This hbox is here so we can have a padding on the filters
        # from the left window edge
        filters_container = gtk.HBox()
        filters_container.show()

        filters_box = gtk.VBox(spacing=6)
        filters_container.pack_start(filters_box, False, False, 6)
        filters_box.show()

        self.vbox.pack_start(filters_container, False, True, 6)

        hbox = gtk.HBox()
        filters_box.pack_start(hbox, False, False)
        hbox.show()
        self.hbox = hbox

        widget = self._primary_filter
        self.hbox.pack_start(widget, False, False)
        widget.show()

        self.search_entry = self._primary_filter.entry

        self.search_button = SearchFilterButton(stock=gtk.STOCK_FIND)
        hbox.pack_start(self.search_button, False, False)
        self.search_button.show()

        self.filters_box = filters_box

    def _migrate_from_pickle(self):
        username = api.get_current_user(self.store).username
        filename = os.path.join(get_application_dir(), 'columns-%s' % username,
                                self._restore_name + '.pickle')
        log.info("Migrating columns from pickle: %s" % (filename, ))
        try:
            with open(filename) as fd:
                import cPickle
                return cPickle.load(fd)
        except Exception, e:
            log.info("Exception while migrating: %r" % (e, ))
            return {}
コード例 #6
0
ファイル: searchslave.py プロジェクト: hackedbellini/stoq
class SearchSlave(SlaveDelegate):
    """
    A search container is a widget which consists of:
    - search entry (w/ a label) (:class:`StringSearchFilter`)
    - search button
    - result view (:class:`SearchResultListView` or class:`SearchResultTreeView`)
    - a query executer (:class:`stoqlib.database.queryexecuter.QueryExecuter`)

    Additionally you can add a number of search filters to the SearchContainer.
    You can chose if you want to add the filter in the top-left corner
    of bottom, see :class:`SearchFilterPosition`
    """
    result_view_class = SearchResultListView

    gsignal("search-completed", object, object)
    gsignal("result-item-activated", object)
    gsignal("result-item-popup-menu", object, object, object)
    gsignal("result-selection-changed")

    def __init__(self, columns=None,
                 tree=False,
                 restore_name=None,
                 chars=25,
                 store=None,
                 search_spec=None,
                 fast_iter=False,
                 result_view_class=None):
        """
        Create a new SearchContainer object.
        :param columns: a list of :class:`kiwi.ui.objectlist.Column`
        :param tree: if we should list the results as a tree
        :param restore_name:
        :param chars: maximum number of chars used by the search entry
        :param store: a database store
        :param search_spec: a search spec for store to find on
        """
        if tree:
            self.result_view_class = SearchResultTreeView

        if result_view_class:
            self.result_view_class = result_view_class

        self._auto_search = True
        self._lazy_search = False
        self._last_results = None
        self._model = None
        self._query_executer = None
        self._restore_name = restore_name
        self._search_filters = []
        self._selected_item = None
        self._summary_label = None
        self._search_spec = search_spec
        self._fast_iter = fast_iter
        self.store = store
        self.menu = None
        self.result_view = None
        self._settings_key = 'search-columns-%s' % (
            api.get_current_user(self.store).username, )
        self.columns = self.restore_columns(columns)

        self.vbox = Gtk.VBox()
        SlaveDelegate.__init__(self, toplevel=self.vbox)
        self.vbox.show()

        search_filter = StringSearchFilter(_('Search:'), chars=chars,
                                           container=self)
        search_filter.connect('changed', self._on_search_filter__changed)
        self._search_filters.append(search_filter)
        self._primary_filter = search_filter

        self._create_ui()
        self.focus_search_entry()

    #
    # Private API
    #

    def _create_ui(self):
        self._create_basic_search()

        self.set_result_view(self.result_view_class, refresh=False)

    def _create_basic_search(self):
        # This hbox is here so we can have a padding on the filters
        # from the left window edge
        filters_container = Gtk.HBox()
        filters_container.show()

        filters_box = Gtk.VBox(spacing=6)
        filters_container.pack_start(filters_box, False, False, 6)
        filters_box.show()

        self.vbox.pack_start(filters_container, False, True, 6)

        hbox = Gtk.HBox()
        filters_box.pack_start(hbox, False, False, 0)
        hbox.show()
        self.hbox = hbox

        widget = self._primary_filter
        self.hbox.pack_start(widget, False, False, 0)
        widget.show()

        self.search_entry = self._primary_filter.entry

        self.search_button = SearchFilterButton(_('Find'), icon='edit-find-symbolic')
        hbox.pack_start(self.search_button, False, False, 0)
        self.search_button.show()

        self.filters_box = filters_box

    def _set_filter_position(self, search_filter, position):
        """
        Set the the filter position.
        :param search_filter:
        :param position:
        """
        if search_filter.get_parent():
            search_filter.get_parent().remove(search_filter)

        if position == SearchFilterPosition.TOP:
            self.hbox.pack_start(search_filter, False, False, 0)
            self.hbox.reorder_child(search_filter, 0)
        elif position == SearchFilterPosition.BOTTOM:
            self.filters_box.pack_start(search_filter, False, False, 0)
        search_filter.show()

    #
    # Properties
    #

    @property
    def results(self):
        warnings.warn("Use .result_view instead", DeprecationWarning, stacklevel=2)
        return self.result_view

    @property
    def summary_label(self):
        return self._summary_label

    #
    # Public API
    #

    def clear(self):
        """
        Clears the result list
        """
        self.result_view.clear()

    def refresh(self):
        """
        Triggers a search again with the currently selected inputs
        """
        self.search()

    def search(self, clear=True):
        """
        Starts a search.
        Fetches the states of all filters and send it to a query executer and
        finally puts the result in the result class
        """
        executer = self.get_query_executer()
        states = [(sf.get_state()) for sf in self._search_filters]
        results = executer.search(states)
        if clear:
            self.result_view.clear()
        if self._fast_iter:
            results = results.fast_iter()
        self.result_view.search_completed(results)

        if self.result_view.get_n_items() == 0:
            self.set_message(_("Nothing found."))
        self.emit("search-completed", self.result_view, states)
        if self._selected_item:
            self.result_view.select(self._selected_item)

        self._last_results = results
        self._last_states = states

    def select(self, item):
        self.result_view.select(item)

    def get_selected_item(self):
        return self.result_view.get_selected_item()

    def focus_search_entry(self):
        """
        Grabs the focus of the search entry
        """
        self.search_entry.grab_focus()

    def disable_search_entry(self):
        """
        Disables the search entry
        """
        self.search_entry.hide()
        self._primary_filter.hide()
        self._search_filters.remove(self._primary_filter)
        self._primary_filter = None

    def enable_advanced_search(self):
        """
        Enables an advanced search
        """
        self.label_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
        self.combo_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)

        self.menu = Gtk.Menu()
        for column in self.columns:
            if not isinstance(column, SearchColumn):
                continue

            if column.data_type not in (datetime.date, decimal.Decimal, int, currency,
                                        str, bool):
                continue

            attr = column.search_attribute or column.attribute
            self.add_filter_option(attr, column.get_search_label(),
                                   column.data_type, column.valid_values,
                                   column.search_func, column.use_having,
                                   column.multiple_selection)

    def add_filter_option(self, attr, title, data_type, valid_values=None,
                          callback=None, use_having=False,
                          multiple_selection=False):
        """Adds a new advanced filter option

        Use this if you need a filter option in the advanced filters when you
        don't have a equivalente SearchColumn in the object list. If its
        possible to add the column, you should probably do that.

        See add_filter_by_attribute for more information
        """
        assert data_type in (datetime.date, decimal.Decimal, int, currency, str,
                             bool)
        menu_item = Gtk.MenuItem(label=title)
        menu_item.show()
        menu_item.connect('activate', self._on_menu_item__activate, attr, title,
                          data_type, valid_values, callback, use_having,
                          multiple_selection)
        self.menu.append(menu_item)

    def set_message(self, message):
        self.result_view.set_message(message)

    def get_column_by_attribute(self, attribute):
        """Returns a column by its model attribute."""
        for column in self.columns:
            if column.attribute == attribute:
                return column

    def set_query(self, callback):
        """
        Overrides the default query mechanism.

        :param callback: a callable which till take two arguments (query, store)
        """
        executer = self.get_query_executer()
        executer.set_query(callback)

    def set_search_spec(self, search_spec):
        """
        Update the search spec this search uses to search

        :param search_spec: a search spec for store to find on
        """
        executer = self.get_query_executer()
        executer.set_search_spec(search_spec)

    def get_query_executer(self):
        """
        Fetchs the QueryExecuter for the SearchContainer

        :returns: a querty executer
        :rtype: a :class:`QueryExecuter` subclass
        """
        if self._query_executer is None:
            executer = QueryExecuter(self.store)
            if not self._lazy_search:
                executer.set_limit(sysparam.get_int('MAX_SEARCH_RESULTS'))
            if self._search_spec is not None:
                executer.set_search_spec(self._search_spec)
            self._query_executer = executer
        return self._query_executer

    def add_filter(self, search_filter, position=SearchFilterPosition.BOTTOM,
                   columns=None, callback=None, use_having=False):
        """
        Adds a search filter

        :param search_filter: the search filter
        :param postition: a :class:`SearchFilterPosition` enum
        :param columns:
        :param callback:
        """
        if not isinstance(search_filter, SearchFilter):
            raise TypeError("search_filter must be a SearchFilter subclass, "
                            "not %r" % (search_filter,))

        executer = self.get_query_executer()
        if executer:
            if callback:
                if not callable(callback):
                    raise TypeError("callback must be callable")
                executer.add_filter_query_callback(search_filter, callback,
                                                   use_having=use_having)
            elif columns:
                executer.set_filter_columns(search_filter, columns,
                                            use_having=use_having)
        else:
            if columns or callback:
                raise TypeError(
                    "You need to set an executor before calling set_filters "
                    "with columns or callback set")

        assert not search_filter.get_parent()
        self._set_filter_position(search_filter, position)
        search_filter.connect('changed', self._on_search_filter__changed)
        search_filter.connect('removed', self._on_search_filter__remove)
        self._search_filters.append(search_filter)

    def remove_filter(self, filter):
        self.filters_box.remove(filter)
        self._search_filters.remove(filter)
        filter.destroy()

        if self._auto_search:
            self.search()

    def add_filter_by_attribute(self, attr, title, data_type, valid_values=None,
                                callback=None, use_having=False,
                                multiple_selection=False):
        """Add a filter accordingly to the attributes specified

        :param attr: attribute that will be filtered. This can be either the
          name of the attribute or the attribute itself.
        :param title: the title of the filter that will be visible in the
                      interface
        :param data_type: the attribute type (str, bool, decimal, etc)
        :param callback: the callback function that will be triggered
        :param use_having: use having expression in the query
        """
        if data_type is not bool:
            title += ':'

        if data_type == datetime.date:
            filter = DateSearchFilter(title)
            if valid_values:
                filter.clear_options()
                filter.add_custom_options()
                for opt in valid_values:
                    filter.add_option(opt)
                filter.select(valid_values[0])

        elif (data_type == decimal.Decimal or
              data_type == int or
              data_type == currency):
            filter = NumberSearchFilter(title)
            if data_type != int:
                filter.set_digits(2)
        elif data_type == str:
            if multiple_selection:
                filter = MultiSearchFilter(title, valid_values)
            elif valid_values:
                filter = ComboSearchFilter(title, valid_values)
                filter.enable_advanced()
            else:
                filter = StringSearchFilter(title)
                filter.enable_advanced()
        elif data_type == bool:
            filter = BoolSearchFilter(title)
        else:
            raise NotImplementedError(title, data_type)

        filter.set_removable()
        self.add_filter(filter, columns=[attr],
                        callback=callback,
                        use_having=use_having)

        if data_type is not bool:
            label = filter.get_title_label()
            label.set_alignment(1.0, 0.5)
            self.label_group.add_widget(label)
        combo = filter.get_mode_combo()
        if combo:
            self.combo_group.add_widget(combo)

        return filter

    def parse_states(self):
        """ Returns the states as clauses
        """
        states = [(sf.get_state()) for sf in self._search_filters]
        return self.get_query_executer().parse_states(states)

    def get_filter_states(self):
        dict_state = {}
        for search_filter in self._search_filters:
            dict_state[search_filter.label] = data = {}
            state = search_filter.get_state()
            if isinstance(state, DateQueryState):
                data['start'] = state.date
            elif isinstance(state, BoolQueryState):
                data['value'] = state.value
            elif isinstance(state, DateIntervalQueryState):
                data['start'] = state.start
                data['end'] = state.end
            elif isinstance(state, NumberQueryState):
                data['value'] = state.value
                data['mode'] = state.mode
                if hasattr(state, 'value_id'):
                    data['value_id'] = state.value_id
                    data['value'] = None
            elif isinstance(state, NumberIntervalQueryState):
                data['start'] = state.start
                data['end'] = state.end
            elif isinstance(state, StringQueryState):
                data['text'] = state.text
                data['mode'] = state.mode
            elif isinstance(state, MultiQueryState):
                # Converting to list since set is not serializable
                data['values'] = list(state.values)
            else:
                raise NotImplementedError(state)
        return dict_state

    def set_filter_states(self, dict_state):
        for label, filter_state in dict_state.items():
            search_filter = self.get_search_filter_by_label(label)
            if search_filter is None:
                continue
            search_filter.set_state(**filter_state)

    def get_search_filters(self):
        return self._search_filters

    def get_search_filter_by_label(self, label):
        for search_filter in self._search_filters:
            if search_filter.label == label:
                return search_filter

    def get_primary_filter(self):
        """
        Fetches the primary filter for the SearchContainer.
        The primary filter is the filter attached to the standard entry
        normally used to do free text searching
        :returns: the primary filter
        """
        return self._primary_filter

    def enable_lazy_search(self):
        if self.result_view:
            self.result_view.enable_lazy_search()
        self._lazy_search = True

    def set_auto_search(self, auto_search):
        """
        Enables/Disables auto search which means that the search result box
        is automatically populated when a filter changes
        :param auto_search: True to enable, False to disable
        """
        self._auto_search = auto_search

    def set_text_field_columns(self, columns):
        if self._primary_filter is None:
            raise ValueError("The primary filter is disabled")

        executer = self.get_query_executer()
        executer.set_filter_columns(self._primary_filter, columns)

    def set_summary_label(self, column, label='Total:', format='%s',
                          parent=None):
        """
        Adds a summary label to the result set

        :param column: the column to sum from
        :param label: the label to use, defaults to 'Total:'
        :param format: the format, defaults to '%%s', must include '%%s'
        :param parent: the parent widget a label should be added to or
           ``None`` if it should be added to the SearchContainer
        """
        if not '%s' in format:
            raise ValueError("format must contain %s")

        try:
            self.result_view.get_column_by_name(column)
        except LookupError:
            raise ValueError("%s is not a valid column" % (column,))

        if not parent:
            parent = self.vbox
        elif not isinstance(parent, Gtk.Box):
            raise TypeError("parent %r must be a GtkBox subclass" % (
                parent))

        if self._summary_label:
            self._summary_label.get_parent().remove(self._summary_label)
        if self._lazy_search:
            summary_label_class = LazySummaryLabel
        else:
            summary_label_class = SummaryLabel
        self._summary_label = summary_label_class(klist=self.result_view,
                                                  column=column,
                                                  label=label,
                                                  value_format=format)
        parent.pack_start(self._summary_label, False, False, 0)
        self._summary_label.show()

    def get_summary_label(self):
        return self._summary_label

    def get_last_results(self):
        return self._last_results

    def set_result_view(self, result_view_class, refresh=False):
        """
        Creates a new result view and attaches it to this search container.

        If a previous view was created it will be destroyed.
        :param result_view_class: a result view factory
        :param refresh: ``True`` if the results should be updated
        """

        if not verifyClass(ISearchResultView, result_view_class):
            raise TypeError("%s needs to implement ISearchResultView" % (
                result_view_class, ))

        if self.result_view:
            item = self.result_view.get_selected_item()
            self.vbox.remove(self.result_view)
            self.result_view = None
        else:
            item = None

        self.result_view = result_view_class()
        self.result_view.attach(search=self,
                                columns=self.columns)

        if self._lazy_search:
            self.result_view.enable_lazy_search()

        self.vbox.pack_start(self.result_view, True, True, 0)

        if refresh:
            if item is not None:
                self._selected_item = item
            self.search()

        self.result_view.show()

    def save_columns(self):
        if not self._restore_name:
            return

        d = self.result_view.get_settings()
        columns = api.user_settings.get(self._settings_key, {})
        columns[self._restore_name] = d

    def restore_columns(self, cols):
        if not self._restore_name:
            return cols

        columns = api.user_settings.get(self._settings_key, {})
        saved = columns.get(self._restore_name, {})

        cols_dict = {}
        for original_pos, col in enumerate(cols):
            attr = col.attribute
            props = saved.get(attr)
            # This will happen for two reasons: a) the column is referenced by
            # another one (only the column that references is saved) and
            # b) Its a brand new column (not saved in the preferences)
            if not props:
                cols_dict[attr] = (col, original_pos)
                continue

            col.visible = props[0]
            # Expanded columns should not have a width set
            if not col.expand:
                col.width = props[1]

            # We didn't store sorted and order before
            if len(props) <= 2:
                continue

            col.sorted = props[2]
            col.order = props[3]

            pos = props[4]
            # If col references another column, the referenced column
            # should appear before this col
            if col.column:
                referenced_col = cols_dict[col.column][0]
                cols_dict[col.column] = (referenced_col, pos)
                cols_dict[attr] = (col, pos + 1)
                if col.sorted:
                    referenced_col.sorted, col.sorted = True, False
            else:
                cols_dict[attr] = (col, pos)

        cols.sort(key=lambda col: cols_dict[col.attribute][1])
        return cols

    def save_filter_settings(self, domain, restore_name):
        """
        Save filters to user settings

        :param domain: settings domain, like "app-ui"
        :param restore_name: name of the setting, like "admin"
        """
        if self._loading_filters:
            return
        filter_states = self.get_filter_states()
        domain_settings = api.user_settings.get(domain, {})
        settings = domain_settings.setdefault(restore_name, {})
        settings['filter-states'] = filter_states

    def restore_filter_settings(self, domain, restore_name):
        """
        Restore filters from user settings

        :param domain: settings domain, like "app-ui"
        :param restore_name: name of the setting, like "admin"
        """
        self._loading_filters = True
        domain_settings = api.user_settings.get(domain, {})
        settings = domain_settings.setdefault(restore_name, {})
        filter_states = settings.get('filter-states')
        if filter_states is not None:
            # Disable auto search to avoid an extra query when restoring the
            # state
            self.set_auto_search(False)
            try:
                self.set_filter_states(filter_states)
            except KeyError:
                # Sometimes when reseting the database, saved filters will have invalid defaults
                pass
            self.set_auto_search(True)
        self._loading_filters = False

    def create_branch_filter(self, label=None, column=None):
        """Returns a new branch filter.

        :param label: The label to be used for the filter
        :param column: When provided, besides creating the filter, we will also
          add it to the interface, filtering by the informed column.
        """
        items = api.get_branches_for_filter(self.store, use_id=True)
        if not label:
            label = _('Branch:')

        if column and not isinstance(column, list):
            column = [column]

        branch_filter = ComboSearchFilter(label, items)
        current = api.get_current_branch(self.store)
        branch_filter.select(current.id)
        if column:
            self.add_filter(branch_filter, columns=column,
                            position=SearchFilterPosition.TOP)

        return branch_filter

    #
    #  Callbacks
    #

    def on_result_view__item_activated(self, result_view, item):
        self.emit('result-item-activated', item)

    def on_result_view__item_popup_menu(self, result_view, objectlist, results, event):
        self.emit('result-item-popup-menu', objectlist, results, event)

    def on_result_view__selection_changed(self, result_view, selected):
        self.emit('result-selection-changed')

    def on_search_button__clicked(self, button):
        self.search()

    def on_search_entry__activate(self, button):
        self.search()

    def _on_menu_item__activate(self, item, attr, title, data_type,
                                valid_values, callback, use_having,
                                multiple_selection):
        self.add_filter_by_attribute(attr, title, data_type, valid_values,
                                     callback, use_having, multiple_selection)

    def _on_search_filter__remove(self, filter):
        self.remove_filter(filter)

    def _on_search_filter__changed(self, search_filter):
        # The 'changed' event is emitted when someone presses Enter on the
        # entry. Since we already do a search when the entry is activated,
        # doing again when the primary filter is changed would result in doing
        # the same query twice.
        if search_filter == self._primary_filter:
            return
        if self._auto_search:
            self.search()
コード例 #7
0
class SearchSlave(SlaveDelegate):
    """
    A search container is a widget which consists of:
    - search entry (w/ a label) (:class:`StringSearchFilter`)
    - search button
    - result view (:class:`SearchResultListView` or class:`SearchResultTreeView`)
    - a query executer (:class:`stoqlib.database.queryexecuter.QueryExecuter`)

    Additionally you can add a number of search filters to the SearchContainer.
    You can chose if you want to add the filter in the top-left corner
    of bottom, see :class:`SearchFilterPosition`
    """
    result_view_class = SearchResultListView

    gsignal("search-completed", object, object)
    gsignal("result-item-activated", object)
    gsignal("result-item-popup-menu", object, object)
    gsignal("result-selection-changed")

    def __init__(self,
                 columns=None,
                 tree=False,
                 restore_name=None,
                 chars=25,
                 store=None,
                 search_spec=None):
        """
        Create a new SearchContainer object.
        :param columns: a list of :class:`kiwi.ui.objectlist.Column`
        :param tree: if we should list the results as a tree
        :param restore_name:
        :param chars: maximum number of chars used by the search entry
        :param store: a database store
        :param search_spec: a search spec for store to find on
        """
        if tree:
            self.result_view_class = SearchResultTreeView

        self._auto_search = True
        self._columns = columns
        self._lazy_search = False
        self._last_results = None
        self._model = None
        self._query_executer = None
        self._restore_name = restore_name
        self._search_filters = []
        self._selected_item = None
        self._summary_label = None
        self._search_spec = search_spec
        self.store = store
        self.menu = None
        self.result_view = None
        self._settings_key = 'search-columns-%s' % (api.get_current_user(
            self.store).username, )
        self._columns = self.restore_columns(columns)

        self.vbox = gtk.VBox()
        SlaveDelegate.__init__(self, toplevel=self.vbox)
        self.vbox.show()

        search_filter = StringSearchFilter(_('Search:'),
                                           chars=chars,
                                           container=self)
        search_filter.connect('changed', self._on_search_filter__changed)
        self._search_filters.append(search_filter)
        self._primary_filter = search_filter

        self._create_ui()

    #
    # Private API
    #

    def _create_ui(self):
        self._create_basic_search()

        self.set_result_view(self.result_view_class, refresh=False)

    def _create_basic_search(self):
        filters_box = gtk.VBox()
        filters_box.show()
        self.vbox.pack_start(filters_box, expand=False)

        hbox = gtk.HBox()
        hbox.set_border_width(3)
        filters_box.pack_start(hbox, False, False)
        hbox.show()
        self.hbox = hbox

        widget = self._primary_filter
        self.hbox.pack_start(widget, False, False)
        widget.show()

        self.search_entry = self._primary_filter.entry

        self.search_button = SearchFilterButton(stock=gtk.STOCK_FIND)
        hbox.pack_start(self.search_button, False, False)
        self.search_button.show()

        self.filters_box = filters_box

    def _migrate_from_pickle(self):
        username = api.get_current_user(self.store).username
        filename = os.path.join(get_application_dir(), 'columns-%s' % username,
                                self._restore_name + '.pickle')
        log.info("Migrating columns from pickle: %s" % (filename, ))
        try:
            with open(filename) as fd:
                import cPickle
                return cPickle.load(fd)
        except Exception, e:
            log.info("Exception while migrating: %r" % (e, ))
            return {}
コード例 #8
0
class SearchFilter(gtk.HBox):
    """
    A base class used by common search filters
    """

    #: the label of this filter
    label = gobject.property(type=str, flags=(gobject.PARAM_READWRITE))

    gsignal('changed')
    gsignal('removed')
    __gtype_name__ = 'SearchFilter'

    def __init__(self, label=''):
        gtk.HBox.__init__(self)
        self.props.label = label
        self._label = label
        self._remove_button = None

    def _add_remove_button(self):
        self._remove_button = SearchFilterButton(stock=gtk.STOCK_REMOVE)
        self._remove_button.set_relief(gtk.RELIEF_NONE)
        self._remove_button.set_label_visible(False)
        self._remove_button.connect('clicked', self._on_remove_clicked)
        self._remove_button.show()
        self.pack_start(self._remove_button, False, False)

    def _on_remove_clicked(self, button):
        self.emit('removed')

    def do_set_property(self, pspec, value):
        if pspec.name == 'label':
            self._label = value
        else:
            raise AssertionError(pspec.name)

    def do_get_property(self, child, property_id, pspec):
        if pspec.name == 'label':
            return self._label
        else:
            raise AssertionError(pspec.name)

    def set_label(self, label):
        self._label = label

    def get_state(self):
        """
        Implement this in a subclass
        """
        raise NotImplementedError

    def get_title_label(self):
        raise NotImplementedError

    def get_mode_combo(self):
        raise NotImplementedError

    def get_description(self):
        """Returns a description of the search filter.
        :returns: a string describing the search filter.
        """
        raise NotImplementedError

    def set_removable(self):
        if self._remove_button is None:
            self._add_remove_button()
コード例 #9
0
class SearchSlave(SlaveDelegate):
    """
    A search container is a widget which consists of:
    - search entry (w/ a label) (:class:`StringSearchFilter`)
    - search button
    - result view (:class:`SearchResultListView` or class:`SearchResultTreeView`)
    - a query executer (:class:`stoqlib.database.queryexecuter.QueryExecuter`)

    Additionally you can add a number of search filters to the SearchContainer.
    You can chose if you want to add the filter in the top-left corner
    of bottom, see :class:`SearchFilterPosition`
    """
    result_view_class = SearchResultListView

    gsignal("search-completed", object, object)
    gsignal("result-item-activated", object)
    gsignal("result-item-popup-menu", object, object)
    gsignal("result-selection-changed")

    def __init__(self,
                 columns=None,
                 tree=False,
                 restore_name=None,
                 chars=25,
                 store=None,
                 search_spec=None,
                 fast_iter=False,
                 result_view_class=None):
        """
        Create a new SearchContainer object.
        :param columns: a list of :class:`kiwi.ui.objectlist.Column`
        :param tree: if we should list the results as a tree
        :param restore_name:
        :param chars: maximum number of chars used by the search entry
        :param store: a database store
        :param search_spec: a search spec for store to find on
        """
        if tree:
            self.result_view_class = SearchResultTreeView

        if result_view_class:
            self.result_view_class = result_view_class

        self._auto_search = True
        self._lazy_search = False
        self._last_results = None
        self._model = None
        self._query_executer = None
        self._restore_name = restore_name
        self._search_filters = []
        self._selected_item = None
        self._summary_label = None
        self._search_spec = search_spec
        self._fast_iter = fast_iter
        self.store = store
        self.menu = None
        self.result_view = None
        self._settings_key = 'search-columns-%s' % (api.get_current_user(
            self.store).username, )
        self.columns = self.restore_columns(columns)

        self.vbox = Gtk.VBox()
        SlaveDelegate.__init__(self, toplevel=self.vbox)
        self.vbox.show()

        search_filter = StringSearchFilter(_('Search:'),
                                           chars=chars,
                                           container=self)
        search_filter.connect('changed', self._on_search_filter__changed)
        self._search_filters.append(search_filter)
        self._primary_filter = search_filter

        self._create_ui()
        self.focus_search_entry()

    #
    # Private API
    #

    def _create_ui(self):
        self._create_basic_search()

        self.set_result_view(self.result_view_class, refresh=False)

    def _create_basic_search(self):
        # This hbox is here so we can have a padding on the filters
        # from the left window edge
        filters_container = Gtk.HBox()
        filters_container.show()

        filters_box = Gtk.VBox(spacing=6)
        filters_container.pack_start(filters_box, False, False, 6)
        filters_box.show()

        self.vbox.pack_start(filters_container, False, True, 6)

        hbox = Gtk.HBox()
        filters_box.pack_start(hbox, False, False, 0)
        hbox.show()
        self.hbox = hbox

        widget = self._primary_filter
        self.hbox.pack_start(widget, False, False, 0)
        widget.show()

        self.search_entry = self._primary_filter.entry

        self.search_button = SearchFilterButton(stock=Gtk.STOCK_FIND)
        hbox.pack_start(self.search_button, False, False, 0)
        self.search_button.show()

        self.filters_box = filters_box

    def _set_filter_position(self, search_filter, position):
        """
        Set the the filter position.
        :param search_filter:
        :param position:
        """
        if search_filter.get_parent():
            search_filter.get_parent().remove(search_filter)

        if position == SearchFilterPosition.TOP:
            self.hbox.pack_start(search_filter, False, False, 0)
            self.hbox.reorder_child(search_filter, 0)
        elif position == SearchFilterPosition.BOTTOM:
            self.filters_box.pack_start(search_filter, False, False, 0)
        search_filter.show()

    #
    # Properties
    #

    @property
    def results(self):
        warnings.warn("Use .result_view instead",
                      DeprecationWarning,
                      stacklevel=2)
        return self.result_view

    @property
    def summary_label(self):
        return self._summary_label

    #
    # Public API
    #

    def clear(self):
        """
        Clears the result list
        """
        self.result_view.clear()

    def refresh(self):
        """
        Triggers a search again with the currently selected inputs
        """
        self.search()

    def search(self, clear=True):
        """
        Starts a search.
        Fetches the states of all filters and send it to a query executer and
        finally puts the result in the result class
        """
        executer = self.get_query_executer()
        states = [(sf.get_state()) for sf in self._search_filters]
        results = executer.search(states)
        if clear:
            self.result_view.clear()
        if self._fast_iter:
            results = results.fast_iter()
        self.result_view.search_completed(results)

        if self.result_view.get_n_items() == 0:
            self.set_message(_("Nothing found."))
        self.emit("search-completed", self.result_view, states)
        if self._selected_item:
            self.result_view.select(self._selected_item)

        self._last_results = results
        self._last_states = states

    def select(self, item):
        self.result_view.select(item)

    def get_selected_item(self):
        return self.result_view.get_selected_item()

    def focus_search_entry(self):
        """
        Grabs the focus of the search entry
        """
        self.search_entry.grab_focus()

    def disable_search_entry(self):
        """
        Disables the search entry
        """
        self.search_entry.hide()
        self._primary_filter.hide()
        self._search_filters.remove(self._primary_filter)
        self._primary_filter = None

    def enable_advanced_search(self):
        """
        Enables an advanced search
        """
        self.label_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
        self.combo_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)

        self.menu = Gtk.Menu()
        for column in self.columns:
            if not isinstance(column, SearchColumn):
                continue

            if column.data_type not in (datetime.date, decimal.Decimal, int,
                                        currency, str, bool):
                continue

            attr = column.search_attribute or column.attribute
            self.add_filter_option(attr, column.get_search_label(),
                                   column.data_type, column.valid_values,
                                   column.search_func, column.use_having,
                                   column.multiple_selection)

    def add_filter_option(self,
                          attr,
                          title,
                          data_type,
                          valid_values=None,
                          callback=None,
                          use_having=False,
                          multiple_selection=False):
        """Adds a new advanced filter option

        Use this if you need a filter option in the advanced filters when you
        don't have a equivalente SearchColumn in the object list. If its
        possible to add the column, you should probably do that.

        See add_filter_by_attribute for more information
        """
        assert data_type in (datetime.date, decimal.Decimal, int, currency,
                             str, bool)
        menu_item = Gtk.MenuItem(label=title)
        menu_item.show()
        menu_item.connect('activate', self._on_menu_item__activate, attr,
                          title, data_type, valid_values, callback, use_having,
                          multiple_selection)
        self.menu.append(menu_item)

    def set_message(self, message):
        self.result_view.set_message(message)

    def get_column_by_attribute(self, attribute):
        """Returns a column by its model attribute."""
        for column in self.columns:
            if column.attribute == attribute:
                return column

    def set_query(self, callback):
        """
        Overrides the default query mechanism.

        :param callback: a callable which till take two arguments (query, store)
        """
        executer = self.get_query_executer()
        executer.set_query(callback)

    def set_search_spec(self, search_spec):
        """
        Update the search spec this search uses to search

        :param search_spec: a search spec for store to find on
        """
        executer = self.get_query_executer()
        executer.set_search_spec(search_spec)

    def get_query_executer(self):
        """
        Fetchs the QueryExecuter for the SearchContainer

        :returns: a querty executer
        :rtype: a :class:`QueryExecuter` subclass
        """
        if self._query_executer is None:
            executer = QueryExecuter(self.store)
            if not self._lazy_search:
                executer.set_limit(sysparam.get_int('MAX_SEARCH_RESULTS'))
            if self._search_spec is not None:
                executer.set_search_spec(self._search_spec)
            self._query_executer = executer
        return self._query_executer

    def add_filter(self,
                   search_filter,
                   position=SearchFilterPosition.BOTTOM,
                   columns=None,
                   callback=None,
                   use_having=False):
        """
        Adds a search filter

        :param search_filter: the search filter
        :param postition: a :class:`SearchFilterPosition` enum
        :param columns:
        :param callback:
        """
        if not isinstance(search_filter, SearchFilter):
            raise TypeError("search_filter must be a SearchFilter subclass, "
                            "not %r" % (search_filter, ))

        executer = self.get_query_executer()
        if executer:
            if callback:
                if not callable(callback):
                    raise TypeError("callback must be callable")
                executer.add_filter_query_callback(search_filter,
                                                   callback,
                                                   use_having=use_having)
            elif columns:
                executer.set_filter_columns(search_filter,
                                            columns,
                                            use_having=use_having)
        else:
            if columns or callback:
                raise TypeError(
                    "You need to set an executor before calling set_filters "
                    "with columns or callback set")

        assert not search_filter.get_parent()
        self._set_filter_position(search_filter, position)
        search_filter.connect('changed', self._on_search_filter__changed)
        search_filter.connect('removed', self._on_search_filter__remove)
        self._search_filters.append(search_filter)

    def remove_filter(self, filter):
        self.filters_box.remove(filter)
        self._search_filters.remove(filter)
        filter.destroy()

        if self._auto_search:
            self.search()

    def add_filter_by_attribute(self,
                                attr,
                                title,
                                data_type,
                                valid_values=None,
                                callback=None,
                                use_having=False,
                                multiple_selection=False):
        """Add a filter accordingly to the attributes specified

        :param attr: attribute that will be filtered. This can be either the
          name of the attribute or the attribute itself.
        :param title: the title of the filter that will be visible in the
                      interface
        :param data_type: the attribute type (str, bool, decimal, etc)
        :param callback: the callback function that will be triggered
        :param use_having: use having expression in the query
        """
        if data_type is not bool:
            title += ':'

        if data_type == datetime.date:
            filter = DateSearchFilter(title)
            if valid_values:
                filter.clear_options()
                filter.add_custom_options()
                for opt in valid_values:
                    filter.add_option(opt)
                filter.select(valid_values[0])

        elif (data_type == decimal.Decimal or data_type == int
              or data_type == currency):
            filter = NumberSearchFilter(title)
            if data_type != int:
                filter.set_digits(2)
        elif data_type == str:
            if multiple_selection:
                filter = MultiSearchFilter(title, valid_values)
            elif valid_values:
                filter = ComboSearchFilter(title, valid_values)
                filter.enable_advanced()
            else:
                filter = StringSearchFilter(title)
                filter.enable_advanced()
        elif data_type == bool:
            filter = BoolSearchFilter(title)
        else:
            raise NotImplementedError(title, data_type)

        filter.set_removable()
        self.add_filter(filter,
                        columns=[attr],
                        callback=callback,
                        use_having=use_having)

        if data_type is not bool:
            label = filter.get_title_label()
            label.set_alignment(1.0, 0.5)
            self.label_group.add_widget(label)
        combo = filter.get_mode_combo()
        if combo:
            self.combo_group.add_widget(combo)

        return filter

    def parse_states(self):
        """ Returns the states as clauses
        """
        states = [(sf.get_state()) for sf in self._search_filters]
        return self.get_query_executer().parse_states(states)

    def get_filter_states(self):
        dict_state = {}
        for search_filter in self._search_filters:
            dict_state[search_filter.label] = data = {}
            state = search_filter.get_state()
            if isinstance(state, DateQueryState):
                data['start'] = state.date
            elif isinstance(state, BoolQueryState):
                data['value'] = state.value
            elif isinstance(state, DateIntervalQueryState):
                data['start'] = state.start
                data['end'] = state.end
            elif isinstance(state, NumberQueryState):
                data['value'] = state.value
                data['mode'] = state.mode
                if hasattr(state, 'value_id'):
                    data['value_id'] = state.value_id
                    data['value'] = None
            elif isinstance(state, NumberIntervalQueryState):
                data['start'] = state.start
                data['end'] = state.end
            elif isinstance(state, StringQueryState):
                data['text'] = state.text
                data['mode'] = state.mode
            elif isinstance(state, MultiQueryState):
                # Converting to list since set is not serializable
                data['values'] = list(state.values)
            else:
                raise NotImplementedError(state)
        return dict_state

    def set_filter_states(self, dict_state):
        for label, filter_state in dict_state.items():
            search_filter = self.get_search_filter_by_label(label)
            if search_filter is None:
                continue
            search_filter.set_state(**filter_state)

    def get_search_filters(self):
        return self._search_filters

    def get_search_filter_by_label(self, label):
        for search_filter in self._search_filters:
            if search_filter.label == label:
                return search_filter

    def get_primary_filter(self):
        """
        Fetches the primary filter for the SearchContainer.
        The primary filter is the filter attached to the standard entry
        normally used to do free text searching
        :returns: the primary filter
        """
        return self._primary_filter

    def enable_lazy_search(self):
        if self.result_view:
            self.result_view.enable_lazy_search()
        self._lazy_search = True

    def set_auto_search(self, auto_search):
        """
        Enables/Disables auto search which means that the search result box
        is automatically populated when a filter changes
        :param auto_search: True to enable, False to disable
        """
        self._auto_search = auto_search

    def set_text_field_columns(self, columns):
        if self._primary_filter is None:
            raise ValueError("The primary filter is disabled")

        executer = self.get_query_executer()
        executer.set_filter_columns(self._primary_filter, columns)

    def set_summary_label(self,
                          column,
                          label='Total:',
                          format='%s',
                          parent=None):
        """
        Adds a summary label to the result set

        :param column: the column to sum from
        :param label: the label to use, defaults to 'Total:'
        :param format: the format, defaults to '%%s', must include '%%s'
        :param parent: the parent widget a label should be added to or
           ``None`` if it should be added to the SearchContainer
        """
        # if not '%s' in format:
        #     raise ValueError("format must contain %s")

        try:
            self.result_view.get_column_by_name(column)
        except LookupError:
            raise ValueError("%s is not a valid column" % (column, ))

        if not parent:
            parent = self.vbox
        elif not isinstance(parent, Gtk.Box):
            raise TypeError("parent %r must be a GtkBox subclass" % (parent))

        if self._summary_label:
            self._summary_label.get_parent().remove(self._summary_label)
        if self._lazy_search:
            summary_label_class = LazySummaryLabel
        else:
            summary_label_class = SummaryLabel
        self._summary_label = summary_label_class(klist=self.result_view,
                                                  column=column,
                                                  label=label,
                                                  value_format=format)
        parent.pack_start(self._summary_label, False, False, 0)
        self._summary_label.show()

    def get_summary_label(self):
        return self._summary_label

    def get_last_results(self):
        return self._last_results

    def set_result_view(self, result_view_class, refresh=False):
        """
        Creates a new result view and attaches it to this search container.

        If a previous view was created it will be destroyed.
        :param result_view_class: a result view factory
        :param refresh: ``True`` if the results should be updated
        """

        if not verifyClass(ISearchResultView, result_view_class):
            raise TypeError("%s needs to implement ISearchResultView" %
                            (result_view_class, ))

        if self.result_view:
            item = self.result_view.get_selected_item()
            self.vbox.remove(self.result_view)
            self.result_view = None
        else:
            item = None

        self.result_view = result_view_class()
        self.result_view.attach(search=self, columns=self.columns)

        if self._lazy_search:
            self.result_view.enable_lazy_search()

        self.vbox.pack_start(self.result_view, True, True, 0)

        if refresh:
            if item is not None:
                self._selected_item = item
            self.search()

        self.result_view.show()

    def save_columns(self):
        if not self._restore_name:
            return

        d = self.result_view.get_settings()
        columns = api.user_settings.get(self._settings_key, {})
        columns[self._restore_name] = d

    def restore_columns(self, cols):
        if not self._restore_name:
            return cols

        columns = api.user_settings.get(self._settings_key, {})
        saved = columns.get(self._restore_name, {})

        cols_dict = {}
        for original_pos, col in enumerate(cols):
            attr = col.attribute
            props = saved.get(attr)
            # This will happen for two reasons: a) the column is referenced by
            # another one (only the column that references is saved) and
            # b) Its a brand new column (not saved in the preferences)
            if not props:
                cols_dict[attr] = (col, original_pos)
                continue

            col.visible = props[0]
            # Expanded columns should not have a width set
            if not col.expand:
                col.width = props[1]

            # We didn't store sorted and order before
            if len(props) <= 2:
                continue

            col.sorted = props[2]
            col.order = props[3]

            pos = props[4]
            # If col references another column, the referenced column
            # should appear before this col
            if col.column:
                referenced_col = cols_dict[col.column][0]
                cols_dict[col.column] = (referenced_col, pos)
                cols_dict[attr] = (col, pos + 1)
                if col.sorted:
                    referenced_col.sorted, col.sorted = True, False
            else:
                cols_dict[attr] = (col, pos)

        cols.sort(key=lambda col: cols_dict[col.attribute][1])
        return cols

    def save_filter_settings(self, domain, restore_name):
        """
        Save filters to user settings

        :param domain: settings domain, like "app-ui"
        :param restore_name: name of the setting, like "admin"
        """
        if self._loading_filters:
            return
        filter_states = self.get_filter_states()
        domain_settings = api.user_settings.get(domain, {})
        settings = domain_settings.setdefault(restore_name, {})
        settings['filter-states'] = filter_states

    def restore_filter_settings(self, domain, restore_name):
        """
        Restore filters from user settings

        :param domain: settings domain, like "app-ui"
        :param restore_name: name of the setting, like "admin"
        """
        self._loading_filters = True
        domain_settings = api.user_settings.get(domain, {})
        settings = domain_settings.setdefault(restore_name, {})
        filter_states = settings.get('filter-states')
        if filter_states is not None:
            # Disable auto search to avoid an extra query when restoring the
            # state
            self.set_auto_search(False)
            self.set_filter_states(filter_states)
            self.set_auto_search(True)
        self._loading_filters = False

    def create_branch_filter(self, label=None, column=None):
        """Returns a new branch filter.

        :param label: The label to be used for the filter
        :param column: When provided, besides creating the filter, we will also
          add it to the interface, filtering by the informed column.
        """
        items = api.get_branches_for_filter(self.store, use_id=True)
        if not label:
            label = _('Branch:')

        if column and not isinstance(column, list):
            column = [column]

        branch_filter = ComboSearchFilter(label, items)
        current = api.get_current_branch(self.store)
        branch_filter.select(current.id)
        if column:
            self.add_filter(branch_filter,
                            columns=column,
                            position=SearchFilterPosition.TOP)

        return branch_filter

    #
    #  Callbacks
    #

    def on_result_view__item_activated(self, result_view, item):
        self.emit('result-item-activated', item)

    def on_result_view__item_popup_menu(self, result_view, results, event):
        self.emit('result-item-popup-menu', results, event)

    def on_result_view__selection_changed(self, result_view, selected):
        self.emit('result-selection-changed')

    def on_search_button__clicked(self, button):
        self.search()

    def on_search_entry__activate(self, button):
        self.search()

    def _on_menu_item__activate(self, item, attr, title, data_type,
                                valid_values, callback, use_having,
                                multiple_selection):
        self.add_filter_by_attribute(attr, title, data_type, valid_values,
                                     callback, use_having, multiple_selection)

    def _on_search_filter__remove(self, filter):
        self.remove_filter(filter)

    def _on_search_filter__changed(self, search_filter):
        # The 'changed' event is emitted when someone presses Enter on the
        # entry. Since we already do a search when the entry is activated,
        # doing again when the primary filter is changed would result in doing
        # the same query twice.
        if search_filter == self._primary_filter:
            return
        if self._auto_search:
            self.search()
コード例 #10
0
ファイル: searchfilters.py プロジェクト: hackedbellini/stoq
 def _add_remove_button(self):
     self._remove_button = SearchFilterButton(icon='list-remove-symbolic')
     self._remove_button.set_relief(Gtk.ReliefStyle.NONE)
     self._remove_button.connect('clicked', self._on_remove_clicked)
     self._remove_button.show()
     self.pack_start(self._remove_button, False, False, 0)
コード例 #11
0
 def _add_remove_button(self):
     self._remove_button = SearchFilterButton(icon='list-remove-symbolic')
     self._remove_button.set_relief(Gtk.ReliefStyle.NONE)
     self._remove_button.connect('clicked', self._on_remove_clicked)
     self._remove_button.show()
     self.pack_start(self._remove_button, False, False, 0)
コード例 #12
0
class SearchFilter(Gtk.HBox):
    """
    A base class used by common search filters
    """

    #: the label of this filter
    label = GObject.Property(type=str, flags=(GObject.ParamFlags.READWRITE))

    gsignal('changed')
    gsignal('removed')
    __gtype_name__ = 'SearchFilter'

    def __init__(self, label=''):
        super(SearchFilter, self).__init__()

        self.props.label = label
        self._label = label
        self._remove_button = None

    def _add_remove_button(self):
        self._remove_button = SearchFilterButton(icon='list-remove-symbolic')
        self._remove_button.set_relief(Gtk.ReliefStyle.NONE)
        self._remove_button.connect('clicked', self._on_remove_clicked)
        self._remove_button.show()
        self.pack_start(self._remove_button, False, False, 0)

    def _on_remove_clicked(self, button):
        self.emit('removed')

    def do_set_property(self, pspec, value):
        if pspec.name == 'label':
            self._label = value
        else:
            raise AssertionError(pspec.name)

    def do_get_property(self, child, property_id, pspec):
        if pspec.name == 'label':
            return self._label
        else:
            raise AssertionError(pspec.name)

    def set_label(self, label):
        self._label = label

    def get_state(self):
        """
        Implement this in a subclass
        """
        raise NotImplementedError

    def get_title_label(self):
        raise NotImplementedError

    def get_mode_combo(self):
        raise NotImplementedError

    def get_description(self):
        """Returns a description of the search filter.
        :returns: a string describing the search filter.
        """
        raise NotImplementedError

    def set_removable(self):
        if self._remove_button is None:
            self._add_remove_button()
コード例 #13
0
class SearchFilter(gtk.HBox):
    """
    A base class used by common search filters
    """

    #: the label of this filter
    label = gobject.property(type=str, flags=(gobject.PARAM_READWRITE))

    gsignal('changed')
    gsignal('removed')
    __gtype_name__ = 'SearchFilter'

    implements(ISearchFilter)

    def __init__(self, label=''):
        gtk.HBox.__init__(self)
        self.props.label = label
        self._label = label
        self._remove_button = None

    def _add_remove_button(self):
        self._remove_button = SearchFilterButton(stock=gtk.STOCK_REMOVE)
        self._remove_button.set_relief(gtk.RELIEF_NONE)
        self._remove_button.set_label_visible(False)
        self._remove_button.connect('clicked', self._on_remove_clicked)
        self._remove_button.show()
        self.pack_start(self._remove_button, False, False)

    def _on_remove_clicked(self, button):
        self.emit('removed')

    def do_set_property(self, pspec, value):
        if pspec.name == 'label':
            self._label = value
        else:
            raise AssertionError(pspec.name)

    def do_get_property(self, child, property_id, pspec):
        if pspec.name == 'label':
            return self._label
        else:
            raise AssertionError(pspec.name)

    def set_label(self, label):
        self._label = label

    def get_state(self):
        """
        Implement this in a subclass
        """
        raise NotImplementedError

    def get_title_label(self):
        raise NotImplementedError

    def get_mode_combo(self):
        raise NotImplementedError

    def get_description(self):
        """Returns a description of the search filter.
        :returns: a string describing the search filter.
        """
        raise NotImplementedError

    def set_removable(self):
        if self._remove_button is None:
            self._add_remove_button()