From 528fa8db596260011997385eb9865af4c7be1e19 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 10:03:44 +0200 Subject: [PATCH] initial commit --- openpype/tools/project_manager/__init__.py | 6 + openpype/tools/project_manager/__main__.py | 30 + .../project_manager/__init__.py | 30 + .../project_manager/constants.py | 5 + .../project_manager/delegates.py | 23 + .../project_manager/project_manager/model.py | 562 ++++++++++++++++++ .../project_manager/project_manager/view.py | 180 ++++++ .../project_manager/project_manager/window.py | 56 ++ 8 files changed, 892 insertions(+) create mode 100644 openpype/tools/project_manager/__init__.py create mode 100644 openpype/tools/project_manager/__main__.py create mode 100644 openpype/tools/project_manager/project_manager/__init__.py create mode 100644 openpype/tools/project_manager/project_manager/constants.py create mode 100644 openpype/tools/project_manager/project_manager/delegates.py create mode 100644 openpype/tools/project_manager/project_manager/model.py create mode 100644 openpype/tools/project_manager/project_manager/view.py create mode 100644 openpype/tools/project_manager/project_manager/window.py diff --git a/openpype/tools/project_manager/__init__.py b/openpype/tools/project_manager/__init__.py new file mode 100644 index 0000000000..7d8f8bf432 --- /dev/null +++ b/openpype/tools/project_manager/__init__.py @@ -0,0 +1,6 @@ +from .project_manager import Window + + +__all__ = ( + "Window", +) diff --git a/openpype/tools/project_manager/__main__.py b/openpype/tools/project_manager/__main__.py new file mode 100644 index 0000000000..3f29eb5a21 --- /dev/null +++ b/openpype/tools/project_manager/__main__.py @@ -0,0 +1,30 @@ +import os +import sys +paths = [ + r"C:\Users\iLLiCiT\PycharmProjects\pype3\.venv\Lib\site-packages", + r"C:\Users\iLLiCiT\PycharmProjects\pype3", + r"C:\Users\iLLiCiT\PycharmProjects\pype3\repos\avalon-core" +] +for path in paths: + sys.path.append(path) + +os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" +os.environ["OPENPYPE_MONGO"] = "mongodb://localhost:2707" +os.environ["AVALON_TIMEOUT"] = "1000" + +from project_manager import Window + +from Qt import QtWidgets + + +def main(): + app = QtWidgets.QApplication([]) + + window = Window() + window.show() + + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main() diff --git a/openpype/tools/project_manager/project_manager/__init__.py b/openpype/tools/project_manager/project_manager/__init__.py new file mode 100644 index 0000000000..1a538baa20 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/__init__.py @@ -0,0 +1,30 @@ +from .constants import ( + IDENTIFIER_ROLE +) +from .view import HierarchyView +from .model import ( + HierarchyModel, + HierarchySelectionModel, + BaseItem, + RootItem, + ProjectItem, + AssetItem, + TaskItem +) +from .window import Window + +__all__ = ( + "IDENTIFIER_ROLE", + + "HierarchyView", + + "HierarchyModel", + "HierarchySelectionModel", + "BaseItem", + "RootItem", + "ProjectItem", + "AssetItem", + "TaskItem", + + "Window" +) diff --git a/openpype/tools/project_manager/project_manager/constants.py b/openpype/tools/project_manager/project_manager/constants.py new file mode 100644 index 0000000000..6c84ee6635 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/constants.py @@ -0,0 +1,5 @@ +from Qt import QtWidgets, QtCore + + +IDENTIFIER_ROLE = QtCore.Qt.UserRole + 1 +COLUMNS_ROLE = QtCore.Qt.UserRole + 2 diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py new file mode 100644 index 0000000000..605d0cbbab --- /dev/null +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -0,0 +1,23 @@ +from Qt import QtWidgets, QtCore + + +class NumberDelegate(QtWidgets.QStyledItemDelegate): + def createEditor(self, parent, option, index): + editor = QtWidgets.QSpinBox(parent) + value = index.data(QtCore.Qt.DisplayRole) + if value is not None: + editor.setValue(value) + return editor + + # def updateEditorGeometry(self, editor, options, index): + # print(editor) + # return super().updateEditorGeometry(editor, options, index) + + +class StringDelegate(QtWidgets.QStyledItemDelegate): + def createEditor(self, parent, option, index): + editor = QtWidgets.QLineEdit(parent) + value = index.data(QtCore.Qt.DisplayRole) + if value is not None: + editor.setText(str(value)) + return editor diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py new file mode 100644 index 0000000000..d8341df8c1 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/model.py @@ -0,0 +1,562 @@ +import collections +from queue import Queue +from uuid import uuid4 + +from .constants import ( + IDENTIFIER_ROLE, + COLUMNS_ROLE +) + +from avalon.api import AvalonMongoDB + +from Qt import QtWidgets, QtCore + + +class HierarchySelectionModel(QtCore.QItemSelectionModel): + def setCurrentIndex(self, index, command): + # v = "" + # if command == QtCore.QItemSelectionModel.NoUpdate: + # v += "NoUpdate" + # if command & QtCore.QItemSelectionModel.Clear: + # v += "Clear" + # if command & QtCore.QItemSelectionModel.Select: + # v += "Select" + # if command & QtCore.QItemSelectionModel.Deselect: + # v += "Deselect" + # if command & QtCore.QItemSelectionModel.Toggle: + # v += "Toggle" + # if command & QtCore.QItemSelectionModel.Current: + # v += "Current" + # if command & QtCore.QItemSelectionModel.Rows: + # v += "Rows" + # if command & QtCore.QItemSelectionModel.Columns: + # v += "Columns" + if index.column() > 0: + if ( + command & QtCore.QItemSelectionModel.Clear + and command & QtCore.QItemSelectionModel.Select + ): + command = QtCore.QItemSelectionModel.NoUpdate + super(HierarchySelectionModel, self).setCurrentIndex(index, command) + + +class HierarchyModel(QtCore.QAbstractItemModel): + columns = [ + "name", + "type", + "frameStart", + "frameEnd", + "fps", + "resolutionWidth", + "resolutionHeight" + ] + + def __init__(self, parent=None): + super(HierarchyModel, self).__init__(parent) + self._root_item = None + self._items_by_id = {} + self.dbcon = AvalonMongoDB() + + self._hierarchy_mode = True + self._reset_root_item() + + def change_edit_mode(self, hiearchy_mode): + self._hierarchy_mode = hiearchy_mode + + @property + def items_by_id(self): + return self._items_by_id + + def _reset_root_item(self): + self._root_item = RootItem(self) + + def set_project(self, project_doc): + self.clear() + + item = ProjectItem(project_doc) + self.add_item(item) + + def rowCount(self, parent=None): + if parent is None or not parent.isValid(): + parent_item = self._root_item + else: + parent_item = parent.internalPointer() + return parent_item.childCount() + + def columnCount(self, *args, **kwargs): + return len(self.columns) + + def data(self, index, role): + if not index.isValid(): + return None + + column = index.column() + key = self.columns[column] + + item = index.internalPointer() + return item.data(key, role) + + def setData(self, index, value, role=QtCore.Qt.EditRole): + if not index.isValid(): + return False + + item = index.internalPointer() + column = index.column() + key = self.columns[column] + result = item.setData(key, value, role) + if result: + self.dataChanged.emit(index, index, [role]) + + return result + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + if section < len(self.columns): + return self.columns[section] + + super(HierarchyModel, self).headerData(section, orientation, role) + + def flags(self, index): + item = index.internalPointer() + column = index.column() + key = self.columns[column] + return item.flags(key) + + def parent(self, index): + item = index.internalPointer() + parent_item = item.parent() + + # If it has no parents we return invalid + if not parent_item or parent_item is self._root_item: + return QtCore.QModelIndex() + + return self.createIndex(parent_item.row(), 0, parent_item) + + def index(self, row, column, parent=None): + """Return index for row/column under parent""" + parent_item = None + if parent is not None and parent.isValid(): + parent_item = parent.internalPointer() + + return self.index_from_item(row, column, parent_item) + + def index_for_item(self, item, column=0): + return self.index_from_item( + item.row(), column, item.parent() + ) + + def index_from_item(self, row, column, parent=None): + if parent is None: + parent = self._root_item + + child_item = parent.child(row) + if child_item: + return self.createIndex(row, column, child_item) + + return QtCore.QModelIndex() + + def add_new_asset(self, source_index): + item_id = source_index.data(IDENTIFIER_ROLE) + item = self.items_by_id[item_id] + + if isinstance(item, (RootItem, ProjectItem)): + name = "eq" + parent = item + else: + name = source_index.data(QtCore.Qt.DisplayRole) + parent = item.parent() + + data = {"name": name} + new_child = AssetItem(data) + return self.add_item(new_child, parent) + + def add_new_task(self, parent): + pass + + def add_new_item(self, parent): + data = {"name": "Test {}".format(parent.childCount())} + new_child = AssetItem(data) + + return self.add_item(new_child, parent) + + def add_item(self, item, parent=None): + if parent is None: + parent = self._root_item + + idx = parent.childCount() + + parent_index = self.index_from_item(parent.row(), 0, parent.parent()) + self.beginInsertRows(parent_index, idx, idx) + + if item.parent() is not parent: + item.set_parent(parent) + + parent.add_child(item) + + if item.id not in self._items_by_id: + self._items_by_id[item.id] = item + + self.endInsertRows() + + self.rowsInserted.emit(parent_index, idx, idx) + + return self.index_from_item(idx, 0, parent) + + def remove_index(self, index): + if not index.isValid(): + return + + item_id = index.data(IDENTIFIER_ROLE) + item = self._items_by_id[item_id] + if isinstance(item, (RootItem, ProjectItem)): + return + + parent = item.parent() + all_descendants = collections.defaultdict(dict) + all_descendants[parent.id][item.id] = item + + row = item.row() + self.beginRemoveRows(self.index_for_item(parent), row, row) + + todo_queue = Queue() + todo_queue.put(item) + while not todo_queue.empty(): + _item = todo_queue.get() + for row in range(_item.childCount()): + child_item = _item.child(row) + all_descendants[_item.id][child_item.id] = child_item + todo_queue.put(child_item) + + while all_descendants: + for parent_id in tuple(all_descendants.keys()): + children = all_descendants[parent_id] + if not children: + all_descendants.pop(parent_id) + continue + + for child_id in tuple(children.keys()): + child_item = children[child_id] + if child_id in all_descendants: + continue + + children.pop(child_id) + child_item.set_parent(None) + self._items_by_id.pop(child_id) + + self.endRemoveRows() + + def move_vertical(self, index, direction): + if not index.isValid(): + return + + item_id = index.data(IDENTIFIER_ROLE) + if item_id is None: + return + + item = self._items_by_id[item_id] + if isinstance(item, (RootItem, ProjectItem)): + return + + if abs(direction) != 1: + return + + # Move under parent of parent + src_row = item.row() + src_parent = item.parent() + src_parent_index = self.index_from_item( + src_parent.row(), 0, src_parent.parent() + ) + + dst_row = None + dst_parent = None + dst_parent_index = None + + if direction == -1: + if isinstance(src_parent, (RootItem, ProjectItem)): + return + dst_parent = src_parent.parent() + dst_row = src_parent.row() + 1 + + # Move under parent before or after if before is None + elif direction == 1: + if src_parent.childCount() == 1: + return + + if item.row() == 0: + parent_row = item.row() + 1 + else: + parent_row = item.row() - 1 + dst_parent = src_parent.child(parent_row) + dst_row = dst_parent.childCount() + + if src_parent is dst_parent: + return + + if dst_parent_index is None: + dst_parent_index = self.index_from_item( + dst_parent.row(), 0, dst_parent.parent() + ) + + self.beginMoveRows( + src_parent_index, + src_row, + src_row, + dst_parent_index, + dst_row + ) + src_parent.remove_child(item) + dst_parent.add_child(item) + item.set_parent(dst_parent) + dst_parent.move_to(item, dst_row) + + self.endMoveRows() + + def move_horizontal(self, index, direction): + if not index.isValid(): + return + + item_id = index.data(IDENTIFIER_ROLE) + item = self._items_by_id[item_id] + if isinstance(item, (RootItem, ProjectItem)): + return + + if abs(direction) != 1: + return + + src_parent = item.parent() + src_parent_index = self.index_from_item( + src_parent.row(), 0, src_parent.parent() + ) + source_row = item.row() + + dst_parent = None + dst_parent_index = None + destination_row = None + _destination_row = None + # Down + if direction == 1: + if source_row < src_parent.childCount() - 1: + dst_parent_index = src_parent_index + dst_parent = src_parent + destination_row = source_row + 1 + # This row is not row number after moving but before moving + _destination_row = destination_row + 1 + else: + destination_row = 0 + parent_parent = src_parent.parent() + if not parent_parent: + return + + new_parent = parent_parent.child(src_parent.row() + 1) + if not new_parent: + return + dst_parent = new_parent + + # Up + elif direction == -1: + if source_row > 0: + dst_parent_index = src_parent_index + dst_parent = src_parent + destination_row = source_row - 1 + else: + parent_parent = src_parent.parent() + if not parent_parent: + return + + previous_parent = parent_parent.child(src_parent.row() - 1) + if not previous_parent: + return + dst_parent = previous_parent + destination_row = previous_parent.childCount() + + if dst_parent_index is None: + dst_parent_index = self.index_from_item( + dst_parent.row(), 0, dst_parent.parent() + ) + + if _destination_row is None: + _destination_row = destination_row + + self.beginMoveRows( + src_parent_index, + source_row, + source_row, + dst_parent_index, + _destination_row + ) + + if src_parent is dst_parent: + dst_parent.move_to(item, destination_row) + + else: + src_parent.remove_child(item) + dst_parent.add_child(item) + item.set_parent(dst_parent) + dst_parent.move_to(item, destination_row) + + self.endMoveRows() + + def child_removed(self, child): + self._items_by_id.pop(child.id, None) + + def column_name(self, column): + """Return column key by index""" + if column < len(self.columns): + return self.columns[column] + return None + + def clear(self): + self.beginResetModel() + self._reset_root_item() + self.endResetModel() + + +class BaseItem: + columns = ["name"] + + def __init__(self, data=None): + self._id = uuid4() + self._children = list() + self._parent = None + + self._data = { + key: None + for key in self.columns + } + self._source_data = data + if data: + for key, value in data.items(): + if key in self.columns: + self._data[key] = value + + def model(self): + return self._parent.model + + def move_to(self, item, row): + idx = self._children.index(item) + if idx == row: + return + + self._children.pop(idx) + self._children.insert(row, item) + + def data(self, key, role): + if role == IDENTIFIER_ROLE: + return self._id + + if role == COLUMNS_ROLE: + return self.columns + + if key not in self.columns: + return None + + if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): + value = self._data[key] + if value is None: + value = self.parent().data(key, role) + return value + + return None + + def setData(self, key, value, role): + if key not in self.columns: + return False + + if role == QtCore.Qt.EditRole: + self._data[key] = value + + # must return true if successful + return True + + return False + + @property + def id(self): + return self._id + + def childCount(self): + return len(self._children) + + def child(self, row): + if -1 < row < self.childCount(): + return self._children[row] + return None + + def children(self): + return self._children + + def child_row(self, child): + if child not in self._children: + return -1 + return self._children.index(child) + + def parent(self): + return self._parent + + def set_parent(self, parent): + if parent is self._parent: + return + + if self._parent: + self._parent.remove_child(self) + self._parent = parent + + def row(self): + if self._parent is not None: + return self._parent.child_row(self) + return -1 + + def add_child(self, item): + if item not in self._children: + self._children.append(item) + + def remove_child(self, item): + if item in self._children: + self._children.remove(item) + + def flags(self, key): + flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + if key in self.columns: + flags |= QtCore.Qt.ItemIsEditable + return flags + + +class RootItem(BaseItem): + def __init__(self, model): + super(RootItem, self).__init__() + self._model = model + + def model(self): + return self._model + + def flags(self, *args, **kwargs): + return QtCore.Qt.NoItemFlags + + +class ProjectItem(BaseItem): + def __init__(self, data=None): + super(ProjectItem, self).__init__(data) + self._data["name"] = "project" + + def flags(self, *args, **kwargs): + return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + + +class AssetItem(BaseItem): + columns = [ + "name", + "type", + "frameStart", + "frameEnd", + "fps", + "resolutionWidth", + "resolutionHeight" + ] + + def __init__(self, data=None): + super(AssetItem, self).__init__(data) + + +class TaskItem(BaseItem): + def __init__(self, data=None): + super(TaskItem, self).__init__(data) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py new file mode 100644 index 0000000000..d49b5e3b52 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/view.py @@ -0,0 +1,180 @@ +from Qt import QtWidgets, QtCore + +from .constants import ( + IDENTIFIER_ROLE, + COLUMNS_ROLE +) +from .delegates import NumberDelegate, StringDelegate + + +class HierarchyView(QtWidgets.QTreeView): + """A tree view that deselects on clicking on an empty area in the view""" + column_delegate_defs = { + "name": StringDelegate, + "frameStart": NumberDelegate, + "frameEnd": NumberDelegate, + "fps": NumberDelegate, + "resolutionWidth": NumberDelegate, + "resolutionHeight": NumberDelegate + } + persistent_columns = [ + "frameStart", "frameEnd", "fps", "resolutionWidth", "resolutionHeight" + ] + + def __init__(self, source_model, *args, **kwargs): + super(HierarchyView, self).__init__(*args, **kwargs) + self._source_model = source_model + + main_delegate = QtWidgets.QStyledItemDelegate() + self.setItemDelegate(main_delegate) + self.setAlternatingRowColors(True) + self.setSelectionMode(HierarchyView.ContiguousSelection) + + column_delegates = {} + column_key_to_index = {} + for key, delegate_klass in self.column_delegate_defs.items(): + delegate = delegate_klass() + column = self._source_model.columns.index(key) + self.setItemDelegateForColumn(column, delegate) + column_delegates[key] = delegate + column_key_to_index[key] = column + + self._delegate = main_delegate + self._column_delegates = column_delegates + self._column_key_to_index = column_key_to_index + + def commitData(self, editor): + super(HierarchyView, self).commitData(editor) + current_index = self.currentIndex() + column = current_index.column() + row = current_index.row() + skipped_index = None + if column > 0: + indexes = [] + for index in self.selectedIndexes(): + if index.column() == column: + if index.row() == row: + skipped_index = index + else: + indexes.append(index) + + if skipped_index is not None: + value = current_index.data(QtCore.Qt.EditRole) + for index in indexes: + index.model().setData(index, value, QtCore.Qt.EditRole) + + # Update children data + self.updateEditorData() + + def _deselect_editor(self, editor): + if editor: + if isinstance(editor, QtWidgets.QSpinBox): + line_edit = editor.findChild(QtWidgets.QLineEdit) + line_edit.deselect() + + elif isinstance(editor, QtWidgets.QLineEdit): + editor.deselect() + + def edit(self, index, *args, **kwargs): + result = super(HierarchyView, self).edit(index, *args, **kwargs) + self._deselect_editor(self.indexWidget(index)) + return result + + def openPersistentEditor(self, index): + super(HierarchyView, self).openPersistentEditor(index) + self._deselect_editor(self.indexWidget(index)) + + def rowsInserted(self, parent_index, start, end): + super(HierarchyView, self).rowsInserted(parent_index, start, end) + + for row in range(start, end + 1): + index = self._source_model.index(row, 0, parent_index) + columns = index.data(COLUMNS_ROLE) or [] + for key, column in self._column_key_to_index.items(): + if key not in self.persistent_columns: + continue + col_index = self._source_model.index(row, column, parent_index) + self.openPersistentEditor(col_index) + + # Expand parent on insert + if not self.isExpanded(parent_index): + self.expand(parent_index) + + def mousePressEvent(self, event): + index = self.indexAt(event.pos()) + if not index.isValid(): + # clear the selection + self.clearSelection() + # clear the current index + self.setCurrentIndex(QtCore.QModelIndex()) + + super(HierarchyView, self).mousePressEvent(event) + + def keyPressEvent(self, event): + call_super = False + if event.key() == QtCore.Qt.Key_Delete: + self._delete_item() + + elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): + if event.modifiers() == QtCore.Qt.ShiftModifier: + self._on_shift_enter_pressed() + else: + if self.state() == HierarchyView.NoState: + self._on_enter_pressed() + + elif event.modifiers() == QtCore.Qt.ControlModifier: + if event.key() == QtCore.Qt.Key_Left: + self._on_left_ctrl_pressed() + elif event.key() == QtCore.Qt.Key_Right: + self._on_right_ctrl_pressed() + elif event.key() == QtCore.Qt.Key_Up: + self._on_up_ctrl_pressed() + elif event.key() == QtCore.Qt.Key_Down: + self._on_down_ctrl_pressed() + else: + call_super = True + + if call_super: + super(HierarchyView, self).keyPressEvent(event) + else: + event.accept() + + def _delete_item(self): + index = self.currentIndex() + self._source_model.remove_index(index) + + def _on_shift_enter_pressed(self): + index = self.currentIndex() + if not index.isValid(): + return + + # Stop editing + self.setState(HierarchyView.NoState) + QtWidgets.QApplication.processEvents() + + new_index = self._source_model.add_new_asset(index) + + # Change current index + self.setCurrentIndex(new_index) + # Start editing + self.edit(new_index) + + def _on_up_ctrl_pressed(self): + self._source_model.move_horizontal(self.currentIndex(), -1) + + def _on_down_ctrl_pressed(self): + self._source_model.move_horizontal(self.currentIndex(), 1) + + def _on_left_ctrl_pressed(self): + self._source_model.move_vertical(self.currentIndex(), -1) + + def _on_right_ctrl_pressed(self): + self._source_model.move_vertical(self.currentIndex(), 1) + + def _on_enter_pressed(self): + index = self.currentIndex() + if ( + index.isValid() + and index.flags() & QtCore.Qt.ItemIsEditable + ): + self.edit(index) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py new file mode 100644 index 0000000000..db5527b4ac --- /dev/null +++ b/openpype/tools/project_manager/project_manager/window.py @@ -0,0 +1,56 @@ +from Qt import QtWidgets, QtCore + +from . import ( + HierarchyModel, + HierarchySelectionModel, + HierarchyView +) + + +class Window(QtWidgets.QWidget): + def __init__(self, parent=None): + super(Window, self).__init__(parent) + + model = HierarchyModel() + view = HierarchyView(model, self) + view.setModel(model) + _selection_model = HierarchySelectionModel() + _selection_model.setModel(view.model()) + view.setSelectionModel(_selection_model) + + header = view.header() + header.setStretchLastSection(False) + header.setSectionResizeMode( + header.logicalIndex(0), QtWidgets.QHeaderView.Stretch + ) + checkbox = QtWidgets.QCheckBox(self) + # btn = QtWidgets.QPushButton("Refresh") + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(view) + main_layout.addWidget(checkbox) + # main_layout.addWidget(btn) + # btn.clicked.connect(self._on_refresh) + + checkbox.toggled.connect(self._on_checkbox) + + self.view = view + self.model = model + # self.btn = btn + self.checkbox = checkbox + + self.change_edit_mode() + + self.resize(1200, 600) + model.set_project({"name": "test"}) + + def change_edit_mode(self, value=None): + if value is None: + value = self.checkbox.isChecked() + self.model.change_edit_mode(value) + + def _on_checkbox(self, value): + self.change_edit_mode(value) + + def _on_refresh(self): + self.model.clear()