camelot.view.controls.editors.many2oneeditor — Camelot documentation

### Navigation

- index
- modules |
- Camelot documentation »
- Module code »
- camelot.view.controls.editors »

# Source code for camelot.view.controls.editors.many2oneeditor

    #  ============================================================================
    #
    #  Copyright (C) 2007-2011 Conceptive Engineering bvba. All rights reserved.
    #  www.conceptive.be / project-camelot@conceptive.be
    #
    #  This file is part of the Camelot Library.
    #
    #  This file may be used under the terms of the GNU General Public
    #  License version 2.0 as published by the Free Software Foundation
    #  and appearing in the file license.txt included in the packaging of
    #  this file.  Please review this information to ensure GNU
    #  General Public Licensing requirements will be met.
    #
    #  If you are unsure which license is appropriate for your use, please
    #  visit www.python-camelot.com or contact project-camelot@conceptive.be
    #
    #  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
    #  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
    #
    #  For use of this library in commercial applications, please contact
    #  project-camelot@conceptive.be
    #
    #  ============================================================================

    from PyQt4 import QtGui
    from PyQt4 import QtCore
    from PyQt4.QtCore import Qt

    from camelot.view.art import Icon
    from camelot.view.model_thread import post
    from camelot.view.model_thread import gui_function
    from camelot.view.model_thread import model_function
    from camelot.view.search import create_entity_search_query_decorator
    from camelot.view.controls.decorated_line_edit import DecoratedLineEdit

    from camelot.core.utils import ugettext as _
    from camelot.core.utils import variant_to_pyobject
    from camelot.core.utils import create_constant_function

    from customeditor import CustomEditor, set_background_color_palette
    from abstractmanytooneeditor import AbstractManyToOneEditor

    import logging
    logger = logging.getLogger('camelot.view.controls.editors.many2oneeditor')

    class Many2OneEditor(CustomEditor, AbstractManyToOneEditor):
        """Widget for editing many 2 one relations"""

        new_icon = Icon('tango/16x16/actions/document-new.png')
        search_icon = Icon('tango/16x16/actions/system-search.png')

        arrow_down_key_pressed = QtCore.pyqtSignal()

        class CompletionsModel(QtCore.QAbstractListModel):

            def __init__(self, parent=None):
                QtCore.QAbstractListModel.__init__(self, parent)
                self._completions = []

            def setCompletions(self, completions):
                self._completions = completions
                self.layoutChanged.emit()

            def data(self, index, role):
                if role == Qt.DisplayRole:
                    return QtCore.QVariant(self._completions[index.row()][0])
                elif role == Qt.EditRole:
                    return QtCore.QVariant(self._completions[index.row()][1])
                return QtCore.QVariant()

            def rowCount(self, index=None):
                return len(self._completions)

            def columnCount(self, index=None):
                return 1

        def __init__(self,
                     admin=None,
                     parent=None,
                     editable=True,
                     field_name='manytoone',
                     **kwargs):
            """:param entity_admin : The Admin interface for the object on the one
            side of the relation
            """

            CustomEditor.__init__(self, parent)
            self.setObjectName( field_name )
            self.admin = admin
            self.entity_set = False
            self._editable = editable
            self._entity_representation = ''
            self.entity_instance_getter = None
            self._last_highlighted_entity_getter = None

            self.layout = QtGui.QHBoxLayout()
            self.layout.setSpacing(0)
            self.layout.setContentsMargins( 0, 0, 0, 0)

            # Search button
            self.search_button = QtGui.QToolButton()
            self.search_button.setAutoRaise(True)
            self.search_button.setFocusPolicy(Qt.ClickFocus)
            self.search_button.setFixedHeight(self.get_height())
            self.search_button.clicked.connect(self.searchButtonClicked)
            self.search_button.setIcon(
                Icon('tango/16x16/actions/edit-clear.png').getQIcon()
            )
            self.search_button.setToolTip(unicode(_('clear')))

            # Open button
            self.open_button = QtGui.QToolButton()
            self.open_button.setAutoRaise(True)
            self.open_button.setFocusPolicy(Qt.ClickFocus)
            self.open_button.setFixedHeight(self.get_height())
            self.open_button.clicked.connect(self.openButtonClicked)
            self.open_button.setIcon( self.new_icon.getQIcon() )
            self.open_button.setToolTip(unicode(_('new')))

            # Search input
            self.search_input = DecoratedLineEdit(self)
            self.search_input.set_background_text(_('Search...'))
            self.search_input.textEdited.connect(self.textEdited)
            self.search_input.set_minimum_width( 20 )
            self.search_input.arrow_down_key_pressed.connect(self.on_arrow_down_key_pressed)
            # suppose garbage was entered, we need to refresh the content
            self.search_input.editingFinished.connect( self.search_input_editing_finished )
            self.setFocusProxy(self.search_input)

            # Search Completer
            self.completer = QtGui.QCompleter()
            self.completions_model = self.CompletionsModel(self.completer)
            self.completer.setModel(self.completions_model)
            self.completer.setCaseSensitivity(Qt.CaseInsensitive)
            self.completer.setCompletionMode(
                QtGui.QCompleter.UnfilteredPopupCompletion
            )
            #self.completer.activated.connect(self.completionActivated)
            #self.completer.highlighted.connect(self.completion_highlighted)
            self.completer.activated[QtCore.QModelIndex].connect(self.completionActivated)
            self.completer.highlighted[QtCore.QModelIndex].connect(self.completion_highlighted)
            self.search_input.setCompleter(self.completer)

            # Setup layout
            self.layout.addWidget(self.search_input)
            self.layout.addWidget(self.search_button)
            self.layout.addWidget(self.open_button)
            self.setLayout(self.layout)

        def set_field_attributes(self, editable = True,
                                       background_color = None,
                                       tooltip = None, **kwargs):
            self.set_editable(editable)
            set_background_color_palette( self.search_input, background_color )
            self.search_input.setToolTip(unicode(tooltip or ''))

        def set_editable(self, editable):
            self._editable = editable
            self.search_input.setEnabled(editable)
            self.search_button.setEnabled(editable)

        def on_arrow_down_key_pressed(self):
            self.arrow_down_key_pressed.emit()

        def textEdited(self, text):
            self._last_highlighted_entity_getter = None
            text = self.search_input.user_input()

            def create_search_completion(text):
                return lambda: self.search_completions(text)

            post(
                create_search_completion(unicode(text)),
                self.display_search_completions
            )
            self.completer.complete()

        @model_function
        def search_completions(self, text):
            """Search for object that match text, to fill the list of completions

            :return: a list of tuples of (object_representation, object_getter)
            """
            search_decorator = create_entity_search_query_decorator(
                self.admin, text
            )
            if search_decorator:
                sresult = [
                    (unicode(e), create_constant_function(e))
                    for e in search_decorator(self.admin.entity.query).limit(20)
                ]
                return text, sresult
            return text, []

        @gui_function
        def display_search_completions(self, prefix_and_completions):
            prefix, completions = prefix_and_completions
            self.completions_model.setCompletions(completions)
            self.completer.setCompletionPrefix(prefix)
            self.completer.complete()

        def completionActivated(self, index):
            object_getter = index.data(Qt.EditRole)
            self.setEntity(variant_to_pyobject(object_getter))

        def completion_highlighted(self, index ):
            object_getter = index.data(Qt.EditRole)
            pyob = variant_to_pyobject(object_getter)
            self._last_highlighted_entity_getter = pyob

        def openButtonClicked(self):
            if self.entity_set:
                return self.createFormView()
            else:
                return self.createNew()

        def returnPressed(self):
            if not self.entity_set:
                self.createSelectView()

        def searchButtonClicked(self):
            if self.entity_set:
                self.setEntity(lambda:None)
            else:
                self.createSelectView()

        def trashButtonClicked(self):
            self.setEntity(lambda:None)

        @gui_function
        def createNew(self):

            @model_function
            def get_has_subclasses():
                return len(self.admin.get_subclass_tree())

            post(get_has_subclasses, self.show_new_view)

        @gui_function
        def show_new_view(self, has_subclasses):
            from camelot.view.workspace import show_top_level
            selected = QtGui.QDialog.Accepted
            admin = self.admin
            if has_subclasses:
                from camelot.view.controls.inheritance import SubclassDialog
                select_subclass = SubclassDialog(self, self.admin)
                select_subclass.setWindowTitle(_('select'))
                selected = select_subclass.exec_()
                admin = select_subclass.selected_subclass
            if selected:
                form = admin.create_new_view()
                form.entity_created_signal.connect(self.selectEntity)
                # @todo: dirty trick to keep reference
                #self.__new_form = form
                show_top_level( form, self )

        def createFormView(self):
            if self.entity_instance_getter:

                def get_admin_and_title():
                    obj = self.entity_instance_getter()
                    admin = self.admin.get_related_entity_admin(obj.__class__)
                    return admin, ''

                post(get_admin_and_title, self.show_form_view)

        def show_form_view(self, admin_and_title):
            from camelot.view.workspace import show_top_level
            admin, title = admin_and_title

            def create_collection_getter(instance_getter):
                return lambda:[instance_getter()]

            from camelot.view.proxy.collection_proxy import CollectionProxy

            model = CollectionProxy(
                admin,
                create_collection_getter(self.entity_instance_getter),
                admin.get_fields
            )
            model.dataChanged.connect(self.dataChanged)
            form = admin.create_form_view(title, model, 0)
            # @todo : dirty trick to keep reference
            #self.__form = form
            show_top_level( form, self )

        def dataChanged(self, index1, index2):
            self.setEntity(self.entity_instance_getter, False)

        def search_input_editing_finished(self):
            if not self.entity_set:
                # Only try to 'guess' what the user meant when no entity is set
                # to avoid inappropriate removal of data, (eg when the user presses
                # Esc, editingfinished will be called as well, and we should not
                # overwrite the current entity set)
                if self._last_highlighted_entity_getter:
                    self.setEntity(self._last_highlighted_entity_getter)
                elif not self.entity_set and self.completions_model.rowCount()==1:
                    # There is only one possible option
                    index = self.completions_model.index(0,0)
                    entity_getter = variant_to_pyobject(index.data(Qt.EditRole))
                    self.setEntity(entity_getter)
            self.search_input.set_user_input(self._entity_representation)

        def set_value(self, value):
            """:param value: either ValueLoading, or a function that returns None
            or the entity to be shown in the editor"""
            self._last_highlighted_entity_getter = None
            value = CustomEditor.set_value(self, value)
            if value:
                self.setEntity(value, propagate = False)

        def get_value(self):
            """:return: a function that returns the selected entity or ValueLoading
            or None"""
            value = CustomEditor.get_value(self)
            if not value:
                value = self.entity_instance_getter
            return value

        @QtCore.pyqtSlot(tuple)
        def set_instance_representation(self, representation_and_propagate):
            """Update the gui"""
            ((desc, pk), propagate) = representation_and_propagate
            self._entity_representation = desc
            self.search_input.set_user_input(desc)

            if pk != False:
                self.open_button.setIcon(
                    Icon('tango/16x16/places/folder.png').getQIcon()
                )
                self.open_button.setToolTip(unicode(_('open')))
                self.open_button.setEnabled(True)

                self.search_button.setIcon(
                    Icon('tango/16x16/actions/edit-clear.png').getQIcon()
                )
                self.search_button.setToolTip(unicode(_('clear')))
                self.entity_set = True
            else:
                self.open_button.setIcon( self.new_icon.getQIcon() )
                self.open_button.setToolTip(unicode(_('new')))
                self.open_button.setEnabled(self._editable)

                self.search_button.setIcon( self.search_icon.getQIcon() )
                self.search_button.setToolTip(_('Search'))
                self.entity_set = False

            if propagate:
                self.editingFinished.emit()

        def setEntity(self, entity_instance_getter, propagate=True):

            def create_instance_getter(entity_instance):
                return lambda:entity_instance

            def get_instance_representation():
                """Get a representation of the instance

                :return: (unicode, pk) its unicode representation and its primary
                key or ('', False) if the instance was None"""
                entity = entity_instance_getter()
                self.entity_instance_getter = create_instance_getter(entity)
                if entity and hasattr(entity, 'id'):
                    return ((unicode(entity), entity.id), propagate)
                elif entity:
                    return ((unicode(entity), False), propagate)
                return ((None, False), propagate)

            post(get_instance_representation, self.set_instance_representation)

        def selectEntity(self, entity_instance_getter):
            self.setEntity(entity_instance_getter)

### Navigation

- index
- modules |
- Camelot documentation »
- Module code »
- camelot.view.controls.editors »

Comments
blog comments powered by Disqus