Пример #1
0
class ExpandableFrame(base.BaseFrame, object):

    def __init__(self, title='', icon=None, parent=None):
        self._is_collapsed = True
        self._title_frame = None
        self._content = None
        self._title = title
        self._icon = icon
        self._content_layout = None

        super(ExpandableFrame, self).__init__(parent=parent)

    def ui(self):
        super(ExpandableFrame, self).ui()

        title_layout = layouts.HorizontalLayout(spacing=0, margins=(0, 0, 0, 0))
        self._title_frame = TitleFrame(title=self._title, icon=self._icon, collapsed=self._is_collapsed)
        self._icon_button = label.BaseLabel(parent=self)
        if self._icon:
            self._icon_button.setPixmap(self._icon.pixmap(QSize(20, 20)))
        else:
            self._icon_button.setVisible(False)
        title_layout.addWidget(self._icon_button)
        title_layout.addWidget(self._title_frame)
        title_layout.addStretch()

        self._content = QWidget()
        self._content_layout = layouts.VerticalLayout()
        self._content.setLayout(self._content_layout)
        self._content.setVisible(not self._is_collapsed)

        self.main_layout.addLayout(title_layout)
        self.main_layout.addWidget(self._content)

    def setup_signals(self):
        self._title_frame.clicked.connect(self._on_toggle_collapsed)
        self._icon_button.clicked.connect(self._on_toggle_collapsed)

    def addWidget(self, widget):
        self._content_layout.addWidget(widget)

    def addLayout(self, layout):
        self._content_layout.addLayout(layout)

    def set_title(self, title):
        self._title_frame.set_title(title)

    def _on_toggle_collapsed(self):
        self._content.setVisible(self._is_collapsed)
        self._is_collapsed = not self._is_collapsed
        self._title_frame._arrow.setArrow(self._is_collapsed)
Пример #2
0
class JointWidget(base.CommandRigToolBoxWidget, object):
    def __init__(self, client, commands_data, parent=None):

        self._model = JointWidgetModel()

        super(JointWidget, self).__init__(
            title='Joint',
            commands_data=commands_data,
            controller=JointWidgetController(model=self._model, client=client),
            parent=parent)

        self.refresh()

    @property
    def model(self):
        return self._model

    @property
    def controller(self):
        return self._controller

    def ui(self):
        super(JointWidget, self).ui()

        self._joints_to_insert_widget = QWidget()
        joints_to_insert_layout = layouts.HorizontalLayout(spacing=0,
                                                           margins=(0, 0, 0,
                                                                    0))
        self._joints_to_insert_widget.setLayout(joints_to_insert_layout)
        joints_to_insert_lbl = label.BaseLabel('Num. Joints: ', parent=self)
        self._joints_to_insert_spn = spinbox.BaseSpinBox(parent=self)
        self._joints_to_insert_spn.setMinimum(1)
        self._joints_to_insert_spn.setMaximum(99999999)
        joints_to_insert_layout.addWidget(joints_to_insert_lbl)
        joints_to_insert_layout.addWidget(self._joints_to_insert_spn)

        self._create_joints_on_curve_widget = QWidget()
        create_joints_on_curve_layout = layouts.HorizontalLayout(spacing=0,
                                                                 margins=(0, 0,
                                                                          0,
                                                                          0))
        self._create_joints_on_curve_widget.setLayout(
            create_joints_on_curve_layout)
        create_joints_on_curve_lbl = label.BaseLabel('Num. Joints: ',
                                                     parent=self)
        self._create_joints_on_curve_spn = spinbox.BaseSpinBox(parent=self)
        self._create_joints_on_curve_spn.setMinimum(1)
        self._create_joints_on_curve_spn.setMaximum(99999999)
        create_joints_on_curve_layout.addWidget(create_joints_on_curve_lbl)
        create_joints_on_curve_layout.addWidget(
            self._create_joints_on_curve_spn)

        self._snap_joints_to_curve_widget = QWidget()
        snap_joints_to_curve_layout = layouts.HorizontalLayout(spacing=0,
                                                               margins=(0, 0,
                                                                        0, 0))
        self._snap_joints_to_curve_widget.setLayout(
            snap_joints_to_curve_layout)
        snap_joints_to_curve_lbl = label.BaseLabel('Num. Joints: ',
                                                   parent=self)
        self._snap_joints_to_curve_spn = spinbox.BaseSpinBox(parent=self)
        self._snap_joints_to_curve_spn.setMinimum(0)
        self._snap_joints_to_curve_spn.setMaximum(99999999)
        snap_joints_to_curve_layout.addWidget(snap_joints_to_curve_lbl)
        snap_joints_to_curve_layout.addWidget(self._snap_joints_to_curve_spn)

        self._joints_display_size_widget = QWidget()
        joint_display_size_layout = layouts.HorizontalLayout(spacing=0,
                                                             margins=(0, 0, 0,
                                                                      0))
        self._joints_display_size_widget.setLayout(joint_display_size_layout)
        joint_display_size_lbl = label.BaseLabel('Joints Size: ', parent=self)
        self._joints_display_size_spn = spinbox.BaseDoubleSpinBox(parent=self)
        self._joints_display_size_spn.setSingleStep(0.5)
        self._joints_display_size_spn.setMinimum(0.1)
        self._joints_display_size_spn.setMaximum(999)
        self._joints_display_live_cbx = checkbox.BaseCheckBox('Live',
                                                              parent=self)
        joint_display_size_layout.addWidget(joint_display_size_lbl)
        joint_display_size_layout.addWidget(self._joints_display_size_spn)
        joint_display_size_layout.addWidget(self._joints_display_live_cbx)

        self._joints_to_insert_widget.setVisible(False)
        self._create_joints_on_curve_widget.setVisible(False)
        self._snap_joints_to_curve_widget.setVisible(False)
        self._joints_display_size_widget.setVisible(False)

        self.main_layout.addWidget(self._joints_to_insert_widget)
        self.main_layout.addWidget(self._create_joints_on_curve_widget)
        self.main_layout.addWidget(self._snap_joints_to_curve_widget)
        self.main_layout.addWidget(self._joints_display_size_widget)

    def setup_signals(self):
        self._joints_to_insert_spn.valueChanged.connect(
            self._controller.change_joints_to_insert)
        self._create_joints_on_curve_spn.valueChanged.connect(
            self._controller.change_joints_on_curve)
        self._snap_joints_to_curve_spn.valueChanged.connect(
            self._controller.change_snap_joints_to_curve)
        self._joints_display_size_spn.valueChanged.connect(
            self._controller.change_joints_display_size)
        self._joints_display_live_cbx.toggled.connect(
            self._controller.change_joints_display_size_live)

        self._model.jointsToInsertChanged.connect(
            self._joints_to_insert_spn.setValue)
        self._model.jointsOnCurveChanged.connect(
            self._create_joints_on_curve_spn.setValue)
        self._model.snapJointsToCurveChanged.connect(
            self._snap_joints_to_curve_spn.setValue)
        self._model.jointsDisplaySizeChanged.connect(
            self._on_joints_display_size_changed)
        self._model.jointsDisplaySizeLiveChanged.connect(
            self._joints_display_live_cbx.setChecked)

    def refresh(self):
        self._joints_to_insert_spn.setValue(self._model.joints_to_insert)
        self._joints_display_size_spn.setValue(self._model.joints_display_size)
        self._joints_display_live_cbx.setChecked(
            self._model.joints_display_size_live)
        self._create_joints_on_curve_spn.setValue(self._model.joints_on_curve)
        self._snap_joints_to_curve_spn.setValue(
            self._model.snap_joints_to_curve)

    def _on_joints_display_size_changed(self, value):
        live = self._model.joints_display_size_live

        with qt_contexts.block_signals(self._joints_display_size_spn):
            self._joints_display_size_spn.setValue(value)

        if live:
            self._controller.joint_display_size()
class InfoMessage(base.BaseWidget, object):
    def __init__(self, name='', description='', instructions='', parent=None):
        self._name = ''
        self._description = ''
        self._instructions = instructions

        super(InfoMessage, self).__init__(parent)

        self.setAttribute(Qt.WA_StyledBackground)
        self.theme_type = message.MessageTypes.INFO
        self.style().polish(self)
        self.name = name
        self.description = description
        self.instructions = instructions

    # =================================================================================================================
    # PROPERTIES
    # =================================================================================================================

    def _get_name(self):
        return self._name

    def _set_name(self, name):
        self._name = str(name)
        self._expandable_frame.set_title(self._name)

    def _get_description(self):
        return self._description

    def _set_description(self, text):
        self._description = str(text)
        self._description_text.setPlainText(self._description)

    def _get_instructions(self):
        return self._instructions

    def _set_instructions(self, instructions):
        self._instructions = str(instructions)
        self._instructions_text.clear()
        self._instructions_text.insertHtml(instructions)
        self._instructions_widget.setVisible(bool(instructions))

    name = Property(str, _get_name, _set_name)
    description = Property(str, _get_description, _set_description)
    instructions = Property(str, _get_instructions, _set_instructions)

    # =================================================================================================================
    # OVERRIDES
    # =================================================================================================================

    def ui(self):
        super(InfoMessage, self).ui()

        self.setMaximumHeight(150)

        info_icon = resources.icon('info')
        self._expandable_frame = expandables.ExpandableFrame(icon=info_icon,
                                                             parent=self)
        self._expandable_frame.setFrameStyle(QFrame.StyledPanel
                                             | QFrame.Raised)

        expandable_layout = layouts.HorizontalLayout(margins=(2, 2, 2, 2))

        texts_layout = layouts.HorizontalLayout(spacing=0,
                                                margins=(0, 0, 0, 0))
        self._description_text = QPlainTextEdit(parent=self)
        self._description_text.setReadOnly(True)
        self._description_text.setSizePolicy(QSizePolicy.Preferred,
                                             QSizePolicy.Maximum)
        self._description_text.setFocusPolicy(Qt.NoFocus)
        self._description_text.setFrameShape(QFrame.NoFrame)
        self._instructions_widget = QWidget()
        instructions_layout = layouts.VerticalLayout(spacing=2,
                                                     margins=(0, 0, 0, 0))
        self._instructions_widget.setLayout(instructions_layout)
        self._instructions_text = QTextEdit(parent=self)
        self._instructions_text.setReadOnly(True)
        self._instructions_text.setSizePolicy(QSizePolicy.Preferred,
                                              QSizePolicy.Maximum)
        self._instructions_text.setFocusPolicy(Qt.NoFocus)
        self._instructions_text.setFrameShape(QFrame.NoFrame)
        self._instructions_widget.setVisible(False)
        # self._instructions_text.insertHtml("<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul> <br />")
        instructions_layout.addWidget(dividers.Divider('Instructions'))
        instructions_layout.addWidget(self._instructions_text)
        texts_layout.addWidget(self._description_text)
        texts_layout.addWidget(self._instructions_widget)

        content_layout = layouts.VerticalLayout()
        content_layout.addLayout(texts_layout)
        expandable_layout.addLayout(content_layout)

        self._expandable_frame.addLayout(expandable_layout)

        self.main_layout.addWidget(self._expandable_frame)
class SkinningWidget(base.CommandRigToolBoxWidget, object):
    def __init__(self, client, commands_data, parent=None):

        self._model = SkinningWidgetModel()

        super(SkinningWidget, self).__init__(
            title='Skinning',
            commands_data=commands_data,
            controller=SkinningWidgetController(model=self._model,
                                                client=client),
            parent=parent)

        self.refresh()

    @property
    def model(self):
        return self._model

    @property
    def controller(self):
        return self._controller

    def ui(self):
        super(SkinningWidget, self).ui()

        self._average_falloff_widget = QWidget()
        average_falloff_layout = layouts.VerticalLayout(spacing=2,
                                                        margins=(5, 5, 5, 5))
        self._average_falloff_widget.setLayout(average_falloff_layout)
        self._average_falloff_curve = fallofcurve.FallofCurveWidget(
            parent=self)
        average_falloff_layout.addWidget(self._average_falloff_curve)

        self._mirror_auto_assign_joints_labels_cbx = checkbox.BaseCheckBox(
            'Auto Assign Labels', self)
        self._copy_skin_weights_auto_assign_joints_labels_cbx = checkbox.BaseCheckBox(
            'Auto Assign Labels', self)
        self._transfer_skin_uvs_auto_assign_joints_labels_cbx = checkbox.BaseCheckBox(
            'Auto Assign Labels', self)
        self._clean_skin_mesh_auto_assign_joints_labels_cbx = checkbox.BaseCheckBox(
            'Auto Assign Labels', self)
        self._extract_skin_faces_auto_assign_joints_labels_cbx = checkbox.BaseCheckBox(
            'Auto Assign Labels', self)

        self._distance_widget = QWidget()
        distance_layout = layouts.VerticalLayout(spacing=2,
                                                 margins=(5, 5, 5, 5))
        self._distance_widget.setLayout(distance_layout)
        self._distance_average_cbx = checkbox.BaseCheckBox('On Distance', self)
        self._average_falloff_curve = fallofcurve.FallofCurveWidget(
            parent=self)
        distance_layout.addWidget(self._distance_average_cbx)
        distance_layout.addWidget(self._average_falloff_curve)
        self._fast_delete_cbx = checkbox.BaseCheckBox('Fast Delete', self)

        self._average_falloff_widget.setVisible(False)
        self._mirror_auto_assign_joints_labels_cbx.setVisible(False)
        self._copy_skin_weights_auto_assign_joints_labels_cbx.setVisible(False)
        self._transfer_skin_uvs_auto_assign_joints_labels_cbx.setVisible(False)
        self._clean_skin_mesh_auto_assign_joints_labels_cbx.setVisible(False)
        self._extract_skin_faces_auto_assign_joints_labels_cbx.setVisible(
            False)
        self._distance_widget.setVisible(False)
        self._fast_delete_cbx.setVisible(False)

        self.main_layout.addWidget(self._average_falloff_widget)
        self.main_layout.addWidget(self._mirror_auto_assign_joints_labels_cbx)
        self.main_layout.addWidget(
            self._copy_skin_weights_auto_assign_joints_labels_cbx)
        self.main_layout.addWidget(
            self._transfer_skin_uvs_auto_assign_joints_labels_cbx)
        self.main_layout.addWidget(
            self._clean_skin_mesh_auto_assign_joints_labels_cbx)
        self.main_layout.addWidget(
            self._extract_skin_faces_auto_assign_joints_labels_cbx)
        self.main_layout.addWidget(self._distance_widget)
        self.main_layout.addWidget(self._fast_delete_cbx)

    def setup_signals(self):
        self._mirror_auto_assign_joints_labels_cbx.toggled.connect(
            self._controller.set_mirror_auto_assign_labels)
        self._copy_skin_weights_auto_assign_joints_labels_cbx.toggled.connect(
            self._controller.set_copy_skin_weights_auto_assign_labels)
        self._transfer_skin_uvs_auto_assign_joints_labels_cbx.toggled.connect(
            self._controller.set_transfer_skin_uvs_auto_assign_labels)
        self._clean_skin_mesh_auto_assign_joints_labels_cbx.toggled.connect(
            self._controller.set_clean_skin_mesh_auto_assign_labels)
        self._extract_skin_faces_auto_assign_joints_labels_cbx.toggled.connect(
            self._controller.set_extract_skin_faces_auto_assign_labels)
        self._distance_average_cbx.toggled.connect(
            self._controller.set_distance_average)
        self._fast_delete_cbx.toggled.connect(self._controller.set_fast_delete)
        self._average_falloff_curve.curveUpdated.connect(
            self._controller.set_average_weights_curve_points)

        self._model.mirrorAutoAssignLabelsChanged.connect(
            self._mirror_auto_assign_joints_labels_cbx.setChecked)
        self._model.copySkinWeightsAutoAssignLabelsChanged.connect(
            self._copy_skin_weights_auto_assign_joints_labels_cbx.setChecked)
        self._model.transferSkinUVsAutoAssignLabelsChanged.connect(
            self._transfer_skin_uvs_auto_assign_joints_labels_cbx.setChecked)
        self._model.cleanSkinMeshAutoAssignLabelsChanged.connect(
            self._clean_skin_mesh_auto_assign_joints_labels_cbx.setChecked)
        self._model.extractSkinFacesAutoAssignLabelsChanged.connect(
            self._extract_skin_faces_auto_assign_joints_labels_cbx.setChecked)
        self._model.useDistanceAverageChanged.connect(
            self._distance_average_cbx.setChecked)
        self._model.fastDeleteChanged.connect(self._fast_delete_cbx.setChecked)

    def _check_command_availability(self, command_name):

        client = self._controller.client
        if not client:
            return False

        if command_name == 'br_smooth_weights' or command_name == 'br_transfer_weights':
            for plugin_name in [
                    'rampWeights.mll', 'weightsServer.mll', 'weightDriver.mll',
                    'brSmoothWeights.mll'
            ]:
                client.load_plugin(plugin_name, quiet=True)
            plugin_name = 'brSmoothWeights.mll'
            return client.is_plugin_loaded(plugin_name)
        elif command_name == 'ng_skin_tools':
            plugin_names = ['ngSkinTools2.mll', 'ngSkinTools.mll']
            for plugin_name in plugin_names:
                client.load_plugin(plugin_name, quiet=True)
                if client.is_plugin_loaded(plugin_name):
                    return True
            return False

        return True

    def _on_show_context_menu(self):
        super(SkinningWidget, self)._on_show_context_menu()

        self._average_falloff_curve.update_view()

    def refresh(self):

        self._mirror_auto_assign_joints_labels_cbx.setChecked(
            self._model.mirror_auto_assign_labels)
        self._copy_skin_weights_auto_assign_joints_labels_cbx.setChecked(
            self._model.copy_skin_weights_auto_assign_labels)
        self._transfer_skin_uvs_auto_assign_joints_labels_cbx.setChecked(
            self._model.transfer_uvs_auto_assign_labels)
        self._clean_skin_mesh_auto_assign_joints_labels_cbx.setChecked(
            self._model.clean_skin_mesh_auto_assign_labels)
        self._extract_skin_faces_auto_assign_joints_labels_cbx.setChecked(
            self._model.extract_skin_faces_auto_assign_labels)
        self._distance_average_cbx.setChecked(self._model.use_distance_average)
        self._fast_delete_cbx.setChecked(self._model.fast_delete)

        # NOTE: We do this to force model, to have point list value on startup
        self._controller.set_average_weights_curve_points(
            self._average_falloff_curve.curve_as_points())
    def ui(self):
        super(BaseAlembicImporter, self).ui()

        buttons_layout = layouts.GridLayout()
        self.main_layout.addLayout(buttons_layout)

        shot_name_lbl = label.BaseLabel('Shot Name: ', parent=self)
        self._shot_line = lineedit.BaseLineEdit(parent=self)
        buttons_layout.addWidget(shot_name_lbl, 1, 0, 1, 1, Qt.AlignRight)
        buttons_layout.addWidget(self._shot_line, 1, 1)
        shot_name_lbl.setVisible(False)
        self._shot_line.setVisible(False)

        folder_icon = resources.icon('folder')
        alembic_path_layout = layouts.HorizontalLayout(spacing=2,
                                                       margins=(2, 2, 2, 2))
        alembic_path_widget = QWidget()
        alembic_path_widget.setLayout(alembic_path_layout)
        alembic_path_lbl = label.BaseLabel('Alembic File: ', parent=self)
        self._alembic_path_line = lineedit.BaseLineEdit(parent=self)
        self._alembic_path_line.setReadOnly(True)
        self._alembic_path_btn = buttons.BaseButton(parent=self)
        self._alembic_path_btn.setIcon(folder_icon)
        self._alembic_path_btn.setIconSize(QSize(18, 18))
        self._alembic_path_btn.setStyleSheet(
            "background-color: rgba(255, 255, 255, 0); border: 0px solid rgba(255,255,255,0);"
        )
        alembic_path_layout.addWidget(self._alembic_path_line)
        alembic_path_layout.addWidget(self._alembic_path_btn)
        buttons_layout.addWidget(alembic_path_lbl, 2, 0, 1, 1, Qt.AlignRight)
        buttons_layout.addWidget(alembic_path_widget, 2, 1)

        import_mode_layout = layouts.HorizontalLayout(spacing=2,
                                                      margins=(2, 2, 2, 2))
        import_mode_layout.setContentsMargins(2, 2, 2, 2)
        import_mode_layout.setSpacing(2)
        import_mode_widget = QWidget()
        import_mode_widget.setLayout(import_mode_layout)
        import_mode_lbl = label.BaseLabel('Import mode: ', parent=self)
        self._create_radio = buttons.BaseRadioButton('Create', parent=self)
        self._add_radio = buttons.BaseRadioButton('Add', parent=self)
        self._merge_radio = buttons.BaseRadioButton('Merge', parent=self)
        self._create_radio.setChecked(True)
        import_mode_layout.addWidget(self._create_radio)
        import_mode_layout.addWidget(self._add_radio)
        import_mode_layout.addWidget(self._merge_radio)
        buttons_layout.addWidget(import_mode_lbl, 3, 0, 1, 1, Qt.AlignRight)
        buttons_layout.addWidget(import_mode_widget, 3, 1)
        import_mode_lbl.setVisible(False)
        import_mode_widget.setVisible(False)

        self._auto_display_lbl = label.BaseLabel('Auto Display Smooth?: ',
                                                 parent=self)
        self._auto_smooth_display = checkbox.BaseCheckBox(parent=self)
        self._auto_smooth_display.setChecked(True)
        buttons_layout.addWidget(self._auto_display_lbl, 4, 0, 1, 1,
                                 Qt.AlignRight)
        buttons_layout.addWidget(self._auto_smooth_display, 4, 1)

        if dcc.client().is_maya():
            maya_gpu_cache_lbl = label.BaseLabel(
                'Import Alembic as GPU Cache?', parent=self)
            self._maya_gpu_cache_cbx = checkbox.BaseCheckBox(parent=self)
            self._maya_gpu_cache_cbx.setChecked(True)
            buttons_layout.addWidget(maya_gpu_cache_lbl, 5, 0, 1, 1,
                                     Qt.AlignRight)
            buttons_layout.addWidget(self._maya_gpu_cache_cbx, 5, 1)
        elif dcc.client().is_houdini():
            hou_archive_abc_node_lbl = label.BaseLabel(
                'Import Alembic as Archive?', parent=self)
            self._hou_archive_abc_node_cbx = checkbox.BaseCheckBox(parent=self)
            buttons_layout.addWidget(hou_archive_abc_node_lbl, 5, 0, 1, 1,
                                     Qt.AlignRight)
            buttons_layout.addWidget(self._hou_archive_abc_node_cbx, 5, 1)

        self.main_layout.addStretch()
        self.main_layout.addLayout(dividers.DividerLayout())

        buttons_layout = layouts.HorizontalLayout(spacing=2,
                                                  margins=(2, 2, 2, 2))
        self.main_layout.addLayout(buttons_layout)
        self._import_btn = buttons.BaseButton('Import', parent=self)
        self._import_btn.setIcon(resources.icon('import'))
        self._import_btn.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Preferred)
        self._reference_btn = buttons.BaseButton('Reference', parent=self)
        self._reference_btn.setIcon(resources.icon('reference'))
        self._reference_btn.setSizePolicy(QSizePolicy.Expanding,
                                          QSizePolicy.Preferred)
        buttons_layout.addWidget(self._import_btn)
        buttons_layout.addWidget(self._reference_btn)

        if dcc.client().is_houdini():
            self._reference_btn.setEnabled(False)
Пример #6
0
class OptionsViewer(base.BaseWidget):

    OPTION_LIST_CLASS = optionlist.OptionList

    editModeChanged = Signal(bool)

    def __init__(self, option_object=None, settings=None, parent=None):

        self._option_object = None
        self._settings = settings
        self._edit_mode = False
        self._current_widgets = list()
        self._widget_to_copy = None

        super(OptionsViewer, self).__init__(parent)

        policy = self.sizePolicy()
        policy.setHorizontalPolicy(policy.Expanding)
        policy.setVerticalPolicy(policy.Expanding)
        self.main_layout.setContentsMargins(2, 2, 2, 2)
        self.main_layout.setSpacing(2)
        self.setSizePolicy(policy)

        if option_object:
            self.set_option_object(option_object=option_object)

    def ui(self):
        super(OptionsViewer, self).ui()

        edit_mode_icon = resources.icon('edit')
        move_up_icon = resources.icon('sort_up')
        move_down_icon = resources.icon('sort_down')
        remove_icon = resources.icon('delete')

        self._edit_widget = QWidget()
        top_layout = layouts.HorizontalLayout()
        top_layout.setContentsMargins(0, 0, 0, 0)
        top_layout.setSpacing(2)
        self._edit_widget.setLayout(top_layout)
        self.main_layout.addWidget(self._edit_widget)
        self._edit_mode_btn = buttons.BaseButton(parent=self)
        self._edit_mode_btn.setIcon(edit_mode_icon)
        self._edit_mode_btn.setCheckable(True)
        top_layout.addWidget(self._edit_mode_btn)

        horizontal_separator = QFrame()
        horizontal_separator.setFrameShape(QFrame.VLine)
        horizontal_separator.setFrameShadow(QFrame.Sunken)
        top_layout.addWidget(horizontal_separator)

        self._move_up_btn = buttons.BaseButton(parent=self)
        self.move_down_btn = buttons.BaseButton(parent=self)
        self.remove_btn = buttons.BaseButton(parent=self)
        self._move_up_btn.setIcon(move_up_icon)
        self.move_down_btn.setIcon(move_down_icon)
        self.remove_btn.setIcon(remove_icon)
        self._move_up_btn.setVisible(False)
        self.move_down_btn.setVisible(False)
        self.remove_btn.setVisible(False)
        top_layout.addWidget(self._move_up_btn)
        top_layout.addWidget(self.move_down_btn)
        top_layout.addWidget(self.remove_btn)
        top_layout.addStretch()
        self.main_layout.addWidget(dividers.Divider())

        self._scroll = QScrollArea()
        self._scroll.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        self._scroll.setFocusPolicy(Qt.NoFocus)
        self._scroll.setWidgetResizable(True)
        self.setFocusPolicy(Qt.NoFocus)
        self._options_list = self.OPTION_LIST_CLASS(parent=self)
        self._scroll.setWidget(self._options_list)

        self.main_layout.addWidget(self._scroll)

    def setup_signals(self):
        self._edit_mode_btn.toggled.connect(self._on_edit_mode)
        self._move_up_btn.clicked.connect(self._on_move_up)
        self.move_down_btn.clicked.connect(self._on_move_down)
        self.remove_btn.clicked.connect(self._on_remove)

    def settings(self):
        """
        Returns settings object
        :return: JSONSettings
        """

        return self._settings

    def set_settings(self, settings):
        """
        Sets save widget settings
        :param settings: JSONSettings
        """

        self._settings = settings

    def get_option_object(self):
        """
        Returns the option object linked to this widget
        :return: object
        """

        return self._option_object

    def set_option_object(self, option_object, force_update=True):
        """
        Sets option_object linked to this widget
        :param option_object: object
        :param force_update: bool
        """

        self._option_object = option_object
        self._options_list.set_option_object(option_object)
        if option_object and force_update:
            self.update_options()

    def get_option_type(self):
        """
        Returns option widget type
        :return: str
        """

        return self._option_type

    def is_edit_mode(self):
        """
        Returns whether current option is editable or not
        :return: bool
        """

        return self._edit_mode

    def set_edit_mode(self, flag):
        """
        Sets whether the current option is editable or not
        :param flag: bool
        """

        self._on_edit_mode(flag)

    def is_widget_to_copy(self):
        """
        Returns whether an option widget is being copied or not
        :return: bool
        """

        return self._widget_to_copy

    def set_widget_to_copy(self, widget_to_copy):
        """
        Sets widget that we want to copy
        :param QWidget
        """

        self._widget_to_copy = widget_to_copy

    def show_edit_widget(self):
        self._edit_widget.setVisible(True)
        self._edit_splitter.setVisible(True)

    def hide_edit_widget(self):
        self._edit_widget.setVisible(False)
        self._edit_splitter.setVisible(False)

    def update_options(self):
        """
        Function that updates the current options of the selected task
        """

        if not self._option_object:
            self._options_list.clear_widgets()
            LOGGER.warning(
                'Impossible to update options because option object is not defined!'
            )
            return

        self._options_list.update_options()

    def clear_options(self):
        """
        Clears all the options
        """

        self._options_list.clear_widgets()
        if self._option_object:
            self._option_object = None

    def has_options(self):
        """
        Checks if the current task has options or not
        :return: bool
        """

        if not self._option_object:
            LOGGER.warning(
                'Impossible to check options because option object is not defined!'
            )
            return

        return self._option_object.has_options()

    def _edit_activate(self, edit_value):
        """
        Internal function that updates widget states when edit button is pressed
        :param edit_value: bool
        """

        self._edit_mode = edit_value
        self.move_down_btn.setVisible(edit_value)
        self._move_up_btn.setVisible(edit_value)
        self.remove_btn.setVisible(edit_value)
        if not edit_value:
            self._options_list.clear_selection()
        self._options_list.set_edit(edit_value)

    def _on_edit_mode(self, edit_value):
        """
        Internal callback function that is called when the user presses edit mode button
        :param edit_value: bool
        """

        self._edit_activate(edit_value)
        self.editModeChanged.emit(edit_value)

    def _on_move_up(self):
        """
        Internal callback function that is called when the user pressed move up button
        Move selected items up in the list
        """

        widgets = self._current_widgets
        if not widgets:
            return

        widgets = self._options_list.sort_widgets(widgets,
                                                  widgets[0].get_parent())
        if not widgets:
            return
        for w in widgets:
            w.move_up()

    def _on_move_down(self):
        """
        Internal callback function that is called when the user pressed move down button
        Move selected items down in the list
        """

        widgets = self._current_widgets
        if not widgets:
            return

        widgets = self._options_list.sort_widgets(widgets,
                                                  widgets[0].get_parent())
        if not widgets:
            return
        for w in widgets:
            w.move_down()

    def _on_remove(self):
        """
        Internal callback function that is called when the user pressed remove button
        Remove selected options
        """

        widgets = self._current_widgets
        if not widgets:
            return

        widgets = self._options_list.sort_widgets(widgets,
                                                  widgets[0].get_parent())
        if not widgets:
            return
        for w in widgets:
            w.remove()
Пример #7
0
class CollapsableGroup(BaseGroup, object):
    def __init__(self, name='', parent=None, collapsable=True):
        self._collapsable = collapsable
        super(CollapsableGroup,
              self).__init__(name, parent, layout_orientation=Qt.Horizontal)

    def ui(self):
        super(CollapsableGroup, self).ui()

        self._base_widget = QWidget()
        if self._layout_orientation == Qt.Vertical:
            manager_layout = layouts.VerticalLayout(spacing=2,
                                                    margins=(4, 4, 4, 4))
        else:
            manager_layout = layouts.HorizontalLayout(spacing=2,
                                                      margins=(4, 4, 4, 4))
        manager_layout.setAlignment(Qt.AlignCenter)
        self._base_widget.setLayout(manager_layout)
        self.main_layout.addWidget(self._base_widget)
        self.main_layout = manager_layout

    def mousePressEvent(self, event):
        super(CollapsableGroup, self).mousePressEvent(event)

        if not event.button() == Qt.LeftButton:
            return

        if self._collapsable:
            if event.y() < 30:
                if self._base_widget.isHidden():
                    self.expand_group()
                else:
                    self.collapse_group()

    def set_collapsable(self, flag):
        """
        Sets if the group can be collapsed or not
        :param flag: bool
        """

        self._collapsable = flag

    def set_title(self, title):
        if not title.startswith('+ '):
            title = '+ ' + title
        self.setTitle(title)

    def expand_group(self):
        """
        Expands the content of the group
        """

        self.setVisible(True)
        title = self.title()
        title = title.replace('+', '-')
        self.setTitle(title)

    def collapse_group(self):
        """
        Collapse the content of the group
        """

        self._base_widget.setVisible(False)
        title = self.title()
        title = title.replace('-', '+')
        self.setTitle(title)
Пример #8
0
    def _build_ui(self):
        layout = QGridLayout()

        layout.addWidget(QLabel("center of rotation:"), 0, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        self.cor_button = QComboBox()
        self.cor_button.addItems(
            ["automatic", "select atoms", "view's center of rotation"])
        layout.addWidget(self.cor_button, 0, 1, 1, 1, Qt.AlignTop)

        self.set_cor_selection = QPushButton("set selection")
        self.cor_button.currentTextChanged.connect(
            lambda t, widget=self.set_cor_selection: widget.setEnabled(
                t == "select atoms"))
        self.set_cor_selection.clicked.connect(self.manual_cor)
        layout.addWidget(self.set_cor_selection, 0, 2, 1, 1, Qt.AlignTop)
        self.set_cor_selection.setEnabled(False)

        layout.addWidget(QLabel("rotation vector:"), 1, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        self.vector_option = QComboBox()
        self.vector_option.addItems([
            "axis", "view axis", "bond", "perpendicular to plane",
            "centroid of atoms", "custom"
        ])
        layout.addWidget(self.vector_option, 1, 1, 1, 1, Qt.AlignVCenter)

        vector = QWidget()
        vector.setToolTip("vector will be normalized before rotating")
        vector_layout = QHBoxLayout(vector)
        vector_layout.setContentsMargins(0, 0, 0, 0)
        self.vector_x = QDoubleSpinBox()
        self.vector_y = QDoubleSpinBox()
        self.vector_z = QDoubleSpinBox()
        self.vector_z.setValue(1.0)
        for c, t in zip([self.vector_x, self.vector_y, self.vector_z],
                        [" x", " y", " z"]):
            c.setSingleStep(0.01)
            c.setRange(-100, 100)
            # c.setSuffix(t)
            c.valueChanged.connect(self.show_rot_vec)
            vector_layout.addWidget(c)

        layout.addWidget(vector, 1, 2, 1, 1, Qt.AlignTop)
        vector.setVisible(self.vector_option.currentText() == "custom")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=vector: widget.setVisible(text == "custom"))

        self.view_axis = QComboBox()
        self.view_axis.addItems(["z", "y", "x"])
        layout.addWidget(self.view_axis, 1, 2, 1, 1, Qt.AlignTop)
        self.view_axis.setVisible(
            self.vector_option.currentText() == "view axis")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.view_axis: widget.setVisible(text ==
                                                                  "view axis"))

        self.axis = QComboBox()
        self.axis.addItems(["z", "y", "x"])
        layout.addWidget(self.axis, 1, 2, 1, 1, Qt.AlignTop)
        self.axis.setVisible(self.vector_option.currentText() == "axis")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.axis: widget.setVisible(text == "axis"))

        self.bond_button = QPushButton("set selected bond")
        self.bond_button.clicked.connect(self.set_bonds)
        layout.addWidget(self.bond_button, 1, 2, 1, 1, Qt.AlignTop)
        self.bond_button.setVisible(self.vector_option.currentText() == "bond")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.bond_button: widget.setVisible(text ==
                                                                    "bond"))

        self.perp_button = QPushButton("set selected atoms")
        self.perp_button.clicked.connect(self.set_perpendicular)
        layout.addWidget(self.perp_button, 1, 2, 1, 1, Qt.AlignTop)
        self.perp_button.setVisible(
            self.vector_option.currentText() == "perpendicular to plane")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.perp_button: widget.setVisible(
                text == "perpendicular to plane"))

        self.group_button = QPushButton("set selected atoms")
        self.group_button.clicked.connect(self.set_group)
        layout.addWidget(self.group_button, 1, 2, 1, 1, Qt.AlignTop)
        self.group_button.setVisible(
            self.vector_option.currentText() == "centroid of atoms")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.group_button: widget.setVisible(
                text == "centroid of atoms"))

        layout.addWidget(QLabel("angle:"), 2, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        self.angle = QDoubleSpinBox()
        self.angle.setRange(-360, 360)
        self.angle.setSingleStep(5)
        self.angle.setSuffix("°")
        layout.addWidget(self.angle, 2, 1, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        layout.addWidget(QLabel("preview rotation axis:"), 3, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)
        self.display_rot_vec = QCheckBox()
        self.display_rot_vec.setCheckState(Qt.Checked)
        self.display_rot_vec.stateChanged.connect(self.show_rot_vec)
        layout.addWidget(self.display_rot_vec, 3, 1, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        rotate_button = QPushButton("rotate selected atoms")
        rotate_button.clicked.connect(self.do_rotate)
        layout.addWidget(rotate_button, 4, 0, 1, 3, Qt.AlignTop)
        self.rotate_button = rotate_button

        self.status_bar = QStatusBar()
        self.status_bar.setSizeGripEnabled(False)
        layout.addWidget(self.status_bar, 5, 0, 1, 3, Qt.AlignTop)

        self.vector_option.currentTextChanged.connect(self.show_auto_status)
        self.cor_button.currentIndexChanged.connect(
            lambda *args: self.show_auto_status("select atoms"))

        self.cor_button.currentIndexChanged.connect(self.show_rot_vec)
        self.set_cor_selection.clicked.connect(self.show_rot_vec)
        self.vector_option.currentIndexChanged.connect(self.show_rot_vec)
        self.axis.currentIndexChanged.connect(self.show_rot_vec)
        self.view_axis.currentIndexChanged.connect(self.show_rot_vec)
        self.bond_button.clicked.connect(self.show_rot_vec)
        self.perp_button.clicked.connect(self.show_rot_vec)
        self.group_button.clicked.connect(self.show_rot_vec)

        layout.setRowStretch(0, 0)
        layout.setRowStretch(1, 0)
        layout.setRowStretch(2, 0)
        layout.setRowStretch(3, 0)
        layout.setRowStretch(4, 0)
        layout.setRowStretch(5, 1)

        layout.setColumnStretch(0, 0)
        layout.setColumnStretch(1, 1)
        layout.setColumnStretch(2, 1)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)
Пример #9
0
    def _build_ui(self):
        layout = QGridLayout()

        tabs = QTabWidget()
        calc_widget = QWidget()
        calc_layout = QFormLayout(calc_widget)
        settings_widget = QWidget()
        settings_layout = QFormLayout(settings_widget)
        steric_map_widget = QWidget()
        steric_layout = QFormLayout(steric_map_widget)
        cutout_widget = QWidget()
        vol_cutout_layout = QFormLayout(cutout_widget)
        layout.addWidget(tabs)

        tabs.addTab(calc_widget, "calculation")
        tabs.addTab(settings_widget, "settings")
        tabs.addTab(steric_map_widget, "steric map")
        tabs.addTab(cutout_widget, "volume cutout")

        self.radii_option = QComboBox()
        self.radii_option.addItems(["Bondi", "UMN"])
        ndx = self.radii_option.findText(self.settings.radii, Qt.MatchExactly)
        self.radii_option.setCurrentIndex(ndx)
        settings_layout.addRow("radii:", self.radii_option)

        self.scale = QDoubleSpinBox()
        self.scale.setValue(self.settings.vdw_scale)
        self.scale.setSingleStep(0.01)
        self.scale.setRange(1., 1.5)
        settings_layout.addRow("VDW scale:", self.scale)

        set_ligand_atoms = QPushButton("set ligands to current selection")
        set_ligand_atoms.clicked.connect(self.set_ligand_atoms)
        set_ligand_atoms.setToolTip(
            "specify atoms to use in calculation\n" +
            "by default, all atoms will be used unless a single center is specified\n"
            +
            "in the case of a single center, all atoms except the center is used"
        )
        calc_layout.addRow(set_ligand_atoms)
        self.set_ligand_atoms = set_ligand_atoms

        self.radius = QDoubleSpinBox()
        self.radius.setValue(self.settings.center_radius)
        self.radius.setSuffix(" \u212B")
        self.radius.setDecimals(1)
        self.radius.setSingleStep(0.1)
        self.radius.setRange(1., 15.)
        settings_layout.addRow("radius around center:", self.radius)

        self.method = QComboBox()
        self.method.addItems(["Lebedev", "Monte-Carlo"])
        self.method.setToolTip("Lebedev: deterministic method\n" +
                               "Monte-Carlo: non-deterministic method")
        ndx = self.method.findText(self.settings.method, Qt.MatchExactly)
        self.method.setCurrentIndex(ndx)
        settings_layout.addRow("integration method:", self.method)

        leb_widget = QWidget()
        leb_layout = QFormLayout(leb_widget)
        leb_layout.setContentsMargins(0, 0, 0, 0)

        self.radial_points = QComboBox()
        self.radial_points.addItems(["20", "32", "64", "75", "99", "127"])
        self.radial_points.setToolTip(
            "more radial points will give more accurate results, but integration will take longer"
        )
        ndx = self.radial_points.findText(self.settings.radial_points,
                                          Qt.MatchExactly)
        self.radial_points.setCurrentIndex(ndx)
        leb_layout.addRow("radial points:", self.radial_points)

        self.angular_points = QComboBox()
        self.angular_points.addItems([
            "110", "194", "302", "590", "974", "1454", "2030", "2702", "5810"
        ])
        self.angular_points.setToolTip(
            "more angular points will give more accurate results, but integration will take longer"
        )
        ndx = self.angular_points.findText(self.settings.angular_points,
                                           Qt.MatchExactly)
        self.angular_points.setCurrentIndex(ndx)
        leb_layout.addRow("angular points:", self.angular_points)

        settings_layout.addRow(leb_widget)

        mc_widget = QWidget()
        mc_layout = QFormLayout(mc_widget)
        mc_layout.setContentsMargins(0, 0, 0, 0)

        self.min_iter = QSpinBox()
        self.min_iter.setValue(self.settings.minimum_iterations)
        self.min_iter.setRange(0, 10000)
        self.min_iter.setToolTip(
            "each iteration is 3000 points\n" +
            "iterations continue until convergence criteria are met")
        mc_layout.addRow("minimum interations:", self.min_iter)

        settings_layout.addRow(mc_widget)

        if self.settings.method == "Lebedev":
            mc_widget.setVisible(False)
        elif self.settings.method == "Monte-Carlo":
            leb_widget.setVisible(False)

        self.report_component = QComboBox()
        self.report_component.addItems(["total", "quadrants", "octants"])
        ndx = self.report_component.findText(self.settings.report_component,
                                             Qt.MatchExactly)
        self.report_component.setCurrentIndex(ndx)
        settings_layout.addRow("report volume:", self.report_component)

        self.use_scene = QCheckBox()
        self.use_scene.setChecked(self.settings.use_scene)
        self.use_scene.setToolTip(
            "quadrants/octants will use the orientation the molecule is displayed in"
        )
        settings_layout.addRow("use display orientation:", self.use_scene)

        self.method.currentTextChanged.connect(
            lambda text, widget=leb_widget: widget.setVisible(text == "Lebedev"
                                                              ))
        self.method.currentTextChanged.connect(
            lambda text, widget=mc_widget: widget.setVisible(text ==
                                                             "Monte-Carlo"))

        self.use_centroid = QCheckBox()
        self.use_centroid.setChecked(self.settings.use_centroid)
        self.use_centroid.setToolTip(
            "place the center between selected atoms\n" +
            "might be useful for polydentate ligands")
        calc_layout.addRow("use centroid of centers:", self.use_centroid)

        self.steric_map = QCheckBox()
        self.steric_map.setChecked(self.settings.steric_map)
        self.steric_map.setToolTip(
            "produce a 2D projection of steric bulk\ncauses buried volume to be reported for individual quadrants"
        )
        steric_layout.addRow("create steric map:", self.steric_map)

        self.num_pts = QSpinBox()
        self.num_pts.setRange(25, 250)
        self.num_pts.setValue(self.settings.num_pts)
        self.num_pts.setToolTip("number of points along x and y axes")
        steric_layout.addRow("number of points:", self.num_pts)

        self.include_vbur = QCheckBox()
        self.include_vbur.setChecked(self.settings.include_vbur)
        steric_layout.addRow("label quadrants with %V<sub>bur</sub>",
                             self.include_vbur)

        self.map_shape = QComboBox()
        self.map_shape.addItems(["circle", "square"])
        ndx = self.map_shape.findText(self.settings.map_shape, Qt.MatchExactly)
        self.map_shape.setCurrentIndex(ndx)
        steric_layout.addRow("map shape:", self.map_shape)

        self.auto_minmax = QCheckBox()
        self.auto_minmax.setChecked(self.settings.auto_minmax)
        steric_layout.addRow("automatic min. and max.:", self.auto_minmax)

        self.map_min = QDoubleSpinBox()
        self.map_min.setRange(-15., 0.)
        self.map_min.setSuffix(" \u212B")
        self.map_min.setSingleStep(0.1)
        self.map_min.setValue(self.settings.map_min)
        steric_layout.addRow("minimum value:", self.map_min)

        self.map_max = QDoubleSpinBox()
        self.map_max.setRange(0., 15.)
        self.map_max.setSuffix(" \u212B")
        self.map_max.setSingleStep(0.1)
        self.map_max.setValue(self.settings.map_max)
        steric_layout.addRow("maximum value:", self.map_max)

        self.num_pts.setEnabled(self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.num_pts: widget.setEnabled(state == Qt.
                                                                 Checked))

        self.include_vbur.setEnabled(self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.include_vbur: widget.setEnabled(
                state == Qt.Checked))

        self.map_shape.setEnabled(self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.map_shape: widget.setEnabled(state == Qt.
                                                                   Checked))

        self.auto_minmax.setEnabled(self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.auto_minmax: widget.setEnabled(
                state == Qt.Checked))

        self.map_min.setEnabled(not self.settings.auto_minmax
                                and self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.map_min, widget2=self.auto_minmax: widget
            .setEnabled(state == Qt.Checked and not widget2.isChecked()))
        self.auto_minmax.stateChanged.connect(
            lambda state, widget=self.map_min, widget2=self.steric_map: widget.
            setEnabled(not state == Qt.Checked and widget2.isChecked()))

        self.map_max.setEnabled(not self.settings.auto_minmax
                                and self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.map_max, widget2=self.auto_minmax: widget
            .setEnabled(state == Qt.Checked and not widget2.isChecked()))
        self.auto_minmax.stateChanged.connect(
            lambda state, widget=self.map_max, widget2=self.steric_map: widget.
            setEnabled(not state == Qt.Checked and widget2.isChecked()))

        self.display_cutout = QComboBox()
        self.display_cutout.addItems(["no", "free", "buried"])
        ndx = self.display_cutout.findText(self.settings.display_cutout,
                                           Qt.MatchExactly)
        self.display_cutout.setCurrentIndex(ndx)
        self.display_cutout.setToolTip("show free or buried volume")
        vol_cutout_layout.addRow("display volume:", self.display_cutout)

        self.point_spacing = QDoubleSpinBox()
        self.point_spacing.setDecimals(3)
        self.point_spacing.setRange(0.01, 0.5)
        self.point_spacing.setSingleStep(0.005)
        self.point_spacing.setSuffix(" \u212B")
        self.point_spacing.setValue(self.settings.point_spacing)
        self.point_spacing.setToolTip(
            "distance between points on cutout\n" +
            "smaller spacing will narrow gaps, but increase time to create the cutout"
        )
        vol_cutout_layout.addRow("point spacing:", self.point_spacing)

        self.intersection_scale = QDoubleSpinBox()
        self.intersection_scale.setDecimals(2)
        self.intersection_scale.setRange(1., 10.)
        self.intersection_scale.setSingleStep(0.5)
        self.intersection_scale.setSuffix("x")
        self.intersection_scale.setToolTip(
            "relative density of points where VDW radii intersect\n" +
            "higher density will narrow gaps, but increase time to create cutout"
        )
        self.intersection_scale.setValue(self.settings.intersection_scale)
        vol_cutout_layout.addRow("intersection density:",
                                 self.intersection_scale)

        self.cutout_labels = QComboBox()
        self.cutout_labels.addItems(["none", "quadrants", "octants"])
        ndx = self.cutout_labels.findText(self.settings.cutout_labels,
                                          Qt.MatchExactly)
        self.cutout_labels.setCurrentIndex(ndx)
        vol_cutout_layout.addRow("label sections:", self.cutout_labels)

        self.point_spacing.setEnabled(self.settings.display_cutout != "no")
        self.intersection_scale.setEnabled(
            self.settings.display_cutout != "no")
        self.cutout_labels.setEnabled(self.settings.display_cutout != "no")

        self.display_cutout.currentTextChanged.connect(
            lambda text, widget=self.point_spacing: widget.setEnabled(text !=
                                                                      "no"))
        self.display_cutout.currentTextChanged.connect(
            lambda text, widget=self.intersection_scale: widget.setEnabled(
                text != "no"))
        self.display_cutout.currentTextChanged.connect(
            lambda text, widget=self.cutout_labels: widget.setEnabled(text !=
                                                                      "no"))

        calc_vbur_button = QPushButton(
            "calculate % buried volume for selected centers")
        calc_vbur_button.clicked.connect(self.calc_vbur)
        calc_layout.addRow(calc_vbur_button)
        self.calc_vbur_button = calc_vbur_button

        remove_vbur_button = QPushButton(
            "remove % buried volume visualizations")
        remove_vbur_button.clicked.connect(self.del_vbur)
        vol_cutout_layout.addRow(remove_vbur_button)

        self.table = QTableWidget()
        self.table.setColumnCount(3)
        self.table.setHorizontalHeaderLabels(['model', 'center', '%Vbur'])
        self.table.setSelectionBehavior(QTableWidget.SelectRows)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.table.resizeColumnToContents(0)
        self.table.resizeColumnToContents(1)
        self.table.resizeColumnToContents(2)
        self.table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Interactive)
        self.table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Interactive)
        self.table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)
        calc_layout.addRow(self.table)

        menu = QMenuBar()

        export = menu.addMenu("&Export")

        clear = QAction("Clear data table", self.tool_window.ui_area)
        clear.triggered.connect(self.clear_table)
        export.addAction(clear)

        copy = QAction("&Copy CSV to clipboard", self.tool_window.ui_area)
        copy.triggered.connect(self.copy_csv)
        shortcut = QKeySequence(Qt.CTRL + Qt.Key_C)
        copy.setShortcut(shortcut)
        export.addAction(copy)
        self.copy = copy

        save = QAction("&Save CSV...", self.tool_window.ui_area)
        save.triggered.connect(self.save_csv)
        #this shortcut interferes with main window's save shortcut
        #I've tried different shortcut contexts to no avail
        #thanks Qt...
        #shortcut = QKeySequence(Qt.CTRL + Qt.Key_S)
        #save.setShortcut(shortcut)
        #save.setShortcutContext(Qt.WidgetShortcut)
        export.addAction(save)

        delimiter = export.addMenu("Delimiter")

        comma = QAction("comma", self.tool_window.ui_area, checkable=True)
        comma.setChecked(self.settings.delimiter == "comma")
        comma.triggered.connect(lambda *args, delim="comma": self.settings.
                                __setattr__("delimiter", delim))
        delimiter.addAction(comma)

        tab = QAction("tab", self.tool_window.ui_area, checkable=True)
        tab.setChecked(self.settings.delimiter == "tab")
        tab.triggered.connect(lambda *args, delim="tab": self.settings.
                              __setattr__("delimiter", delim))
        delimiter.addAction(tab)

        space = QAction("space", self.tool_window.ui_area, checkable=True)
        space.setChecked(self.settings.delimiter == "space")
        space.triggered.connect(lambda *args, delim="space": self.settings.
                                __setattr__("delimiter", delim))
        delimiter.addAction(space)

        semicolon = QAction("semicolon",
                            self.tool_window.ui_area,
                            checkable=True)
        semicolon.setChecked(self.settings.delimiter == "semicolon")
        semicolon.triggered.connect(lambda *args, delim="semicolon": self.
                                    settings.__setattr__("delimiter", delim))
        delimiter.addAction(semicolon)

        add_header = QAction("&Include CSV header",
                             self.tool_window.ui_area,
                             checkable=True)
        add_header.setChecked(self.settings.include_header)
        add_header.triggered.connect(self.header_check)
        export.addAction(add_header)

        comma.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        comma.triggered.connect(
            lambda *args, action=space: action.setChecked(False))
        comma.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        tab.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        tab.triggered.connect(
            lambda *args, action=space: action.setChecked(False))
        tab.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        space.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        space.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        space.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        semicolon.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        semicolon.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        semicolon.triggered.connect(
            lambda *args, action=space: action.setChecked(False))

        menu.setNativeMenuBar(False)
        self._menu = menu
        layout.setMenuBar(menu)
        menu.setVisible(True)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)