mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
added first version of logging gui without filtering and NEAT features
This commit is contained in:
parent
56d42251bf
commit
2cbee84e3e
4 changed files with 726 additions and 0 deletions
37
pype/logging/gui/app.py
Normal file
37
pype/logging/gui/app.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from Qt import QtWidgets, QtCore
|
||||
from .widgets import LogsWidget, LogDetailWidget
|
||||
from pypeapp import style
|
||||
|
||||
|
||||
class LogsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(LogsWindow, self).__init__(parent)
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
self.resize(1200, 800)
|
||||
logs_widget = LogsWidget(parent=self)
|
||||
log_detail = LogDetailWidget(parent=self)
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout()
|
||||
|
||||
log_splitter = QtWidgets.QSplitter()
|
||||
log_splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
log_splitter.addWidget(logs_widget)
|
||||
log_splitter.addWidget(log_detail)
|
||||
log_splitter.setStretchFactor(0, 65)
|
||||
log_splitter.setStretchFactor(1, 35)
|
||||
|
||||
main_layout.addWidget(log_splitter)
|
||||
|
||||
self.logs_widget = logs_widget
|
||||
self.log_detail = log_detail
|
||||
|
||||
self.setLayout(main_layout)
|
||||
self.setWindowTitle("Logs")
|
||||
|
||||
self.logs_widget.active_changed.connect(self.on_selection_changed)
|
||||
|
||||
def on_selection_changed(self):
|
||||
index = self.logs_widget.selected_log()
|
||||
node = index.data(self.logs_widget.model.NodeRole)
|
||||
self.log_detail.set_detail(node)
|
||||
94
pype/logging/gui/lib.py
Normal file
94
pype/logging/gui/lib.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import contextlib
|
||||
from Qt import QtCore
|
||||
|
||||
|
||||
def _iter_model_rows(
|
||||
model, column, include_root=False
|
||||
):
|
||||
"""Iterate over all row indices in a model"""
|
||||
indices = [QtCore.QModelIndex()] # start iteration at root
|
||||
|
||||
for index in indices:
|
||||
# Add children to the iterations
|
||||
child_rows = model.rowCount(index)
|
||||
for child_row in range(child_rows):
|
||||
child_index = model.index(child_row, column, index)
|
||||
indices.append(child_index)
|
||||
|
||||
if not include_root and not index.isValid():
|
||||
continue
|
||||
|
||||
yield index
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def preserve_states(
|
||||
tree_view, column=0, role=None,
|
||||
preserve_expanded=True, preserve_selection=True,
|
||||
expanded_role=QtCore.Qt.DisplayRole, selection_role=QtCore.Qt.DisplayRole
|
||||
|
||||
):
|
||||
"""Preserves row selection in QTreeView by column's data role.
|
||||
|
||||
This function is created to maintain the selection status of
|
||||
the model items. When refresh is triggered the items which are expanded
|
||||
will stay expanded and vise versa.
|
||||
|
||||
tree_view (QWidgets.QTreeView): the tree view nested in the application
|
||||
column (int): the column to retrieve the data from
|
||||
role (int): the role which dictates what will be returned
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
# When `role` is set then override both expanded and selection roles
|
||||
if role:
|
||||
expanded_role = role
|
||||
selection_role = role
|
||||
|
||||
model = tree_view.model()
|
||||
selection_model = tree_view.selectionModel()
|
||||
flags = selection_model.Select | selection_model.Rows
|
||||
|
||||
expanded = set()
|
||||
|
||||
if preserve_expanded:
|
||||
for index in _iter_model_rows(
|
||||
model, column=column, include_root=False
|
||||
):
|
||||
if tree_view.isExpanded(index):
|
||||
value = index.data(expanded_role)
|
||||
expanded.add(value)
|
||||
|
||||
selected = None
|
||||
|
||||
if preserve_selection:
|
||||
selected_rows = selection_model.selectedRows()
|
||||
if selected_rows:
|
||||
selected = set(row.data(selection_role) for row in selected_rows)
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if expanded:
|
||||
for index in _iter_model_rows(
|
||||
model, column=0, include_root=False
|
||||
):
|
||||
value = index.data(expanded_role)
|
||||
is_expanded = value in expanded
|
||||
# skip if new index was created meanwhile
|
||||
if is_expanded is None:
|
||||
continue
|
||||
tree_view.setExpanded(index, is_expanded)
|
||||
|
||||
if selected:
|
||||
# Go through all indices, select the ones with similar data
|
||||
for index in _iter_model_rows(
|
||||
model, column=column, include_root=False
|
||||
):
|
||||
value = index.data(selection_role)
|
||||
state = value in selected
|
||||
if state:
|
||||
tree_view.scrollTo(index) # Ensure item is visible
|
||||
selection_model.select(index, flags)
|
||||
169
pype/logging/gui/models.py
Normal file
169
pype/logging/gui/models.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import os
|
||||
from Qt import QtCore
|
||||
from pypeapp import Logger
|
||||
from pypeapp.lib.log import _bootstrap_mongo_log
|
||||
|
||||
log = Logger().get_logger("LogModel", "LoggingModule")
|
||||
|
||||
|
||||
class LogModel(QtCore.QAbstractItemModel):
|
||||
COLUMNS = [
|
||||
"user",
|
||||
"host",
|
||||
"lineNumber",
|
||||
"method",
|
||||
"module",
|
||||
"fileName",
|
||||
"loggerName",
|
||||
"message",
|
||||
"level",
|
||||
"timestamp",
|
||||
]
|
||||
|
||||
colums_mapping = {
|
||||
"user": "User",
|
||||
"host": "Host",
|
||||
"lineNumber": "Line n.",
|
||||
"method": "Method",
|
||||
"module": "Module",
|
||||
"fileName": "File name",
|
||||
"loggerName": "Logger name",
|
||||
"message": "Message",
|
||||
"level": "Level",
|
||||
"timestamp": "Timestamp",
|
||||
}
|
||||
|
||||
NodeRole = QtCore.Qt.UserRole + 1
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(LogModel, self).__init__(parent)
|
||||
self._root_node = Node()
|
||||
|
||||
collection = os.environ.get('PYPE_LOG_MONGO_COL')
|
||||
database = _bootstrap_mongo_log()
|
||||
self.dbcon = None
|
||||
if collection in database.list_collection_names():
|
||||
self.dbcon = database[collection]
|
||||
|
||||
def add_log(self, log):
|
||||
node = Node(log)
|
||||
self._root_node.add_child(node)
|
||||
|
||||
def refresh(self):
|
||||
self.clear()
|
||||
self.beginResetModel()
|
||||
if self.dbcon:
|
||||
result = self.dbcon.find({})
|
||||
for item in result:
|
||||
self.add_log(item)
|
||||
self.endResetModel()
|
||||
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
|
||||
node = index.internalPointer()
|
||||
column = index.column()
|
||||
|
||||
key = self.COLUMNS[column]
|
||||
if key == "timestamp":
|
||||
return str(node.get(key, None))
|
||||
return node.get(key, None)
|
||||
|
||||
if role == self.NodeRole:
|
||||
return index.internalPointer()
|
||||
|
||||
def index(self, row, column, parent):
|
||||
"""Return index for row/column under parent"""
|
||||
|
||||
if not parent.isValid():
|
||||
parent_node = self._root_node
|
||||
else:
|
||||
parent_node = parent.internalPointer()
|
||||
|
||||
child_item = parent_node.child(row)
|
||||
if child_item:
|
||||
return self.createIndex(row, column, child_item)
|
||||
else:
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
def rowCount(self, parent):
|
||||
node = self._root_node
|
||||
if parent.isValid():
|
||||
node = parent.internalPointer()
|
||||
return node.childCount()
|
||||
|
||||
def columnCount(self, parent):
|
||||
return len(self.COLUMNS)
|
||||
|
||||
def parent(self, index):
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if section < len(self.COLUMNS):
|
||||
key = self.COLUMNS[section]
|
||||
return self.colums_mapping.get(key, key)
|
||||
|
||||
super(LogModel, self).headerData(section, orientation, role)
|
||||
|
||||
def flags(self, index):
|
||||
return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
|
||||
|
||||
def clear(self):
|
||||
self.beginResetModel()
|
||||
self._root_node = Node()
|
||||
self.endResetModel()
|
||||
|
||||
|
||||
class Node(dict):
|
||||
"""A node that can be represented in a tree view.
|
||||
|
||||
The node can store data just like a dictionary.
|
||||
|
||||
>>> data = {"name": "John", "score": 10}
|
||||
>>> node = Node(data)
|
||||
>>> assert node["name"] == "John"
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, data=None):
|
||||
super(Node, self).__init__()
|
||||
|
||||
self._children = list()
|
||||
self._parent = None
|
||||
|
||||
if data is not None:
|
||||
assert isinstance(data, dict)
|
||||
self.update(data)
|
||||
|
||||
def childCount(self):
|
||||
return len(self._children)
|
||||
|
||||
def child(self, row):
|
||||
if row >= len(self._children):
|
||||
log.warning("Invalid row as child: {0}".format(row))
|
||||
return
|
||||
|
||||
return self._children[row]
|
||||
|
||||
def children(self):
|
||||
return self._children
|
||||
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
def row(self):
|
||||
"""
|
||||
Returns:
|
||||
int: Index of this node under parent"""
|
||||
if self._parent is not None:
|
||||
siblings = self.parent().children()
|
||||
return siblings.index(self)
|
||||
|
||||
def add_child(self, child):
|
||||
"""Add a child to this node"""
|
||||
child._parent = self
|
||||
self._children.append(child)
|
||||
426
pype/logging/gui/widgets.py
Normal file
426
pype/logging/gui/widgets.py
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
import datetime
|
||||
import inspect
|
||||
from Qt import QtCore, QtWidgets, QtGui
|
||||
from PyQt5.QtCore import QVariant
|
||||
from .models import LogModel
|
||||
|
||||
from .lib import preserve_states
|
||||
|
||||
|
||||
class SearchComboBox(QtWidgets.QComboBox):
|
||||
"""Searchable ComboBox with empty placeholder value as first value"""
|
||||
|
||||
def __init__(self, parent=None, placeholder=""):
|
||||
super(SearchComboBox, self).__init__(parent)
|
||||
|
||||
self.setEditable(True)
|
||||
self.setInsertPolicy(self.NoInsert)
|
||||
self.lineEdit().setPlaceholderText(placeholder)
|
||||
|
||||
# Apply completer settings
|
||||
completer = self.completer()
|
||||
completer.setCompletionMode(completer.PopupCompletion)
|
||||
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
|
||||
# Force style sheet on popup menu
|
||||
# It won't take the parent stylesheet for some reason
|
||||
# todo: better fix for completer popup stylesheet
|
||||
if parent:
|
||||
popup = completer.popup()
|
||||
popup.setStyleSheet(parent.styleSheet())
|
||||
|
||||
self.currentIndexChanged.connect(self.onIndexChange)
|
||||
|
||||
def onIndexChange(self, index):
|
||||
print(index)
|
||||
|
||||
def populate(self, items):
|
||||
self.clear()
|
||||
self.addItems([""]) # ensure first item is placeholder
|
||||
self.addItems(items)
|
||||
|
||||
def get_valid_value(self):
|
||||
"""Return the current text if it's a valid value else None
|
||||
|
||||
Note: The empty placeholder value is valid and returns as ""
|
||||
|
||||
"""
|
||||
|
||||
text = self.currentText()
|
||||
lookup = set(self.itemText(i) for i in range(self.count()))
|
||||
if text not in lookup:
|
||||
return None
|
||||
|
||||
return text
|
||||
|
||||
class CheckableComboBox2(QtWidgets.QComboBox):
|
||||
def __init__(self, parent=None):
|
||||
super(CheckableComboBox, self).__init__(parent)
|
||||
self.view().pressed.connect(self.handleItemPressed)
|
||||
self._changed = False
|
||||
|
||||
def handleItemPressed(self, index):
|
||||
item = self.model().itemFromIndex(index)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self._changed = True
|
||||
|
||||
def hidePopup(self):
|
||||
if not self._changed:
|
||||
super(CheckableComboBox, self).hidePopup()
|
||||
self._changed = False
|
||||
|
||||
def itemChecked(self, index):
|
||||
item = self.model().item(index, self.modelColumn())
|
||||
return item.checkState() == QtCore.Qt.Checked
|
||||
|
||||
def setItemChecked(self, index, checked=True):
|
||||
item = self.model().item(index, self.modelColumn())
|
||||
if checked:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
|
||||
class SelectableMenu(QtWidgets.QMenu):
|
||||
|
||||
selection_changed = QtCore.Signal()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
action = self.activeAction()
|
||||
if action and action.isEnabled():
|
||||
action.trigger()
|
||||
self.selection_changed.emit()
|
||||
else:
|
||||
super(SelectableMenu, self).mouseReleaseEvent(event)
|
||||
|
||||
class CustomCombo(QtWidgets.QWidget):
|
||||
|
||||
selection_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, title, parent=None):
|
||||
super(CustomCombo, self).__init__(parent)
|
||||
toolbutton = QtWidgets.QToolButton(self)
|
||||
toolbutton.setText(title)
|
||||
|
||||
toolmenu = SelectableMenu(self)
|
||||
|
||||
toolbutton.setMenu(toolmenu)
|
||||
toolbutton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(toolbutton)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
# toolmenu.selection_changed.connect(self.on_selection_changed)
|
||||
toolmenu.selection_changed.connect(self.selection_changed)
|
||||
|
||||
self.toolbutton = toolbutton
|
||||
self.toolmenu = toolmenu
|
||||
self.main_layout = layout
|
||||
|
||||
def populate(self, items):
|
||||
self.toolmenu.clear()
|
||||
self.addItems(items)
|
||||
|
||||
def addItems(self, items):
|
||||
for item in items:
|
||||
action = self.toolmenu.addAction(item)
|
||||
action.setCheckable(True)
|
||||
action.setChecked(True)
|
||||
self.toolmenu.addAction(action)
|
||||
|
||||
def items(self):
|
||||
for action in self.toolmenu.actions():
|
||||
yield action
|
||||
|
||||
|
||||
class CheckableComboBox(QtWidgets.QComboBox):
|
||||
def __init__(self, parent=None):
|
||||
super(CheckableComboBox, self).__init__(parent)
|
||||
|
||||
view = QtWidgets.QTreeView()
|
||||
view.header().hide()
|
||||
view.setRootIsDecorated(False)
|
||||
|
||||
model = QtGui.QStandardItemModel()
|
||||
|
||||
view.pressed.connect(self.handleItemPressed)
|
||||
self._changed = False
|
||||
|
||||
self.setView(view)
|
||||
self.setModel(model)
|
||||
|
||||
self.view = view
|
||||
self.model = model
|
||||
|
||||
def handleItemPressed(self, index):
|
||||
item = self.model.itemFromIndex(index)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self._changed = True
|
||||
|
||||
def hidePopup(self):
|
||||
if not self._changed:
|
||||
super(CheckableComboBox, self).hidePopup()
|
||||
self._changed = False
|
||||
|
||||
def itemChecked(self, index):
|
||||
item = self.model.item(index, self.modelColumn())
|
||||
return item.checkState() == QtCore.Qt.Checked
|
||||
|
||||
def setItemChecked(self, index, checked=True):
|
||||
item = self.model.item(index, self.modelColumn())
|
||||
if checked:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
def addItems(self, items):
|
||||
for text, checked in items:
|
||||
text_item = QtGui.QStandardItem(text)
|
||||
checked_item = QtGui.QStandardItem()
|
||||
checked_item.setData(QVariant(checked), QtCore.Qt.CheckStateRole)
|
||||
self.model.appendRow([text_item, checked_item])
|
||||
|
||||
|
||||
class LogsWidget(QtWidgets.QWidget):
|
||||
"""A widget that lists the published subsets for an asset"""
|
||||
|
||||
active_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(LogsWidget, self).__init__(parent=parent)
|
||||
|
||||
model = LogModel()
|
||||
|
||||
filter_layout = QtWidgets.QHBoxLayout()
|
||||
|
||||
# user_filter = SearchComboBox(self, "Users")
|
||||
user_filter = CustomCombo("Users", self)
|
||||
users = model.dbcon.distinct("user")
|
||||
user_filter.populate(users)
|
||||
user_filter.selection_changed.connect(self.user_changed)
|
||||
|
||||
level_filter = CustomCombo("Levels", self)
|
||||
# levels = [(level, True) for level in model.dbcon.distinct("level")]
|
||||
levels = model.dbcon.distinct("level")
|
||||
level_filter.addItems(levels)
|
||||
|
||||
date_from_label = QtWidgets.QLabel("From:")
|
||||
date_filter_from = QtWidgets.QDateTimeEdit()
|
||||
|
||||
date_from_layout = QtWidgets.QVBoxLayout()
|
||||
date_from_layout.addWidget(date_from_label)
|
||||
date_from_layout.addWidget(date_filter_from)
|
||||
|
||||
# now = datetime.datetime.now()
|
||||
# QtCore.QDateTime(now.year, now.month, now.day, now.hour, now.minute, second = 0, msec = 0, timeSpec = 0)
|
||||
date_to_label = QtWidgets.QLabel("To:")
|
||||
date_filter_to = QtWidgets.QDateTimeEdit()
|
||||
|
||||
date_to_layout = QtWidgets.QVBoxLayout()
|
||||
date_to_layout.addWidget(date_to_label)
|
||||
date_to_layout.addWidget(date_filter_to)
|
||||
|
||||
filter_layout.addWidget(user_filter)
|
||||
filter_layout.addWidget(level_filter)
|
||||
|
||||
filter_layout.addLayout(date_from_layout)
|
||||
filter_layout.addLayout(date_to_layout)
|
||||
|
||||
view = QtWidgets.QTreeView(self)
|
||||
view.setAllColumnsShowFocus(True)
|
||||
|
||||
# # Set view delegates
|
||||
# time_delegate = PrettyTimeDelegate()
|
||||
# column = model.COLUMNS.index("time")
|
||||
# view.setItemDelegateForColumn(column, time_delegate)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addLayout(filter_layout)
|
||||
layout.addWidget(view)
|
||||
|
||||
view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
view.setSortingEnabled(True)
|
||||
view.sortByColumn(
|
||||
model.COLUMNS.index("timestamp"),
|
||||
QtCore.Qt.AscendingOrder
|
||||
)
|
||||
|
||||
view.setModel(model)
|
||||
|
||||
view.customContextMenuRequested.connect(self.on_context_menu)
|
||||
view.selectionModel().selectionChanged.connect(self.active_changed)
|
||||
# user_filter.connect()
|
||||
|
||||
# TODO remove if nothing will affect...
|
||||
# header = self.view.header()
|
||||
# # Enforce the columns to fit the data (purely cosmetic)
|
||||
# if Qt.__binding__ in ("PySide2", "PyQt5"):
|
||||
# header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
|
||||
# else:
|
||||
# header.setResizeMode(QtWidgets.QHeaderView.ResizeToContents)
|
||||
|
||||
# Set signals
|
||||
|
||||
# prepare
|
||||
model.refresh()
|
||||
|
||||
# Store to memory
|
||||
self.model = model
|
||||
self.view = view
|
||||
|
||||
self.user_filter = user_filter
|
||||
|
||||
def user_changed(self):
|
||||
for action in self.user_filter.items():
|
||||
print(action)
|
||||
|
||||
def on_context_menu(self, point):
|
||||
# TODO will be any actions? it's ready
|
||||
return
|
||||
|
||||
point_index = self.view.indexAt(point)
|
||||
if not point_index.isValid():
|
||||
return
|
||||
|
||||
# Get selected subsets without groups
|
||||
selection = self.view.selectionModel()
|
||||
rows = selection.selectedRows(column=0)
|
||||
|
||||
def selected_log(self):
|
||||
selection = self.view.selectionModel()
|
||||
rows = selection.selectedRows(column=0)
|
||||
if len(rows) == 1:
|
||||
return rows[0]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class LogDetailWidget(QtWidgets.QWidget):
|
||||
"""A Widget that display information about a specific version"""
|
||||
data_rows = [
|
||||
"user",
|
||||
"message",
|
||||
"level",
|
||||
"logname",
|
||||
"method",
|
||||
"module",
|
||||
"fileName",
|
||||
"lineNumber",
|
||||
"host",
|
||||
"timestamp"
|
||||
]
|
||||
|
||||
html_text = u"""
|
||||
<h3>{user} - {timestamp}</h3>
|
||||
<b>User</b><br>{user}<br>
|
||||
<br><b>Level</b><br>{level}<br>
|
||||
<br><b>Message</b><br>{message}<br>
|
||||
<br><b>Log Name</b><br>{logname}<br><br><b>Method</b><br>{method}<br>
|
||||
<br><b>File</b><br>{fileName}<br>
|
||||
<br><b>Line</b><br>{lineNumber}<br>
|
||||
<br><b>Host</b><br>{host}<br>
|
||||
<br><b>Timestamp</b><br>{timestamp}<br>
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(LogDetailWidget, self).__init__(parent=parent)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
label = QtWidgets.QLabel("Detail")
|
||||
detail_widget = LogDetailTextEdit()
|
||||
detail_widget.setReadOnly(True)
|
||||
layout.addWidget(label)
|
||||
layout.addWidget(detail_widget)
|
||||
|
||||
self.detail_widget = detail_widget
|
||||
|
||||
self.setEnabled(True)
|
||||
|
||||
self.set_detail(None)
|
||||
|
||||
def set_detail(self, detail_data):
|
||||
if not detail_data:
|
||||
self.detail_widget.setText("")
|
||||
return
|
||||
|
||||
data = dict()
|
||||
for row in self.data_rows:
|
||||
value = detail_data.get(row) or "< Not set >"
|
||||
data[row] = value
|
||||
|
||||
|
||||
self.detail_widget.setHtml(self.html_text.format(**data))
|
||||
|
||||
|
||||
class LogDetailTextEdit(QtWidgets.QTextEdit):
|
||||
"""QTextEdit that displays version specific information.
|
||||
|
||||
This also overrides the context menu to add actions like copying
|
||||
source path to clipboard or copying the raw data of the version
|
||||
to clipboard.
|
||||
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
super(LogDetailTextEdit, self).__init__(parent=parent)
|
||||
|
||||
# self.data = {
|
||||
# "source": None,
|
||||
# "raw": None
|
||||
# }
|
||||
#
|
||||
# def contextMenuEvent(self, event):
|
||||
# """Context menu with additional actions"""
|
||||
# menu = self.createStandardContextMenu()
|
||||
#
|
||||
# # Add additional actions when any text so we can assume
|
||||
# # the version is set.
|
||||
# if self.toPlainText().strip():
|
||||
#
|
||||
# menu.addSeparator()
|
||||
# action = QtWidgets.QAction("Copy source path to clipboard",
|
||||
# menu)
|
||||
# action.triggered.connect(self.on_copy_source)
|
||||
# menu.addAction(action)
|
||||
#
|
||||
# action = QtWidgets.QAction("Copy raw data to clipboard",
|
||||
# menu)
|
||||
# action.triggered.connect(self.on_copy_raw)
|
||||
# menu.addAction(action)
|
||||
#
|
||||
# menu.exec_(event.globalPos())
|
||||
# del menu
|
||||
#
|
||||
# def on_copy_source(self):
|
||||
# """Copy formatted source path to clipboard"""
|
||||
# source = self.data.get("source", None)
|
||||
# if not source:
|
||||
# return
|
||||
#
|
||||
# # path = source.format(root=api.registered_root())
|
||||
# # clipboard = QtWidgets.QApplication.clipboard()
|
||||
# # clipboard.setText(path)
|
||||
#
|
||||
# def on_copy_raw(self):
|
||||
# """Copy raw version data to clipboard
|
||||
#
|
||||
# The data is string formatted with `pprint.pformat`.
|
||||
#
|
||||
# """
|
||||
# raw = self.data.get("raw", None)
|
||||
# if not raw:
|
||||
# return
|
||||
#
|
||||
# raw_text = pprint.pformat(raw)
|
||||
# clipboard = QtWidgets.QApplication.clipboard()
|
||||
# clipboard.setText(raw_text)
|
||||
Loading…
Add table
Add a link
Reference in a new issue