diff --git a/openpype/modules/sync_server/tray/lib.py b/openpype/modules/sync_server/tray/lib.py index 04bd1f568e..5e59d653ac 100644 --- a/openpype/modules/sync_server/tray/lib.py +++ b/openpype/modules/sync_server/tray/lib.py @@ -1,9 +1,11 @@ -from Qt import QtCore +from Qt import QtCore, QtWidgets, QtGui +from Qt.QtCore import Qt import attr import abc import six from openpype.lib import PypeLogger +from avalon.vendor import qtawesome log = PypeLogger().get_logger("SyncServer") @@ -164,3 +166,56 @@ def get_item_by_id(model, object_id): index = model.get_index(object_id) item = model.data(index, FullItemRole) return item + + +class PriorityDelegate(QtWidgets.QStyledItemDelegate): + + def paint(self, painter, option, index): + super(PriorityDelegate, self).paint(painter, option, index) + + if option.widget.selectionModel().isSelected(index) or \ + option.state & QtWidgets.QStyle.State_MouseOver: + edit_icon = qtawesome.icon("fa.edit", color="white") + state = QtGui.QIcon.On + mode = QtGui.QIcon.Selected + + icon_side = 16 + icon_rect = QtCore.QRect( + option.rect.left() + option.rect.width() - icon_side - 4, + option.rect.top() + ((option.rect.height() - icon_side) / 2), + icon_side, + icon_side + ) + + edit_icon.paint( + painter, icon_rect, + QtCore.Qt.AlignRight, mode, state + ) + + def createEditor(self, parent, option, index): + editor = PriorityLineEdit( + parent, + option.widget.selectionModel().selectedRows()) + editor.setValidator(QtGui.QIntValidator(0, 1000, self)) + editor.setFocus(True) + return editor + + def setModelData(self, editor, model, index): + for index in editor.selected_idxs: + model.set_priority_data(index, editor.text()) + + +class PriorityLineEdit(QtWidgets.QLineEdit): + """Special LineEdit to consume Enter and store selected indexes""" + def __init__(self, parent=None, selected_idxs=None): + self.selected_idxs = selected_idxs + super(PriorityLineEdit, self).__init__(parent) + + def keyPressEvent(self, event): + result = super(PriorityLineEdit, self).keyPressEvent(event) + if ( + event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter) + ): + return event.accept() + + return result diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 8fdd9487a4..9fcaefbe6e 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -6,6 +6,7 @@ from Qt import QtCore from Qt.QtCore import Qt from avalon.tools.delegates import pretty_timestamp +from avalon.vendor import qtawesome from openpype.lib import PypeLogger @@ -41,6 +42,8 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): PAGE_SIZE = 20 # default page size to query for REFRESH_SEC = 5000 # in seconds, requery DB for new status + DEFAULT_PRIORITY = 50 + @property def dbcon(self): """ @@ -79,6 +82,14 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): return self.COLUMN_LABELS[section][0] # return name def get_column(self, index): + """ + Returns info about column + + Args: + index (QModelIndex) + Returns: + (tuple): (COLUMN_NAME: COLUMN_LABEL) + """ return self.COLUMN_LABELS[index] def get_header_index(self, value): @@ -109,7 +120,8 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): than single page of records) """ if self.sync_server.is_paused() or \ - self.sync_server.is_project_paused(self.project): + self.sync_server.is_project_paused(self.project) or \ + self.is_editing: return self.refresh_started.emit() self.beginResetModel() @@ -191,7 +203,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): self.sort = {self.SORT_BY_COLUMN[index]: order} # reset # add last one for key, val in backup_sort.items(): - if key != '_id': + if key != '_id' and key != self.SORT_BY_COLUMN[index]: self.sort[key] = val break # add default one @@ -363,7 +375,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): "updated_dt_remote", # remote created_dt "files_count", # count of files "files_size", # file size of all files - "context.asset", # priority TODO + "priority", # priority "status" # status ] @@ -374,6 +386,8 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): 'representation': lib.MultiSelectFilter('representation') } + EDITABLE_COLUMNS = ["priority"] + refresh_started = QtCore.Signal() refresh_finished = QtCore.Signal() @@ -413,6 +427,9 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self._word_filter = None self._column_filtering = {} + self.edit_icon = qtawesome.icon("fa.edit", color="white") + self.is_editing = False + self._word_filter = None self._initialized = False @@ -472,7 +489,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): return item.status == lib.STATUS[2] and \ item.remote_progress < 1 - if role == Qt.DisplayRole: + if role in (Qt.DisplayRole, Qt.EditRole): # because of ImageDelegate if header_value in ['remote_site', 'local_site']: return "" @@ -549,7 +566,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): avg_progress_remote, repre.get("files_count", 1), lib.pretty_size(repre.get("files_size", 0)), - 1, + repre.get("priority"), lib.STATUS[repre.get("status", -1)], files[0].get('path') ) @@ -668,6 +685,10 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): '$cond': [{'$size': "$order_local.paused"}, 1, 0]}, + 'priority': { + '$cond': [{'$size': '$order_local.priority'}, + {'$first': '$order_local.priority'}, + self.DEFAULT_PRIORITY]}, }}, {'$group': { '_id': '$_id', @@ -690,7 +711,8 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): 'failed_local_tries': {'$sum': '$failed_local_tries'}, 'paused_remote': {'$sum': '$paused_remote'}, 'paused_local': {'$sum': '$paused_local'}, - 'updated_dt_local': {'$max': "$updated_dt_local"} + 'updated_dt_local': {'$max': "$updated_dt_local"}, + 'priority': {'$max': "$priority"}, }}, {"$project": self.projection} ] @@ -772,6 +794,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): 'updated_dt_local': 1, 'paused_remote': 1, 'paused_local': 1, + 'priority': 1, 'status': { '$switch': { 'branches': [ @@ -818,6 +841,27 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): } } + def set_priority_data(self, index, value): + """ + Sets 'priority' flag and value on active site for selected reprs. + + Args: + index (QItemIndex): selected index from View + value (int): priority value + + Updates DB + """ + repre_id = self.data(index, Qt.UserRole) + + representation = list(self.dbcon.find({"type": "representation", + "_id": repre_id})) + if representation: + self.sync_server.update_db(self.project, None, None, + representation.pop(), self.active_site, + priority=value) + self.is_editing = False + self.refresh(None, self._rec_loaded) + class SyncRepresentationDetailModel(_SyncRepresentationModel): """ @@ -901,6 +945,8 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self._initialized = False self._column_filtering = {} + self.is_editing = False + self.sync_server = sync_server # TODO think about admin mode # this is for regular user, always only single local and single remote diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 106fc4b8a8..838b325c73 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -171,6 +171,13 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): """ Opens representation dialog with all files after doubleclick """ + # priority editing + column_name = self.model.get_column(index.column()) + if column_name[0] in self.model.EDITABLE_COLUMNS: + self.model.is_editing = True + self.table_view.openPersistentEditor(index) + return + _id = self.model.data(index, Qt.UserRole) detail_window = SyncServerDetailWindow( self.sync_server, _id, self.model.project) @@ -253,6 +260,11 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): actions_mapping[action] = self._remove_site menu.addAction(action) + action = QtWidgets.QAction("Change priority") + action_kwarg_map[action] = self._get_action_kwargs(active_site) + actions_mapping[action] = self._change_priority + menu.addAction(action) + # # temp for testing only !!! # action = QtWidgets.QAction("Download") # action_kwarg_map[action] = self._get_action_kwargs(active_site) @@ -397,6 +409,15 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): except OSError: raise OSError('unsupported xdg-open call??') + def _change_priority(self, **kwargs): + """Open editor to change priority on first selected row""" + if self._selected_ids: + index = self.model.get_index(self._selected_ids[0]) # column = 0 + column_no = self.model.get_header_index("priority") # real column + real_index = self.model.index(index.row(), column_no) + self.model.is_editing = True + self.table_view.openPersistentEditor(real_index) + def _get_progress(self, item, site_name, opposite=False): """Returns progress value according to site (side)""" progress = {'local': item.local_progress, @@ -470,6 +491,7 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): -1, Qt.AscendingOrder) table_view.setAlternatingRowColors(True) table_view.verticalHeader().hide() + table_view.viewport().setAttribute(QtCore.Qt.WA_Hover, True) column = table_view.model().get_header_index("local_site") delegate = ImageDelegate(self) @@ -479,6 +501,10 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): delegate = ImageDelegate(self) table_view.setItemDelegateForColumn(column, delegate) + column = table_view.model().get_header_index("priority") + priority_delegate = lib.PriorityDelegate(self) + table_view.setItemDelegateForColumn(column, priority_delegate) + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout)