[ui] NodeEditor: refactor ChunksList and add global stats

This commit is contained in:
Fabien Castan 2021-01-22 09:40:07 +01:00
parent bd5d447d12
commit 831443c29d
9 changed files with 415 additions and 307 deletions

View file

@ -58,12 +58,12 @@ class ExecMode(Enum):
EXTERN = 2 EXTERN = 2
class StatusData: class StatusData(BaseObject):
""" """
""" """
dateTimeFormatting = '%Y-%m-%d %H:%M:%S.%f' dateTimeFormatting = '%Y-%m-%d %H:%M:%S.%f'
def __init__(self, nodeName, nodeType, packageName, packageVersion): def __init__(self, nodeName='', nodeType='', packageName='', packageVersion=''):
self.status = Status.NONE self.status = Status.NONE
self.execMode = ExecMode.NONE self.execMode = ExecMode.NONE
self.nodeName = nodeName self.nodeName = nodeName
@ -79,6 +79,11 @@ class StatusData:
self.hostname = "" self.hostname = ""
self.sessionUid = meshroom.core.sessionUid self.sessionUid = meshroom.core.sessionUid
def merge(self, other):
self.startDateTime = min(self.startDateTime, other.startDateTime)
self.endDateTime = max(self.endDateTime, other.endDateTime)
self.elapsedTime += other.elapsedTime
def reset(self): def reset(self):
self.status = Status.NONE self.status = Status.NONE
self.execMode = ExecMode.NONE self.execMode = ExecMode.NONE
@ -112,8 +117,12 @@ class StatusData:
return d return d
def fromDict(self, d): def fromDict(self, d):
self.status = getattr(Status, d.get('status', ''), Status.NONE) self.status = d.get('status', Status.NONE)
self.execMode = getattr(ExecMode, d.get('execMode', ''), ExecMode.NONE) if not isinstance(self.status, Status):
self.status = Status[self.status]
self.execMode = d.get('execMode', ExecMode.NONE)
if not isinstance(self.execMode, ExecMode):
self.execMode = ExecMode[self.execMode]
self.nodeName = d.get('nodeName', '') self.nodeName = d.get('nodeName', '')
self.nodeType = d.get('nodeType', '') self.nodeType = d.get('nodeType', '')
self.packageName = d.get('packageName', '') self.packageName = d.get('packageName', '')
@ -236,7 +245,7 @@ class NodeChunk(BaseObject):
self.node = node self.node = node
self.range = range self.range = range
self.logManager = LogManager(self) self.logManager = LogManager(self)
self.status = StatusData(node.name, node.nodeType, node.packageName, node.packageVersion) self._status = StatusData(node.name, node.nodeType, node.packageName, node.packageVersion)
self.statistics = stats.Statistics() self.statistics = stats.Statistics()
self.statusFileLastModTime = -1 self.statusFileLastModTime = -1
self._subprocess = None self._subprocess = None
@ -258,7 +267,7 @@ class NodeChunk(BaseObject):
@property @property
def statusName(self): def statusName(self):
return self.status.status.name return self._status.status.name
@property @property
def logger(self): def logger(self):
@ -266,24 +275,24 @@ class NodeChunk(BaseObject):
@property @property
def execModeName(self): def execModeName(self):
return self.status.execMode.name return self._status.execMode.name
def updateStatusFromCache(self): def updateStatusFromCache(self):
""" """
Update node status based on status file content/existence. Update node status based on status file content/existence.
""" """
statusFile = self.statusFile statusFile = self.statusFile
oldStatus = self.status.status oldStatus = self._status.status
# No status file => reset status to Status.None # No status file => reset status to Status.None
if not os.path.exists(statusFile): if not os.path.exists(statusFile):
self.statusFileLastModTime = -1 self.statusFileLastModTime = -1
self.status.reset() self._status.reset()
else: else:
with open(statusFile, 'r') as jsonFile: with open(statusFile, 'r') as jsonFile:
statusData = json.load(jsonFile) statusData = json.load(jsonFile)
self.status.fromDict(statusData) self._status.fromDict(statusData)
self.statusFileLastModTime = os.path.getmtime(statusFile) self.statusFileLastModTime = os.path.getmtime(statusFile)
if oldStatus != self.status.status: if oldStatus != self._status.status:
self.statusChanged.emit() self.statusChanged.emit()
@property @property
@ -311,7 +320,7 @@ class NodeChunk(BaseObject):
""" """
Write node status on disk. Write node status on disk.
""" """
data = self.status.toDict() data = self._status.toDict()
statusFilepath = self.statusFile statusFilepath = self.statusFile
folder = os.path.dirname(statusFilepath) folder = os.path.dirname(statusFilepath)
if not os.path.exists(folder): if not os.path.exists(folder):
@ -322,16 +331,16 @@ class NodeChunk(BaseObject):
renameWritingToFinalPath(statusFilepathWriting, statusFilepath) renameWritingToFinalPath(statusFilepathWriting, statusFilepath)
def upgradeStatusTo(self, newStatus, execMode=None): def upgradeStatusTo(self, newStatus, execMode=None):
if newStatus.value <= self.status.status.value: if newStatus.value <= self._status.status.value:
print('WARNING: downgrade status on node "{}" from {} to {}'.format(self.name, self.status.status, logging.warning('Downgrade status on node "{}" from {} to {}'.format(self.name, self._status.status,
newStatus)) newStatus))
if newStatus == Status.SUBMITTED: if newStatus == Status.SUBMITTED:
self.status = StatusData(self.node.name, self.node.nodeType, self.node.packageName, self.node.packageVersion) self._status = StatusData(self.node.name, self.node.nodeType, self.node.packageName, self.node.packageVersion)
if execMode is not None: if execMode is not None:
self.status.execMode = execMode self._status.execMode = execMode
self.execModeNameChanged.emit() self.execModeNameChanged.emit()
self.status.status = newStatus self._status.status = newStatus
self.saveStatusFile() self.saveStatusFile()
self.statusChanged.emit() self.statusChanged.emit()
@ -360,24 +369,24 @@ class NodeChunk(BaseObject):
renameWritingToFinalPath(statisticsFilepathWriting, statisticsFilepath) renameWritingToFinalPath(statisticsFilepathWriting, statisticsFilepath)
def isAlreadySubmitted(self): def isAlreadySubmitted(self):
return self.status.status in (Status.SUBMITTED, Status.RUNNING) return self._status.status in (Status.SUBMITTED, Status.RUNNING)
def isAlreadySubmittedOrFinished(self): def isAlreadySubmittedOrFinished(self):
return self.status.status in (Status.SUBMITTED, Status.RUNNING, Status.SUCCESS) return self._status.status in (Status.SUBMITTED, Status.RUNNING, Status.SUCCESS)
def isFinishedOrRunning(self): def isFinishedOrRunning(self):
return self.status.status in (Status.SUCCESS, Status.RUNNING) return self._status.status in (Status.SUCCESS, Status.RUNNING)
def isStopped(self): def isStopped(self):
return self.status.status == Status.STOPPED return self._status.status == Status.STOPPED
def process(self, forceCompute=False): def process(self, forceCompute=False):
if not forceCompute and self.status.status == Status.SUCCESS: if not forceCompute and self._status.status == Status.SUCCESS:
print("Node chunk already computed:", self.name) logging.info("Node chunk already computed: {}".format(self.name))
return return
global runningProcesses global runningProcesses
runningProcesses[self.name] = self runningProcesses[self.name] = self
self.status.initStartCompute() self._status.initStartCompute()
startTime = time.time() startTime = time.time()
self.upgradeStatusTo(Status.RUNNING) self.upgradeStatusTo(Status.RUNNING)
self.statThread = stats.StatisticsThread(self) self.statThread = stats.StatisticsThread(self)
@ -385,16 +394,16 @@ class NodeChunk(BaseObject):
try: try:
self.node.nodeDesc.processChunk(self) self.node.nodeDesc.processChunk(self)
except Exception as e: except Exception as e:
if self.status.status != Status.STOPPED: if self._status.status != Status.STOPPED:
self.upgradeStatusTo(Status.ERROR) self.upgradeStatusTo(Status.ERROR)
raise raise
except (KeyboardInterrupt, SystemError, GeneratorExit) as e: except (KeyboardInterrupt, SystemError, GeneratorExit) as e:
self.upgradeStatusTo(Status.STOPPED) self.upgradeStatusTo(Status.STOPPED)
raise raise
finally: finally:
self.status.initEndCompute() self._status.initEndCompute()
self.status.elapsedTime = time.time() - startTime self._status.elapsedTime = time.time() - startTime
print(' - elapsed time:', self.status.elapsedTimeStr) logging.info(' - elapsed time: {}'.format(self._status.elapsedTimeStr))
# ask and wait for the stats thread to stop # ask and wait for the stats thread to stop
self.statThread.stopRequest() self.statThread.stopRequest()
self.statThread.join() self.statThread.join()
@ -408,9 +417,10 @@ class NodeChunk(BaseObject):
self.node.nodeDesc.stopProcess(self) self.node.nodeDesc.stopProcess(self)
def isExtern(self): def isExtern(self):
return self.status.execMode == ExecMode.EXTERN return self._status.execMode == ExecMode.EXTERN
statusChanged = Signal() statusChanged = Signal()
status = Property(Variant, lambda self: self._status, notify=statusChanged)
statusName = Property(str, statusName.fget, notify=statusChanged) statusName = Property(str, statusName.fget, notify=statusChanged)
execModeNameChanged = Signal() execModeNameChanged = Signal()
execModeName = Property(str, execModeName.fget, notify=execModeNameChanged) execModeName = Property(str, execModeName.fget, notify=execModeNameChanged)
@ -422,7 +432,7 @@ class NodeChunk(BaseObject):
statisticsFile = Property(str, statisticsFile.fget, notify=nodeFolderChanged) statisticsFile = Property(str, statisticsFile.fget, notify=nodeFolderChanged)
nodeName = Property(str, lambda self: self.node.name, constant=True) nodeName = Property(str, lambda self: self.node.name, constant=True)
statusNodeName = Property(str, lambda self: self.status.nodeName, constant=True) statusNodeName = Property(str, lambda self: self._status.nodeName, constant=True)
# simple structure for storing node position # simple structure for storing node position
@ -837,6 +847,24 @@ class BaseNode(BaseObject):
return Status.NONE return Status.NONE
@Slot(result=StatusData)
def getFusedStatus(self):
fusedStatus = StatusData()
if self._chunks:
fusedStatus.fromDict(self._chunks[0].status.toDict())
for chunk in self._chunks[1:]:
fusedStatus.merge(chunk.status)
fusedStatus.status = self.getGlobalStatus()
return fusedStatus
@Slot(result=StatusData)
def getRecursiveFusedStatus(self):
fusedStatus = self.getFusedStatus()
nodes = self.getInputNodes(recursive=True, dependenciesOnly=True)
for node in nodes:
fusedStatus.merge(node.fusedStatus)
return fusedStatus
@property @property
def globalExecMode(self): def globalExecMode(self):
return self._chunks.at(0).execModeName return self._chunks.at(0).execModeName
@ -1000,6 +1028,10 @@ class BaseNode(BaseObject):
size = Property(int, getSize, notify=sizeChanged) size = Property(int, getSize, notify=sizeChanged)
globalStatusChanged = Signal() globalStatusChanged = Signal()
globalStatus = Property(str, lambda self: self.getGlobalStatus().name, notify=globalStatusChanged) globalStatus = Property(str, lambda self: self.getGlobalStatus().name, notify=globalStatusChanged)
fusedStatus = Property(StatusData, getFusedStatus, notify=globalStatusChanged)
elapsedTime = Property(float, lambda self: self.getFusedStatus().elapsedTime, notify=globalStatusChanged)
recursiveElapsedTime = Property(float, lambda self: self.getRecursiveFusedStatus().elapsedTime, notify=globalStatusChanged)
globalExecModeChanged = Signal() globalExecModeChanged = Signal()
globalExecMode = Property(str, globalExecMode.fget, notify=globalExecModeChanged) globalExecMode = Property(str, globalExecMode.fget, notify=globalExecModeChanged)
isComputed = Property(bool, _isComputed, notify=globalStatusChanged) isComputed = Property(bool, _isComputed, notify=globalStatusChanged)

View file

@ -0,0 +1,52 @@
import QtQuick 2.7
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import MaterialIcons 2.2
/**
* KeyValue allows to create a list of key/value, like a table.
*/
Rectangle {
property alias key: keyLabel.text
property alias value: valueText.text
color: activePalette.window
width: parent.width
height: childrenRect.height
RowLayout {
width: parent.width
Rectangle {
anchors.margins: 2
color: Qt.darker(activePalette.window, 1.1)
// Layout.preferredWidth: sizeHandle.x
Layout.minimumWidth: 10.0 * Qt.application.font.pixelSize
Layout.maximumWidth: 15.0 * Qt.application.font.pixelSize
Layout.fillWidth: false
Layout.fillHeight: true
Label {
id: keyLabel
text: "test"
anchors.fill: parent
anchors.top: parent.top
topPadding: 4
leftPadding: 6
verticalAlignment: TextEdit.AlignTop
elide: Text.ElideRight
}
}
TextArea {
id: valueText
text: ""
anchors.margins: 2
Layout.fillWidth: true
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
textFormat: TextEdit.PlainText
readOnly: true
selectByMouse: true
background: Rectangle { anchors.fill: parent; color: Qt.darker(activePalette.window, 1.05) }
}
}
}

View file

@ -3,6 +3,7 @@ module Controls
ColorChart 1.0 ColorChart.qml ColorChart 1.0 ColorChart.qml
FloatingPane 1.0 FloatingPane.qml FloatingPane 1.0 FloatingPane.qml
Group 1.0 Group.qml Group 1.0 Group.qml
KeyValue 1.0 KeyValue.qml
MessageDialog 1.0 MessageDialog.qml MessageDialog 1.0 MessageDialog.qml
Panel 1.0 Panel.qml Panel 1.0 Panel.qml
SearchBar 1.0 SearchBar.qml SearchBar 1.0 SearchBar.qml

View file

@ -10,35 +10,64 @@ import "common.js" as Common
/** /**
* ChunkListView * ChunkListView
*/ */
ListView { ColumnLayout {
id: chunksLV id: root
property variant chunks
property int currentIndex: 0
property variant currentChunk: (chunks && currentIndex >= 0) ? chunks.at(currentIndex) : undefined
// model: node.chunks onChunksChanged: {
// When the list changes, ensure the current index is in the new range
if(currentIndex >= chunks.count)
currentIndex = chunks.count-1
}
property variant currentChunk: currentItem ? currentItem.chunk : undefined // chunksSummary is in sync with allChunks button (but not directly accessible as it is in a Component)
property bool chunksSummary: (currentIndex === -1)
width: 60 width: 60
ListView {
id: chunksLV
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
highlightFollowsCurrentItem: true
model: root.chunks
highlightFollowsCurrentItem: (root.chunksSummary === false)
keyNavigationEnabled: true keyNavigationEnabled: true
focus: true focus: true
currentIndex: 0 currentIndex: root.currentIndex
onCurrentIndexChanged: {
signal changeCurrentChunk(int chunkIndex) if(chunksLV.currentIndex !== root.currentIndex)
{
// When the list is resized, the currentIndex is reset to 0.
// So here we force it to keep the binding.
chunksLV.currentIndex = Qt.binding(function() { return root.currentIndex })
}
}
header: Component { header: Component {
Label { Button {
width: chunksLV.width id: allChunks
elide: Label.ElideRight
text: "Chunks" text: "Chunks"
padding: 4 width: parent.width
z: 10 flat: true
background: Rectangle { color: parent.palette.window } checkable: true
property bool summaryEnabled: root.chunksSummary
checked: summaryEnabled
onSummaryEnabledChanged: {
checked = summaryEnabled
}
onClicked: {
root.currentIndex = -1
checked = true
}
} }
} }
highlight: Component { highlight: Component {
Rectangle { Rectangle {
visible: true // !root.chunksSummary
color: activePalette.highlight color: activePalette.highlight
opacity: 0.3 opacity: 0.3
z: 2 z: 2
@ -55,7 +84,7 @@ ListView {
leftPadding: 8 leftPadding: 8
onClicked: { onClicked: {
chunksLV.forceActiveFocus() chunksLV.forceActiveFocus()
chunksLV.changeCurrentChunk(index) root.currentIndex = index
} }
Rectangle { Rectangle {
width: 4 width: 4
@ -64,3 +93,4 @@ ListView {
} }
} }
} }
}

View file

@ -1,5 +1,6 @@
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtQuick.Controls 1.4 as Controls1 // SplitView
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import MaterialIcons 2.2 import MaterialIcons 2.2
import Controls 1.0 import Controls 1.0
@ -19,15 +20,6 @@ Panel {
signal attributeDoubleClicked(var mouse, var attribute) signal attributeDoubleClicked(var mouse, var attribute)
signal upgradeRequest() signal upgradeRequest()
Item {
id: m
property int chunkCurrentIndex: 0
}
onNodeChanged: {
m.chunkCurrentIndex = 0 // Needed to avoid invalid state of ChunksListView
}
title: "Node" + (node !== null ? " - <b>" + node.label + "</b>" : "") title: "Node" + (node !== null ? " - <b>" + node.label + "</b>" : "")
icon: MaterialLabel { text: MaterialIcons.tune } icon: MaterialLabel { text: MaterialIcons.tune }
@ -114,7 +106,16 @@ Panel {
Component { Component {
id: editor_component id: editor_component
ColumnLayout { Controls1.SplitView {
anchors.fill: parent
// The list of chunks
ChunksListView {
id: chunksLV
visible: (tabBar.currentIndex >= 1 && tabBar.currentIndex <= 3)
chunks: root.node.chunks
}
StackLayout { StackLayout {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
@ -122,35 +123,65 @@ Panel {
currentIndex: tabBar.currentIndex currentIndex: tabBar.currentIndex
AttributeEditor { AttributeEditor {
Layout.fillHeight: true
Layout.fillWidth: true
model: root.node.attributes model: root.node.attributes
readOnly: root.readOnly || root.isCompatibilityNode readOnly: root.readOnly || root.isCompatibilityNode
onAttributeDoubleClicked: root.attributeDoubleClicked(mouse, attribute) onAttributeDoubleClicked: root.attributeDoubleClicked(mouse, attribute)
onUpgradeRequest: root.upgradeRequest() onUpgradeRequest: root.upgradeRequest()
} }
NodeLog { Loader {
active: (tabBar.currentIndex === 1)
Layout.fillHeight: true
Layout.fillWidth: true
sourceComponent: NodeLog {
// anchors.fill: parent
Layout.fillHeight: true
Layout.fillWidth: true
width: parent.width
height: parent.height
id: nodeLog id: nodeLog
node: root.node node: root.node
chunkCurrentIndex: m.chunkCurrentIndex currentChunkIndex: chunksLV.currentIndex
onChangeCurrentChunk: { m.chunkCurrentIndex = chunkIndex } currentChunk: chunksLV.currentChunk
}
} }
NodeStatistics { Loader {
active: (tabBar.currentIndex === 2)
Layout.fillHeight: true
Layout.fillWidth: true
sourceComponent: NodeStatistics {
id: nodeStatistics id: nodeStatistics
Layout.fillHeight: true
Layout.fillWidth: true
node: root.node node: root.node
chunkCurrentIndex: m.chunkCurrentIndex currentChunkIndex: chunksLV.currentIndex
onChangeCurrentChunk: { m.chunkCurrentIndex = chunkIndex } currentChunk: chunksLV.currentChunk
}
} }
NodeStatus { Loader {
active: (tabBar.currentIndex === 3)
Layout.fillHeight: true
Layout.fillWidth: true
sourceComponent: NodeStatus {
id: nodeStatus id: nodeStatus
Layout.fillHeight: true
Layout.fillWidth: true
node: root.node node: root.node
chunkCurrentIndex: m.chunkCurrentIndex currentChunkIndex: chunksLV.currentIndex
onChangeCurrentChunk: { m.chunkCurrentIndex = chunkIndex } currentChunk: chunksLV.currentChunk
}
} }
NodeDocumentation { NodeDocumentation {
id: nodeDocumentation id: nodeDocumentation
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
node: root.node node: root.node
} }

View file

@ -1,6 +1,5 @@
import QtQuick 2.11 import QtQuick 2.11
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Controls 1.4 as Controls1 // SplitView
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import MaterialIcons 2.2 import MaterialIcons 2.2
import Controls 1.0 import Controls 1.0
@ -16,53 +15,34 @@ import "common.js" as Common
FocusScope { FocusScope {
id: root id: root
property variant node property variant node
property alias chunkCurrentIndex: chunksLV.currentIndex property int currentChunkIndex
signal changeCurrentChunk(int chunkIndex) property variant currentChunk
Layout.fillWidth: true
Layout.fillHeight: true
SystemPalette { id: activePalette } SystemPalette { id: activePalette }
Controls1.SplitView {
anchors.fill: parent
// The list of chunks
ChunksListView {
id: chunksLV
Layout.fillHeight: true
model: node.chunks
onChangeCurrentChunk: root.changeCurrentChunk(chunkIndex)
}
Loader { Loader {
id: componentLoader id: componentLoader
clip: true clip: true
Layout.fillWidth: true anchors.fill: parent
Layout.fillHeight: true
property url source
property string currentFile: chunksLV.currentChunk ? chunksLV.currentChunk["logFile"] : "" property string currentFile: (root.currentChunkIndex >= 0 && root.currentChunk) ? root.currentChunk["logFile"] : ""
onCurrentFileChanged: { property url source: Filepath.stringToUrl(currentFile)
// only set text file viewer source when ListView is fully ready
// (either empty or fully populated with a valid currentChunk)
// to avoid going through an empty url when switching between two nodes
if(!chunksLV.count || chunksLV.currentChunk)
componentLoader.source = Filepath.stringToUrl(currentFile);
}
sourceComponent: textFileViewerComponent sourceComponent: textFileViewerComponent
} }
Component { Component {
id: textFileViewerComponent id: textFileViewerComponent
TextFileViewer { TextFileViewer {
id: textFileViewer id: textFileViewer
anchors.fill: parent
source: componentLoader.source source: componentLoader.source
Layout.fillWidth: true autoReload: root.currentChunk !== undefined && root.currentChunk.statusName === "RUNNING"
Layout.fillHeight: true
autoReload: chunksLV.currentChunk !== undefined && chunksLV.currentChunk.statusName === "RUNNING"
// source is set in fileSelector // source is set in fileSelector
} }
} }
} }
}

View file

@ -4,6 +4,7 @@ import QtQuick.Controls 1.4 as Controls1 // SplitView
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import MaterialIcons 2.2 import MaterialIcons 2.2
import Controls 1.0 import Controls 1.0
import Utils 1.0
import "common.js" as Common import "common.js" as Common
@ -15,50 +16,44 @@ import "common.js" as Common
*/ */
FocusScope { FocusScope {
id: root id: root
property variant node property variant node
property alias chunkCurrentIndex: chunksLV.currentIndex property variant currentChunkIndex
signal changeCurrentChunk(int chunkIndex) property variant currentChunk
SystemPalette { id: activePalette } SystemPalette { id: activePalette }
Controls1.SplitView {
anchors.fill: parent
// The list of chunks
ChunksListView {
id: chunksLV
Layout.fillHeight: true
model: node.chunks
onChangeCurrentChunk: root.changeCurrentChunk(chunkIndex)
}
Loader { Loader {
id: componentLoader id: componentLoader
clip: true clip: true
Layout.fillWidth: true anchors.fill: parent
Layout.fillHeight: true property string currentFile: currentChunk ? currentChunk["statisticsFile"] : ""
property url source property url source: Filepath.stringToUrl(currentFile)
property string currentFile: chunksLV.currentChunk ? chunksLV.currentChunk["statisticsFile"] : "" sourceComponent: chunksLV.chunksSummary ? statViewerComponent : chunkStatViewerComponent
onCurrentFileChanged: {
// only set text file viewer source when ListView is fully ready
// (either empty or fully populated with a valid currentChunk)
// to avoid going through an empty url when switching between two nodes
if(!chunksLV.count || chunksLV.currentChunk)
componentLoader.source = Filepath.stringToUrl(currentFile);
} }
sourceComponent: statViewerComponent Component {
id: chunkStatViewerComponent
StatViewer {
id: statViewer
anchors.fill: parent
source: componentLoader.source
}
} }
Component { Component {
id: statViewerComponent id: statViewerComponent
StatViewer {
id: statViewer Column {
Layout.fillWidth: true spacing: 2
Layout.fillHeight: true KeyValue {
source: componentLoader.source key: "Time"
value: Format.sec2time(node.elapsedTime)
}
KeyValue {
key: "Cumulated Time"
value: Format.sec2time(node.recursiveElapsedTime)
} }
} }
} }

View file

@ -1,6 +1,5 @@
import QtQuick 2.11 import QtQuick 2.11
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Controls 1.4 as Controls1 // SplitView
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import MaterialIcons 2.2 import MaterialIcons 2.2
import Controls 1.0 import Controls 1.0
@ -16,38 +15,18 @@ import "common.js" as Common
FocusScope { FocusScope {
id: root id: root
property variant node property variant node
property alias chunkCurrentIndex: chunksLV.currentIndex property variant currentChunkIndex
signal changeCurrentChunk(int chunkIndex) property variant currentChunk
SystemPalette { id: activePalette } SystemPalette { id: activePalette }
Controls1.SplitView {
anchors.fill: parent
// The list of chunks
ChunksListView {
id: chunksLV
Layout.fillHeight: true
model: node.chunks
onChangeCurrentChunk: root.changeCurrentChunk(chunkIndex)
}
Loader { Loader {
id: componentLoader id: componentLoader
clip: true clip: true
Layout.fillWidth: true anchors.fill: parent
Layout.fillHeight: true
property url source
property string currentFile: chunksLV.currentChunk ? chunksLV.currentChunk["statusFile"] : "" property string currentFile: (root.currentChunkIndex >= 0) ? root.currentChunk["statusFile"] : ""
onCurrentFileChanged: { property url source: Filepath.stringToUrl(currentFile)
// only set text file viewer source when ListView is fully ready
// (either empty or fully populated with a valid currentChunk)
// to avoid going through an empty url when switching between two nodes
if(!chunksLV.count || chunksLV.currentChunk)
componentLoader.source = Filepath.stringToUrl(currentFile);
}
sourceComponent: statViewerComponent sourceComponent: statViewerComponent
} }
@ -184,4 +163,3 @@ FocusScope {
} }
} }
} }
}

View file

@ -13,3 +13,12 @@ function plainToHtml(t) {
var escaped = t.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); // escape text var escaped = t.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); // escape text
return escaped.replace(/\n/g, '<br>'); // replace line breaks return escaped.replace(/\n/g, '<br>'); // replace line breaks
} }
function sec2time(time) {
var pad = function(num, size) { return ('000' + num).slice(size * -1); },
hours = Math.floor(time / 60 / 60),
minutes = Math.floor(time / 60) % 60,
seconds = Math.floor(time - minutes * 60);
return pad(hours, 2) + ':' + pad(minutes, 2) + ':' + pad(seconds, 2)
}