Source code for stoqlib.gui.widgets.searchentry

# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4

##
## Copyright (C) 2013 Async Open Source <http://www.async.com.br>
## All rights reserved
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., or visit: http://www.gnu.org/.
##
## Author(s): Stoq Team <stoq-devel@async.com.br>
##
##
"""A gadget that will transform a regular entry in an advanced entry, that
allows the user to select the object using a regular search."""

import gtk
from kiwi.ui.entry import ENTRY_MODE_DATA

from stoqlib.api import api
from stoqlib.database.queryexecuter import QueryExecuter
from stoqlib.gui.base.dialogs import run_dialog
from stoqlib.gui.search.searchfilters import StringSearchFilter
from stoqlib.gui.search.personsearch import BasePersonSearch, ClientSearch
from stoqlib.gui.wizards.personwizard import run_person_role_dialog
from stoqlib.lib.translation import stoqlib_gettext as _


[docs]class SearchEntryGadget(object): find_tooltip = _('Search') edit_tooltip = _('Edit') new_tooltip = _('Create') def __init__(self, entry, store, model, model_property, search_columns, search_class, parent, run_editor=None): """ This gadget modifies a ProxyEntry turning it into a replacement for ProxyComboEntry. When instanciated, the gadget will remove the entry from the editor, add a gtk.HBox on its place, and re-attach the entry to the newly created hbox. This hbox will also have two buttons: One for showing the related search dialog (or search editor), and another one to add/edit a new object. There are a few advantages in using this instead of a combo: - There is no need to prefill the combo with all the options, which can be very slow depending on the number of objects. - This allows the user to use a better search mechanism, allowing him to filter using multiple keywords and even candidade keys (like a client document) :param entry: The entry that we should modify :param store: The store that will be used for database queries :param model: The model that we are updating :param model_property: Property name of the model that should be updated :param search_columns: Columns that will be queried when the user activates the entry :param search_class: Class of the search editor/dialog that will be displayed when more than one object is found :param parent: The parent that should be respected when running other dialogs :param find_tooltip: the tooltip to use for the search button :param edit_tooltip: the tooltip to use for the edit button :param new_tooltip: the tooltip to use for the new button """ self.store = store self._entry = entry self._model = model # TODO: Maybe this two variables shoulb be a list of properties of the # table instead of strings self._model_property = model_property self._search_columns = search_columns self._search_class = search_class self._parent = parent self._on_run_editor = run_editor # TODO: Respect permission manager self._editor_class = search_class.editor_class # If the search is for a person, the editor is called with a special # function if issubclass(search_class, BasePersonSearch): self._is_person = True else: self._is_person = False self._setup_widgets() self._setup_callbacks() # # Private API # def _setup_widgets(self): self._replace_widget() # Add the two buttons self.find_button = self._create_button(gtk.STOCK_FIND) self.edit_button = self._create_button(gtk.STOCK_NEW) can_edit = self._entry.get_editable() and self._entry.get_sensitive() self.find_button.set_sensitive(can_edit) self.find_button.set_tooltip_text(self.find_tooltip) self.edit_button.set_tooltip_text(self.new_tooltip) # the entry needs a completion to work in MODE_DATA self._completion = gtk.EntryCompletion() self._entry.set_completion(self._completion) self._entry.set_mode(ENTRY_MODE_DATA) initial_value = getattr(self._model, self._model_property) self.set_value(initial_value) # The filter that will be used. This is not really in the interface. We # will just use it to perform the search. self._filter = StringSearchFilter('') self._executer = QueryExecuter(self.store) self._executer.set_search_spec(self._search_class.search_spec) self._executer.set_filter_columns(self._filter, self._search_columns) def _create_button(self, stock): image = gtk.image_new_from_stock(stock, gtk.ICON_SIZE_MENU) button = gtk.Button() button.set_relief(gtk.RELIEF_NONE) button.set_image(image) button.show() self.box.pack_start(button, False, False) return button def _replace_widget(self): # This will remove the entry, add a hbox in the entry old position, and # reattach the entry to this box. The box will then be used to add two # new buttons (one for searching, other for editing/adding new objects container = self._entry.parent # stolen from gazpacho code (widgets/base/base.py): props = {} for pspec in gtk.container_class_list_child_properties(container): props[pspec.name] = container.child_get_property(self._entry, pspec.name) self.box = gtk.HBox() self.box.show() self._entry.reparent(self.box) container.add(self.box) for name, value in props.items(): container.child_set_property(self.box, name, value) def _setup_callbacks(self): self._entry.connect('activate', self._on_entry_activate) self._entry.connect('changed', self._on_entry_changed) self._entry.connect('notify::sensitive', self._on_entry_sensitive) self.find_button.connect('clicked', self._on_find_button__clicked) self.edit_button.connect('clicked', self._on_edit_button__clicked) def _run_search(self): text = self._entry.get_text() value = run_dialog(self._search_class, self._parent, self.store, double_click_confirm=True, initial_string=text) if value: self.set_value(self.get_model_obj(value)) def _run_editor(self): with api.new_store() as store: model = getattr(self._model, self._model_property) model = store.fetch(model) if self._on_run_editor: value = self._on_run_editor(store, model) elif self._is_person: value = run_person_role_dialog(self._editor_class, self._parent, store, model) else: value = run_dialog(self._editor_class, self._parent, store, model) if value: value = self.store.fetch(self.get_model_obj(value)) self.set_value(value) # # Public API #
[docs] def set_value(self, obj): if obj: display_value = obj.get_description() self._entry.prefill([(display_value, obj)]) self.update_edit_button(gtk.STOCK_INFO, self.edit_tooltip) else: display_value = '' self._entry.prefill([]) self.update_edit_button(gtk.STOCK_NEW, self.new_tooltip) self._value = obj self._entry.update(obj) self._entry.set_text(display_value)
[docs] def get_model_obj(self, obj): return obj
[docs] def update_edit_button(self, stock, tooltip): image = gtk.image_new_from_stock(stock, gtk.ICON_SIZE_MENU) self.edit_button.set_image(image) self.edit_button.set_tooltip_text(tooltip)
# # Callbacks # def _on_entry_activate(self, entry): if not self._entry.get_property('editable'): return text = entry.get_text() self._filter.set_state(text) state = self._filter.get_state() results = list(self._executer.search([state])[:2]) if len(results) != 1: # XXX: If nothing is found in the query above, runing the search # will cause the query to be executed a second time. Refactor the # search to allow us to send the initial results avoiding this # second query. return self._run_search() # This means the search returned only one result. self.set_value(self.get_model_obj(results[0])) def _on_entry_changed(self, entry): # If the user edits the value in the entry, it invalidates the value. if self._value: self.set_value(None) def _on_entry_sensitive(self, entry, pspec): can_edit = self._entry.get_editable() and self._entry.get_sensitive() self.find_button.set_sensitive(can_edit) self.edit_button.set_sensitive(can_edit) def _on_edit_button__clicked(self, entry): self._run_editor() def _on_find_button__clicked(self, entry): self._run_search()
[docs]class ClientSearchEntryGadget(SearchEntryGadget): find_tooltip = _('Search for clients') edit_tooltip = _('Edit the selected client') new_tooltip = _('Create a new client') def __init__(self, entry, store, model, parent, model_property='client', search_class=ClientSearch, run_editor=None): search_columns = ['name', 'cpf', 'phone_number', 'mobile_number'] SearchEntryGadget.__init__(self, entry=entry, store=store, model=model, parent=parent, model_property=model_property, search_class=search_class, search_columns=search_columns, run_editor=run_editor)
[docs] def get_model_obj(self, obj): return obj and obj.client
[docs] def set_editable(self, can_edit): self.find_button.set_sensitive(can_edit) self.edit_button.set_sensitive(can_edit) self._entry.set_property('editable', can_edit)