Home | Trees | Indices | Help |
|
---|
|
1 # ============================================================================ 2 # 3 # Copyright (C) 2007-2008 Conceptive Engineering bvba. All rights reserved. 4 # www.conceptive.be / project-camelot@conceptive.be 5 # 6 # This file is part of the Camelot Library. 7 # 8 # This file may be used under the terms of the GNU General Public 9 # License version 2.0 as published by the Free Software Foundation 10 # and appearing in the file LICENSE.GPL included in the packaging of 11 # this file. Please review the following information to ensure GNU 12 # General Public Licensing requirements will be met: 13 # http://www.trolltech.com/products/qt/opensource.html 14 # 15 # If you are unsure which license is appropriate for your use, please 16 # review the following information: 17 # http://www.trolltech.com/products/qt/licensing.html or contact 18 # project-camelot@conceptive.be. 19 # 20 # This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE 21 # WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 22 # 23 # For use of this library in commercial applications, please contact 24 # project-camelot@conceptive.be 25 # 26 # ============================================================================ 27 28 """form view""" 29 30 import logging 31 logger = logging.getLogger( 'camelot.view.controls.formview' ) 32 33 from PyQt4 import QtCore 34 from PyQt4.QtCore import Qt 35 from PyQt4 import QtGui 36 from camelot.view.model_thread import model_function, post 37 from camelot.view.controls.view import AbstractView40 41 changed_signal = QtCore.SIGNAL( 'changed()' ) 4272 7544 QtGui.QWidget.__init__(self) 45 self._admin = admin 46 self._widget_mapper = QtGui.QDataWidgetMapper() 47 self._widget_layout = QtGui.QHBoxLayout() 48 self._widget_layout.setSpacing( 0 ) 49 self._widget_layout.setMargin( 0 ) 50 self._index = 0 51 self._model = None 52 self._form = None 53 self._columns = None 54 self._delegate = None 55 self.setLayout( self._widget_layout )56 5961 self._model = model 62 sig = 'dataChanged(const QModelIndex &, const QModelIndex &)' 63 self.connect( self._model, QtCore.SIGNAL( sig ), self._data_changed ) 64 self.connect( self._model, QtCore.SIGNAL( 'layoutChanged()' ), self._layout_changed ) 65 self.connect( self._model, self._model.item_delegate_changed_signal, self._item_delegate_changed ) 66 self._widget_mapper.setModel( model ) 67 68 def get_columns_and_form(): 69 return ( self._model.getColumns(), self._admin.get_form_display() )70 71 post( get_columns_and_form, self._set_columns_and_form )77 #@TODO: only revert if this form is in the changed range 78 self._widget_mapper.revert() 79 self.emit(self.changed_signal)80 8486 from camelot.view.controls.delegates.delegatemanager import DelegateManager 87 self._delegate = self._model.getItemDelegate() 88 assert self._delegate 89 assert isinstance(self._delegate, DelegateManager) 90 self._create_widgets()91 95 98100 self._widget_mapper.submit()101 105 109 113 117 121123 """Create value and label widgets""" 124 from camelot.view.controls.field_label import FieldLabel 125 from camelot.view.controls.editors.wideeditor import WideEditor 126 # 127 # Dirty trick to make form views work during unit tests, since unit tests 128 # have no event loop running, so the delegate will never be set, so we get 129 # it and are sure it will be there if we are running without threads 130 # 131 if not self._delegate: 132 self._delegate = self._model.getItemDelegate() 133 # 134 # end of dirty trick 135 # 136 # only if all information is available, we can start building the form 137 if not (self._form and self._columns and self._delegate): 138 return 139 widgets = {} 140 self._widget_mapper.setItemDelegate( self._delegate ) 141 option = QtGui.QStyleOptionViewItem() 142 # set version to 5 to indicate the widget will appear on a 143 # a form view and not on a table view 144 option.version = 5 145 146 # 147 # this loop can take a while to complete, so processEvents is called regulary 148 # 149 for i, ( field_name, field_attributes ) in enumerate( self._columns ): 150 # if i%10==0: 151 # QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.ExcludeSocketNotifiers, 100) 152 model_index = self._model.index( self._index, i ) 153 hide_title = False 154 if 'hide_title' in field_attributes: 155 hide_title = field_attributes['hide_title'] 156 widget_label = None 157 widget_editor = self._delegate.createEditor( self, option, model_index ) 158 if not hide_title: 159 widget_label = FieldLabel( field_name, field_attributes['name'], field_attributes, self._admin ) 160 if not isinstance(widget_editor, WideEditor): 161 widget_label.setAlignment( Qt.AlignVCenter | Qt.AlignRight ) 162 163 # required fields font is bold 164 if ( 'nullable' in field_attributes ) and \ 165 ( not field_attributes['nullable'] ): 166 font = QtGui.QApplication.font() 167 font.setBold( True ) 168 widget_label.setFont( font ) 169 170 assert widget_editor 171 assert isinstance(widget_editor, QtGui.QWidget) 172 173 self._widget_mapper.addMapping( widget_editor, i ) 174 widgets[field_name] = ( widget_label, widget_editor ) 175 176 self._widget_mapper.setCurrentIndex( self._index ) 177 self._widget_layout.insertWidget( 0, self._form.render( widgets, self ) ) 178 self._widget_layout.setContentsMargins( 7, 7, 7, 7 )179181 """A FormView is the combination of a FormWidget, possible actions and menu items 182 183 .. form_widget: The class to be used as a the form widget inside the form view 184 185 """ 186 187 form_widget = FormWidget 188215190 AbstractView.__init__( self ) 191 layout = QtGui.QHBoxLayout() 192 self._form = FormWidget(admin) 193 self.model = model 194 self.title_prefix = title 195 self.admin = admin 196 self.connect(self._form, FormWidget.changed_signal, self.update_title) 197 self._form.set_model(model) 198 self._form.set_index(index) 199 layout.addWidget(self._form) 200 self.change_title(title) 201 self.closeAfterValidation = QtCore.SIGNAL( 'closeAfterValidation()' ) 202 self.setLayout( layout ) 203 204 if hasattr( admin, 'form_size' ) and admin.form_size: 205 self.setMinimumSize( admin.form_size[0], admin.form_size[1] ) 206 207 self.validator = admin.create_validator( model ) 208 self.validate_before_close = True 209 210 def getActions(): 211 return admin.get_form_actions( None )212 213 post( getActions, self.setActions ) 214 self.update_title()217 218 def get_title(): 219 obj = self.getEntity() 220 return u'%s %s' % ( self.title_prefix, self.admin.get_verbose_identifier( obj ) )221 222 post( get_title, self.change_title ) 223 226228 if actions: 229 side_panel_layout = QtGui.QVBoxLayout() 230 from camelot.view.controls.actionsbox import ActionsBox 231 logger.debug( 'setting Actions for formview' ) 232 self.actions_widget = ActionsBox( self, self.getEntity ) 233 action_widgets = self.actions_widget.setActions( actions ) 234 for action_widget in action_widgets: 235 self.connect( self._form, FormWidget.changed_signal, action_widget.changed ) 236 action_widget.changed() 237 side_panel_layout.insertWidget( 1, self.actions_widget ) 238 side_panel_layout.addStretch() 239 self.layout().addLayout(side_panel_layout)240 245247 """select model's last row""" 248 # submit should not happen a second time, since then we don't want 249 # the widgets data to be written to the model 250 self._form.submit() 251 self._form.to_last()252254 """select model's next row""" 255 # submit should not happen a second time, since then we don't want 256 # the widgets data to be written to the model 257 self._form.submit() 258 self._form.to_next()259261 """select model's previous row""" 262 # submit should not happen a second time, since then we don't want 263 # the widgets data to be written to the model 264 self._form.submit() 265 self._form.to_previous()266268 import sip 269 if not valid: 270 reply = self.validator.validityDialog( self._form.get_index(), self ).exec_() 271 if reply == QtGui.QMessageBox.Discard: 272 # clear mapping to prevent data being written again to the model, 273 # then we reverted the row 274 self._form.clear_mapping() 275 self.model.revertRow( self._form.get_index() ) 276 self.validate_before_close = False 277 self.emit( self.closeAfterValidation ) 278 else: 279 self.validate_before_close = False 280 if not sip.isdeleted( self ): 281 self.emit( self.closeAfterValidation )282284 logger.debug( 'validate before close : %s' % self.validate_before_close ) 285 if self.validate_before_close: 286 # submit should not happen a second time, since then we don't 287 # want the widgets data to be written to the model 288 self._form.submit() 289 290 def validate(): 291 return self.validator.isValid( self._form.get_index() )292 293 post( validate, self.showMessage ) 294 return False 295 296 return True 297299 logger.debug( 'formview closed' ) 300 if self.validateClose(): 301 event.accept() 302 else: 303 event.ignore()304 305 @model_function307 """generates html of the form""" 308 from jinja import Environment 309 310 def to_html( d = u'' ): 311 """Jinja 1 filter to convert field values to their default html 312 representation 313 """ 314 315 def wrapped_in_table( env, context, value ): 316 if isinstance( value, list ): 317 return u'<table><tr><td>' + \ 318 u'</td></tr><tr><td>'.join( [unicode( e ) for e in value] ) + \ 319 u'</td></tr></table>' 320 return unicode( value )321 322 return wrapped_in_table 323 324 entity = self.getEntity() 325 fields = self.admin.get_fields() 326 table = [dict( field_attributes = field_attributes, 327 value = getattr( entity, name ) ) 328 for name, field_attributes in fields] 329 330 context = { 331 'title': self.admin.get_verbose_name(), 332 'table': table, 333 } 334 335 from camelot.view.templates import loader 336 env = Environment( loader = loader ) 337 env.filters['to_html'] = to_html 338 tp = env.get_template( 'form_view.html' ) 339 340 return tp.render( context ) 341
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Sat Jun 12 15:42:11 2010 | http://epydoc.sourceforge.net |