# ============================================================================
#
# 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')
[docs]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()
[docs] class CompletionsModel(QtCore.QAbstractListModel):
def __init__(self, parent=None):
QtCore.QAbstractListModel.__init__(self, parent)
self._completions = []
[docs] def setCompletions(self, completions):
self._completions = completions
self.layoutChanged.emit()
[docs] 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()
[docs] def rowCount(self, index=None):
return len(self._completions)
[docs] 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)
[docs] 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 ''))
[docs] def set_editable(self, editable):
self._editable = editable
self.search_input.setEnabled(editable)
self.search_button.setEnabled(editable)
[docs] def on_arrow_down_key_pressed(self):
self.arrow_down_key_pressed.emit()
[docs] 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
[docs] 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
[docs] 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()
[docs] def completionActivated(self, index):
object_getter = index.data(Qt.EditRole)
self.setEntity(variant_to_pyobject(object_getter))
[docs] def completion_highlighted(self, index ):
object_getter = index.data(Qt.EditRole)
pyob = variant_to_pyobject(object_getter)
self._last_highlighted_entity_getter = pyob
[docs] def returnPressed(self):
if not self.entity_set:
self.createSelectView()
@gui_function
[docs] 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
[docs] 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 )
[docs] def dataChanged(self, index1, index2):
self.setEntity(self.entity_instance_getter, False)
[docs] 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)
[docs] 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)
[docs] 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()
[docs] 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)
[docs] def selectEntity(self, entity_instance_getter):
self.setEntity(entity_instance_getter)