class PM_ComboBox( QComboBox ): """ The PM_ComboBox widget provides a combobox with a text label for a Property Manager group box. The text label can be positioned on either the left or right side of the combobox. A combobox is a combined button and popup list that provides a means of presenting a list of options to the user in a way that takes up the minimum amount of screen space. Also, a combobox is a selection widget that displays the current item, and can pop up a list of selectable items. A combobox may be editable, allowing the user to modify each item in the list. Comboboxes can contain pixmaps as well as strings; the insertItem() and changeItem() functions are suitably overloaded. For editable comboboxes, the function clearEdit() is provided, to clear the displayed string without changing the combobox's contents. There are two signals emitted if the current item of a combobox changes, currentIndexChanged() and activated(). currentIndexChanged() is always emitted regardless if the change was done programmatically or by user interaction, while activated() is only emitted when the change is caused by user interaction. The highlighted() signal is emitted when the user highlights an item in the combobox popup list. All three signals exist in two versions, one with a U{B{QString}<http://doc.trolltech.com/4/qstring.html>} argument and one with an int argument. If the user selectes or highlights a pixmap, only the int signals are emitted. Whenever the text of an editable combobox is changed the editTextChanged() signal is emitted. When the user enters a new string in an editable combobox, the widget may or may not insert it, and it can insert it in several locations. The default policy is is AtBottom but you can change this using setInsertPolicy(). It is possible to constrain the input to an editable combobox using U{B{QValidator}<http://doc.trolltech.com/4/qvalidator.html>} ; see setValidator(). By default, any input is accepted. A combobox can be populated using the insert functions, insertStringList() and insertItem() for example. Items can be changed with changeItem(). An item can be removed with removeItem() and all items can be removed with clear(). The text of the current item is returned by currentText(), and the text of a numbered item is returned with text(). The current item can be set with setCurrentIndex(). The number of items in the combobox is returned by count(); the maximum number of items can be set with setMaxCount(). You can allow editing using setEditable(). For editable comboboxes you can set auto-completion using setCompleter() and whether or not the user can add duplicates is set with setDuplicatesEnabled(). PM_ComboBox uses the model/view framework for its popup list and to store its items. By default a QStandardItemModel stores the items and a QListView subclass displays the popuplist. You can access the model and view directly (with model() and view()), but PM_ComboBox also provides functions to set and get item data (e.g., setItemData() and itemText()). You can also set a new model and view (with setModel() and setView()). For the text and icon in the combobox label, the data in the model that has the Qt.DisplayRole and Qt.DecorationRole is used. @cvar defaultChoices: The default choices of the combobox. @type defaultChoices: list @cvar defaultIndex: The default index of the combobox. @type defaultIndex: int @cvar setAsDefault: Determines whether to reset the choices to I{defaultChoices} and currentIndex to I{defaultIndex} when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this combobox. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} @see: U{B{QComboBox}<http://doc.trolltech.com/4/qcombobox.html>} """ defaultIndex = 0 defaultChoices = [] setAsDefault = True labelWidget = None def __init__(self, parentWidget, label = '', labelColumn = 0, choices = [], index = 0, setAsDefault = True, spanWidth = False ): """ Appends a QComboBox widget (with a QLabel widget) to <parentWidget>, a property manager group box. Arguments: @param parentWidget: the group box containing this PM widget. @type parentWidget: PM_GroupBox @param label: label that appears to the left of (or above) this PM widget. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param choices: list of combo box choices (strings). @type choices: list @param index: initial index (choice) of combobox. (0=first item) @type index: int (default 0) @param setAsDefault: if True, will restore <index> as the current index when the "Restore Defaults" button is clicked. @type setAsDefault: bool (default True) @param spanWidth: If True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: bool (default False) @see: U{B{QComboBox}<http://doc.trolltech.com/4/qcombobox.html>} """ if 0: # Debugging code print "PM_ComboBox.__init__():" print " label =", label print " choices =", choices print " index =", index print " setAsDefault =", setAsDefault print " spanWidth =", spanWidth QComboBox.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Load QComboBox widget choices and set initial choice (index). for choice in choices: self.addItem(choice) self.setCurrentIndex(index) # Set default index self.defaultIndex = index self.defaultChoices = choices self.setAsDefault = setAsDefault parentWidget.addPmWidget(self) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.clear() # Generates signals! for choice in self.defaultChoices: self.addItem(choice) self.setCurrentIndex(self.defaultIndex) def hide(self): """ Hides the combobox and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the combobox and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() # End of PM_ComboBox ############################
class PM_ListWidget(QListWidget): """ The PM_ListWidget widget provides a QListWidget with a QLabel for a Property Manager group box. @cvar defaultItems: The default list of items in the list widget. @type defaultItems: list @cvar defaultRow: The default row of the list widget. @type defaultRow: int @cvar setAsDefault: Determines whether to reset the choices to I{defaultItems} and current row to I{defaultRow} when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this list widget. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} """ defaultRow = 0 defaultItems = [] setAsDefault = True labelWidget = None def __init__(self, parentWidget, label='', labelColumn=0, items=[], defaultRow=0, setAsDefault=True, heightByRows=6, spanWidth=False): """ Appends a QListWidget (Qt) widget to the bottom of I{parentWidget}, a Property Manager group box. @param parentWidget: The parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left or right of the checkbox. If spanWidth is True, the label will be displayed on its own row directly above the list widget. To suppress the label, set I{label} to an empty string. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param items: list of items (strings) to be inserted in the widget. @type items: list @param defaultRow: The default row (item) selected, where 0 is the first row. @type defaultRow: int @param setAsDefault: If True, will restore <idx> as the current index when the "Restore Defaults" button is clicked. @type setAsDefault: bool @param heightByRows: The height of the list widget. @type heightByRows: int @param spanWidth: If True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: bool @see: U{B{QListWidget}<http://doc.trolltech.com/4/qlistwidget.html>} """ if 0: # Debugging code print "PM_ListWidget.__init__():" print " label = ", label print " labelColumn = ", labelColumn print " items = ", items print " defaultRow = ", defaultRow print " setAsDefault = ", setAsDefault print " heightByRows = ", heightByRows print " spanWidth = ", spanWidth QListWidget.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Load QComboBox widget choice items and set initial choice (index). self.insertItems(0, items, setAsDefault) self.setCurrentRow(defaultRow, setAsDefault) # Set height of list widget. margin = self.fontMetrics().leading() * 2 # Mark 2007-05-28 height = heightByRows * self.fontMetrics().lineSpacing() + margin self.setMaximumHeight(height) #As of 2008-04-16, the items in any list widgets won't be sorted #automatically. It can be changes by simply uncommentting the lines #below -- Ninad ##self.setSortingEnabled(True) ##self.sortItems() self.setAlternatingRowColors(True) parentWidget.addPmWidget(self) def insertItems(self, row, items, setAsDefault=True): """ Insert items of widget starting at <row>. If <setAsDefault> is True, <items> become the default list of items for this widget. "Restore Defaults" will reset the list of items to <items>. Note: <items> will always replace the list of current items in the widget. <row> is ignored. This is considered a bug. -- Mark 2007-06-04 """ if row <> 0: msg = "PM_ListWidget.insertItems(): <row> must be zero."\ "See docstring for details:" print_compact_traceback(msg) return if setAsDefault: self.setAsDefault = setAsDefault self.defaultItems = items self.clear() QListWidget.insertItems(self, row, items) def setCurrentRow(self, row, setAsDefault=False): """ Select new row. @param row: The new row to select. @type row: int @param setAsDefault: If True, I{row} becomes the default row when "Restore Defaults" is clicked. """ if setAsDefault: self.setAsDefault = setAsDefault self.defaultRow = row QListWidget.setCurrentRow(self, row) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.insertItems(0, self.defaultItems) self.setCurrentRow(self.defaultRow) ## self.clear() ## for choice in self.defaultChoices: ## self.addItem(choice) ## self.setCurrentRow(self.defaultRow) def hide(self): """ Hides the list widget and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the list widget and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() # End of PM_ListWidget ############################
class Ui_DnaSequenceEditor(PM_DockWidget): """ The Ui_DnaSequenceEditor class defines UI elements for the Sequence Editor object. The sequence editor is usually visible while in DNA edit mode. It is a DockWidget that is doced at the bottom of the MainWindow """ _title = "Sequence Editor" _groupBoxCount = 0 _lastGroupBox = None def __init__(self, win): """ Constructor for the Ui_DnaSequenceEditor @param win: The parentWidget (MainWindow) for the sequence editor """ self.win = win # Should parentWidget for a docwidget always be win? #Not necessary but most likely it will be the case. parentWidget = win _superclass.__init__(self, parentWidget, title = self._title) #A flag used to restore the state of the Reports dock widget #(which can be accessed through View > Reports) see self.show() and #self.closeEvent() for more details. self._reportsDockWidget_closed_in_show_method = False self.setFixedHeight(90) def show(self): """ Shows the sequence editor. While doing this, it also closes the reports dock widget (if visible) the state of the reports dockwidget will be restored when the sequence editor is closed. @see:self.closeEvent() """ self._reportsDockWidget_closed_in_show_method = False #hide the history widget first #(It will be shown back during self.close) #The history widget is hidden or shown only when both # 'View > Full Screen' and View > Semi Full Screen actions # are *unchecked* #Thus show or close methods won't do anything to history widget # if either of the above mentioned actions is checked. if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self.win.reportsDockWidget.isVisible(): self.win.reportsDockWidget.close() self._reportsDockWidget_closed_in_show_method = True _superclass.show(self) def closeEvent(self, event): """ Overrides close event. Makes sure that the visible state of the reports widgetis restored when the sequence editor is closed. @see: self.show() """ _superclass.closeEvent(self, event) if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self._reportsDockWidget_closed_in_show_method: self.win.viewReportsAction.setChecked(True) self._reportsDockWidget_closed_in_show_method = False def _loadWidgets(self): """ Overrides PM.PM_DockWidget._loadWidgets. Loads the widget in this dockwidget. """ self._loadMenuWidgets() self._loadTextEditWidget() def _loadMenuWidgets(self): """ Load the various menu widgets (e.g. Open, save sequence options, Find and replace widgets etc. """ #Note: Find and replace widgets might be moved to their own class. self.loadSequenceButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Open.png") self.saveSequenceButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Save_Strand_Sequence.png") self.loadSequenceButton.setAutoRaise(True) self.saveSequenceButton.setAutoRaise(True) editDirectionChoices = ["5' to 3'", "3' to 5'"] self.baseDirectionChoiceComboBox = \ PM_ComboBox( self, choices = editDirectionChoices, index = 0, spanWidth = False ) #Find and replace widgets -- self.findLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.findLineEdit.setMaximumWidth(60) self.replaceLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.replaceLineEdit.setMaximumWidth(60) self.findOptionsToolButton = PM_ToolButton(self) self.findOptionsToolButton.setMaximumWidth(12) self.findOptionsToolButton.setAutoRaise(True) self.findOptionsToolButton.setPopupMode(QToolButton.MenuButtonPopup) self._setFindOptionsToolButtonMenu() self.findNextToolButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Find_Next.png") self.findNextToolButton.setAutoRaise(True) self.findPreviousToolButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Find_Previous.png") self.findPreviousToolButton.setAutoRaise(True) self.replacePushButton = PM_PushButton(self, text = "Replace") self.warningSign = QLabel(self) self.warningSign.setPixmap( getpixmap('ui/actions/Properties Manager/Warning.png')) self.warningSign.hide() self.phraseNotFoundLabel = QLabel(self) self.phraseNotFoundLabel.setText("Sequence Not Found") self.phraseNotFoundLabel.hide() # NOTE: Following needs cleanup in the PM_WidgetRow/ PM_WidgetGrid # but this explanation is sufficient until thats done -- # When the widget type starts with the word 'PM_' , the # PM_WidgetRow treats it as a well defined widget and thus doesn't try # to create a QWidget object (or its subclasses) # This is the reason why qLabels such as self.warningSign and # self.phraseNotFoundLabel are defined as PM_Labels and not 'QLabels' # If they were defined as 'QLabel'(s) then PM_WidgetRow would have # recreated the label. Since we want to show/hide the above mentioned # labels (and if they were recreated as mentioned above), # we would have needed to define those something like this: # self.phraseNotFoundLabel = widgetRow._widgetList[-2] #Cleanup in PM_widgetGrid could be to check if the widget starts with #'Q' instead of 'PM_' #Widgets to include in the widget row. widgetList = [('PM_ToolButton', self.loadSequenceButton, 0), ('PM_ToolButton', self.saveSequenceButton, 1), ('QLabel', " Sequence direction:", 2), ('PM_ComboBox', self.baseDirectionChoiceComboBox , 3), ('QLabel', " Find:", 4), ('PM_LineEdit', self.findLineEdit, 5), ('PM_ToolButton', self.findOptionsToolButton, 6), ('PM_ToolButton', self.findPreviousToolButton, 7), ('PM_ToolButton', self.findNextToolButton, 8), ('QLabel', " Replace:", 9), ('PM_TextEdit', self.replaceLineEdit, 10), ('PM_PushButton', self.replacePushButton, 11), ('PM_Label', self.warningSign, 12), ('PM_Label', self.phraseNotFoundLabel, 13), ('QSpacerItem', 5, 5, 14) ] widgetRow = PM_WidgetRow(self, title = '', widgetList = widgetList, label = "", spanWidth = True ) def _loadTextEditWidget(self): """ Load the SequenceTexteditWidgets. """ self.sequenceTextEdit = \ PM_TextEdit( self, label = " Sequence: ", spanWidth = False, permit_enter_keystroke = False) self.sequenceTextEdit.setCursorWidth(2) self.sequenceTextEdit.setWordWrapMode( QTextOption.WrapAnywhere ) self.sequenceTextEdit.setFixedHeight(20) #The StrandSequence 'Mate' it is a read only etxtedit that shows #the complementary strand sequence. self.sequenceTextEdit_mate = \ PM_TextEdit(self, label = "", spanWidth = False, permit_enter_keystroke = False ) palette = getPalette(None, QPalette.Base, sequenceEditStrandMateBaseColor) self.sequenceTextEdit_mate.setPalette(palette) self.sequenceTextEdit_mate.setFixedHeight(20) self.sequenceTextEdit_mate.setReadOnly(True) self.sequenceTextEdit_mate.setWordWrapMode(QTextOption.WrapAnywhere) #Important to make sure that the horizontal and vertical scrollbars #for these text edits are never displayed. for textEdit in (self.sequenceTextEdit, self.sequenceTextEdit_mate): textEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) textEdit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) def _getFindLineEditStyleSheet(self): """ Return the style sheet for the findLineEdit. This sets the following properties only: - background-color This style is set whenever the searchStrig can't be found (sets a light red color background to the lineedit when this happens) @return: The line edit style sheet. @rtype: str """ styleSheet = \ "QLineEdit {\ background-color: rgb(255, 102, 102)\ }" #Not used: # background-color: rgb(217, 255, 216)\ return styleSheet def _setFindOptionsToolButtonMenu(self): """ Sets the menu for the findOptionstoolbutton that appears a small menu button next to the findLineEdit. """ self.findOptionsMenu = QMenu(self.findOptionsToolButton) self.caseSensitiveFindAction = QAction(self.findOptionsToolButton) self.caseSensitiveFindAction.setText('Match Case') self.caseSensitiveFindAction.setCheckable(True) self.caseSensitiveFindAction.setChecked(False) self.findOptionsMenu.addAction(self.caseSensitiveFindAction) self.findOptionsMenu.addSeparator() self.findOptionsToolButton.setMenu(self.findOptionsMenu) def _addToolTipText(self): """ What's Tool Tip text for widgets in this Property Manager. """ from ne1_ui.ToolTipText_for_PropertyManagers import ToolTip_SequenceEditor ToolTip_SequenceEditor(self) def _addWhatsThisText(self): """ What's This text for widgets in this Property Manager. """ from ne1_ui.WhatsThisText_for_PropertyManagers import whatsThis_SequenceEditor whatsThis_SequenceEditor(self)
class PM_Slider(QSlider): labelWidget = None def __init__( self, parentWidget, orientation = None, ##Qt.Horizontal, currentValue = 0, minimum = 0, maximum = 100, label = '', labelColumn = 0, setAsDefault = True, spanWidth = True): """ Appends a Qslider widget (with a QLabel widget) to <parentWidget> a property manager group box. Arguments: @param parentWidget: the group box containing this PM widget. @type parentWidget: PM_GroupBox @param currentValue: @type currentValue: int @param minimum: minimum value the slider can get. (used to set its 'range') @type minimum: int @param maximum: maximum value the slider can get. (used to set its 'range') @type maximum: int @param label: label that appears to the left of (or above) this widget. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param setAsDefault: if True, will restore value when the "Restore Defaults" button is clicked. @type setAsDefault: bool (default True) @param spanWidth: If True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty)and is left justified. @type spanWidth: bool (default True) @see: U{B{QSlider}<http://doc.trolltech.com/4/qslider.html>} """ ##QSlider.__init__(self, orientation, parentWidget) QSlider.__init__(self, parentWidget) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth #Ideally, this should be simply self.setOrientation(orientation) with the #default orientation = Qt.Horizontal in the init argument itself. But, #apparently pylint chokes up when init argument is a Qt enum. #This problem happened while running pylint 0.23 on the SEMBOT server #so comitting this temporary workaround. The pylint on my machine is #0.25 and it runs fine even before this workaround. Similar changes made #in PM_CheckBox. -- Ninad 2008-06-30 if orientation is None: self.setOrientation(Qt.Horizontal) else: self.setOrientation(orientation) if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) self.setValue(currentValue) self.setRange(minimum, maximum) self.setAsDefault = setAsDefault if self.setAsDefault: self.setDefaultValue(currentValue) parentWidget.addPmWidget(self) def hide(self): """ Hides the slider and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the slider and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setValue(self.defaultValue) def setDefaultValue(self, value): """ Sets the Default value for the slider. This value will be set if user hits Restore Defaults button in the Property Manager. """ self.defaultValue = value self.setAsDefault = True
class PM_SpinBox( QSpinBox ): """ The PM_SpinBox widget provides a QSpinBox (with an optional label) for a Property Manager group box. Detailed Description ==================== The PM_SpinBox class provides a spin box widget (with an optional label) for a U{B{Property Manager dialog}<http://www.nanoengineer-1.net/mediawiki/ index.php?title=Property_Manager>}. PM_SpinBox is designed to handle integers and discrete sets of values (e.g., month names); use PM_DoubleSpinBox for floating point values. PM_SpinBox allows the user to choose a value by clicking the up/down buttons or pressing up/down on the keyboard to increase/decrease the value currently displayed. The user can also type the value in manually. The spin box supports integer values but can be extended to use different strings with validate(), textFromValue() and valueFromText(). Every time the value changes PM_SpinBox emits the valueChanged() signals. The current value can be fetched with value() and set with setValue(). Clicking the up/down buttons or using the keyboard accelerator's up and down arrows will increase or decrease the current value in steps of size singleStep(). If you want to change this behaviour you can reimplement the virtual function stepBy(). The minimum and maximum value and the step size can be set using one of the constructors, and can be changed later with setMinimum(), setMaximum() and setSingleStep(). Most spin boxes are directional, but PM_SpinBox can also operate as a circular spin box, i.e. if the range is 0-99 and the current value is 99, clicking "up" will give 0 if wrapping() is set to true. Use setWrapping() if you want circular behavior. The displayed value can be prepended and appended with arbitrary strings indicating, for example, currency or the unit of measurement. See setPrefix() and setSuffix(). The text in the spin box is retrieved with text() (which includes any prefix() and suffix()), or with cleanText() (which has no prefix(), no suffix() and no leading or trailing whitespace). It is often desirable to give the user a special (often default) choice in addition to the range of numeric values. See setSpecialValueText() for how to do this with QSpinBox. @see: U{B{QSpinBox}<http://doc.trolltech.com/4/qspinbox.html>} @cvar defaultValue: The default value of the spin box. @type defaultValue: int @cvar setAsDefault: Determines whether to reset the value of the spin box to I{defaultValue} when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this spin box. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} """ defaultValue = 0 setAsDefault = True labelWidget = None def __init__(self, parentWidget, label = '', labelColumn = 0, value = 0, setAsDefault = True, minimum = 0, maximum = 99, singleStep = 1, suffix = '', spanWidth = False ): """ Appends a QSpinBox (Qt) widget to the bottom of I{parentWidget}, a Property Manager group box. @param parentWidget: the parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left of (or above) the spin box. If label contains the relative path to an icon (.png) file, that icon image will be used for the label. If spanWidth is True, the label will be displayed on its own row directly above the spin box. To suppress the label, set I{label} to an empty string. @type label: str @param value: The initial value of the spin box. @type value: int @param setAsDefault: Determines if the spin box value is reset when the "Restore Defaults" button is clicked. If True, (the default) I{value} will be used as the reset value. @type setAsDefault: bool @param minimum: The minimum value of the spin box. @type minimum: int @param maximum: The maximum value of the spin box. @type maximum: int @param singleStep: When the user uses the arrows to change the spin box's value the value will be incremented/decremented by the amount of the singleStep. The default value is 1. Setting a singleStep value of less than 0 does nothing. @type singleStep: int @param suffix: The suffix is appended to the end of the displayed value. Typical use is to display a unit of measurement. The default is no suffix. The suffix is not displayed for the minimum value if specialValueText() is set. @type suffix: str @param spanWidth: If True, the spin box and its label will span the width of the group box. The label will appear directly above the spin box and is left justified. @type spanWidth: bool @see: U{B{QSpinBox}<http://doc.trolltech.com/4/qspinbox.html>} """ if 0: # Debugging code print "PM_SpinBox.__init__():" print " label = ", label print " labelColumn = ", labelColumn print " value = ", value print " setAsDefault = ", setAsDefault print " minimum = ", minimum print " maximum = ", maximum print " singleStep = ", singleStep print " suffix = ", suffix print " spanWidth = ", spanWidth QSpinBox.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth self._suppress_valueChanged_signal = False if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Set QSpinBox minimum, maximum and initial value self.setRange(minimum, maximum) self.setSingleStep(singleStep) self.setValue(value) # Set default value self.defaultValue = value self.setAsDefault = setAsDefault # Add suffix if supplied. if suffix: self.setSuffix(suffix) parentWidget.addPmWidget(self) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setValue(self.defaultValue) def setDefaultValue(self, value): """ Sets the default value of the spin box to I{value}. The current spin box value is unchanged. @param value: The new default value of the spin box. @type value: int @see: L{setValue} """ self.setAsDefault = True self.defaultValue = value def setValue(self, value, setAsDefault = True, blockSignals = False): """ Sets the value of the spin box to I{value}. setValue() will emit valueChanged() if the new value is different from the old one. (and if blockSignals flag is False) @param value: The new spin box value. @type value: int @param setAsDefault: Determines if the spin box value is reset when the "Restore Defaults" button is clicked. If True, (the default) I{value} will be used as the reset value. @type setAsDefault: bool @param blockSignals: Many times, the caller just wants to setValue and don't want to send valueChanged signal. If this flag is set to True, the valueChanged signal won't be emitted. The default value is False. @type blockSignals: bool @see: L{setDefaultValue} @see: QObject.blockSignals(bool block) @see: B{InsertNanotube_PropertyManager._chiralityFixup()} for an example use of blockSignals flag """ #If blockSignals flag is True, the valueChanged signal won't be emitted #This is done by self.blockSignals method below. -- Ninad 2008-08-13 self.blockSignals(blockSignals) if setAsDefault: self.setDefaultValue(value) QSpinBox.setValue(self, value) #Make sure to always 'unblock' signals that might have been temporarily #blocked before calling superclass.setValue. self.blockSignals(False) def hide(self): """ Hides the spin box and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the spin box and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() # End of PM_SpinBox ############################
class PM_ColorChooser( QWidget ): """ The PM_ColorChooser widget provides a color chooser widget for a Property Manager group box. The PM_ColorChooser widget is a composite widget made from 3 other Qt widgets: - a QLabel - a QFrame and - a QToolButton (with a "..." text label). IMAGE(http://www.nanoengineer-1.net/mediawiki/images/e/e2/PM_ColorChooser1.jpg) The user can color using Qt's color (chooser) dialog by clicking the "..." button. The selected color will be used as the color of the QFrame widget. The parent must make the following signal-slot connection to be notified when the user has selected a new color via the color chooser dialog: self.connect(pmColorChooser.colorFrame, SIGNAL("editingFinished()"), self.mySlotMethod) @cvar setAsDefault: Determines whether to reset the value of the color to I{defaultColor} when the user clicks the "Restore Defaults" button. @type setAsDefault: boolean @cvar labelWidget: The Qt label widget of this PM widget. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} @cvar colorFrame: The Qt frame widget for this PM widget. @type colorFrame: U{B{QFrame}<http://doc.trolltech.com/4/qframe.html>} @cvar chooseButton: The Qt tool button widget for this PM widget. @type chooseButton: U{B{QToolButton}<http://doc.trolltech.com/4/qtoolbutton.html>} """ defaultColor = None setAsDefault = True hidden = False chooseButton = None customColorCount = 0 standardColorList = [white, black] def __init__(self, parentWidget, label = 'Color:', labelColumn = 0, color = white, setAsDefault = True, spanWidth = False, ): """ Appends a color chooser widget to <parentWidget>, a property manager group box. @param parentWidget: the parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left or right of the color frame (and "Browse" button). If spanWidth is True, the label will be displayed on its own row directly above the lineedit (and button). To suppress the label, set I{label} to an empty string. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param color: initial color. White is the default. @type color: tuple of 3 floats (r, g, b) @param setAsDefault: if True, will restore L{color} when the "Restore Defaults" button is clicked. @type setAsDefault: boolean @param spanWidth: if True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: boolean @see: U{B{QColorDialog}<http://doc.trolltech.com/4/qcolordialog.html>} """ QWidget.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.color = color self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Create the color frame (color swath) and "..." button. self.colorFrame = QFrame() self.colorFrame.setFrameShape(QFrame.Box) self.colorFrame.setFrameShadow(QFrame.Plain) # Set browse button text and make signal-slot connection. self.chooseButton = QToolButton() self.chooseButton.setText("...") self.connect(self.chooseButton, SIGNAL("clicked()"), self.openColorChooserDialog) # Add a horizontal spacer to keep the colorFrame and "..." squeezed # together, even when the PM width changes. self.hSpacer = QSpacerItem(10, 10, QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) # Create vertical box layout. self.hBoxLayout = QHBoxLayout(self) self.hBoxLayout.setMargin(0) self.hBoxLayout.setSpacing(2) self.hBoxLayout.insertWidget(-1, self.colorFrame) self.hBoxLayout.insertWidget(-1, self.chooseButton) # Set this to False to make the colorFrame an expandable rectangle. COLORFRAME_IS_SQUARE = True if COLORFRAME_IS_SQUARE: squareSize = 20 self.colorFrame.setMinimumSize(QSize(squareSize, squareSize)) self.colorFrame.setMaximumSize(QSize(squareSize, squareSize)) self.hBoxLayout.addItem(self.hSpacer) self.setColor(color, default = setAsDefault) parentWidget.addPmWidget(self) return def setColor(self, color, default = False): """ Set the color. @param color: The color. @type color: tuple of 3 floats (r, g, b) @param default: If True, make I{color} the default color. Default is False. @type default: boolean """ if default: self.defaultColor = color self.setAsDefault = default self.color = color self._updateColorFrame() return def getColor(self): """ Return the current color. @return: The current r, g, b color. @rtype: Tuple of 3 floats (r, g, b) """ return self.color def getQColor(self): """ Return the current QColor. @return: The current color. @rtype: QColor """ return RGBf_to_QColor(self.color) def _updateColorFrame(self): """ Updates the color frame with the current color. """ colorframe = self.colorFrame try: qcolor = self.getQColor() palette = QPalette() # QPalette(qcolor) would have window color set from qcolor, but that doesn't help us here qcolorrole = QPalette.Window ## http://doc.trolltech.com/4.2/qpalette.html#ColorRole-enum says: ## QPalette.Window 10 A general background color. palette.setColor(QPalette.Active, qcolorrole, qcolor) # used when window is in fg and has focus palette.setColor(QPalette.Inactive, qcolorrole, qcolor) # used when window is in bg or does not have focus palette.setColor(QPalette.Disabled, qcolorrole, qcolor) # used when widget is disabled colorframe.setPalette(palette) colorframe.setAutoFillBackground(True) except: print "data for following exception: ", print "colorframe %r has palette %r" % (colorframe, colorframe.palette()) pass def openColorChooserDialog(self): """ Prompts the user to choose a color and then updates colorFrame with the selected color. """ qcolor = RGBf_to_QColor(self.color) if not self.color in self.standardColorList: QColorDialog.setCustomColor(self.customColorCount, qcolor.rgb()) self.customColorCount += 1 c = QColorDialog.getColor(qcolor, self) if c.isValid(): self.setColor(QColor_to_RGBf(c)) self.colorFrame.emit(SIGNAL("editingFinished()")) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setColor(self.defaultColor) return def hide(self): """ Hides the lineedit and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() return def show(self): """ Unhides the lineedit and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() return
class InformationWindow(QWidget): def __init__(self): QWidget.__init__(self, ctx.mainScreen) self.setObjectName("InformationWindow") self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setFixedHeight(50) self.setMaximumWidth(800) self.setStyleSheet(""" QFrame#frame { border: 1px solid rgba(255,255,255,30); /*border-radius: 4px;*/ background-color: rgba(0,0,0,100);} QLabel { border:none; color:#FFFFFF;} QProgressBar { border: 1px solid white;} QProgressBar::chunk { background-color: #F1610D; width: 0.5px;} """) self.gridlayout = QGridLayout(self) self.frame = QFrame(self) self.frame.setObjectName("frame") self.horizontalLayout = QHBoxLayout(self.frame) self.horizontalLayout.setContentsMargins(10, 0, 10, 0) # Spinner self.spinner = QLabel(self.frame) self.spinner.setMinimumSize(QSize(16, 16)) self.spinner.setMaximumSize(QSize(16, 16)) self.spinner.setIndent(6) self.movie = QMovie(':/images/working.mng') self.spinner.setMovie(self.movie) self.movie.start() self.horizontalLayout.addWidget(self.spinner) # Message self.label = QLabel(self.frame) self.label.setAlignment(Qt.AlignCenter) self.label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.icon = QLabel(self.frame) self.icon.setFixedWidth(16) self.icon.setFixedHeight(16) self.horizontalLayout.setSpacing(10) self.horizontalLayout.addWidget(self.icon) self.horizontalLayout.addWidget(self.label) self.gridlayout.addWidget(self.frame,0,0,1,1) def update(self, message, type=None, spinner=False): fontMetric = self.label.fontMetrics() textWidth = fontMetric.width(message) if type: self.icon.show() if type == "error": self.icon.setPixmap(QPixmap(":/gui/pics/dialog-error.png")) self.setStyleSheet(" QFrame#frame {background-color: rgba(255,0,0,100);} ") elif type == "warning": self.icon.setPixmap(QPixmap(":/gui/pics/dialog-warning.png")) self.setStyleSheet(" QFrame#frame {background-color: rgba(0,0,0,100);} ") self.setFixedWidth(textWidth + self.icon.width() + 50) self.label.setText(message) else: self.icon.hide() self.setStyleSheet(" QFrame#frame {background-color: rgba(0,0,0,100);} ") self.setFixedWidth(textWidth + self.icon.width() + 100) self.label.setText(message) self.spinner.setVisible(spinner) self.move(ctx.mainScreen.width()/2 - self.width()/2, ctx.mainScreen.height() - self.height()/2 - 50) self.show() def refresh(self): ctx.mainScreen.processEvents() def show(self): QWidget.show(self) self.refresh() def hide(self): QWidget.hide(self) self.refresh()
class PM_FontComboBox( QFontComboBox ): """ Much (practically all) of this code was taken from PM_ComboBox with only slight modifications. The PM_FontComboBox widget provides a combobox with a text label for a Property Manager group box. The text label can be positioned on either the left or right side of the combobox. For a more complete explanation of a ComboBox, refer to the definition provided in PM_ComboBox. In this case, the list of available fonts is not provided by variable, but is read from the system. @cvar defaultFont: The default font of the combobox. @type defaultFont: QFont @cvar setAsDefault: Determines whether to reset the currentFont to the defaultFont when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this combobox. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} @see: U{B{QComboBox}<http://doc.trolltech.com/4/qcombobox.html>} """ defaultFont = None setAsDefault = True labelWidget = None def __init__(self, parentWidget, label = '', labelColumn = 0, selectFont = '', setAsDefault = True, spanWidth = False ): """ Appends a QFontComboBox widget (with a QLabel widget) to <parentWidget>, a property manager group box. Arguments: @param parentWidget: the group box containing this PM widget. @type parentWidget: PM_GroupBox @param label: label that appears to the left of (or above) this PM widget. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param selectFont: initial font of combobox. (''=default) @type selectFont: QFont object (default = '') @param setAsDefault: if True, will restore <defaultFont> as the current Font when the "Restore Defaults" button is clicked. @type setAsDefault: bool (default True) @param spanWidth: If True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: bool (default False) @see: U{B{QComboBox}<http://doc.trolltech.com/4/qcombobox.html>} """ if 0: # Debugging code print "PM_FontComboBox.__init__():" print " label =", label print " selectFont =", selectFont print " setAsDefault =", setAsDefault print " spanWidth =", spanWidth QFontComboBox.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Set initial choice (selectFont). if selectFont != '': self.setCurrentFont(selectFont) self.defaultFont = self.currentFont() parentWidget.addPmWidget(self) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setCurrentFont(self.defaultFont) def hide(self): """ Hides the combobox and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the combobox and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show()
class PM_Dial(QDial): """ The PM_Dial widget provides a QDial (with an optional label) for a Property Manager group box. """ defaultValue = 0.0 setAsDefault = True labelWidget = None def __init__(self, parentWidget, label='', suffix='', labelColumn=1, value=0.0, setAsDefault=True, minimum=0.0, maximum=360.0, notchSize=1, notchTarget=10.0, notchesVisible=True, wrapping=True, spanWidth=True): """ Appends a QDial (Qt) widget to the bottom of I{parentWidget}, a Property Manager group box. @param parentWidget: The parent group box containing this widget. @type parentWidget: PM_GroupBox @see: U{B{QDial}<http://doc.trolltech.com/4/qdial.html>} """ if not parentWidget: return QDial.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth self.suffix = suffix if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.updateValueLabel() self.labelWidget.setText(self.value_label) # Set QDial minimum, maximum, then value self.setRange(minimum, maximum) self.setValue(value) # This must come after setDecimals(). if setAsDefault: self.setDefaultValue(value) self.notchSize = notchSize self.setNotchTarget(notchTarget) self.setNotchesVisible(notchesVisible) self.setWrapping(wrapping) parentWidget.addPmWidget(self) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setValue(self.defaultValue) def setDefaultValue(self, value): """ Sets the default value of the dial to I{value}. The current dial value is unchanged. @param value: The new default value of the dial. @type value: float @see: L{setValue} """ self.setAsDefault = True self.defaultValue = value def setValue(self, value, setAsDefault=True): """ Sets the value of the dial to I{value}. setValue() will emit valueChanged() if the new value is different from the old one. @param value: The new dial value. @type value: float ## @param setAsDefault: Determines if the dial value is reset when ## the "Restore Defaults" button is clicked. If True, ## (the default) I{value} will be used as the reset ## value. ## @type setAsDefault: bool @note: The value will be rounded so it can be displayed with the current setting of decimals. @see: L{setDefaultValue} """ QDial.setValue(self, value) self.updateValueLabel() def updateValueLabel(self): """ Updates a widget's label with a value. """ if self.label: self.value_label = " " + self.label + " " + \ "%-4d" % int(self.value()) + " " + self.suffix self.labelWidget.setText(self.value_label) def connectWithState(self, stateref, set_metainfo=True, debug_metainfo=False): """ Connect self to the state referred to by stateref, so changes to self's value change that state's value and vice versa. By default, also set self's metainfo to correspond to what the stateref provides. @param stateref: a reference to state of type double, which meets the state-reference interface StateRef_API. @type stateref: StateRef_API @param set_metainfo: whether to also set defaultValue, minimum, and/or maximum, if these are provided by the stateref. (This list of metainfo attributes will probably be extended.) @type set_metainfo: bool @param debug_metainfo: whether to print debug messages about the actions taken by set_metainfo, when it's true. @type debug_metainfo: bool """ if set_metainfo: # Do this first, so old min/max don't prevent setting the # correct current value when we connect new state. # # REVIEW: the conventions for expressing a lack of # minimum, maximum, or defaultValue, either on self # or on the stateref, may need revision, so it's not # ambiguous whether the stateref knows the minimum (etc) # should be unset on self or doesn't care what it's set to. # Ideally, some explicit value of stateref.minimum # would correspond to "no minimum" (i.e. a minimum of # negative infinity), etc. # [bruce 070926] set_metainfo_from_stateref(self.setMinimum, stateref, 'minimum', debug_metainfo) set_metainfo_from_stateref(self.setMaximum, stateref, 'maximum', debug_metainfo) set_metainfo_from_stateref(self.setDefaultValue, stateref, 'defaultValue', debug_metainfo) ###widget_connectWithState( self, stateref, ### QDoubleSpinBox_ConnectionWithState) print "PM_Dial.connectWithState: not yet implemented" #bruce 080811 added this line return def hide(self): """ Hides the dial and its label (if it has one). Call L{show()} to unhide the dial. @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the dial and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show()
class PM_FontComboBox(QFontComboBox): """ Much (practically all) of this code was taken from PM_ComboBox with only slight modifications. The PM_FontComboBox widget provides a combobox with a text label for a Property Manager group box. The text label can be positioned on either the left or right side of the combobox. For a more complete explanation of a ComboBox, refer to the definition provided in PM_ComboBox. In this case, the list of available fonts is not provided by variable, but is read from the system. @cvar defaultFont: The default font of the combobox. @type defaultFont: QFont @cvar setAsDefault: Determines whether to reset the currentFont to the defaultFont when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this combobox. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} @see: U{B{QComboBox}<http://doc.trolltech.com/4/qcombobox.html>} """ defaultFont = None setAsDefault = True labelWidget = None def __init__(self, parentWidget, label='', labelColumn=0, selectFont='', setAsDefault=True, spanWidth=False): """ Appends a QFontComboBox widget (with a QLabel widget) to <parentWidget>, a property manager group box. Arguments: @param parentWidget: the group box containing this PM widget. @type parentWidget: PM_GroupBox @param label: label that appears to the left of (or above) this PM widget. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param selectFont: initial font of combobox. (''=default) @type selectFont: QFont object (default = '') @param setAsDefault: if True, will restore <defaultFont> as the current Font when the "Restore Defaults" button is clicked. @type setAsDefault: bool (default True) @param spanWidth: If True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: bool (default False) @see: U{B{QComboBox}<http://doc.trolltech.com/4/qcombobox.html>} """ if 0: # Debugging code print "PM_FontComboBox.__init__():" print " label =", label print " selectFont =", selectFont print " setAsDefault =", setAsDefault print " spanWidth =", spanWidth QFontComboBox.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Set initial choice (selectFont). if selectFont != '': self.setCurrentFont(selectFont) self.defaultFont = self.currentFont() parentWidget.addPmWidget(self) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setCurrentFont(self.defaultFont) def hide(self): """ Hides the combobox and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the combobox and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show()
class PM_ComboBox(QComboBox): """ The PM_ComboBox widget provides a combobox with a text label for a Property Manager group box. The text label can be positioned on either the left or right side of the combobox. A combobox is a combined button and popup list that provides a means of presenting a list of options to the user in a way that takes up the minimum amount of screen space. Also, a combobox is a selection widget that displays the current item, and can pop up a list of selectable items. A combobox may be editable, allowing the user to modify each item in the list. Comboboxes can contain pixmaps as well as strings; the insertItem() and changeItem() functions are suitably overloaded. For editable comboboxes, the function clearEdit() is provided, to clear the displayed string without changing the combobox's contents. There are two signals emitted if the current item of a combobox changes, currentIndexChanged() and activated(). currentIndexChanged() is always emitted regardless if the change was done programmatically or by user interaction, while activated() is only emitted when the change is caused by user interaction. The highlighted() signal is emitted when the user highlights an item in the combobox popup list. All three signals exist in two versions, one with a U{B{QString}<http://doc.trolltech.com/4/qstring.html>} argument and one with an int argument. If the user selectes or highlights a pixmap, only the int signals are emitted. Whenever the text of an editable combobox is changed the editTextChanged() signal is emitted. When the user enters a new string in an editable combobox, the widget may or may not insert it, and it can insert it in several locations. The default policy is is AtBottom but you can change this using setInsertPolicy(). It is possible to constrain the input to an editable combobox using U{B{QValidator}<http://doc.trolltech.com/4/qvalidator.html>} ; see setValidator(). By default, any input is accepted. A combobox can be populated using the insert functions, insertStringList() and insertItem() for example. Items can be changed with changeItem(). An item can be removed with removeItem() and all items can be removed with clear(). The text of the current item is returned by currentText(), and the text of a numbered item is returned with text(). The current item can be set with setCurrentIndex(). The number of items in the combobox is returned by count(); the maximum number of items can be set with setMaxCount(). You can allow editing using setEditable(). For editable comboboxes you can set auto-completion using setCompleter() and whether or not the user can add duplicates is set with setDuplicatesEnabled(). PM_ComboBox uses the model/view framework for its popup list and to store its items. By default a QStandardItemModel stores the items and a QListView subclass displays the popuplist. You can access the model and view directly (with model() and view()), but PM_ComboBox also provides functions to set and get item data (e.g., setItemData() and itemText()). You can also set a new model and view (with setModel() and setView()). For the text and icon in the combobox label, the data in the model that has the Qt.DisplayRole and Qt.DecorationRole is used. @cvar defaultChoices: The default choices of the combobox. @type defaultChoices: list @cvar defaultIndex: The default index of the combobox. @type defaultIndex: int @cvar setAsDefault: Determines whether to reset the choices to I{defaultChoices} and currentIndex to I{defaultIndex} when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this combobox. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} @see: U{B{QComboBox}<http://doc.trolltech.com/4/qcombobox.html>} """ defaultIndex = 0 defaultChoices = [] setAsDefault = True labelWidget = None def __init__(self, parentWidget, label='', labelColumn=0, choices=[], index=0, setAsDefault=True, spanWidth=False): """ Appends a QComboBox widget (with a QLabel widget) to <parentWidget>, a property manager group box. Arguments: @param parentWidget: the group box containing this PM widget. @type parentWidget: PM_GroupBox @param label: label that appears to the left of (or above) this PM widget. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param choices: list of combo box choices (strings). @type choices: list @param index: initial index (choice) of combobox. (0=first item) @type index: int (default 0) @param setAsDefault: if True, will restore <index> as the current index when the "Restore Defaults" button is clicked. @type setAsDefault: bool (default True) @param spanWidth: If True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: bool (default False) @see: U{B{QComboBox}<http://doc.trolltech.com/4/qcombobox.html>} """ if 0: # Debugging code print "PM_ComboBox.__init__():" print " label =", label print " choices =", choices print " index =", index print " setAsDefault =", setAsDefault print " spanWidth =", spanWidth QComboBox.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Load QComboBox widget choices and set initial choice (index). for choice in choices: self.addItem(choice) self.setCurrentIndex(index) # Set default index self.defaultIndex = index self.defaultChoices = choices self.setAsDefault = setAsDefault parentWidget.addPmWidget(self) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.clear() # Generates signals! for choice in self.defaultChoices: self.addItem(choice) self.setCurrentIndex(self.defaultIndex) def hide(self): """ Hides the combobox and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the combobox and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() def setCurrentIndex(self, val, blockSignals=False): """ Overrides the superclass method. @param blockSignals: Many times, the caller just wants to setCurrentIndex and don't want to send valueChanged signal. If this flag is set to True, the currentIdexChanged signal won't be emitted. The default value is False. @type blockSignals: bool @see: DnaDisplayStyle_PropertyManager.updateDnaDisplayStyleWidgets() """ #If blockSignals flag is True, the valueChanged signal won't be emitted #This is done by self.blockSignals method below. -- Ninad 2008-08-13 self.blockSignals(blockSignals) QComboBox.setCurrentIndex(self, val) #Make sure to always 'unblock' signals that might have been temporarily #blocked before calling superclass.setValue. self.blockSignals(False) # End of PM_ComboBox ############################
class AppForm(QMainWindow): 'QT form for application' def __init__(self, splash,args,parent=None): self.starttime=None # start time of data self.endtime=None # end time of data self.bbox=args.b # geographical bounding box [w,e,s,n] self.preferredOrigin='p' # prefferred should be 'p': preferred origin ; 'f': first origin ; 'l': last origin self.includeunassoc=False # should we look also at unassociated origins (default - False) self.minmag=0 # minimum magnitude self.maxmag=10.0 # maximum magnitude self.deltaT=60.0 # absolute time difference in seconds to associate events self.deltaR=100.0 # absolute distance difference in km to associate events self._datadict = {} # dictionary of input events self._refdict = {} # dictionary of reference events self.data2reflut = {} # input to ref look up table self.ref2datalut = {} # ref to input look up table self.Bbox = bboxForm() self.Bbox.setLims(*self.bbox) splash.showMessage('Initializing...',Qt.AlignCenter) QApplication.processEvents() QMainWindow.__init__(self, parent) self.setWindowTitle('SC3 XML Diff') self.args = args # save command line arguments self.meter = mpl.lines.Line2D([],[],color='r') # line for measuring distances along canvas splash.showMessage('Creating application...',Qt.AlignCenter) QApplication.processEvents() self.create_menu() # create the app top menu self.create_status_bar() # add a status bar self.create_main_frame() # create the main frame. self.create_toolbox() # create a toolbox if args.i: splash.showMessage('Reading input data...',Qt.AlignCenter) QApplication.processEvents() self.get_input_file(args.i) if args.r: self.get_ref_file(args.r) splash.showMessage('Reading reference data...',Qt.AlignCenter) QApplication.processEvents() self.init_connections() # initialize signal connections splash.showMessage('Populating tables...',Qt.AlignCenter) QApplication.processEvents() self.updatetables() def init_connections(self): '''Connect signals to functions. Using signals to run functions from subprocesses.''' self.Bbox.accepted.connect(self.onBboxAccepted) # connect bbox to dialog ok button self.connect(self.minmagLine, SIGNAL('ok'),self.updateMagLims) self.connect(self.maxmagLine, SIGNAL('ok'),self.updateMagLims) self.connect(self.deltaTLine, SIGNAL('ok'),self.updateDeltaT) self.connect(self.deltaRLine, SIGNAL('ok'),self.updateDeltaR) self.starttimeLine.dateTimeChanged.connect(self.updatestarttime) self.endtimeLine.dateTimeChanged.connect(self.updateendtime) self.includeunassocLine.stateChanged.connect(self.updateUnassoc) self.preferredOriginLine.currentIndexChanged[str].connect(self.updatePreferredOrigin) # self.canvas.mpl_connect('button_press_event',self.on_click) # connect click on canvas # self.canvas.mpl_connect('button_release_event',self.on_unclick) # connect mouse button release on canvas # self.canvas.mpl_connect('motion_notify_event',self.on_move) # connect mouse motion on canvas def create_main_frame(self): 'Create the main frame of application' # create widget self.main_frame = QWidget() # create a general layout vbox = QVBoxLayout() # create a side by side layout for tables self.tb = self.addToolBar('Tools') # tool bar can be moved around self.tb.setMovable(True) self.tb.setFloatable(True) hsplit = QSplitter(Qt.Horizontal) self.headerlabels = HEADERLABELS ###################################### # create a layout for reference data # ###################################### vboxref = QVBoxLayout() # add a title label = QLabel('Reference Data') # create the reference table self.reftable = QTableWidget(0,len(self.headerlabels)) # Add header to table self.reftable.setHorizontalHeaderLabels(self.headerlabels) #self.reftable.setSortingEnabled(True) # settings for drag&drop self.reftable.setDragEnabled(True) self.reftable.setDragDropOverwriteMode(False) self.reftable.setDragDropMode(3) # can drag and drop # connect click events to row selection self.reftable.cellClicked.connect(self.reftable_cellClicked) # can select only rows self.reftable.setSelectionBehavior(QAbstractItemView.SelectRows) # no edits please #self.reftable.setEditTriggers(QAbstractItemView.NoEditTriggers) # No auto scrolling self.reftable.setAutoScroll(False) # connect drop events to adding events self.reftable.dropEvent = self.reftable_dropEvent # add label to ref layout vboxref.addWidget(label) # add table to ref layout vboxref.addWidget(self.reftable) ################################## # create a layout for input data # ################################## vboxdata = QVBoxLayout() # add a title label = QLabel('Input data') # create the input table self.datatable = QTableWidget(0,len(self.headerlabels)) # Add header to table self.datatable.setHorizontalHeaderLabels(self.headerlabels) #self.datatable.setSortingEnabled(True) # settings for drag&drop self.datatable.setDragEnabled(True) self.datatable.setDragDropOverwriteMode(False) self.datatable.setDragDropMode(3) # can drag and drop # connect click events to row selection self.datatable.cellClicked.connect(self.datatable_cellClicked) # can select only rows self.datatable.setSelectionBehavior(QAbstractItemView.SelectRows) # no edits please #self.datatable.setEditTriggers(QAbstractItemView.NoEditTriggers) # No auto scrolling self.datatable.setAutoScroll(False) # connect drop events to ID association self.datatable.dropEvent = self.datatable_dropEvent # add label to SC3 layout vboxdata.addWidget(label) # add table to SC3 layout vboxdata.addWidget(self.datatable) # Add layouts to side by side layout w = QWidget() w.setLayout(vboxref) hsplit.addWidget(w) w = QWidget() w.setLayout(vboxdata) hsplit.addWidget(w) # add side by side to general layout vbox.insertWidget(1,hsplit) # add layout to widget self.main_frame.setLayout(vbox) # set widget to be central widget self.setCentralWidget(self.main_frame) def reftable_cellClicked(self,row,col): 'any click on table will select the whole row' # select all row self.reftable.selectRow(row) # clear any selection in data table self.datatable.clearSelection() def datatable_cellClicked(self,row,col): 'any click on table will select the whole row' # select all row self.datatable.selectRow(row) # clear any selection in ref table self.reftable.clearSelection() def datatable_dropEvent(self,event): 'Associate ref event with input event if ref row is dragged and dropped on input row' if not event.source()==self.reftable: return # make sure row is from reference table if not self.datatable.itemAt(event.pos()): return # make sure we dropped reference row on an input row # get the referrence ID refID = str(event.source().selectedItems()[0].text()) # get the ID item on input table item = self.datatable.item(self.datatable.itemAt(event.pos()).row(),0) inputID = str(item.text()) # confirm with user to associate if refID in self.data2reflut or inputID in self.data2reflut: self.statusBar().showMessage('%s and/or %s are already associated. Aborting.'%(refID,inputID) , 2000) return ok = QMessageBox.question(self,'Associate Event', 'Are you sure you want to associate\n%s to %s?'%(refID,inputID), 'No','Yes') if ok: # update the lut self.ref2datalut[refID]=inputID self.data2reflut[inputID]=refID self.updateStatus() # report to status bar self.statusBar().showMessage('Associated %s with %s'%(refID,inputID) , 2000) def reftable_dropEvent(self,event): 'Add new events to reference table if input row is dragged and dropped on ref table' if not event.source()==self.datatable: return # make sure its an input row # get row items items = event.source().selectedItems() # get input ID ID = str(items[0].text()) # make sure ID is associated already if ID in self.data2reflut: self.statusBar().showMessage('Event %s already associated with %s'%(ID,self.data2reflut[ID]) , 2000) return # disable sorting self.datatable.setSortingEnabled(False) self.reftable.setSortingEnabled(False) #update lut self.data2reflut[ID]=ID self.ref2datalut[ID]=ID # add a new row to reference table i = self.reftable.rowCount() self.reftable.insertRow(i) # copy line from input table for j,item in enumerate(items): newitem = QTableWidgetItem(item.text()) self.reftable.setItem(i,j,newitem) # update status self.updateStatus() # sort ref table self.datatable.setSortingEnabled(True) self.reftable.setSortingEnabled(True) # report to statusbar self.statusBar().showMessage('Added Event %s'%(ID) , 2000) def delete_row(self): 'Delete a selected row from table' # get selected row(s) on SC3 table items = [i for i in self.datatable.selectedItems()+self.reftable.selectedItems() if i.column()==0] for item in items: # get selected row number row = item.row() # get event ID ID = str(item.text()) # remove the row from table item.tableWidget().removeRow(row) # remove lut records [self.data2reflut.pop(k) for k,v in self.data2reflut.items() if k==ID or v==ID] [self.ref2datalut.pop(k) for k,v in self.ref2datalut.items() if k==ID or v==ID] self.updateStatus() # report to statusbar self.statusBar().showMessage('Removed Event %s from table'%ID , 2000) if len(items)>1: self.statusBar().showMessage('%d Events removed from table'%len(items) , 2000) def clear_table(self,table): 'Clear a table' for i in range(table.rowCount())[::-1]: table.removeRow(i) def clear_tables(self): [self.clear_table(t) for t in [self.datatable,self.reftable]] def create_tooltip_widget(self): 'creates tooltip of figure elements' self.TTWidget = QLabel() # take a QLabel self.TTWidget.setFrameShape(QFrame.StyledPanel) # add a frame self.TTWidget.setWindowFlags(Qt.ToolTip) # make window look like a tooltip self.TTWidget.setAttribute(Qt.WA_TransparentForMouseEvents) # mouse events can't affet it. self.TTWidget.hide() # hide for now. see self.on_move function on how to use. def get_input_file(self,filesurl=None): 'open a dialog to get a file name' if not filesurl: filesurl = QFileDialog.getOpenFileNames(self, 'Open Input data file(s)',filter='*.xml') # get the file name self.ref2datalut={} # init look up table self.data2reflut={} # init look up table if len(filesurl): xmls = [str(f) for f in filesurl] Dataep = concatenateSC3XML(xmls) if not Dataep: self.statusBar().showMessage('No Events in file.', 2000) return self._datadict = eparams2seisEvents(Dataep,starttime=self.starttime,endtime=self.endtime,minmag=self.minmag,maxmag=self.maxmag,bbox=self.bbox,preferred=self.preferredOrigin,includeunassoc=self.includeunassoc) self.statusBar().showMessage('Loaded %d events'%len(self._datadict), 2000) self.updatetables() def get_ref_file(self,filesurl=None,refFileType=None): 'open a dialog to get a file name' if not filesurl: filesurl,refFileType = QFileDialog.getOpenFileNamesAndFilter(self, 'Open Reference data file(s)',filter='XML Files (*.xml);;CSV Files (*.csv)') # get the file name else: refFileType = os.path.splitext(filesurl[0])[1].upper() self.ref2datalut={}# init look up table self.data2reflut={}# init look up table if 'XML' in refFileType: xmls = [str(f) for f in filesurl] Refep = concatenateSC3XML(xmls) if not Refep: self.statusBar().showMessage('No Events in file.', 2000) return self._refdict = eparams2seisEvents(Refep,starttime=self.starttime,endtime=self.endtime,minmag=self.minmag,maxmag=self.maxmag,bbox=self.bbox,preferred=self.preferredOrigin,includeunassoc=self.includeunassoc) self.statusBar().showMessage('Loaded %d events'%len(self._refdict), 2000) return self.updatetables() if 'CSV' in refFileType: CSVs = [str(f) for f in filesurl] Refdb = concatenateCSV(CSVs) if not len(Refdb): self.statusBar().showMessage('No Events in file.', 2000) return self.updatetables() self._refdict = db2seisEvents(Refdb,starttime=self.starttime,endtime=self.endtime,minmag=self.minmag,maxmag=self.maxmag,bbox=self.bbox,preferred=self.preferredOrigin,includeunassoc=self.includeunassoc) self.statusBar().showMessage('Loaded %d events'%len(self._refdict), 2000) return self.updatetables() self.statusBar().showMessage("Can't load reference file(s)", 3000) def filterEvents(self,D): 'filter events by time,location and magnitude limits' [D.pop(i) for i in [k for k in D if D[k].mag>self.maxmag or D[k].mag<self.minmag]] if self.starttime: [D.pop(i) for i in [k for k in D if D[k].ot<self.starttime]] if self.endtime: [D.pop(i) for i in [k for k in D if D[k].ot>self.endtime]] [D.pop(i) for i in [k for k in D if D[k].lon>self.bbox[1] or D[k].lon<self.bbox[0]]] [D.pop(i) for i in [k for k in D if D[k].lat>self.bbox[3] or D[k].lat<self.bbox[2]]] return D def associate(self): 'associate events' self.ref2datalut = {} self.data2reflut = {} # filter unwanted events self.refdict = {} self.datadict = {} self.refdict.update(self._refdict) self.datadict.update(self._datadict) self.refdict = self.filterEvents(self.refdict) self.datadict= self.filterEvents(self.datadict) for ref in self.refdict.keys(): # get potentials associated events for reference. (time diff,seisevent obj) list potentials = [(abs(self.datadict[k].ot-self.refdict[ref].ot).total_seconds(),k) for k in self.datadict if abs(self.datadict[k].ot-self.refdict[ref].ot).total_seconds()<=self.deltaT and (self.datadict[k]-self.refdict[ref])[-1]/1000.0<=self.deltaR] potentials.sort() # sort according to time difference if potentials: self.ref2datalut[ref] = potentials[0][1] # get first one. self.data2reflut[potentials[0][1]] = ref def recolor_table(self,table): ' add colors to lines. Green - True event, Red - false event, orange - missed event' # mark missed events on table stat = HEADERLABELS.index('Status') for i in xrange(table.rowCount()): table.selectRow(i) for item in table.selectedItems(): f = item.foreground() if str(table.item(i,stat).text())=='M': f.setColor(QColor('#FFA500')) elif str(table.item(i,stat).text())=='F': f.setColor(QColor('red')) elif str(table.item(i,stat).text())=='T': f.setColor(QColor('k')) item.setForeground(f) table.clearSelection() def recolor_tables(self): [self.recolor_table(table) for table in [self.datatable,self.reftable]] def build_tables(self): 'populate the gui tables from dictionaries' # populate ref table self.reftable.setSortingEnabled(False) for i,e in enumerate(self.refdict): # add a row self.reftable.insertRow(i) # create an ID item item = QTableWidgetItem(e) # add ID to row self.reftable.setItem(i,0,item) # insert event date to row for j,v in enumerate([self.refdict[e].__dict__[k] for k in ['ot','mag','lat','lon','status','refid','SCevaluationStatus']]): item = QTableWidgetItem(str(v)) self.reftable.setItem(i,j+1,item) # sort the table self.reftable.setSortingEnabled(True) # adjust columns size self.reftable.resizeColumnsToContents() # populate data table self.datatable.setSortingEnabled(False) for i,e in enumerate(self.datadict): # add a row self.datatable.insertRow(i) # create an ID cell item = QTableWidgetItem(e) # add ID to row self.datatable.setItem(i,0,item) # insert event date to row for j,v in enumerate([self.datadict[e].__dict__[k] for k in ['ot','mag','lat','lon','status','refid','SCevaluationStatus']]): item = QTableWidgetItem(str(v)) self.datatable.setItem(i,j+1,item) # sort the table self.datatable.setSortingEnabled(True) # adjust columns size self.datatable.resizeColumnsToContents() # update status self.updateStatus() def updateStatus(self): 'update status of event (T,M,F)' # referrece table stat = HEADERLABELS.index('Status') refID = HEADERLABELS.index('refID') self.reftable.setSortingEnabled(False) for i in xrange(self.reftable.rowCount()): ID = str(self.reftable.item(i,0).text()) if ID in self.ref2datalut: self.reftable.item(i,stat).setText('T') self.reftable.item(i,refID).setText(self.ref2datalut[ID]) else: self.reftable.item(i,stat).setText('M') self.reftable.item(i,refID).setText('') self.reftable.setSortingEnabled(True) self.datatable.setSortingEnabled(False) for i in xrange(self.datatable.rowCount()): ID = str(self.datatable.item(i,0).text()) if ID in self.data2reflut: self.datatable.item(i,stat).setText('T') self.datatable.item(i,refID).setText(self.data2reflut[ID]) else: self.datatable.item(i,stat).setText('F') self.datatable.item(i,refID).setText('') self.datatable.setSortingEnabled(True) # repaint missed False and true events self.recolor_tables() def updatetables(self): self.associate() self.clear_tables() self.build_tables() self.statusBar().showMessage('Tables updated',2000) def updateMagLims(self): self.minmag = float(self.minmagLine.text()) self.maxmag = float(self.maxmagLine.text()) self.statusBar().showMessage('Magnitude limits updated',2000) def updateDeltaT(self): self.deltaT = float(self.deltaTLine.text()) self.statusBar().showMessage('Association time limit updated',2000) def updateDeltaR(self): self.deltaR = float(self.deltaRLine.text()) self.statusBar().showMessage('Association distance limit updated',2000) def updatestarttime(self,starttime): self.starttime = starttime.toPyDateTime() self.statusBar().showMessage('Start time limit updated',2000) def updateendtime(self,endtime): self.endtime = endtime.toPyDateTime() self.statusBar().showMessage('End time limit updated',2000) def updateUnassoc(self,stat): self.includeunassoc = bool(stat) self.statusBar().showMessage('Including unassociated origins set to %s'%self.includeunassoc,2000) def updatePreferredOrigin(self,pref): self.preferredOrigin = pref[0] self.statusBar().showMessage('Preferred Origins set to %s'%pref,2000) def evaluate(self): i = self.headerlabels.index('Status') table = self.datatable T = len([table.item(j,i) for j in xrange(table.rowCount()) if str(table.item(j,i).text())=='T']) F = len([table.item(j,i) for j in xrange(table.rowCount()) if str(table.item(j,i).text())=='F']) table = self.reftable M = len([table.item(j,i) for j in xrange(table.rowCount()) if str(table.item(j,i).text())=='M']) self.statusBar().showMessage('T: %d ; M: %d ; F: %d ; Score: %.1f%% for %d events'%(T,M,F,100.0*T/sum([T,F,M]),sum([T,F,M])), 5000) return T,F,M,100.0*T/sum([T,F,M]) def saveAs_figure(self): pass def create_menu(self): 'Creates main menu' # Populate the menubar: # Add File submenu self.file_menu = self.menuBar().addMenu("&File") # load input data load_input_action = self.create_action("Load &Input", shortcut="Ctrl+I", slot=self.get_input_file, icon='document-open',tip="Load input data from a file(s)") # load reference data load_ref_action = self.create_action("Load &Reference", shortcut="Ctrl+R", slot=self.get_ref_file, icon='document-open',tip="Load reference data from a file(s)") # Save As... saveAs_action = self.create_action("S&ave As...", shortcut="Shift+S", slot=self.saveAs_figure, icon='filesaveas',tip="Save the figure") # Quit quit_action = self.create_action("&Quit", slot=self.close, icon='system-shutdown',shortcut="Ctrl+Q", tip="Close the application") # populate the file submenu self.add_actions(self.file_menu, (load_input_action,load_ref_action, saveAs_action, None, quit_action)) # Add Edit submenu self.Edit_menu = self.menuBar().addMenu("&Edit") # delete an event delete_action = self.create_action("&Delete", slot=self.delete_row,shortcut="Del", tip="Delete selected row from table") # populate tools submenu self.add_actions(self.Edit_menu,[delete_action]) # Add help submenu self.help_menu = self.menuBar().addMenu("&Help") # Help help_action = self.create_action("&Help", shortcut='F1', slot=self.on_help, icon='Help',tip='help') # About about_action = self.create_action("&About", shortcut='F2', slot=self.on_about, tip='About This Application') # About QT aboutQt_action = self.create_action("&About QT", shortcut='F3', slot=self.on_aboutQt, tip='About QT') # License license_action = self.create_action("&License", shortcut='F4', slot=self.on_license, tip='Application License') # Populate help submenu self.add_actions(self.help_menu, (help_action,None,about_action,aboutQt_action,license_action)) def create_toolbox(self): w = QWidget() l = QGridLayout(w) self.refreshbutton = self.create_pushButton('Refresh', slot=self.updatetables, shortcut=None, icon=None, tip='Refresh Tables') self.scorebutton = self.create_pushButton('Score', slot=self.evaluate, shortcut=None, icon=None, tip='Evaluate Score') l.addWidget(self.refreshbutton,0,0) l.addWidget(self.scorebutton,1,0) self.tb.addWidget(w) w = QWidget() l = QGridLayout(w) self.minmagLine = ConnectedLineEdit(str(self.minmag),0,self.maxmag) self.maxmagLine = ConnectedLineEdit(str(self.maxmag),self.minmag,10) self.minmagLine.setConnected(self.maxmagLine, 'bottom') self.maxmagLine.setConnected(self.minmagLine, 'top') l.addWidget(QLabel('Magnitude Limits'),0,1) l.addWidget(QLabel('Min'),1,0) l.addWidget(QLabel('Max'),2,0) l.addWidget(self.minmagLine,1,1) l.addWidget(self.maxmagLine,2,1) self.tb.addWidget(w) w = QWidget() l = QGridLayout(w) self.deltaTLine = ConnectedLineEdit(str(self.deltaT),0,18000,3) self.deltaRLine = ConnectedLineEdit(str(self.deltaR),0,1000,3) l.addWidget(QLabel('Association Limits'),0,1) l.addWidget(QLabel('Time (sec)'),1,0) l.addWidget(QLabel('Range (km)'),2,0) l.addWidget(self.deltaTLine,1,1) l.addWidget(self.deltaRLine,2,1) self.tb.addWidget(w) w = QWidget() v = QVBoxLayout(w) ww = QWidget() v.addWidget(ww) l = QGridLayout(ww) W,E,S,N = self.bbox self.EAST = QLabel(str(E)) self.WEST = QLabel(str(W)) self.NORTH = QLabel(str(N)) self.SOUTH = QLabel(str(S)) self.bboxbutton = self.create_pushButton('Region', slot=self.Bbox.show, shortcut=None, icon=None, tip='Define a geographic region') l.addWidget(self.EAST,1,2,1,1,Qt.Alignment(Qt.AlignLeft)) l.addWidget(self.WEST,1,0,1,1,Qt.Alignment(Qt.AlignRight)) l.addWidget(self.NORTH,0,1,1,1,Qt.Alignment(Qt.AlignHCenter)) l.addWidget(self.SOUTH,2,1,1,1,Qt.Alignment(Qt.AlignHCenter)) v.addWidget(self.bboxbutton) self.tb.addWidget(w) w = QWidget() l = QGridLayout(w) self.starttimeLine = QDateTimeEdit() self.starttimeLine.setDisplayFormat('yyyy-MM-dd HH:mm:ss') self.starttimeLine.setCalendarPopup(True) self.endtimeLine = QDateTimeEdit() self.endtimeLine.setDateTime(QDateTime.currentDateTimeUtc()) self.endtimeLine.setDisplayFormat('yyyy-MM-dd HH:mm:ss') self.endtimeLine.setCalendarPopup(True) l.addWidget(QLabel('Start:'),0,0) l.addWidget(self.starttimeLine,0,1) l.addWidget(QLabel('End:'),1,0) l.addWidget(self.endtimeLine,1,1) self.tb.addWidget(w) w = QWidget() l = QGridLayout(w) self.preferredOriginLine = QComboBox() self.preferredOriginLine.addItems(['preferred','first','last']) self.includeunassocLine = QCheckBox('Unassociated') self.includeunassocLine.setCheckState(self.includeunassoc) l.addWidget(QLabel('Origins:'),0,0) l.addWidget(self.preferredOriginLine,1,0) l.addWidget(self.includeunassocLine,2,0) self.tb.addWidget(w) def onBboxAccepted(self): '''get region limits from BboxForm. fires when Bbox is accepted''' if self.Bbox.validate(): # make sure limits are acceptable self.bbox = self.Bbox.getLims() # get limits w,e,s,n = self.bbox self.WEST.setText(str(w)) self.EAST.setText(str(e)) self.NORTH.setText(str(n)) self.SOUTH.setText(str(s)) def add_actions(self, target, actions): 'Utility function for menu creation' for action in actions: if action is None: target.addSeparator() else: target.addAction(action) def create_action( self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): 'Utility function for menu actions creation' action = QAction(text, self) action.setIconVisibleInMenu(True) if icon is not None: i = QIcon.fromTheme(icon,QIcon(":/%s.png" % icon)) action.setIcon(i) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def create_pushButton(self,text,toolbar=None, slot=None, shortcut=None, icon=None, tip=None): 'Utility function for button creation' # create the button button = QPushButton(text,self) # populate properties if slot: # connect a function button.clicked.connect(slot) if icon: # add icon i = QIcon.fromTheme(icon,QIcon(":/%s.png" % icon)) button.setIcon(i) button.setIconSize(QSize(24,24)) if shortcut: # set the shortcut button.setShortcut(shortcut) if tip: # add tooltip and status tip button.setToolTip(tip) button.setStatusTip(tip) if toolbar: # add the button to a toolbar (or any widget) toolbar.addWidget(button) return button def create_status_bar(self): 'Add a status bar' # set default message self.status_text = QLabel("Ready") self.connstatLabel = QLabel() self.statusBar().addWidget(self.status_text, 1) self.statusBar().addPermanentWidget(self.connstatLabel) def on_about(self): 'show a messagebox about the application' msg = "<p align='center'><big>SCxmlDiff</big><br><br> \ Compare two Seiscomp3 XML files<br><br> \ <small>Created<br> \ by<br> \ Ran Novitsky Nof @ BSL, 2015</small><br><br>\ <a href='http://ran.rnof.info/'>http://ran.rnof.info</a><p>" QMessageBox.about(self,"About", msg.strip()) def on_aboutQt(self): 'show a messagebox about QT' QMessageBox.aboutQt(self,'') def on_license(self): 'GPL licanse message' msg = "<p><b>This</b> is a free software; you can redistribute it and/or modify it under the \ terms of the GNU General Public License as published by the Free Software \ Foundation; either version 3 of the License, or (at your option) any later \ version.</p>\ <p><b>This application</b> is distributed in the hope that it will be useful, but WITHOUT ANY \ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR \ A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p> \ <p>You should have received a copy of the GNU General Public License along with \ this application; if not, see <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.</p>" QMessageBox.about(self,"Application Licanse", msg.strip()) def on_help(self): 'Show help on a message window. Uses the argparse help' msg = '<pre>'+parser.format_help()#.replace('\n','</p><p>') msg = msg.replace('*****@*****.**',"<a href='mailto:[email protected]'>[email protected]</a>").strip()+'<\pre>' QMessageBox.about(self,"Help", msg) def message(self,msg,title='Error'): 'a simple message window' QMessageBox.about(self,title,msg)
class InformationWindow(QWidget): def __init__(self): QWidget.__init__(self, ctx.mainScreen) self.setObjectName("InformationWindow") self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setFixedHeight(50) self.setMaximumWidth(800) self.setStyleSheet(""" QFrame#frame { border: 1px solid rgba(255,255,255,30); /*border-radius: 4px;*/ background-color: rgba(0,0,0,100);} QLabel { border:none; color:#FFFFFF;} QProgressBar { border: 1px solid white;} QProgressBar::chunk { background-color: #F1610D; width: 0.5px;} """) self.gridlayout = QGridLayout(self) self.frame = QFrame(self) self.frame.setObjectName("frame") self.horizontalLayout = QHBoxLayout(self.frame) self.horizontalLayout.setContentsMargins(10, 0, 10, 0) # Spinner self.spinner = QLabel(self.frame) self.spinner.setMinimumSize(QSize(16, 16)) self.spinner.setMaximumSize(QSize(16, 16)) self.spinner.setIndent(6) self.movie = QMovie(':/images/working.mng') self.spinner.setMovie(self.movie) self.movie.start() self.horizontalLayout.addWidget(self.spinner) # Message self.label = QLabel(self.frame) self.label.setAlignment(Qt.AlignCenter) self.label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.icon = QLabel(self.frame) self.icon.setFixedWidth(16) self.icon.setFixedHeight(16) self.horizontalLayout.setSpacing(10) self.horizontalLayout.addWidget(self.icon) self.horizontalLayout.addWidget(self.label) self.gridlayout.addWidget(self.frame, 0, 0, 1, 1) def update(self, message, type=None, spinner=False): fontMetric = self.label.fontMetrics() textWidth = fontMetric.width(message) if type: self.icon.show() if type == "error": self.icon.setPixmap(QPixmap(":/gui/pics/dialog-error.png")) self.setStyleSheet( " QFrame#frame {background-color: rgba(255,0,0,100);} ") elif type == "warning": self.icon.setPixmap(QPixmap(":/gui/pics/dialog-warning.png")) self.setStyleSheet( " QFrame#frame {background-color: rgba(0,0,0,100);} ") self.setFixedWidth(textWidth + self.icon.width() + 50) self.label.setText(message) else: self.icon.hide() self.setStyleSheet( " QFrame#frame {background-color: rgba(0,0,0,100);} ") self.setFixedWidth(textWidth + self.icon.width() + 100) self.label.setText(message) self.spinner.setVisible(spinner) self.move(ctx.mainScreen.width() / 2 - self.width() / 2, ctx.mainScreen.height() - self.height() / 2 - 50) self.show() def refresh(self): ctx.mainScreen.processEvents() def show(self): QWidget.show(self) self.refresh() def hide(self): QWidget.hide(self) self.refresh()
class PM_ListWidget( QListWidget ): """ The PM_ListWidget widget provides a QListWidget with a QLabel for a Property Manager group box. @cvar defaultItems: The default list of items in the list widget. @type defaultItems: list @cvar defaultRow: The default row of the list widget. @type defaultRow: int @cvar setAsDefault: Determines whether to reset the choices to I{defaultItems} and current row to I{defaultRow} when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this list widget. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} """ defaultRow = 0 defaultItems = [] setAsDefault = True labelWidget = None def __init__(self, parentWidget, label = '', labelColumn = 0, items = [], defaultRow = 0, setAsDefault = True, heightByRows = 6, spanWidth = False ): """ Appends a QListWidget (Qt) widget to the bottom of I{parentWidget}, a Property Manager group box. @param parentWidget: The parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left or right of the checkbox. If spanWidth is True, the label will be displayed on its own row directly above the list widget. To suppress the label, set I{label} to an empty string. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param items: list of items (strings) to be inserted in the widget. @type items: list @param defaultRow: The default row (item) selected, where 0 is the first row. @type defaultRow: int @param setAsDefault: If True, will restore <idx> as the current index when the "Restore Defaults" button is clicked. @type setAsDefault: bool @param heightByRows: The height of the list widget. @type heightByRows: int @param spanWidth: If True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: bool @see: U{B{QListWidget}<http://doc.trolltech.com/4/qlistwidget.html>} """ if 0: # Debugging code print "PM_ListWidget.__init__():" print " label = ", label print " labelColumn = ", labelColumn print " items = ", items print " defaultRow = ", defaultRow print " setAsDefault = ", setAsDefault print " heightByRows = ", heightByRows print " spanWidth = ", spanWidth QListWidget.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Load QComboBox widget choice items and set initial choice (index). self.insertItems(0, items, setAsDefault) self.setCurrentRow(defaultRow, setAsDefault) # Set height of list widget. margin = self.fontMetrics().leading() * 2 # Mark 2007-05-28 height = heightByRows * self.fontMetrics().lineSpacing() + margin self.setMaximumHeight(height) #As of 2008-04-16, the items in any list widgets won't be sorted #automatically. It can be changes by simply uncommentting the lines #below -- Ninad ##self.setSortingEnabled(True) ##self.sortItems() self.setAlternatingRowColors(True) parentWidget.addPmWidget(self) def insertItems(self, row, items, setAsDefault = True): """ Insert items of widget starting at <row>. If <setAsDefault> is True, <items> become the default list of items for this widget. "Restore Defaults" will reset the list of items to <items>. Note: <items> will always replace the list of current items in the widget. <row> is ignored. This is considered a bug. -- Mark 2007-06-04 """ if row <> 0: msg = "PM_ListWidget.insertItems(): <row> must be zero."\ "See docstring for details:" print_compact_traceback(msg) return if setAsDefault: self.setAsDefault = setAsDefault self.defaultItems = items self.clear() QListWidget.insertItems(self, row, items) def setCurrentRow(self, row, setAsDefault = False ): """ Select new row. @param row: The new row to select. @type row: int @param setAsDefault: If True, I{row} becomes the default row when "Restore Defaults" is clicked. """ if setAsDefault: self.setAsDefault = setAsDefault self.defaultRow = row QListWidget.setCurrentRow(self, row) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.insertItems(0, self.defaultItems) self.setCurrentRow(self.defaultRow) ## self.clear() ## for choice in self.defaultChoices: ## self.addItem(choice) ## self.setCurrentRow(self.defaultRow) def hide(self): """ Hides the list widget and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the list widget and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() # End of PM_ListWidget ############################
class HTMLViewerDialog(QDialog): """This class implements a dialog to view a piece of HTML text.""" def __init__(self, parent, config_name=None, buttons=[], *args): """Creates dialog. 'config_name' is used to get/set default window size from Config object 'buttons' can be a list of names or (QPixmapWrapper,name[,tooltip]) tuples to provide custom buttons at the bottom of the dialog. When a button is clicked, the dialog emits SIGNAL("name"). A "Close" button is always provided, this simply hides the dialog. """ QDialog.__init__(self, parent, *args) self.setModal(False) lo = QVBoxLayout(self) # create viewer self.label = QLabel(self) self.label.setMargin(5) self.label.setWordWrap(True) lo.addWidget(self.label) self.label.hide() self.viewer = QTextBrowser(self) lo.addWidget(self.viewer) # self.viewer.setReadOnly(True) self.viewer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) QObject.connect(self.viewer, SIGNAL("anchorClicked(const QUrl &)"), self._urlClicked) self._source = None lo.addSpacing(5) # create button bar btnfr = QFrame(self) btnfr.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) # btnfr.setMargin(5) lo.addWidget(btnfr) lo.addSpacing(5) btnfr_lo = QHBoxLayout(btnfr) btnfr_lo.setMargin(5) # add user buttons self._user_buttons = {} for name in buttons: if isinstance(name, str): btn = QPushButton(name, btnfr) elif isinstance(name, (list, tuple)): if len(name) < 3: pixmap, name = name tip = None else: pixmap, name, tip = name btn = QPushButton(pixmap.icon(), name, btnfr) if tip: btn.setToolTip(tip) self._user_buttons[name] = btn btn._clicked = Kittens.utils.curry(self.emit, SIGNAL(name)) self.connect(btn, SIGNAL("clicked()"), btn._clicked) btnfr_lo.addWidget(btn, 1) # add a Close button btnfr_lo.addStretch(100) closebtn = QPushButton(pixmaps.grey_round_cross.icon(), "Close", btnfr) self.connect(closebtn, SIGNAL("clicked()"), self.hide) btnfr_lo.addWidget(closebtn, 1) # resize selves self.config_name = config_name or "html-viewer" width = Config.getint('%s-width' % self.config_name, 512) height = Config.getint('%s-height' % self.config_name, 512) self.resize(QSize(width, height)) def resizeEvent(self, ev): QDialog.resizeEvent(self, ev) sz = ev.size() Config.set('%s-width' % self.config_name, sz.width()) Config.set('%s-height' % self.config_name, sz.height()) def setDocument(self, filename, empty=""): """Sets the HTML text to be displayed. """ self._source = QUrl.fromLocalFile(filename) if os.path.exists(filename): self.viewer.setSource(self._source) else: self.viewer.setText(empty) def _urlClicked(self, url): path = str(url.path()) if path: self.emit(SIGNAL("viewPath"), path) # to make sure it keeps displaying the same thing self.viewer.setSource(self._source) def reload(self): self.viewer.reload() def setLabel(self, label=None): if label is None: self.label.hide() else: self.label.setText(label) self.label.show()
class SelectNodeByNameDockWidget(PM_DockWidget): """ The Ui_DnaSequenceEditor class defines UI elements for the Sequence Editor object. The sequence editor is usually visible while in DNA edit mode. It is a DockWidget that is doced at the bottom of the MainWindow """ _title = "Select Node by Name" _groupBoxCount = 0 _lastGroupBox = None def __init__(self, win): """ Constructor for the Ui_DnaSequenceEditor @param win: The parentWidget (MainWindow) for the sequence editor """ self.win = win parentWidget = win _superclass.__init__(self, parentWidget, title=self._title) win.addDockWidget(Qt.BottomDockWidgetArea, self) self.setFixedHeight(120) ##self.setFixedWidth(90) self.connect_or_disconnect_signals(True) if not self.win.selectByNameAction.isChecked(): self.close() def show(self): """ Overrides superclass method. """ _superclass.show(self) val = env.prefs[dnaSearchTypeLabelChoice_prefs_key] self.searchTypeComboBox_indexChanged(val) def connect_or_disconnect_signals(self, isConnect): """ Connect or disconnect widget signals sent to their slot methods. This can be overridden in subclasses. By default it does nothing. @param isConnect: If True the widget will send the signals to the slot method. @type isConnect: boolean """ if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect self._listWidget.connect_or_disconnect_signals(isConnect) change_connect(self.searchToolButton, SIGNAL("clicked()"), self.searchNodes) prefs_key = dnaSearchTypeLabelChoice_prefs_key connect_comboBox_with_pref(self.searchTypeComboBox, prefs_key) change_connect(self.searchTypeComboBox, SIGNAL("currentIndexChanged(int)"), self.searchTypeComboBox_indexChanged) def searchTypeComboBox_indexChanged(self, val): if val == 0: ##self._widgetRow1.show() ##self._widgetRow2.hide() self._widgetRow1.setEnabled(True) self._widgetRow2.setEnabled(False) else: ##self._widgetRow2.show() ##self._widgetRow1.hide() self._widgetRow2.setEnabled(True) self._widgetRow1.setEnabled(False) def searchNodes(self): """ ONLY implemented for DnaStrand or DnaSegments. """ assy = self.win.assy topnode = assy.part.topnode lst = [] def func(node): if isinstance(node, assy.DnaStrandOrSegment): lst.append(node) topnode.apply2all(func) choice = env.prefs[dnaSearchTypeLabelChoice_prefs_key] if choice == 0: nodes = self._searchNodesByName(lst) elif choice == 1: nodes = self._searchNodesByNucleotides(lst) self._listWidget.insertItems(row=0, items=nodes) def _searchNodesByNucleotides(self, nodeList): lst = nodeList min_val = self._nucleotidesSpinBox_1.value() max_val = self._nucleotidesSpinBox_2.value() if min_val > max_val: print "Lower value for number of nucleotides exceeds max search value" return () def func2(node): n = node.getNumberOfNucleotides() return (n >= min_val and n <= max_val) return filter(lambda m: func2(m), lst) def _searchNodesByName(self, nodeList): nodeNameString = self.findLineEdit.text() nodeNameString = str(nodeNameString) lst = nodeList def func2(node): n = len(nodeNameString) if len(node.name) < n: return False nameString = str(node.name[:n]) if nameString.lower() == nodeNameString.lower(): return True return False return filter(lambda m: func2(m), lst) def closeEvent(self, event): self.win.selectByNameAction.setChecked(False) _superclass.closeEvent(self, event) def _loadWidgets(self): """ Overrides PM.PM_DockWidget._loadWidgets. Loads the widget in this dockwidget. """ self._loadMenuWidgets() self._loadTableWidget() def _loadTableWidget(self): self._listWidget = PM_DnaSearchResultTable(self, self.win) def _loadMenuWidgets(self): """ Load the various menu widgets (e.g. Open, save sequence options, Find and replace widgets etc. """ #Note: Find and replace widgets might be moved to their own class. self.searchTypeComboBox = \ PM_ComboBox( self, label = "Search options:", choices = ["By node name", "By # of bases (DNA only)"], setAsDefault = True) #Find widgets -- self._nucleotidesSpinBox_1 = PM_SpinBox(self, label="", value=10, setAsDefault=False, singleStep=10, minimum=1, maximum=50000) self._nucleotidesSpinBox_2 = PM_SpinBox(self, label="", value=50, setAsDefault=False, singleStep=10, minimum=1, maximum=50000) self.findLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.findLineEdit.setMaximumWidth(80) self.findOptionsToolButton = PM_ToolButton(self) self.findOptionsToolButton.setMaximumWidth(12) self.findOptionsToolButton.setAutoRaise(True) ##self.findOptionsToolButton.setPopupMode(QToolButton.MenuButtonPopup) ##self._setFindOptionsToolButtonMenu() self.searchToolButton = PM_ToolButton( self, iconPath="ui/actions/Properties Manager/Find_Next.png") self.searchToolButton.setAutoRaise(False) self.warningSign = QLabel(self) self.warningSign.setPixmap( getpixmap('ui/actions/Properties Manager/Warning.png')) self.warningSign.hide() self.phraseNotFoundLabel = QLabel(self) self.phraseNotFoundLabel.setText("Not Found") self.phraseNotFoundLabel.hide() # NOTE: Following needs cleanup in the PM_WidgetRow/ PM_WidgetGrid # but this explanation is sufficient until thats done -- # When the widget type starts with the word 'PM_' , the # PM_WidgetRow treats it as a well defined widget and thus doesn't try # to create a QWidget object (or its subclasses) # This is the reason why qLabels such as self.warningSign and # self.phraseNotFoundLabel are defined as PM_Labels and not 'QLabels' # If they were defined as 'QLabel'(s) then PM_WidgetRow would have # recreated the label. Since we want to show/hide the above mentioned # labels (and if they were recreated as mentioned above), # we would have needed to define those something like this: # self.phraseNotFoundLabel = widgetRow._widgetList[-2] #Cleanup in PM_widgetGrid could be to check if the widget starts with #'Q' instead of 'PM_' #Widgets to include in the widget row. widgetList1 = [('QLabel', " Search for name:", 1), ('PM_LineEdit', self.findLineEdit, 2), ('PM_ToolButton', self.findOptionsToolButton, 3), ('PM_ToolButton', self.searchToolButton, 4), ('PM_Label', self.warningSign, 5), ('PM_Label', self.phraseNotFoundLabel, 6), ('QSpacerItem', 5, 5, 7)] widgetList2 = [('QLabel', " Number of bases: >=", 1), ('PM_SpinBox', self._nucleotidesSpinBox_1, 2), ('QLabel', " <=", 3), ('PM_SpinBox', self._nucleotidesSpinBox_2, 4), ('QSpacerItem', 5, 5, 5)] widgetList3 = [('QSpacerItem', 5, 5, 1), ('PM_ToolButton', self.searchToolButton, 2), ('PM_Label', self.warningSign, 3), ('PM_Label', self.phraseNotFoundLabel, 4), ('QSpacerItem', 5, 5, 5)] self._widgetRow1 = PM_WidgetRow(self, title='', widgetList=widgetList1, label="", spanWidth=True) self._widgetRow2 = PM_WidgetRow(self, title='', widgetList=widgetList2, label="", spanWidth=True) self._widgetRow3 = PM_WidgetRow(self, title='', widgetList=widgetList3, label="", spanWidth=True)
class PM_DoubleSpinBox( QDoubleSpinBox ): """ The PM_DoubleSpinBox widget provides a QDoubleSpinBox (with an optional label) for a Property Manager group box. Detailed Description ==================== The PM_DoubleSpinBox class provides a spin box widget (with an optional label) for a U{B{Property Manager dialog}<http://www.nanoengineer-1.net/mediawiki/ index.php?title=Property_Manager>} that takes floats. PM_DoubleSpinBox allows the user to choose a value by clicking the up and down buttons or by pressing Up or Down on the keyboard to increase or decrease the value currently displayed. The user can also type the value in manually. The spin box supports float values but can be extended to use different strings with validate(), textFromValue() and valueFromText(). Every time the value changes PM_DoubleSpinBox emits the valueChanged() signal.The current value can be fetched with value() and set with setValue() Note: PM_DoubleSpinBox will round numbers so they can be displayed with the current precision. In a PM_DoubleSpinBox with decimals set to 2, calling setValue(2.555) will cause value() to return 2.56. Clicking the up and down buttons or using the keyboard accelerator's Up and Down arrows will increase or decrease the current value in steps of size singleStep(). If you want to change this behavior you can reimplement the virtual function stepBy(). The minimum and maximum value and the step size can be set using one of the constructors, and can be changed later with setMinimum(), setMaximum() and setSingleStep(). The spin box has a default precision of 2 decimal places but this can be changed using setDecimals(). Most spin boxes are directional, but PM_DoubleSpinBox can also operate as a circular spin box, i.e. if the range is 0.0-99.9 and the current value is 99.9, clicking "up" will give 0 if wrapping() is set to true. Use setWrapping() if you want circular behavior. The displayed value can be prepended and appended with arbitrary strings indicating, for example, currency or the unit of measurement. See setPrefix() and setSuffix(). The text in the spin box is retrieved with text() (which includes any prefix() and suffix()), or with cleanText() (which has no prefix(), no suffix() and no leading or trailing whitespace). It is often desirable to give the user a special (often default) choice in addition to the range of numeric values. See setSpecialValueText() for how to do this with PM_DoubleSpinBox. @see: U{B{QDoubleSpinBox}<http://doc.trolltech.com/4/qdoublespinbox.html>} @cvar defaultValue: The default value of the spin box. @type defaultValue: float @cvar setAsDefault: Determines whether to reset the value of the spin box to I{defaultValue} when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this spin box. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} """ defaultValue = 0.0 setAsDefault = True labelWidget = None def __init__(self, parentWidget, label = '', labelColumn = 0, value = 0.0, setAsDefault = True, minimum = 0.0, maximum = 99.0, singleStep = 1.0, decimals = 1, suffix = '', spanWidth = False ): """ Appends a QDoubleSpinBox (Qt) widget to the bottom of I{parentWidget}, a Property Manager group box. @param parentWidget: The parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left or right of the spin box. If label contains the relative path to an icon (.png) file, that icon image will be used for the label. If spanWidth is True, the label will be displayed on its own row directly above the spin box. To suppress the label, set I{label} to an empty string. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param value: The initial value of the spin box. @type value: float @param setAsDefault: If True, will restore I{value} when the "Restore Defaults" button is clicked. @type setAsDefault: bool @param minimum: The minimum value of the spin box. @type minimum: float @param maximum: The maximum value of the spin box. @type maximum: float @param singleStep: When the user uses the arrows to change the spin box's value the value will be incremented/decremented by the amount of the singleStep. The default value is 1.0. Setting a singleStep value of less than 0 does nothing. @type singleStep: float @param decimals: The precision of the spin box. @type decimals: int @param suffix: The suffix is appended to the end of the displayed value. Typical use is to display a unit of measurement. The default is no suffix. The suffix is not displayed for the minimum value if specialValueText() is set. @type suffix: str @param spanWidth: If True, the spin box and its label will span the width of the group box. The label will appear directly above the spin box and is left justified. @type spanWidth: bool @see: U{B{QDoubleSpinBox}<http://doc.trolltech.com/4/qdoublespinbox.html>} @see: B{InsertNanotube_PropertyManager._chiralityFixup()} for an example use of blockSignals flag """ if 0: # Debugging code print "PropMgrSpinBox.__init__():" print " label = ", label print " labelColumn = ", labelColumn print " value = ", value print " setAsDefault = ", setAsDefault print " minimum = ", minimum print " maximum = ", maximum print " singleStep = ", singleStep print " decimals = ", decimals print " suffix = ", suffix print " spanWidth = ", spanWidth if not parentWidget: return QDoubleSpinBox.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Set QDoubleSpinBox minimum, maximum, singleStep, decimals, then value self.setRange(minimum, maximum) self.setSingleStep(singleStep) self.setDecimals(decimals) self.setValue(value) # This must come after setDecimals(). if setAsDefault: self.setDefaultValue(value) # Add suffix if supplied. if suffix: self.setSuffix(suffix) parentWidget.addPmWidget(self) def restoreDefault( self ): """ Restores the default value. """ if self.setAsDefault: self.setValue(self.defaultValue) def setDefaultValue(self, value): """ Sets the default value of the spin box to I{value}. The current spin box value is unchanged. @param value: The new default value of the spin box. @type value: float @see: L{setValue} """ self.setAsDefault = True self.defaultValue = value def setValue(self, value, setAsDefault = True, blockSignals = False): """ Sets the value of the spin box to I{value}. setValue() will emit valueChanged() if the new value is different from the old one. @param value: The new spin box value. @type value: float ## @param setAsDefault: Determines if the spin box value is reset when ## the "Restore Defaults" button is clicked. If True, ## (the default) I{value} will be used as the reset ## value. ## @type setAsDefault: bool @note: The value will be rounded so it can be displayed with the current setting of decimals. @param blockSignals: Many times, the caller just wants to setValue and don't want to send valueChanged signal. If this flag is set to True, the valueChanged signal won't be emitted. The default value is False. @type blockSignals: bool @see: L{setDefaultValue} @see: QObject.blockSignals(bool block) """ ## if setAsDefault: ### THIS IS A BUG, if the default value of this option remains True. ## # it also looks useless, so i'll zap it. btw that means i could zap the entire method, but i won't yet. ## # I verified nothing calls it with changed version... not enough to prove this zapping is ok... ## # same issue in PM_SpinBox and PropMgrBaseClass. I've discussed this with Mark & Ninad and they agree ## # it should be changed. Ninad, feel free to clean up this method & comment when you see this. ## # [bruce 070814] ## self.setDefaultValue(value) #If blockSignals flag is True, the valueChanged signal won't be emitted #This is done by self.blockSignals method below. -- Ninad 2008-08-13 self.blockSignals(blockSignals) QDoubleSpinBox.setValue(self, value) #Make sure to always 'unblock' signals that might have been temporarily #blocked before calling superclass.setValue. self.blockSignals(False) def connectWithState(self, stateref, set_metainfo = True, debug_metainfo = False): """ Connect self to the state referred to by stateref, so changes to self's value change that state's value and vice versa. By default, also set self's metainfo to correspond to what the stateref provides. @param stateref: a reference to state of type double, which meets the state-reference interface StateRef_API. @type stateref: StateRef_API @param set_metainfo: whether to also set defaultValue, minimum, and/or maximum, if these are provided by the stateref. (This list of metainfo attributes will probably be extended.) @type set_metainfo: bool @param debug_metainfo: whether to print debug messages about the actions taken by set_metainfo, when it's true. @type debug_metainfo: bool """ if set_metainfo: # Do this first, so old min/max don't prevent setting the # correct current value when we connect new state. # # REVIEW: the conventions for expressing a lack of # minimum, maximum, or defaultValue, either on self # or on the stateref, may need revision, so it's not # ambiguous whether the stateref knows the minimum (etc) # should be unset on self or doesn't care what it's set to. # Ideally, some explicit value of stateref.minimum # would correspond to "no minimum" (i.e. a minimum of # negative infinity), etc. # [bruce 070926] set_metainfo_from_stateref( self.setMinimum, stateref, 'minimum', debug_metainfo) set_metainfo_from_stateref( self.setMaximum, stateref, 'maximum', debug_metainfo) set_metainfo_from_stateref( self.setDefaultValue, stateref, 'defaultValue', debug_metainfo) widget_connectWithState( self, stateref, QDoubleSpinBox_ConnectionWithState) return def hide( self ): """ Hides the spin box and its label (if it has one). Call L{show()} to unhide the spin box. @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show( self ): """ Unhides the spin box and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show()
class PM_Dial( QDial ): """ The PM_Dial widget provides a QDial (with an optional label) for a Property Manager group box. """ defaultValue = 0.0 setAsDefault = True labelWidget = None def __init__(self, parentWidget, label = '', suffix = '', labelColumn = 1, value = 0.0, setAsDefault = True, minimum = 0.0, maximum = 360.0, notchSize = 1, notchTarget = 10.0, notchesVisible = True, wrapping = True, spanWidth = True ): """ Appends a QDial (Qt) widget to the bottom of I{parentWidget}, a Property Manager group box. @param parentWidget: The parent group box containing this widget. @type parentWidget: PM_GroupBox @see: U{B{QDial}<http://doc.trolltech.com/4/qdial.html>} """ if not parentWidget: return QDial.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth self.suffix = suffix if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.updateValueLabel() self.labelWidget.setText(self.value_label) # Set QDial minimum, maximum, then value self.setRange(minimum, maximum) self.setValue(value) # This must come after setDecimals(). if setAsDefault: self.setDefaultValue(value) self.notchSize = notchSize self.setNotchTarget(notchTarget) self.setNotchesVisible(notchesVisible) self.setWrapping(wrapping) parentWidget.addPmWidget(self) def restoreDefault( self ): """ Restores the default value. """ if self.setAsDefault: self.setValue(self.defaultValue) def setDefaultValue(self, value): """ Sets the default value of the dial to I{value}. The current dial value is unchanged. @param value: The new default value of the dial. @type value: float @see: L{setValue} """ self.setAsDefault = True self.defaultValue = value def setValue(self, value, setAsDefault = True): """ Sets the value of the dial to I{value}. setValue() will emit valueChanged() if the new value is different from the old one. @param value: The new dial value. @type value: float ## @param setAsDefault: Determines if the dial value is reset when ## the "Restore Defaults" button is clicked. If True, ## (the default) I{value} will be used as the reset ## value. ## @type setAsDefault: bool @note: The value will be rounded so it can be displayed with the current setting of decimals. @see: L{setDefaultValue} """ QDial.setValue(self, value) self.updateValueLabel() def updateValueLabel(self): """ Updates a widget's label with a value. """ if self.label: self.value_label = " " + self.label + " " + \ "%-4d" % int(self.value()) + " " + self.suffix self.labelWidget.setText(self.value_label) def connectWithState(self, stateref, set_metainfo = True, debug_metainfo = False): """ Connect self to the state referred to by stateref, so changes to self's value change that state's value and vice versa. By default, also set self's metainfo to correspond to what the stateref provides. @param stateref: a reference to state of type double, which meets the state-reference interface StateRef_API. @type stateref: StateRef_API @param set_metainfo: whether to also set defaultValue, minimum, and/or maximum, if these are provided by the stateref. (This list of metainfo attributes will probably be extended.) @type set_metainfo: bool @param debug_metainfo: whether to print debug messages about the actions taken by set_metainfo, when it's true. @type debug_metainfo: bool """ if set_metainfo: # Do this first, so old min/max don't prevent setting the # correct current value when we connect new state. # # REVIEW: the conventions for expressing a lack of # minimum, maximum, or defaultValue, either on self # or on the stateref, may need revision, so it's not # ambiguous whether the stateref knows the minimum (etc) # should be unset on self or doesn't care what it's set to. # Ideally, some explicit value of stateref.minimum # would correspond to "no minimum" (i.e. a minimum of # negative infinity), etc. # [bruce 070926] set_metainfo_from_stateref( self.setMinimum, stateref, 'minimum', debug_metainfo) set_metainfo_from_stateref( self.setMaximum, stateref, 'maximum', debug_metainfo) set_metainfo_from_stateref( self.setDefaultValue, stateref, 'defaultValue', debug_metainfo) ###widget_connectWithState( self, stateref, ### QDoubleSpinBox_ConnectionWithState) print "PM_Dial.connectWithState: not yet implemented" #bruce 080811 added this line return def hide( self ): """ Hides the dial and its label (if it has one). Call L{show()} to unhide the dial. @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show( self ): """ Unhides the dial and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show()
class ImageControlDialog(QDialog): def __init__(self, parent, rc, imgman): """An ImageControlDialog is initialized with a parent widget, a RenderControl object, and an ImageManager object""" QDialog.__init__(self, parent) image = rc.image self.setWindowTitle("%s: Colour Controls" % image.name) self.setWindowIcon(pixmaps.colours.icon()) self.setModal(False) self.image = image self._rc = rc self._imgman = imgman self._currier = PersistentCurrier() # init internal state self._prev_range = self._display_range = None, None self._hist = None self._geometry = None # create layouts lo0 = QVBoxLayout(self) # lo0.setContentsMargins(0,0,0,0) # histogram plot whide = self.makeButton("Hide", self.hide, width=128) whide.setShortcut(Qt.Key_F9) lo0.addWidget(Separator(self, "Histogram and ITF", extra_widgets=[whide])) lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) self._histplot = QwtPlot(self) self._histplot.setAutoDelete(False) lo1.addWidget(self._histplot, 1) lo2 = QHBoxLayout() lo2.setContentsMargins(0, 0, 0, 0) lo2.setSpacing(2) lo0.addLayout(lo2) lo0.addLayout(lo1) self._wautozoom = QCheckBox("autozoom", self) self._wautozoom.setChecked(True) self._wautozoom.setToolTip("""<P>If checked, then the histrogram plot will zoom in automatically when you narrow the current intensity range.</P>""") self._wlogy = QCheckBox("log Y", self) self._wlogy.setChecked(True) self._ylogscale = True self._wlogy.setToolTip( """<P>If checked, a log-scale Y axis is used for the histogram plot instead of a linear one.""") QObject.connect(self._wlogy, SIGNAL("toggled(bool)"), self._setHistLogScale) self._whistunzoom = self.makeButton("", self._unzoomHistogram, icon=pixmaps.full_range.icon()) self._whistzoomout = self.makeButton("-", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(.1))) self._whistzoomin = self.makeButton("+", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(10))) self._whistzoomin.setToolTip("""<P>Click to zoom into the histogram plot by one step. This does not change the current intensity range.</P>""") self._whistzoomout.setToolTip("""<P>Click to zoom out of the histogram plot by one step. This does not change the current intensity range.</P>""") self._whistunzoom.setToolTip("""<P>Click to reset the histogram plot back to its full extent. This does not change the current intensity range.</P>""") self._whistzoom = QwtWheel(self) self._whistzoom.setOrientation(Qt.Horizontal) self._whistzoom.setMaximumWidth(80) self._whistzoom.setRange(10, 0) self._whistzoom.setStep(0.1) self._whistzoom.setTickCnt(30) self._whistzoom.setTracking(False) QObject.connect(self._whistzoom, SIGNAL("valueChanged(double)"), self._zoomHistogramFinalize) QObject.connect(self._whistzoom, SIGNAL("sliderMoved(double)"), self._zoomHistogramPreview) self._whistzoom.setToolTip("""<P>Use this wheel control to zoom in/out of the histogram plot. This does not change the current intensity range. Note that the zoom wheel should also respond to your mouse wheel, if you have one.</P>""") # This works around a stupid bug in QwtSliders -- when using the mousewheel, only sliderMoved() signals are emitted, # with no final valueChanged(). If we want to do a fast preview of something on sliderMoved(), and a "slow" final # step on valueChanged(), we're in trouble. So we start a timer on sliderMoved(), and if the timer expires without # anything else happening, do a valueChanged(). # Here we use a timer to call zoomHistogramFinalize() w/o an argument. self._whistzoom_timer = QTimer(self) self._whistzoom_timer.setSingleShot(True) self._whistzoom_timer.setInterval(500) QObject.connect(self._whistzoom_timer, SIGNAL("timeout()"), self._zoomHistogramFinalize) # set same size for all buttons and controls width = 24 for w in self._whistunzoom, self._whistzoomin, self._whistzoomout: w.setMinimumSize(width, width) w.setMaximumSize(width, width) self._whistzoom.setMinimumSize(80, width) self._wlab_histpos_text = "(hover here for help)" self._wlab_histpos = QLabel(self._wlab_histpos_text, self) self._wlab_histpos.setToolTip(""" <P>The plot shows a histogram of either the full image or its selected subset (as per the "Data subset" section below).</P> <P>The current intensity range is indicated by the grey box in the plot.</P> <P>Use the left mouse button to change the low intensity limit, and the right button (on Macs, use Ctrl-click) to change the high limit.</P> <P>Use Shift with the left mouse button to zoom into an area of the histogram, or else use the "zoom wheel" control or the plus/minus toolbuttons above the histogram to zoom in or out. To zoom back out to the full extent of the histogram, click on the rightmost button above the histogram.</P> """) lo2.addWidget(self._wlab_histpos, 1) lo2.addWidget(self._wautozoom) lo2.addWidget(self._wlogy, 0) lo2.addWidget(self._whistzoomin, 0) lo2.addWidget(self._whistzoom, 0) lo2.addWidget(self._whistzoomout, 0) lo2.addWidget(self._whistunzoom, 0) self._zooming_histogram = False sliced_axes = rc.slicedAxes() dprint(1, "sliced axes are", sliced_axes) self._stokes_axis = None # subset indication lo0.addWidget(Separator(self, "Data subset")) # sliced axis selectors self._wslicers = [] if sliced_axes: lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1) lo1.addWidget(QLabel("Current slice: ", self)) for i, (iextra, name, labels) in enumerate(sliced_axes): lo1.addWidget(QLabel("%s:" % name, self)) if name == "STOKES": self._stokes_axis = iextra # add controls wslicer = QComboBox(self) self._wslicers.append(wslicer) wslicer.addItems(labels) wslicer.setToolTip("""<P>Selects current slice along the %s axis.</P>""" % name) wslicer.setCurrentIndex(self._rc.currentSlice()[iextra]) QObject.connect(wslicer, SIGNAL("activated(int)"), self._currier.curry(self._rc.changeSlice, iextra)) lo2 = QVBoxLayout() lo1.addLayout(lo2) lo2.setContentsMargins(0, 0, 0, 0) lo2.setSpacing(0) wminus = QToolButton(self) wminus.setArrowType(Qt.UpArrow) QObject.connect(wminus, SIGNAL("clicked()"), self._currier.curry(self._rc.incrementSlice, iextra, 1)) if i == 0: wminus.setShortcut(Qt.SHIFT + Qt.Key_F7) elif i == 1: wminus.setShortcut(Qt.SHIFT + Qt.Key_F8) wplus = QToolButton(self) wplus.setArrowType(Qt.DownArrow) QObject.connect(wplus, SIGNAL("clicked()"), self._currier.curry(self._rc.incrementSlice, iextra, -1)) if i == 0: wplus.setShortcut(Qt.Key_F7) elif i == 1: wplus.setShortcut(Qt.Key_F8) wminus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) wplus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sz = QSize(12, 8) wminus.setMinimumSize(sz) wplus.setMinimumSize(sz) wminus.resize(sz) wplus.resize(sz) lo2.addWidget(wminus) lo2.addWidget(wplus) lo1.addWidget(wslicer) lo1.addSpacing(5) lo1.addStretch(1) # subset indicator lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1) self._wlab_subset = QLabel("Subset: xxx", self) self._wlab_subset.setToolTip("""<P>This indicates the current data subset to which the histogram and the stats given here apply. Use the "Reset to" control on the right to change the current subset and recompute the histogram and stats.</P>""") lo1.addWidget(self._wlab_subset, 1) self._wreset_full = self.makeButton("\u2192 full", self._rc.setFullSubset) lo1.addWidget(self._wreset_full) if sliced_axes: # if self._stokes_axis is not None and len(sliced_axes)>1: # self._wreset_stokes = self.makeButton(u"\u21920Stokes",self._rc.setFullSubset) self._wreset_slice = self.makeButton("\u2192 slice", self._rc.setSliceSubset) lo1.addWidget(self._wreset_slice) else: self._wreset_slice = None # min/max controls lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) self._wlab_stats = QLabel(self) lo1.addWidget(self._wlab_stats, 0) self._wmore_stats = self.makeButton("more...", self._showMeanStd) self._wlab_stats.setMinimumHeight(self._wmore_stats.height()) lo1.addWidget(self._wmore_stats, 0) lo1.addStretch(1) # intensity controls lo0.addWidget(Separator(self, "Intensity mapping")) lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1, 0) self._range_validator = FloatValidator(self) self._wrange = QLineEdit(self), QLineEdit(self) self._wrange[0].setToolTip("""<P>This is the low end of the intensity range.</P>""") self._wrange[1].setToolTip("""<P>This is the high end of the intensity range.</P>""") for w in self._wrange: w.setValidator(self._range_validator) QObject.connect(w, SIGNAL("editingFinished()"), self._changeDisplayRange) lo1.addWidget(QLabel("low:", self), 0) lo1.addWidget(self._wrange[0], 1) self._wrangeleft0 = self.makeButton("\u21920", self._setZeroLeftLimit, width=32) self._wrangeleft0.setToolTip("""<P>Click this to set the low end of the intensity range to 0.</P>""") lo1.addWidget(self._wrangeleft0, 0) lo1.addSpacing(8) lo1.addWidget(QLabel("high:", self), 0) lo1.addWidget(self._wrange[1], 1) lo1.addSpacing(8) self._wrange_full = self.makeButton(None, self._setHistDisplayRange, icon=pixmaps.intensity_graph.icon()) lo1.addWidget(self._wrange_full) self._wrange_full.setToolTip( """<P>Click this to reset the intensity range to the current extent of the histogram plot.</P>""") # add menu for display range range_menu = QMenu(self) wrange_menu = QToolButton(self) wrange_menu.setText("Reset to") wrange_menu.setToolTip("""<P>Use this to reset the intensity range to various pre-defined settings.</P>""") lo1.addWidget(wrange_menu) self._qa_range_full = range_menu.addAction(pixmaps.full_range.icon(), "Full subset", self._rc.resetSubsetDisplayRange) self._qa_range_hist = range_menu.addAction(pixmaps.intensity_graph.icon(), "Current histogram limits", self._setHistDisplayRange) for percent in (99.99, 99.9, 99.5, 99, 98, 95): range_menu.addAction("%g%%" % percent, self._currier.curry(self._changeDisplayRangeToPercent, percent)) wrange_menu.setMenu(range_menu) wrange_menu.setPopupMode(QToolButton.InstantPopup) lo1 = QGridLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) self._wimap = QComboBox(self) lo1.addWidget(QLabel("Intensity policy:", self), 0, 0) lo1.addWidget(self._wimap, 1, 0) self._wimap.addItems(rc.getIntensityMapNames()) QObject.connect(self._wimap, SIGNAL("currentIndexChanged(int)"), self._rc.setIntensityMapNumber) self._wimap.setToolTip("""<P>Use this to change the type of the intensity transfer function (ITF).</P>""") # log cycles control lo1.setColumnStretch(1, 1) self._wlogcycles_label = QLabel("Log cycles: ", self) lo1.addWidget(self._wlogcycles_label, 0, 1) # self._wlogcycles = QwtWheel(self) # self._wlogcycles.setTotalAngle(360) self._wlogcycles = QwtSlider(self) self._wlogcycles.setToolTip( """<P>Use this to change the log-base for the logarithmic intensity transfer function (ITF).</P>""") # This works around a stupid bug in QwtSliders -- see comments on histogram zoom wheel above self._wlogcycles_timer = QTimer(self) self._wlogcycles_timer.setSingleShot(True) self._wlogcycles_timer.setInterval(500) QObject.connect(self._wlogcycles_timer, SIGNAL("timeout()"), self._setIntensityLogCycles) lo1.addWidget(self._wlogcycles, 1, 1) self._wlogcycles.setRange(1., 10) self._wlogcycles.setStep(0.1) self._wlogcycles.setTracking(False) QObject.connect(self._wlogcycles, SIGNAL("valueChanged(double)"), self._setIntensityLogCycles) QObject.connect(self._wlogcycles, SIGNAL("sliderMoved(double)"), self._previewIntensityLogCycles) self._updating_imap = False # lock intensity map lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) # lo1.addWidget(QLabel("Lock range accross",self)) wlock = QCheckBox("Lock display range", self) wlock.setToolTip("""<P>If checked, then the intensity range will be locked. The ranges of all locked images change simultaneously.</P>""") lo1.addWidget(wlock) wlockall = QToolButton(self) wlockall.setIcon(pixmaps.locked.icon()) wlockall.setText("Lock all to this") wlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) wlockall.setAutoRaise(True) wlockall.setToolTip("""<P>Click this to lock together the intensity ranges of all images.</P>""") lo1.addWidget(wlockall) wunlockall = QToolButton(self) wunlockall.setIcon(pixmaps.unlocked.icon()) wunlockall.setText("Unlock all") wunlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) wunlockall.setAutoRaise(True) wunlockall.setToolTip("""<P>Click this to unlock the intensity ranges of all images.</P>""") lo1.addWidget(wunlockall) wlock.setChecked(self._rc.isDisplayRangeLocked()) QObject.connect(wlock, SIGNAL("clicked(bool)"), self._rc.lockDisplayRange) QObject.connect(wlockall, SIGNAL("clicked()"), self._currier.curry(self._imgman.lockAllDisplayRanges, self._rc)) QObject.connect(wunlockall, SIGNAL("clicked()"), self._imgman.unlockAllDisplayRanges) QObject.connect(self._rc, SIGNAL("displayRangeLocked"), wlock.setChecked) # self._wlock_imap_axis = [ QCheckBox(name,self) for iaxis,name,labels in sliced_axes ] # for iw,w in enumerate(self._wlock_imap_axis): # QObject.connect(w,SIGNAL("toggled(bool)"),self._currier.curry(self._rc.lockDisplayRangeForAxis,iw)) # lo1.addWidget(w,0) lo1.addStretch(1) # lo0.addWidget(Separator(self,"Colourmap")) # color bar self._colorbar = QwtPlot(self) lo0.addWidget(self._colorbar) self._colorbar.setAutoDelete(False) self._colorbar.setMinimumHeight(32) self._colorbar.enableAxis(QwtPlot.yLeft, False) self._colorbar.enableAxis(QwtPlot.xBottom, False) # color plot self._colorplot = QwtPlot(self) lo0.addWidget(self._colorplot) self._colorplot.setAutoDelete(False) self._colorplot.setMinimumHeight(64) self._colorplot.enableAxis(QwtPlot.yLeft, False) self._colorplot.enableAxis(QwtPlot.xBottom, False) # self._colorplot.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Preferred) self._colorbar.hide() self._colorplot.hide() # color controls lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 1) lo1.addWidget(QLabel("Colourmap:", self)) # colormap list ### NB: use setIconSize() and icons in QComboBox!!! self._wcolmaps = QComboBox(self) self._wcolmaps.setIconSize(QSize(128, 16)) self._wcolmaps.setToolTip("""<P>Use this to select a different colourmap.</P>""") for cmap in self._rc.getColormapList(): self._wcolmaps.addItem(QIcon(cmap.makeQPixmap(128, 16)), cmap.name) lo1.addWidget(self._wcolmaps) QObject.connect(self._wcolmaps, SIGNAL("activated(int)"), self._rc.setColorMapNumber) # add widgetstack for colormap controls self._wcolmap_control_stack = QStackedWidget(self) self._wcolmap_control_blank = QWidget(self._wcolmap_control_stack) self._wcolmap_control_stack.addWidget(self._wcolmap_control_blank) lo0.addWidget(self._wcolmap_control_stack) self._colmap_controls = [] # add controls to stack for index, cmap in enumerate(self._rc.getColormapList()): if isinstance(cmap, Colormaps.ColormapWithControls): controls = cmap.makeControlWidgets(self._wcolmap_control_stack) self._wcolmap_control_stack.addWidget(controls) QObject.connect(cmap, SIGNAL("colormapChanged"), self._currier.curry(self._previewColormapParameters, index, cmap)) QObject.connect(cmap, SIGNAL("colormapPreviewed"), self._currier.curry(self._previewColormapParameters, index, cmap)) self._colmap_controls.append(controls) else: self._colmap_controls.append(self._wcolmap_control_blank) # connect updates from renderControl and image self.image.connect(SIGNAL("slice"), self._updateImageSlice) QObject.connect(self._rc, SIGNAL("intensityMapChanged"), self._updateIntensityMap) QObject.connect(self._rc, SIGNAL("colorMapChanged"), self._updateColorMap) QObject.connect(self._rc, SIGNAL("dataSubsetChanged"), self._updateDataSubset) QObject.connect(self._rc, SIGNAL("displayRangeChanged"), self._updateDisplayRange) # update widgets self._setupHistogramPlot() self._updateDataSubset(*self._rc.currentSubset()) self._updateColorMap(image.colorMap()) self._updateIntensityMap(rc.currentIntensityMap(), rc.currentIntensityMapNumber()) self._updateDisplayRange(*self._rc.displayRange()) def makeButton(self, label, callback=None, width=None, icon=None): btn = QToolButton(self) # btn.setAutoRaise(True) label and btn.setText(label) icon and btn.setIcon(icon) # btn = QPushButton(label,self) # btn.setFlat(True) if width: btn.setMinimumWidth(width) btn.setMaximumWidth(width) if icon: btn.setIcon(icon) if callback: QObject.connect(btn, SIGNAL("clicked()"), callback) return btn # def closeEvent (self,ev): # ev.ignore() # self.hide() def hide(self): self._geometry = self.geometry() QDialog.hide(self) def show(self): dprint(4, "show entrypoint") if self._geometry: dprint(4, "setting geometry") self.setGeometry(self._geometry) if self._hist is None: busy = BusyIndicator() dprint(4, "updating histogram") self._updateHistogram() dprint(4, "updating stats") self._updateStats(self._subset, self._subset_range) busy = None dprint(4, "calling QDialog.show") QDialog.show(self) # number of bins used to compute intensity transfer function NumItfBins = 1000 # number of bins used for displaying histograms NumHistBins = 500 # number of bins used for high-res histograms NumHistBinsHi = 10000 # colorbar height, as fraction of plot area ColorBarHeight = 0.1 class HistLimitPicker(QwtPlotPicker): """Auguments QwtPlotPicker with functions for selecting hist min/max values""" def __init__(self, plot, label, color="green", mode=QwtPicker.PointSelection, rubber_band=QwtPicker.VLineRubberBand, tracker_mode=QwtPicker.ActiveOnly, track=None): QwtPlotPicker.__init__(self, QwtPlot.xBottom, QwtPlot.yRight, mode, rubber_band, tracker_mode, plot.canvas()) self.plot = plot self.label = label self.track = track self.color = QColor(color) self.setRubberBandPen(QPen(self.color)) def trackerText(self, pos): x, y = self.plot.invTransform(QwtPlot.xBottom, pos.x()), self.plot.invTransform(QwtPlot.yLeft, pos.y()) if self.track: text = self.track(x, y) if text is not None: return text if self.label: text = QwtText(self.label % dict(x=x, y=y)) text.setColor(self.color) return text return QwtText() def widgetLeaveEvent(self, ev): if self.track: self.track(None, None) QwtPlotPicker.widgetLeaveEvent(self, ev) class ColorBarPlotItem(QwtPlotItem): def __init__(self, y0, y1, *args): QwtPlotItem.__init__(self, *args) self._y0 = y1 self._dy = y1 - y0 def setIntensityMap(self, imap): self.imap = imap def setColorMap(self, cmap): self.cmap = cmap def draw(self, painter, xmap, ymap, rect): """Implements QwtPlotItem.draw(), to render the colorbar on the given painter.""" xp1, xp2, xdp, xs1, xs2, xds = xinfo = xmap.p1(), xmap.p2(), xmap.pDist(), xmap.s1(), xmap.s2(), xmap.sDist() yp1, yp2, ydp, ys1, ys2, yds = yinfo = ymap.p1(), ymap.p2(), ymap.pDist(), ymap.s1(), ymap.s2(), ymap.sDist() # xp: coordinates of pixels xp1...xp2 in data units xp = xs1 + (xds / xdp) * (0.5 + numpy.arange(int(xdp))) # convert y0 and y1 into pixel coordinates y0 = yp1 - (self._y0 - ys1) * (ydp / yds) dy = self._dy * (ydp / yds) # remap into an Nx1 image qimg = self.cmap.colorize(self.imap.remap(xp.reshape((len(xp), 1)))) # plot image painter.drawImage(QRect(xp1, y0, xdp, dy), qimg) class HistogramLineMarker(object): """Helper class implementing a line marker for a histogram plot""" def __init__(self, plot, color="black", linestyle=Qt.DotLine, align=Qt.AlignBottom | Qt.AlignRight, z=90, label="", zlabel=None, linewidth=1, spacing=2, yaxis=QwtPlot.yRight): self.line = TiggerPlotCurve() self.color = color = color if isinstance(color, QColor) else QColor(color) self.line.setPen(QPen(color, linewidth, linestyle)) self.marker = TiggerPlotMarker() self.marker.setLabelAlignment(align) try: self.marker.setSpacing(spacing) except AttributeError: pass self.setText(label) self.line.setZ(z) self.marker.setZ(zlabel if zlabel is not None else z) # set axes -- using yRight, since that is the "markup" z-axis self.line.setAxis(QwtPlot.xBottom, yaxis) self.marker.setAxis(QwtPlot.xBottom, yaxis) # attach to plot self.line.attach(plot) self.marker.attach(plot) def show(self): self.line.show() self.marker.show() def hide(self): self.line.hide() self.marker.hide() def setText(self, text): label = QwtText(text) label.setColor(self.color) self.marker.setLabel(label) def _setupHistogramPlot(self): self._histplot.setCanvasBackground(QColor("lightgray")) self._histplot.setAxisFont(QwtPlot.yLeft, QApplication.font()) self._histplot.setAxisFont(QwtPlot.xBottom, QApplication.font()) # add histogram curves self._histcurve1 = TiggerPlotCurve() self._histcurve2 = TiggerPlotCurve() self._histcurve1.setStyle(QwtPlotCurve.Steps) self._histcurve2.setStyle(QwtPlotCurve.Steps) self._histcurve1.setPen(QPen(Qt.NoPen)) self._histcurve1.setBrush(QBrush(QColor("slategrey"))) pen = QPen(QColor("red")) pen.setWidth(1) self._histcurve2.setPen(pen) self._histcurve1.setZ(0) self._histcurve2.setZ(100) # self._histcurve1.attach(self._histplot) self._histcurve2.attach(self._histplot) # add maxbin and half-max curves self._line_0 = self.HistogramLineMarker(self._histplot, color="grey50", linestyle=Qt.SolidLine, align=Qt.AlignTop | Qt.AlignLeft, z=90) self._line_mean = self.HistogramLineMarker(self._histplot, color="black", linestyle=Qt.SolidLine, align=Qt.AlignBottom | Qt.AlignRight, z=91, label="mean", zlabel=151) self._line_std = self.HistogramLineMarker(self._histplot, color="black", linestyle=Qt.SolidLine, align=Qt.AlignTop | Qt.AlignRight, z=91, label="std", zlabel=151) sym = QwtSymbol() sym.setStyle(QwtSymbol.VLine) sym.setSize(8) self._line_std.line.setSymbol(sym) self._line_maxbin = self.HistogramLineMarker(self._histplot, color="green", linestyle=Qt.DotLine, align=Qt.AlignTop | Qt.AlignRight, z=92, label="max bin", zlabel=150) self._line_halfmax = self.HistogramLineMarker(self._histplot, color="green", linestyle=Qt.DotLine, align=Qt.AlignBottom | Qt.AlignRight, z=90, label="half-max", yaxis=QwtPlot.yLeft) # add current range self._rangebox = TiggerPlotCurve() self._rangebox.setStyle(QwtPlotCurve.Steps) self._rangebox.setYAxis(QwtPlot.yRight) self._rangebox.setPen(QPen(Qt.NoPen)) self._rangebox.setBrush(QBrush(QColor("darkgray"))) self._rangebox.setZ(50) self._rangebox.attach(self._histplot) self._rangebox2 = TiggerPlotCurve() self._rangebox2.setStyle(QwtPlotCurve.Sticks) self._rangebox2.setYAxis(QwtPlot.yRight) self._rangebox2.setZ(60) # self._rangebox2.attach(self._histplot) # add intensity transfer function self._itfcurve = TiggerPlotCurve() self._itfcurve.setStyle(QwtPlotCurve.Lines) self._itfcurve.setPen(QPen(QColor("blue"))) self._itfcurve.setYAxis(QwtPlot.yRight) self._itfcurve.setZ(120) self._itfcurve.attach(self._histplot) self._itfmarker = TiggerPlotMarker() label = QwtText("ITF") label.setColor(QColor("blue")) self._itfmarker.setLabel(label) try: self._itfmarker.setSpacing(0) except AttributeError: pass self._itfmarker.setLabelAlignment(Qt.AlignTop | Qt.AlignRight) self._itfmarker.setZ(120) self._itfmarker.attach(self._histplot) # add colorbar self._cb_item = self.ColorBarPlotItem(1, 1 + self.ColorBarHeight) self._cb_item.setYAxis(QwtPlot.yRight) self._cb_item.attach(self._histplot) # add pickers self._hist_minpicker = self.HistLimitPicker(self._histplot, "low: %(x).4g") self._hist_minpicker.setMousePattern(QwtEventPattern.MouseSelect1, Qt.LeftButton) QObject.connect(self._hist_minpicker, SIGNAL("selected(const QwtDoublePoint &)"), self._selectLowLimit) self._hist_maxpicker = self.HistLimitPicker(self._histplot, "high: %(x).4g") self._hist_maxpicker.setMousePattern(QwtEventPattern.MouseSelect1, Qt.RightButton) QObject.connect(self._hist_maxpicker, SIGNAL("selected(const QwtDoublePoint &)"), self._selectHighLimit) self._hist_maxpicker1 = self.HistLimitPicker(self._histplot, "high: %(x).4g") self._hist_maxpicker1.setMousePattern(QwtEventPattern.MouseSelect1, Qt.LeftButton, Qt.CTRL) QObject.connect(self._hist_maxpicker1, SIGNAL("selected(const QwtDoublePoint &)"), self._selectHighLimit) self._hist_zoompicker = self.HistLimitPicker(self._histplot, label="zoom", tracker_mode=QwtPicker.AlwaysOn, track=self._trackHistCoordinates, color="black", mode=QwtPicker.RectSelection, rubber_band=QwtPicker.RectRubberBand) self._hist_zoompicker.setMousePattern(QwtEventPattern.MouseSelect1, Qt.LeftButton, Qt.SHIFT) QObject.connect(self._hist_zoompicker, SIGNAL("selected(const QwtDoubleRect &)"), self._zoomHistogramIntoRect) def _trackHistCoordinates(self, x, y): self._wlab_histpos.setText((DataValueFormat + " %d") % (x, y) if x is not None else self._wlab_histpos_text) return QwtText() def _updateITF(self): """Updates current ITF array.""" # do nothing if no histogram -- means we're not visible if self._hist is not None: xdata = self._itf_bins ydata = self.image.intensityMap().remap(xdata) self._rangebox.setData(self._rc.displayRange(), [1, 1]) self._rangebox2.setData(self._rc.displayRange(), [1, 1]) self._itfcurve.setData(xdata, ydata) self._itfmarker.setValue(xdata[0], 1) def _updateHistogram(self, hmin=None, hmax=None): """Recomputes histogram. If no arguments, computes full histogram for data subset. If hmin/hmax is specified, computes zoomed-in histogram.""" busy = BusyIndicator() self._prev_range = self._display_range dmin, dmax = self._subset_range hmin0, hmax0 = dmin, dmax if hmin0 >= hmax0: hmax0 = hmin0 + 1 subset, mask = self.image.optimalRavel(self._subset) # compute full-subset hi-res histogram, if we don't have one (for percentile stats) if self._hist_hires is None: dprint(1, "computing histogram for full subset range", hmin0, hmax0) self._hist_hires = measurements.histogram(subset, hmin0, hmax0, self.NumHistBinsHi, labels=mask, index=None if mask is None else False) self._hist_bins_hires = hmin0 + (hmax0 - hmin0) * (numpy.arange(self.NumHistBinsHi) + 0.5) / float( self.NumHistBinsHi) self._hist_binsize_hires = (hmax0 - hmin0) / self.NumHistBins # if hist limits not specified, then compute lo-res histogram based on the hi-res one if hmin is None: hmin, hmax = hmin0, hmax0 # downsample to low-res histogram self._hist = self._hist_hires.reshape((self.NumHistBins, self.NumHistBinsHi / self.NumHistBins)).sum(1) else: # zoomed-in low-res histogram # bracket limits at subset range hmin, hmax = max(hmin, dmin), min(hmax, dmax) if hmin >= hmax: hmax = hmin + 1 dprint(1, "computing histogram for", self._subset.shape, self._subset.dtype, hmin, hmax) self._hist = measurements.histogram(subset, hmin, hmax, self.NumHistBins, labels=mask, index=None if mask is None else False) dprint(1, "histogram computed") # compute bins self._itf_bins = hmin + (hmax - hmin) * (numpy.arange(self.NumItfBins)) / (float(self.NumItfBins) - 1) self._hist_bins = hmin + (hmax - hmin) * (numpy.arange(self.NumHistBins) + 0.5) / float(self.NumHistBins) # histogram range and position of peak self._hist_range = hmin, hmax self._hist_min, self._hist_max, self._hist_imin, self._hist_imax = measurements.extrema(self._hist) self._hist_peak = self._hist_bins[self._hist_imax] # set controls accordingly if dmin >= dmax: dmax = dmin + 1 zoom = math.log10((dmax - dmin) / (hmax - hmin)) self._whistzoom.setValue(zoom) self._whistunzoom.setEnabled(zoom > 0) self._whistzoomout.setEnabled(zoom > 0) # reset scales self._histplot.setAxisScale(QwtPlot.xBottom, hmin, hmax) self._histplot.setAxisScale(QwtPlot.yRight, 0, 1 + self.ColorBarHeight) # update curves # call _setHistLogScale() (with current setting) to update axis scales and set data self._setHistLogScale(self._ylogscale, replot=False) # set plot lines self._line_0.line.setData([0, 0], [0, 1]) self._line_0.marker.setValue(0, 0) self._line_maxbin.line.setData([self._hist_peak, self._hist_peak], [0, 1]) self._line_maxbin.marker.setValue(self._hist_peak, 0) self._line_maxbin.setText(("max bin:" + DataValueFormat) % self._hist_peak) # set half-max line self._line_halfmax.line.setData(self._hist_range, [self._hist_max / 2, self._hist_max / 2]) self._line_halfmax.marker.setValue(hmin, self._hist_max / 2) # update ITF self._updateITF() def _updateStats(self, subset, minmax): """Recomputes subset statistics.""" if subset.size <= (2048 * 2048): self._showMeanStd(busy=False) else: self._wlab_stats.setText( ("min: %s max: %s np: %d" % (DataValueFormat, DataValueFormat, self._subset.size)) % minmax) self._wmore_stats.show() def _updateDataSubset(self, subset, minmax, desc, subset_type): """Called when the displayed data subset is changed. Updates the histogram.""" self._subset = subset self._subset_range = minmax self._wlab_subset.setText("Subset: %s" % desc) self._hist = self._hist_hires = None self._wreset_full.setVisible(subset_type is not RenderControl.SUBSET_FULL) self._wreset_slice and self._wreset_slice.setVisible(subset_type is not RenderControl.SUBSET_SLICE) # hide the mean/std markers, they will only be shown when _showMeanStd() is called self._line_mean.hide() self._line_std.hide() # if we're visibile, recompute histograms and stats if self.isVisible(): # if subset is sufficiently small, compute extended stats on-the-fly. Else show the "more" button to compute them later self._updateHistogram() self._updateStats(subset, minmax) self._histplot.replot() def _showMeanStd(self, busy=True): if busy: busy = BusyIndicator() dmin, dmax = self._subset_range subset, mask = self.image.optimalRavel(self._subset) dprint(5, "computing mean") mean = measurements.mean(subset, labels=mask, index=None if mask is None else False) dprint(5, "computing std") std = measurements.standard_deviation(subset, labels=mask, index=None if mask is None else False) dprint(5, "done") text = " ".join([("%s: " + DataValueFormat) % (name, value) for name, value in ("min", dmin), ("max", dmax), ("mean", mean), ("std", std)] + ["np: %d" % self._subset.size]) self._wlab_stats.setText(text) self._wmore_stats.hide() # update markers ypos = 0.3 self._line_mean.line.setData([mean, mean], [0, 1]) self._line_mean.marker.setValue(mean, ypos) self._line_mean.setText(("\u03BC=" + DataValueFormat) % mean) self._line_mean.show() self._line_std.line.setData([mean - std, mean + std], [ypos, ypos]) self._line_std.marker.setValue(mean, ypos) self._line_std.setText(("\u03C3=" + DataValueFormat) % std) self._line_std.show() self._histplot.replot() def _setIntensityLogCyclesLabel(self, value): self._wlogcycles_label.setText("Log cycles: %4.1f" % value) def _previewIntensityLogCycles(self, value): self._setIntensityLogCycles(value, notify_image=False, write_config=False) self._wlogcycles_timer.start(500) def _setIntensityLogCycles(self, value=None, notify_image=True, write_config=True): if value is None: value = self._wlogcycles.value() # stop timer if being called to finalize the change in value if notify_image: self._wlogcycles_timer.stop() if not self._updating_imap: self._setIntensityLogCyclesLabel(value) self._rc.setIntensityMapLogCycles(value, notify_image=notify_image, write_config=write_config) self._updateITF() self._histplot.replot() def _updateDisplayRange(self, dmin, dmax): self._rangebox.setData([dmin, dmax], [.9, .9]) self._wrange[0].setText(DataValueFormat % dmin) self._wrange[1].setText(DataValueFormat % dmax) self._wrangeleft0.setEnabled(dmin != 0) self._display_range = dmin, dmax # if auto-zoom is on, zoom the histogram # try to be a little clever about this. Zoom only if (a) both limits have changed (so that adjusting one end of the range # does not cause endless rezooms), or (b) display range is < 1/10 of the histogram range if self._wautozoom.isChecked() and self._hist is not None: if (dmax - dmin) / (self._hist_range[1] - self._hist_range[0]) < .1 or ( dmin != self._prev_range[0] and dmax != self._prev_range[1]): margin = (dmax - dmin) / 8 self._updateHistogram(dmin - margin, dmax + margin) self._updateITF() self._histplot.replot() def _updateIntensityMap(self, imap, index): self._updating_imap = True try: self._cb_item.setIntensityMap(imap) self._updateITF() self._histplot.replot() self._wimap.setCurrentIndex(index) if isinstance(imap, Colormaps.LogIntensityMap): self._wlogcycles.setValue(imap.log_cycles) self._setIntensityLogCyclesLabel(imap.log_cycles) self._wlogcycles.show() self._wlogcycles_label.show() else: self._wlogcycles.hide() self._wlogcycles_label.hide() finally: self._updating_imap = False def _updateColorMap(self, cmap): self._cb_item.setColorMap(cmap) self._histplot.replot() try: index = self._rc.getColormapList().index(cmap) except: return self._setCurrentColormapNumber(index, cmap) def _previewColormapParameters(self, index, cmap): """Called to preview a new colormap parameter value""" self._histplot.replot() self._wcolmaps.setItemIcon(index, QIcon(cmap.makeQPixmap(128, 16))) def _setCurrentColormapNumber(self, index, cmap): self._wcolmaps.setCurrentIndex(index) # show controls for colormap self._wcolmap_control_stack.setCurrentWidget(self._colmap_controls[index]) def _changeDisplayRange(self): """Gets display range from widgets and updates the image with it.""" try: newrange = [float(str(w.text())) for w in self._wrange] except ValueError: return self._rc.setDisplayRange(*newrange) def _setHistDisplayRange(self): self._rc.setDisplayRange(*self._hist_range) def _updateImageSlice(self, slice): for i, (iextra, name, labels) in enumerate(self._rc.slicedAxes()): self._wslicers[i].setCurrentIndex(slice[iextra]) def _changeDisplayRangeToPercent(self, percent): busy = BusyIndicator() if self._hist is None: self._updateHistogram() self._updateStats(self._subset, self._subset_range) # delta: we need the [delta,100-delta] interval of the total distribution delta = self._subset.size * ((100. - percent) / 200.) # get F(x): cumulative sum cumsum = numpy.zeros(len(self._hist_hires) + 1, dtype=int) cumsum[1:] = numpy.cumsum(self._hist_hires) bins = numpy.zeros(len(self._hist_hires) + 1, dtype=float) bins[0] = self._subset_range[0] bins[1:] = self._hist_bins_hires + self._hist_binsize_hires / 2 # use interpolation to find value interval corresponding to [delta,100-delta] of the distribution dprint(2, self._subset.size, delta, self._subset.size - delta) dprint(2, cumsum, self._hist_bins_hires) # if first bin is already > delta, then set colour range to first bin x0, x1 = numpy.interp([delta, self._subset.size - delta], cumsum, bins) # and change the display range (this will also cause a histplot.replot() via _updateDisplayRange above) self._rc.setDisplayRange(x0, x1) def _setZeroLeftLimit(self): self._rc.setDisplayRange(0., self._rc.displayRange()[1]) def _selectLowLimit(self, pos): self._rc.setDisplayRange(pos.x(), self._rc.displayRange()[1]) def _selectHighLimit(self, pos): self._rc.setDisplayRange(self._rc.displayRange()[0], pos.x()) def _unzoomHistogram(self): self._updateHistogram() self._histplot.replot() def _zoomHistogramByFactor(self, factor): """Changes histogram limits by specified factor""" # get max distance of plot limit from peak dprint(1, "zooming histogram by", factor) halfdist = (self._hist_range[1] - self._hist_range[0]) / (factor * 2) self._updateHistogram(self._hist_peak - halfdist, self._hist_peak + halfdist) self._histplot.replot() def _zoomHistogramIntoRect(self, rect): hmin, hmax = rect.bottomLeft().x(), rect.bottomRight().x() if hmax > hmin: self._updateHistogram(rect.bottomLeft().x(), rect.bottomRight().x()) self._histplot.replot() def _zoomHistogramPreview(self, value): dprint(2, "wheel moved to", value) self._zoomHistogramFinalize(value, preview=True) self._whistzoom_timer.start() def _zoomHistogramFinalize(self, value=None, preview=False): if self._zooming_histogram: return self._zooming_histogram = True try: if value is not None: dmin, dmax = self._subset_range dist = max(dmax - self._hist_peak, self._hist_peak - dmin) / 10 ** value self._preview_hist_range = max(self._hist_peak - dist, dmin), min(self._hist_peak + dist, dmax) if preview: self._histplot.setAxisScale(QwtPlot.xBottom, *self._preview_hist_range) else: dprint(2, "wheel finalized at", value) self._whistzoom_timer.stop() self._updateHistogram(*self._preview_hist_range) self._histplot.replot() finally: self._zooming_histogram = False def _setHistLogScale(self, logscale, replot=True): self._ylogscale = logscale if logscale: self._histplot.setAxisScaleEngine(QwtPlot.yLeft, QwtLog10ScaleEngine()) ymax = max(1, self._hist_max) self._histplot.setAxisScale(QwtPlot.yLeft, 1, 10 ** (math.log10(ymax) * (1 + self.ColorBarHeight))) y = self._hist.copy() y[y == 0] = 1 self._histcurve1.setData(self._hist_bins, y) self._histcurve2.setData(self._hist_bins, y) else: self._histplot.setAxisScaleEngine(QwtPlot.yLeft, QwtLinearScaleEngine()) self._histplot.setAxisScale(QwtPlot.yLeft, 0, self._hist_max * (1 + self.ColorBarHeight)) self._histcurve1.setData(self._hist_bins, self._hist) self._histcurve2.setData(self._hist_bins, self._hist) if replot: self._histplot.replot()
class PM_FileChooser(QWidget): """ The PM_FileChooser widget provides a file chooser widget for a Property Manager group box. The PM_FileChooser widget is a composite widget made from 3 other Qt widgets: - a QLabel - a QLineEdit and - a QToolButton (with a "..." text label). IMAGE(http://www.nanoengineer-1.net/mediawiki/images/e/e2/PM_FileChooser1.jpg) The user can type the path name of a file into the line edit widget or select a file using Qt's file (chooser) dialog by clicking the "..." button. The path name of the selected file will be inserted into the line edit widget. The parent must make the following signal-slot connection to be notified when the user has selected a new file via the file chooser dialog: self.connect(pmFileChooser.lineEdit, SIGNAL("editingFinished()"), self.mySlotMethod) @cvar defaultText: The default text (path) of the line edit widget. @type defaultText: string @cvar setAsDefault: Determines whether to reset the value of the lineedit to I{defaultText} when the user clicks the "Restore Defaults" button. @type setAsDefault: boolean @cvar labelWidget: The Qt label widget of this PM widget. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} @cvar lineEdit: The Qt line edit widget for this PM widget. @type lineEdit: U{B{QLineEdit}<http://doc.trolltech.com/4/qlineedit.html>} @cvar browseButton: The Qt tool button widget for this PM widget. @type browseButton: U{B{QToolButton}<http://doc.trolltech.com/4/qtoolbutton.html>} """ defaultText = "" setAsDefault = True hidden = False lineEdit = None browseButton = None def __init__(self, parentWidget, label='', labelColumn=0, text='', setAsDefault=True, spanWidth=False, caption="Choose file", directory='', filter="All Files (*.*)"): """ Appends a file chooser widget to <parentWidget>, a property manager group box. @param parentWidget: the parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left or right of the file chooser lineedit (and "Browse" button). If spanWidth is True, the label will be displayed on its own row directly above the lineedit (and button). To suppress the label, set I{label} to an empty string. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param text: initial value of LineEdit widget. @type text: string @param setAsDefault: if True, will restore <val> when the "Restore Defaults" button is clicked. @type setAsDefault: boolean @param spanWidth: if True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: boolean @param caption: The caption used as the title of the file chooser dialog. "Choose file" is the default. @type caption: string @param directory: The directory that the file chooser dialog should open in when the "..." button is clicked. If blank or if directory does not exist, the current working directory is used. @type directory: string @param filter: The file type filters to use for the file chooser dialog. @type filter: string (a semicolon-separated list of file types) @see: U{B{QLineEdit}<http://doc.trolltech.com/4/qlineedit.html>} """ QWidget.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.text = text self.setAsDefault = setAsDefault self.spanWidth = spanWidth self.caption = caption self.directory = directory self.filter = filter if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) else: # Create a dummy attribute for PM_GroupBox to see. This might have # needed to be fixed in PM_GroupBox, but it was done here to try to # avoid causing errors in other PM widgets. -Derrick 20080916 self.labelWidget = None self.lineEdit = QLineEdit() self.browseButton = QToolButton() # Create vertical box layout. self.hBoxLayout = QHBoxLayout(self) self.hBoxLayout.setMargin(0) self.hBoxLayout.setSpacing(2) self.hBoxLayout.insertWidget(-1, self.lineEdit) self.hBoxLayout.insertWidget(-1, self.browseButton) # Set (QLineEdit) text self.setText(text) # Set browse button text and make signal-slot connection. self.browseButton.setText("...") self.connect(self.browseButton, SIGNAL("clicked()"), self.openFileChooserDialog) # Set default value self.defaultText = text self.setAsDefault = setAsDefault parentWidget.addPmWidget(self) return def setText(self, text): """ Set the line edit text. @param text: The text. @type text: string """ self.lineEdit.setText(text) self.text = text return def openFileChooserDialog(self): """ Prompts the user to choose a file from disk and inserts the full path into the lineEdit widget. """ _dir = getDefaultWorkingDirectory() if self.directory: if os.path.isdir(self.directory): _dir = self.directory fname = QFileDialog.getOpenFileName(self, self.caption, _dir, self.filter) if fname: self.setText(fname) self.lineEdit.emit(SIGNAL("editingFinished()")) return def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setText(self.defaultText) return def hide(self): """ Hides the lineedit and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() return def show(self): """ Unhides the lineedit and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() return # End of PM_FileChooser ############################
class PM_TextEdit(QTextEdit): """ The PM_TextEdit widget provides a QTextEdit with a QLabel for a Property Manager groupbox. @cvar defaultText: The default text of the textedit. @type defaultText: str @cvar setAsDefault: Determines whether to reset the value of the textedit to I{defaultText} when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this textedit. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} """ defaultText = "" setAsDefault = True labelWidget = None def __init__(self, parentWidget, label='', labelColumn=0, spanWidth=False, addToParent=True, permit_enter_keystroke=True): """ Appends a QTextEdit (Qt) widget to the bottom of I{parentWidget}, a Property Manager group box. The QTextEdit is empty (has no text) by default. Use insertHtml() to insert HTML text into the TextEdit. @param parentWidget: the parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left of (or above) the spin box. To suppress the label, set I{label} to an empty string. @type label: str @param spanWidth: If True, the spin box and its label will span the width of the group box. The label will appear directly above the spin box and is left justified. @type spanWidth: bool @param addToParent: If True (the default), self will be added to parentWidget by passing it to parentWidget.addPmWidget. If False, self will not be added to parentWidget. Typically, when this is False, the caller will add self to parent in some other way. @type addToParent: bool @param permit_enter_keystroke: If set to True, this PM_textEdit can have multiple lines. Otherwise, it will block the 'Enter' keypress within the text editor. Note that caller needs to make sure that linewrapping option is appropriately set, (in addition to this flag) so as to permit/ not permit multiple lines in the text edit. @see: U{B{QTextEdit}<http://doc.trolltech.com/4/qtextedit.html>} """ if 0: # Debugging code print "QTextEdit.__init__():" print " label =", label print " labelColumn =", labelColumn print " spanWidth =", spanWidth QTextEdit.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.spanWidth = spanWidth self._permit_enter_keystroke = permit_enter_keystroke if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) self._setHeight() # Default height is 4 lines high. ## from PM.PM_MessageGroupBox import PM_MessageGroupBox ## if isinstance(parentWidget, PM_MessageGroupBox): ## # Add to parentWidget's vBoxLayout if <parentWidget> is a MessageGroupBox. ## parentWidget.vBoxLayout.addWidget(self) ## # We should be calling the PM's getMessageTextEditPalette() method, ## # but that will take some extra work which I will do soon. Mark 2007-06-21 ## self.setPalette(getPalette( None, ## QPalette.Base, ## pmMessageBoxColor)) ## self.setReadOnly(True) ## #@self.labelWidget = None # Never has one. Mark 2007-05-31 ## parentWidget._widgetList.append(self) ## parentWidget._rowCount += 1 ## else: ## parentWidget.addPmWidget(self) # bruce 071103 refactored the above into the new addToParent option and # code added to PM_MessageGroupBox.__init__ after it calls this method. if addToParent: parentWidget.addPmWidget(self) return def keyPressEvent(self, event): """ Overrides the superclass method. """ #If user hits 'Enter' key (return key), don't do anything. if event.key() == Qt.Key_Return: #Urmi 20080724: emit a signal to indicate end of processing self.emit(SIGNAL("editingFinished()")) #there is no obvious way to allow only a single line in a #QTextEdit (we can use some methods that restrict the columnt width #, line wrapping etc but this is untested when the line contains # huge umber of characters. Anyway, the following always works #and fixes bug 2713 if not self._permit_enter_keystroke: return QTextEdit.keyPressEvent(self, event) def insertHtml(self, text, setAsDefault=False, minLines=4, maxLines=6, replace=True): """ Insert <text> (HTML) into the Prop Mgr's message groupbox. <minLines> is the minimum number of lines to display, even if the text takes up fewer lines. The default number of lines is 4. <maxLines> is the maximum number of lines to diplay before adding a vertical scrollbar. <replace> should be set to False if you do not wish to replace the current text. It will append <text> instead. """ if setAsDefault: self.defaultText = text self.setAsDefault = True if replace: # Replace the text by selecting effectively all text and # insert the new text 'over' it (overwrite). :jbirac: 20070629 cursor = self.textCursor() cursor.setPosition(0, QTextCursor.MoveAnchor) cursor.setPosition(len(self.toPlainText()), QTextCursor.KeepAnchor) self.setTextCursor(cursor) QTextEdit.insertHtml(self, text) if replace: # Restore the previous cursor position/selection and mode. cursor.setPosition(len(self.toPlainText()), QTextCursor.MoveAnchor) self.setTextCursor(cursor) #Don't call _setHeight after insertHtml, it increases the height of the #text widget and thus gives an undesirable visual effect. #This was seen in DnaSequenceEditor. Also tried using 'setSizePolicy' like #done in PM_MessagegroupBox but that didn't work. ##self._setHeight(minLines, maxLines) def _setHeight(self, minLines=4, maxLines=8): """ Set the height just high enough to display the current text without a vertical scrollbar. <minLines> is the minimum number of lines to display, even if the text takes up fewer lines. <maxLines> is the maximum number of lines to diplay before adding a vertical scrollbar. """ if minLines == 0: fitToHeight = True else: fitToHeight = False # Current width of PM_TextEdit widget. current_width = self.sizeHint().width() # Probably including Html tags. text = self.toPlainText() text_width = self.fontMetrics().width(text) num_lines = text_width / current_width + 1 # + 1 may create an extra (empty) line on rare occasions. if fitToHeight: num_lines = min(num_lines, maxLines) else: num_lines = max(num_lines, minLines) #margin = self.fontMetrics().leading() * 2 # leading() returned 0. Mark 2007-05-28 margin = 10 # Based on trial and error. Maybe it is pm?Spacing=5 (*2)? Mark 2007-05-28 new_height = num_lines * self.fontMetrics().lineSpacing() + margin if 0: # Debugging code for me. Mark 2007-05-24 print "--------------------------------" print "Widget name = ", self.objectName() print "minLines =", minLines print "maxLines = ", maxLines print "num_lines = ", num_lines print "New height = ", new_height print "text = ", text print "Text width = ", text_width print "current_width (of PM_TextEdit)=", current_width # Reset height of PM_TextEdit. self.setMinimumSize(QSize(PM_MINIMUM_WIDTH * 0.5, new_height)) self.setMaximumHeight(new_height) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.insertHtml(self.defaultText, setAsDefault=True, replace=True) def hide(self): """ Hides the tool button and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the tool button and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show()
class PM_LineEdit( QLineEdit ): """ The PM_LineEdit widget provides a QLineEdit with a QLabel for a Property Manager group box. @cvar defaultText: The default text of the lineedit. @type defaultText: str @cvar setAsDefault: Determines whether to reset the value of the lineedit to I{defaultText} when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this lineedit. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} """ defaultText = "" setAsDefault = True hidden = False labelWidget = None def __init__(self, parentWidget, label = '', labelColumn = 0, text = '', setAsDefault = True, spanWidth = False ): """ Appends a QLineEdit widget to <parentWidget>, a property manager group box. @param parentWidget: the parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left or right of the checkbox. If spanWidth is True, the label will be displayed on its own row directly above the checkbox. To suppress the label, set I{label} to an empty string. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param text: initial value of LineEdit widget. @type text: str @param setAsDefault: if True, will restore <val> when the "Restore Defaults" button is clicked. @type setAsDefault: bool @param spanWidth: if True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: bool @see: U{B{QLineEdit}<http://doc.trolltech.com/4/qlineedit.html>} """ if 0: # Debugging code print "PM_LineEdit.__init__():" print " label = ", label print " labelColumn = ", labelColumn print " text = ", text print " setAsDefault = ", setAsDefault print " spanWidth = ", spanWidth QLineEdit.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Set QLineEdit text self.setText(text) # Set default value self.defaultText = text self.setAsDefault = setAsDefault parentWidget.addPmWidget(self) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setText(self.defaultText) def hide(self): """ Hides the lineedit and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the lineedit and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() # End of PM_LineEdit ############################
class Ui_ProteinSequenceEditor(PM_DockWidget): """ The Ui_DnaSequenceEditor class defines UI elements for the Sequence Editor object. The sequence editor is usually visible while in DNA edit mode. It is a DockWidget that is doced at the bottom of the MainWindow """ _title = "Sequence Editor" _groupBoxCount = 0 _lastGroupBox = None def __init__(self, win): """ Constructor for the Ui_DnaSequenceEditor @param win: The parentWidget (MainWindow) for the sequence editor """ self.win = win # Should parentWidget for a docwidget always be win? #Not necessary but most likely it will be the case. parentWidget = win _superclass.__init__(self, parentWidget, title = self._title) #A flag used to restore the state of the Reports dock widget #(which can be accessed through View > Reports) see self.show() and #self.closeEvent() for more details. self._reportsDockWidget_closed_in_show_method = False self.setFixedHeight(90) def show(self): """ Shows the sequence editor. While doing this, it also closes the reports dock widget (if visible) the state of the reports dockwidget will be restored when the sequence editor is closed. @see:self.closeEvent() """ self._reportsDockWidget_closed_in_show_method = False if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self.win.reportsDockWidget.isVisible(): self.win.reportsDockWidget.close() self._reportsDockWidget_closed_in_show_method = True _superclass.show(self) def closeEvent(self, event): """ Overrides close event. Makes sure that the visible state of the reports widgetis restored when the sequence editor is closed. @see: self.show() """ _superclass.closeEvent(self, event) if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self._reportsDockWidget_closed_in_show_method: self.win.viewReportsAction.setChecked(True) self._reportsDockWidget_closed_in_show_method = False def _loadWidgets(self): """ Overrides PM.PM_DockWidget._loadWidgets. Loads the widget in this dockwidget. """ self._loadMenuWidgets() self._loadTextEditWidget() def _loadMenuWidgets(self): """ Load the various menu widgets (e.g. Open, save sequence options, Find and replace widgets etc. """ #Note: Find and replace widgets might be moved to their own class. self.loadSequenceButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Open.png") self.saveSequenceButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Save_Strand_Sequence.png") self.loadSequenceButton.setAutoRaise(True) self.saveSequenceButton.setAutoRaise(True) #Find and replace widgets -- self.findLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.findLineEdit.setMaximumWidth(60) self.replaceLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.replaceLineEdit.setMaximumWidth(60) self.findOptionsToolButton = PM_ToolButton(self) self.findOptionsToolButton.setMaximumWidth(12) self.findOptionsToolButton.setAutoRaise(True) self.findOptionsToolButton.setPopupMode(QToolButton.MenuButtonPopup) self._setFindOptionsToolButtonMenu() self.findNextToolButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Find_Next.png") self.findNextToolButton.setAutoRaise(True) self.findPreviousToolButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Find_Previous.png") self.findPreviousToolButton.setAutoRaise(True) self.replacePushButton = PM_PushButton(self, text = "Replace") self.warningSign = QLabel(self) self.warningSign.setPixmap( getpixmap('ui/actions/Properties Manager/Warning.png')) self.warningSign.hide() self.phraseNotFoundLabel = QLabel(self) self.phraseNotFoundLabel.setText("Sequence Not Found") self.phraseNotFoundLabel.hide() #Widgets to include in the widget row. widgetList = [('PM_ToolButton', self.loadSequenceButton, 0), ('PM_ToolButton', self.saveSequenceButton, 1), ('QLabel', " Find:", 4), ('PM_LineEdit', self.findLineEdit, 5), ('PM_ToolButton', self.findOptionsToolButton, 6), ('PM_ToolButton', self.findPreviousToolButton, 7), ('PM_ToolButton', self.findNextToolButton, 8), ('QLabel', " Replace:", 9), ('PM_TextEdit', self.replaceLineEdit, 10), ('PM_PushButton', self.replacePushButton, 11), ('PM_Label', self.warningSign, 12), ('PM_Label', self.phraseNotFoundLabel, 13), ('QSpacerItem', 5, 5, 14) ] widgetRow = PM_WidgetRow(self, title = '', widgetList = widgetList, label = "", spanWidth = True ) def _loadTextEditWidget(self): """ Load the SequenceTexteditWidgets. """ self.aaRulerTextEdit = \ PM_TextEdit( self, label = "", spanWidth = False, permit_enter_keystroke = False) palette = getPalette(None, QPalette.Base, pmGrpBoxColor) self.aaRulerTextEdit.setPalette(palette) self.aaRulerTextEdit.setWordWrapMode( QTextOption.WrapAnywhere ) self.aaRulerTextEdit.setFixedHeight(20) self.aaRulerTextEdit.setReadOnly(True) self.sequenceTextEdit = \ PM_TextEdit( self, label = " Sequence: ", spanWidth = False, permit_enter_keystroke = False) self.sequenceTextEdit.setCursorWidth(2) self.sequenceTextEdit.setWordWrapMode( QTextOption.WrapAnywhere ) self.sequenceTextEdit.setFixedHeight(20) self.secStrucTextEdit = \ PM_TextEdit( self, label = " Secondary structure: ", spanWidth = False, permit_enter_keystroke = False) palette = getPalette(None, QPalette.Base, sequenceEditStrandMateBaseColor) self.secStrucTextEdit.setPalette(palette) self.secStrucTextEdit.setWordWrapMode( QTextOption.WrapAnywhere ) self.secStrucTextEdit.setFixedHeight(20) self.secStrucTextEdit.setReadOnly(True) #Important to make sure that the horizontal and vertical scrollbars #for these text edits are never displayed. self.sequenceTextEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.sequenceTextEdit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.secStrucTextEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.secStrucTextEdit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.aaRulerTextEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.aaRulerTextEdit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) def _getFindLineEditStyleSheet(self): """ Return the style sheet for the findLineEdit. This sets the following properties only: - background-color This style is set whenever the searchStrig can't be found (sets a light red color background to the lineedit when this happens) @return: The line edit style sheet. @rtype: str """ styleSheet = \ "QLineEdit {\ background-color: rgb(255, 102, 102)\ }" #Not used: # background-color: rgb(217, 255, 216)\ return styleSheet def _setFindOptionsToolButtonMenu(self): """ Sets the menu for the findOptionstoolbutton that appears a small menu button next to the findLineEdit. """ self.findOptionsMenu = QMenu(self.findOptionsToolButton) self.caseSensitiveFindAction = QAction(self.findOptionsToolButton) self.caseSensitiveFindAction.setText('Match Case') self.caseSensitiveFindAction.setCheckable(True) self.caseSensitiveFindAction.setChecked(False) self.findOptionsMenu.addAction(self.caseSensitiveFindAction) self.findOptionsMenu.addSeparator() self.findOptionsToolButton.setMenu(self.findOptionsMenu) def _addToolTipText(self): """ What's Tool Tip text for widgets in this Property Manager. """ pass def _addWhatsThisText(self): """ What's This text for widgets in this Property Manager. """ pass
class PM_TextEdit( QTextEdit ): """ The PM_TextEdit widget provides a QTextEdit with a QLabel for a Property Manager groupbox. @cvar defaultText: The default text of the textedit. @type defaultText: str @cvar setAsDefault: Determines whether to reset the value of the textedit to I{defaultText} when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this textedit. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} """ defaultText = "" setAsDefault = True labelWidget = None def __init__(self, parentWidget, label = '', labelColumn = 0, spanWidth = False, addToParent = True, permit_enter_keystroke = True ): """ Appends a QTextEdit (Qt) widget to the bottom of I{parentWidget}, a Property Manager group box. The QTextEdit is empty (has no text) by default. Use insertHtml() to insert HTML text into the TextEdit. @param parentWidget: the parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left of (or above) the spin box. To suppress the label, set I{label} to an empty string. @type label: str @param spanWidth: If True, the spin box and its label will span the width of the group box. The label will appear directly above the spin box and is left justified. @type spanWidth: bool @param addToParent: If True (the default), self will be added to parentWidget by passing it to parentWidget.addPmWidget. If False, self will not be added to parentWidget. Typically, when this is False, the caller will add self to parent in some other way. @type addToParent: bool @param permit_enter_keystroke: If set to True, this PM_textEdit can have multiple lines. Otherwise, it will block the 'Enter' keypress within the text editor. Note that caller needs to make sure that linewrapping option is a propriately set, (in addition to this flag) so as to permit/ not permit multiple lines in the text edit. @see: U{B{QTextEdit}<http://doc.trolltech.com/4/qtextedit.html>} """ if 0: # Debugging code print "QTextEdit.__init__():" print " label =", label print " labelColumn =", labelColumn print " spanWidth =", spanWidth QTextEdit.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.spanWidth = spanWidth self._permit_enter_keystroke = permit_enter_keystroke if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) self._setHeight() # Default height is 4 lines high. ## from PM.PM_MessageGroupBox import PM_MessageGroupBox ## if isinstance(parentWidget, PM_MessageGroupBox): ## # Add to parentWidget's vBoxLayout if <parentWidget> is a MessageGroupBox. ## parentWidget.vBoxLayout.addWidget(self) ## # We should be calling the PM's getMessageTextEditPalette() method, ## # but that will take some extra work which I will do soon. Mark 2007-06-21 ## self.setPalette(getPalette( None, ## QPalette.Base, ## pmMessageBoxColor)) ## self.setReadOnly(True) ## #@self.labelWidget = None # Never has one. Mark 2007-05-31 ## parentWidget._widgetList.append(self) ## parentWidget._rowCount += 1 ## else: ## parentWidget.addPmWidget(self) # bruce 071103 refactored the above into the new addToParent option and # code added to PM_MessageGroupBox.__init__ after it calls this method. if addToParent: parentWidget.addPmWidget(self) return def keyPressEvent(self, event): """ Overrides the superclass method. """ #If user hits 'Enter' key (return key), don't do anything. if event.key() == Qt.Key_Return: #there is no obvious way to allow only a single line in a #QTextEdit (we can use some methods that restrict the columnt width #, line wrapping etc but this is untested when the line contains # huge umber of characters. Anyway, the following always works #and fixes bug 2713 if not self._permit_enter_keystroke: return QTextEdit.keyPressEvent(self, event) def insertHtml(self, text, setAsDefault = False, minLines = 4, maxLines = 6, replace = True ): """ Insert <text> (HTML) into the Prop Mgr's message groupbox. <minLines> is the minimum number of lines to display, even if the text takes up fewer lines. The default number of lines is 4. <maxLines> is the maximum number of lines to diplay before adding a vertical scrollbar. <replace> should be set to False if you do not wish to replace the current text. It will append <text> instead. """ if setAsDefault: self.defaultText = text self.setAsDefault = True if replace: # Replace the text by selecting effectively all text and # insert the new text 'over' it (overwrite). :jbirac: 20070629 cursor = self.textCursor() cursor.setPosition( 0, QTextCursor.MoveAnchor ) cursor.setPosition( len(self.toPlainText()), QTextCursor.KeepAnchor ) self.setTextCursor( cursor ) QTextEdit.insertHtml(self, text) if replace: # Restore the previous cursor position/selection and mode. cursor.setPosition( len(self.toPlainText()), QTextCursor.MoveAnchor ) self.setTextCursor( cursor ) #Don't call _setHeight after insertHtml, it increases the height of the #text widget and thus gives an undesirable visual effect. #This was seen in DnaSequenceEditor. Also tried using 'setSizePolicy' like #done in PM_MessagegroupBox but that didn't work. ##self._setHeight(minLines, maxLines) def _setHeight( self, minLines = 4, maxLines = 8 ): """ Set the height just high enough to display the current text without a vertical scrollbar. <minLines> is the minimum number of lines to display, even if the text takes up fewer lines. <maxLines> is the maximum number of lines to diplay before adding a vertical scrollbar. """ if minLines == 0: fitToHeight=True else: fitToHeight=False # Current width of PM_TextEdit widget. current_width = self.sizeHint().width() # Probably including Html tags. text = self.toPlainText() text_width = self.fontMetrics().width(text) num_lines = text_width/current_width + 1 # + 1 may create an extra (empty) line on rare occasions. if fitToHeight: num_lines = min(num_lines, maxLines) else: num_lines = max(num_lines, minLines) #margin = self.fontMetrics().leading() * 2 # leading() returned 0. Mark 2007-05-28 margin = 10 # Based on trial and error. Maybe it is pm?Spacing=5 (*2)? Mark 2007-05-28 new_height = num_lines * self.fontMetrics().lineSpacing() + margin if 0: # Debugging code for me. Mark 2007-05-24 print "--------------------------------" print "Widget name = ", self.objectName() print "minLines =" , minLines print "maxLines = ", maxLines print "num_lines = ", num_lines print "New height = ", new_height print "text = ", text print "Text width = ", text_width print "current_width (of PM_TextEdit)=", current_width # Reset height of PM_TextEdit. self.setMinimumSize(QSize(PM_MINIMUM_WIDTH * 0.5, new_height)) self.setMaximumHeight(new_height) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.insertHtml(self.defaultText, setAsDefault = True, replace = True) def hide(self): """ Hides the tool button and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the tool button and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show()
class PM_FileChooser( QWidget ): """ The PM_FileChooser widget provides a file chooser widget for a Property Manager group box. The PM_FileChooser widget is a composite widget made from 3 other Qt widgets: - a QLabel - a QLineEdit and - a QToolButton (with a "..." text label). IMAGE(http://www.nanoengineer-1.net/mediawiki/images/e/e2/PM_FileChooser1.jpg) The user can type the path name of a file into the line edit widget or select a file using Qt's file (chooser) dialog by clicking the "..." button. The path name of the selected file will be inserted into the line edit widget. The parent must make the following signal-slot connection to be notified when the user has selected a new file via the file chooser dialog: self.connect(pmFileChooser.lineEdit, SIGNAL("editingFinished()"), self.mySlotMethod) @cvar defaultText: The default text (path) of the line edit widget. @type defaultText: string @cvar setAsDefault: Determines whether to reset the value of the lineedit to I{defaultText} when the user clicks the "Restore Defaults" button. @type setAsDefault: boolean @cvar labelWidget: The Qt label widget of this PM widget. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} @cvar lineEdit: The Qt line edit widget for this PM widget. @type lineEdit: U{B{QLineEdit}<http://doc.trolltech.com/4/qlineedit.html>} @cvar browseButton: The Qt tool button widget for this PM widget. @type browseButton: U{B{QToolButton}<http://doc.trolltech.com/4/qtoolbutton.html>} """ defaultText = "" setAsDefault = True hidden = False lineEdit = None browseButton = None def __init__(self, parentWidget, label = '', labelColumn = 0, text = '', setAsDefault = True, spanWidth = False, caption = "Choose file", directory = '', filter = "All Files (*.*)" ): """ Appends a file chooser widget to <parentWidget>, a property manager group box. @param parentWidget: the parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left or right of the file chooser lineedit (and "Browse" button). If spanWidth is True, the label will be displayed on its own row directly above the lineedit (and button). To suppress the label, set I{label} to an empty string. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param text: initial value of LineEdit widget. @type text: string @param setAsDefault: if True, will restore <val> when the "Restore Defaults" button is clicked. @type setAsDefault: boolean @param spanWidth: if True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: boolean @param caption: The caption used as the title of the file chooser dialog. "Choose file" is the default. @type caption: string @param directory: The directory that the file chooser dialog should open in when the "..." button is clicked. If blank or if directory does not exist, the current working directory is used. @type directory: string @param filter: The file type filters to use for the file chooser dialog. @type filter: string (a semicolon-separated list of file types) @see: U{B{QLineEdit}<http://doc.trolltech.com/4/qlineedit.html>} """ QWidget.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.text = text self.setAsDefault = setAsDefault self.spanWidth = spanWidth self.caption = caption self.directory = directory self.filter = filter if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) self.lineEdit = QLineEdit() self.browseButton = QToolButton() # Create vertical box layout. self.hBoxLayout = QHBoxLayout(self) self.hBoxLayout.setMargin(0) self.hBoxLayout.setSpacing(2) self.hBoxLayout.insertWidget(-1, self.lineEdit) self.hBoxLayout.insertWidget(-1, self.browseButton) # Set (QLineEdit) text self.setText(text) # Set browse button text and make signal-slot connection. self.browseButton.setText("...") self.connect(self.browseButton, SIGNAL("clicked()"), self.openFileChooserDialog) # Set default value self.defaultText = text self.setAsDefault = setAsDefault parentWidget.addPmWidget(self) return def setText(self, text): """ Set the line edit text. @param text: The text. @type text: string """ self.lineEdit.setText(text) self.text = text return def openFileChooserDialog(self): """ Prompts the user to choose a file from disk and inserts the full path into the lineEdit widget. """ _dir = getDefaultWorkingDirectory() if self.directory: if os.path.isdir(self.directory): _dir = self.directory fname = QFileDialog.getOpenFileName(self, self.caption, _dir, self.filter) if fname: self.setText(fname) self.lineEdit.emit(SIGNAL("editingFinished()")) return def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setText(self.defaultText) return def hide(self): """ Hides the lineedit and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() return def show(self): """ Unhides the lineedit and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() return # End of PM_FileChooser ############################
class PM_ColorChooser(QWidget): """ The PM_ColorChooser widget provides a color chooser widget for a Property Manager group box. The PM_ColorChooser widget is a composite widget made from 3 other Qt widgets: - a QLabel - a QFrame and - a QToolButton (with a "..." text label). IMAGE(http://www.nanoengineer-1.net/mediawiki/images/e/e2/PM_ColorChooser1.jpg) The user can color using Qt's color (chooser) dialog by clicking the "..." button. The selected color will be used as the color of the QFrame widget. The parent must make the following signal-slot connection to be notified when the user has selected a new color via the color chooser dialog: self.connect(pmColorChooser.colorFrame, SIGNAL("editingFinished()"), self.mySlotMethod) @cvar setAsDefault: Determines whether to reset the value of the color to I{defaultColor} when the user clicks the "Restore Defaults" button. @type setAsDefault: boolean @cvar labelWidget: The Qt label widget of this PM widget. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} @cvar colorFrame: The Qt frame widget for this PM widget. @type colorFrame: U{B{QFrame}<http://doc.trolltech.com/4/qframe.html>} @cvar chooseButton: The Qt tool button widget for this PM widget. @type chooseButton: U{B{QToolButton}<http://doc.trolltech.com/4/qtoolbutton.html>} """ defaultColor = None setAsDefault = True hidden = False chooseButton = None customColorCount = 0 standardColorList = [white, black] def __init__( self, parentWidget, label='Color:', labelColumn=0, color=white, setAsDefault=True, spanWidth=False, ): """ Appends a color chooser widget to <parentWidget>, a property manager group box. @param parentWidget: the parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left or right of the color frame (and "Browse" button). If spanWidth is True, the label will be displayed on its own row directly above the lineedit (and button). To suppress the label, set I{label} to an empty string. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param color: initial color. White is the default. @type color: tuple of 3 floats (r, g, b) @param setAsDefault: if True, will restore L{color} when the "Restore Defaults" button is clicked. @type setAsDefault: boolean @param spanWidth: if True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: boolean @see: U{B{QColorDialog}<http://doc.trolltech.com/4/qcolordialog.html>} """ QWidget.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.color = color self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Create the color frame (color swath) and "..." button. self.colorFrame = QFrame() self.colorFrame.setFrameShape(QFrame.Box) self.colorFrame.setFrameShadow(QFrame.Plain) # Set browse button text and make signal-slot connection. self.chooseButton = QToolButton() self.chooseButton.setText("...") self.connect(self.chooseButton, SIGNAL("clicked()"), self.openColorChooserDialog) # Add a horizontal spacer to keep the colorFrame and "..." squeezed # together, even when the PM width changes. self.hSpacer = QSpacerItem(10, 10, QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) # Create vertical box layout. self.hBoxLayout = QHBoxLayout(self) self.hBoxLayout.setMargin(0) self.hBoxLayout.setSpacing(2) self.hBoxLayout.insertWidget(-1, self.colorFrame) self.hBoxLayout.insertWidget(-1, self.chooseButton) # Set this to False to make the colorFrame an expandable rectangle. COLORFRAME_IS_SQUARE = True if COLORFRAME_IS_SQUARE: squareSize = 20 self.colorFrame.setMinimumSize(QSize(squareSize, squareSize)) self.colorFrame.setMaximumSize(QSize(squareSize, squareSize)) self.hBoxLayout.addItem(self.hSpacer) self.setColor(color, default=setAsDefault) parentWidget.addPmWidget(self) return def setColor(self, color, default=False): """ Set the color. @param color: The color. @type color: tuple of 3 floats (r, g, b) @param default: If True, make I{color} the default color. Default is False. @type default: boolean """ if default: self.defaultColor = color self.setAsDefault = default self.color = color self._updateColorFrame() return def getColor(self): """ Return the current color. @return: The current r, g, b color. @rtype: Tuple of 3 floats (r, g, b) """ return self.color def getQColor(self): """ Return the current QColor. @return: The current color. @rtype: QColor """ return RGBf_to_QColor(self.color) def _updateColorFrame(self): """ Updates the color frame with the current color. """ colorframe = self.colorFrame try: qcolor = self.getQColor() palette = QPalette( ) # QPalette(qcolor) would have window color set from qcolor, but that doesn't help us here qcolorrole = QPalette.Window ## http://doc.trolltech.com/4.2/qpalette.html#ColorRole-enum says: ## QPalette.Window 10 A general background color. palette.setColor(QPalette.Active, qcolorrole, qcolor) # used when window is in fg and has focus palette.setColor( QPalette.Inactive, qcolorrole, qcolor) # used when window is in bg or does not have focus palette.setColor(QPalette.Disabled, qcolorrole, qcolor) # used when widget is disabled colorframe.setPalette(palette) colorframe.setAutoFillBackground(True) except: print "data for following exception: ", print "colorframe %r has palette %r" % (colorframe, colorframe.palette()) pass def openColorChooserDialog(self): """ Prompts the user to choose a color and then updates colorFrame with the selected color. """ qcolor = RGBf_to_QColor(self.color) if not self.color in self.standardColorList: QColorDialog.setCustomColor(self.customColorCount, qcolor.rgb()) self.customColorCount += 1 c = QColorDialog.getColor(qcolor, self) if c.isValid(): self.setColor(QColor_to_RGBf(c)) self.colorFrame.emit(SIGNAL("editingFinished()")) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setColor(self.defaultColor) return def hide(self): """ Hides the lineedit and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() return def show(self): """ Unhides the lineedit and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() return
class PM_GroupBox( QGroupBox ): """ The PM_GroupBox widget provides a group box container with a collapse/expand button and a title button. PM group boxes can be nested by supplying an existing PM_GroupBox as the parentWidget of a new PM_GroupBox (as an argument to its constructor). If the parentWidget is a PM_GroupBox, no title button will be created for the new group box. @cvar setAsDefault: Determines whether to reset the value of all widgets in the group box when the user clicks the "Restore Defaults" button. If set to False, no widgets will be reset regardless thier own I{setAsDefault} value. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this group box. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} @cvar expanded: Expanded flag. @type expanded: bool @cvar _title: The group box title. @type _title: str @cvar _widgetList: List of widgets in the group box (except the title button). @type _widgetList: list @cvar _rowCount: Number of rows in the group box. @type _rowCount: int @cvar _groupBoxCount: Number of group boxes in this group box. @type _groupBoxCount: int @cvar _lastGroupBox: The last group box in this group box (i.e. the most recent PM group box added). @type _lastGroupBox: PM_GroupBox """ setAsDefault = True labelWidget = None expanded = True _title = "" _widgetList = [] _rowCount = 0 _groupBoxCount = 0 _lastGroupBox = None titleButtonRequested = True def __init__(self, parentWidget, title = '', connectTitleButton = True, setAsDefault = True ): """ Appends a PM_GroupBox widget to I{parentWidget}, a L{PM_Dialog} or a L{PM_GroupBox}. If I{parentWidget} is a L{PM_Dialog}, the group box will have a title button at the top for collapsing and expanding the group box. If I{parentWidget} is a PM_GroupBox, the title will simply be a text label at the top of the group box. @param parentWidget: The parent dialog or group box containing this widget. @type parentWidget: L{PM_Dialog} or L{PM_GroupBox} @param title: The title (button) text. If empty, no title is added. @type title: str @param connectTitleButton: If True, this class will automatically connect the title button of the groupbox to send signal to expand or collapse the groupbox. Otherwise, the caller has to connect this signal by itself. See: B{Ui_MovePropertyManager.addGroupBoxes} and B{MovePropertyManager.connect_or_disconnect_signals} for examples where the client connects this slot. @type connectTitleButton: bool @param setAsDefault: If False, no widgets in this group box will have thier default values restored when the B{Restore Defaults} button is clicked, regardless thier own I{setAsDefault} value. @type setAsDefault: bool @see: U{B{QGroupBox}<http://doc.trolltech.com/4/qgroupbox.html>} """ QGroupBox.__init__(self) self.parentWidget = parentWidget # Calling addWidget() here is important. If done at the end, # the title button does not get assigned its palette for some # unknown reason. Mark 2007-05-20. # Add self to PropMgr's vBoxLayout if parentWidget: parentWidget._groupBoxCount += 1 parentWidget.vBoxLayout.addWidget(self) parentWidget._widgetList.append(self) _groupBoxCount = 0 self._widgetList = [] self._title = title self.setAsDefault = setAsDefault self.setAutoFillBackground(True) self.setStyleSheet(self._getStyleSheet()) # Create vertical box layout which will contain two widgets: # - the group box title button (or title) on row 0. # - the container widget for all PM widgets on row 1. self._vBoxLayout = QVBoxLayout(self) self._vBoxLayout.setMargin(0) self._vBoxLayout.setSpacing(0) # _containerWidget contains all PM widgets in this group box. # Its sole purpose is to easily support the collapsing and # expanding of a group box by calling this widget's hide() # and show() methods. self._containerWidget = QWidget() self._vBoxLayout.insertWidget(0, self._containerWidget) # Create vertical box layout self.vBoxLayout = QVBoxLayout(self._containerWidget) self.vBoxLayout.setMargin(PM_GROUPBOX_VBOXLAYOUT_MARGIN) self.vBoxLayout.setSpacing(PM_GROUPBOX_VBOXLAYOUT_SPACING) # Create grid layout self.gridLayout = QGridLayout() self.gridLayout.setMargin(PM_GRIDLAYOUT_MARGIN) self.gridLayout.setSpacing(PM_GRIDLAYOUT_SPACING) # Insert grid layout in its own vBoxLayout self.vBoxLayout.addLayout(self.gridLayout) # Add title button (or just a title if the parent is not a PM_Dialog). if not parentWidget or isinstance(parentWidget, PM_GroupBox): self.setTitle(title) else: # Parent is a PM_Dialog, so add a title button. if not self.titleButtonRequested: self.setTitle(title) else: self.titleButton = self._getTitleButton(self, title) self._vBoxLayout.insertWidget(0, self.titleButton) if connectTitleButton: self.connect( self.titleButton, SIGNAL("clicked()"), self.toggleExpandCollapse) self._insertMacSpacer() # Fixes the height of the group box. Very important. Mark 2007-05-29 self.setSizePolicy( QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred), QSizePolicy.Policy(QSizePolicy.Fixed))) self._addBottomSpacer() return def _insertMacSpacer(self, spacerHeight = 6): """ This addresses a Qt 4.3.5 layout bug on Mac OS X in which the title button will overlap with and cover the first widget in the group box. This workaround inserts a I{spacerHeight} tall spacer between the group box's title button and container widget. """ if sys.platform != "darwin": return self._macVerticalSpacer = QSpacerItem(10, spacerHeight, QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) self._vBoxLayout.insertItem(1, self._macVerticalSpacer) return def _addBottomSpacer(self): """ Add a vertical spacer below this group box. Method: Assume this is going to be the last group box in the PM, so set its spacer's vertical sizePolicy to MinimumExpanding. We then set the vertical sizePolicy of the last group box's spacer to Fixed and set its height to PM_GROUPBOX_SPACING. """ # Spacers are only added to groupboxes in the PropMgr, not # nested groupboxes. ## from PM.PM_Dialog import PM_Dialog ## if not isinstance(self.parentWidget, PM_Dialog): if not self.parentWidget or isinstance(self.parentWidget, PM_GroupBox): #bruce 071103 revised test to remove import cycle; I assume that # self.parentWidget is either a PM_GroupBox or a PM_Dialog, since # comments about similar code in __init__ imply that. # # A cleaner fix would involve asking self.parentWidget whether to # do this, with instances of PM_GroupBox and PM_Dialog giving # different answers, and making them both inherit an API class # which documents the method or attr with which we ask them that # question; the API class would be inherited by any object to # which PM_GroupBox's such as self can be added. # # Or, an even cleaner fix would just call a method in # self.parentWidget to do what this code does now (implemented # differently in PM_GroupBox and PM_Dialog). self.verticalSpacer = None return if self.parentWidget._lastGroupBox: # _lastGroupBox is no longer the last one. <self> will be the # _lastGroupBox, so we must change the verticalSpacer height # and sizePolicy of _lastGroupBox to be a fixed # spacer between it and <self>. defaultHeight = PM_GROUPBOX_SPACING self.parentWidget._lastGroupBox.verticalSpacer.changeSize( 10, defaultHeight, QSizePolicy.Fixed, QSizePolicy.Fixed) self.parentWidget._lastGroupBox.verticalSpacer.defaultHeight = \ defaultHeight # Add a 1 pixel high, MinimumExpanding VSpacer below this group box. # This keeps all group boxes in the PM layout squeezed together as # group boxes are expanded, collapsed, hidden and shown again. defaultHeight = 1 self.verticalSpacer = QSpacerItem(10, defaultHeight, QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) self.verticalSpacer.defaultHeight = defaultHeight self.parentWidget.vBoxLayout.addItem(self.verticalSpacer) # This groupbox is now the last one in the PropMgr. self.parentWidget._lastGroupBox = self return def restoreDefault (self): """ Restores the default values for all widgets in this group box. """ for widget in self._widgetList: if debug_flags.atom_debug: print "PM_GroupBox.restoreDefault(): widget =", \ widget.objectName() widget.restoreDefault() return def getTitle(self): """ Returns the group box title. @return: The group box title. @rtype: str """ return self._title def setTitle(self, title): """ Sets the groupbox title to <title>. @param title: The group box title. @type title: str @attention: This overrides QGroupBox's setTitle() method. """ if not title: return # Create QLabel widget. if not self.labelWidget: self.labelWidget = QLabel() self.vBoxLayout.insertWidget(0, self.labelWidget) labelAlignment = PM_LABEL_LEFT_ALIGNMENT self.labelWidget.setAlignment(labelAlignment) self._title = title self.labelWidget.setText(title) return def getPmWidgetPlacementParameters(self, pmWidget): """ Returns all the layout parameters needed to place a PM_Widget in the group box grid layout. @param pmWidget: The PM widget. @type pmWidget: PM_Widget """ row = self._rowCount #PM_CheckBox doesn't have a label. So do the following to decide the #placement of the checkbox. (can be placed either in column 0 or 1 , #This also needs to be implemented for PM_RadioButton, but at present #the following code doesn't support PM_RadioButton. if isinstance(pmWidget, PM_CheckBox): spanWidth = pmWidget.spanWidth if not spanWidth: # Set the widget's row and column parameters. widgetRow = row widgetColumn = pmWidget.widgetColumn widgetSpanCols = 1 widgetAlignment = PM_LABEL_LEFT_ALIGNMENT rowIncrement = 1 #set a virtual label labelRow = row labelSpanCols = 1 labelAlignment = PM_LABEL_RIGHT_ALIGNMENT if widgetColumn == 0: labelColumn = 1 elif widgetColumn == 1: labelColumn = 0 else: # Set the widget's row and column parameters. widgetRow = row widgetColumn = pmWidget.widgetColumn widgetSpanCols = 2 widgetAlignment = PM_LABEL_LEFT_ALIGNMENT rowIncrement = 1 #no label labelRow = 0 labelColumn = 0 labelSpanCols = 0 labelAlignment = PM_LABEL_RIGHT_ALIGNMENT return widgetRow, \ widgetColumn, \ widgetSpanCols, \ widgetAlignment, \ rowIncrement, \ labelRow, \ labelColumn, \ labelSpanCols, \ labelAlignment label = pmWidget.label labelColumn = pmWidget.labelColumn spanWidth = pmWidget.spanWidth if not spanWidth: # This widget and its label are on the same row labelRow = row labelSpanCols = 1 labelAlignment = PM_LABEL_RIGHT_ALIGNMENT # Set the widget's row and column parameters. widgetRow = row widgetColumn = 1 widgetSpanCols = 1 widgetAlignment = PM_LABEL_LEFT_ALIGNMENT rowIncrement = 1 if labelColumn == 1: widgetColumn = 0 labelAlignment = PM_LABEL_LEFT_ALIGNMENT widgetAlignment = PM_LABEL_RIGHT_ALIGNMENT else: # This widget spans the full width of the groupbox if label: # The label and widget are on separate rows. # Set the label's row, column and alignment. labelRow = row labelColumn = 0 labelSpanCols = 2 # Set this widget's row and column parameters. widgetRow = row + 1 # Widget is below the label. widgetColumn = 0 widgetSpanCols = 2 rowIncrement = 2 else: # No label. Just the widget. labelRow = 0 labelColumn = 0 labelSpanCols = 0 # Set the widget's row and column parameters. widgetRow = row widgetColumn = 0 widgetSpanCols = 2 rowIncrement = 1 labelAlignment = PM_LABEL_LEFT_ALIGNMENT widgetAlignment = PM_LABEL_LEFT_ALIGNMENT return widgetRow, \ widgetColumn, \ widgetSpanCols, \ widgetAlignment, \ rowIncrement, \ labelRow, \ labelColumn, \ labelSpanCols, \ labelAlignment def addPmWidget(self, pmWidget): """ Add a PM widget and its label to this group box. @param pmWidget: The PM widget to add. @type pmWidget: PM_Widget """ # Get all the widget and label layout parameters. widgetRow, \ widgetColumn, \ widgetSpanCols, \ widgetAlignment, \ rowIncrement, \ labelRow, \ labelColumn, \ labelSpanCols, \ labelAlignment = \ self.getPmWidgetPlacementParameters(pmWidget) if pmWidget.labelWidget: #Create Label as a pixmap (instead of text) if a valid icon path #is provided labelPath = str(pmWidget.label) if labelPath and labelPath.startswith("ui/"): #bruce 080325 revised labelPixmap = getpixmap(labelPath) if not labelPixmap.isNull(): pmWidget.labelWidget.setPixmap(labelPixmap) pmWidget.labelWidget.setText('') self.gridLayout.addWidget( pmWidget.labelWidget, labelRow, labelColumn, 1, labelSpanCols, labelAlignment ) # The following is a workaround for a Qt bug. If addWidth()'s # <alignment> argument is not supplied, the widget spans the full # column width of the grid cell containing it. If <alignment> # is supplied, this desired behavior is lost and there is no # value that can be supplied to maintain the behavior (0 doesn't # work). The workaround is to call addWidget() without the <alignment> # argument. Mark 2007-07-27. if widgetAlignment == PM_LABEL_LEFT_ALIGNMENT: self.gridLayout.addWidget( pmWidget, widgetRow, widgetColumn, 1, widgetSpanCols) # aligment = 0 doesn't work. else: self.gridLayout.addWidget( pmWidget, widgetRow, widgetColumn, 1, widgetSpanCols, widgetAlignment ) self._widgetList.append(pmWidget) self._rowCount += rowIncrement return def getRowCount(self): """ Return the row count of gridlayout of L{PM_Groupbox} @return: The row count of L{self.gridlayout} @rtype: int """ return self._rowCount def incrementRowCount(self, increment = 1): """ Increment the row count of the gridlayout of L{PM_groupBox} @param increment: The incremental value @type increment: int """ self._rowCount += increment return def addQtWidget(self, qtWidget, column = 0, spanWidth = False): """ Add a Qt widget to this group box. @param qtWidget: The Qt widget to add. @type qtWidget: QWidget @warning: this method has not been tested yet. """ # Set the widget's row and column parameters. widgetRow = self._rowCount widgetColumn = column if spanWidth: widgetSpanCols = 2 else: widgetSpanCols = 1 self.gridLayout.addWidget( qtWidget, widgetRow, widgetColumn, 1, widgetSpanCols ) self._rowCount += 1 return def collapse(self): """ Collapse this group box i.e. hide all its contents and change the look and feel of the groupbox button. """ self.vBoxLayout.setMargin(0) self.vBoxLayout.setSpacing(0) self.gridLayout.setMargin(0) self.gridLayout.setSpacing(0) # The styleSheet contains the expand/collapse. styleSheet = self._getTitleButtonStyleSheet(showExpanded = False) self.titleButton.setStyleSheet(styleSheet) self._containerWidget.hide() self.expanded = False return def expand(self): """ Expand this group box i.e. show all its contents and change the look and feel of the groupbox button. """ self.vBoxLayout.setMargin(PM_GROUPBOX_VBOXLAYOUT_MARGIN) self.vBoxLayout.setSpacing(PM_GROUPBOX_VBOXLAYOUT_SPACING) self.gridLayout.setMargin(PM_GROUPBOX_GRIDLAYOUT_MARGIN) self.gridLayout.setSpacing(PM_GROUPBOX_GRIDLAYOUT_SPACING) # The styleSheet contains the expand/collapse. styleSheet = self._getTitleButtonStyleSheet(showExpanded = True) self.titleButton.setStyleSheet(styleSheet) self._containerWidget.show() self.expanded = True return def hide(self): """ Hides the group box . @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() # Change the spacer height to zero to "hide" it unless # self is the last GroupBox in the Property Manager. if self.parentWidget._lastGroupBox is self: return if self.verticalSpacer: self.verticalSpacer.changeSize(10, 0) return def show(self): """ Unhides the group box. @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() if self.parentWidget._lastGroupBox is self: return if self.verticalSpacer: self.verticalSpacer.changeSize(10, self.verticalSpacer.defaultHeight) return # Title Button Methods ##################################### def _getTitleButton(self, parentWidget = None, title = '', showExpanded = True ): """ Return the group box title push button. The push button is customized such that it appears as a title bar at the top of the group box. If the user clicks on this 'title bar' it sends a signal to open or close the group box. @param parentWidget: The parent dialog or group box containing this widget. @type parentWidget: PM_Dialog or PM_GroupBox @param title: The button title. @type title: str @param showExpanded: dDetermines whether the expand or collapse image is displayed on the title button. @type showExpanded: bool @see: L{_getTitleButtonStyleSheet()} @Note: Including a title button should only be legal if the parentWidget is a PM_Dialog. """ button = QPushButton(title, parentWidget) button.setFlat(False) button.setAutoFillBackground(True) button.setStyleSheet(self._getTitleButtonStyleSheet(showExpanded)) return button def _getTitleButtonStyleSheet(self, showExpanded = True): """ Returns the style sheet for a group box title button (or checkbox). @param showExpanded: Determines whether to include an expand or collapse icon. @type showExpanded: bool @return: The title button style sheet. @rtype: str """ # Need to move border color and text color to top # (make global constants). if showExpanded: styleSheet = \ "QPushButton {"\ "border-style: outset; "\ "border-width: 2px; "\ "border-color: #%s; "\ "border-radius: 2px; "\ "background-color: #%s; "\ "font: bold 12px 'Arial'; "\ "color: #%s; "\ "min-width: 10em; "\ "background-image: url(%s); "\ "background-position: right; "\ "background-repeat: no-repeat; "\ "text-align: left; "\ "}" % (QColor_to_Hex(pmGrpBoxButtonBorderColor), QColor_to_Hex(pmGrpBoxButtonColor), QColor_to_Hex(pmGrpBoxButtonTextColor), pmGrpBoxExpandedIconPath ) else: # Collapsed. styleSheet = \ "QPushButton {"\ "border-style: outset; "\ "border-width: 2px; "\ "border-color: #%s; "\ "border-radius: 2px; "\ "background-color: #%s; "\ "font: bold 12px 'Arial'; "\ "color: #%s; "\ "min-width: 10em; "\ "background-image: url(%s); "\ "background-position: right; "\ "background-repeat: no-repeat; "\ "text-align: left; "\ "}" % (QColor_to_Hex(pmGrpBoxButtonBorderColor), QColor_to_Hex(pmGrpBoxButtonColor), QColor_to_Hex(pmGrpBoxButtonTextColor), pmGrpBoxCollapsedIconPath ) return styleSheet def toggleExpandCollapse(self): """ Slot method for the title button to expand/collapse the group box. """ if self._widgetList: if self.expanded: self.collapse() else: # Expand groupbox by showing all widgets in groupbox. self.expand() else: print "Clicking on the group box button has no effect "\ "since it has no widgets." return # GroupBox stylesheet methods. ############################## def _getStyleSheet(self): """ Return the style sheet for the groupbox. This sets the following properties only: - border style - border width - border color - border radius (on corners) - background color @return: The group box style sheet. @rtype: str """ styleSheet = \ "QGroupBox {"\ "border-style: solid; "\ "border-width: 1px; "\ "border-color: #%s; "\ "border-radius: 0px; "\ "background-color: #%s; "\ "min-width: 10em; "\ "}" % ( QColor_to_Hex(pmGrpBoxBorderColor), QColor_to_Hex(pmGrpBoxColor) ) return styleSheet
class Ui_DnaSequenceEditor(PM_DockWidget): """ The Ui_DnaSequenceEditor class defines UI elements for the Sequence Editor object. The sequence editor is usually visible while in while editing a DnaStrand. It is a DockWidget that is docked at the bottom of the MainWindow. """ _title = "Sequence Editor" _groupBoxCount = 0 _lastGroupBox = None def __init__(self, win): """ Constructor for the Ui_DnaSequenceEditor @param win: The parentWidget (MainWindow) for the sequence editor """ self.win = win # Should parentWidget for a docwidget always be win? #Not necessary but most likely it will be the case. parentWidget = win _superclass.__init__(self, parentWidget, title=self._title) #A flag used to restore the state of the Reports dock widget #(which can be accessed through View > Reports) see self.show() and #self.closeEvent() for more details. self._reportsDockWidget_closed_in_show_method = False self.setFixedHeight(90) return def show(self): """ Shows the sequence editor. While doing this, it also closes the reports dock widget (if visible) the state of the reports dockwidget will be restored when the sequence editor is closed. @see:self.closeEvent() """ self._reportsDockWidget_closed_in_show_method = False #hide the history widget first #(It will be shown back during self.close) #The history widget is hidden or shown only when both # 'View > Full Screen' and View > Semi Full Screen actions # are *unchecked* #Thus show or close methods won't do anything to history widget # if either of the above mentioned actions is checked. if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self.win.reportsDockWidget.isVisible(): self.win.reportsDockWidget.close() self._reportsDockWidget_closed_in_show_method = True _superclass.show(self) return def closeEvent(self, event): """ Overrides close event. Makes sure that the visible state of the reports widgetis restored when the sequence editor is closed. @see: self.show() """ _superclass.closeEvent(self, event) if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self._reportsDockWidget_closed_in_show_method: self.win.viewReportsAction.setChecked(True) self._reportsDockWidget_closed_in_show_method = False return def _loadWidgets(self): """ Overrides PM.PM_DockWidget._loadWidgets. Loads the widget in this dockwidget. """ self._loadMenuWidgets() self._loadTextEditWidget() return def _loadMenuWidgets(self): """ Load the various menu widgets (e.g. Open, save sequence options, Find and replace widgets etc. """ #Note: Find and replace widgets might be moved to their own class. self.loadSequenceButton = PM_ToolButton( self, iconPath="ui/actions/Properties Manager/Open.png") self.saveSequenceButton = PM_ToolButton( self, iconPath="ui/actions/Properties Manager/Save_Strand_Sequence.png") self.loadSequenceButton.setAutoRaise(True) self.saveSequenceButton.setAutoRaise(True) # Only supporting 5' to 3' direction until bug 2956 is fixed. # Mark 2008-12-19 editDirectionChoices = ["5' to 3'"] # , "3' to 5'"] self.baseDirectionChoiceComboBox = \ PM_ComboBox( self, choices = editDirectionChoices, index = 0, spanWidth = False ) #Find and replace widgets -- self.findLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.findLineEdit.setMaximumWidth(60) self.replaceLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.replaceLineEdit.setMaximumWidth(60) self.findOptionsToolButton = PM_ToolButton(self) self.findOptionsToolButton.setMaximumWidth(12) self.findOptionsToolButton.setAutoRaise(True) self.findOptionsToolButton.setPopupMode(QToolButton.MenuButtonPopup) self._setFindOptionsToolButtonMenu() self.findNextToolButton = PM_ToolButton( self, iconPath="ui/actions/Properties Manager/Find_Next.png") self.findNextToolButton.setAutoRaise(True) self.findPreviousToolButton = PM_ToolButton( self, iconPath="ui/actions/Properties Manager/Find_Previous.png") self.findPreviousToolButton.setAutoRaise(True) self.replacePushButton = PM_PushButton(self, text="Replace") self.warningSign = QLabel(self) self.warningSign.setPixmap( getpixmap('ui/actions/Properties Manager/Warning.png')) self.warningSign.hide() self.phraseNotFoundLabel = QLabel(self) self.phraseNotFoundLabel.setText("Sequence Not Found") self.phraseNotFoundLabel.hide() # NOTE: Following needs cleanup in the PM_WidgetRow/ PM_WidgetGrid # but this explanation is sufficient until thats done -- # When the widget type starts with the word 'PM_' , the # PM_WidgetRow treats it as a well defined widget and thus doesn't try # to create a QWidget object (or its subclasses) # This is the reason why qLabels such as self.warningSign and # self.phraseNotFoundLabel are defined as PM_Labels and not 'QLabels' # If they were defined as 'QLabel'(s) then PM_WidgetRow would have # recreated the label. Since we want to show/hide the above mentioned # labels (and if they were recreated as mentioned above), # we would have needed to define those something like this: # self.phraseNotFoundLabel = widgetRow._widgetList[-2] #Cleanup in PM_widgetGrid could be to check if the widget starts with #'Q' instead of 'PM_' #Widgets to include in the widget row. widgetList = [('PM_ToolButton', self.loadSequenceButton, 0), ('PM_ToolButton', self.saveSequenceButton, 1), ('QLabel', " Sequence direction:", 2), ('PM_ComboBox', self.baseDirectionChoiceComboBox, 3), ('QLabel', " Find:", 4), ('PM_LineEdit', self.findLineEdit, 5), ('PM_ToolButton', self.findOptionsToolButton, 6), ('PM_ToolButton', self.findPreviousToolButton, 7), ('PM_ToolButton', self.findNextToolButton, 8), ('QLabel', " Replace:", 9), ('PM_TextEdit', self.replaceLineEdit, 10), ('PM_PushButton', self.replacePushButton, 11), ('PM_Label', self.warningSign, 12), ('PM_Label', self.phraseNotFoundLabel, 13), ('QSpacerItem', 5, 5, 14)] widgetRow = PM_WidgetRow(self, title='', widgetList=widgetList, label="", spanWidth=True) return def _loadTextEditWidget(self): """ Load the SequenceTexteditWidgets. """ self.sequenceTextEdit = \ PM_TextEdit( self, label = " Sequence: ", spanWidth = False, permit_enter_keystroke = False) self.sequenceTextEdit.setCursorWidth(2) self.sequenceTextEdit.setWordWrapMode(QTextOption.WrapAnywhere) self.sequenceTextEdit.setFixedHeight(20) #The StrandSequence 'Mate' it is a read only etxtedit that shows #the complementary strand sequence. self.sequenceTextEdit_mate = \ PM_TextEdit(self, label = "", spanWidth = False, permit_enter_keystroke = False ) palette = getPalette(None, QPalette.Base, sequenceEditStrandMateBaseColor) self.sequenceTextEdit_mate.setPalette(palette) self.sequenceTextEdit_mate.setFixedHeight(20) self.sequenceTextEdit_mate.setReadOnly(True) self.sequenceTextEdit_mate.setWordWrapMode(QTextOption.WrapAnywhere) #Important to make sure that the horizontal and vertical scrollbars #for these text edits are never displayed. for textEdit in (self.sequenceTextEdit, self.sequenceTextEdit_mate): textEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) textEdit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) return def _getFindLineEditStyleSheet(self): """ Return the style sheet for the findLineEdit. This sets the following properties only: - background-color This style is set whenever the searchStrig can't be found (sets a light red color background to the lineedit when this happens) @return: The line edit style sheet. @rtype: str """ styleSheet = "QLineEdit {"\ "background-color: rgb(255, 102, 102)"\ "}" #Not used: # background-color: rgb(217, 255, 216)\ return styleSheet def _setFindOptionsToolButtonMenu(self): """ Sets the menu for the findOptionstoolbutton that appears a small menu button next to the findLineEdit. """ self.findOptionsMenu = QMenu(self.findOptionsToolButton) self.caseSensitiveFindAction = QAction(self.findOptionsToolButton) self.caseSensitiveFindAction.setText('Match Case') self.caseSensitiveFindAction.setCheckable(True) self.caseSensitiveFindAction.setChecked(False) self.findOptionsMenu.addAction(self.caseSensitiveFindAction) self.findOptionsMenu.addSeparator() self.findOptionsToolButton.setMenu(self.findOptionsMenu) return def _addToolTipText(self): """ What's Tool Tip text for widgets in this Property Manager. """ from ne1_ui.ToolTipText_for_PropertyManagers import ToolTip_DnaSequenceEditor ToolTip_DnaSequenceEditor(self) return def _addWhatsThisText(self): """ What's This text for widgets in this Property Manager. """ from ne1_ui.WhatsThisText_for_PropertyManagers import whatsThis_DnaSequenceEditor whatsThis_DnaSequenceEditor(self) return
class PM_Slider(QSlider): labelWidget = None def __init__( self, parentWidget, orientation=None, ##Qt.Horizontal, currentValue=0, minimum=0, maximum=100, label='', labelColumn=0, setAsDefault=True, spanWidth=True): """ Appends a Qslider widget (with a QLabel widget) to <parentWidget> a property manager group box. Arguments: @param parentWidget: the group box containing this PM widget. @type parentWidget: PM_GroupBox @param currentValue: @type currentValue: int @param minimum: minimum value the slider can get. (used to set its 'range') @type minimum: int @param maximum: maximum value the slider can get. (used to set its 'range') @type maximum: int @param label: label that appears to the left of (or above) this widget. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param setAsDefault: if True, will restore value when the "Restore Defaults" button is clicked. @type setAsDefault: bool (default True) @param spanWidth: If True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty)and is left justified. @type spanWidth: bool (default True) @see: U{B{QSlider}<http://doc.trolltech.com/4/qslider.html>} """ ##QSlider.__init__(self, orientation, parentWidget) QSlider.__init__(self, parentWidget) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth #Ideally, this should be simply self.setOrientation(orientation) with the #default orientation = Qt.Horizontal in the init argument itself. But, #apparently pylint chokes up when init argument is a Qt enum. #This problem happened while running pylint 0.23 on the SEMBOT server #so comitting this temporary workaround. The pylint on my machine is #0.25 and it runs fine even before this workaround. Similar changes made #in PM_CheckBox. -- Ninad 2008-06-30 if orientation is None: self.setOrientation(Qt.Horizontal) else: self.setOrientation(orientation) if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) self.setValue(currentValue) self.setRange(minimum, maximum) self.setAsDefault = setAsDefault if self.setAsDefault: self.setDefaultValue(currentValue) parentWidget.addPmWidget(self) def hide(self): """ Hides the slider and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the slider and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show() def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setValue(self.defaultValue) def setDefaultValue(self, value): """ Sets the Default value for the slider. This value will be set if user hits Restore Defaults button in the Property Manager. """ self.defaultValue = value self.setAsDefault = True
class PM_ToolButton(QToolButton): """ The PM_ToolButton widget provides a QToolButton with a QLabel for a Property Manager group box. @cvar defaultText: The default text of the tool button. @type defaultText: str @cvar setAsDefault: Determines whether to reset the value of the tool button to I{defaultText} when the user clicks the "Restore Defaults" button. @type setAsDefault: bool @cvar labelWidget: The Qt label widget of this tool button. @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>} """ defaultText = "" setAsDefault = True labelWidget = None def __init__(self, parentWidget, label='', labelColumn=0, text='', iconPath='', setAsDefault=True, spanWidth=False): """ Appends a QToolButton (Qt) widget to the bottom of I{parentWidget}, a Property Manager group box. @param parentWidget: The parent group box containing this widget. @type parentWidget: PM_GroupBox @param label: The label that appears to the left or right of the checkbox. If spanWidth is True, the label will be displayed on its own row directly above the list widget. To suppress the label, set I{label} to an empty string. @type label: str @param labelColumn: The column number of the label in the group box grid layout. The only valid values are 0 (left column) and 1 (right column). The default is 0 (left column). @type labelColumn: int @param text: The button's text. @type text: str @param iconPath: The relative path to the button's icon. @type iconPath: str @param setAsDefault: If True, will restore <text> as the button's text when the "Restore Defaults" button is clicked. @type setAsDefault: bool @param spanWidth: If True, the widget and its label will span the width of the group box. Its label will appear directly above the widget (unless the label is empty) and is left justified. @type spanWidth: bool @see: U{B{QToolButton}<http://doc.trolltech.com/4/qtoolbutton.html>} """ if 0: # Debugging code print "PM_ToolButton.__init__():" print " label = ", label print " labelColumn = ", labelColumn print " text = ", text print " iconPath = ", iconPath print " setAsDefault = ", setAsDefault print " spanWidth = ", spanWidth QToolButton.__init__(self) self.parentWidget = parentWidget self.label = label self.labelColumn = labelColumn self.setAsDefault = setAsDefault self.spanWidth = spanWidth if label: # Create this widget's QLabel. self.labelWidget = QLabel() self.labelWidget.setText(label) # Set text self.setText(text) # Set icon self.setIcon(geticon(iconPath)) self.setIconSize(QSize(22, 22)) # Set default text self.defaultText = text self.setAsDefault = setAsDefault parentWidget.addPmWidget(self) def restoreDefault(self): """ Restores the default value. """ if self.setAsDefault: self.setText(self.defaultText) def hide(self): """ Hides the tool button and its label (if it has one). @see: L{show} """ QWidget.hide(self) if self.labelWidget: self.labelWidget.hide() def show(self): """ Unhides the tool button and its label (if it has one). @see: L{hide} """ QWidget.show(self) if self.labelWidget: self.labelWidget.show()
class SelectNodeByNameDockWidget(PM_DockWidget): """ The Ui_DnaSequenceEditor class defines UI elements for the Sequence Editor object. The sequence editor is usually visible while in DNA edit mode. It is a DockWidget that is doced at the bottom of the MainWindow """ _title = "Select Node by Name" _groupBoxCount = 0 _lastGroupBox = None def __init__(self, win): """ Constructor for the Ui_DnaSequenceEditor @param win: The parentWidget (MainWindow) for the sequence editor """ self.win = win parentWidget = win _superclass.__init__(self, parentWidget, title = self._title) win.addDockWidget(Qt.BottomDockWidgetArea, self) self.setFixedHeight(120) ##self.setFixedWidth(90) self.connect_or_disconnect_signals(True) if not self.win.selectByNameAction.isChecked(): self.close() def show(self): """ Overrides superclass method. """ _superclass.show(self) val = env.prefs[dnaSearchTypeLabelChoice_prefs_key] self.searchTypeComboBox_indexChanged(val) def connect_or_disconnect_signals(self, isConnect): """ Connect or disconnect widget signals sent to their slot methods. This can be overridden in subclasses. By default it does nothing. @param isConnect: If True the widget will send the signals to the slot method. @type isConnect: boolean """ if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect self._listWidget.connect_or_disconnect_signals(isConnect) change_connect( self.searchToolButton, SIGNAL("clicked()"), self.searchNodes) prefs_key = dnaSearchTypeLabelChoice_prefs_key connect_comboBox_with_pref(self.searchTypeComboBox, prefs_key ) change_connect( self.searchTypeComboBox, SIGNAL("currentIndexChanged(int)"), self.searchTypeComboBox_indexChanged) def searchTypeComboBox_indexChanged(self, val): if val == 0: ##self._widgetRow1.show() ##self._widgetRow2.hide() self._widgetRow1.setEnabled(True) self._widgetRow2.setEnabled(False) else: ##self._widgetRow2.show() ##self._widgetRow1.hide() self._widgetRow2.setEnabled(True) self._widgetRow1.setEnabled(False) def searchNodes(self): """ ONLY implemented for DnaStrand or DnaSegments. """ assy = self.win.assy topnode = assy.part.topnode lst = [] def func(node): if isinstance(node, assy.DnaStrandOrSegment): lst.append(node) topnode.apply2all(func) choice = env.prefs[dnaSearchTypeLabelChoice_prefs_key] if choice == 0: nodes = self._searchNodesByName(lst) elif choice == 1: nodes = self._searchNodesByNucleotides(lst) self._listWidget.insertItems( row = 0, items = nodes) def _searchNodesByNucleotides(self, nodeList): lst = nodeList min_val = self._nucleotidesSpinBox_1.value() max_val = self._nucleotidesSpinBox_2.value() if min_val > max_val: print "Lower value for number of nucleotides exceeds max search value" return () def func2(node): n = node.getNumberOfNucleotides() return (n >= min_val and n <= max_val) return filter(lambda m:func2(m), lst) def _searchNodesByName(self, nodeList): nodeNameString = self.findLineEdit.text() nodeNameString = str(nodeNameString) lst = nodeList def func2(node): n = len(nodeNameString) if len(node.name)< n: return False nameString = str(node.name[:n]) if nameString.lower() == nodeNameString.lower(): return True return False return filter(lambda m:func2(m), lst) def closeEvent(self, event): self.win.selectByNameAction.setChecked(False) _superclass.closeEvent(self, event) def _loadWidgets(self): """ Overrides PM.PM_DockWidget._loadWidgets. Loads the widget in this dockwidget. """ self._loadMenuWidgets() self._loadTableWidget() def _loadTableWidget(self): self._listWidget = PM_DnaSearchResultTable(self, self.win) def _loadMenuWidgets(self): """ Load the various menu widgets (e.g. Open, save sequence options, Find and replace widgets etc. """ #Note: Find and replace widgets might be moved to their own class. self.searchTypeComboBox = \ PM_ComboBox( self, label = "Search options:", choices = ["By node name", "By # of bases (DNA only)"], setAsDefault = True) #Find widgets -- self._nucleotidesSpinBox_1 = PM_SpinBox(self, label = "", value = 10, setAsDefault = False, singleStep = 10, minimum = 1, maximum = 50000) self._nucleotidesSpinBox_2 = PM_SpinBox(self, label = "", value = 50, setAsDefault = False, singleStep = 10, minimum = 1, maximum = 50000) self.findLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.findLineEdit.setMaximumWidth(80) self.findOptionsToolButton = PM_ToolButton(self) self.findOptionsToolButton.setMaximumWidth(12) self.findOptionsToolButton.setAutoRaise(True) ##self.findOptionsToolButton.setPopupMode(QToolButton.MenuButtonPopup) ##self._setFindOptionsToolButtonMenu() self.searchToolButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Find_Next.png") self.searchToolButton.setAutoRaise(False) self.warningSign = QLabel(self) self.warningSign.setPixmap( getpixmap('ui/actions/Properties Manager/Warning.png')) self.warningSign.hide() self.phraseNotFoundLabel = QLabel(self) self.phraseNotFoundLabel.setText("Not Found") self.phraseNotFoundLabel.hide() # NOTE: Following needs cleanup in the PM_WidgetRow/ PM_WidgetGrid # but this explanation is sufficient until thats done -- # When the widget type starts with the word 'PM_' , the # PM_WidgetRow treats it as a well defined widget and thus doesn't try # to create a QWidget object (or its subclasses) # This is the reason why qLabels such as self.warningSign and # self.phraseNotFoundLabel are defined as PM_Labels and not 'QLabels' # If they were defined as 'QLabel'(s) then PM_WidgetRow would have # recreated the label. Since we want to show/hide the above mentioned # labels (and if they were recreated as mentioned above), # we would have needed to define those something like this: # self.phraseNotFoundLabel = widgetRow._widgetList[-2] #Cleanup in PM_widgetGrid could be to check if the widget starts with #'Q' instead of 'PM_' #Widgets to include in the widget row. widgetList1 = [ ('QLabel', " Search for name:", 1), ('PM_LineEdit', self.findLineEdit, 2), ('PM_ToolButton', self.findOptionsToolButton, 3), ('PM_ToolButton', self.searchToolButton, 4), ('PM_Label', self.warningSign, 5), ('PM_Label', self.phraseNotFoundLabel, 6), ('QSpacerItem', 5, 5, 7) ] widgetList2 = [ ('QLabel', " Number of bases: >=", 1), ('PM_SpinBox', self._nucleotidesSpinBox_1, 2), ('QLabel', " <=", 3), ('PM_SpinBox', self._nucleotidesSpinBox_2, 4), ('QSpacerItem', 5, 5, 5)] widgetList3 = [ ('QSpacerItem', 5, 5, 1), ('PM_ToolButton', self.searchToolButton, 2), ('PM_Label', self.warningSign, 3), ('PM_Label', self.phraseNotFoundLabel, 4), ('QSpacerItem', 5, 5, 5) ] self._widgetRow1 = PM_WidgetRow(self, title = '', widgetList = widgetList1, label = "", spanWidth = True ) self._widgetRow2 = PM_WidgetRow(self, title = '', widgetList = widgetList2, label = "", spanWidth = True ) self._widgetRow3 = PM_WidgetRow(self, title = '', widgetList = widgetList3, label = "", spanWidth = True )