Source code for camelot.view.controls.formview

#  ============================================================================
#
#  Copyright (C) 2007-2016 Conceptive Engineering bvba.
#  www.conceptive.be / info@conceptive.be
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#      * Redistributions of source code must retain the above copyright
#        notice, this list of conditions and the following disclaimer.
#      * Redistributions in binary form must reproduce the above copyright
#        notice, this list of conditions and the following disclaimer in the
#        documentation and/or other materials provided with the distribution.
#      * Neither the name of Conceptive Engineering nor the
#        names of its contributors may be used to endorse or promote products
#        derived from this software without specific prior written permission.
#  
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#  DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
#  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
#  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#  ============================================================================

"""form view"""

import logging

LOGGER = logging.getLogger('camelot.view.controls.formview')

from ...core.qt import QtGui, QtCore, QtWidgets, Qt, py_to_variant, is_deleted

from camelot.admin.action.application_action import Refresh
from camelot.admin.action.form_action import FormActionGuiContext
from camelot.view.model_thread import post
from camelot.view.controls.view import AbstractView
from camelot.view.controls.busy_widget import BusyWidget
from camelot.view import register
from .delegates.delegatemanager import DelegateManager

[docs]class FormEditors( object ): """A class that holds the editors used on a form """ option = None bold_font = None def __init__( self, columns, widget_mapper, admin ): if self.option == None: self.option = QtGui.QStyleOptionViewItem() # set version to 5 to indicate the widget will appear on a # a form view and not on a table view self.option.version = 5 self._admin = admin self._widget_mapper = widget_mapper self._field_attributes = dict() self._index = dict() for i, (field_name, field_attributes ) in enumerate( columns): self._field_attributes[field_name] = field_attributes self._index[field_name] = i
[docs] def create_editor( self, field_name, parent ): """ :return: a :class:`QtWidgets.QWidget` or `None` if field_name is unknown """ index = self._index[field_name] model = self._widget_mapper.model() delegate = self._widget_mapper.itemDelegate() model_index = model.createIndex(self._widget_mapper.currentIndex(), index, 0) widget_editor = delegate.createEditor( parent, self.option, model_index ) widget_editor.setObjectName('%s_editor'%field_name) stretch = self._field_attributes[field_name].get('stretch', 1) widget_editor.setProperty('stretch', py_to_variant(stretch)) delegate.setEditorData( widget_editor, model_index ) self._widget_mapper.addMapping( widget_editor, index ) return widget_editor
def create_label( self, field_name, editor, parent ): from camelot.view.controls.field_label import FieldLabel from camelot.view.controls.editors.wideeditor import WideEditor field_attributes = self._field_attributes[field_name] hide_title = field_attributes.get( 'hide_title', False ) widget_label = None if not hide_title: widget_label = FieldLabel( field_name, field_attributes['name'], self._admin, parent, ) widget_label.setObjectName('%s_label'%field_name) if not isinstance(editor, WideEditor): widget_label.setAlignment(Qt.AlignVCenter | Qt.AlignRight) editor.set_label(widget_label) return widget_label
[docs]class FormWidget(QtWidgets.QWidget): """A form widget comes inside a form view""" changed_signal = QtCore.qt_signal( int ) def __init__(self, admin, model, form_display, columns, parent): QtWidgets.QWidget.__init__(self, parent) self._admin = admin widget_mapper = QtWidgets.QDataWidgetMapper(self) widget_mapper.setObjectName('widget_mapper') widget_mapper.setItemDelegate(DelegateManager(columns, parent=self)) widget_mapper.currentIndexChanged.connect( self.current_index_changed ) widget_layout = QtWidgets.QHBoxLayout() widget_layout.setSpacing(0) widget_layout.setContentsMargins(0, 0, 0, 0) self._index = 0 self._form = None self._columns = None self.setLayout(widget_layout) self.set_model(model) self.create_widgets(widget_mapper, columns, form_display, admin) def set_model(self, model): widget_mapper = self.findChild(QtWidgets.QDataWidgetMapper, 'widget_mapper') if model is not None: model.dataChanged.connect(self._data_changed) model.layoutChanged.connect(self._layout_changed) model.modelReset.connect(self._model_reset) model.rowsInserted.connect(self._layout_changed) model.rowsRemoved.connect(self._layout_changed) if widget_mapper is not None: widget_mapper.setModel( model ) register.register( model, widget_mapper ) def get_model(self): widget_mapper = self.findChild(QtWidgets.QDataWidgetMapper, 'widget_mapper') if widget_mapper is not None: return widget_mapper.model() def clear_mapping(self): widget_mapper = self.findChild(QtWidgets.QDataWidgetMapper, 'widget_mapper') if widget_mapper: widget_mapper.clearMapping() @QtCore.qt_slot() def _model_reset(self): self._layout_changed() @QtCore.qt_slot( QtCore.QModelIndex, QtCore.QModelIndex ) def _data_changed(self, index_from, index_to): #@TODO: only revert if this form is in the changed range self._layout_changed() @QtCore.qt_slot() def _layout_changed(self): widget_mapper = self.findChild(QtWidgets.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: # after a layout change, the row we want to display might be there if widget_mapper.currentIndex() < 0: widget_mapper.setCurrentIndex(self._index) widget_mapper.revert() self.changed_signal.emit( widget_mapper.currentIndex() ) @QtCore.qt_slot(int) def current_index_changed( self, index ): self.changed_signal.emit( index ) def set_index(self, index): self._index = index widget_mapper = self.findChild(QtWidgets.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.setCurrentIndex(self._index) def get_index(self): widget_mapper = self.findChild(QtWidgets.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: return widget_mapper.currentIndex() def submit(self): widget_mapper = self.findChild(QtWidgets.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.submit()
[docs] def create_widgets(self, widget_mapper, columns, form_display, admin): """Create value and label widgets""" LOGGER.debug( 'begin creating widgets' ) widgets = FormEditors( columns, widget_mapper, admin ) widget_mapper.setCurrentIndex( self._index ) LOGGER.debug( 'put widgets on form' ) self.layout().insertWidget(0, form_display.render( widgets, self, True) ) # give focus to the first editor in the form that can receive focus for i in range(10): first_widget = widget_mapper.mappedWidgetAt(i) if first_widget is None: break if first_widget.focusPolicy() != Qt.NoFocus: first_widget.setFocus(Qt.PopupFocusReason) break LOGGER.debug( 'done' )
[docs]class FormView(AbstractView): """A FormView is the combination of a FormWidget, possible actions and menu items .. form_widget: The class to be used as a the form widget inside the form view""" form_widget = FormWidget def __init__(self, title, admin, model, form_display, columns, index, parent = None): AbstractView.__init__( self, parent ) layout = QtWidgets.QVBoxLayout() layout.setSpacing( 1 ) layout.setContentsMargins( 1, 1, 1, 1 ) layout.setObjectName( 'layout' ) form_and_actions_layout = QtWidgets.QHBoxLayout() form_and_actions_layout.setObjectName('form_and_actions_layout') layout.addLayout( form_and_actions_layout ) self.model = model self.admin = admin self.title_prefix = title self.refresh_action = Refresh() form = FormWidget(admin=admin, model=model, form_display=form_display, columns=columns, parent=self) form.setObjectName( 'form' ) form.changed_signal.connect( self.update_title ) form.set_index(index) form_and_actions_layout.addWidget(form) self.gui_context = FormActionGuiContext() self.gui_context.workspace = self self.gui_context.admin = admin self.gui_context.view = self self.gui_context.widget_mapper = self.findChild( QtWidgets.QDataWidgetMapper, 'widget_mapper' ) self.setLayout( layout ) self.change_title(title) if hasattr(admin, 'form_size') and admin.form_size: self.setMinimumSize(admin.form_size[0], admin.form_size[1]) self.accept_close_event = False @QtCore.qt_slot()
[docs] def refresh(self): """Refresh the data in the current view""" self.model.refresh()
def _get_title( self, index ): obj = self.model._get_object( index ) verbose_identifier = '' if obj is not None: verbose_identifier = self.admin.get_verbose_identifier(obj) return u'%s %s' % ( self.title_prefix, verbose_identifier ) @QtCore.qt_slot( int ) def update_title(self, current_index ): post( self._get_title, self.change_title, args=(current_index,) ) @QtCore.qt_slot(list) def set_actions(self, actions): form = self.findChild(QtWidgets.QWidget, 'form' ) layout = self.findChild(QtGui.QLayout, 'form_and_actions_layout' ) if actions and form and layout: side_panel_layout = QtWidgets.QVBoxLayout() from camelot.view.controls.actionsbox import ActionsBox LOGGER.debug('setting Actions for formview') actions_widget = ActionsBox( parent = self, gui_context = self.gui_context ) actions_widget.setObjectName('actions') actions_widget.set_actions( actions ) side_panel_layout.addWidget( actions_widget ) side_panel_layout.addStretch() layout.addLayout( side_panel_layout ) @QtCore.qt_slot(list) def set_toolbar_actions(self, actions): layout = self.findChild( QtGui.QLayout, 'layout' ) if layout and actions: toolbar = QtWidgets.QToolBar() for action in actions: qaction = action.render( self.gui_context, toolbar ) qaction.triggered.connect( self.action_triggered ) toolbar.addAction( qaction ) toolbar.addWidget( BusyWidget() ) layout.insertWidget( 0, toolbar, 0, Qt.AlignTop ) # @todo : this show is needed on OSX or the form window # is hidden after the toolbar is added, maybe this can # be solved using windowflags, since this causes some # flicker self.show() @QtCore.qt_slot( bool ) def action_triggered( self, _checked = False ): action_action = self.sender() action_action.action.gui_run( self.gui_context ) @QtCore.qt_slot() def validate_close( self ): self.admin.form_close_action.gui_run( self.gui_context ) def close_view( self, accept ): self.accept_close_event = accept if (accept == True) and not is_deleted(self): # clear mapping to prevent data being written again to the model, # when the underlying object would be reverted form = self.findChild( QtWidgets.QWidget, 'form' ) if form is not None: form.clear_mapping() self.close() def closeEvent(self, event): if self.accept_close_event == True: event.accept() else: # make sure the next closeEvent is sent after this one # is processed QtCore.QTimer.singleShot( 0, self.validate_close ) event.ignore()