def __init__(self):
        super(DockableWindow, self).__init__()

        self.shown = False

        # Build our *.ui files into qt/resources, so the subclass can load its layout.
        qt_helpers.compile_all_layouts()

        # How do we make our window handle global hotkeys?
        undo = Qt.QAction('Undo', self)
        undo.setShortcut(Qt.Qt.CTRL + Qt.Qt.Key_Z)
        undo.triggered.connect(lambda: pm.undo())
        self.addAction(undo)

        redo = Qt.QAction('Redo', self)
        redo.setShortcut(Qt.Qt.CTRL + Qt.Qt.Key_Y)
        redo.triggered.connect(lambda: pm.redo(redo=True))
        self.addAction(redo)

        style = ''
        # Maya's checkbox style makes the checkbox invisible when it's deselected,
        # which makes it impossible to tell that there's even a checkbox there to
        # click.  Adjust the background color to fix this.
        style += 'QTreeView::indicator:unchecked { background-color: #000; }'

        # Make tree and list view items slightly larger by default.
        style += 'QTreeView::item { height: 20px; }'
        style += 'QListView::item { height: 26px; }'
        self.setStyleSheet(self.styleSheet() + style)
Exemple #2
0
    def __init__(self):
        super(KeyframeNamingWindow, self).__init__()

        self.callback_ids = om.MCallbackIdArray()
        self._reregister_callback_queued = False

        self.frames_in_list = []
        self._currently_refreshing = False
        self._currently_setting_selection = False
        self._listening_to_singleton = None
        self._listening_to_anim_curve = None

        self.time_change_listener = maya_helpers.TimeChangeListener(self._time_changed)

        from .qt_generated import keyframe_naming
        reload(keyframe_naming)

        self._ui = keyframe_naming.Ui_keyframe_naming()
        self._ui.setupUi(self)

        self._ui.removeFrame.clicked.connect(self.delete_selected_frame)
        self._ui.renameFrame.clicked.connect(self.rename_selected_frame)
        self._ui.addFrame.clicked.connect(self.add_new_frame)
        self._ui.frameList.itemDelegate().commitData.connect(self.frame_name_edited)
        self._ui.frameList.itemDelegate().closeEditor.connect(self.name_editor_closed)
        self._ui.frameList.itemSelectionChanged.connect(self.selected_frame_changed)
        self._ui.frameList.itemClicked.connect(self.selected_frame_changed)
        self._ui.frameList.setContextMenuPolicy(Qt.Qt.CustomContextMenu)

        def context_menu(pos):
            # Activate the context menu for the selected item.
            item = self._ui.frameList.itemAt(pos)
            if item is None:
                return

            keyframe_context_menu = self._create_keyframe_context_menu(item)
            action = keyframe_context_menu.exec_(self._ui.frameList.mapToGlobal(pos))
            
        self._ui.frameList.customContextMenuRequested.connect(context_menu)

        # Create the menu.  Why can't this be done in the designer?
        menu_bar = Qt.QMenuBar()
        self.layout().setMenuBar(menu_bar)

        edit_menu = menu_bar.addMenu('Edit')
        menu_select_naming_node = Qt.QAction('Select zKeyframeNaming node', self)
        menu_select_naming_node.setStatusTip('Select the zKeyframeNaming node, to edit keyframes in the graph editor')
        menu_select_naming_node.triggered.connect(select_naming_node)
        edit_menu.addAction(menu_select_naming_node)
        
        add_arnold_attribute = Qt.QAction('Add Arnold attribute', self)
        add_arnold_attribute.setStatusTip('Add a custom Arnold attribute to export the current frame name to rendered EXR files')
        add_arnold_attribute.triggered.connect(connect_to_arnold)
        edit_menu.addAction(add_arnold_attribute)

        self.installEventFilter(self)
        self._ui.frameList.installEventFilter(self)
Exemple #3
0
    def refresh(self):
        if not self.shown:
            return

        # Don't refresh while editing.
        if self._ui.frameList.state() == Qt.QAbstractItemView.EditingState:
            return

        self._currently_refreshing = True
        try:
            all_keys = get_all_keys()
            all_names = get_all_names()
            self._ui.frameList.clear()
            self.frames_in_list = []

            # Add keys in chronological order.
            for frame in sorted(all_keys.keys()):
                idx = all_keys[frame]
                name = all_names.get(idx, '')
                item = Qt.QListWidgetItem(name)
                item.frame = frame
                item.setFlags(item.flags() | Qt.Qt.ItemIsEditable)

                self._ui.frameList.addItem(item)

                self.frames_in_list.append(frame)

            self.set_selected_frame_from_current_time()
        finally:
            self._currently_refreshing = False
Exemple #4
0
    def startDrag(self, supportedActions):
        """
        This mess is only needed because apparently there's no way to simply tell QT
        that you don't want a big picture of the text you're dragging covering up the
        drag so you can't see where you're dropping.  QT is not a good API.
        """
        drag = Qt.QDrag(self)
        data = self.model().mimeData(self.selectedIndexes())
        drag.setMimeData(data)
        drag.exec_(supportedActions, Qt.Qt.MoveAction)

        super(NoDragPreviewMixin, self).startDrag(supportedActions)
Exemple #5
0
    def _create_keyframe_context_menu(self, item):
        keyframe_context_menu = Qt.QMenu()
        time_slider_start = keyframe_context_menu.addAction('Set time slider start')
        time_slider_start.triggered.connect(lambda: pm.playbackOptions(min=item.frame))

        time_slider_end = keyframe_context_menu.addAction('Set time slider end')
        time_slider_end.triggered.connect(lambda: pm.playbackOptions(max=item.frame))

        time_render_start = keyframe_context_menu.addAction('Set render start')
        time_render_start.triggered.connect(lambda: pm.PyNode('defaultRenderGlobals').startFrame.set(item.frame))

        time_render_end = keyframe_context_menu.addAction('Set render end')
        time_render_end.triggered.connect(lambda: pm.PyNode('defaultRenderGlobals').endFrame.set(item.frame))

        return keyframe_context_menu
        def add_controllers_recursively(node, parent, level=0):
            # Controllers don't actually support cycles (they seem to, but crash when you
            # try to use them), but the DG connections can still have cycles.  Check for
            # this to avoid infinite recursion.
            if node in seen_nodes:
                log.warning('Controller cycle: %s', node)
                return
            seen_nodes.add(node)

            item = Qt.QTreeWidgetItem(parent)

            item.controller_node = node
            self.controllers_to_items[node] = item

            item.controller_object = get_controller_object(node)
            name = item.controller_object.nodeName(
            ) if item.controller_object else item.controller_node.nodeName()

            item.setText(0, name)
            item.setFlags(Qt.Qt.ItemIsEnabled | Qt.Qt.ItemIsSelectable
                          | Qt.Qt.ItemIsDragEnabled | Qt.Qt.ItemIsDropEnabled)

            if parent is self.ui.controllerTree:
                self.ui.controllerTree.addTopLevelItem(item)
            else:
                parent.addChild(item)

            # By default, expand everything except the first level.  This way, top-level
            # controllers are closed to keep the list small, but you don't have to expand
            # controllers one by one to find things.  It would be nicer to support shift-clicking
            # the expansion button like Maya does, but this seems to be a pain with QT.
            #
            # Expand the node if it's new or if it was expanded previously.
            expand_by_default = level != 0
            if old_nodes.get(node, expand_by_default):
                self.ui.controllerTree.expandItem(item)

            # If this controller was selected, reselect it.
            if node in selected_controllers:
                self.ui.controllerTree.setCurrentItem(item)

            # Add the controller's children.
            children = node.children.listConnections(s=True, d=False)
            for child in children:
                add_controllers_recursively(child, item, level + 1)
Exemple #7
0
    def populate_transform_list(self):
        self.currently_refreshing = True
        try:
            # Remember which indexes were selected.
            old_selection = [
                idx.row() for idx in self.ui.transformList.selectedIndexes()
            ]

            if self.current_node is None:
                new_transform_list = []
            else:
                curve = CreateCurve(self.current_node)
                new_transform_list = curve.get_transforms()

            self.ui.transformList.clear()
            self.current_transform_list = new_transform_list

            for transform_attr in new_transform_list:
                # The input connection usually goes to worldMatrix[0].  Only show the attribute if
                # it's connected to something else.
                name = transform_attr.name()
                if transform_attr.attrName(longName=True) == 'worldMatrix':
                    # Work around a PyMEL consistency: node.nodeName() returns just the node name,
                    # but attr.nodeName() returns the disambiguated name.  We just want the node
                    # name.
                    name = transform_attr.node().nodeName()
                else:
                    name = '%s.%s' % (transform_attr.node().nodeName(),
                                      transform_attr.attrName(longName=True))

                item = Qt.QListWidgetItem(name)
                item.transform_attr = transform_attr
                self.ui.transformList.addItem(item)

            # Reselect the old selection.  These might not point at the same things, but it prevents
            # losing the selection whenever we refresh.
            self.select_transform_rows(old_selection)

            # Make sure the "remove from curve" button state is enabled after we change the list.
            self.refresh_after_transform_selection_changed()

        finally:
            self.currently_refreshing = False
Exemple #8
0
 def dragMoveEvent(self, event):
     # In QT 4, QAbstractItemViewPrivate::position normally gives a margin of 2 pixels
     # on the top and bottom for AboveItem and BelowItem, which is much too small and makes
     # dragging painful.  In QT 5 it gives a fraction of the heigh, which is much more usable.
     # Maya uses QT 5, and its built-in UIs like the shape editor have QT 5's behavior, but
     # for some reason QTreeView, etc. still have QT 4's behavior.
     #
     # Work around this by looking at the event position.  If it's in a position where an
     # above or below drag should be happening, snap the drag position to the boundary so
     # it's treated as an above or below drag.
     pos = event.pos()
     index = self.indexAt(event.pos())
     if index.isValid():
         rect = self.visualRect(index)
         margin = round(float(rect.height()) / 5.5)
         margin = min(max(margin, 2), 12)
         if pos.y() < margin + rect.top() :
             # Move the drag position to the top of the item, forcing AboveItem.
             pos.setY(rect.top())
         elif pos.y() > rect.bottom() - margin:
             # Move the drag position to the bottom of the item, forcing BelowItem.
             pos.setY(rect.bottom())
         elif rect.contains(pos, True):
             # Move the drag position to the center of the item, forcing OnItem.
             pos.setY((rect.bottom() + rect.top()) / 2)
         
     # Create a new, equivalent QDragMoveEvent with our adjusted position.
     #
     # The QT docs say not to construct these.  But, it doesn't offer any alternative,
     # there's no setPos, and this works fine.
     event2 = Qt.QDragMoveEvent(
             pos,
             event.dropAction(),
             event.mimeData(),
             event.mouseButtons(),
             event.keyboardModifiers(),
             event.type())
     super(DragFromMayaMixin, self).dragMoveEvent(event2)
    def refresh(self):
        """
        Update the UI to reflect the node's current values.
        """
        if not self.shown or self.weight_node is None:
            return

        # This is a little tricky.  If the selected node is deleted, _weight_nodes_changed
        # will be called and queue refresh_weight_node_list.  However, other callbacks will
        # also happen and cause refresh to be called first, so we'll try to refresh a deleted
        # node before we update to notice that it's gone.  Check for this before refreshing
        # the rest of the UI.
        if not self.weight_node.exists():
            self.refresh_weight_node_list()
            if self.weight_node is None:
                return

        self._currently_refreshing = True
        try:
            solo = self.weight_node.attr('solo').get()

            if solo != 0:
                selection = 0 if solo == 1 else 1
                main_weight = 1

                self.ui.selectionBar.setEnabled(False)
                self.ui.keySelection.setEnabled(False)
                self.ui.mainWeightBar.setEnabled(False)
                self.ui.keyMainWeight.setEnabled(False)
            else:
                selection = self.weight_node.attr('selection').get()
                main_weight = self.weight_node.attr('mainWeight').get()

                self.ui.selectionBar.setEnabled(True)
                self.ui.keySelection.setEnabled(True)
                self.ui.mainWeightBar.setEnabled(True)
                self.ui.keyMainWeight.setEnabled(True)

            self.ui.selectionBar.setValue(int(selection * 1000))

            self.ui.mainWeightBar.setValue(int(main_weight * 1000))

            palette = Qt.QPalette()
            color = Qt.QColor(0, 255 * (1 - selection), 0, 255)
            palette.setColor(Qt.QPalette.Background, color)
            self.ui.shape1WeightColor.setAutoFillBackground(True)
            self.ui.shape1WeightColor.setPalette(palette)

            palette = Qt.QPalette()
            color = Qt.QColor(0, 255 * selection, 0, 255)
            palette.setColor(Qt.QPalette.Background, color)
            self.ui.shape2WeightColor.setAutoFillBackground(True)
            self.ui.shape2WeightColor.setPalette(palette)

            solo = self.weight_node.attr('solo').get()
            self.ui.soloShape1.setChecked(solo == 1)
            self.ui.soloShape2.setChecked(solo == 2)

            self._refresh_dropdowns()
        finally:
            self._currently_refreshing = False
Exemple #10
0
class DragFromMayaMixin(object):
    """
    This adds support for dragging Maya nodes and attributes into a widget.
    """
    # Note that the arguments for these actually depend on the base class type, and can be eg. a
    # QListWidgetItem.

    # source, target (or None for DropIndicatorPosition.OnViewport), dropIndicatorPosition
    dragged_internally = Qt.Signal(Qt.QTreeWidgetItem, Qt.QAbstractItemView.DropIndicatorPosition)

    # nodes, target (or None for DropIndicatorPosition.OnViewport), dropIndicatorPosition
    dragged_from_maya = Qt.Signal(basestring, Qt.QTreeWidgetItem, Qt.QAbstractItemView.DropIndicatorPosition)

    def __init__(self, parent):
        super(DragFromMayaMixin, self).__init__(parent)

    def checkDragFromMaya(self, nodes):
        """
        Given a list of nodes/attributes being dragged into the widget, return a list of
        which ones should be accepted.

        If the returned list is empty, the drag will be ignored.
        """
        return pm.ls(nodes, type='transform')

    def dropEvent(self, event):
        # QTreeWidget and QListWidget::dropEvent have crazy handling for drops from ourself.  They just
        # move items around on their own assuming the model will have exactly the same behavior, and
        # then change MoveAction to CopyAction, confusing what's actually happening.
        #
        # This doesn't happen if the event is already accepted.   QListModeViewBase::dropOn, etc. will
        # return false and the dropEvent won't do this.  It may still do other things, like stop auto-scrolling.
        # So, do our event checks first, accept the event if we process it, then run the base implementation
        # last.

        if 'application/x-maya-data' in event.mimeData().formats():
            if event.dropAction() == Qt.Qt.DropAction.CopyAction:
                event.accept()

                nodes = event.mimeData().text().rstrip().split()

                nodes = [pm.PyNode(node) for node in nodes]
                nodes = self.checkDragFromMaya(nodes)
                if nodes:
                    target = self.itemAt(event.pos())
                    self.dragged_from_maya.emit(nodes, target, self.dropIndicatorPosition())

            # event.source() crashes if the drag comes from a Maya object, so don't check the other cases.
        elif event.source() is self:
            # Always accept drops from ourself (including for move events) to prevent the crazy base class behavior.
            event.accept()

            if event.dropAction() == Qt.Qt.DropAction.CopyAction:
                selected_indexes = self.selectedIndexes()
                target = self.itemAt(event.pos())
                target_index = self.indexAt(event.pos())

                # For QTreeWidget, if any index in the selection is a child of the drop position, so the drop
                # is invalid.  Note that we still need to accept the event, or the base class will do nasty
                # things.
                if not _any_is_descendant(selected_indexes, target_index):
                    indicator_position = self.dropIndicatorPosition()
                    self.dragged_internally.emit(target, self.dropIndicatorPosition())

        super(DragFromMayaMixin, self).dropEvent(event)

    def dragMoveEvent(self, event):
        # In QT 4, QAbstractItemViewPrivate::position normally gives a margin of 2 pixels
        # on the top and bottom for AboveItem and BelowItem, which is much too small and makes
        # dragging painful.  In QT 5 it gives a fraction of the heigh, which is much more usable.
        # Maya uses QT 5, and its built-in UIs like the shape editor have QT 5's behavior, but
        # for some reason QTreeView, etc. still have QT 4's behavior.
        #
        # Work around this by looking at the event position.  If it's in a position where an
        # above or below drag should be happening, snap the drag position to the boundary so
        # it's treated as an above or below drag.
        pos = event.pos()
        index = self.indexAt(event.pos())
        if index.isValid():
            rect = self.visualRect(index)
            margin = round(float(rect.height()) / 5.5)
            margin = min(max(margin, 2), 12)
            if pos.y() < margin + rect.top() :
                # Move the drag position to the top of the item, forcing AboveItem.
                pos.setY(rect.top())
            elif pos.y() > rect.bottom() - margin:
                # Move the drag position to the bottom of the item, forcing BelowItem.
                pos.setY(rect.bottom())
            elif rect.contains(pos, True):
                # Move the drag position to the center of the item, forcing OnItem.
                pos.setY((rect.bottom() + rect.top()) / 2)
            
        # Create a new, equivalent QDragMoveEvent with our adjusted position.
        #
        # The QT docs say not to construct these.  But, it doesn't offer any alternative,
        # there's no setPos, and this works fine.
        event2 = Qt.QDragMoveEvent(
                pos,
                event.dropAction(),
                event.mimeData(),
                event.mouseButtons(),
                event.keyboardModifiers(),
                event.type())
        super(DragFromMayaMixin, self).dragMoveEvent(event2)

    def mouseMoveEvent(self, event):
        # Match Maya's behavior and only drag on MMB-drag, since Qt's LMB-dragging is
        # broken with multiple selection.
        buttons = event.buttons()
        middle_pressed = bool(buttons & Qt.Qt.MiddleButton)
        self.setDragEnabled(middle_pressed)

        super(DragFromMayaMixin, self).mouseMoveEvent(event)

    def dragEnterEvent(self, event):
        super(DragFromMayaMixin, self).dragEnterEvent(event)

        # For Maya nodes, check if the nodes are an accepted type.
        if 'application/x-maya-data' in event.mimeData().formats():
            if not event.mimeData().hasText():
                event.ignore()
                return

            nodes = event.mimeData().text().rstrip().split()
            nodes = self.checkDragFromMaya(nodes)
            if not nodes:
                event.ignore()

    def mimeTypes(self):
        """
        Add Maya nodes to the MIME types accepted for drag and drop.
        """
        result = super(DragFromMayaMixin, self).mimeTypes()
        result.append('application/x-maya-data')
        return result
Exemple #11
0
 def add_menu_item(menu, text, func):
     action = Qt.QAction(text, menu)
     menu.addAction(action)
     action.triggered.connect(func)
     return action
Exemple #12
0
 def add_menu(text):
     menu = Qt.QMenu(text, self.menubar)
     self.menubar.addAction(menu.menuAction())
     return menu
Exemple #13
0
    def __init__(self):
        super(CreateCurveEditor, self).__init__()

        self.currently_refreshing = False

        self.node_listener = maya_callbacks.NodeChangeListener(
            'zCreateCurve', self.refresh_all)
        self.callbacks = maya_callbacks.MayaCallbackList()
        self.callbacks.add(
            self.refresh_on_selection_changed,
            lambda func: om.MEventMessage.addEventCallback(
                'SelectionChanged', func))

        for delete_key in (Qt.Qt.Key_Backspace, Qt.Qt.Key_Delete):
            shortcut = Qt.QShortcut(Qt.QKeySequence(delete_key), self, None,
                                    None, Qt.Qt.WidgetWithChildrenShortcut)
            shortcut.activated.connect(
                self.remove_selected_transforms_from_curve)

        from zMayaTools.qt_generated import zCreateCurve
        reload(zCreateCurve)

        # Set up the UI.
        self.ui = zCreateCurve.Ui_zCreateCurve()
        self.ui.setupUi(self)

        # Set up the menu bar.
        self.menubar = Qt.QMenuBar()
        self.layout().setMenuBar(self.menubar)
        menubar = self.menubar

        # If we create menus with addMenu and addAction, the parent is set, but the object is
        # still deleted, and we get "internal C++ object already deleted" errors later.  This seems
        # like a QT or PySide bug.

        def add_menu(text):
            menu = Qt.QMenu(text, self.menubar)
            self.menubar.addAction(menu.menuAction())
            return menu

        def add_menu_item(menu, text, func):
            action = Qt.QAction(text, menu)
            menu.addAction(action)
            action.triggered.connect(func)
            return action

        menu = add_menu('Node')
        self.menu_create_node = add_menu_item(menu, 'Create Node',
                                              self.create_and_select_node)
        self.menu_delete_node = add_menu_item(menu, 'Delete Node',
                                              self.delete_node)
        self.menu_select_node = add_menu_item(menu, 'Select Node',
                                              self.select_current_node)
        self.menu_select_curve = add_menu_item(menu, 'Select Output Curve',
                                               self.select_current_curve)

        self.ui.selectNodeButton.clicked.connect(self.select_current_node)
        self.ui.createNodeButton.clicked.connect(self.create_and_select_node)
        self.ui.addToCurveButton.clicked.connect(
            self.append_selected_transforms_to_curve)
        self.ui.removeFromCurveButton.clicked.connect(
            self.remove_selected_transforms_from_curve)

        self.ui.nodeDropdown.currentTextChanged.connect(
            self.current_node_changed)

        self.ui.transformList.setSelectionMode(
            Qt.QAbstractItemView.ExtendedSelection)
        self.ui.transformList.viewport().setAcceptDrops(True)
        self.ui.transformList.setDropIndicatorShown(True)

        self.ui.transformList.setDragDropMode(Qt.QAbstractItemView.DragDrop)
        self.ui.transformList.checkDragFromMaya = lambda nodes: nodes  # allow dropping anything

        self.ui.transformList.dragged_internally.connect(
            self.dragged_internally)
        self.ui.transformList.dragged_from_maya.connect(self.dragged_from_maya)

        self.ui.transformList.itemSelectionChanged.connect(
            self.transform_selection_changed)
        self.ui.transformList.itemDoubleClicked.connect(
            self.double_clicked_transform)
Exemple #14
0
    def __init__(self):
        super(KeyingWindow, self).__init__()

        # How do we make our window handle global hotkeys?
        undo = Qt.QAction('Undo', self)
        undo.setShortcut(Qt.Qt.CTRL + Qt.Qt.Key_Z)
        undo.triggered.connect(lambda: pm.undo())
        self.addAction(undo)

        redo = Qt.QAction('Redo', self)
        redo.setShortcut(Qt.Qt.CTRL + Qt.Qt.Key_Y)
        redo.triggered.connect(lambda: pm.redo(redo=True))
        self.addAction(redo)

        self.weight_node = None
        self.shown = False
        self.callback_ids = om.MCallbackIdArray()

        self._currently_refreshing = False

        style = r'''
        /* Maya's checkbox style makes the checkbox invisible when it's deselected,
         * which makes it impossible to tell that there's even a checkbox there to
         * click.  Adjust the background color to fix this. */
        QTreeView::indicator:unchecked {
            background-color: #000;
        }
        '''
        self.setStyleSheet(style)

        self.time_change_listener = maya_helpers.TimeChangeListener(self._time_changed, pause_during_playback=False)

        # Make sure zMouthController has been generated.
        qt_helpers.compile_all_layouts()

        from zMayaTools.qt_widgets import draggable_progress_bar
        reload(draggable_progress_bar)

        from zMayaTools.qt_generated import zMouthController
        reload(zMouthController)

        self.ui = zMouthController.Ui_zMouthController()
        self.ui.setupUi(self)

        self.ui.selectionBar.setMinimum(0)
        self.ui.selectionBar.setMaximum(1000)
        self.ui.mainWeightBar.setMinimum(0)
        self.ui.mainWeightBar.setMaximum(1000)

        self.ui.selectNodeButton.clicked.connect(self.select_current_node)
        self.ui.shapeSelection1.currentIndexChanged.connect(self.shape1Changed)
        self.ui.shapeSelection2.currentIndexChanged.connect(self.shape2Changed)
        self.ui.selectedNodeDropdown.currentIndexChanged.connect(self.selectedNodeChanged)
        self.ui.setKeyShape1.clicked.connect(self.clicked_key_shape_1)
        self.ui.setKeyShape2.clicked.connect(self.clicked_key_shape_2)
        self.ui.keySelection.clicked.connect(self.clicked_key_selection)
        self.ui.keyMainWeight.clicked.connect(self.clicked_key_main_weight)
        self.ui.soloShape1.clicked.connect(self.solo_shape1)
        self.ui.soloShape2.clicked.connect(self.solo_shape2)
        self.ui.selectionBar.mouse_movement.connect(self.set_selection_bar_value)
        self.ui.mainWeightBar.mouse_movement.connect(self.set_main_weight)

        self.ui.selectionBar.set_undo_chunk_around_dragging('Dragging selection')
        self.ui.mainWeightBar.set_undo_chunk_around_dragging('Dragging weight')

        # This will call selectedNodeChanged, and trigger the rest of the refresh.
        self.refresh_weight_node_list()