mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 13:24:54 +01:00
SyncServer GUI - multiselect for Detail
Refactor
This commit is contained in:
parent
4036eefe7b
commit
d2a27fd858
4 changed files with 296 additions and 351 deletions
|
|
@ -7,7 +7,7 @@ from openpype import resources
|
|||
|
||||
from openpype.modules.sync_server.tray.widgets import (
|
||||
SyncProjectListWidget,
|
||||
SyncRepresentationWidget
|
||||
SyncRepresentationSummaryWidget
|
||||
)
|
||||
|
||||
log = PypeLogger().get_logger("SyncServer")
|
||||
|
|
@ -47,7 +47,7 @@ class SyncServerWindow(QtWidgets.QDialog):
|
|||
left_column_layout.addWidget(self.pause_btn)
|
||||
left_column.setLayout(left_column_layout)
|
||||
|
||||
repres = SyncRepresentationWidget(
|
||||
repres = SyncRepresentationSummaryWidget(
|
||||
sync_server,
|
||||
project=self.projects.current_project,
|
||||
parent=self)
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ class FilterDefinition:
|
|||
type = attr.ib()
|
||||
values = attr.ib(factory=list)
|
||||
|
||||
|
||||
def pretty_size(value, suffix='B'):
|
||||
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
|
||||
if abs(value) < 1024.0:
|
||||
|
|
@ -157,3 +158,9 @@ def translate_provider_for_icon(sync_server, project, site):
|
|||
if site == sync_server.DEFAULT_SITE:
|
||||
return sync_server.DEFAULT_SITE
|
||||
return sync_server.get_provider_for_site(project, site)
|
||||
|
||||
|
||||
def get_item_by_id(model, object_id):
|
||||
index = model.get_index(object_id)
|
||||
item = model.data(index, FullItemRole)
|
||||
return item
|
||||
|
|
|
|||
|
|
@ -908,6 +908,9 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
|||
def data(self, index, role):
|
||||
item = self._data[index.row()]
|
||||
|
||||
if role == lib.FullItemRole:
|
||||
return item
|
||||
|
||||
header_value = self._header[index.column()]
|
||||
if role == lib.ProviderRole:
|
||||
if header_value == 'local_site':
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ class SyncProjectListWidget(ProjectListWidget):
|
|||
self.refresh()
|
||||
|
||||
|
||||
class SyncRepresentationWidget(QtWidgets.QWidget):
|
||||
class _SyncRepresentationWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
Summary dialog with list of representations that matches current
|
||||
settings 'local_site' and 'remote_site'.
|
||||
|
|
@ -143,90 +143,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
active_changed = QtCore.Signal() # active index changed
|
||||
message_generated = QtCore.Signal(str)
|
||||
|
||||
default_widths = (
|
||||
("asset", 190),
|
||||
("subset", 170),
|
||||
("version", 60),
|
||||
("representation", 145),
|
||||
("local_site", 160),
|
||||
("remote_site", 160),
|
||||
("files_count", 50),
|
||||
("files_size", 60),
|
||||
("priority", 70),
|
||||
("status", 110)
|
||||
)
|
||||
|
||||
def __init__(self, sync_server, project=None, parent=None):
|
||||
super(SyncRepresentationWidget, self).__init__(parent)
|
||||
|
||||
self.sync_server = sync_server
|
||||
|
||||
self._selected_ids = [] # keep last selected _id
|
||||
|
||||
self.txt_filter = QtWidgets.QLineEdit()
|
||||
self.txt_filter.setPlaceholderText("Quick filter representations..")
|
||||
self.txt_filter.setClearButtonEnabled(True)
|
||||
self.txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"),
|
||||
QtWidgets.QLineEdit.LeadingPosition)
|
||||
|
||||
self._scrollbar_pos = None
|
||||
|
||||
top_bar_layout = QtWidgets.QHBoxLayout()
|
||||
top_bar_layout.addWidget(self.txt_filter)
|
||||
|
||||
self.table_view = QtWidgets.QTableView()
|
||||
headers = [item[0] for item in self.default_widths]
|
||||
|
||||
model = SyncRepresentationSummaryModel(sync_server, headers, project)
|
||||
self.table_view.setModel(model)
|
||||
self.table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.table_view.setSelectionMode(
|
||||
QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.table_view.setSelectionBehavior(
|
||||
QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.table_view.horizontalHeader().setSortIndicator(
|
||||
-1, Qt.AscendingOrder)
|
||||
self.table_view.setAlternatingRowColors(True)
|
||||
self.table_view.verticalHeader().hide()
|
||||
|
||||
column = self.table_view.model().get_header_index("local_site")
|
||||
delegate = ImageDelegate(self)
|
||||
self.table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
column = self.table_view.model().get_header_index("remote_site")
|
||||
delegate = ImageDelegate(self)
|
||||
self.table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addLayout(top_bar_layout)
|
||||
layout.addWidget(self.table_view)
|
||||
|
||||
self.table_view.doubleClicked.connect(self._double_clicked)
|
||||
self.txt_filter.textChanged.connect(lambda: model.set_word_filter(
|
||||
self.txt_filter.text()))
|
||||
self.table_view.customContextMenuRequested.connect(
|
||||
self._on_context_menu)
|
||||
|
||||
model.refresh_started.connect(self._save_scrollbar)
|
||||
model.refresh_finished.connect(self._set_scrollbar)
|
||||
model.modelReset.connect(self._set_selection)
|
||||
|
||||
self.model = model
|
||||
|
||||
self.selection_model = self.table_view.selectionModel()
|
||||
self.selection_model.selectionChanged.connect(self._selection_changed)
|
||||
|
||||
horizontal_header = HorizontalHeader(self)
|
||||
|
||||
self.table_view.setHorizontalHeader(horizontal_header)
|
||||
self.table_view.setSortingEnabled(True)
|
||||
|
||||
for column_name, width in self.default_widths:
|
||||
idx = model.get_header_index(column_name)
|
||||
self.table_view.setColumnWidth(idx, width)
|
||||
|
||||
def _selection_changed(self, new_selected, all_selected):
|
||||
def _selection_changed(self, _new_selected, _all_selected):
|
||||
idxs = self.selection_model.selectedRows()
|
||||
self._selected_ids = []
|
||||
|
||||
|
|
@ -273,21 +190,36 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
|
||||
if is_multi:
|
||||
index = self.model.get_index(self._selected_ids[0])
|
||||
self.item = self.model.data(index, lib.FullItemRole)
|
||||
item = self.model.data(index, lib.FullItemRole)
|
||||
else:
|
||||
self.item = self.model.data(point_index, lib.FullItemRole)
|
||||
item = self.model.data(point_index, lib.FullItemRole)
|
||||
|
||||
action_kwarg_map, actions_mapping, menu = self._prepare_menu(item,
|
||||
is_multi)
|
||||
|
||||
result = menu.exec_(QtGui.QCursor.pos())
|
||||
if result:
|
||||
to_run = actions_mapping[result]
|
||||
to_run_kwargs = action_kwarg_map.get(result, {})
|
||||
if to_run:
|
||||
to_run(**to_run_kwargs)
|
||||
|
||||
self.model.refresh()
|
||||
|
||||
def _prepare_menu(self, item, is_multi):
|
||||
menu = QtWidgets.QMenu()
|
||||
|
||||
actions_mapping = {}
|
||||
action_kwarg_map = {}
|
||||
|
||||
active_site = self.model.active_site
|
||||
remote_site = self.model.remote_site
|
||||
|
||||
local_progress = self.item.local_progress
|
||||
remote_progress = self.item.remote_progress
|
||||
local_progress = item.local_progress
|
||||
remote_progress = item.remote_progress
|
||||
|
||||
project = self.model.project
|
||||
|
||||
for site, progress in {active_site: local_progress,
|
||||
remote_site: remote_progress}.items():
|
||||
provider = self.sync_server.get_provider_for_site(project, site)
|
||||
|
|
@ -303,24 +235,6 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
self._get_action_kwargs(site)
|
||||
menu.addAction(action)
|
||||
|
||||
if self.item.status in [lib.STATUS[0], lib.STATUS[1]] or is_multi:
|
||||
action = QtWidgets.QAction("Pause in queue")
|
||||
actions_mapping[action] = self._pause
|
||||
# pause handles which site_name it will pause itself
|
||||
action_kwarg_map[action] = {"repre_ids": self._selected_ids}
|
||||
menu.addAction(action)
|
||||
|
||||
if self.item.status == lib.STATUS[3] or is_multi:
|
||||
action = QtWidgets.QAction("Unpause in queue")
|
||||
actions_mapping[action] = self._unpause
|
||||
action_kwarg_map[action] = {"repre_ids": self._selected_ids}
|
||||
menu.addAction(action)
|
||||
|
||||
# if self.item.status == lib.STATUS[1]:
|
||||
# action = QtWidgets.QAction("Open error detail")
|
||||
# actions_mapping[action] = self._show_detail
|
||||
# menu.addAction(action)
|
||||
|
||||
if remote_progress == 1.0 or is_multi:
|
||||
action = QtWidgets.QAction("Re-sync Active site")
|
||||
action_kwarg_map[action] = self._get_action_kwargs(active_site)
|
||||
|
|
@ -350,19 +264,12 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
actions_mapping[action] = None
|
||||
menu.addAction(action)
|
||||
|
||||
result = menu.exec_(QtGui.QCursor.pos())
|
||||
if result:
|
||||
to_run = actions_mapping[result]
|
||||
to_run_kwargs = action_kwarg_map.get(result, {})
|
||||
if to_run:
|
||||
to_run(**to_run_kwargs)
|
||||
return action_kwarg_map, actions_mapping, menu
|
||||
|
||||
self.model.refresh()
|
||||
|
||||
def _pause(self, repre_ids=None):
|
||||
log.debug("Pause {}".format(repre_ids))
|
||||
for representation_id in repre_ids:
|
||||
item = self._get_item_by_repre_id(representation_id)
|
||||
def _pause(self, selected_ids=None):
|
||||
log.debug("Pause {}".format(selected_ids))
|
||||
for representation_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, representation_id)
|
||||
if item.status not in [lib.STATUS[0], lib.STATUS[1]]:
|
||||
continue
|
||||
for site_name in [self.model.active_site, self.model.remote_site]:
|
||||
|
|
@ -374,10 +281,10 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
|
||||
self.message_generated.emit("Paused {}".format(representation_id))
|
||||
|
||||
def _unpause(self, repre_ids=None):
|
||||
log.debug("UnPause {}".format(repre_ids))
|
||||
for representation_id in repre_ids:
|
||||
item = self._get_item_by_repre_id(representation_id)
|
||||
def _unpause(self, selected_ids=None):
|
||||
log.debug("UnPause {}".format(selected_ids))
|
||||
for representation_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, representation_id)
|
||||
if item.status not in lib.STATUS[3]:
|
||||
continue
|
||||
for site_name in [self.model.active_site, self.model.remote_site]:
|
||||
|
|
@ -391,10 +298,10 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
self.message_generated.emit("Unpause {}".format(representation_id))
|
||||
|
||||
# temporary here for testing, will be removed TODO
|
||||
def _add_site(self, repre_ids=None, site_name=None):
|
||||
log.debug("Add site {}:{}".format(repre_ids, site_name))
|
||||
for representation_id in repre_ids:
|
||||
item = self._get_item_by_repre_id(representation_id)
|
||||
def _add_site(self, selected_ids=None, site_name=None):
|
||||
log.debug("Add site {}:{}".format(selected_ids, site_name))
|
||||
for representation_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, representation_id)
|
||||
if item.local_site == site_name or item.remote_site == site_name:
|
||||
# site already exists skip
|
||||
continue
|
||||
|
|
@ -411,7 +318,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
except ValueError as exp:
|
||||
self.message_generated.emit("Error {}".format(str(exp)))
|
||||
|
||||
def _remove_site(self, repre_ids=None, site_name=None):
|
||||
def _remove_site(self, selected_ids=None, site_name=None):
|
||||
"""
|
||||
Removes site record AND files.
|
||||
|
||||
|
|
@ -421,8 +328,8 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
This could only happen when artist work on local machine, not
|
||||
connected to studio mounted drives.
|
||||
"""
|
||||
log.debug("Remove site {}:{}".format(repre_ids, site_name))
|
||||
for representation_id in repre_ids:
|
||||
log.debug("Remove site {}:{}".format(selected_ids, site_name))
|
||||
for representation_id in selected_ids:
|
||||
log.info("Removing {}".format(representation_id))
|
||||
try:
|
||||
self.sync_server.remove_site(
|
||||
|
|
@ -438,14 +345,14 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
self.model.refresh(
|
||||
load_records=self.model._rec_loaded)
|
||||
|
||||
def _reset_site(self, repre_ids=None, site_name=None):
|
||||
def _reset_site(self, selected_ids=None, site_name=None):
|
||||
"""
|
||||
Removes errors or success metadata for particular file >> forces
|
||||
redo of upload/download
|
||||
"""
|
||||
log.debug("Reset site {}:{}".format(repre_ids, site_name))
|
||||
for representation_id in repre_ids:
|
||||
item = self._get_item_by_repre_id(representation_id)
|
||||
log.debug("Reset site {}:{}".format(selected_ids, site_name))
|
||||
for representation_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, representation_id)
|
||||
check_progress = self._get_progress(item, site_name, True)
|
||||
|
||||
# do not reset if opposite side is not fully there
|
||||
|
|
@ -463,10 +370,10 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
self.model.refresh(
|
||||
load_records=self.model._rec_loaded)
|
||||
|
||||
def _open_in_explorer(self, repre_ids=None, site_name=None):
|
||||
log.debug("Open in Explorer {}:{}".format(repre_ids, site_name))
|
||||
for representation_id in repre_ids:
|
||||
item = self._get_item_by_repre_id(representation_id)
|
||||
def _open_in_explorer(self, selected_ids=None, site_name=None):
|
||||
log.debug("Open in Explorer {}:{}".format(selected_ids, site_name))
|
||||
for selected_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, selected_id)
|
||||
if not item:
|
||||
return
|
||||
|
||||
|
|
@ -500,14 +407,9 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
|
||||
return progress[side]
|
||||
|
||||
def _get_item_by_repre_id(self, representation_id):
|
||||
index = self.model.get_index(representation_id)
|
||||
item = self.model.data(index, lib.FullItemRole)
|
||||
return item
|
||||
|
||||
def _get_action_kwargs(self, site_name):
|
||||
"""Default format of kwargs for action"""
|
||||
return {"repre_ids": self._selected_ids, "site_name": site_name}
|
||||
return {"selected_ids": self._selected_ids, "site_name": site_name}
|
||||
|
||||
def _save_scrollbar(self):
|
||||
self._scrollbar_pos = self.table_view.verticalScrollBar().value()
|
||||
|
|
@ -517,7 +419,155 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
self.table_view.verticalScrollBar().setValue(self._scrollbar_pos)
|
||||
|
||||
|
||||
class SyncRepresentationDetailWidget(QtWidgets.QWidget):
|
||||
class SyncRepresentationSummaryWidget(_SyncRepresentationWidget):
|
||||
|
||||
default_widths = (
|
||||
("asset", 190),
|
||||
("subset", 170),
|
||||
("version", 60),
|
||||
("representation", 145),
|
||||
("local_site", 160),
|
||||
("remote_site", 160),
|
||||
("files_count", 50),
|
||||
("files_size", 60),
|
||||
("priority", 70),
|
||||
("status", 110)
|
||||
)
|
||||
|
||||
def __init__(self, sync_server, project=None, parent=None):
|
||||
super(SyncRepresentationSummaryWidget, self).__init__(parent)
|
||||
|
||||
self.sync_server = sync_server
|
||||
|
||||
self._selected_ids = [] # keep last selected _id
|
||||
|
||||
txt_filter = QtWidgets.QLineEdit()
|
||||
txt_filter.setPlaceholderText("Quick filter representations..")
|
||||
txt_filter.setClearButtonEnabled(True)
|
||||
txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"),
|
||||
QtWidgets.QLineEdit.LeadingPosition)
|
||||
self.txt_filter = txt_filter
|
||||
|
||||
self._scrollbar_pos = None
|
||||
|
||||
top_bar_layout = QtWidgets.QHBoxLayout()
|
||||
top_bar_layout.addWidget(self.txt_filter)
|
||||
|
||||
table_view = QtWidgets.QTableView()
|
||||
headers = [item[0] for item in self.default_widths]
|
||||
|
||||
model = SyncRepresentationSummaryModel(sync_server, headers, project)
|
||||
table_view.setModel(model)
|
||||
table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
table_view.setSelectionMode(
|
||||
QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
table_view.setSelectionBehavior(
|
||||
QtWidgets.QAbstractItemView.SelectRows)
|
||||
table_view.horizontalHeader().setSortIndicator(
|
||||
-1, Qt.AscendingOrder)
|
||||
table_view.setAlternatingRowColors(True)
|
||||
table_view.verticalHeader().hide()
|
||||
|
||||
column = table_view.model().get_header_index("local_site")
|
||||
delegate = ImageDelegate(self)
|
||||
table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
column = table_view.model().get_header_index("remote_site")
|
||||
delegate = ImageDelegate(self)
|
||||
table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addLayout(top_bar_layout)
|
||||
layout.addWidget(table_view)
|
||||
|
||||
self.table_view = table_view
|
||||
self.model = model
|
||||
|
||||
horizontal_header = HorizontalHeader(self)
|
||||
|
||||
table_view.setHorizontalHeader(horizontal_header)
|
||||
table_view.setSortingEnabled(True)
|
||||
|
||||
for column_name, width in self.default_widths:
|
||||
idx = model.get_header_index(column_name)
|
||||
table_view.setColumnWidth(idx, width)
|
||||
|
||||
table_view.doubleClicked.connect(self._double_clicked)
|
||||
self.txt_filter.textChanged.connect(lambda: model.set_word_filter(
|
||||
self.txt_filter.text()))
|
||||
table_view.customContextMenuRequested.connect(self._on_context_menu)
|
||||
|
||||
model.refresh_started.connect(self._save_scrollbar)
|
||||
model.refresh_finished.connect(self._set_scrollbar)
|
||||
model.modelReset.connect(self._set_selection)
|
||||
|
||||
self.selection_model = self.table_view.selectionModel()
|
||||
self.selection_model.selectionChanged.connect(self._selection_changed)
|
||||
|
||||
|
||||
def _prepare_menu(self, item, is_multi):
|
||||
action_kwarg_map, actions_mapping, menu = \
|
||||
super()._prepare_menu(item, is_multi)
|
||||
|
||||
if item.status in [lib.STATUS[0], lib.STATUS[1]] or is_multi:
|
||||
action = QtWidgets.QAction("Pause in queue")
|
||||
actions_mapping[action] = self._pause
|
||||
# pause handles which site_name it will pause itself
|
||||
action_kwarg_map[action] = {"selected_ids": self._selected_ids}
|
||||
menu.addAction(action)
|
||||
|
||||
if item.status == lib.STATUS[3] or is_multi:
|
||||
action = QtWidgets.QAction("Unpause in queue")
|
||||
actions_mapping[action] = self._unpause
|
||||
action_kwarg_map[action] = {"selected_ids": self._selected_ids}
|
||||
menu.addAction(action)
|
||||
|
||||
return action_kwarg_map, actions_mapping, menu
|
||||
|
||||
|
||||
class SyncServerDetailWindow(QtWidgets.QDialog):
|
||||
"""Wrapper window for SyncRepresentationDetailWidget
|
||||
|
||||
Creates standalone window with list of files for selected repre_id.
|
||||
"""
|
||||
def __init__(self, sync_server, _id, project, parent=None):
|
||||
log.debug(
|
||||
"!!! SyncServerDetailWindow _id:: {}".format(_id))
|
||||
super(SyncServerDetailWindow, self).__init__(parent)
|
||||
self.setWindowFlags(QtCore.Qt.Window)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
self.setWindowIcon(QtGui.QIcon(style.app_icon_path()))
|
||||
self.resize(1000, 400)
|
||||
|
||||
body = QtWidgets.QWidget()
|
||||
footer = QtWidgets.QWidget()
|
||||
footer.setFixedHeight(20)
|
||||
|
||||
container = SyncRepresentationDetailWidget(sync_server, _id, project,
|
||||
parent=self)
|
||||
body_layout = QtWidgets.QHBoxLayout(body)
|
||||
body_layout.addWidget(container)
|
||||
body_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.message = QtWidgets.QLabel()
|
||||
self.message.hide()
|
||||
|
||||
footer_layout = QtWidgets.QVBoxLayout(footer)
|
||||
footer_layout.addWidget(self.message)
|
||||
footer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(body)
|
||||
layout.addWidget(footer)
|
||||
|
||||
self.setLayout(body_layout)
|
||||
self.setWindowTitle("Sync Representation Detail")
|
||||
|
||||
|
||||
class SyncRepresentationDetailWidget(_SyncRepresentationWidget):
|
||||
"""
|
||||
Widget to display list of synchronizable files for single repre.
|
||||
|
||||
|
|
@ -541,13 +591,12 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget):
|
|||
super(SyncRepresentationDetailWidget, self).__init__(parent)
|
||||
|
||||
log.debug("Representation_id:{}".format(_id))
|
||||
self.representation_id = _id
|
||||
self.item = None # set to item that mouse was clicked over
|
||||
self.project = project
|
||||
|
||||
self.sync_server = sync_server
|
||||
|
||||
self._selected_id = None
|
||||
self.representation_id = _id
|
||||
self._selected_ids = []
|
||||
|
||||
self.txt_filter = QtWidgets.QLineEdit()
|
||||
self.txt_filter.setPlaceholderText("Quick filter representation..")
|
||||
|
|
@ -568,7 +617,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget):
|
|||
table_view.setModel(model)
|
||||
table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
table_view.setSelectionMode(
|
||||
QtWidgets.QAbstractItemView.SingleSelection)
|
||||
QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
table_view.setSelectionBehavior(
|
||||
QtWidgets.QTableView.SelectRows)
|
||||
table_view.horizontalHeader().setSortIndicator(-1, Qt.AscendingOrder)
|
||||
|
|
@ -613,171 +662,118 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget):
|
|||
model.refresh_finished.connect(self._set_scrollbar)
|
||||
model.modelReset.connect(self._set_selection)
|
||||
|
||||
def _selection_changed(self):
|
||||
index = self.selection_model.currentIndex()
|
||||
self._selected_id = self.model.data(index, Qt.UserRole)
|
||||
|
||||
def _set_selection(self):
|
||||
"""
|
||||
Sets selection to 'self._selected_id' if exists.
|
||||
|
||||
Keep selection during model refresh.
|
||||
"""
|
||||
if self._selected_id:
|
||||
index = self.model.get_index(self._selected_id)
|
||||
if index and index.isValid():
|
||||
mode = QtCore.QItemSelectionModel.Select | \
|
||||
QtCore.QItemSelectionModel.Rows
|
||||
self.selection_model.setCurrentIndex(index, mode)
|
||||
else:
|
||||
self._selected_id = None
|
||||
|
||||
def _show_detail(self):
|
||||
def _show_detail(self, selected_ids=None):
|
||||
"""
|
||||
Shows windows with error message for failed sync of a file.
|
||||
"""
|
||||
dt = max(self.item.created_dt, self.item.sync_dt)
|
||||
detail_window = SyncRepresentationErrorWindow(self.item._id,
|
||||
self.project,
|
||||
dt,
|
||||
self.item.tries,
|
||||
self.item.error)
|
||||
detail_window = SyncRepresentationErrorWindow(self.model, selected_ids)
|
||||
|
||||
detail_window.exec()
|
||||
|
||||
def _on_context_menu(self, point):
|
||||
"""
|
||||
Shows menu with loader actions on Right-click.
|
||||
"""
|
||||
point_index = self.table_view.indexAt(point)
|
||||
if not point_index.isValid():
|
||||
return
|
||||
def _prepare_menu(self, item, is_multi):
|
||||
"""Adds view (and model) dependent actions to default ones"""
|
||||
action_kwarg_map, actions_mapping, menu = \
|
||||
super()._prepare_menu(item, is_multi)
|
||||
|
||||
self.item = self.model._data[point_index.row()]
|
||||
|
||||
menu = QtWidgets.QMenu()
|
||||
actions_mapping = {}
|
||||
action_kwarg_map = {}
|
||||
|
||||
local_site = self.item.local_site
|
||||
local_progress = self.item.local_progress
|
||||
remote_site = self.item.remote_site
|
||||
remote_progress = self.item.remote_progress
|
||||
|
||||
for site, progress in {local_site: local_progress,
|
||||
remote_site: remote_progress}.items():
|
||||
project = self.model.project
|
||||
provider = self.sync_server.get_provider_for_site(project,
|
||||
site)
|
||||
if provider == 'local_drive':
|
||||
if 'studio' in site:
|
||||
txt = " studio version"
|
||||
else:
|
||||
txt = " local version"
|
||||
action = QtWidgets.QAction("Open in explorer" + txt)
|
||||
if progress == 1:
|
||||
actions_mapping[action] = self._open_in_explorer
|
||||
action_kwarg_map[action] = {'site': site}
|
||||
menu.addAction(action)
|
||||
|
||||
if self.item.status == lib.STATUS[2]:
|
||||
if item.status == lib.STATUS[2] or is_multi:
|
||||
action = QtWidgets.QAction("Open error detail")
|
||||
actions_mapping[action] = self._show_detail
|
||||
action_kwarg_map[action] = {"selected_ids": self._selected_ids}
|
||||
|
||||
menu.addAction(action)
|
||||
|
||||
if float(remote_progress) == 1.0:
|
||||
action = QtWidgets.QAction("Re-sync active site")
|
||||
actions_mapping[action] = self._reset_local_site
|
||||
menu.addAction(action)
|
||||
|
||||
if float(local_progress) == 1.0:
|
||||
action = QtWidgets.QAction("Re-sync remote site")
|
||||
actions_mapping[action] = self._reset_remote_site
|
||||
menu.addAction(action)
|
||||
|
||||
if not actions_mapping:
|
||||
action = QtWidgets.QAction("< No action >")
|
||||
actions_mapping[action] = None
|
||||
menu.addAction(action)
|
||||
|
||||
result = menu.exec_(QtGui.QCursor.pos())
|
||||
if result:
|
||||
to_run = actions_mapping[result]
|
||||
to_run_kwargs = action_kwarg_map.get(result, {})
|
||||
if to_run:
|
||||
to_run(**to_run_kwargs)
|
||||
|
||||
def _reset_local_site(self):
|
||||
return action_kwarg_map, actions_mapping, menu
|
||||
|
||||
def _reset_site(self, selected_ids=None, site_name=None):
|
||||
"""
|
||||
Removes errors or success metadata for particular file >> forces
|
||||
redo of upload/download
|
||||
"""
|
||||
self.sync_server.reset_provider_for_file(
|
||||
self.model.project,
|
||||
self.representation_id,
|
||||
'local',
|
||||
self.item._id)
|
||||
for file_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, file_id)
|
||||
check_progress = self._get_progress(item, site_name, True)
|
||||
|
||||
# do not reset if opposite side is not fully there
|
||||
if check_progress != 1:
|
||||
log.debug("Not fully available {} on other side, skipping".
|
||||
format(check_progress))
|
||||
continue
|
||||
|
||||
self.sync_server.reset_provider_for_file(
|
||||
self.model.project,
|
||||
self.representation_id,
|
||||
site_name=site_name,
|
||||
file_id=file_id,
|
||||
force=True)
|
||||
self.model.refresh(
|
||||
load_records=self.model._rec_loaded)
|
||||
|
||||
def _reset_remote_site(self):
|
||||
"""
|
||||
Removes errors or success metadata for particular file >> forces
|
||||
redo of upload/download
|
||||
"""
|
||||
self.sync_server.reset_provider_for_file(
|
||||
self.model.project,
|
||||
self.representation_id,
|
||||
'remote',
|
||||
self.item._id)
|
||||
self.model.refresh(
|
||||
load_records=self.model._rec_loaded)
|
||||
|
||||
def _open_in_explorer(self, site):
|
||||
if not self.item:
|
||||
return
|
||||
class SyncRepresentationErrorWindow(QtWidgets.QDialog):
|
||||
"""Wrapper window to show errors during sync on file(s)"""
|
||||
def __init__(self, model, selected_ids, parent=None):
|
||||
super(SyncRepresentationErrorWindow, self).__init__(parent)
|
||||
self.setWindowFlags(QtCore.Qt.Window)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
fpath = self.item.path
|
||||
project = self.project
|
||||
fpath = self.sync_server.get_local_file_path(project, site, fpath)
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
self.setWindowIcon(QtGui.QIcon(style.app_icon_path()))
|
||||
self.resize(900, 150)
|
||||
|
||||
fpath = os.path.normpath(os.path.dirname(fpath))
|
||||
if os.path.isdir(fpath):
|
||||
if 'win' in sys.platform: # windows
|
||||
subprocess.Popen('explorer "%s"' % fpath)
|
||||
elif sys.platform == 'darwin': # macOS
|
||||
subprocess.Popen(['open', fpath])
|
||||
else: # linux
|
||||
try:
|
||||
subprocess.Popen(['xdg-open', fpath])
|
||||
except OSError:
|
||||
raise OSError('unsupported xdg-open call??')
|
||||
body = QtWidgets.QWidget()
|
||||
|
||||
def _save_scrollbar(self):
|
||||
self._scrollbar_pos = self.table_view.verticalScrollBar().value()
|
||||
container = SyncRepresentationErrorWidget(model,
|
||||
selected_ids,
|
||||
parent=self)
|
||||
body_layout = QtWidgets.QHBoxLayout(body)
|
||||
body_layout.addWidget(container)
|
||||
body_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
def _set_scrollbar(self):
|
||||
if self._scrollbar_pos:
|
||||
self.table_view.verticalScrollBar().setValue(self._scrollbar_pos)
|
||||
message = QtWidgets.QLabel()
|
||||
message.hide()
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(body)
|
||||
|
||||
self.setLayout(body_layout)
|
||||
self.setWindowTitle("Sync Representation Error Detail")
|
||||
|
||||
|
||||
class SyncRepresentationErrorWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
Dialog to show when sync error happened, prints error message
|
||||
Dialog to show when sync error happened, prints formatted error message
|
||||
"""
|
||||
|
||||
def __init__(self, _id, dt, tries, msg, parent=None):
|
||||
def __init__(self, model, selected_ids, parent=None):
|
||||
super(SyncRepresentationErrorWidget, self).__init__(parent)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
txts = []
|
||||
txts.append("{}: {}".format("Last update date", pretty_timestamp(dt)))
|
||||
txts.append("{}: {}".format("Retries", str(tries)))
|
||||
txts.append("{}: {}".format("Error message", msg))
|
||||
no_errors = True
|
||||
for file_id in selected_ids:
|
||||
item = lib.get_item_by_id(model, file_id)
|
||||
if not item.created_dt or not item.sync_dt or not item.error:
|
||||
continue
|
||||
|
||||
text_area = QtWidgets.QPlainTextEdit("\n\n".join(txts))
|
||||
text_area.setReadOnly(True)
|
||||
layout.addWidget(text_area)
|
||||
no_errors = False
|
||||
dt = max(item.created_dt, item.sync_dt)
|
||||
|
||||
txts = []
|
||||
txts.append("{}: {}<br>".format("<b>Last update date</b>",
|
||||
pretty_timestamp(dt)))
|
||||
txts.append("{}: {}<br>".format("<b>Retries</b>",
|
||||
str(item.tries)))
|
||||
txts.append("{}: {}<br>".format("<b>Error message</b>",
|
||||
item.error))
|
||||
|
||||
text_area = QtWidgets.QTextEdit("\n\n".join(txts))
|
||||
text_area.setReadOnly(True)
|
||||
layout.addWidget(text_area)
|
||||
|
||||
if no_errors:
|
||||
text_area = QtWidgets.QTextEdit()
|
||||
text_area.setText("<h4>No errors located</h4>")
|
||||
text_area.setReadOnly(True)
|
||||
layout.addWidget(text_area)
|
||||
|
||||
|
||||
class ImageDelegate(QtWidgets.QStyledItemDelegate):
|
||||
|
|
@ -830,72 +826,8 @@ class ImageDelegate(QtWidgets.QStyledItemDelegate):
|
|||
QtGui.QBrush(QtGui.QColor(255, 0, 0, 35)))
|
||||
|
||||
|
||||
class SyncServerDetailWindow(QtWidgets.QDialog):
|
||||
def __init__(self, sync_server, _id, project, parent=None):
|
||||
log.debug(
|
||||
"!!! SyncServerDetailWindow _id:: {}".format(_id))
|
||||
super(SyncServerDetailWindow, self).__init__(parent)
|
||||
self.setWindowFlags(QtCore.Qt.Window)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
self.setWindowIcon(QtGui.QIcon(style.app_icon_path()))
|
||||
self.resize(1000, 400)
|
||||
|
||||
body = QtWidgets.QWidget()
|
||||
footer = QtWidgets.QWidget()
|
||||
footer.setFixedHeight(20)
|
||||
|
||||
container = SyncRepresentationDetailWidget(sync_server, _id, project,
|
||||
parent=self)
|
||||
body_layout = QtWidgets.QHBoxLayout(body)
|
||||
body_layout.addWidget(container)
|
||||
body_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.message = QtWidgets.QLabel()
|
||||
self.message.hide()
|
||||
|
||||
footer_layout = QtWidgets.QVBoxLayout(footer)
|
||||
footer_layout.addWidget(self.message)
|
||||
footer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(body)
|
||||
layout.addWidget(footer)
|
||||
|
||||
self.setLayout(body_layout)
|
||||
self.setWindowTitle("Sync Representation Detail")
|
||||
|
||||
|
||||
class SyncRepresentationErrorWindow(QtWidgets.QDialog):
|
||||
def __init__(self, _id, project, dt, tries, msg, parent=None):
|
||||
super(SyncRepresentationErrorWindow, self).__init__(parent)
|
||||
self.setWindowFlags(QtCore.Qt.Window)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
self.setWindowIcon(QtGui.QIcon(style.app_icon_path()))
|
||||
self.resize(900, 150)
|
||||
|
||||
body = QtWidgets.QWidget()
|
||||
|
||||
container = SyncRepresentationErrorWidget(_id, dt, tries, msg,
|
||||
parent=self)
|
||||
body_layout = QtWidgets.QHBoxLayout(body)
|
||||
body_layout.addWidget(container)
|
||||
body_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
message = QtWidgets.QLabel()
|
||||
message.hide()
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(body)
|
||||
|
||||
self.setLayout(body_layout)
|
||||
self.setWindowTitle("Sync Representation Error Detail")
|
||||
|
||||
|
||||
class TransparentWidget(QtWidgets.QWidget):
|
||||
"""Used for header cell for resizing to work properly"""
|
||||
clicked = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, column_name, *args, **kwargs):
|
||||
|
|
@ -911,7 +843,7 @@ class TransparentWidget(QtWidgets.QWidget):
|
|||
|
||||
|
||||
class HorizontalHeader(QtWidgets.QHeaderView):
|
||||
|
||||
"""Reiplemented QHeaderView to contain clickable changeable button"""
|
||||
def __init__(self, parent=None):
|
||||
super(HorizontalHeader, self).__init__(QtCore.Qt.Horizontal, parent)
|
||||
self._parent = parent
|
||||
|
|
@ -939,6 +871,7 @@ class HorizontalHeader(QtWidgets.QHeaderView):
|
|||
return self._parent.model
|
||||
|
||||
def init_layout(self):
|
||||
"""Initial preparation of header's content"""
|
||||
for column_idx in range(self.model.columnCount()):
|
||||
column_name, column_label = self.model.get_column(column_idx)
|
||||
filter_rec = self.model.get_filters().get(column_name)
|
||||
|
|
@ -958,6 +891,7 @@ class HorizontalHeader(QtWidgets.QHeaderView):
|
|||
self.filter_buttons[column_name] = button
|
||||
|
||||
def showEvent(self, event):
|
||||
"""Paint header"""
|
||||
super(HorizontalHeader, self).showEvent(event)
|
||||
|
||||
for i in range(len(self.header_cells)):
|
||||
|
|
@ -968,6 +902,7 @@ class HorizontalHeader(QtWidgets.QHeaderView):
|
|||
cell_content.show()
|
||||
|
||||
def _set_filter_icon(self, column_name):
|
||||
"""Set different states of button depending on its engagement"""
|
||||
button = self.filter_buttons.get(column_name)
|
||||
if button:
|
||||
if self.checked_values.get(column_name):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue