diff --git a/Desktop/components/JASP/Widgets/AnalysesPanel.qml b/Desktop/components/JASP/Widgets/AnalysesPanel.qml new file mode 100644 index 0000000000..58906517e8 --- /dev/null +++ b/Desktop/components/JASP/Widgets/AnalysesPanel.qml @@ -0,0 +1,42 @@ +import QtQuick +import JASP +import QtQuick.Controls +import JASP.Controls as JC + + +Item +{ + id: analysesPane + implicitWidth: analysesForm.width + extraBorder.width + height: parent.height + + Rectangle + { + // When there is an analysis without data, add an extra border at the left side of the analysis form + id: extraBorder + width: visible ? jaspTheme.splitHandleWidth : 0 + visible: !hasData && hasAnalysis && analysesModel.visible + color: jaspTheme.uiBackground + border.width: 1 + border.color: jaspTheme.uiBorder + anchors + { + top: parent.top + bottom: parent.bottom + left: parent.left + leftMargin: -1 + topMargin: -1 + bottomMargin: -1 + } + + } + + AnalysisForms + { + id: analysesForm + visible: hasAnalysis && analysesModel.visible + width: hasAnalysis ? implicitWidth : 0 + height: parent.height + x: extraBorder.width + } +} diff --git a/Desktop/components/JASP/Widgets/EnginesWindow.qml b/Desktop/components/JASP/Widgets/EnginesPanel.qml similarity index 84% rename from Desktop/components/JASP/Widgets/EnginesWindow.qml rename to Desktop/components/JASP/Widgets/EnginesPanel.qml index 9889b989e4..18ea632900 100644 --- a/Desktop/components/JASP/Widgets/EnginesWindow.qml +++ b/Desktop/components/JASP/Widgets/EnginesPanel.qml @@ -3,29 +3,22 @@ import QtQuick.Window import QtWebEngine import JASP.Widgets import JASP.Controls +import QtQuick.Layouts -Window +FocusScope { id: enginesWindow width: 400 * jaspTheme.uiScale height: Screen.height - minimumWidth: 200 * preferencesModel.uiScale - minimumHeight: minimumWidth - title: qsTr("Engines Overview") - color: jaspTheme.uiBackground - visible: true + Layout.minimumWidth: 200 * preferencesModel.uiScale + Layout.maximumWidth: 600 * preferencesModel.uiScale + Layout.minimumHeight: minimumWidth + //title: qsTr("Engines Overview") + //color: jaspTheme.uiBackground x: 0 y: 0 - Shortcut { onActivated: enginesWindow.close(); sequences: ["Ctrl+W", Qt.Key_Close]; } - - - Connections - { - target: mainWindow - function onCloseWindows() { enginesWindow.close(); } - } - + Shortcut { onActivated: engineSync.showEngines = false; sequences: ["Ctrl+W", Qt.Key_Close]; } ListView { diff --git a/Desktop/components/JASP/Widgets/HelpPanel.qml b/Desktop/components/JASP/Widgets/HelpPanel.qml new file mode 100644 index 0000000000..cadace1d09 --- /dev/null +++ b/Desktop/components/JASP/Widgets/HelpPanel.qml @@ -0,0 +1,106 @@ +import QtQuick +import QtWebEngine +import JASP.Widgets +import JASP.Controls +import QtQuick.Layouts + +FocusScope +{ + id: helpWindowRoot + width: 400 * preferencesModel.uiScale + height: Math.min(700 * preferencesModel.uiScale, Screen.desktopAvailableHeight) //If bigger than screenheight weird jumping behaviour observed by Rens + + //title: qsTr("JASP Help") + //color: jaspTheme.uiBackground + + Shortcut { onActivated: helpModel.visible = false; sequences: ["Ctrl+W", Qt.Key_Close]; } + //Shortcut { onActivated: helpWindowRoot.toggleFullScreen(); sequences: ["Ctrl+M"]; } + Shortcut { onActivated: searchBar.startSearching(); sequences: ["Ctrl+F", Qt.Key_Search]; } + + /*onClosing: { helpModel.markdown = ""; } //break binding + + Connections + { + target: mainWindow + function onCloseWindows() { helpWindowRoot.close(); } + } + + UIScaleNotifier { anchors.centerIn: parent }*/ + + WebEngineView + { + id: helpView + url: helpModel.indexURL() + anchors.fill: parent + anchors.bottomMargin: searchBar.height + (jaspTheme.generalAnchorMargin * 2) + zoomFactor: preferencesModel.uiScale + backgroundColor: jaspTheme.uiBackground + onLoadingChanged: (loadRequest)=> + { + if(loadRequest.status === WebEngineView.LoadSucceededStatus) + helpModel.loadingSucceeded() + searchBar.search(); + } + + onNavigationRequested: (request)=> + { + if(request.navigationType === WebEngineNavigationRequest.LinkClickedNavigation) + { + Qt.openUrlExternally(request.url); + request.reject(); + } + } + + Connections + { + target: helpModel + function onRunJavaScriptSignal(helpJS) + { + helpView.runJavaScript(helpJS); + searchBar.search(); + } + } + } + + Row + { + CheckBox + { + id: trackAnalyses + label: qsTr("Follow") + checked: preferencesModel.helpFollowsAnalyses + onCheckedChanged: preferencesModel.helpFollowsAnalyses = checked + } + + TextField + { + id: searchBar + label: qsTr("Search for:") + value: "" + onValueChanged: search() + Keys.onEscapePressed: value = "" + fieldWidth: parent.width - (controlLabel.width + control.anchors.leftMargin + jaspTheme.generalAnchorMargin * 2) + + function startSearching() + { + forceActiveFocus(); + control.selectAll() + } + + function search() + { + helpView.findText(value) + } + } + + + anchors + { + left: parent.left + right: parent.right + bottom: parent.bottom + margins: jaspTheme.generalAnchorMargin + } + + } +} diff --git a/Desktop/components/JASP/Widgets/HelpWindow.qml b/Desktop/components/JASP/Widgets/HelpWindow.qml deleted file mode 100644 index e288a30f77..0000000000 --- a/Desktop/components/JASP/Widgets/HelpWindow.qml +++ /dev/null @@ -1,98 +0,0 @@ -import QtQuick -import QtQuick.Window -import QtWebEngine -import JASP.Widgets -import JASP.Controls - -Window -{ - id: helpWindowRoot - width: 400 * preferencesModel.uiScale - height: Math.min(700 * preferencesModel.uiScale, Screen.desktopAvailableHeight) //If bigger than screenheight weird jumping behaviour observed by Rens - minimumWidth: 200 * preferencesModel.uiScale - minimumHeight: minimumWidth - visible: helpModel.visible - onVisibleChanged: helpModel.visible = visible - title: qsTr("JASP Help") - color: jaspTheme.uiBackground - - Shortcut { onActivated: helpWindowRoot.close(); sequences: ["Ctrl+W", Qt.Key_Close]; } - Shortcut { onActivated: helpWindowRoot.toggleFullScreen(); sequences: ["Ctrl+M"]; } - Shortcut { onActivated: searchBar.startSearching(); sequences: ["Ctrl+F", Qt.Key_Search]; } - - onClosing: { helpModel.markdown = ""; } //break binding - - Connections - { - target: mainWindow - function onCloseWindows() { helpWindowRoot.close(); } - } - - UIScaleNotifier { anchors.centerIn: parent } - - WebEngineView - { - id: helpView - url: helpModel.indexURL() - anchors.fill: parent - anchors.bottomMargin: searchBar.height + (jaspTheme.generalAnchorMargin * 2) - zoomFactor: preferencesModel.uiScale - backgroundColor: jaspTheme.uiBackground - onLoadingChanged: (loadRequest)=> - { - if(loadRequest.status === WebEngineView.LoadSucceededStatus) - helpModel.loadingSucceeded() - searchBar.search(); - } - - onNavigationRequested: (request)=> - { - if(request.navigationType === WebEngineNavigationRequest.LinkClickedNavigation) - { - Qt.openUrlExternally(request.url); - request.reject(); - } - } - - Connections - { - target: helpModel - function onRunJavaScriptSignal(helpJS) - { - helpView.runJavaScript(helpJS); - searchBar.search(); - } - } - } - - - TextField - { - id: searchBar - label: qsTr("Search for:") - value: "" - onValueChanged: search() - Keys.onEscapePressed: value = "" - fieldWidth: parent.width - (controlLabel.width + control.anchors.leftMargin + jaspTheme.generalAnchorMargin * 2) - - function startSearching() - { - forceActiveFocus(); - control.selectAll() - } - - function search() - { - helpView.findText(value) - } - - anchors - { - left: parent.left - right: parent.right - bottom: parent.bottom - margins: jaspTheme.generalAnchorMargin - } - - } -} diff --git a/Desktop/components/JASP/Widgets/JASPSplitHandle.qml b/Desktop/components/JASP/Widgets/JASPSplitHandle.qml index fc6e4b7213..31c71ae9ef 100644 --- a/Desktop/components/JASP/Widgets/JASPSplitHandle.qml +++ b/Desktop/components/JASP/Widgets/JASPSplitHandle.qml @@ -9,7 +9,7 @@ Rectangle id: handleRoot signal arrowClicked - signal handleDragging(bool active, var mouseArea) + property bool pointingLeft: true property bool showArrow: true property bool dragEnabled: true @@ -18,25 +18,15 @@ Rectangle property string toolTipDrag: "" property string toolTipArrow: "" property bool hovered: hoverMouse.containsMouse - property alias dragging: hoverMouse.drag.active - property alias dragX: hoverMouse.x - width: jaspTheme.splitHandleWidth - anchors - { - top: parent.top - bottom: parent.bottom - topMargin: -1 - bottomMargin: -1 - leftMargin: removeLeftBorder ? -1 : 0 - } + implicitWidth: jaspTheme.splitHandleWidth + width: implicitWidth + color: handleRoot.dragEnabled && handleRoot.hovered ? jaspTheme.grayLighter : jaspTheme.uiBackground border.color: jaspTheme.uiBorder border.width: 1 - Drag.active: hoverMouse.drag.active - ToolTip { @@ -65,9 +55,9 @@ Rectangle cursorShape: handleRoot.dragEnabled ? Qt.SplitHCursor : Qt.ArrowCursor //Take into account resizing? styleData.resizing //onPositionChanged: (mouse)=>{ mouse.accepted = true; } - drag.target: parent - drag.axis: Drag.XAxis - drag.onActiveChanged: handleDragging(drag.active, hoverMouse) + //drag.target: parent + //drag.axis: Drag.XAxis + //drag.onActiveChanged: handleDragging(drag.active, hoverMouse) } Item diff --git a/Desktop/components/JASP/Widgets/MainPage.qml b/Desktop/components/JASP/Widgets/MainPage.qml index a744efecd2..bf6dc66c03 100644 --- a/Desktop/components/JASP/Widgets/MainPage.qml +++ b/Desktop/components/JASP/Widgets/MainPage.qml @@ -17,40 +17,25 @@ // import QtQuick -import QtWebEngine -import QtWebChannel import JASP import QtQuick.Controls import JASP.Controls as JC -Item +SplitView { id: splitViewContainer - // The MainPage has 3 panels: Data, Analyses form and Results. - // There are 3 configurations: only data, only analyses with results or all 3 panels. - // Between these 3 panels, there are 2 handles: handleBetweenDataAndAnalyses & handleBetweenAnalysesAndResults: if there are only data - // then no handle is displayed, if there are only analyses with results, then only handleBetweenAnalysesAndResults is displayed, and if - // there are data and analyses, then both handles are displayed. - // We cannot use the QML SplitView, since we cannot use click events on the handles (since Qt 6.3.2), - // so to simulate the handles between the panels, we just use rectangles that can be dragged on the X axe. - // The Analyses form panel has always the same width, so if we drag the first handle, the second handle should move at the same pace. - // To coordinate the panels in function of the handles movement, all positions and widths of the panels are deduced from the position - // of handleBetweenDataAndAnalyses (except when handleBetweenAnalysesAndResults is dragged, then this handle determine temporary the - // position of the rest) - property bool hasData: mainWindow.dataAvailable property bool hasAnalysis: mainWindow.analysesAvailable && !ribbonModel.dataMode function minimizeDataPanel() { - handleDataAnalyses.x = 0 - + dataPanel.width = 0 } function maximizeDataPanel() { - handleDataAnalyses.x = splitViewContainer.width - (hasAnalysis ? handleAnalysesResults.width : 0) + dataPanel.width = splitViewContainer.width } Connections @@ -59,41 +44,15 @@ Item function onAnalysisAdded() { // When adding an analysis, if the analyses pane is cut or the results pane has not enough space, hide the data panel - if (resultsPane.width < jaspTheme.resultWidth || analysesPane.x < 0) + if (resultsPane.width < jaspTheme.resultWidth) //|| analysesPane.x < 0) minimizeDataPanel() } } - - Connections - { - target: mainWindow - function onHideDataPanel() { minimizeDataPanel() } - } - - onHasDataChanged: - { - if (hasData && !hasAnalysis) maximizeDataPanel() - else minimizeDataPanel() - } - - onWidthChanged: - { - if (handleDataAnalyses.visible && handleDataAnalyses.x > (width - handleDataAnalyses.width)) maximizeDataPanel() - } - - DataPanel - { - id: dataPanel - visible: hasData - width: hasAnalysis ? handleDataAnalyses.x + 1 : parent.width // -1 is for the border to the right - height: parent.height - } - - JASPSplitHandle + + handle: JASPSplitHandle { - id: handleDataAnalyses - x: 0 // All other items are depending on this point - visible: hasData && hasAnalysis + id: handle + onArrowClicked: { if (pointingLeft) minimizeDataPanel() @@ -102,442 +61,53 @@ Item pointingLeft: x > 0 toolTipArrow: pointingLeft ? qsTr("Hide data") : qsTr("Show data") toolTipDrag: pointingLeft ? qsTr("Resize data/results") : qsTr("Drag to show data") - onXChanged: checkPosition(false) - onDraggingChanged: checkPosition(true) + //onXChanged: checkPosition(false) + //onDraggingChanged: checkPosition(true) JC.ALTNavigation.enabled: true JC.ALTNavigation.onTagMatch: { arrowClicked(); } JC.ALTNavigation.requestedPostfix: "D" JC.ALTNavigation.y: height / 2 - 25 * jaspTheme.uiScale - function checkPosition(forceCheck) - { - if (forceCheck || !dragging) - { - // When there is no data, the handle is not visible, but is still used to position the other items: then this handle might be negative. - // Also when the analysesPane is open, then you may drag this handle outside at the left of the container. - if (x < 0 && !analysesModel.visible && hasData) - x = 0 - - else if (x + width > parent.width) - x = parent.width - width // Take care that this handle is always inside the container - } - } } - - Item + + EnginesPanel { - id: analysesPane - anchors.left: handleDataAnalyses.right - width: analysesForm.width + extraBorder.width - height: parent.height - - Rectangle - { - // When there is an analysis without data, add an axtra border at the left side of the analysis form - id: extraBorder - width: visible ? jaspTheme.splitHandleWidth : 0 - visible: !hasData && hasAnalysis && analysesModel.visible - color: jaspTheme.uiBackground - border.width: 1 - border.color: jaspTheme.uiBorder - anchors - { - top: parent.top - bottom: parent.bottom - left: parent.left - leftMargin: -1 - topMargin: -1 - bottomMargin: -1 - } - - } - - AnalysisForms - { - id: analysesForm - visible: hasAnalysis && analysesModel.visible - width: hasAnalysis ? implicitWidth : 0 - height: parent.height - x: extraBorder.width - } + id: enginesPanel + visible: engineSync.showEngines } - - JASPSplitHandle + DataPanel { - id: handleAnalysesResults - anchors.left: !dragging ? analysesPane.right : undefined - visible: hasAnalysis && !ribbonModel.dataMode - pointingLeft: analysesModel.visible - onArrowClicked: analysesModel.visible = !analysesModel.visible - toolTipDrag: hasData ? (handleAnalysesResults.pointingLeft ? qsTr("Resize data/results") : qsTr("Drag to show data")) : "" - toolTipArrow: analysesModel.visible ? qsTr("Hide input options") : qsTr("Show input options") - removeLeftBorder: true //!analysesModel.visible - - Binding - { - // When dragging, the handleDataAnalyses must follow the movement of handleAnalysesResults - target: handleDataAnalyses - property: "x" - value: handleAnalysesResults.x + handleAnalysesResults.dragX - handleDataAnalyses.width - analysesPane.width - when: handleAnalysesResults.dragging - restoreMode: Binding.RestoreNone - } - - onDraggingChanged: - { - if (dragging) - return - - if (!hasData && !analysesModel.visible && x > 0) - analysesModel.visible = true - - checkPosition(true) - - } - - onXChanged: checkPosition(false) - - function checkPosition(forceCheck) - { - if((!visible || dragging) && !forceCheck) - return - - if (x < 0) - { - // We have moved the handle outside the container to the left: the analyses form panel must be hidden, and take care that the handleDataAnalyses appears. - //analysesModel.visible = false - handleDataAnalyses.x = - (handleDataAnalyses.width + analysesPane.width) - } - else if (!hasData && x > analysesPane.width) - // If there is no data, the handle should not move more to the right than the widh of the analyses form panel. - handleDataAnalyses.x = - handleDataAnalyses.width - } - + id: dataPanel + visible: mainWindow.showData + + SplitView.fillWidth: true } + - Rectangle + AnalysesPanel { - id: resultsPane - anchors - { - top: parent.top - left: handleAnalysesResults.right - right: parent.right - bottom: parent.bottom - } - visible: hasAnalysis && !ribbonModel.dataMode - color: analysesModel.currentAnalysisIndex !== -1 ? jaspTheme.uiBackground : jaspTheme.white - - JC.ALTNavigation.enabled: true - JC.ALTNavigation.requestedPostfix: "R" - JC.ALTNavigation.onTagMatch: { resultsView.nextItemInFocusChain().forceActiveFocus(); } - - Rectangle - { - id: searchRectangle - height: searchBar.height + 2*jaspTheme.generalAnchorMargin - z: 1 - anchors - { - left: parent.left - right: parent.right - bottom: parent.bottom - } - visible: false - color: jaspTheme.uiBackground - border.color: jaspTheme.uiBorder - border.width: 1 - - JC.TextField - { - id: searchBar - placeholderText: qsTr("Search in results:") - value: "" - Keys.onEscapePressed: searchRectangle.visible = false - Keys.onReturnPressed: search(displayValue) - onValueChanged: search(displayValue) - onDisplayValueChanged: search(displayValue) - onVisibleChanged: search("") - moveFocusOnEdit: false - - function startSearching() - { - searchRectangle.visible = true - forceActiveFocus(); - } - - Shortcut - { - sequences: ["Ctrl+F"] - onActivated: searchBar.startSearching() - } - - function search(thisText) { if(resultsView) resultsView.findText(thisText); } - - anchors - { - left: parent.left - right: closeButton.left - bottom: parent.bottom - margins: jaspTheme.generalAnchorMargin - } - fieldWidth: parent.width - (jaspTheme.scrollbarBoxWidthBig + closeButton.width) - } + id: analysesPane + visible: analysesModel.visible && !ribbonModel.dataMode + + SplitView.minimumWidth: 0 + SplitView.preferredWidth: implicitWidth + SplitView.maximumWidth: implicitWidth + } - JC.MenuButton - { - id: closeButton - anchors - { - right: searchRectangle.right - verticalCenter: searchBar.verticalCenter - margins: jaspTheme.generalAnchorMargin - } - height: searchBar.height - width: height - iconSource: jaspTheme.iconPath + "close-button.png" - onClicked: searchRectangle.visible = false - toolTip: qsTr("Close search bar") - radius: height - } - } - - WebEngineView - { - id: resultsView - clip: true - anchors.fill: parent - anchors.leftMargin: 1 - - url: resultsJsInterface.resultsPageUrl - - onContextMenuRequested: (request) => request.accepted = true - - backgroundColor: jaspTheme.uiBackground - - Keys.onPressed: (event) => - { - switch(event) - { - case Qt.Key_PageDown: resultsView.runJavaScript("windows.pageDown();"); event.accepted=true; break; - case Qt.Key_PageUp: resultsView.runJavaScript("windows.pageUp();"); event.accepted=true; break; - } - } - - property var urlWhitelist : ['www.youtube.com', 'www.youtu.be'] //'*.vimeo.com', '*.vimeocdn.com', '*.akamaized.net', '*.bilibili.com', '*.hdslb.com', '*.qq.com', '*.smtcdns.com']; - - function isURLInWhitelist(hostname) - { - for (var i = 0; i < urlWhitelist.length; i++) - { - var pattern = urlWhitelist[i]; - if (pattern === hostname) - { - return true; - } - else if (pattern.indexOf('*') !== -1) - { - var regex = new RegExp(`^${pattern.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`); - if (regex.test(hostname)) - { - return true; - } - } - } - return false; - } - - onFullScreenRequested: function(request) - { - request.accept() - - if(request.toggleOn) - { - analysesModel.visible = false - minimizeDataPanel() - } - } - - onNavigationRequested: (request)=> - { - var requestedURL = new URL(request.url); - - if(request.navigationType === WebEngineNavigationRequest.ReloadNavigation || request.url == resultsJsInterface.resultsPageUrl) - { - request.accept() - } - else if(request.navigationType === WebEngineNavigationRequest.LinkClickedNavigation) - { - Qt.openUrlExternally(request.url); - request.reject(); - } - else if(isURLInWhitelist(requestedURL.hostname)) - { - request.accept(); - console.log("Navigation requested accepted:", requestedURL.hostname) - } - else - { - request.reject(); - console.log("Navigation requested rejected:", requestedURL.hostname) - } - } - - onLoadingChanged: (loadRequest)=> - { - resultsJsInterface.resultsLoaded = loadRequest.status === WebEngineView.LoadSucceededStatus; - setTranslatedResultsString(); - if(resultsJsInterface.resultsLoaded) - runJavaScript(`window.sendUrlWhitelist(${JSON.stringify(urlWhitelist)})`); //sent urlWhitelist to js side - } - - - - Connections - { - target: resultsJsInterface - function onRunJavaScriptSignal(js) { resultsView.runJavaScript(js); } - function onScrollAtAllChanged(scrollAtAll) { resultsView.runJavaScript("window.setScrollAtAll("+(scrollAtAll ? "true" : "false")+")"); } - - function onExportToPDF(pdfPath) - { - resultsView.printToPdf(pdfPath, preferencesModel.pdfPageSize, preferencesModel.pdfLandscape ? WebEngineView.Landscape : WebEngineView.Portrait); - } - - function onPrepForExport() - { - //set light theme and unselect - resultsView.runJavaScript("window.unselect(); window.setTheme(\"lightTheme\");", function() { resultsJsInterface.exportPrepFinished(); }); - } - } - onPdfPrintingFinished: (filePath)=> - { - if(preferencesModel.currentThemeName !== "lightTheme") - resultsJsInterface.setThemeCss(preferencesModel.currentThemeName); - - resultsJsInterface.pdfPrintingFinished(filePath); - } - - webChannel.registeredObjects: [ resultsJsInterfaceInterface ] - - property string resultsString: qsTr("Results") - onResultsStringChanged: setTranslatedResultsString(); - - // Defined the elements that need to be translated in the html/js interface - // where use i18n(...) to return translations - property var i18nObject: - { - "Bold": qsTr("Bold"), "Italic": qsTr("Italic"), "Underline": qsTr("Underline"), "Link": qsTr("Link"), - "Formula": qsTr("Formula"), "Font Color": qsTr("Font Color"), "Add Indent": qsTr("Add Indent"), "Embed web video" : qsTr("Embed web video"), - "Code Block": qsTr("Code Block"), "Header": qsTr("Header"), "Ordered List": qsTr("Ordered List"), "Unordered List": qsTr("Unordered List"), - "Background Color": qsTr("Background Color"), "Subscript": qsTr("Subscript"), "Superscript": qsTr("Superscript"), "Blockquote": qsTr("Blockquote"), - "Remove Indent": qsTr("Remove Indent"), "Font Size": qsTr("Font Size"), "Clear Formatting": qsTr("Clear Formatting"), "Click here to add text" : qsTr("Click here to add text"), - "Copied to clipboard": qsTr("Copied to clipboard"), "Introduction:": qsTr("Introduction:"), "Conclusion:" : qsTr("Conclusion:"), "Image" : qsTr("Image"), - - "Unsupported video services": qsTr("Unsupported video services"), "Input LaTeX here:": qsTr("Input LaTeX here:"), - "Press `Cmd/Ctrl + Enter` to apply;": qsTr("Press `Cmd/Ctrl + Enter` to apply;"), "Click to apply formula": qsTr("Click to apply formula"), - "Click to edit this formula": qsTr("Click to edit this formula"), "Citations copied to clipboard": qsTr("Citations copied to clipboard"), - "LaTeX code copied to clipboard": qsTr("LaTeX code copied to clipboard"), "Remove this note": qsTr("Remove this note"), - - "JASP only allows the following videoservices:": qsTr("JASP only allows the following videoservices:"), - "Contact the JASP team to request adding another videoservice to the list." : qsTr("Contact the JASP team to request adding another videoservice to the list.") - - } - - function setTranslatedResultsString() - { - if(resultsJsInterface.resultsLoaded) - { - runJavaScript("window.setAnalysesTitle(\"" + resultsString + "\");"); - - // To parse the QML i18n object to js object - // will also make the js i18n follow changes of JASP GUI language - runJavaScript(`window.setI18nStrings(${JSON.stringify(i18nObject)})`) - } - } - - QtObject - { - id: resultsJsInterfaceInterface - WebChannel.id: "jasp" - - property bool reportingVisible: preferencesModel.reportingMode - - onReportingVisibleChanged: mainWindow.reloadResults() - - // Yeah I know this "resultsJsInterfaceInterface" looks a bit stupid but this honestly seems like the best way to make the current resultsJsInterface functions available to javascript without rewriting (more of) the structure of Desktop right now. - // It would be much better to have resultsJsInterface be passed directly though.. - // It also gives you an overview of the functions used in results html - - function openFileTab() { resultsJsInterface.openFileTab() } - function saveTextToFile(fileName, html) { resultsJsInterface.saveTextToFile(fileName, html) } - function analysisUnselected() { resultsJsInterface.analysisUnselected() } - function analysisSelected(id) { resultsJsInterface.analysisSelected(id) } - function analysisChangedDownstream(id, model) { resultsJsInterface.analysisChangedDownstream(id, model) } - function analysisTitleChangedInResults(id, title) { resultsJsInterface.analysisTitleChangedInResults(id, title) } - function analysisSaveImage(id, options) { resultsJsInterface.analysisSaveImage(id, options) } - function analysisEditImage(id, options) { resultsJsInterface.analysisEditImage(id, options) } - function removeAnalysisRequest(id) { resultsJsInterface.removeAnalysisRequest(id) } - function pushToClipboard(mime, raw, coded) { resultsJsInterface.pushToClipboard(mime, raw, coded) } - function pushImageToClipboard(raw, coded) { resultsJsInterface.pushImageToClipboard(raw, coded) } - function saveTempImage(index, path, base64) { resultsJsInterface.saveTempImage(index, path, base64) } - function getImageInBase64(index, path) { resultsJsInterface.getImageInBase64(index, path) } - function resultsDocumentChanged() { resultsJsInterface.resultsDocumentChanged() } - function displayMessageFromResults(msg) { resultsJsInterface.displayMessageFromResults(msg) } - function setAllUserDataFromJavascript(json) { resultsJsInterface.setAllUserDataFromJavascript(json) } - function setResultsMetaFromJavascript(json) { resultsJsInterface.setResultsMetaFromJavascript(json) } - function duplicateAnalysis(id) { resultsJsInterface.duplicateAnalysis(id) } - function showDependenciesInAnalysis(id, optName) { resultsJsInterface.showDependenciesInAnalysis(id, optName) } - function showRSyntaxInResults(show) { resultsJsInterface.showRSyntaxInResults(show) } - - function showAnalysesMenu(options) - { - // FIXME: This is a mess - // TODO: 1. remove redundant computations - // 2. move everything to one place :P - - var optionsJSON = JSON.parse(options); - var functionCall = function (index) - { - var name = customMenu.props['model'].getName(index); - var jsfunction = customMenu.props['model'].getJSFunction(index); - - customMenu.hide() - - if (name === 'hasExportResults') { fileMenuModel.exportResultsInteractive(); return; } - if (name === 'hasRefreshAllAnalyses') { resultsJsInterface.refreshAllAnalyses(); return; } - if (name === 'hasRemoveAllAnalyses') { resultsJsInterface.removeAllAnalyses(); return; } - if (name === 'hasCopy' || name === 'hasCite') resultsJsInterface.purgeClipboard(); - - resultsJsInterface.runJavaScript(jsfunction); - - if (name === 'hasEditTitle' || name === 'hasNotes') - resultsJsInterface.packageModified(); - - } - - var selectedOptions = [] - for (var key in optionsJSON) - if (optionsJSON.hasOwnProperty(key) && optionsJSON[key] === true) - selectedOptions.push(key) - - resultMenuModel.setOptions(options, selectedOptions); - - var props = { - "model" : resultMenuModel, - "functionCall" : functionCall - }; - - customMenu.toggle(resultsView, props, (optionsJSON['rXright'] + 10) * preferencesModel.uiScale, optionsJSON['rY'] * preferencesModel.uiScale); + HelpPanel + { + id: helpPanel + visible: helpModel.visible + + SplitView.minimumWidth: 200 * preferencesModel.uiScale + SplitView.maximumWidth: jaspTheme.resultWidth + } - customMenu.scrollOri = resultsView.scrollPosition; - customMenu.menuScroll.x = Qt.binding(function() { return -1 * (resultsView.scrollPosition.x - customMenu.scrollOri.x) / resultsView.zoomFactor; }); - customMenu.menuScroll.y = Qt.binding(function() { return -1 * (resultsView.scrollPosition.y - customMenu.scrollOri.y) / resultsView.zoomFactor; }); - customMenu.menuMinIsMin = true - } - } - } + ResultsPanel + { + id: resultsPane + visible: hasAnalysis && !ribbonModel.dataMode//mainWindow.showResults } } diff --git a/Desktop/components/JASP/Widgets/ResultsPanel.qml b/Desktop/components/JASP/Widgets/ResultsPanel.qml new file mode 100644 index 0000000000..8f42df787f --- /dev/null +++ b/Desktop/components/JASP/Widgets/ResultsPanel.qml @@ -0,0 +1,322 @@ +import QtQuick +import QtWebEngine +import QtWebChannel +import JASP +import QtQuick.Controls +import JASP.Controls as JC + +Rectangle +{ + id: resultsPane + color: analysesModel.currentAnalysisIndex !== -1 ? jaspTheme.uiBackground : jaspTheme.white + + JC.ALTNavigation.enabled: true + JC.ALTNavigation.requestedPostfix: "R" + JC.ALTNavigation.onTagMatch: { resultsView.nextItemInFocusChain().forceActiveFocus(); } + + Rectangle + { + id: searchRectangle + height: searchBar.height + 2*jaspTheme.generalAnchorMargin + z: 1 + anchors + { + left: parent.left + right: parent.right + bottom: parent.bottom + } + visible: false + color: jaspTheme.uiBackground + border.color: jaspTheme.uiBorder + border.width: 1 + + JC.TextField + { + id: searchBar + placeholderText: qsTr("Search in results:") + value: "" + Keys.onEscapePressed: searchRectangle.visible = false + Keys.onReturnPressed: search(displayValue) + onValueChanged: search(displayValue) + onDisplayValueChanged: search(displayValue) + onVisibleChanged: search("") + moveFocusOnEdit: false + + function startSearching() + { + searchRectangle.visible = true + forceActiveFocus(); + } + + Shortcut + { + sequences: ["Ctrl+F"] + onActivated: searchBar.startSearching() + } + + function search(thisText) { if(resultsView) resultsView.findText(thisText); } + + anchors + { + left: parent.left + right: closeButton.left + bottom: parent.bottom + margins: jaspTheme.generalAnchorMargin + } + fieldWidth: parent.width - (jaspTheme.scrollbarBoxWidthBig + closeButton.width) + } + + JC.MenuButton + { + id: closeButton + anchors + { + right: searchRectangle.right + verticalCenter: searchBar.verticalCenter + margins: jaspTheme.generalAnchorMargin + } + height: searchBar.height + width: height + iconSource: jaspTheme.iconPath + "close-button.png" + onClicked: searchRectangle.visible = false + toolTip: qsTr("Close search bar") + radius: height + } + } + + WebEngineView + { + id: resultsView + clip: true + anchors.fill: parent + anchors.leftMargin: 1 + + url: resultsJsInterface.resultsPageUrl + + onContextMenuRequested: (request) => request.accepted = true + + backgroundColor: jaspTheme.uiBackground + + Keys.onPressed: (event) => + { + switch(event) + { + case Qt.Key_PageDown: resultsView.runJavaScript("windows.pageDown();"); event.accepted=true; break; + case Qt.Key_PageUp: resultsView.runJavaScript("windows.pageUp();"); event.accepted=true; break; + } + } + + property var urlWhitelist : ['www.youtube.com', 'www.youtu.be'] //'*.vimeo.com', '*.vimeocdn.com', '*.akamaized.net', '*.bilibili.com', '*.hdslb.com', '*.qq.com', '*.smtcdns.com']; + + function isURLInWhitelist(hostname) + { + for (var i = 0; i < urlWhitelist.length; i++) + { + var pattern = urlWhitelist[i]; + if (pattern === hostname) + { + return true; + } + else if (pattern.indexOf('*') !== -1) + { + var regex = new RegExp(`^${pattern.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`); + if (regex.test(hostname)) + { + return true; + } + } + } + return false; + } + + onFullScreenRequested: function(request) + { + request.accept() + + if(request.toggleOn) + { + analysesModel.visible = false + minimizeDataPanel() + } + } + + onNavigationRequested: (request)=> + { + var requestedURL = new URL(request.url); + + if(request.navigationType === WebEngineNavigationRequest.ReloadNavigation || request.url == resultsJsInterface.resultsPageUrl) + { + request.accept() + } + else if(request.navigationType === WebEngineNavigationRequest.LinkClickedNavigation) + { + Qt.openUrlExternally(request.url); + request.reject(); + } + else if(isURLInWhitelist(requestedURL.hostname)) + { + request.accept(); + console.log("Navigation requested accepted:", requestedURL.hostname) + } + else + { + request.reject(); + console.log("Navigation requested rejected:", requestedURL.hostname) + } + } + + onLoadingChanged: (loadRequest)=> + { + resultsJsInterface.resultsLoaded = loadRequest.status === WebEngineView.LoadSucceededStatus; + setTranslatedResultsString(); + if(resultsJsInterface.resultsLoaded) + runJavaScript(`window.sendUrlWhitelist(${JSON.stringify(urlWhitelist)})`); //sent urlWhitelist to js side + } + + + + Connections + { + target: resultsJsInterface + function onRunJavaScriptSignal(js) { resultsView.runJavaScript(js); } + function onScrollAtAllChanged(scrollAtAll) { resultsView.runJavaScript("window.setScrollAtAll("+(scrollAtAll ? "true" : "false")+")"); } + + function onExportToPDF(pdfPath) + { + resultsView.printToPdf(pdfPath, preferencesModel.pdfPageSize, preferencesModel.pdfLandscape ? WebEngineView.Landscape : WebEngineView.Portrait); + } + + function onPrepForExport() + { + //set light theme and unselect + resultsView.runJavaScript("window.unselect(); window.setTheme(\"lightTheme\");", function() { resultsJsInterface.exportPrepFinished(); }); + } + } + onPdfPrintingFinished: (filePath)=> + { + if(preferencesModel.currentThemeName !== "lightTheme") + resultsJsInterface.setThemeCss(preferencesModel.currentThemeName); + + resultsJsInterface.pdfPrintingFinished(filePath); + } + + webChannel.registeredObjects: [ resultsJsInterfaceInterface ] + + property string resultsString: qsTr("Results") + onResultsStringChanged: setTranslatedResultsString(); + + // Defined the elements that need to be translated in the html/js interface + // where use i18n(...) to return translations + property var i18nObject: + { + "Bold": qsTr("Bold"), "Italic": qsTr("Italic"), "Underline": qsTr("Underline"), "Link": qsTr("Link"), + "Formula": qsTr("Formula"), "Font Color": qsTr("Font Color"), "Add Indent": qsTr("Add Indent"), "Embed web video" : qsTr("Embed web video"), + "Code Block": qsTr("Code Block"), "Header": qsTr("Header"), "Ordered List": qsTr("Ordered List"), "Unordered List": qsTr("Unordered List"), + "Background Color": qsTr("Background Color"), "Subscript": qsTr("Subscript"), "Superscript": qsTr("Superscript"), "Blockquote": qsTr("Blockquote"), + "Remove Indent": qsTr("Remove Indent"), "Font Size": qsTr("Font Size"), "Clear Formatting": qsTr("Clear Formatting"), "Click here to add text" : qsTr("Click here to add text"), + "Copied to clipboard": qsTr("Copied to clipboard"), "Introduction:": qsTr("Introduction:"), "Conclusion:" : qsTr("Conclusion:"), "Image" : qsTr("Image"), + + "Unsupported video services": qsTr("Unsupported video services"), "Input LaTeX here:": qsTr("Input LaTeX here:"), + "Press `Cmd/Ctrl + Enter` to apply;": qsTr("Press `Cmd/Ctrl + Enter` to apply;"), "Click to apply formula": qsTr("Click to apply formula"), + "Click to edit this formula": qsTr("Click to edit this formula"), "Citations copied to clipboard": qsTr("Citations copied to clipboard"), + "LaTeX code copied to clipboard": qsTr("LaTeX code copied to clipboard"), "Remove this note": qsTr("Remove this note"), + + "JASP only allows the following videoservices:": qsTr("JASP only allows the following videoservices:"), + "Contact the JASP team to request adding another videoservice to the list." : qsTr("Contact the JASP team to request adding another videoservice to the list.") + + } + + function setTranslatedResultsString() + { + if(resultsJsInterface.resultsLoaded) + { + runJavaScript("window.setAnalysesTitle(\"" + resultsString + "\");"); + + // To parse the QML i18n object to js object + // will also make the js i18n follow changes of JASP GUI language + runJavaScript(`window.setI18nStrings(${JSON.stringify(i18nObject)})`) + } + } + + QtObject + { + id: resultsJsInterfaceInterface + WebChannel.id: "jasp" + + property bool reportingVisible: preferencesModel.reportingMode + + onReportingVisibleChanged: mainWindow.reloadResults() + + // Yeah I know this "resultsJsInterfaceInterface" looks a bit stupid but this honestly seems like the best way to make the current resultsJsInterface functions available to javascript without rewriting (more of) the structure of Desktop right now. + // It would be much better to have resultsJsInterface be passed directly though.. + // It also gives you an overview of the functions used in results html + + function openFileTab() { resultsJsInterface.openFileTab() } + function saveTextToFile(fileName, html) { resultsJsInterface.saveTextToFile(fileName, html) } + function analysisUnselected() { resultsJsInterface.analysisUnselected() } + function analysisSelected(id) { resultsJsInterface.analysisSelected(id) } + function analysisChangedDownstream(id, model) { resultsJsInterface.analysisChangedDownstream(id, model) } + function analysisTitleChangedInResults(id, title) { resultsJsInterface.analysisTitleChangedInResults(id, title) } + function analysisSaveImage(id, options) { resultsJsInterface.analysisSaveImage(id, options) } + function analysisEditImage(id, options) { resultsJsInterface.analysisEditImage(id, options) } + function removeAnalysisRequest(id) { resultsJsInterface.removeAnalysisRequest(id) } + function pushToClipboard(mime, raw, coded) { resultsJsInterface.pushToClipboard(mime, raw, coded) } + function pushImageToClipboard(raw, coded) { resultsJsInterface.pushImageToClipboard(raw, coded) } + function saveTempImage(index, path, base64) { resultsJsInterface.saveTempImage(index, path, base64) } + function getImageInBase64(index, path) { resultsJsInterface.getImageInBase64(index, path) } + function resultsDocumentChanged() { resultsJsInterface.resultsDocumentChanged() } + function displayMessageFromResults(msg) { resultsJsInterface.displayMessageFromResults(msg) } + function setAllUserDataFromJavascript(json) { resultsJsInterface.setAllUserDataFromJavascript(json) } + function setResultsMetaFromJavascript(json) { resultsJsInterface.setResultsMetaFromJavascript(json) } + function duplicateAnalysis(id) { resultsJsInterface.duplicateAnalysis(id) } + function showDependenciesInAnalysis(id, optName) { resultsJsInterface.showDependenciesInAnalysis(id, optName) } + function showRSyntaxInResults(show) { resultsJsInterface.showRSyntaxInResults(show) } + + function showAnalysesMenu(options) + { + // FIXME: This is a mess + // TODO: 1. remove redundant computations + // 2. move everything to one place :P + + var optionsJSON = JSON.parse(options); + var functionCall = function (index) + { + var name = customMenu.props['model'].getName(index); + var jsfunction = customMenu.props['model'].getJSFunction(index); + + customMenu.hide() + + if (name === 'hasExportResults') { fileMenuModel.exportResultsInteractive(); return; } + if (name === 'hasRefreshAllAnalyses') { resultsJsInterface.refreshAllAnalyses(); return; } + if (name === 'hasRemoveAllAnalyses') { resultsJsInterface.removeAllAnalyses(); return; } + if (name === 'hasCopy' || name === 'hasCite') resultsJsInterface.purgeClipboard(); + + resultsJsInterface.runJavaScript(jsfunction); + + if (name === 'hasEditTitle' || name === 'hasNotes') + resultsJsInterface.packageModified(); + + } + + var selectedOptions = [] + for (var key in optionsJSON) + if (optionsJSON.hasOwnProperty(key) && optionsJSON[key] === true) + selectedOptions.push(key) + + resultMenuModel.setOptions(options, selectedOptions); + + var props = { + "model" : resultMenuModel, + "functionCall" : functionCall + }; + + customMenu.toggle(resultsView, props, (optionsJSON['rXright'] + 10) * preferencesModel.uiScale, optionsJSON['rY'] * preferencesModel.uiScale); + + customMenu.scrollOri = resultsView.scrollPosition; + customMenu.menuScroll.x = Qt.binding(function() { return -1 * (resultsView.scrollPosition.x - customMenu.scrollOri.x) / resultsView.zoomFactor; }); + customMenu.menuScroll.y = Qt.binding(function() { return -1 * (resultsView.scrollPosition.y - customMenu.scrollOri.y) / resultsView.zoomFactor; }); + customMenu.menuMinIsMin = true + } + } + } +} diff --git a/Desktop/components/JASP/Widgets/qmldir b/Desktop/components/JASP/Widgets/qmldir index a44434b2c8..18c36c8abc 100644 --- a/Desktop/components/JASP/Widgets/qmldir +++ b/Desktop/components/JASP/Widgets/qmldir @@ -25,6 +25,7 @@ MainPage 1.0 MainPage.qml FileMenu 1.0 FileMenu/FileMenu.qml MessageBox 1.0 MessageBox.qml AnalysisForms 1.0 AnalysisForms.qml +AnalysesPanel 1.0 AnalysesPanel.qml AnalysisFormExpander 1.0 AnalysisFormExpander.qml CustomMenu 1.0 CustomMenu.qml JASPSplitHandle 1.0 JASPSplitHandle.qml @@ -37,7 +38,8 @@ LoadingIndicator 1.0 LoadingIndicator.qml ImageInverter 1.0 ImageInverter.qml PlotEditor 1.0 PlotEditor/PlotEditor.qml ErrorMessage 1.0 ErrorMessage.qml -EnginesWindow 1.0 EnginesWindow.qml +EnginesPanel 1.0 EnginesPanel.qml +ResultsPanel 1.0 ResultsPanel.qml ResizeDataDialog 1.0 ResizeDataDialog.qml RenameColumnDialog 1.0 RenameColumnDialog.qml WavyWindow 1.0 WavyWindow.qml diff --git a/Desktop/engine/enginesync.cpp b/Desktop/engine/enginesync.cpp index 19dfe97d10..bbf7b9e3c8 100644 --- a/Desktop/engine/enginesync.cpp +++ b/Desktop/engine/enginesync.cpp @@ -1425,3 +1425,17 @@ void EngineSync::stopAndDestroyEngine(EngineRepresentation * engine) engine->shutEngineDown(); destroyEngine(engine); } + +bool EngineSync::showEngines() const +{ + return _showEngines; +} + +void EngineSync::setShowEngines(bool newShowEngines) +{ + if (_showEngines == newShowEngines) + return; + + _showEngines = newShowEngines; + emit showEnginesChanged(); +} diff --git a/Desktop/engine/enginesync.h b/Desktop/engine/enginesync.h index 604825039d..542817083c 100644 --- a/Desktop/engine/enginesync.h +++ b/Desktop/engine/enginesync.h @@ -43,6 +43,8 @@ class EngineSync : public QAbstractListModel { Q_OBJECT + Q_PROPERTY(bool showEngines READ showEngines WRITE setShowEngines NOTIFY showEnginesChanged) + public: EngineSync(QObject *parent); @@ -62,6 +64,9 @@ class EngineSync : public QAbstractListModel std::string currentStateForDebug() const; + bool showEngines() const; + void setShowEngines(bool newShowEngines); + public slots: void destroyEngine(EngineRepresentation * engine); void stopAndDestroyEngine(EngineRepresentation * engine); @@ -114,6 +119,8 @@ public slots: void reloadData(); void checkDataSetForUpdates(); + void showEnginesChanged(); + private: //These process functions can request a new engine to be started: stringset processRCodeQueue(); @@ -177,7 +184,8 @@ private slots: RFilterStore * _waitingFilter = nullptr; bool _stopProcessing = false, _dataMode = false, - _filterRunning = false; + _filterRunning = false, + _showEngines = false; int _filterCurrentRequestID = 0; std::string _memoryName, _engineInfo; @@ -192,7 +200,6 @@ private slots: EngineRepresentation * _rCmder = nullptr; ///< For those special occassions where you just want to shout at R in a more personal manner IPCChannel * _rCmderChannel = nullptr; ///< The channel for shouting at R in a more personal manner std::vector _engineStopTimes; ///< Here we keep track of how long ago it is an engine shut down, this way we can give it a slight time between closing and starting an engine. To avoid shared memory problems on windows. - }; #endif // ENGINESYNC_H diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index 30a5b85ac2..f2f6138074 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -338,7 +338,7 @@ const QString MainWindow::contactText() const void MainWindow::showAnalysis() { _ribbonModel->showStatistics(); - emit hideDataPanel(); + setShowData(false); _analyses->setVisible(true); } @@ -654,7 +654,6 @@ void MainWindow::loadQML() _fileMenu->refresh(); //Now that the theme is loaded we can determine the proper width for the buttons in the filemenu - Log::log() << "Loading HelpWindow" << std::endl; _qml->load(QUrl("qrc:///components/JASP/Widgets/HelpWindow.qml")); Log::log() << "Loading AboutWindow" << std::endl; _qml->load(QUrl("qrc:///components/JASP/Widgets/AboutWindow.qml")); Log::log() << "Loading ContactWindow" << std::endl; _qml->load(QUrl("qrc:///components/JASP/Widgets/ContactWindow.qml")); Log::log() << "Loading CommunityWindow" << std::endl; _qml->load(QUrl("qrc:///components/JASP/Widgets/CommunityWindow.qml")); @@ -703,7 +702,7 @@ void MainWindow::loadQML() void MainWindow::showEnginesWindow() { Log::log() << "Showing EnginesWindow" << std::endl; - _qml->load(QUrl("qrc:///components/JASP/Widgets/EnginesWindow.qml")); + _engineSync->setShowEngines(true); } void MainWindow::setQmlImportPaths() @@ -1369,6 +1368,7 @@ void MainWindow::dataSetIOCompleted(FileEvent *event) _resultsJsInterface->resetResults(); _analyses->setVisible(false); + setShowResults(false); _analyses->clear(); _package->dbDelete(); _package->reset(false); @@ -1407,11 +1407,14 @@ void MainWindow::populateUIfromDataSet() bool hasAnalyses = _analyses->count() > 0; - setDataAvailable(_package->dataSet() && (_package->dataSet()->rowCount() > 0 && _package->dataSet()->columnCount() > 0)); - hideProgress(); + setDataAvailable(_package->dataSet() && (_package->dataSet()->rowCount() > 0 && _package->dataSet()->columnCount() > 0)); + + if(!hasAnalyses && dataAvailable()) + setShowData(true); _analyses->setVisible(hasAnalyses && !resultXmlCompare::compareResults::theOne()->testMode()); + setShowResults(hasAnalyses); if (_package->warningMessage() != "") MessageForwarder::showWarning(_package->warningMessage()); else if (errorFound) MessageForwarder::showWarning(errorMsg.str()); @@ -2034,6 +2037,9 @@ void MainWindow::setDataAvailable(bool dataAvailable) _dataAvailable = dataAvailable; emit dataAvailableChanged(_dataAvailable); + + if(_dataAvailable) + setShowData(false); } void MainWindow::setAnalysesAvailable(bool analysesAvailable) @@ -2174,3 +2180,29 @@ void MainWindow::loadModulesFromUserConfiguration(configState state) } } + +bool MainWindow::showData() const +{ + return _showData; +} + +bool MainWindow::showResults() const +{ + return _showResults; +} + +void MainWindow::setShowResults(bool newShowResults) +{ + if (_showResults == newShowResults) + return; + _showResults = newShowResults; + emit showResultsChanged(); +} + +void MainWindow::setShowData(bool newShowData) +{ + if (_showData == newShowData) + return; + _showData = newShowData; + emit showDataChanged(); +} diff --git a/Desktop/mainwindow.h b/Desktop/mainwindow.h index b3bc0e8de6..52b6ffd229 100644 --- a/Desktop/mainwindow.h +++ b/Desktop/mainwindow.h @@ -75,6 +75,8 @@ class MainWindow : public QObject Q_PROPERTY(QString windowTitle READ windowTitle NOTIFY windowTitleChanged ) Q_PROPERTY(int screenPPI READ screenPPI WRITE setScreenPPI NOTIFY screenPPIChanged ) Q_PROPERTY(bool dataAvailable READ dataAvailable NOTIFY dataAvailableChanged ) + Q_PROPERTY(bool showData READ showData WRITE setShowData NOTIFY showDataChanged ) + Q_PROPERTY(bool showResults READ showResults WRITE setShowResults NOTIFY showResultsChanged ) Q_PROPERTY(bool analysesAvailable READ analysesAvailable NOTIFY analysesAvailableChanged ) Q_PROPERTY(bool welcomePageVisible READ welcomePageVisible WRITE setWelcomePageVisible NOTIFY welcomePageVisibleChanged ) Q_PROPERTY(QString downloadNewJASPUrl READ downloadNewJASPUrl WRITE setDownloadNewJASPUrl NOTIFY downloadNewJASPUrlChanged ) @@ -129,6 +131,13 @@ class MainWindow : public QObject const QString contactText() const; const QString questionsUrl() const { return "https://forum.cogsci.nl/index.php?p=/categories/jasp-bayesfactor"; } + bool showData() const; + + bool showResults() const; + void setShowResults(bool newShowResults); + + void setShowData(bool newShowData); + public slots: void setImageBackgroundHandler(QString value); void plotPPIChangedHandler(int ppi, bool wasUserAction); @@ -242,7 +251,6 @@ public slots: void welcomePageVisibleChanged( bool welcomePageVisible); void downloadNewJASPUrlChanged (QString downloadNewJASPUrl); void closeWindows(); - void hideDataPanel(); void exitSignal( int returnCode = 0) const; void contactVisibleChanged(); void communityVisibleChanged(); @@ -250,6 +258,10 @@ public slots: void resizeData(int row, int col); void qmlLoadedChanged(); + void showDataChanged(); + + void showResultsChanged(); + private slots: void resultsPageLoaded(); void analysisResultsChangedHandler(Analysis* analysis); @@ -354,9 +366,11 @@ private slots: _welcomePageVisible = true, _checkAutomaticSync = false, _contactVisible = false, - _communityVisible = false; + _communityVisible = false, + _showData = false; QFont _defaultFont; + bool _showResults; }; #endif // MAINWIDGET_H diff --git a/Desktop/utilities/helpmodel.cpp b/Desktop/utilities/helpmodel.cpp index 072c530508..d3b2744abe 100644 --- a/Desktop/utilities/helpmodel.cpp +++ b/Desktop/utilities/helpmodel.cpp @@ -5,7 +5,6 @@ #include #include "stringutils.h" #include "gui/preferencesmodel.h" -#include "log.h" HelpModel::HelpModel(QObject * parent) : QObject(parent) { @@ -18,15 +17,11 @@ HelpModel::HelpModel(QObject * parent) : QObject(parent) void HelpModel::runJavaScript(QString renderFunc, QString content) { -#ifdef JASP_DEBUG - Log::log() << "Help is sending content: '" << content << "'" << std::endl; -#endif - - content.replace("\\", "\\\\"); //The order here is important, replace the escaped escape characters first. Helps with latex, see: https://github.com/jasp-stats/INTERNAL-jasp/issues/2530 - content.replace("\"", "\\\""); - content.replace("\r\n", "\\n"); - content.replace("\r", "\\n"); - content.replace("\n", "\\n"); + content.replace("\\", "\\\\" ); //The order here is important, replace the escaped escape characters first. Helps with latex, see: https://github.com/jasp-stats/INTERNAL-jasp/issues/2530 + content.replace("\"", "\\\"" ); + content.replace("\r\n", "\\n" ); + content.replace("\r", "\\n" ); + content.replace("\n", "\\n" ); diff --git a/QMLComponents/controls/comboboxbase.cpp b/QMLComponents/controls/comboboxbase.cpp index 3245c41de7..63f6f56d24 100644 --- a/QMLComponents/controls/comboboxbase.cpp +++ b/QMLComponents/controls/comboboxbase.cpp @@ -305,6 +305,8 @@ void ComboBoxBase::_setCurrentProperties(int index, bool bindValue) QString ComboBoxBase::generateMDHelp(int depth) const { + if(!isVisible()) + return ""; QStringList markdown; printLabelMD(markdown, depth); @@ -317,15 +319,13 @@ QString ComboBoxBase::generateMDHelp(int depth) const { QString label = term.label(), info = term.info(); - if (label.isEmpty()) + if (label.isEmpty() || info.isEmpty()) continue; - markdown << "\n" << QString{depth * 2, ' '} << "- *" << label << "*"; - if (!info.isEmpty()) - markdown << (": " + info); + markdown << "\n" << QString{depth * 2, ' '} << "- *" << label << "*" << (": " + info); } } - else + else if(!hasInfo()) { markdown << "\n" << QString{depth * 2, ' '}; // Display the options in one line separated by a comma. diff --git a/QMLComponents/controls/expanderbuttonbase.cpp b/QMLComponents/controls/expanderbuttonbase.cpp index 9649e46ca3..38089c248b 100644 --- a/QMLComponents/controls/expanderbuttonbase.cpp +++ b/QMLComponents/controls/expanderbuttonbase.cpp @@ -35,7 +35,8 @@ void ExpanderButtonBase::setUp() QString ExpanderButtonBase::generateMDHelp(int depth) const { - if (!hasInfo()) return ""; + if (!hasInfo() || !isVisible()) + return ""; // For Section, draw first a line, and reset the depth to 0. return "\n---\n\n" + JASPControl::generateMDHelp(0); diff --git a/QMLComponents/controls/jaspcontrol.cpp b/QMLComponents/controls/jaspcontrol.cpp index 051857fbe9..3e3c65665f 100644 --- a/QMLComponents/controls/jaspcontrol.cpp +++ b/QMLComponents/controls/jaspcontrol.cpp @@ -598,7 +598,7 @@ bool JASPControl::printLabelMD(QStringList& md, int depth) const QString JASPControl::generateMDHelp(int depth) const { - if (!hasInfo()) return ""; + if (!hasInfo() || !isVisible()) return ""; QStringList childMDs, markdown;