CMS 3D CMS Logo

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