CMS 3D CMS Logo

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