CMS 3D CMS Logo

PropertyView.py
Go to the documentation of this file.
1 import logging
2 import sys
3 import os.path
4 
5 from PyQt4.QtCore import Qt,SIGNAL,QCoreApplication,QSize
6 from PyQt4.QtGui import QTableWidget,QTableWidgetItem,QCheckBox,QWidget,QSpinBox,QHBoxLayout,QVBoxLayout,QLineEdit,QSizePolicy,QTextEdit,QTextOption,QFrame,QToolButton,QPalette,QComboBox, QFileDialog,QTextCursor,QInputDialog,QPushButton,QGridLayout,QIcon,QHeaderView,QMessageBox
7 
8 from Vispa.Main.Application import Application
9 from Vispa.Main.AbstractTab import AbstractTab
10 from Vispa.Share.BasicDataAccessor import BasicDataAccessor
11 from Vispa.Views.AbstractView import AbstractView
12 from Vispa.Share.ThreadChain import ThreadChain
13 from Vispa.Gui.TextDialog import TextDialog
14 
15 class ClosableProperty(QWidget):
16  def __init__(self,property):
17  QWidget.__init__(self)
18  self.setContentsMargins(0, 0, 0, 0)
19  self.setLayout(QHBoxLayout())
20  self.layout().setSpacing(0)
21  self.layout().setContentsMargins(0, 0, 0, 0)
22  self.layout().addWidget(property)
23  self._closeButton=QToolButton()
24  self._closeButton.setText("x")
25  self._closeButton.hide()
26  self._property=property
27  self.layout().addWidget(self._closeButton)
28  def closableProperty(self):
29  return self._property
30  def closeButton(self):
31  return self._closeButton
32  def enterEvent(self,event):
33  self._closeButton.show()
34  def leaveEvent(self,event):
35  self._closeButton.hide()
36 
37 class ComboBoxReturn(QComboBox):
38  def keyPressEvent(self,event):
39  QComboBox.keyPressEvent(self,event)
40  if event.key()==Qt.Key_Return:
41  self.emit(SIGNAL("returnPressed()"))
42 
43 class PropertyView(QTableWidget, AbstractView):
44  """ Shows properties of an object in a QTableWidget using the DataAccessor.
45 
46  The view can be used readonly ('setReadOnly') or for editing.
47  On editing the signals 'valueChanged', 'propertyDeleted', 'propertyAdded' are emitted.
48  """
49 
50  LABEL = "&Property View"
51 
52  def __init__(self, parent=None, name=None):
53  """ Constructor """
54  #logging.debug(self.__class__.__name__ + ": __init__()")
55  AbstractView.__init__(self)
56  QTableWidget.__init__(self, parent)
57 
58  self._operationId = 0
60  self.updateIni = False
62  self._relativePath=None
63  self.setSortingEnabled(False)
64  self.verticalHeader().hide()
65  self.setSelectionMode(QTableWidget.NoSelection)
66  self.clear() # sets header
67 
68  self._readOnly = False
70 
71  self.connect(self.horizontalHeader(), SIGNAL("sectionResized(int,int,int)"), self.sectionResized)
72  self.connect(self, SIGNAL("itemDoubleClicked(QTableWidgetItem *)"), self.itemDoubleClickedSlot)
73 
74  def cancel(self):
75  """ Stop all running operations.
76  """
77  self._operationId += 1
78 
79  def clear(self):
80  """ Clear the table and set the header label.
81  """
82  QTableWidget.clear(self)
83  self.setRowCount(0)
84  self.setColumnCount(2)
85  self.setHorizontalHeaderLabels(['Property', 'Value'])
86 
87  def propertyWidgets(self):
88  """ Return all property widgets in the right column.
89 
90  Closable as well as normal properties are returned.
91  """
92  widgets=[]
93  for i in range(self.rowCount()):
94  widget=self.cellWidget(i,1)
95  if isinstance(widget,Property):
96  widgets+=[(widget,i)]
97  elif hasattr(widget,"closableProperty"):
98  widgets+=[(widget.closableProperty(),i)]
99  return widgets
100 
101  def updatePropertyHeight(self,property):
102  """ Update the height of the column that holds a certain property.
103  """
104  #logging.debug(self.__class__.__name__ + ": updatePropertyHeight()")
105  for widget,i in self.propertyWidgets():
106  if widget==property:
107  self.verticalHeader().resizeSection(i, property.properyHeight())
108  return
109 
110  def append(self, property):
111  """ Adds a property to the PropertyView and returns it.
112  """
113  property.setPropertyView(self)
114  if self._readOnly:
115  property.setReadOnly(True)
116  self.insertRow(self.lastRow()+1)
117  self.setItem(self.lastRow(), 0, LabelItem(property))
118  if not self._readOnly and self._showAddDeleteButtonFlag and property.deletable():
119  widget=ClosableProperty(property)
120  self.connect(widget.closeButton(), SIGNAL('clicked(bool)'), self.removeProperty)
121  self.setCellWidget(self.lastRow(), 1, widget)
122  else:
123  self.setCellWidget(self.lastRow(), 1, property)
124  self.updatePropertyHeight(property)
125  self.connect(property, SIGNAL('updatePropertyHeight'), self.updatePropertyHeight)
126  return property
127 
128  def lastRow(self):
129  """ Return the last row holding a property.
130 
131  The row with the add new property field is not counted.
132  """
133  if not self._readOnly and self._showAddDeleteButtonFlag and not self._updatingFlag>0:
134  return self.rowCount() - 2
135  else:
136  return self.rowCount() - 1
137 
138  def addCategory(self, name):
139  """ Add a category row to the tabel which consists of two gray LabelItems.
140  """
141  self.insertRow(self.lastRow()+1)
142  self.setItem(self.lastRow(), 0, LabelItem(name, Qt.lightGray))
143  self.setItem(self.lastRow(), 1, LabelItem("", Qt.lightGray))
144  self.verticalHeader().resizeSection(self.rowCount() - 1, Property.DEFAULT_HEIGHT)
145  return name
146 
147  def setReadOnly(self, readOnly):
148  """ Sets all properties in the PropertyView to read-only.
149 
150  After calling this function all properties that are added are set to read-only as well.
151  """
152  #logging.debug('PropertyView: setReadOnly()')
153  self._readOnly = readOnly
154  for property,i in self.propertyWidgets():
155  if property:
156  property.setReadOnly(self._readOnly)
157 
158  def readOnly(self):
159  return self._readOnly
160 
161  def setShowAddDeleteButton(self,show):
162  self._showAddDeleteButtonFlag=show
163 
165  return self._showAddDeleteButtonFlag
166 
167  def resizeEvent(self, event):
168  """ Resize columns when table size is changed.
169  """
170  if event != None:
171  QTableWidget.resizeEvent(self, event)
172  space = self.width() - 4
173  if self.verticalScrollBar().isVisible():
174  space -= self.verticalScrollBar().width()
175  space -= self.columnWidth(0)
176  self.setColumnWidth(1, space)
177  if self.updateIni:
178  self.writeIni()
179 
180  def sectionResized(self,index,old,new):
181  space = self.width() - 4
182  if self.verticalScrollBar().isVisible():
183  space -= self.verticalScrollBar().width()
184  space -= self.columnWidth(0)
185  self.setColumnWidth(1, space)
186  if self.updateIni:
187  self.writeIni()
188 
189  def setDataAccessor(self, accessor):
190  """ Sets the DataAccessor from which the object properties are read.
191 
192  You need to call updateContent() in order to make the changes visible.
193  """
194  if not isinstance(accessor, BasicDataAccessor):
195  raise TypeError(__name__ + " requires data accessor of type BasicDataAccessor.")
196  AbstractView.setDataAccessor(self, accessor)
197 
198  def appendAddRow(self):
199  """ Append a row with a field to add new properties.
200  """
201  self.insertRow(self.lastRow()+1)
202  lineedit=QLineEdit()
203  lineedit.setFrame(False)
204  lineedit.setContentsMargins(0, 0, 0, 0)
205  self.setCellWidget(self.lastRow(), 0, lineedit)
206  widget=QWidget()
207  widget.setContentsMargins(0, 0, 0, 0)
208  widget.setLayout(QHBoxLayout())
209  widget.layout().setSpacing(0)
210  widget.layout().setContentsMargins(0, 0, 0, 0)
211  typelist=ComboBoxReturn()
212  types=["String","Boolean","Integer","Double","File","FileVector"]
213  for type in types:
214  typelist.addItem(type)
215  widget.layout().addWidget(typelist)
216  addButton=QToolButton()
217  addButton.setText("+")
218  widget.layout().addWidget(addButton)
219  self.setCellWidget(self.lastRow(), 1, widget)
220  self.verticalHeader().resizeSection(self.lastRow(), Property.DEFAULT_HEIGHT)
221  self.connect(addButton, SIGNAL('clicked(bool)'), self.addProperty)
222  self.connect(lineedit, SIGNAL('returnPressed()'), self.addProperty)
223  self.connect(typelist, SIGNAL('returnPressed()'), self.addProperty)
224  addButton._lineedit=lineedit
225  addButton._typelist=typelist
226  lineedit._lineedit=lineedit
227  lineedit._typelist=typelist
228  typelist._lineedit=lineedit
229  typelist._typelist=typelist
230 
231  def updateContent(self):
232  """ Fill the properties of an object in the PropertyView using the DataAccessor.
233  """
234  #logging.debug('PropertyView: updateContent()')
235  self.cancel()
236  if self.dataAccessor() == None:
237  return False
238  self._updatingFlag+=1
239  self.clear()
240  if self.dataObject()==None:
241  self._updatingFlag-=1
242  return True
243  self._ignoreValueChangeFlag = True # prevent infinite loop
244  operationId = self._operationId
245  # do not use threads here since this may lead to crashes
246  for property in self.dataAccessor().properties(self.dataObject()):
247  if property[0] == "Category":
248  self._currentCategoryName = self.addCategory(property[1])
249  else:
250  propertyWidget=PropertyView.propertyWidgetFromProperty(property, self._currentCategoryName)
251  if propertyWidget:
252  self.append(propertyWidget)
253  if isinstance(propertyWidget,(FileProperty,FileVectorProperty)):
254  propertyWidget.useRelativePaths(self._relativePath)
255  if isinstance(propertyWidget,QCheckBox):
256  propertyWidget.setChecked(property[2],False) # strange, QCheckBox forgets its state on append in Qt 4.4.4
257  if not self._readOnly and self._showAddDeleteButtonFlag:
258  self.appendAddRow()
259  self.resizeEvent(None)
260  self._ignoreValueChangeFlag = False
261  self._updatingFlag-=1
262  return self._operationId==operationId
263 
264  #@staticmethod
265  def propertyWidgetFromProperty(property, categoryName=None):
266  """ Create a property widget from a property tuple.
267 
268  This function is static in order to be used by other view, e.g. TableView.
269  """
270  propertyWidget=None
271  if property[0] == "String":
272  propertyWidget=StringProperty(property[1], property[2], categoryName)
273  elif property[0] == "MultilineString":
274  propertyWidget=StringProperty(property[1], property[2], categoryName, True)
275  elif property[0] == "File":
276  propertyWidget=FileProperty(property[1], property[2], categoryName)
277  elif property[0] == "FileVector":
278  propertyWidget=FileVectorProperty(property[1], property[2], categoryName)
279  elif property[0] == "Boolean":
280  propertyWidget = BooleanProperty(property[1], property[2], categoryName)
281  elif property[0] == "Integer":
282  propertyWidget=IntegerProperty(property[1], property[2], categoryName)
283  elif property[0] == "Double":
284  propertyWidget=DoubleProperty(property[1], property[2], categoryName)
285  elif property[0] == "DropDown":
286  propertyWidget=DropDownProperty(property[1], property[2], property[6], categoryName)
287  else:
288  logging.error(__name__+": propertyWidgetFromProperty() - Unknown property type "+str(property[0]))
289  return None
290  if len(property) > 3 and property[3]:
291  propertyWidget.setUserInfo(property[3])
292  if len(property) > 4 and property[4]:
293  propertyWidget.setReadOnly(True)
294  if len(property) > 5 and property[5]:
295  propertyWidget.setDeletable(True)
296  return propertyWidget
297  propertyWidgetFromProperty = staticmethod(propertyWidgetFromProperty)
298 
299  def valueChanged(self, property):
300  """ This function is called when a property a changed.
301 
302  The DataAcessor is called to handle the property change.
303  """
304  if self.dataAccessor() and not self._ignoreValueChangeFlag:
305  bad=False
306  newvalue = property.value()
307  oldValue = self.dataAccessor().propertyValue(self.dataObject(), property.name())
308  if newvalue != oldValue:
309  if isinstance(newvalue,ValueError):
310  result=str(newvalue)
311  else:
312  result=self.dataAccessor().setProperty(self.dataObject(), property.name(), newvalue, property.categoryName())
313  if result==True:
314  self.emit(SIGNAL('valueChanged'),property.name(), newvalue, oldValue, property.categoryName())
315  else:
316  print "valueChanged() result = ", result, type(result)
317  property.setToolTip(result)
318  QMessageBox.critical(self.parent(), 'Error', result)
319  bad=True
320  property.setHighlighted(bad)
321 
322  def removeProperty(self, bool=False):
323  """ This function deletes a property.
324 
325  The DataAcessor is called to handle the property remove.
326  """
327  property=self.sender().parent()._property
328  name=property.name()
329  if self.dataAccessor():
330  if self.dataAccessor().removeProperty(self.dataObject(), property.name()):
331  for p,i in self.propertyWidgets():
332  if p==property:
333  self.removeRow(i)
334  self.emit(SIGNAL('propertyDeleted'),name)
335 
336  def addProperty(self, bool=False):
337  """ This function adds a property.
338 
339  The DataAcessor is called to add the property.
340  """
341  type=str(self.sender()._typelist.currentText())
342  name=str(self.sender()._lineedit.text().toAscii())
343  if type in ["String","File"]:
344  value=""
345  elif type in ["Integer","Double"]:
346  value=0
347  elif type in ["FileVector"]:
348  value=()
349  elif type in ["Boolean"]:
350  value=False
351  if name==None or name=="":
352  QCoreApplication.instance().infoMessage("Please specify name of property.")
353  return
354  if self.dataAccessor():
355  if self.dataAccessor().addProperty(self.dataObject(), name, value, type):
356  property=self.propertyWidgetFromProperty((type,name,value,None,False,True), self._currentCategoryName)
357  if property:
358  self.append(property)
359  if isinstance(property,(FileProperty,FileVectorProperty)):
360  property.useRelativePaths(self._relativePath)
361  self.sender()._lineedit.setText("")
362  property.setFocus()
363  self.emit(SIGNAL('propertyAdded'),property.name())
364 
365  def itemDoubleClickedSlot(self, item):
366  """ Slot for itemClicked() signal.
367 
368  Calls items's property's doubleClicked().
369  """
370  #logging.debug(self.__class__.__name__ + ": itemDoubleClickedSlot()")
371  if item.property():
372  item.property().labelDoubleClicked()
373 
374  def useRelativePaths(self,path):
375  self._relativePath=path
376 
377 class LabelItem(QTableWidgetItem):
378  """ A QTableWidgetItem with a convenient constructor.
379  """
380  def __init__(self, argument, color=Qt.white):
381  """ Constructor.
382 
383  Argument may be either a string or a Property object.
384  If argument is the latter the property's user info will be used for the label's tooltip.
385  """
386  if isinstance(argument, Property):
387  tooltip = argument.name() + " (" + argument.userInfo() + ")"
388  name = argument.name()
389  self._property = argument
390  else:
391  tooltip = argument
392  name = argument
393  self._property = None
394 
395  QTableWidgetItem.__init__(self, name)
396  self.setToolTip(tooltip)
397  self.setFlags(Qt.ItemIsEnabled)
398  self.setBackgroundColor(color)
399 
400  def property(self):
401  return self._property
402 
404  """ Mother of all properties which can be added to the PropertyView using its append() function.
405  """
406 
407  USER_INFO = "General property"
408  DEFAULT_HEIGHT = 20
409 
410  def __init__(self, name, categoryName=None):
411  self.setName(name)
412  self.setUserInfo(self.USER_INFO)
413  self._propertyView = None
414  self._deletable=False
415  self._categoryName = categoryName
416 
417  def setName(self, name):
418  """ Sets the name of this property.
419  """
420  self._name = name
421 
422  def name(self):
423  """ Return the name of this property.
424  """
425  return self._name
426 
427  def categoryName(self):
428  return self._categoryName
429 
430  def setDeletable(self,deletable):
431  self._deletable=deletable
432 
433  def deletable(self):
434  return self._deletable
435 
436  def setPropertyView(self, propertyView):
437  """ Sets PropertyView object.
438  """
439  self._propertyView = propertyView
440 
441  def propertyView(self):
442  """ Returns property view.
443  """
444  return self._propertyView
445 
446  def setUserInfo(self, info):
447  """ Returns user info string containing information on type of property and what data may be insert.
448  """
449  self._userInfo=info
450 
451  def userInfo(self):
452  """ Returns user info string containing information on type of property and what data may be insert.
453  """
454  return self._userInfo
455 
456  def setReadOnly(self, readOnly):
457  """ Disables editing functionality.
458  """
459  pass
460 
461  def properyHeight(self):
462  """ Return the height of the property widget.
463  """
464  return self.DEFAULT_HEIGHT
465 
466  def setValue(self, value):
467  """ Abstract function returning current value of this property.
468 
469  Has to be implemented by properties which allow the user to change their value.
470  """
471  raise NotImplementedError
472 
473  def value(self):
474  """ Abstract function returning current value of this property.
475 
476  Has to be implemented by properties which allow the user to change their value.
477  """
478  raise NotImplementedError
479 
480  def valueChanged(self):
481  """ Slot for change events.
482 
483  The actual object which have changed should connect their value changed signal
484  (or similar) to this function to forward change to data accessor of PropertyView.
485  """
486  logging.debug('Property: valueChanged() ' + str(self.name()))
487  if self.propertyView():
488  self.propertyView().valueChanged(self)
489 
491  """ Called by PropertyView itemDoubleClicked().
492  """
493  pass
494 
495  def setHighlighted(self,highlight):
496  """ Highlight the property, e.g. change color.
497  """
498  pass
499 
500 class BooleanProperty(Property, QCheckBox):
501  """ Property holding a check box for boolean values.
502  """
503 
504  USER_INFO = "Enable / Disable"
505 
506  def __init__(self, name, value, categoryName=None):
507  """ Constructor.
508  """
509  Property.__init__(self, name, categoryName)
510  QCheckBox.__init__(self)
511  self.connect(self, SIGNAL('stateChanged(int)'), self.valueChanged)
512 
513  def setChecked(self, check, report=True):
514  if not report:
515  self.disconnect(self, SIGNAL('stateChanged(int)'), self.valueChanged)
516  QCheckBox.setChecked(self, check)
517  if not report:
518  self.connect(self, SIGNAL('stateChanged(int)'), self.valueChanged)
519 
520  def setReadOnly(self, readOnly):
521  """ Disables editing functionality.
522  """
523  if readOnly:
524  self.setEnabled(False)
525  self.disconnect(self, SIGNAL('stateChanged(int)'), self.valueChanged)
526  else:
527  self.setEnabled(True)
528  self.connect(self, SIGNAL('stateChanged(int)'), self.valueChanged)
529 
530  def value(self):
531  """ Returns True if check box is checked.
532  """
533  return self.isChecked()
534 
535 class DropDownProperty(Property, QComboBox):
536  """ Property holding a check box for boolean values.
537  """
538 
539  USER_INFO = "Drop down field"
540 
541  def __init__(self, name, value, values, categoryName=None):
542  """ Constructor.
543  """
544  Property.__init__(self, name, categoryName)
545  QComboBox.__init__(self)
546  self._values=values
547  for v in values:
548  self.addItem(str(v))
549  if value in values:
550  self.setCurrentIndex(values.index(value))
551  self.connect(self, SIGNAL('currentIndexChanged(int)'), self.valueChanged)
552 
553  def setReadOnly(self, readOnly):
554  """ Disables editing functionality.
555  """
556  if readOnly:
557  self.setEnabled(False)
558  self.disconnect(self, SIGNAL('currentIndexChanged(int)'), self.valueChanged)
559  else:
560  self.setEnabled(True)
561  self.connect(self, SIGNAL('currentIndexChanged(int)'), self.valueChanged)
562 
563  def value(self):
564  """ Returns True if check box is checked.
565  """
566  return self._values[self.currentIndex()]
567 
568 class TextEdit(QTextEdit):
569  def focusOutEvent(self,event):
570  QTextEdit.focusOutEvent(self,event)
571  self.emit(SIGNAL("editingFinished()"))
572 
574  """ This class provides a PropertyView property holding an editable text and a button.
575 
576  It is possible to hide the button unless the mouse cursor is over the property. This feature is turned on by default. See setAutohideButton().
577  If the button is pressed nothing happens. This functionality should be implemented in sub-classes. See buttonClicked().
578  The text field can hold single or multiple lines. See setMultiline()
579  """
580 
581  BUTTON_LABEL = ''
582  AUTOHIDE_BUTTON = True
583 
584  def __init__(self, name, value, categoryName=None, multiline=False):
585  """ The constructor creates a QHBoxLayout and calls createLineEdit(), createTextEdit() and createButton().
586  """
587  Property.__init__(self, name, categoryName)
588  QWidget.__init__(self)
589  self._lineEdit = None
590  self._textEdit = None
591  self._button = None
592  self.setAutohideButton(self.AUTOHIDE_BUTTON)
593 
594  self.setLayout(QHBoxLayout())
595  self.layout().setSpacing(0)
596  self.layout().setContentsMargins(0, 0, 0, 0)
597 
598  self._readOnly = False
599  self._multiline = False
600 
601  self.createLineEdit()
602  self.createTextEdit()
603  self.createButton()
604  self.setMultiline(multiline)
605  self.setValue(value)
606 
607  def setValue(self, value):
608  """ Sets value of text edit.
609  """
610  self._originalValue=value
611  if value != None:
612  strValue = str(value)
613  else:
614  strValue = ""
615  if not self._readOnly:
616  self.disconnect(self._lineEdit, SIGNAL('editingFinished()'), self.valueChanged)
617  self.disconnect(self._textEdit, SIGNAL('editingFinished()'), self.valueChanged)
618  self._lineEdit.setText(strValue)
619  self._textEdit.setText(strValue)
620  self.setToolTip(strValue)
621  if not self._readOnly:
622  self.connect(self._lineEdit, SIGNAL('editingFinished()'), self.valueChanged)
623  self.connect(self._textEdit, SIGNAL('editingFinished()'), self.valueChanged)
624  # TODO: sometimes when changing value the text edit appears to be empty when new text is shorter than old text
625  #if not self._multiline:
626  # self._textEdit.setCursorPosition(self._textEdit.displayText().length())
627 
628  def setToolTip(self,text):
629  self._lineEdit.setToolTip(text)
630  self._textEdit.setToolTip(text)
631 
632  def setMultiline(self,multi):
633  """ Switch between single and multi line mode.
634  """
635  self.setValue(self.strValue())
636  self._multiline=multi
637  if self._multiline:
638  self._textEdit.show()
639  self._lineEdit.hide()
640  self.setFocusProxy(self._textEdit)
641  else:
642  self._lineEdit.show()
643  self._textEdit.hide()
644  self.setFocusProxy(self._lineEdit)
645 
646  def createLineEdit(self, value=None):
647  """ This function creates the signle line text field and adds it to the property's layout.
648  """
649  self._lineEdit = QLineEdit(self)
650  self._lineEdit.setFrame(False)
651  self.connect(self._lineEdit, SIGNAL('editingFinished()'), self.valueChanged)
652  self._lineEdit.setContentsMargins(0, 0, 0, 0)
653  self._lineEdit.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.MinimumExpanding))
654  self.layout().addWidget(self._lineEdit)
655 
656  def createTextEdit(self, value=None):
657  """ This function creates the multi line text field and adds it to the property's layout.
658  """
659  self._textEdit = TextEdit(self)
660  self._textEdit.setWordWrapMode(QTextOption.NoWrap)
661  self._textEdit.setFrameStyle(QFrame.NoFrame)
662  self.connect(self._textEdit, SIGNAL('editingFinished()'), self.valueChanged)
663  self._textEdit.setContentsMargins(0, 0, 0, 0)
664  self._textEdit.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.MinimumExpanding))
665  self.layout().addWidget(self._textEdit)
666 
667  def properyHeight(self):
668  """ Return the estimated height of the property.
669 
670  The returned height covers the whole text, even if multiline.
671  """
672  if self._multiline:
673  self._textEdit.document().adjustSize()
674  height=self._textEdit.document().size().height()+3
675  if self._textEdit.horizontalScrollBar().isVisible():
676  height+=self._textEdit.horizontalScrollBar().height()+3
677  return height
678  else:
679  return self.DEFAULT_HEIGHT
680 
681  def lineEdit(self):
682  """ Returns line edit.
683  """
684  return self._lineEdit
685 
686  def textEdit(self):
687  """ Returns text edit.
688  """
689  return self._textEdit
690 
691  def createButton(self):
692  """ Creates a button and adds it to the property's layout.
693  """
694  self._button = QToolButton(self)
695  self._button.setText(self.BUTTON_LABEL)
696  self._button.setContentsMargins(0, 0, 0, 0)
697  self.connect(self._button, SIGNAL('clicked(bool)'), self.buttonClicked)
698  self.layout().addWidget(self._button)
699 
700  if self.autohideButtonFlag:
701  self._button.hide()
702 
703  def button(self):
704  """ Return button.
705  """
706  return self._button
707 
708  def hasButton(self):
709  """ Returns True if the button has been created, otherwise False is returned.
710  """
711  return self._button != None
712 
713  def setReadOnly(self, readOnly):
714  """ Switch between readonly and editable.
715  """
716  self._readOnly = readOnly
717  if not self.lineEdit().isReadOnly() and readOnly:
718  self.disconnect(self._lineEdit, SIGNAL('editingFinished()'), self.valueChanged)
719  self.disconnect(self._textEdit, SIGNAL('editingFinished()'), self.valueChanged)
720  if self.hasButton():
721  self._button=None
722  if self.lineEdit().isReadOnly() and not readOnly:
723  self.connect(self._lineEdit, SIGNAL('editingFinished()'), self.valueChanged)
724  self.connect(self._textEdit, SIGNAL('editingFinished()'), self.valueChanged)
725  if not self.hasButton():
726  self.createButton()
727  self.lineEdit().setReadOnly(readOnly)
728  self.textEdit().setReadOnly(readOnly)
729 
730  def readOnly(self):
731  return self._readOnly
732 
733  def strValue(self):
734  """ Returns value of text edit.
735  """
736  if not self._multiline:
737  return str(self._lineEdit.text().toAscii())
738  else:
739  return str(self._textEdit.toPlainText().toAscii())
740  return ""
741 
742  def value(self):
743  """ Returns the value of correct type (in case its not a string).
744  """
745  return self.strValue()
746 
747  def setAutohideButton(self, hide):
748  """ If hide is True the button will only be visible while the cursor is over the property.
749  """
750  self.autohideButtonFlag = hide
751 
752  def buttonClicked(self, checked=False):
753  """
754  This function is called if the button was clicked. For information on the checked argument see documentation of QPushButton::clicked().
755  This function should be overwritten by sub-classes.
756  """
757  pass
758 
759  def enterEvent(self, event):
760  """ If autohideButtonFlag is set this function makes the button visible. See setAutohideButton().
761  """
762  if self.autohideButtonFlag and self.hasButton() and not self._readOnly:
763  self._button.show()
764 
765  def leaveEvent(self, event):
766  """ If autohideButtonFlag is set this function makes the button invisible. See setAutohideButton().
767  """
768  if self.autohideButtonFlag and self.hasButton() and not self._readOnly:
769  self._button.hide()
770 
771  def valueChanged(self):
772  """ Update tooltip and height when text is changed.
773  """
774  if self._multiline:
775  self.emit(SIGNAL('updatePropertyHeight'),self)
776  self.setToolTip(self.strValue())
777  # set property only if button is not being pressed
778  if not self.button() or not self.button().isDown():
779  Property.valueChanged(self)
780 
781  def setHighlighted(self,highlight):
782  """ Highlight the property by changing the background color of the textfield.
783  """
784  p=QPalette()
785  if highlight:
786  p.setColor(QPalette.Active, QPalette.ColorRole(9),Qt.red)
787  else:
788  p.setColor(QPalette.Active, QPalette.ColorRole(9),Qt.white)
789  self._lineEdit.setPalette(p)
790  self._textEdit.viewport().setPalette(p)
791 
792  def keyPressEvent(self,event):
793  """ Switch back to the original value on ESC.
794  """
795  QWidget.keyPressEvent(self,event)
796  if event.key()==Qt.Key_Escape:
797  self.setValue(self._originalValue)
798 
799 
801  """ Property which holds an editable text.
802 
803  A button is provided to switch between single and multi line mode.
804  """
805 
806  BUTTON_LABEL = 'v'
807  USER_INFO = "Text field"
808 
809  AUTHIDE_BUTTON = False
810 
811  def __init__(self, name, value, categoryName=None, multiline=None):
812  """ Constructor """
813  TextEditWithButtonProperty.__init__(self, name, value, categoryName, (multiline or str(value).count("\n")>0))
814 
815  def setMultiline(self,multiline):
816  TextEditWithButtonProperty.setMultiline(self,multiline)
817  icon = QIcon(":/resources/editor.svg")
818  dummyicon = QIcon()
819  self._button.setIcon(icon)
820  self._button.setIconSize(QSize(15,15))
821 
822  def buttonClicked(self):
823  """ Switch to multiline mode if button is clicked.
824  """
825  dialog=TextDialog(self,"Edit property...",self.strValue())
826  if dialog.exec_():
827  if not self._multiline:
828  self.setMultiline(True)
829  textEdit=dialog.getText()
830  self.setValue(textEdit)
831  self.valueChanged()
832 
833 
834 class IntegerProperty(Property,QWidget):
835  """ Property which hold editable integer numbers.
836 
837  A Spinbox is provided when the property is editable.
838  """
839 
840  USER_INFO = "Integer field"
841 
842  def __init__(self, name, value, categoryName=None):
843  """ Constructor
844  """
845  Property.__init__(self, name, categoryName)
846  QWidget.__init__(self)
847  self.setLayout(QHBoxLayout())
848  self.layout().setSpacing(0)
849  self.layout().setContentsMargins(0, 0, 0, 0)
850 
851  self._spinbox=QSpinBox()
852  #self.maxint = sys.maxint # does not work on Mac OS X (Snow Leopard 10.6.2), confusion between 32 and 64 bit limits
853  self.maxint = 2**31
854  self._spinbox.setRange(-self.maxint+1, self.maxint-1)
855  self._spinbox.setFrame(False)
856  self.layout().addWidget(self._spinbox)
857  self.setFocusProxy(self._spinbox)
858  self._lineedit=QLineEdit()
859  self._lineedit.setReadOnly(True)
860  self._lineedit.setFrame(False)
861  self._lineedit.setContentsMargins(0, 0, 0, 0)
862  self.layout().addWidget(self._lineedit)
863  self._lineedit.hide()
864 
865  self.setValue(value)
866  self.connect(self._spinbox, SIGNAL('valueChanged(int)'), self.valueChanged)
867 
868  def setReadOnly(self, readOnly):
869  """ Switches between lineedit and spinbox.
870  """
871  if readOnly:
872  self._spinbox.hide()
873  self._lineedit.show()
874  self.setFocusProxy(self._lineedit)
875  else:
876  self._spinbox.show()
877  self._lineedit.hide()
878  self.setFocusProxy(self._spinbox)
879 
880  def value(self):
881  """ Returns integer value.
882  """
883  return self._spinbox.value()
884 
885  def setValue(self,value):
886  self.disconnect(self._spinbox, SIGNAL('valueChanged(int)'), self.valueChanged)
887  self._spinbox.setValue(value % self.maxint)
888  self.connect(self._spinbox, SIGNAL('valueChanged(int)'), self.valueChanged)
889  self._lineedit.setText(str(value))
890 
892  """ TextEditWithButtonProperty which holds float numbers.
893  """
894 
895  USER_INFO = "Double field"
896 
897  AUTHIDE_BUTTON = False
898 
899  def __init__(self, name, value, categoryName=None):
900  """ Constructor
901  """
902  TextEditWithButtonProperty.__init__(self, name, value, categoryName=None)
903 
904  def createButton(self):
905  """ Do not create a button."""
906  pass
907 
908  def _toString(self, object):
909  if isinstance(object, float):
910  return "%.10g" % object
911  else:
912  return str(object)
913 
914  def setValue(self, value):
915  TextEditWithButtonProperty.setValue(self, self._toString(value))
916 
917  def value(self):
918  """ Transform text to float and return.
919  """
920  try:
921  return float(TextEditWithButtonProperty.value(self))
922  except:
923  try:
924  return float.fromhex(TextEditWithButtonProperty.value(self))
925  except:
926  return ValueError("Entered value is not of type double.")
927 
929  """ TextEditWithButtonProperty which holds file names.
930 
931  A button for opening a dialog allowing to choose a file is provided.
932  """
933 
934  USER_INFO = "Select a file. Double click on label to open file."
935  BUTTON_LABEL = '...'
936 
937  def __init__(self, name, value, categoryName=None):
938  TextEditWithButtonProperty.__init__(self, name, value, categoryName)
939  self.button().setToolTip(self.userInfo())
940 
941  def buttonClicked(self, checked=False):
942  """ Shows the file selection dialog. """
943  if self.value()!="":
944  if self._relativePath:
945  dir=os.path.join(self._relativePath,self.value())
946  else:
947  dir=self.value()
948  else:
949  dir=QCoreApplication.instance().getLastOpenLocation()
950  filename = QFileDialog.getSaveFileName(
951  self,
952  'Select a file',
953  dir,
954  '',
955  None,
956  QFileDialog.DontConfirmOverwrite)
957  if not filename.isEmpty():
958  filename=str(filename)
959  if self._relativePath:
960  if filename.startswith(self._relativePath):
961  filename=filename[len(self._relativePath):].lstrip("/")
962  self.setValue(filename)
963  self.textEdit().emit(SIGNAL('editingFinished()'))
964 
966  """ Open selected file in default application.
967  """
968  if isinstance(self.propertyView().parent(), AbstractTab):
969  self.propertyView().parent().mainWindow().application().doubleClickOnFile(self.value())
970 
971  def useRelativePaths(self,path):
972  self._relativePath=path
973 
974 
976  """ TextEditWithButtonProperty which holds file names.
977 
978  A button for opening a dialog allowing to choose a list of files is provided.
979  """
980 
981  USER_INFO = "Edit list of files."
982  BUTTON_LABEL = '...'
983 
984  def __init__(self, name, value, categoryName=None):
985  TextEditWithButtonProperty.__init__(self, name, value, categoryName)
986  self.button().setToolTip(self.userInfo())
987 
988  def buttonClicked(self, checked=False):
989  """ Shows the file selection dialog. """
990  if type(self._originalValue)==type(()) and len(self._originalValue)>0:
991  dir=os.path.dirname(self._originalValue[0])
992  elif self._relativePath:
993  dir=self._relativePath
994  else:
995  dir=QCoreApplication.instance().getLastOpenLocation()
996  fileList = QFileDialog.getOpenFileNames(
997  self,
998  'Select a list of files',
999  dir,
1000  '',
1001  None,
1002  QFileDialog.DontConfirmOverwrite)
1003  fileNames=[str(f) for f in fileList]
1004  if self._relativePath:
1005  nfileNames=[]
1006  for v in fileNames:
1007  if v.startswith(self._relativePath):
1008  nfileNames+=[v[len(self._relativePath):].lstrip("/")]
1009  else:
1010  nfileNames+=[v]
1011  fileNames=nfileNames
1012  if len(fileNames)>0:
1013  self.setValue(fileNames)
1014  self.textEdit().emit(SIGNAL('editingFinished()'))
1015 
1016  def isBusy(self):
1017  return self._updatingFlag>0
1018 
1019  def useRelativePaths(self,path):
1020  self._relativePath=path
size
Write out results.
def __init__(self, name, value, categoryName=None)
def __init__(self, name, categoryName=None)
def __init__(self, argument, color=Qt.white)
def __init__(self, name, value, categoryName=None)
def setChecked(self, check, report=True)
def setDeletable(self, deletable)
def __init__(self, name, value, categoryName=None, multiline=None)
def updatePropertyHeight(self, property)
def sectionResized(self, index, old, new)
def __init__(self, name, value, categoryName=None, multiline=False)
def removeProperty(self, bool=False)
def buttonClicked(self, checked=False)
def __init__(self, parent=None, name=None)
Definition: PropertyView.py:52
isReadOnly
Definition: dataDML.py:2333
def setReadOnly(self, readOnly)
def __init__(self, name, value, categoryName=None)
def propertyWidgetFromProperty(property, categoryName=None)
def __init__(self, name, value, values, categoryName=None)
def __init__(self, name, value, categoryName=None)
def __init__(self, name, value, categoryName=None)
def setHighlighted(self, highlight)
def setPropertyView(self, propertyView)