Package Camelot :: Package camelot :: Package view :: Package controls :: Module formview
[frames] | no frames]

Source Code for Module Camelot.camelot.view.controls.formview

  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 AbstractView 
38 39 -class FormWidget( QtGui.QWidget ):
40 41 changed_signal = QtCore.SIGNAL( 'changed()' ) 42
43 - def __init__(self, admin):
44 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
57 - def get_model(self):
58 return self._model
59
60 - def set_model(self, model):
61 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 )
72
73 - def clear_mapping(self):
74 self._widget_mapper.clearMapping()
75
76 - def _data_changed( self, index_from, index_to ):
77 #@TODO: only revert if this form is in the changed range 78 self._widget_mapper.revert() 79 self.emit(self.changed_signal)
80
81 - def _layout_changed(self):
82 self._widget_mapper.revert() 83 self.emit(self.changed_signal)
84
85 - def _item_delegate_changed(self):
86 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
92 - def set_index(self, index):
93 self._index = index 94 self._widget_mapper.setCurrentIndex( self._index )
95
96 - def get_index(self):
97 return self._widget_mapper.currentIndex()
98
99 - def submit(self):
100 self._widget_mapper.submit()
101
102 - def to_first(self):
103 self._widget_mapper.toFirst() 104 self.emit(self.changed_signal)
105
106 - def to_last(self):
107 self._widget_mapper.toLast() 108 self.emit(self.changed_signal)
109
110 - def to_next(self):
111 self._widget_mapper.toNext() 112 self.emit(self.changed_signal)
113
114 - def to_previous(self):
115 self._widget_mapper.toPrevious() 116 self.emit(self.changed_signal)
117
118 - def _set_columns_and_form(self, columns_and_form ):
119 self._columns, self._form = columns_and_form 120 self._create_widgets()
121
122 - def _create_widgets( self ):
123 """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 )
179
180 -class FormView( AbstractView ):
181 """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 188
189 - def __init__( self, title, admin, model, index ):
190 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()
215
216 - def update_title( self ):
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
224 - def getEntity( self ):
225 return self.model._get_object( self._form.get_index() )
226
227 - def setActions( self, actions ):
228 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
241 - def viewFirst( self ):
242 """select model's first row""" 243 self._form.submit() 244 self._form.to_first()
245
246 - def viewLast( self ):
247 """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()
252
253 - def viewNext( self ):
254 """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()
259
260 - def viewPrevious( self ):
261 """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()
266
267 - def showMessage( self, valid ):
268 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 )
282
283 - def validateClose( self ):
284 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 297
298 - def closeEvent( self, event ):
299 logger.debug( 'formview closed' ) 300 if self.validateClose(): 301 event.accept() 302 else: 303 event.ignore()
304 305 @model_function
306 - def toHtml( self ):
307 """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