Index: sources/gui/qml/pages/settings/SettingsExportLogs.qml =================================================================== diff -u -r86e9dfbff50cb7e16fd94c16c1c818cef3b47eac -r5687815256ae070a9a207107088e3f72dd464da0 --- sources/gui/qml/pages/settings/SettingsExportLogs.qml (.../SettingsExportLogs.qml) (revision 86e9dfbff50cb7e16fd94c16c1c818cef3b47eac) +++ sources/gui/qml/pages/settings/SettingsExportLogs.qml (.../SettingsExportLogs.qml) (revision 5687815256ae070a9a207107088e3f72dd464da0) @@ -1,15 +1,15 @@ /*! * - * Copyright (c) 2021-2022 Diality Inc. - All Rights Reserved. + * Copyright (c) 2022-2024 Diality Inc. - All Rights Reserved. * \copyright * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * - * \file SettingsVolumeBrightness.qml + * \file SettingsExportLogs.qml * \author (last) Behrouz NematiPour - * \date (last) 07-Jun-2021 + * \date (last) 04-Apr-2024 * \author (original) Behrouz NematiPour - * \date (original) 06-Jun-2021 + * \date (original) 27-Jun-2022 * */ @@ -21,7 +21,8 @@ // TODO: Add the fileCopy function to have control over the copy // TODO: Add cancel -// TODO: Add Service and Treatment log export and filter +// TODO: Add Service and Treatment log export and exit + // TODO: Add selecction export // TODO: Add error display @@ -36,16 +37,135 @@ /*! * \brief SettingsExportLogs is used to Export logs, and display the SD-Card and USB device informaiton and list of files. */ -SettingsBase { id: _root - itemIndex : SettingsStack.ExportLogs +SettingsBase { id: _root + itemIndex : SettingsStack.ExportLogs + + //////////////////////////////////////////////////////////////////////////////// + // FIXME: The model being used here is the QML FileListModel. // + // This model is so limited and do not update well. // + // This model definitely has to be replaced with a C++ one, // + // like the other MVC classes we have. // + //////////////////////////////////////////////////////////////////////////////// + + readonly property string sdcLabel : qsTr("SD-Card") + readonly property string devUnit : "MB" // no translation + readonly property string devUnitLabel : " (" + devUnit + ")\n" // no translation + readonly property string usbLabel : qsTr("USB Drive") + readonly property int headetRowHight : 50 + + readonly property int typeIndexApplication : 0 + readonly property int typeIndexService : 1 + readonly property int typeIndexTreatment : 2 + + readonly property string typeLabelApplication : qsTr("Application" ) + readonly property string typeLabelService : qsTr("Service" ) + readonly property string typeLabelTreatment : qsTr("Treatment" ) + + property bool isDevice : _GuiView.runOnDevice + readonly property string typePathClr : "" + readonly property string typePathPrefix : "file://" + readonly property string typePathTxr : typePathPrefix + (isDevice ? "/var/configurations" : "/home/denali/Desktop/sd-card" ) + readonly property string typePathSrc : typePathPrefix + (isDevice ? "/media/sd-card" : "/home/denali/Desktop/sd-card" ) + readonly property string typePathDst : typePathPrefix + (isDevice ? "/media/usb" : "/home/denali/Desktop/usb-disk") + readonly property string typeFolderApplication : "/log" + readonly property string typeFolderService : "/service" + readonly property string typeFolderTreatment : "/treatment" + + readonly property string typeFolderApplicationSrc : typePathSrc + typeFolderApplication + readonly property string typeFolderApplicationDst : typePathDst + typeFolderApplication + readonly property string typeFolderServiceSrc : typePathSrc + typeFolderService + readonly property string typeFolderServiceDst : typePathDst + typeFolderService + readonly property string typeFolderTreatmentSrc : typePathTxr + typeFolderTreatment + readonly property string typeFolderTreatmentDst : typePathDst + typeFolderTreatment + + readonly property var typeFilterAll : ["*"] + readonly property var typeFilterClr : [] + + readonly property bool horizontalLayout : _GuiView.useLogLongName + confirmVisible : false + notificationMargin : Variables.notificationHeight + bottomMarginContent : Variables.settingsContentBottomMargin - Variables.minVGap + function refreshModels() { + _usbFolderColumn.clearModel() + _usbFolderColumn.updateModel() + } + + function doExport() { + notificationText = _logTypeCombo.displayText + " " + + qsTr("log export to USB in progress ... ") + refreshModels() + + switch (_logTypeCombo.currentIndex) { + case typeIndexApplication: + _GuiView.doExportLog() + break + case typeIndexService: + _GuiView.doExportService() + break + case typeIndexTreatment: + _GuiView.doExportTreatment() + break + } + + refreshModels() + } + + onVisibleChanged: { + if ( _root.visible ) { + updatePanels( _logTypeCombo.currentIndex ) + } + } + + property bool isUpdatePanels: false + function updatePanels (vIndex) { + isUpdatePanels = true + + _GuiView.doExportListRemove() + _sdcFolderColumn.clearModel() + _usbFolderColumn.clearModel() + + switch (vIndex) { + case typeIndexApplication: + _sdcFolderColumn.currentTypeFolderApplication = typeFolderApplicationSrc + _usbFolderColumn.currentTypeFolderApplication = typeFolderApplicationDst + break + case typeIndexService: + _sdcFolderColumn.currentTypeFolderApplication = typeFolderServiceSrc + _usbFolderColumn.currentTypeFolderApplication = typeFolderServiceDst + break + case typeIndexTreatment: + _sdcFolderColumn.currentTypeFolderApplication = typeFolderTreatmentSrc + _usbFolderColumn.currentTypeFolderApplication = typeFolderTreatmentDst + break + } + + _sdcFolderColumn.updateModel() + _usbFolderColumn.updateModel() + + isUpdatePanels = false + } + + Connections { target: _GuiView + function onSdIsReadyChanged ( vValue ) { _root.updatePanels() } + function onDidExportStat ( vIndex, vFileName, vPercent ) { _sdcFolderView.positionViewAtIndex(vIndex, ListView.Center) } + function onDidUSBDriveUmount ( ) { _usbFolderColumn. clearModel ( ) } + function onDidUSBDriveRemove ( ) { _usbFolderColumn. clearModel ( ) } + function onDidExportLog ( vValue ) { _usbFolderColumn. clearModel ( ) } + function onDidUSBDriveMount ( ) { _usbFolderColumn.updateModel ( ) } + function onDidExport ( ) { _usbFolderColumn.updateModel ( ) + notificationText = _logTypeCombo.displayText + " " + + qsTr("log export to USB is complete") } + } + USBButton { id: _usbEjectButton width : 155 height : 50 anchors.right : _root.right anchors.top : _root.top anchors.margins : Variables.headerButtonsMargin + enabled : _GuiView.usbIsReady && !_GuiView.exportRunning && ! isUpdatePanels } Row { id : _contentRect @@ -58,9 +178,9 @@ anchors.horizontalCenter : parent.horizontalCenter readonly property int columnWidthProgress : 350 - readonly property int columnWidthFolder : 420 - readonly property int columnWidthFileName : 285 // 285 best combination - readonly property int columnWidthFileSize : 105 // 105 best combination + readonly property int columnWidthFolder : _root.horizontalLayout ? 840 : 420 + readonly property int columnWidthFileName : _root.horizontalLayout ? 685 : 285 // best combination + readonly property int columnWidthFileSize : _root.horizontalLayout ? 105 : 105 // best combination Column { id : _progressColumn property int progressWidth : 125 @@ -69,31 +189,35 @@ spacing : 10 width : _contentRect.columnWidthProgress height : parent.height - anchors.verticalCenter : parent.verticalCenter Row { id : _logTypeRow anchors.left : parent.left width : parent.width - height : 50 + height : _root.headetRowHight spacing : _logTypeCombo.width - _logTypeExportButton.width // FIXME: This combobox needs to be a global Component ComboBox { id : _logTypeCombo + onCurrentIndexChanged : { + notificationText = "" + _root.updatePanels(currentIndex) + } + enabled : ! _GuiView.exportRunning && ! isUpdatePanels currentIndex : 0 displayText : currentText font.pixelSize : Fonts.fontPixelTextRectExtra - width : parent.width // 2 + width : parent.width height : parent.height padding : 10 model : [ - qsTr("Application" ), - qsTr("Service" ), - qsTr("Treatment" ) + _root.typeLabelApplication , + _root.typeLabelService , + _root.typeLabelTreatment ] background : Rectangle { color : Colors.transparent - border.color : Colors.borderButton + border.color : enabled ? Colors.borderButton : Colors.borderDisableButton radius : Variables.dialogRadius } @@ -151,7 +275,7 @@ anchors.left : _SDC_progressItem.right anchors.leftMargin : _progressColumn.spacing anchors.verticalCenter : parent.verticalCenter - text : qsTr("SD-Card") + " (MB)\n" + ("Free : %1\nTotal: %2").arg( Variables.sizeConverted( _GuiView.sdAvail, 1000, 3) ).arg( Variables.sizeConverted( _GuiView.sdTotal, 1000, 3) ) + text : _root.sdcLabel + _root.devUnitLabel + ("Free : %1\nTotal: %2").arg( Variables.sizeConverted( _GuiView.sdAvail, 1000, 3) ).arg( Variables.sizeConverted( _GuiView.sdTotal, 1000, 3) ) } Label { anchors.fill : parent @@ -173,7 +297,7 @@ anchors.left : _USB_progressItem.right anchors.leftMargin : _progressColumn.spacing anchors.verticalCenter : parent.verticalCenter - text : qsTr("USB Drive") + " (MB)\n" + ("Free : %1\nTotal: %2").arg( Variables.sizeConverted( _GuiView.usbAvail, 1000, 3) ).arg( Variables.sizeConverted( _GuiView.usbTotal, 1000, 3) ) + text : _root.usbLabel + _root.devUnitLabel + ("Free : %1\nTotal: %2").arg( Variables.sizeConverted( _GuiView.usbAvail, 1000, 3) ).arg( Variables.sizeConverted( _GuiView.usbTotal, 1000, 3) ) } Label { anchors.fill : parent @@ -188,138 +312,225 @@ height : Variables.touchRectHeight radius : Variables.touchRectRadius border.width : Variables.borderWidth - onClicked : _GuiView.doExportLog() + enabled : _GuiView.usbIsReady && !_GuiView.exportRunning && ! isUpdatePanels + onClicked : doExport() } - } - Column { id : _sdcFolderColumn + Grid { + columns : _root.horizontalLayout ? 1 : 2 spacing : 5 width : _contentRect.columnWidthFolder height : parent.height - anchors.verticalCenter : parent.verticalCenter + Column { id : _sdcFolderColumn + property string currentTypeFolderApplication : _root.typeFolderApplicationSrc - Rectangle { id : _sdcFolderRectangle - color : Colors.transparent - border.color : Colors.borderButton - radius : Variables.dialogRadius - anchors.left : parent.left - width : parent.width - height : parent.height + // FIXME: there has to be a View for this, and the timer should be removed and an event driven signal should be implemented there. + function updateModel() { + _sdcFolderModel.folder = currentTypeFolderApplication // FIXME: there has to be a View for this which also get changed by log type. + _sdcFolderModel.nameFilters = _root.typeFilterAll + } + function clearModel() { + _sdcFolderModel.folder = _root.typePathClr + _sdcFolderModel.nameFilters = _root.typeFilterClr + } - ScrollBar { - anchors.fill : _sdcFolderView - flickable : _sdcFolderView + spacing : 5 + width : _contentRect.columnWidthFolder + height : _root.horizontalLayout ? parent.height / 2 : parent.height + Label { id : _sdcLabel + text : _root.sdcLabel + ": %1 files"/*, %2 %3"*/.arg(_sdcFolderModel.count) + (_GuiView.exportCount ? " [Selected: %1]".arg(_GuiView.exportCount ) : "") //.arg("__").arg(_root.dvcUnit) + width : parent.width + height : _root.headetRowHight + verticalAlignment : Text.AlignVCenter } + Rectangle { id : _sdcFolderRectangle + color : Colors.transparent + border.color : Colors.borderButton + radius : Variables.dialogRadius + anchors.left : parent.left + width : parent.width + height : parent.height - _sdcLabel.height - ListView { id : _sdcFolderView - clip : true - anchors.fill : parent - anchors.margins : 10 - spacing : 5 - FolderListModel { id : _sdcFolderModel - showDirs : false - sortField : FolderListModel.Time - folder : "file:///media/sd-card/log" // FIXME: ther has to be a View for this which also get changed by log type. - nameFilters : ["*.log"] // FIXME: ther has to be a View for this which also get changed by log type. + ScrollBar { + anchors.fill : _sdcFolderView + flickable : _sdcFolderView + handleWidth : Variables.settingsExportLogsScrollBarWidth } - Component { id : _sdcFileDelegate - Row { id : _sdcFileRow - width : parent.width - height : 40 - Text { id : _sdcFileNameText - width : _contentRect.columnWidthFileName - text : fileName - color : Colors.textMain - font.pixelSize : Fonts.fontPixelTextRectExtra - verticalAlignment : Text.AlignVCenter - horizontalAlignment : Text.AlignLeft + + ListView { id : _sdcFolderView + enabled : !_GuiView.exportRunning && ! isUpdatePanels + clip : true + anchors.fill : parent + anchors.margins : 10 + anchors.leftMargin : 5 + anchors.rightMargin : 5 + spacing : 3 + FolderListModel { id : _sdcFolderModel + showDirs : false + sortField : FolderListModel.Time + folder : _sdcFolderColumn.currentTypeFolderApplication // FIXME: there has to be a View for this which also get changed by log type. + } + + Component { id : _sdcFileDelegate + ProgressBar { id : _sdcItemBackground + // *** IMPORTANT *** + // QML kills the items when they get out of the view port in regards to the cacheBuffer, + // and the next time it gets in the view port will be created with all the properties in their defaults. + property bool inExportList : _GuiView.exportList [ index ] // I couldn't make it work and this always returns undefined, but has to be used to trigger the change. + ? _GuiView.exportList [ index ] // In case the issue fixes it should return true if it is true, obviously! + : _GuiView.doExportListSelect ( index ) // otherwise for now, it calls the contains() function of the list in C++ backend. + property int exportPercent : _GuiView.exportIndex === index + ? _GuiView.exportPercent + : 0 + onExportPercentChanged : console.log( "%", exportPercent) + + property bool isShownInList : ! _GuiView.isPathSymLink(_sdcFolderColumn.currentTypeFolderApplication.replace(typePathPrefix, "") + "/" + fileName) + + //DEBUG: onInExportListChanged: console.debug(" * ", index, inExportList) + function exportListUpdate() { + if (_GuiView.doExportListSelect( index ) ) { + _GuiView.doExportListDelete( index ) + } + else { + _GuiView.doExportListInsert( index , fileName ) + } + } + + MouseArea { + anchors.fill : parent + onClicked : exportListUpdate() + } + progress.z : 0 + minText.visible : false + maxText.visible : false + marker.visible : false + maximum : 100 // percent + value : maximum - exportPercent + + // Setting height and width to 0 when the file is not shown in the list because + // it is a symlink or other conditions + width : isShownInList ? _sdcFolderView.width : 0 + height : isShownInList ? 40 : 0 + + bgColor : Colors.transparent + color : inExportList ? Colors.borderButtonSelected : Colors.transparent + radius : 5 + Row { id : _sdcFileRow + // Setting height and width to 0 when the file is not shown in the list because + // it is a symlink or other conditions + width : isShownInList ? parent.width : 0 + height : isShownInList ? 40 : 0 + + leftPadding : 5 + Text { id : _sdcFileNameText + x : 2 + clip : true + width : isShownInList ? (_contentRect.columnWidthFileName - 2) : 0 + text : fileName + color : Colors.textMain + font.pixelSize : Fonts.fontPixelTextRectExtra + verticalAlignment : Text.AlignVCenter + horizontalAlignment : Text.AlignLeft + } + Rectangle { id : _sdcColumnVerticalLine + color : Colors.borderButtonUnselected + width : isShownInList ? 1 : 0 + height : parent.height + _usbFolderColumn.spacing + } + Text { id : _sdcFileSizeText + clip : true + width : isShownInList ? _contentRect.columnWidthFileSize : 0 + text : Variables.sizeConverted( fileSize, 1000, 3) + color : Colors.textMain + font.pixelSize : Fonts.fontPixelTextRectExtra + verticalAlignment : Text.AlignVCenter + horizontalAlignment : Text.AlignRight + } + } } - Rectangle { - color: Colors.borderButtonUnselected - width : 1 - height : parent.height + _usbFolderColumn.spacing - } - Text { id : _sdcFileSizeText - width : _contentRect.columnWidthFileSize - text : Variables.sizeConverted( fileSize, 1000, 3) - color : Colors.textMain - font.pixelSize : Fonts.fontPixelTextRectExtra - verticalAlignment : Text.AlignVCenter - horizontalAlignment : Text.AlignRight - } } + model : _sdcFolderModel + delegate : _sdcFileDelegate } - model : _sdcFolderModel - delegate : _sdcFileDelegate } } - } + Column { id : _usbFolderColumn + property string currentTypeFolderApplication : _root.typeFolderApplicationDst - Column { id : _usbFolderColumn // FIXME: there has to be a View for this, and the timer should be removed and an event driven signal should be implemented there. function updateModel() { - _usbFolderModel.folder = "file:///media/usb/log" // FIXME: there has to be a View for this which also get changed by log type. - _usbFolderModel.nameFilters = ["*.log"] // FIXME: there has to be a View for this which also get changed by log type. + _usbFolderModel.folder = currentTypeFolderApplication // FIXME: there has to be a View for this which also get changed by log type. + _usbFolderModel.nameFilters = _root.typeFilterAll } function clearModel() { - _usbFolderModel.folder = "" - _usbFolderModel.nameFilters = [] + _usbFolderModel.folder = _root.typePathClr + _usbFolderModel.nameFilters = _root.typeFilterClr } // FIXME: there has to be a View for this, and the timer should be removed and an event driven signal should be implemented there. Timer { id: _usbUpdate interval : 500 repeat : true running : _GuiView.exportRunning + onRunningChanged: { + if(!running) { + // do one last update to get the correct file size else the USB file size is displayed + // as being smaller than the same file on SD card + _usbFolderColumn. clearModel() + _usbFolderColumn.updateModel() + } + } + onTriggered : { _usbFolderColumn. clearModel() _usbFolderColumn.updateModel() - console.debug("Updating usb") } } - Connections { target: _GuiView - onDidUSBDriveUmount : _usbFolderColumn. clearModel() - onDidUSBDriveRemove : _usbFolderColumn. clearModel() - onDidExportLog : _usbFolderColumn. clearModel() - onDidUSBDriveMount : _usbFolderColumn.updateModel() - onDidExport : _usbFolderColumn.updateModel() - } - spacing : 5 width : _contentRect.columnWidthFolder - height : parent.height - anchors.verticalCenter : parent.verticalCenter + height : _root.horizontalLayout ? parent.height / 2 : parent.height + Label { id : _usbLabel + text : _root.usbLabel + ": %1 files"/*", %2 %3"*/.arg(_usbFolderModel.count)//.arg("__").arg(_root.dvcUnit) + width : parent.width + height : _root.headetRowHight + verticalAlignment : Text.AlignVCenter + } Rectangle { id : _usbFolderRectangle color : Colors.transparent border.color : Colors.borderButton radius : Variables.dialogRadius anchors.left : parent.left width : parent.width - height : parent.height + height : parent.height - _sdcLabel.height ScrollBar { anchors.fill : _usbFolderView flickable : _usbFolderView + handleWidth : Variables.settingsExportLogsScrollBarWidth } ListView { id : _usbFolderView clip : true anchors.fill : parent anchors.margins : 10 - spacing : 5 + spacing : 3 + + anchors.leftMargin : 5 + anchors.rightMargin : 5 + FolderListModel { id : _usbFolderModel // FIXME: I don't like this model, it's too lazy and I don't have control over it. There has to be a Model for this. showDirs : false sortField : FolderListModel.Time - folder : "file:///media/usb" - nameFilters : ["*"] + folder : _usbFolderColumn.currentTypeFolderApplication } Component { id : _usbFileDelegate Row { id : _usbFileRow - width : parent.width + width : _usbFolderView.width height : 40 Text { id : _usbFileNameText + clip : true width : _contentRect.columnWidthFileName text : fileName color : Colors.textMain @@ -333,6 +544,7 @@ height: parent.height + _usbFolderColumn.spacing } Text { id : _usbFileSizeText + clip : true width : _contentRect.columnWidthFileSize text : Variables.sizeConverted( fileSize, 1000, 3) color : Colors.textMain @@ -347,5 +559,6 @@ } } } + } } }