diff --git a/doc/qtdesignstudio/images/content-library.webp b/doc/qtdesignstudio/images/content-library.webp index ac5ca5f6971..6c6a7628fc8 100644 Binary files a/doc/qtdesignstudio/images/content-library.webp and b/doc/qtdesignstudio/images/content-library.webp differ diff --git a/doc/qtdesignstudio/images/qt-bridge-xd-home.png b/doc/qtdesignstudio/images/qt-bridge-xd-home.png index 2d4dfa20db0..3958e0d78ec 100644 Binary files a/doc/qtdesignstudio/images/qt-bridge-xd-home.png and b/doc/qtdesignstudio/images/qt-bridge-xd-home.png differ diff --git a/doc/qtdesignstudio/src/qtbridge/qtbridge-xd-using.qdoc b/doc/qtdesignstudio/src/qtbridge/qtbridge-xd-using.qdoc index f4728fa415a..40c72ab697a 100644 --- a/doc/qtdesignstudio/src/qtbridge/qtbridge-xd-using.qdoc +++ b/doc/qtdesignstudio/src/qtbridge/qtbridge-xd-using.qdoc @@ -113,14 +113,35 @@ \c {QtQuick.Controls 2.3} and to use Qt Quick Studio Components 1.0, you need the import statement \c {QtQuick.Studio.Components 1.0}. You can also import a module as an alias. - \li In the \uicontrol {Properties} field, specify properties for the - component. You can add and modify properties in \QDS. + \li In the \uicontrol {Properties} field, specify new properties or assign + value to the existing properties of the component. You can also add and modify + properties in \QDS. + Following are few examples of properties: + \code + property int counter: 5 + property string label: "ok" + antialiasing : true + width: parent.width / 2 + \endcode + \li In the \uicontrol {Snippet} field, specify component to be added as child under this + component. + Following example adds a Connection component: + \code + Connections { + target: myItem + onVisibleChanged: console.log(original_Text.visible) + } + \endcode + \note The code must have a scope of a component(e.g. Item, MouseArea, Connections etc.) + with a valid syntax for \l {UI Files}. \li Select the \uicontrol Clip check box to enable clipping in the component generated from the layer. The generated component will clip its own painting, as well as the painting of its children, to its bounding rectangle. \li Select the \uicontrol Visible check box to determine the visibility of the layer in the generated UI in \QDS. + \li Select the \uicontrol {Start Screen} check box to set the selected component as the + start component. \li Select \uicontrol Export to launch the export dialog to export the document into a .qtbridge archive. \endlist diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc index 23c998d9fcd..d86d1f55249 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc @@ -226,6 +226,8 @@ To align a camera to the \uicontrol{3D} view: \list 1 \li Select a camera in the \uicontrol{3D} or \uicontrol {Navigator} view. + \note If you don't have a camera selected, the most recently selected camera + is aligned to the view. \li In the \uicontrol{3D} view, select \inlineimage icons/align-camera-on.png . @@ -234,9 +236,11 @@ This moves and rotates the camera so that the camera shows the same view as the current view in the \uicontrol{3D} view. - To align the the \uicontrol{3D} view to a camera: + To align the \uicontrol{3D} view to a camera: \list 1 \li Select a camera in the \uicontrol{3D} view or \uicontrol {Navigator}. + \note If you don't have a camera selected, the view is aligned to the most recently + selected camera. \li In the \uicontrol{3D} view, select \inlineimage icons/align-view-on.png . diff --git a/doc/qtdesignstudio/src/views/studio-content-library.qdoc b/doc/qtdesignstudio/src/views/studio-content-library.qdoc index 67d67c91534..e2785149e19 100644 --- a/doc/qtdesignstudio/src/views/studio-content-library.qdoc +++ b/doc/qtdesignstudio/src/views/studio-content-library.qdoc @@ -11,7 +11,7 @@ \note The \uicontrol {Content Library} view is included in the \l{https://www.qt.io/pricing}{Qt Design Studio Enterprise license}. - The \uicontrol{Content Library} view contains material, texture, and + The \uicontrol{Content Library} view contains material, texture, effect, and environment bundles with assets that you can use in your project. When you have added an asset from \uicontrol {Content Library}, you can use it in your project. @@ -63,4 +63,15 @@ \endlist \endlist + \section1 Adding an Effect to Your Project + + To add an effect to your project, do one of the following: + + \list + \li Right-click the effect in \uicontrol {Content Library} and select + \uicontrol {Add an Instance}. + \li From \uicontrol {Content Library}, drag the effect to the \uicontrol 3D or + \uicontrol Navigator view. + \endlist + */ diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml new file mode 100644 index 00000000000..d544393a33b --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick + +PopupDialog { + property alias backend: form.backend + + BindingsDialogForm { + id: form + y: 32 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml new file mode 100644 index 00000000000..932860f34cb --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml @@ -0,0 +1,101 @@ + +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +import QtQuick +import QtQuick.Controls +import StudioControls + +Rectangle { + width: 400 + height: 800 + color: "#1b1b1b" + + property var backend + + Text { + id: text1 + x: 10 + y: 25 + color: "#ffffff" + text: qsTr("Target") + font.pixelSize: 15 + } + + Text { + id: text111 + x: 80 + y: 25 + color: "red" + text: backend.targetNode + font.pixelSize: 15 + } + + TopLevelComboBox { + id: target + x: 101 + width: 210 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -335 + model: backend.property.model ?? [] + enabled: false + //I see no use case to actually change the property name + //onActivated: backend.targetNode.activateIndex(target.currentIndex) + property int currentTypeIndex: backend.property.currentIndex ?? 0 + onCurrentTypeIndexChanged: target.currentIndex = target.currentTypeIndex + } + + Text { + id: text2 + x: 13 + y: 111 + color: "#ffffff" + text: qsTr("Source Propety") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: sourceNode + x: 135 + y: 98 + width: 156 + + model: backend.sourceNode.model ?? [] + + onModelChanged: sourceNode.currentIndex = sourceNode.currentTypeIndex + + onActivated: backend.sourceNode.activateIndex(sourceNode.currentIndex) + property int currentTypeIndex: backend.sourceNode.currentIndex ?? 0 + onCurrentTypeIndexChanged: sourceNode.currentIndex = sourceNode.currentTypeIndex + } + + Text { + x: 13 + y: 88 + color: "#ffffff" + text: qsTr("Source Node") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: sourceProperty + x: 140 + y: 121 + width: 156 + + model: backend.sourceProperty.model ?? [] + onModelChanged: sourceProperty.currentIndex = sourceProperty.currentTypeIndex + onActivated: backend.sourceProperty.activateIndex( + sourceProperty.currentIndex) + property int currentTypeIndex: backend.sourceProperty.currentIndex ?? 0 + onCurrentTypeIndexChanged: sourceProperty.currentIndex = sourceProperty.currentTypeIndex + } + + Text { + id: text3 + x: 10 + y: 55 + color: "#ffffff" + text: qsTr("Property") + font.pixelSize: 15 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml new file mode 100644 index 00000000000..1e8e123aa3a --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml @@ -0,0 +1,123 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import ConnectionsEditor +import HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioTheme as StudioTheme +import ConnectionsEditorEditorBackend + +ListView { + id: listView + width: 606 + height: 160 + interactive: false + highlightMoveDuration: 0 + + onVisibleChanged: { + dialog.hide() + } + + property int modelCurrentIndex: listView.model.currentIndex ?? 0 + + /* Something weird with currentIndex happens when items are removed added. + listView.model.currentIndex contains the persistent index. + */ + onModelCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + } + + onCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + dialog.backend.currentRow = listView.currentIndex + } + + data: [ + BindingsDialog { + id: dialog + visible: false + backend: listView.model.delegate + } + ] + delegate: Item { + + width: 600 + height: 18 + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: { + listView.model.currentIndex = index + listView.currentIndex = index + dialog.backend.currentRow = index + dialog.popup(mouseArea) + } + + property int currentIndex: listView.currentIndex + } + + Row { + id: row1 + x: 0 + y: 0 + width: 600 + height: 16 + spacing: 10 + + Text { + width: 120 + color: "#ffffff" + text: target ?? "" + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + text: targetProperty ?? "" + color: "#ffffff" + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + text: source ?? "" + anchors.verticalCenter: parent.verticalCenter + color: "#ffffff" + font.bold: false + } + + Text { + width: 120 + text: sourceProperty ?? "" + anchors.verticalCenter: parent.verticalCenter + color: "#ffffff" + font.bold: false + } + + Text { + width: 120 + + text: "-" + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignRight + font.pointSize: 14 + color: "#ffffff" + font.bold: true + MouseArea { + anchors.fill: parent + onClicked: listView.model.remove(index) + } + } + } + } + + highlight: Rectangle { + color: "#2a5593" + width: 600 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml new file mode 100644 index 00000000000..a23ca40c9da --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick + +PopupDialog { + ConnectionsDialogForm { + y: 32 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml new file mode 100644 index 00000000000..d39aefdba3d --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml @@ -0,0 +1,242 @@ + +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +import QtQuick +import QtQuick.Controls +import StudioControls + +Rectangle { + width: 400 + height: 800 + color: "#1b1b1b" + + Text { + id: text1 + x: 10 + y: 25 + color: "#ffffff" + text: qsTr("Target:") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: target + x: 95 + width: 210 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -367 + model: ["mySpinbox", "foo", "backendObject"] + } + + Text { + id: text2 + x: 10 + y: 131 + color: "#ffffff" + text: qsTr("Signal") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: signal + x: 10 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -207 + model: ["onClicked", "onPressed", "onReleased"] + } + + TopLevelComboBox { + id: action + x: 207 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -207 + model: ["Call Function", "Assign", "ChnageState"] + } + + Text { + id: text3 + x: 207 + y: 131 + color: "#ffffff" + text: qsTr("Action") + font.pixelSize: 15 + } + + Item { + id: functionGroup + x: 0 + y: 276 + width: 400 + height: 176 + + Text { + id: text4 + x: 17 + y: -11 + color: "#ffffff" + text: qsTr("Target") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: functionTarget + x: 10 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -48 + model: ["mySpinBox", "backendObject", "someButton"] + } + + TopLevelComboBox { + id: functionName + x: 203 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -48 + model: ["start", "trigger", "stop"] + } + + Text { + id: text5 + x: 203 + y: -11 + color: "#ffffff" + text: qsTr("Function") + font.pixelSize: 15 + } + } + + Item { + id: statesGroup + x: 0 + y: 383 + width: 400 + height: 106 + Text { + id: text6 + x: 17 + y: -11 + color: "#ffffff" + text: qsTr("State Group") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: stateGroup + x: 10 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -12 + model: ["default", "State Group 01", "State Group 02"] + } + + TopLevelComboBox { + id: stateName + x: 209 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -12 + model: ["State 01", "State 02", "State 03"] + } + + Text { + id: text7 + x: 209 + y: -11 + color: "#ffffff" + text: qsTr("State") + font.pixelSize: 15 + } + } + + Item { + id: assignment + x: 10 + y: 505 + width: 400 + height: 106 + Text { + id: text8 + x: 17 + y: -11 + color: "#ffffff" + text: qsTr("target") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: valueTarget + x: 10 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -12 + model: ["mySpinBox", "myButton", "backendObject"] + } + + TopLevelComboBox { + id: valueSource + x: 209 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -12 + model: ["mySpinBox", "myButton", "backendObject"] + } + + Text { + id: text9 + x: 209 + y: -11 + color: "#ffffff" + text: qsTr("source") + font.pixelSize: 15 + } + + Text { + id: text10 + x: 17 + y: 76 + color: "#ffffff" + text: qsTr("value") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: valueOut + x: 10 + y: -2 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: 84 + model: ["width", "height", "opacity"] + } + + TopLevelComboBox { + id: valueIn + x: 209 + y: -2 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: 84 + model: ["width", "height", "x", "y"] + } + + Text { + id: text11 + x: 209 + y: 76 + color: "#ffffff" + text: qsTr("value") + font.pixelSize: 15 + } + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml new file mode 100644 index 00000000000..3238b6cd9b4 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml @@ -0,0 +1,115 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import ConnectionsEditor +import HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioTheme as StudioTheme +import ConnectionsEditorEditorBackend + +ListView { + id: listView + width: 606 + height: 160 + interactive: false + highlightMoveDuration: 0 + + onVisibleChanged: { + dialog.hide() + } + + property int modelCurrentIndex: listView.model.currentIndex ?? 0 + + /* + Something weird with currentIndex happens when items are removed added. + listView.model.currentIndex contains the persistent index. + */ + onModelCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + } + + onCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + } + + data: [ + ConnectionsDialog { + id: dialog + visible: false + } + ] + + delegate: Item { + + width: 600 + height: 18 + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: { + listView.model.currentIndex = index + listView.currentIndex = index + dialog.popup(mouseArea) + } + + property int currentIndex: listView.currentIndex + } + + Row { + id: row1 + x: 0 + y: 0 + width: 600 + height: 16 + spacing: 10 + + Text { + width: 120 + color: "#ffffff" + text: target + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + text: signal + color: "#ffffff" + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + + text: action + anchors.verticalCenter: parent.verticalCenter + color: "#ffffff" + font.bold: false + } + + Text { + width: 120 + + text: "-" + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignRight + font.pointSize: 14 + color: "#ffffff" + font.bold: true + MouseArea { + anchors.fill: parent + onClicked: listView.model.remove(index) + } + } + } + } + + highlight: Rectangle { + color: "#2a5593" + width: 600 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/Main.qml b/share/qtcreator/qmldesigner/connectionseditor/Main.qml new file mode 100644 index 00000000000..628a00a548a --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/Main.qml @@ -0,0 +1,117 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import ConnectionsEditor +import HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioTheme as StudioTheme +import ConnectionsEditorEditorBackend + +Rectangle { + width: 640 + height: 1080 + color: "#232222" + + Rectangle { + id: rectangle + x: 10 + y: 10 + width: 620 + height: 97 + color: "#333333" + + Rectangle { + id: rectangle1 + x: 10 + y: 10 + width: 600 + height: 34 + color: "#00ffffff" + border.width: 1 + + Text { + id: text1 + x: 10 + y: 10 + color: "#b5b2b2" + text: qsTr("Search") + font.pixelSize: 12 + } + } + + RowLayout { + x: 10 + y: 50 + TabCheckButton { + id: connections + text: "Connections" + checked: true + autoExclusive: true + checkable: true + } + + TabCheckButton { + id: bindings + text: "Bindings" + autoExclusive: true + checkable: true + } + + TabCheckButton { + id: properties + text: "Properties" + autoExclusive: true + checkable: true + } + } + + Text { + id: text2 + x: 577 + y: 58 + color: "#ffffff" + text: qsTr("+") + font.pixelSize: 18 + font.bold: true + MouseArea { + anchors.fill: parent + onClicked: { + print(ConnectionsEditorEditorBackend.dynamicPropertiesModel.delegate) + print(ConnectionsEditorEditorBackend.dynamicPropertiesModel.delegate.type) + print(ConnectionsEditorEditorBackend.dynamicPropertiesModel.delegate.type.model) + + if (connections.checked) + ConnectionsEditorEditorBackend.connectionModel.add() + else if (bindings.checked) + ConnectionsEditorEditorBackend.bindingModel.add() + else if (properties.checked) + ConnectionsEditorEditorBackend.dynamicPropertiesModel.add() + } + } + } + } + + ConnectionsListView { + visible: connections.checked + x: 17 + y: 124 + model: ConnectionsEditorEditorBackend.connectionModel + } + + BindingsListView { + visible: bindings.checked + x: 17 + y: 124 + model: ConnectionsEditorEditorBackend.bindingModel + } + + PropertiesListView { + visible: properties.checked + x: 17 + y: 124 + model: ConnectionsEditorEditorBackend.dynamicPropertiesModel + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml new file mode 100644 index 00000000000..3aad2785ce2 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml @@ -0,0 +1,72 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls + +Window { + id: window + width: 400 + height: 800 + visible: true + flags: Qt.FramelessWindowHint || Qt.Dialog + + color: Qt.transparent + + property int bw: 5 + + function popup(item) { + print("popup " + item) + var padding = 12 + var p = item.mapToGlobal(0, 0) + dialog.x = p.x - dialog.width - padding + if (dialog.x < 0) + dialog.x = p.x + item.width + padding + dialog.y = p.y + dialog.show() + dialog.raise() + } + + Rectangle { + id: rectangle1 + color: "#d7d7d7" + border.color: "#232323" + anchors.fill: parent + + + + Rectangle { + id: rectangle + height: 32 + color: "#797979" + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: 0 + anchors.leftMargin: 0 + anchors.rightMargin: 0 + DragHandler { + grabPermissions: TapHandler.CanTakeOverFromAnything + onActiveChanged: if (active) { window.startSystemMove(); } + } + + Rectangle { + id: rectangle2 + x: 329 + width: 16 + height: 16 + color: "#ffffff" + radius: 4 + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 6 + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: window.close() + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml new file mode 100644 index 00000000000..cd0ae16226a --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick + +PopupDialog { + property alias backend: form.backend + + PropertiesDialogForm { + id: form + y: 32 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml new file mode 100644 index 00000000000..c728027f30f --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml @@ -0,0 +1,76 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +import StudioControls + +Rectangle { + width: 400 + height: 800 + color: "#1b1b1b" + property var backend + + Text { + id: text1 + x: 10 + y: 25 + color: "#ffffff" + text: qsTr("Type:") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: target + x: 95 + width: 210 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -367 + model: backend.type.model ?? [] + onActivated: backend.type.activateIndex(target.currentIndex) + property int currentTypeIndex: backend.type.currentIndex ?? 0 + onCurrentTypeIndexChanged: target.currentIndex = target.currentTypeIndex + } + + Text { + id: text2 + x: 10 + y: 131 + color: "#ffffff" + text: qsTr("Name") + font.pixelSize: 15 + } + + TextInput { + id: name + x: 70 + y: 131 + color: "white" + width: 156 + text: backend.name.text ?? "" + onEditingFinished: { + backend.name.activateText(name.text) + } + } + + Text { + x: 10 + y: 81 + color: "#ffffff" + text: qsTr("Value") + font.pixelSize: 15 + } + + TextInput { + id: value + color: "red" + x: 70 + y: 81 + width: 156 + text: backend.value.text ?? "" + onEditingFinished: { + backend.value.activateText(value.text) + } + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml new file mode 100644 index 00000000000..9e0822dc6c3 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml @@ -0,0 +1,128 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import ConnectionsEditor +import HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioTheme as StudioTheme +import ConnectionsEditorEditorBackend + +ListView { + id: listView + width: 606 + height: 160 + interactive: false + highlightMoveDuration: 0 + + onVisibleChanged: { + dialog.hide() + } + + property int modelCurrentIndex: listView.model.currentIndex ?? 0 + + /* Something weird with currentIndex happens when items are removed added. + listView.model.currentIndex contains the persistent index. + */ + onModelCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + } + + onCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + dialog.backend.currentRow = listView.currentIndex + } + + data: [ + PropertiesDialog { + id: dialog + visible: false + backend: listView.model.delegate + } + ] + + delegate: Item { + + width: 600 + height: 18 + + MouseArea { + id: mouseArea + anchors.fill: parent + + property int currentIndex: listView.currentIndex + + Connections { + target: mouseArea + function onClicked() { + listView.model.currentIndex = index + listView.currentIndex = index + dialog.backend.currentRow = index + dialog.popup(mouseArea) + } + } + } + + Row { + id: row1 + x: 0 + y: 0 + width: 600 + height: 16 + spacing: 10 + + Text { + width: 120 + color: "#ffffff" + text: target ?? "" + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + text: name ?? "" + color: "#ffffff" + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + text: type ?? "" + anchors.verticalCenter: parent.verticalCenter + color: "#ffffff" + font.bold: false + } + + Text { + width: 120 + text: value ?? "" + anchors.verticalCenter: parent.verticalCenter + color: "#ffffff" + font.bold: false + } + + Text { + width: 120 + + text: "-" + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignRight + font.pointSize: 14 + color: "#ffffff" + font.bold: true + MouseArea { + anchors.fill: parent + onClicked: listView.model.remove(index) + } + } + } + } + + highlight: Rectangle { + color: "#2a5593" + width: 600 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/TabCheckButton.qml b/share/qtcreator/qmldesigner/connectionseditor/TabCheckButton.qml new file mode 100644 index 00000000000..c49bc539157 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/TabCheckButton.qml @@ -0,0 +1,80 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates + +Button { + id: control + + implicitWidth: Math.max( + buttonBackground ? buttonBackground.implicitWidth : 0, + textItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max( + buttonBackground ? buttonBackground.implicitHeight : 0, + textItem.implicitHeight + topPadding + bottomPadding) + leftPadding: 4 + rightPadding: 4 + + text: "My Button" + + background: buttonBackground + Rectangle { + id: buttonBackground + color: "#047eff" + implicitWidth: 100 + implicitHeight: 40 + opacity: enabled ? 1 : 0.3 + radius: 12 + border.color: "#047eff" + } + + contentItem: textItem + Text { + id: textItem + text: control.text + + opacity: enabled ? 1.0 : 0.3 + color: "#ffffff" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + states: [ + State { + name: "normal" + when: !control.down && !control.checked + + PropertyChanges { + target: buttonBackground + visible: false + color: "#00000000" + border.color: "#047eff" + } + + PropertyChanges { + target: textItem + color: "#ffffff" + } + }, + State { + name: "down" + when: control.down && !control.checked + PropertyChanges { + target: textItem + color: "#ffffff" + } + + PropertyChanges { + target: buttonBackground + color: "#047eff" + border.color: "#00000000" + } + }, + State { + name: "down1" + when: control.checked + extend: "down" + } + ] +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/Constants.qml b/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/Constants.qml new file mode 100644 index 00000000000..c08adcf6360 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/Constants.qml @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +pragma Singleton +import QtQuick + +QtObject { + readonly property int width: 1920 + readonly property int height: 1080 + + property int thumbnailSize: 250 + // Breakpoint to control when the state thumbnail view toggles from normal to tiny + readonly property int thumbnailBreak: 150 + + readonly property int minThumbSize: 100 + readonly property int maxThumbSize: 350 +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/qmldir b/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/qmldir new file mode 100644 index 00000000000..616ac203530 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/qmldir @@ -0,0 +1 @@ +singleton Constants 1.0 Constants.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/SearchBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/SearchBox.qml index 3449fda86a6..4891c969aec 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/SearchBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/SearchBox.qml @@ -40,6 +40,13 @@ T.TextField { hoverEnabled: true clip: true + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Escape && event.modifiers === Qt.NoModifier) { + control.clear() + event.accepted = true + } + } + Text { id: placeholder x: control.leftPadding diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml index f5a3bbdc7bf..364b80a4856 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml @@ -179,166 +179,168 @@ QtObject { readonly property string materialPreviewEnvironment: "\u00C6" readonly property string materialPreviewModel: "\u00C7" readonly property string material_medium: "\u00C8" - readonly property string mergeCells: "\u00C9" - readonly property string merge_small: "\u00CA" - readonly property string minus: "\u00CB" - readonly property string mirror: "\u00CC" - readonly property string more_medium: "\u00CD" - readonly property string mouseArea_small: "\u00CE" - readonly property string moveDown_medium: "\u00CF" - readonly property string moveInwards_medium: "\u00D0" - readonly property string moveUp_medium: "\u00D1" - readonly property string moveUpwards_medium: "\u00D2" - readonly property string move_medium: "\u00D3" - readonly property string newMaterial: "\u00D4" - readonly property string nextFile_large: "\u00D5" - readonly property string openLink: "\u00D6" - readonly property string openMaterialBrowser: "\u00D7" - readonly property string orientation: "\u00D8" - readonly property string orthCam_medium: "\u00D9" - readonly property string orthCam_small: "\u00DA" - readonly property string paddingEdge: "\u00DB" - readonly property string paddingFrame: "\u00DC" - readonly property string particleAnimation_medium: "\u00DD" - readonly property string pasteStyle: "\u00DE" - readonly property string paste_small: "\u00DF" - readonly property string pause: "\u00E0" - readonly property string perspectiveCam_medium: "\u00E1" - readonly property string perspectiveCam_small: "\u00E2" - readonly property string pin: "\u00E3" - readonly property string plane_medium: "\u00E4" - readonly property string plane_small: "\u00E5" - readonly property string play: "\u00E6" - readonly property string playFill_medium: "\u00E7" - readonly property string playOutline_medium: "\u00E8" - readonly property string plus: "\u00E9" - readonly property string pointLight_small: "\u00EA" - readonly property string positioners_small: "\u00EB" - readonly property string previewEnv_medium: "\u00EC" - readonly property string previousFile_large: "\u00ED" - readonly property string promote: "\u00EE" - readonly property string properties_medium: "\u00EF" - readonly property string readOnly: "\u00F0" - readonly property string recordFill_medium: "\u00F1" - readonly property string recordOutline_medium: "\u00F2" - readonly property string redo: "\u00F3" - readonly property string reload_medium: "\u00F4" - readonly property string remove_medium: "\u00F5" - readonly property string remove_small: "\u00F6" - readonly property string rename_small: "\u00F7" - readonly property string replace_small: "\u00F8" - readonly property string resetView_small: "\u00F9" - readonly property string restartParticles_medium: "\u00FA" - readonly property string reverseOrder_medium: "\u00FB" - readonly property string roatate_medium: "\u00FC" - readonly property string rotationFill: "\u00FD" - readonly property string rotationOutline: "\u00FE" - readonly property string runProjFill_large: "\u00FF" - readonly property string runProjOutline_large: "\u0100" - readonly property string s_anchors: "\u0101" - readonly property string s_annotations: "\u0102" - readonly property string s_arrange: "\u0103" - readonly property string s_boundingBox: "\u0104" - readonly property string s_component: "\u0105" - readonly property string s_connections: "\u0106" - readonly property string s_edit: "\u0107" - readonly property string s_enterComponent: "\u0108" - readonly property string s_eventList: "\u0109" - readonly property string s_group: "\u010A" - readonly property string s_layouts: "\u010B" - readonly property string s_merging: "\u010C" - readonly property string s_mouseArea: "\u010D" - readonly property string s_positioners: "\u010E" - readonly property string s_selection: "\u010F" - readonly property string s_snapping: "\u0110" - readonly property string s_timeline: "\u0111" - readonly property string s_visibility: "\u0112" - readonly property string saveLogs_medium: "\u0113" - readonly property string scale_medium: "\u0114" - readonly property string search: "\u0115" - readonly property string search_small: "\u0116" - readonly property string sectionToggle: "\u0117" - readonly property string selectFill_medium: "\u0118" - readonly property string selectOutline_medium: "\u0119" - readonly property string selectParent_small: "\u011A" - readonly property string selection_small: "\u011B" - readonly property string settings_medium: "\u011C" - readonly property string signal_small: "\u011D" - readonly property string snapping_small: "\u011E" - readonly property string sphere_medium: "\u011F" - readonly property string sphere_small: "\u0120" - readonly property string splitColumns: "\u0121" - readonly property string splitRows: "\u0122" - readonly property string spotLight_small: "\u0123" - readonly property string stackedContainer_small: "\u0124" - readonly property string startNode: "\u0125" - readonly property string step_medium: "\u0126" - readonly property string stop_medium: "\u0127" - readonly property string testIcon: "\u0128" - readonly property string textAlignBottom: "\u0129" - readonly property string textAlignCenter: "\u012A" - readonly property string textAlignJustified: "\u012B" - readonly property string textAlignLeft: "\u012C" - readonly property string textAlignMiddle: "\u012D" - readonly property string textAlignRight: "\u012E" - readonly property string textAlignTop: "\u012F" - readonly property string textBulletList: "\u0130" - readonly property string textFullJustification: "\u0131" - readonly property string textNumberedList: "\u0132" - readonly property string textures_medium: "\u0133" - readonly property string tickIcon: "\u0134" - readonly property string tickMark_small: "\u0135" - readonly property string timeline_small: "\u0136" - readonly property string toEndFrame_medium: "\u0137" - readonly property string toNextFrame_medium: "\u0138" - readonly property string toPrevFrame_medium: "\u0139" - readonly property string toStartFrame_medium: "\u013A" - readonly property string topToolbar_annotations: "\u013B" - readonly property string topToolbar_closeFile: "\u013C" - readonly property string topToolbar_designMode: "\u013D" - readonly property string topToolbar_enterComponent: "\u013E" - readonly property string topToolbar_home: "\u013F" - readonly property string topToolbar_makeComponent: "\u0140" - readonly property string topToolbar_navFile: "\u0141" - readonly property string topToolbar_runProject: "\u0142" - readonly property string translationCreateFiles: "\u0143" - readonly property string translationCreateReport: "\u0144" - readonly property string translationExport: "\u0145" - readonly property string translationImport: "\u0146" - readonly property string translationSelectLanguages: "\u0147" - readonly property string translationTest: "\u0148" - readonly property string transparent: "\u0149" - readonly property string triState: "\u014A" - readonly property string triangleArcA: "\u014B" - readonly property string triangleArcB: "\u014C" - readonly property string triangleCornerA: "\u014D" - readonly property string triangleCornerB: "\u014E" - readonly property string unLinked: "\u014F" - readonly property string undo: "\u0150" - readonly property string unify_medium: "\u0151" - readonly property string unpin: "\u0152" - readonly property string upDownIcon: "\u0153" - readonly property string upDownSquare2: "\u0154" - readonly property string updateAvailable_medium: "\u0155" - readonly property string updateContent_medium: "\u0156" - readonly property string visibilityOff: "\u0157" - readonly property string visibilityOn: "\u0158" - readonly property string visible_medium: "\u0159" - readonly property string visible_small: "\u015A" - readonly property string wildcard: "\u015B" - readonly property string wizardsAutomotive: "\u015C" - readonly property string wizardsDesktop: "\u015D" - readonly property string wizardsGeneric: "\u015E" - readonly property string wizardsMcuEmpty: "\u015F" - readonly property string wizardsMcuGraph: "\u0160" - readonly property string wizardsMobile: "\u0161" - readonly property string wizardsUnknown: "\u0162" - readonly property string zoomAll: "\u0163" - readonly property string zoomIn: "\u0164" - readonly property string zoomIn_medium: "\u0165" - readonly property string zoomOut: "\u0166" - readonly property string zoomOut_medium: "\u0167" - readonly property string zoomSelection: "\u0168" + readonly property string maxBar_small: "\u00C9" + readonly property string mergeCells: "\u00CA" + readonly property string merge_small: "\u00CB" + readonly property string minus: "\u00CC" + readonly property string mirror: "\u00CD" + readonly property string more_medium: "\u00CE" + readonly property string mouseArea_small: "\u00CF" + readonly property string moveDown_medium: "\u00D0" + readonly property string moveInwards_medium: "\u00D1" + readonly property string moveUp_medium: "\u00D2" + readonly property string moveUpwards_medium: "\u00D3" + readonly property string move_medium: "\u00D4" + readonly property string newMaterial: "\u00D5" + readonly property string nextFile_large: "\u00D6" + readonly property string normalBar_small: "\u00D7" + readonly property string openLink: "\u00D8" + readonly property string openMaterialBrowser: "\u00D9" + readonly property string orientation: "\u00DA" + readonly property string orthCam_medium: "\u00DB" + readonly property string orthCam_small: "\u00DC" + readonly property string paddingEdge: "\u00DD" + readonly property string paddingFrame: "\u00DE" + readonly property string particleAnimation_medium: "\u00DF" + readonly property string pasteStyle: "\u00E0" + readonly property string paste_small: "\u00E1" + readonly property string pause: "\u00E2" + readonly property string perspectiveCam_medium: "\u00E3" + readonly property string perspectiveCam_small: "\u00E4" + readonly property string pin: "\u00E5" + readonly property string plane_medium: "\u00E6" + readonly property string plane_small: "\u00E7" + readonly property string play: "\u00E8" + readonly property string playFill_medium: "\u00E9" + readonly property string playOutline_medium: "\u00EA" + readonly property string plus: "\u00EB" + readonly property string pointLight_small: "\u00EC" + readonly property string positioners_small: "\u00ED" + readonly property string previewEnv_medium: "\u00EE" + readonly property string previousFile_large: "\u00EF" + readonly property string promote: "\u00F0" + readonly property string properties_medium: "\u00F1" + readonly property string readOnly: "\u00F2" + readonly property string recordFill_medium: "\u00F3" + readonly property string recordOutline_medium: "\u00F4" + readonly property string redo: "\u00F5" + readonly property string reload_medium: "\u00F6" + readonly property string remove_medium: "\u00F7" + readonly property string remove_small: "\u00F8" + readonly property string rename_small: "\u00F9" + readonly property string replace_small: "\u00FA" + readonly property string resetView_small: "\u00FB" + readonly property string restartParticles_medium: "\u00FC" + readonly property string reverseOrder_medium: "\u00FD" + readonly property string roatate_medium: "\u00FE" + readonly property string rotationFill: "\u00FF" + readonly property string rotationOutline: "\u0100" + readonly property string runProjFill_large: "\u0101" + readonly property string runProjOutline_large: "\u0102" + readonly property string s_anchors: "\u0103" + readonly property string s_annotations: "\u0104" + readonly property string s_arrange: "\u0105" + readonly property string s_boundingBox: "\u0106" + readonly property string s_component: "\u0107" + readonly property string s_connections: "\u0108" + readonly property string s_edit: "\u0109" + readonly property string s_enterComponent: "\u010A" + readonly property string s_eventList: "\u010B" + readonly property string s_group: "\u010C" + readonly property string s_layouts: "\u010D" + readonly property string s_merging: "\u010E" + readonly property string s_mouseArea: "\u010F" + readonly property string s_positioners: "\u0110" + readonly property string s_selection: "\u0111" + readonly property string s_snapping: "\u0112" + readonly property string s_timeline: "\u0113" + readonly property string s_visibility: "\u0114" + readonly property string saveLogs_medium: "\u0115" + readonly property string scale_medium: "\u0116" + readonly property string search: "\u0117" + readonly property string search_small: "\u0118" + readonly property string sectionToggle: "\u0119" + readonly property string selectFill_medium: "\u011A" + readonly property string selectOutline_medium: "\u011B" + readonly property string selectParent_small: "\u011C" + readonly property string selection_small: "\u011D" + readonly property string settings_medium: "\u011E" + readonly property string signal_small: "\u011F" + readonly property string snapping_small: "\u0120" + readonly property string sphere_medium: "\u0121" + readonly property string sphere_small: "\u0122" + readonly property string splitColumns: "\u0123" + readonly property string splitRows: "\u0124" + readonly property string spotLight_small: "\u0125" + readonly property string stackedContainer_small: "\u0126" + readonly property string startNode: "\u0127" + readonly property string step_medium: "\u0128" + readonly property string stop_medium: "\u0129" + readonly property string testIcon: "\u012A" + readonly property string textAlignBottom: "\u012B" + readonly property string textAlignCenter: "\u012C" + readonly property string textAlignJustified: "\u012D" + readonly property string textAlignLeft: "\u012E" + readonly property string textAlignMiddle: "\u012F" + readonly property string textAlignRight: "\u0130" + readonly property string textAlignTop: "\u0131" + readonly property string textBulletList: "\u0132" + readonly property string textFullJustification: "\u0133" + readonly property string textNumberedList: "\u0134" + readonly property string textures_medium: "\u0135" + readonly property string tickIcon: "\u0136" + readonly property string tickMark_small: "\u0137" + readonly property string timeline_small: "\u0138" + readonly property string toEndFrame_medium: "\u0139" + readonly property string toNextFrame_medium: "\u013A" + readonly property string toPrevFrame_medium: "\u013B" + readonly property string toStartFrame_medium: "\u013C" + readonly property string topToolbar_annotations: "\u013D" + readonly property string topToolbar_closeFile: "\u013E" + readonly property string topToolbar_designMode: "\u013F" + readonly property string topToolbar_enterComponent: "\u0140" + readonly property string topToolbar_home: "\u0141" + readonly property string topToolbar_makeComponent: "\u0142" + readonly property string topToolbar_navFile: "\u0143" + readonly property string topToolbar_runProject: "\u0144" + readonly property string translationCreateFiles: "\u0145" + readonly property string translationCreateReport: "\u0146" + readonly property string translationExport: "\u0147" + readonly property string translationImport: "\u0148" + readonly property string translationSelectLanguages: "\u0149" + readonly property string translationTest: "\u014A" + readonly property string transparent: "\u014B" + readonly property string triState: "\u014C" + readonly property string triangleArcA: "\u014D" + readonly property string triangleArcB: "\u014E" + readonly property string triangleCornerA: "\u014F" + readonly property string triangleCornerB: "\u0150" + readonly property string unLinked: "\u0151" + readonly property string undo: "\u0152" + readonly property string unify_medium: "\u0153" + readonly property string unpin: "\u0154" + readonly property string upDownIcon: "\u0155" + readonly property string upDownSquare2: "\u0156" + readonly property string updateAvailable_medium: "\u0157" + readonly property string updateContent_medium: "\u0158" + readonly property string visibilityOff: "\u0159" + readonly property string visibilityOn: "\u015A" + readonly property string visible_medium: "\u015B" + readonly property string visible_small: "\u015C" + readonly property string wildcard: "\u015D" + readonly property string wizardsAutomotive: "\u015E" + readonly property string wizardsDesktop: "\u015F" + readonly property string wizardsGeneric: "\u0160" + readonly property string wizardsMcuEmpty: "\u0161" + readonly property string wizardsMcuGraph: "\u0162" + readonly property string wizardsMobile: "\u0163" + readonly property string wizardsUnknown: "\u0164" + readonly property string zoomAll: "\u0165" + readonly property string zoomIn: "\u0166" + readonly property string zoomIn_medium: "\u0167" + readonly property string zoomOut: "\u0168" + readonly property string zoomOut_medium: "\u0169" + readonly property string zoomSelection: "\u016A" readonly property font iconFont: Qt.font({ "family": controlIcons.name, diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf index 3c3fa3bd67d..312554382ce 100644 Binary files a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf and b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf differ diff --git a/src/libs/advanceddockingsystem/CMakeLists.txt b/src/libs/advanceddockingsystem/CMakeLists.txt index 19d89f41d6c..2ec5c94b422 100644 --- a/src/libs/advanceddockingsystem/CMakeLists.txt +++ b/src/libs/advanceddockingsystem/CMakeLists.txt @@ -3,6 +3,9 @@ add_qtc_library(AdvancedDockingSystem SOURCES ads_globals.cpp ads_globals.h advanceddockingsystemtr.h + autohidedockcontainer.cpp autohidedockcontainer.h + autohidesidebar.cpp autohidesidebar.h + autohidetab.cpp autohidetab.h dockareatabbar.cpp dockareatabbar.h dockareatitlebar.cpp dockareatitlebar.h dockareawidget.cpp dockareawidget.h @@ -19,6 +22,8 @@ add_qtc_library(AdvancedDockingSystem floatingdockcontainer.cpp floatingdockcontainer.h floatingdragpreview.cpp floatingdragpreview.h iconprovider.cpp iconprovider.h + pushbutton.cpp pushbutton.h + resizehandle.cpp resizehandle.h workspace.cpp workspace.h workspacedialog.cpp workspacedialog.h workspaceinputdialog.cpp workspaceinputdialog.h @@ -28,6 +33,7 @@ add_qtc_library(AdvancedDockingSystem extend_qtc_library(AdvancedDockingSystem INCLUDES linux + CONDITION UNIX AND NOT APPLE SOURCES linux/floatingwidgettitlebar.cpp linux/floatingwidgettitlebar.h ) diff --git a/src/libs/advanceddockingsystem/ads_globals.cpp b/src/libs/advanceddockingsystem/ads_globals.cpp index 252a31f9d30..664f10ca8a5 100644 --- a/src/libs/advanceddockingsystem/ads_globals.cpp +++ b/src/libs/advanceddockingsystem/ads_globals.cpp @@ -14,10 +14,19 @@ #include #include +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +#include +#include +#include +#endif + namespace ADS { namespace internal { +const int g_floatingWidgetDragStartEvent = QEvent::registerEventType(); +const int g_dockedWidgetDragStartEvent = QEvent::registerEventType(); + void replaceSplitterWidget(QSplitter *splitter, QWidget *from, QWidget *to) { int index = splitter->indexOf(from); @@ -25,6 +34,16 @@ void replaceSplitterWidget(QSplitter *splitter, QWidget *from, QWidget *to) splitter->insertWidget(index, to); } +void hideEmptyParentSplitters(DockSplitter *splitter) +{ + while (splitter && splitter->isVisible()) { + if (!splitter->hasVisibleContent()) + splitter->hide(); + + splitter = internal::findParent(splitter); + } +} + DockInsertParam dockAreaInsertParameters(DockWidgetArea area) { switch (area) { @@ -44,6 +63,45 @@ DockInsertParam dockAreaInsertParameters(DockWidgetArea area) return DockInsertParam(Qt::Vertical, false); } +SideBarLocation toSideBarLocation(DockWidgetArea area) +{ + switch (area) { + case LeftAutoHideArea: + return SideBarLeft; + case RightAutoHideArea: + return SideBarRight; + case TopAutoHideArea: + return SideBarTop; + case BottomAutoHideArea: + return SideBarBottom; + default: + return SideBarNone; + } + + return SideBarNone; +} + +bool isHorizontalSideBarLocation(SideBarLocation location) +{ + switch (location) { + case SideBarTop: + case SideBarBottom: + return true; + case SideBarLeft: + case SideBarRight: + return false; + default: + return false; + } + + return false; +} + +bool isSideBarArea(DockWidgetArea area) +{ + return toSideBarLocation(area) != SideBarNone; +} + QPixmap createTransparentPixmap(const QPixmap &source, qreal opacity) { QPixmap transparentPixmap(source.size()); @@ -54,18 +112,8 @@ QPixmap createTransparentPixmap(const QPixmap &source, qreal opacity) return transparentPixmap; } -void hideEmptyParentSplitters(DockSplitter *splitter) -{ - while (splitter && splitter->isVisible()) { - if (!splitter->hasVisibleContent()) { - splitter->hide(); - } - splitter = internal::findParent(splitter); - } -} - void setButtonIcon(QAbstractButton *button, - QStyle::StandardPixmap standarPixmap, + QStyle::StandardPixmap standardPixmap, ADS::eIcon customIconId) { // First we try to use custom icons if available @@ -75,12 +123,12 @@ void setButtonIcon(QAbstractButton *button, return; } - if (Utils::HostOsInfo::isLinuxHost()) { - button->setIcon(button->style()->standardIcon(standarPixmap)); + if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost()) { + button->setIcon(button->style()->standardIcon(standardPixmap)); } else { // The standard icons does not look good on high DPI screens so we create // our own "standard" icon here. - QPixmap normalPixmap = button->style()->standardPixmap(standarPixmap, nullptr, button); + QPixmap normalPixmap = button->style()->standardPixmap(standardPixmap, nullptr, button); icon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), QIcon::Disabled); icon.addPixmap(normalPixmap, QIcon::Normal); button->setIcon(icon); @@ -98,14 +146,22 @@ void repolishStyle(QWidget *widget, eRepolishChildOptions options) if (RepolishIgnoreChildren == options) return; - QList children = widget->findChildren(QString(), - (RepolishDirectChildren == options) ? Qt::FindDirectChildrenOnly : Qt::FindChildrenRecursively); - for (auto w : children) - { + QList children = widget->findChildren(QString(), + (RepolishDirectChildren == options) + ? Qt::FindDirectChildrenOnly + : Qt::FindChildrenRecursively); + for (auto w : children) { w->style()->unpolish(w); w->style()->polish(w); } } +QRect globalGeometry(QWidget *w) +{ + QRect g = w->geometry(); + g.moveTopLeft(w->mapToGlobal(QPoint(0, 0))); + return g; +} + } // namespace internal } // namespace ADS diff --git a/src/libs/advanceddockingsystem/ads_globals.h b/src/libs/advanceddockingsystem/ads_globals.h index d2f882d8e71..8541e3d9da7 100644 --- a/src/libs/advanceddockingsystem/ads_globals.h +++ b/src/libs/advanceddockingsystem/ads_globals.h @@ -16,11 +16,11 @@ class QSplitter; QT_END_NAMESPACE #if defined(ADVANCEDDOCKINGSYSTEM_LIBRARY) -# define ADS_EXPORT Q_DECL_EXPORT +#define ADS_EXPORT Q_DECL_EXPORT #elif defined(ADVANCEDDOCKINGSYSTEM_STATIC_LIBRARY) -# define ADS_EXPORT +#define ADS_EXPORT #else -# define ADS_EXPORT Q_DECL_IMPORT +#define ADS_EXPORT Q_DECL_IMPORT #endif //#define ADS_DEBUG_PRINT @@ -47,15 +47,27 @@ enum DockWidgetArea { TopDockWidgetArea = 0x04, BottomDockWidgetArea = 0x08, CenterDockWidgetArea = 0x10, + LeftAutoHideArea = 0x20, + RightAutoHideArea = 0x40, + TopAutoHideArea = 0x80, + BottomAutoHideArea = 0x100, InvalidDockWidgetArea = NoDockWidgetArea, OuterDockAreas = TopDockWidgetArea | LeftDockWidgetArea | RightDockWidgetArea | BottomDockWidgetArea, + AutoHideDockAreas = LeftAutoHideArea | RightAutoHideArea | TopAutoHideArea | BottomAutoHideArea, AllDockAreas = OuterDockAreas | CenterDockWidgetArea }; Q_DECLARE_FLAGS(DockWidgetAreas, DockWidgetArea) -enum eTitleBarButton { TitleBarButtonTabsMenu, TitleBarButtonUndock, TitleBarButtonClose }; +enum eTabIndex { TabDefaultInsertIndex = -1, TabInvalidIndex = -2 }; + +enum eTitleBarButton { + TitleBarButtonTabsMenu, + TitleBarButtonUndock, + TitleBarButtonClose, + TitleBarButtonAutoHide +}; /** * The different dragging states @@ -71,42 +83,49 @@ enum eDragState { * The different icons used in the UI */ enum eIcon { - TabCloseIcon, //!< TabCloseIcon - DockAreaMenuIcon, //!< DockAreaMenuIcon - DockAreaUndockIcon, //!< DockAreaUndockIcon - DockAreaCloseIcon, //!< DockAreaCloseIcon - FloatingWidgetCloseIcon, //!< FloatingWidgetCloseIcon + TabCloseIcon, //!< TabCloseIcon + AutoHideIcon, //!< AutoHideIcon + DockAreaMenuIcon, //!< DockAreaMenuIcon + DockAreaUndockIcon, //!< DockAreaUndockIcon + DockAreaCloseIcon, //!< DockAreaCloseIcon + FloatingWidgetCloseIcon, //!< FloatingWidgetCloseIcon + FloatingWidgetNormalIcon, //!< FloatingWidgetNormalIcon + FloatingWidgetMaximizeIcon, //!< FloatingWidgetMaximizeIcon - IconCount, //!< just a delimiter for range checks + IconCount, //!< just a delimiter for range checks }; /** - * For bitwise combination of dock wdget features + * For bitwise combination of dock widget features */ -enum eBitwiseOperator -{ - BitwiseAnd, - BitwiseOr -}; +enum eBitwiseOperator { BitwiseAnd, BitwiseOr }; + +/** + * Each dock container supports 4 sidebars + */ +enum SideBarLocation { SideBarTop, SideBarLeft, SideBarRight, SideBarBottom, SideBarNone }; namespace internal { -const char *const closedProperty = "close"; -const char *const dirtyProperty = "dirty"; + +const char *const g_closedProperty = "close"; +const char *const g_dirtyProperty = "dirty"; + +extern const int g_floatingWidgetDragStartEvent; +extern const int g_dockedWidgetDragStartEvent; /** - * Replace the from widget in the given splitter with the To widget + * Replace the \p from widget in the given splitter with the \p to widget. */ void replaceSplitterWidget(QSplitter *splitter, QWidget *from, QWidget *to); /** - * This function walks the splitter tree upwards to hides all splitters - * that do not have visible content + * This function walks the splitter tree upwards to hide all splitters that do not + * have visible content. */ void hideEmptyParentSplitters(DockSplitter *firstParentSplitter); /** - * Convenience class for QPair to provide better naming than first and - * second + * Convenience class for QPair to provide better naming than first and second. */ class DockInsertParam : public QPair { @@ -121,18 +140,30 @@ public: }; /** - * Returns the insertion parameters for the given dock area + * Returns the insertion parameters for the given dock area. */ DockInsertParam dockAreaInsertParameters(DockWidgetArea area); /** - * Searches for the parent widget of the given type. - * Returns the parent widget of the given widget or 0 if the widget is not - * child of any widget of type T - * - * It is not safe to use this function in in DockWidget because only - * the current dock widget has a parent. All dock widgets that are not the - * current dock widget in a dock area have no parent. + * Returns the SieBarLocation for the AutoHide dock widget areas. + */ +SideBarLocation toSideBarLocation(DockWidgetArea area); + +/** + * Returns true for the top or bottom side bar ansd false for the left and right side bar. + */ +bool isHorizontalSideBarLocation(SideBarLocation location); + +/** + * Returns true, if the given dock area is a SideBar area. + */ +bool isSideBarArea(DockWidgetArea area); + +/** + * Searches for the parent widget of the given type. Returns the parent widget of the given + * widget or 0 if the widget is not child of any widget of type T. + * It is not safe to use this function in in DockWidget because only the current dock widget has a + * parent. All dock widgets that are not the current dock widget in a dock area have no parent. */ template T findParent(const QWidget *widget) @@ -149,14 +180,13 @@ T findParent(const QWidget *widget) } /** - * Creates a semi transparent pixmap from the given pixmap Source. - * The Opacity parameter defines the opacity from completely transparent (0.0) - * to completely opaque (1.0) + * Creates a semi transparent pixmap from the given pixmap source. The opacity parameter defines + * the opacity from completely transparent (0.0) to completely opaque (1.0). */ QPixmap createTransparentPixmap(const QPixmap &source, qreal opacity); /** - * Helper function for settings flags in a QFlags instance. + * Helper function for setting flags in a QFlags instance. */ template void setFlag(T &flags, typename T::enum_type flag, bool on = true) @@ -165,8 +195,7 @@ void setFlag(T &flags, typename T::enum_type flag, bool on = true) } /** - * Helper function for settings tooltips without cluttering the code with - * tests for preprocessor macros + * Helper function for setting tooltips without cluttering the code with tests for preprocessor macros. */ template void setToolTip(QObjectPtr obj, const QString &tip) @@ -180,20 +209,20 @@ void setToolTip(QObjectPtr obj, const QString &tip) } /** - * Helper function to set the icon of a certain button. - * Use this function to set the icons for the dock area and dock widget buttons. - * The function first uses the CustomIconId to get an icon from the - * IconProvider. You can register your custom icons with the icon provider, if - * you do not want to use the default buttons and if you do not want to use - * stylesheets. - * If the IconProvider does not return a valid icon (icon is null), the function - * fetches the given standard pixmap from the QStyle. - * param[in] Button The button whose icons are to be set - * param[in] StandardPixmap The standard pixmap to be used for the button - * param[in] CustomIconId The identifier for the custom icon. + * Helper function to set the icon of a certain button. Use this function to set the icons for + * the dock area and dock widget buttons. + * The function first uses the \p customIconId to get an icon from the IconProvider. You can + * register your custom icons with the icon provider, if you do not want to use the default buttons + * and if you do not want to use stylesheets. + * If the IconProvider does not return a valid icon (icon is null), the function fetches the given + * standard pixmap from the QStyle. + * param[in] button The button whose icons are to be set + * param[in] standardPixmap The standard pixmap to be used for the button + * param[in] customIconId The identifier for the custom icon. */ -void setButtonIcon(QAbstractButton *button, QStyle::StandardPixmap standarPixmap, - ADS::eIcon CustomIconId); +void setButtonIcon(QAbstractButton *button, + QStyle::StandardPixmap standardPixmap, + ADS::eIcon customIconId); enum eRepolishChildOptions { @@ -203,10 +232,14 @@ enum eRepolishChildOptions }; /** - * Calls unpolish() / polish for the style of the given widget to update - * stylesheet if a property changes + * Calls unpolish() / polish for the style of the given widget to update stylesheet if a property changes. */ void repolishStyle(QWidget *widget, eRepolishChildOptions options = RepolishIgnoreChildren); +/** + * Returns the geometry of the given widget in global space. + */ +QRect globalGeometry(QWidget *widget); + } // namespace internal } // namespace ADS diff --git a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs index 8d758b3f7eb..e603a039827 100644 --- a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs +++ b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs @@ -15,6 +15,9 @@ QtcLibrary { files: [ "ads_globals.cpp", "ads_globals.h", "advanceddockingsystemtr.h", + "autohidedockcontainer.cpp", "autohidedockcontainer.h", + "autohidesidebar.cpp", "autohidesidebar.h", + "autohidetab.cpp", "autohidetab.h", "dockareatabbar.cpp", "dockareatabbar.h", "dockareatitlebar.cpp", "dockareatitlebar.h", "dockareawidget.cpp", "dockareawidget.h", @@ -31,6 +34,8 @@ QtcLibrary { "floatingdockcontainer.cpp", "floatingdockcontainer.h", "floatingdragpreview.cpp", "floatingdragpreview.h", "iconprovider.cpp", "iconprovider.h", + "pushbutton.cpp", "pushbutton.h", + "resizehandle.cpp", "resizehandle.h", "workspace.cpp", "workspace.h", "workspacedialog.cpp", "workspacedialog.h", "workspaceinputdialog.cpp", "workspaceinputdialog.h", diff --git a/src/libs/advanceddockingsystem/autohidedockcontainer.cpp b/src/libs/advanceddockingsystem/autohidedockcontainer.cpp new file mode 100644 index 00000000000..cda0b14f002 --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidedockcontainer.cpp @@ -0,0 +1,556 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#include "autohidedockcontainer.h" + +#include "ads_globals_p.h" +#include "autohidesidebar.h" +#include "autohidetab.h" +#include "dockareawidget.h" +#include "dockcomponentsfactory.h" +#include "dockmanager.h" +#include "resizehandle.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace ADS { + +static const int resizeMargin = 30; + +bool static isHorizontalArea(SideBarLocation area) +{ + switch (area) { + case SideBarLocation::SideBarTop: + case SideBarLocation::SideBarBottom: + return true; + case SideBarLocation::SideBarLeft: + case SideBarLocation::SideBarRight: + return false; + default: + return true; + } + + return true; +} + +Qt::Edge static edgeFromSideTabBarArea(SideBarLocation area) +{ + switch (area) { + case SideBarLocation::SideBarTop: + return Qt::BottomEdge; + case SideBarLocation::SideBarBottom: + return Qt::TopEdge; + case SideBarLocation::SideBarLeft: + return Qt::RightEdge; + case SideBarLocation::SideBarRight: + return Qt::LeftEdge; + default: + return Qt::LeftEdge; + } + + return Qt::LeftEdge; +} + +int resizeHandleLayoutPosition(SideBarLocation area) +{ + switch (area) { + case SideBarLocation::SideBarBottom: + case SideBarLocation::SideBarRight: + return 0; + + case SideBarLocation::SideBarTop: + case SideBarLocation::SideBarLeft: + return 1; + + default: + return 0; + } + + return 0; +} + +/** + * Private data of CAutoHideDockContainer - pimpl + */ +struct AutoHideDockContainerPrivate +{ + AutoHideDockContainer *q; + DockAreaWidget *m_dockArea{nullptr}; + DockWidget *m_dockWidget{nullptr}; + SideBarLocation m_sideTabBarArea = SideBarNone; + QBoxLayout *m_layout = nullptr; + ResizeHandle *m_resizeHandle = nullptr; + QSize m_size; // creates invalid size + QPointer m_sideTab; + QSize m_sizeCache; + + /** + * Private data constructor + */ + AutoHideDockContainerPrivate(AutoHideDockContainer *parent); + + /** + * Convenience function to get a dock widget area + */ + DockWidgetArea getDockWidgetArea(SideBarLocation area) + { + switch (area) { + case SideBarLocation::SideBarLeft: + return LeftDockWidgetArea; + case SideBarLocation::SideBarRight: + return RightDockWidgetArea; + case SideBarLocation::SideBarBottom: + return BottomDockWidgetArea; + case SideBarLocation::SideBarTop: + return TopDockWidgetArea; + default: + return LeftDockWidgetArea; + } + + return LeftDockWidgetArea; + } + + /** + * Update the resize limit of the resize handle + */ + void updateResizeHandleSizeLimitMax() + { + auto rect = q->dockContainer()->contentRect(); + const auto maxResizeHandleSize = m_resizeHandle->orientation() == Qt::Horizontal + ? rect.width() + : rect.height(); + m_resizeHandle->setMaxResizeSize(maxResizeHandleSize - resizeMargin); + } + + /** + * Convenience function to check, if this is an horizontal area + */ + bool isHorizontal() const { return isHorizontalArea(m_sideTabBarArea); } + + /** + * Forward this event to the dock container + */ + void forwardEventToDockContainer(QEvent *event) + { + auto dockContainer = q->dockContainer(); + if (dockContainer) + dockContainer->handleAutoHideWidgetEvent(event, q); + } + +}; // struct AutoHideDockContainerPrivate + +AutoHideDockContainerPrivate::AutoHideDockContainerPrivate(AutoHideDockContainer *parent) + : q(parent) +{} + +DockContainerWidget *AutoHideDockContainer::dockContainer() const +{ + return internal::findParent(this); +} + +AutoHideDockContainer::AutoHideDockContainer(DockWidget *dockWidget, + SideBarLocation area, + DockContainerWidget *parent) + : QFrame(parent) + , d(new AutoHideDockContainerPrivate(this)) +{ + hide(); // auto hide dock container is initially always hidden + d->m_sideTabBarArea = area; + d->m_sideTab = componentsFactory()->createDockWidgetSideTab(nullptr); + connect(d->m_sideTab, &AutoHideTab::pressed, this, &AutoHideDockContainer::toggleCollapseState); + d->m_dockArea = new DockAreaWidget(dockWidget->dockManager(), parent); + d->m_dockArea->setObjectName("autoHideDockArea"); + d->m_dockArea->setAutoHideDockContainer(this); + + setObjectName("autoHideDockContainer"); + + d->m_layout = new QBoxLayout(isHorizontalArea(area) ? QBoxLayout::TopToBottom + : QBoxLayout::LeftToRight); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + d->m_resizeHandle = new ResizeHandle(edgeFromSideTabBarArea(area), this); + d->m_resizeHandle->setMinResizeSize(64); + bool opaqueResize = DockManager::testConfigFlag(DockManager::OpaqueSplitterResize); + d->m_resizeHandle->setOpaqueResize(opaqueResize); + d->m_size = d->m_dockArea->size(); + d->m_sizeCache = dockWidget->size(); + + addDockWidget(dockWidget); + parent->registerAutoHideWidget(this); + // The dock area should not be added to the layout before it contains the dock widget. If you + // add it to the layout before it contains the dock widget then you will likely see this + // warning for OpenGL widgets or QAxWidgets: + // setGeometry: Unable to set geometry XxY+Width+Height on QWidgetWindow/'WidgetClassWindow + d->m_layout->addWidget(d->m_dockArea); + d->m_layout->insertWidget(resizeHandleLayoutPosition(area), d->m_resizeHandle); +} + +void AutoHideDockContainer::updateSize() +{ + auto dockContainerParent = dockContainer(); + if (!dockContainerParent) + return; + + auto rect = dockContainerParent->contentRect(); + + switch (sideBarLocation()) { + case SideBarLocation::SideBarTop: + resize(rect.width(), qMin(rect.height() - resizeMargin, d->m_size.height())); + move(rect.topLeft()); + break; + + case SideBarLocation::SideBarLeft: + resize(qMin(d->m_size.width(), rect.width() - resizeMargin), rect.height()); + move(rect.topLeft()); + break; + + case SideBarLocation::SideBarRight: { + resize(qMin(d->m_size.width(), rect.width() - resizeMargin), rect.height()); + QPoint p = rect.topRight(); + p.rx() -= (width() - 1); + move(p); + } break; + + case SideBarLocation::SideBarBottom: { + resize(rect.width(), qMin(rect.height() - resizeMargin, d->m_size.height())); + QPoint p = rect.bottomLeft(); + p.ry() -= (height() - 1); + move(p); + } break; + + default: + break; + } + + if (orientation() == Qt::Horizontal) + d->m_sizeCache.setHeight(this->height()); + else + d->m_sizeCache.setWidth(this->width()); +} + +AutoHideDockContainer::~AutoHideDockContainer() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + + // Remove event filter in case there are any queued messages + qApp->removeEventFilter(this); + if (dockContainer()) + dockContainer()->removeAutoHideWidget(this); + + if (d->m_sideTab) + delete d->m_sideTab; + + delete d; +} + +AutoHideSideBar *AutoHideDockContainer::autoHideSideBar() const +{ + if (d->m_sideTab) { + return d->m_sideTab->sideBar(); + } else { + auto container = dockContainer(); + return container ? container->autoHideSideBar(d->m_sideTabBarArea) : nullptr; + } +} + +AutoHideTab *AutoHideDockContainer::autoHideTab() const +{ + return d->m_sideTab; +} + +DockWidget *AutoHideDockContainer::dockWidget() const +{ + return d->m_dockWidget; +} + +void AutoHideDockContainer::addDockWidget(DockWidget *dockWidget) +{ + if (d->m_dockWidget) { + // Remove the old dock widget at this area + d->m_dockArea->removeDockWidget(d->m_dockWidget); + } + + d->m_dockWidget = dockWidget; + d->m_sideTab->setDockWidget(dockWidget); + DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); + auto isRestoringState = dockWidget->dockManager()->isRestoringState(); + if (oldDockArea && !isRestoringState) { + // The initial size should be a little bit bigger than the original dock area size to + // prevent that the resize handle of this auto hid dock area is near of the splitter of + // the old dock area. + d->m_size = oldDockArea->size() + QSize(16, 16); + oldDockArea->removeDockWidget(dockWidget); + } + d->m_dockArea->addDockWidget(dockWidget); + updateSize(); + // The dock area is not visible and will not update the size when updateSize() is called for + // this auto hide container. Therefore we explicitly resize it here. As soon as it will + // become visible, it will get the right size + d->m_dockArea->resize(size()); +} + +SideBarLocation AutoHideDockContainer::sideBarLocation() const +{ + return d->m_sideTabBarArea; +} + +void AutoHideDockContainer::setSideBarLocation(SideBarLocation sideBarLocation) +{ + if (d->m_sideTabBarArea == sideBarLocation) + return; + + d->m_sideTabBarArea = sideBarLocation; + d->m_layout->removeWidget(d->m_resizeHandle); + d->m_layout->setDirection(isHorizontalArea(sideBarLocation) ? QBoxLayout::TopToBottom + : QBoxLayout::LeftToRight); + d->m_layout->insertWidget(resizeHandleLayoutPosition(sideBarLocation), d->m_resizeHandle); + d->m_resizeHandle->setHandlePosition(edgeFromSideTabBarArea(sideBarLocation)); + internal::repolishStyle(this, internal::RepolishDirectChildren); +} + +DockAreaWidget *AutoHideDockContainer::dockAreaWidget() const +{ + return d->m_dockArea; +} + +void AutoHideDockContainer::moveContentsToParent() +{ + cleanupAndDelete(); + // If we unpin the auto hide dock widget, then we insert it into the same + // location like it had as a auto hide widget. This brings the least surprise + // to the user and he does not have to search where the widget was inserted. + d->m_dockWidget->setDockArea(nullptr); + auto DockContainer = dockContainer(); + DockContainer->addDockWidget(d->getDockWidgetArea(d->m_sideTabBarArea), d->m_dockWidget); +} + +void AutoHideDockContainer::cleanupAndDelete() +{ + const auto dockWidget = d->m_dockWidget; + if (dockWidget) { + auto sideTab = d->m_sideTab; + sideTab->removeFromSideBar(); + sideTab->setParent(nullptr); + sideTab->hide(); + } + + hide(); + deleteLater(); +} + +void AutoHideDockContainer::saveState(QXmlStreamWriter &s) +{ + s.writeStartElement("widget"); + s.writeAttribute("name", d->m_dockWidget->objectName()); + s.writeAttribute("closed", QString::number(d->m_dockWidget->isClosed() ? 1 : 0)); + s.writeAttribute("size", + QString::number(d->isHorizontal() ? d->m_size.height() : d->m_size.width())); + s.writeEndElement(); +} + +void AutoHideDockContainer::toggleView(bool enable) +{ + if (enable) { + if (d->m_sideTab) + d->m_sideTab->show(); + } else { + if (d->m_sideTab) + d->m_sideTab->hide(); + + hide(); + qApp->removeEventFilter(this); + } +} + +void AutoHideDockContainer::collapseView(bool enable) +{ + if (enable) { + hide(); + qApp->removeEventFilter(this); + } else { + updateSize(); + d->updateResizeHandleSizeLimitMax(); + raise(); + show(); + d->m_dockWidget->dockManager()->setDockWidgetFocused(d->m_dockWidget); + qApp->installEventFilter(this); + } + + qCInfo(adsLog) << Q_FUNC_INFO << enable; + d->m_sideTab->updateStyle(); +} + +void AutoHideDockContainer::toggleCollapseState() +{ + collapseView(isVisible()); +} + +void AutoHideDockContainer::setSize(int size) +{ + if (d->isHorizontal()) + d->m_size.setHeight(size); + else + d->m_size.setWidth(size); + + updateSize(); +} + +Qt::Orientation AutoHideDockContainer::orientation() const +{ + return internal::isHorizontalSideBarLocation(d->m_sideTabBarArea) ? Qt::Horizontal + : Qt::Vertical; +} + +void AutoHideDockContainer::resetToInitialDockWidgetSize() +{ + if (orientation() == Qt::Horizontal) + setSize(d->m_sizeCache.height()); + else + setSize(d->m_sizeCache.width()); +} + +void AutoHideDockContainer::moveToNewSideBarLocation(SideBarLocation newSideBarLocation, int index) +{ + if (newSideBarLocation == sideBarLocation() && index == tabIndex()) + return; + + auto oldOrientation = orientation(); + auto sideBar = dockContainer()->autoHideSideBar(newSideBarLocation); + sideBar->addAutoHideWidget(this, index); + // If we move a horizontal auto hide container to a vertical position then we resize it to the + // orginal dock widget size, to avoid an extremely stretched dock widget after insertion. + if (sideBar->orientation() != oldOrientation) + resetToInitialDockWidgetSize(); +} + +int AutoHideDockContainer::tabIndex() const +{ + return d->m_sideTab->tabIndex(); +} + +/** + * Returns true if the object given in ancestor is an ancestor of the object given in descendant + */ +static bool objectIsAncestorOf(const QObject* descendant, const QObject* ancestor) +{ + if (!ancestor) + return false; + + while (descendant) { + if (descendant == ancestor) + return true; + + descendant = descendant->parent(); + } + + return false; +} + +/** + * Returns true if the object given in ancestor is the object given in descendant + * or if it is an ancestor of the object given in descendant + */ +static bool isObjectOrAncestor(const QObject *descendant, const QObject *ancestor) +{ + if (ancestor && (descendant == ancestor)) + return true; + else + return objectIsAncestorOf(descendant, ancestor); +} + +bool AutoHideDockContainer::eventFilter(QObject *watched, QEvent *event) +{ + // A switch case statement would be nicer here, but we cannot use + // internal::FloatingWidgetDragStartEvent in a switch case + if (event->type() == QEvent::Resize) { + if (!d->m_resizeHandle->isResizing()) + updateSize(); + } else if (event->type() == QEvent::MouseButtonPress) { + auto widget = qobject_cast(watched); + // Ignore non widget events + if (!widget) + return QFrame::eventFilter(watched, event); + + // Now check, if the user clicked into the side tab and ignore this event, + // because the side tab click handler will call collapseView(). If we + // do not ignore this here, then we will collapse the container and the side tab + // click handler will uncollapse it + if (widget == d->m_sideTab.data()) + return QFrame::eventFilter(watched, event); + + // Now we check, if the user clicked inside of this auto hide container. + // If the click is inside of this auto hide container, then we can + // ignore the event, because the auto hide overlay should not get collapsed if + // user works in it + if (isObjectOrAncestor(widget, this)) + return QFrame::eventFilter(watched, event); + + // Ignore the mouse click if it is not inside of this container + if (!isObjectOrAncestor(widget, dockContainer())) + return QFrame::eventFilter(watched, event); + + // User clicked into container - collapse the auto hide widget + collapseView(true); + } else if (event->type() == internal::g_floatingWidgetDragStartEvent) { + // If we are dragging our own floating widget, the we do not need to collapse the view + auto widget = dockContainer()->floatingWidget(); + if (widget != watched) + collapseView(true); + } else if (event->type() == internal::g_dockedWidgetDragStartEvent) { + collapseView(true); + } + + return QFrame::eventFilter(watched, event); +} + +void AutoHideDockContainer::resizeEvent(QResizeEvent *event) +{ + QFrame::resizeEvent(event); + + if (d->m_resizeHandle->isResizing()) { + d->m_size = this->size(); + d->updateResizeHandleSizeLimitMax(); + } +} + +void AutoHideDockContainer::leaveEvent(QEvent *event) +{ + // Resizing of the dock container via the resize handle in non opaque mode + // mays cause a leave event that is not really a leave event. Therefore + // we check here, if we are really outside of our rect. + auto pos = mapFromGlobal(QCursor::pos()); + if (!rect().contains(pos)) + d->forwardEventToDockContainer(event); + + QFrame::leaveEvent(event); +} + +bool AutoHideDockContainer::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::Enter: + case QEvent::Hide: + d->forwardEventToDockContainer(event); + break; + + case QEvent::MouseButtonPress: + return true; + break; + + default: + break; + } + + return QFrame::event(event); +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/autohidedockcontainer.h b/src/libs/advanceddockingsystem/autohidedockcontainer.h new file mode 100644 index 00000000000..ac60c86c7ea --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidedockcontainer.h @@ -0,0 +1,168 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include "ads_globals.h" +#include "autohidetab.h" + +#include + +QT_BEGIN_NAMESPACE +class QXmlStreamWriter; +QT_BEGIN_NAMESPACE + +namespace ADS { + +struct AutoHideDockContainerPrivate; +class DockManager; +class DockWidget; +class DockContainerWidget; +class AutoHideSideBar; +class DockAreaWidget; +class DockingStateReader; +struct SideTabBarPrivate; + +/** + * Auto hide container for hosting an auto hide dock widget + */ +class ADS_EXPORT AutoHideDockContainer : public QFrame +{ + Q_OBJECT + Q_PROPERTY(int sideBarLocation READ sideBarLocation CONSTANT) // TODO +private: + AutoHideDockContainerPrivate *d; ///< private data (pimpl) + friend struct AutoHideDockContainerPrivate; + friend AutoHideSideBar; + friend SideTabBarPrivate; + +protected: + virtual bool eventFilter(QObject *watched, QEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; + virtual void leaveEvent(QEvent *event) override; + virtual bool event(QEvent *event) override; + + /** + * Updates the size considering the size limits and the resize margins + */ + void updateSize(); + + /* + * Saves the state and size + */ + void saveState(QXmlStreamWriter &Stream); + +public: + /** + * Create Auto Hide widget with the given dock widget + */ + AutoHideDockContainer(DockWidget *dockWidget, SideBarLocation area, DockContainerWidget *parent); + + /** + * Virtual Destructor + */ + virtual ~AutoHideDockContainer(); + + /** + * Get's the side tab bar + */ + AutoHideSideBar *autoHideSideBar() const; + + /** + * Returns the side tab + */ + AutoHideTab *autoHideTab() const; + + /** + * Get's the dock widget in this dock container + */ + DockWidget *dockWidget() const; + + /** + * Adds a dock widget and removes the previous dock widget + */ + void addDockWidget(DockWidget *dockWidget); + + /** + * Returns the side tab bar area of this Auto Hide dock container + */ + SideBarLocation sideBarLocation() const; + + /** + * Sets a new SideBarLocation. + * If a new side bar location is set, the auto hide dock container needs + * to update its resize handle position + */ + void setSideBarLocation(SideBarLocation sideBarLocation); + + /** + * Returns the dock area widget of this Auto Hide dock container + */ + DockAreaWidget *dockAreaWidget() const; + + /** + * Returns the parent container that hosts this auto hide container + */ + DockContainerWidget *dockContainer() const; + + /** + * Moves the contents to the parent container widget + * Used before removing this Auto Hide dock container + */ + void moveContentsToParent(); + + /** + * Cleanups up the side tab widget and then deletes itself + */ + void cleanupAndDelete(); + + /** + * Toggles the auto Hide dock container widget. This will also hide the side tab widget. + */ + void toggleView(bool enable); + + /** + * Collapses the auto hide dock container widget + * Does not hide the side tab widget + */ + void collapseView(bool enable); + + /** + * Toggles the current collapse state + */ + void toggleCollapseState(); + + /** + * Use this instead of resize. + * Depending on the sidebar location this will set the width or height of this auto hide container. + */ + void setSize(int size); + + /** + * Returns orientation of this container. + * Left and right containers have a Qt::Vertical orientation and top / bottom containers have + * a Qt::Horizontal orientation. The function returns the orientation of the corresponding + * auto hide side bar. + */ + Qt::Orientation orientation() const; + + /** + * Resets the with or hight to the initial dock widget size dependinng on the orientation. + * If the orientation is Qt::Horizontal, then the height is reset to the initial size and if + * orientation is Qt::Vertical, then the width is reset to the initial size. + */ + void resetToInitialDockWidgetSize(); + + /** + * Removes the AutoHide container from the current side bar and adds it to the new side bar + * given in SideBarLocation. + */ + void moveToNewSideBarLocation(SideBarLocation sideBarLocation, int index = -1); + + /** + * Returns the index of this container in the sidebar. + */ + int tabIndex() const; +}; + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/autohidesidebar.cpp b/src/libs/advanceddockingsystem/autohidesidebar.cpp new file mode 100644 index 00000000000..4005f837b5f --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidesidebar.cpp @@ -0,0 +1,366 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#include "autohidesidebar.h" + +#include "ads_globals_p.h" +#include "autohidedockcontainer.h" +#include "autohidetab.h" +#include "dockareawidget.h" +#include "dockcontainerwidget.h" +#include "dockfocuscontroller.h" +#include "dockingstatereader.h" +#include "dockwidgettab.h" + +#include +#include +#include +#include +#include + +namespace ADS { + +class TabsWidget; + +/** + * Private data class of CSideTabBar class (pimpl) + */ +struct AutoHideSideBarPrivate +{ + /** + * Private data constructor + */ + AutoHideSideBarPrivate(AutoHideSideBar *parent); + + AutoHideSideBar *q; + DockContainerWidget *m_containerWidget; + TabsWidget *m_tabsContainerWidget; + QBoxLayout *m_tabsLayout; + Qt::Orientation m_orientation; + SideBarLocation m_sideTabArea = SideBarLocation::SideBarLeft; + + /** + * Convenience function to check if this is a horizontal side bar + */ + bool isHorizontal() const { return Qt::Horizontal == m_orientation; } + + /** + * Called from viewport to forward event handling to this + */ + void handleViewportEvent(QEvent* e); +}; // struct AutoHideSideBarPrivate + +/** + * This widget stores the tab buttons + */ +class TabsWidget : public QWidget +{ +public: + using QWidget::QWidget; + using Super = QWidget; + AutoHideSideBarPrivate *eventHandler; + + /** + * Returns the size hint as minimum size hint + */ + virtual QSize minimumSizeHint() const override { return Super::sizeHint(); } + + /** + * Forward event handling to EventHandler + */ + virtual bool event(QEvent *e) override + { + eventHandler->handleViewportEvent(e); + return Super::event(e); + } +}; + +AutoHideSideBarPrivate::AutoHideSideBarPrivate(AutoHideSideBar *parent) + : q(parent) +{} + +void AutoHideSideBarPrivate::handleViewportEvent(QEvent* e) +{ + switch (e->type()) { + case QEvent::ChildRemoved: + if (m_tabsLayout->isEmpty()) + q->hide(); + break; + + default: + break; + } +} + +AutoHideSideBar::AutoHideSideBar(DockContainerWidget *parent, SideBarLocation area) + : Super(parent) + , d(new AutoHideSideBarPrivate(this)) +{ + d->m_sideTabArea = area; + d->m_containerWidget = parent; + d->m_orientation = (area == SideBarLocation::SideBarBottom + || area == SideBarLocation::SideBarTop) + ? Qt::Horizontal + : Qt::Vertical; + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setFrameStyle(QFrame::NoFrame); + setWidgetResizable(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + d->m_tabsContainerWidget = new TabsWidget(); + d->m_tabsContainerWidget->eventHandler = d; + d->m_tabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + d->m_tabsContainerWidget->setObjectName("sideTabsContainerWidget"); + + d->m_tabsLayout = new QBoxLayout(d->m_orientation == Qt::Vertical ? QBoxLayout::TopToBottom + : QBoxLayout::LeftToRight); + d->m_tabsLayout->setContentsMargins(0, 0, 0, 0); + d->m_tabsLayout->setSpacing(12); + d->m_tabsLayout->addStretch(1); + d->m_tabsContainerWidget->setLayout(d->m_tabsLayout); + setWidget(d->m_tabsContainerWidget); + + setFocusPolicy(Qt::NoFocus); + if (d->isHorizontal()) + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + else + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + hide(); +} + +AutoHideSideBar::~AutoHideSideBar() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + // The SideTabeBar is not the owner of the tabs and to prevent deletion + // we set the parent here to nullptr to remove it from the children + auto tabs = findChildren(QString(), Qt::FindDirectChildrenOnly); + for (auto tab : tabs) + tab->setParent(nullptr); + + delete d; +} + +void AutoHideSideBar::insertTab(int index, AutoHideTab *sideTab) +{ + sideTab->setSideBar(this); + sideTab->installEventFilter(this); + // Default insertion is append + if (index < 0) + d->m_tabsLayout->insertWidget(d->m_tabsLayout->count() - 1, sideTab); + else + d->m_tabsLayout->insertWidget(index, sideTab); + + show(); +} + +AutoHideDockContainer *AutoHideSideBar::insertDockWidget(int index, DockWidget *dockWidget) +{ + auto autoHideContainer = new AutoHideDockContainer(dockWidget, + d->m_sideTabArea, + d->m_containerWidget); + dockWidget->dockManager()->dockFocusController()->clearDockWidgetFocus(dockWidget); + auto tab = autoHideContainer->autoHideTab(); + dockWidget->setSideTabWidget(tab); + insertTab(index, tab); + return autoHideContainer; +} + +void AutoHideSideBar::removeAutoHideWidget(AutoHideDockContainer *autoHideWidget) +{ + autoHideWidget->autoHideTab()->removeFromSideBar(); + auto dockContainer = autoHideWidget->dockContainer(); + if (dockContainer) + dockContainer->removeAutoHideWidget(autoHideWidget); + + autoHideWidget->setParent(nullptr); +} + +void AutoHideSideBar::addAutoHideWidget(AutoHideDockContainer *autoHideWidget, int index) +{ + auto sideBar = autoHideWidget->autoHideTab()->sideBar(); + if (sideBar == this) { + // If we move to the same tab index or if we insert before the next tab index, then we will + // end at the same tab position and can leave. + if (autoHideWidget->tabIndex() == index || (autoHideWidget->tabIndex() + 1) == index) + return; + + // We remove this auto hide widget from the sidebar in the code below and therefore need + // to correct the TabIndex here. + if (autoHideWidget->tabIndex() < index) + --index; + } + + if (sideBar) + sideBar->removeAutoHideWidget(autoHideWidget); + + autoHideWidget->setParent(d->m_containerWidget); + autoHideWidget->setSideBarLocation(d->m_sideTabArea); + d->m_containerWidget->registerAutoHideWidget(autoHideWidget); + insertTab(index, autoHideWidget->autoHideTab()); +} + +void AutoHideSideBar::removeTab(AutoHideTab *sideTab) +{ + sideTab->removeEventFilter(this); + d->m_tabsLayout->removeWidget(sideTab); + if (d->m_tabsLayout->isEmpty()) + hide(); +} + +bool AutoHideSideBar::eventFilter(QObject *watched, QEvent *event) +{ + auto tab = qobject_cast(watched); + if (!tab) + return false; + + switch (event->type()) { + case QEvent::ShowToParent: + show(); + break; + + case QEvent::HideToParent: + if (!hasVisibleTabs()) + hide(); + break; + + default: + break; + } + + return false; +} + +Qt::Orientation AutoHideSideBar::orientation() const +{ + return d->m_orientation; +} + +AutoHideTab *AutoHideSideBar::tab(int index) const +{ + return qobject_cast(d->m_tabsLayout->itemAt(index)->widget()); +} + +int AutoHideSideBar::tabAt(const QPoint &pos) const +{ + if (!isVisible()) + return TabInvalidIndex; + + if (orientation() == Qt::Horizontal) { + if (pos.x() < tab(0)->geometry().x()) + return -1; + } else { + if (pos.y() < tab(0)->geometry().y()) + return -1; + } + + for (int i = 0; i < count(); ++i) { + if (tab(i)->geometry().contains(pos)) + return i; + } + + return count(); +} + +int AutoHideSideBar::tabInsertIndexAt(const QPoint &pos) const +{ + int index = tabAt(pos); + if (index == TabInvalidIndex) + return TabDefaultInsertIndex; + else + return (index < 0) ? 0 : index; +} + +int AutoHideSideBar::indexOfTab(const AutoHideTab &autoHideTab) const +{ + for (auto i = 0; i < count(); i++) { + if (tab(i) == &autoHideTab) + return i; + } + + return -1; +} + +int AutoHideSideBar::count() const +{ + return d->m_tabsLayout->count() - 1; +} + +int AutoHideSideBar::visibleTabCount() const +{ + int c = 0; + auto parent = parentWidget(); + for (auto i = 0; i < count(); i++) { + if (tab(i)->isVisibleTo(parent)) + c++; + } + + return c; +} + +bool AutoHideSideBar::hasVisibleTabs() const +{ + auto parent = parentWidget(); + for (auto i = 0; i < count(); i++) { + if (tab(i)->isVisibleTo(parent)) + return true; + } + + return false; +} + +SideBarLocation AutoHideSideBar::sideBarLocation() const +{ + return d->m_sideTabArea; +} + +void AutoHideSideBar::saveState(QXmlStreamWriter &s) const +{ + if (!count()) + return; + + s.writeStartElement("sideBar"); + s.writeAttribute("area", QString::number(sideBarLocation())); + s.writeAttribute("tabs", QString::number(count())); + + for (auto i = 0; i < count(); ++i) { + auto currentTab = tab(i); + if (!currentTab) + continue; + + currentTab->dockWidget()->autoHideDockContainer()->saveState(s); + } + + s.writeEndElement(); +} + +QSize AutoHideSideBar::minimumSizeHint() const +{ + QSize size = sizeHint(); + size.setWidth(10); + return size; +} + +QSize AutoHideSideBar::sizeHint() const +{ + return d->m_tabsContainerWidget->sizeHint(); +} + +int AutoHideSideBar::spacing() const +{ + return d->m_tabsLayout->spacing(); +} + +void AutoHideSideBar::setSpacing(int spacing) +{ + d->m_tabsLayout->setSpacing(spacing); +} + +DockContainerWidget *AutoHideSideBar::dockContainer() const +{ + return d->m_containerWidget; +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/autohidesidebar.h b/src/libs/advanceddockingsystem/autohidesidebar.h new file mode 100644 index 00000000000..7477ca8c499 --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidesidebar.h @@ -0,0 +1,174 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include "ads_globals.h" + +#include "autohidetab.h" + +#include + +class QXmlStreamWriter; + +namespace ADS { + +struct AutoHideSideBarPrivate; +class DockContainerWidgetPrivate; +class DockContainerWidget; +class AutoHideTab; +class AutoHideDockContainer; +class DockingStateReader; + +/** + * Side tab bar widget that is shown at the edges of a dock container. + * The tab bar is only visible, if it contains visible content, that means if + * it contains visible tabs. If it is empty or all tabs are hidden, then the + * side bar is also hidden. As soon as one single tab becomes visible, this + * tab bar will be shown. + * The CAutoHideSideBar uses a QScrollArea here, to enable proper resizing. + * If the side bar contains many tabs, then the tabs are simply clipped - this + * is the same like in visual studio + */ +class ADS_EXPORT AutoHideSideBar : public QScrollArea +{ + Q_OBJECT + Q_PROPERTY(int sideBarLocation READ sideBarLocation) + Q_PROPERTY(Qt::Orientation orientation READ orientation) + Q_PROPERTY(int spacing READ spacing WRITE setSpacing) + +private: + AutoHideSideBarPrivate* d; ///< private data (pimpl) + friend struct AutoHideSideBarPrivate; + friend class DockWidgetSideTab; + friend DockContainerWidgetPrivate; + friend DockContainerWidget; + +protected: + virtual bool eventFilter(QObject *watched, QEvent *event) override; + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter &stream) const; + + /** + * Inserts the given dock widget tab at the given position. + * An Index value of -1 appends the side tab at the end. + */ + void insertTab(int index, AutoHideTab *sideTab); + +public: + using Super = QScrollArea; + + /** + * Default Constructor + */ + AutoHideSideBar(DockContainerWidget *parent, SideBarLocation area); + + /** + * Virtual Destructor + */ + virtual ~AutoHideSideBar(); + + /** + * Removes the given DockWidgetSideTab from the tabbar + */ + void removeTab(AutoHideTab *sideTab); + + /** + * Insert dock widget into the side bar. The function creates the auto hide dock container, + * inserts the auto hide tab + */ + AutoHideDockContainer *insertDockWidget(int index, DockWidget *dockWidget); + + /** + * Removes the auto hide widget from this side bar + */ + void removeAutoHideWidget(AutoHideDockContainer *autoHideWidget); + + /** + * Adds the given AutoHideWidget to this sidebar. If the AutoHideWidget is in another sidebar, + * then it will be removed from this sidebar. + */ + void addAutoHideWidget(AutoHideDockContainer *autoHideWidget, int index = TabDefaultInsertIndex); + + /** + * Returns orientation of side tab. + */ + Qt::Orientation orientation() const; + + /** + * Get the side tab widget at position, returns nullptr if it's out of bounds. + */ + AutoHideTab *tab(int index) const; + + /** + * Returns the tab at the given position. + * Returns -1 if the position is left of the first tab and count() if the position is right + * of the last tab. Returns InvalidTabIndex (-2) to indicate an invalid value. + */ + int tabAt(const QPoint &pos) const; + + /** + * Returns the tab insertion index for the given mouse cursor position. + */ + int tabInsertIndexAt(const QPoint &pos) const; + + /** + * Returns the index of the given tab. + */ + int indexOfTab(const AutoHideTab &tab) const; + + /** + * Gets the count of the tab widgets. + */ + int count() const; + + /** + * Returns the number of visible tabs to its parent widget. + */ + int visibleTabCount() const; + + /** + * Returns true, if the sidebar contains visible tabs to its parent widget. + * The function returns as soon as it finds the first visible tab. That means, if you just want + * to find out if there are visible tabs then this function is quicker than visibleTabCount(). + */ + bool hasVisibleTabs() const; + + /** + * Getter for side tab bar area property + */ + SideBarLocation sideBarLocation() const; + + /** + * Overrides the minimumSizeHint() function of QScrollArea + * The minimumSizeHint() is bigger than the sizeHint () for the scroll + * area because even if the scrollbars are invisible, the required speace + * is reserved in the minimumSizeHint(). This override simply returns sizeHint(); + */ + virtual QSize minimumSizeHint() const override; + + /** + * The function provides a sizeHint that matches the height of the internal viewport. + */ + virtual QSize sizeHint() const override; + + /** + * Getter for spacing property - returns the spacing of the tabs + */ + int spacing() const; + + /** + * Setter for spacing property - sets the spacing + */ + void setSpacing(int spacing); + + /** + * Returns the dock container that hosts this sideBar() + */ + DockContainerWidget *dockContainer() const; +}; + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/autohidetab.cpp b/src/libs/advanceddockingsystem/autohidetab.cpp new file mode 100644 index 00000000000..c6bfd7bdb8b --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidetab.cpp @@ -0,0 +1,464 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#include "autohidetab.h" + +#include "ads_globals_p.h" +#include "autohidedockcontainer.h" +#include "autohidesidebar.h" +#include "dockareawidget.h" +#include "dockmanager.h" +#include "dockoverlay.h" +#include "dockwidget.h" +#include "floatingdragpreview.h" + +#include +#include +#include +#include +#include + +namespace ADS { + +static const char *const g_locationProperty = "Location"; + +/** + * Private data class of CDockWidgetTab class (pimpl) + */ +struct AutoHideTabPrivate +{ + AutoHideTab *q; + DockWidget *m_dockWidget = nullptr; + AutoHideSideBar *m_sideBar = nullptr; + Qt::Orientation m_orientation{Qt::Vertical}; + QElapsedTimer m_timeSinceHoverMousePress; + bool m_mousePressed = false; + eDragState m_dragState = DraggingInactive; + QPoint m_globalDragStartMousePosition; + QPoint m_dragStartMousePosition; + AbstractFloatingWidget *m_floatingWidget = nullptr; + Qt::Orientation m_dragStartOrientation; + + /** + * Private data constructor + */ + AutoHideTabPrivate(AutoHideTab *parent); + + /** + * Update the orientation, visibility and spacing based on the area of the side bar + */ + void updateOrientation(); + + /** + * Convenience function to ease dock container access + */ + DockContainerWidget *dockContainer() const + { + return m_dockWidget ? m_dockWidget->dockContainer() : nullptr; + } + + /** + * Forward this event to the dock container + */ + void forwardEventToDockContainer(QEvent *event) + { + auto container = dockContainer(); + if (container) + container->handleAutoHideWidgetEvent(event, q); + } + + /** + * Helper function to create and initialize the menu entries for the "Auto Hide Group To..." menu. + */ + QAction *createAutoHideToAction(const QString &title, SideBarLocation location, QMenu *menu) + { + auto action = menu->addAction(title); + action->setProperty("Location", location); + QObject::connect(action, &QAction::triggered, q, &AutoHideTab::onAutoHideToActionClicked); + action->setEnabled(location != q->sideBarLocation()); + return action; + } + + /** + * Test function for current drag state. + */ + bool isDraggingState(eDragState dragState) const { return m_dragState == dragState; } + + /** + * Saves the drag start position in global and local coordinates. + */ + void saveDragStartMousePosition(const QPoint &globalPos) + { + m_globalDragStartMousePosition = globalPos; + m_dragStartMousePosition = q->mapFromGlobal(globalPos); + } + + /** + * Starts floating of the dock widget that belongs to this title bar + * Returns true, if floating has been started and false if floating is not possible for any reason. + */ + bool startFloating(eDragState draggingState = DraggingFloatingWidget); + + template + AbstractFloatingWidget *createFloatingWidget(T *widget) + { + auto w = new FloatingDragPreview(widget); + q->connect(w, &FloatingDragPreview::draggingCanceled, [=]() { + m_dragState = DraggingInactive; + }); + return w; + } +}; // struct DockWidgetTabPrivate + +AutoHideTabPrivate::AutoHideTabPrivate(AutoHideTab *parent) + : q(parent) +{} + +void AutoHideTabPrivate::updateOrientation() +{ + bool iconOnly = DockManager::testAutoHideConfigFlag(DockManager::AutoHideSideBarsIconOnly); + if (iconOnly && !q->icon().isNull()) { + q->setText(""); + q->setOrientation(Qt::Horizontal); + } else { + auto area = m_sideBar->sideBarLocation(); + q->setOrientation((area == SideBarBottom || area == SideBarTop) ? Qt::Horizontal + : Qt::Vertical); + } +} + +bool AutoHideTabPrivate::startFloating(eDragState draggingState) +{ + auto dockArea = m_dockWidget->dockAreaWidget(); + qCInfo(adsLog) << Q_FUNC_INFO << "isFloating " << dockContainer()->isFloating(); + + m_dragState = draggingState; + AbstractFloatingWidget *floatingWidget = nullptr; + floatingWidget = createFloatingWidget(dockArea); + auto size = dockArea->size(); + auto startPos = m_dragStartMousePosition; + auto autoHideContainer = m_dockWidget->autoHideDockContainer(); + m_dragStartOrientation = autoHideContainer->orientation(); + + switch (m_sideBar->sideBarLocation()) { + case SideBarLeft: + startPos.rx() = autoHideContainer->rect().left() + 10; + break; + + case SideBarRight: + startPos.rx() = autoHideContainer->rect().right() - 10; + break; + + case SideBarTop: + startPos.ry() = autoHideContainer->rect().top() + 10; + break; + + case SideBarBottom: + startPos.ry() = autoHideContainer->rect().bottom() - 10; + break; + + case SideBarNone: + return false; + } + floatingWidget->startFloating(startPos, size, DraggingFloatingWidget, q); + auto dockManager = m_dockWidget->dockManager(); + auto overlay = dockManager->containerOverlay(); + overlay->setAllowedAreas(OuterDockAreas); + m_floatingWidget = floatingWidget; + qApp->postEvent(m_dockWidget, new QEvent((QEvent::Type) internal::g_dockedWidgetDragStartEvent)); + + return true; +} + +AutoHideTab::AutoHideTab(QWidget *parent) + : PushButton(parent) + , d(new AutoHideTabPrivate(this)) +{ + setAttribute(Qt::WA_NoMousePropagation); + setFocusPolicy(Qt::NoFocus); +} + +AutoHideTab::~AutoHideTab() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; +} + +void AutoHideTab::updateStyle() +{ + internal::repolishStyle(this, internal::RepolishDirectChildren); + update(); +} + +SideBarLocation AutoHideTab::sideBarLocation() const +{ + if (d->m_sideBar) + return d->m_sideBar->sideBarLocation(); + + return SideBarLeft; +} + +void AutoHideTab::setOrientation(Qt::Orientation value) +{ + d->m_orientation = value; + if (orientation() == Qt::Horizontal) { + setMinimumWidth(100); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + } else { + setMinimumHeight(100); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + } + + PushButton::setButtonOrientation((Qt::Horizontal == value) ? PushButton::Horizontal + : PushButton::VerticalTopToBottom); + updateStyle(); +} + +Qt::Orientation AutoHideTab::orientation() const +{ + return d->m_orientation; +} + +bool AutoHideTab::isActiveTab() const +{ + if (d->m_dockWidget && d->m_dockWidget->autoHideDockContainer()) + return d->m_dockWidget->autoHideDockContainer()->isVisible(); + + return false; +} + +DockWidget *AutoHideTab::dockWidget() const +{ + return d->m_dockWidget; +} + +void AutoHideTab::setDockWidget(DockWidget *dockWidget) +{ + if (!dockWidget) + return; + + d->m_dockWidget = dockWidget; + setText(dockWidget->windowTitle()); + setIcon(d->m_dockWidget->icon()); + setToolTip(dockWidget->windowTitle()); +} +bool AutoHideTab::iconOnly() const +{ + return DockManager::testAutoHideConfigFlag(DockManager::AutoHideSideBarsIconOnly) + && !icon().isNull(); +} + +AutoHideSideBar *AutoHideTab::sideBar() const +{ + return d->m_sideBar; +} + +int AutoHideTab::tabIndex() const +{ + if (!d->m_sideBar) + return -1; + + return d->m_sideBar->indexOfTab(*this); +} + +void AutoHideTab::setDockWidgetFloating() +{ + d->m_dockWidget->setFloating(); +} + +void AutoHideTab::unpinDockWidget() +{ + d->m_dockWidget->setAutoHide(false); +} + +void AutoHideTab::requestCloseDockWidget() +{ + d->m_dockWidget->requestCloseDockWidget(); +} + +void AutoHideTab::onAutoHideToActionClicked() +{ + int location = sender()->property(g_locationProperty).toInt(); + d->m_dockWidget->setAutoHide(true, (SideBarLocation) location); +} + +void AutoHideTab::setSideBar(AutoHideSideBar *sideTabBar) +{ + d->m_sideBar = sideTabBar; + if (d->m_sideBar) + d->updateOrientation(); +} + +void AutoHideTab::removeFromSideBar() +{ + if (d->m_sideBar == nullptr) + return; + + d->m_sideBar->removeTab(this); + setSideBar(nullptr); +} + +bool AutoHideTab::event(QEvent *event) +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideShowOnMouseOver)) + return Super::event(event); + + switch (event->type()) { + case QEvent::Enter: + case QEvent::Leave: + d->forwardEventToDockContainer(event); + break; + + case QEvent::MouseButtonPress: + // If AutoHideShowOnMouseOver is active, then the showing is triggered by a MousePressEvent + // sent to this tab. To prevent accidental hiding of the tab by a mouse click, we wait at + // least 500 ms before we accept the mouse click. + if (!event->spontaneous()) { + d->m_timeSinceHoverMousePress.restart(); + d->forwardEventToDockContainer(event); + } else if (d->m_timeSinceHoverMousePress.hasExpired(500)) { + d->forwardEventToDockContainer(event); + } + break; + + default: + break; + } + return Super::event(event); +} + +void AutoHideTab::contextMenuEvent(QContextMenuEvent *event) +{ + event->accept(); + d->saveDragStartMousePosition(event->globalPos()); + + const bool isFloatable = d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable); + QMenu menu(this); + + QAction *detachAction = menu.addAction(tr("Detach")); + detachAction->connect(detachAction, + &QAction::triggered, + this, + &AutoHideTab::setDockWidgetFloating); + detachAction->setEnabled(isFloatable); + + auto isPinnable = d->m_dockWidget->features().testFlag(DockWidget::DockWidgetPinnable); + detachAction->setEnabled(isPinnable); + + auto pinToMenu = menu.addMenu(tr("Pin To...")); + pinToMenu->setEnabled(isPinnable); + d->createAutoHideToAction(tr("Top"), SideBarTop, pinToMenu); + d->createAutoHideToAction(tr("Left"), SideBarLeft, pinToMenu); + d->createAutoHideToAction(tr("Right"), SideBarRight, pinToMenu); + d->createAutoHideToAction(tr("Bottom"), SideBarBottom, pinToMenu); + + QAction *unpinAction = menu.addAction(tr("Unpin (Dock)")); + unpinAction->connect(unpinAction, &QAction::triggered, this, &AutoHideTab::unpinDockWidget); + menu.addSeparator(); + QAction *closeAction = menu.addAction(tr("Close")); + closeAction->connect(closeAction, + &QAction::triggered, + this, + &AutoHideTab::requestCloseDockWidget); + closeAction->setEnabled(d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable)); + + menu.exec(event->globalPos()); +} + +void AutoHideTab::mousePressEvent(QMouseEvent *event) +{ + // If AutoHideShowOnMouseOver is active, then the showing is triggered by a MousePressEvent + // sent to this tab. To prevent accidental hiding of the tab by a mouse click, we wait at + // least 500 ms before we accept the mouse click. + if (!event->spontaneous()) { + d->m_timeSinceHoverMousePress.restart(); + d->forwardEventToDockContainer(event); + } else if (d->m_timeSinceHoverMousePress.hasExpired(500)) { + d->forwardEventToDockContainer(event); + } + + if (event->button() == Qt::LeftButton) { + event->accept(); + d->m_mousePressed = true; + d->saveDragStartMousePosition(event->globalPosition().toPoint()); + d->m_dragState = DraggingMousePressed; + } + + Super::mousePressEvent(event); +} + +void AutoHideTab::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + d->m_mousePressed = false; + auto currentDragState = d->m_dragState; + d->m_globalDragStartMousePosition = QPoint(); + d->m_dragStartMousePosition = QPoint(); + d->m_dragState = DraggingInactive; + + switch (currentDragState) { + case DraggingTab: + // End of tab moving, emit signal + //if (d->DockArea) { + // event->accept(); + // Q_EMIT moved(internal::globalPositionOf(event)); + //} + break; + + case DraggingFloatingWidget: + event->accept(); + d->m_floatingWidget->finishDragging(); + if (d->m_dockWidget->isAutoHide() && d->m_dragStartOrientation != orientation()) + d->m_dockWidget->autoHideDockContainer()->resetToInitialDockWidgetSize(); + + break; + + default: + break; // do nothing + } + } + + Super::mouseReleaseEvent(event); +} + +void AutoHideTab::mouseMoveEvent(QMouseEvent *event) +{ + if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { + d->m_dragState = DraggingInactive; + Super::mouseMoveEvent(event); + return; + } + + // Move floating window + if (d->isDraggingState(DraggingFloatingWidget)) { + d->m_floatingWidget->moveFloating(); + Super::mouseMoveEvent(event); + return; + } + + // move tab + if (d->isDraggingState(DraggingTab)) { + // Moving the tab is always allowed because it does not mean moving the dock widget around. + //d->moveTab(ev); + } + + auto mappedPos = mapToParent(event->pos()); + bool mouseOutsideBar = (mappedPos.x() < 0) || (mappedPos.x() > parentWidget()->rect().right()); + // Maybe a fixed drag distance is better here ? + int dragDistanceY = qAbs(d->m_globalDragStartMousePosition.y() + - event->globalPosition().toPoint().y()); + if (dragDistanceY >= DockManager::startDragDistance() || mouseOutsideBar) { + // Floating is only allowed for widgets that are floatable + // We can create the drag preview if the widget is movable. + auto Features = d->m_dockWidget->features(); + if (Features.testFlag(DockWidget::DockWidgetFloatable) + || (Features.testFlag(DockWidget::DockWidgetMovable))) { + d->startFloating(); + } + return; + } + + Super::mouseMoveEvent(event); +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/autohidetab.h b/src/libs/advanceddockingsystem/autohidetab.h new file mode 100644 index 00000000000..d8a4a2a1e61 --- /dev/null +++ b/src/libs/advanceddockingsystem/autohidetab.h @@ -0,0 +1,133 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include "pushbutton.h" + +#include "ads_globals.h" + +namespace ADS { + +struct AutoHideTabPrivate; +class DockWidget; +class AutoHideSideBar; +class DockWidgetTab; +class DockContainerWidgetPrivate; + +/** + * A dock widget Side tab that shows a title or an icon. + * The dock widget tab is shown in the side tab bar to switch between pinned dock widgets. + */ +class ADS_EXPORT AutoHideTab : public PushButton +{ + Q_OBJECT + + Q_PROPERTY(int sideBarLocation READ sideBarLocation CONSTANT) + Q_PROPERTY(Qt::Orientation orientation READ orientation CONSTANT) + Q_PROPERTY(bool activeTab READ isActiveTab CONSTANT) + Q_PROPERTY(bool iconOnly READ iconOnly CONSTANT) + +private: + AutoHideTabPrivate *d; ///< private data (pimpl) + friend struct AutoHideTabPrivate; + friend class DockWidget; + friend class AutoHideDockContainer; + friend class AutoHideSideBar; + friend class DockAreaWidget; + friend class DockContainerWidget; + friend DockContainerWidgetPrivate; + + void onAutoHideToActionClicked(); + +protected: + void setSideBar(AutoHideSideBar *sideTabBar); + void removeFromSideBar(); + virtual bool event(QEvent *event) override; + virtual void contextMenuEvent(QContextMenuEvent *event) override; + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + +public: + using Super = PushButton; + + /** + * Default Constructor + * param[in] parent The parent widget of this title bar + */ + AutoHideTab(QWidget *parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~AutoHideTab(); + + /** + * Update stylesheet style if a property changes. + */ + void updateStyle(); + + /** + * Getter for side tab bar area property. + */ + SideBarLocation sideBarLocation() const; + + /** + * Set orientation vertical or horizontal. + */ + void setOrientation(Qt::Orientation value); + + /** + * Returns the current orientation. + */ + Qt::Orientation orientation() const; + + /** + * Returns true, if this is the active tab. The tab is active if the auto hide widget is visible. + */ + bool isActiveTab() const; + + /** + * Returns the dock widget this belongs to. + */ + DockWidget *dockWidget() const; + + /** + * Sets the dock widget that is controlled by this tab. + */ + void setDockWidget(DockWidget *dockWidget); + + /** + * Returns true if the auto hide config flag AutoHideSideBarsIconOnly is set and if + * the tab has an icon - that means the icon is not null. + */ + bool iconOnly() const; + + /** + * Returns the side bar that contains this tab or a nullptr if the tab is not in a side bar. + */ + AutoHideSideBar *sideBar() const; + + /** + * Returns the index of this tab in the sideBar. + */ + int tabIndex() const; + + /** + * Set the dock widget floating, if it is floatable + */ + void setDockWidgetFloating(); + + /** + * Unpin and dock the auto hide widget. + */ + void unpinDockWidget(); + + /** + * Calls the requestCloseDockWidget() function for the assigned dock widget. + */ + void requestCloseDockWidget(); +}; // class AutoHideTab + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareatabbar.cpp b/src/libs/advanceddockingsystem/dockareatabbar.cpp index 62f0b80b499..e1ac0c9b861 100644 --- a/src/libs/advanceddockingsystem/dockareatabbar.cpp +++ b/src/libs/advanceddockingsystem/dockareatabbar.cpp @@ -13,340 +13,378 @@ #include #include #include +#include #include #include -namespace ADS +namespace ADS { +/** + * Private data class of DockAreaTabBar class (pimpl) + */ +class DockAreaTabBarPrivate { +public: + DockAreaTabBar *q; + DockAreaWidget *m_dockArea = nullptr; + QWidget *m_tabsContainerWidget = nullptr; + QBoxLayout *m_tabsLayout = nullptr; + int m_currentIndex = -1; + /** - * Private data class of DockAreaTabBar class (pimpl) + * Private data constructor */ - class DockAreaTabBarPrivate - { - public: - DockAreaTabBar *q; - DockAreaWidget *m_dockArea = nullptr; - QWidget *m_tabsContainerWidget = nullptr; - QBoxLayout *m_tabsLayout = nullptr; - int m_currentIndex = -1; + DockAreaTabBarPrivate(DockAreaTabBar *parent); - /** - * Private data constructor - */ - DockAreaTabBarPrivate(DockAreaTabBar *parent); + /** + * Update tabs after current index changed or when tabs are removed. + * The function reassigns the stylesheet to update the tabs. + */ + void updateTabs(); - /** - * Update tabs after current index changed or when tabs are removed. - * The function reassigns the stylesheet to update the tabs - */ - void updateTabs(); + /** + * Convenience function to access first tab. + */ + DockWidgetTab *firstTab() const { return q->tab(0); } - /** - * Convenience function to access first tab - */ - DockWidgetTab *firstTab() const { return q->tab(0); } + /** + * Convenience function to access last tab. + */ + DockWidgetTab *lastTab() const { return q->tab(q->count() - 1); } +}; // class DockAreaTabBarPrivate - /** - * Convenience function to access last tab - */ - DockWidgetTab *lastTab() const { return q->tab(q->count() - 1); } - }; // class DockAreaTabBarPrivate +DockAreaTabBarPrivate::DockAreaTabBarPrivate(DockAreaTabBar *parent) + : q(parent) +{} - DockAreaTabBarPrivate::DockAreaTabBarPrivate(DockAreaTabBar *parent) - : q(parent) - {} +void DockAreaTabBarPrivate::updateTabs() +{ + // Set active TAB and update all other tabs to be inactive + for (int i = 0; i < q->count(); ++i) { + auto tabWidget = q->tab(i); + if (!tabWidget) + continue; - void DockAreaTabBarPrivate::updateTabs() - { - // Set active TAB and update all other tabs to be inactive - for (int i = 0; i < q->count(); ++i) { - auto tabWidget = q->tab(i); - if (!tabWidget) - continue; + if (i == m_currentIndex) { + tabWidget->show(); + tabWidget->setActiveTab(true); + // Sometimes the synchronous calculation of the rectangular area fails. Therefore we + // use QTimer::singleShot here to execute the call within the event loop - see #520. + QTimer::singleShot(0, q, [&, tabWidget] { q->ensureWidgetVisible(tabWidget); }); + } else { + tabWidget->setActiveTab(false); + } + } +} - if (i == m_currentIndex) { - tabWidget->show(); - tabWidget->setActiveTab(true); - q->ensureWidgetVisible(tabWidget); - } else { - tabWidget->setActiveTab(false); +DockAreaTabBar::DockAreaTabBar(DockAreaWidget *parent) + : QScrollArea(parent) + , d(new DockAreaTabBarPrivate(this)) +{ + d->m_dockArea = parent; + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setFrameStyle(QFrame::NoFrame); + setWidgetResizable(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + d->m_tabsContainerWidget = new QWidget(); + d->m_tabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + d->m_tabsContainerWidget->setObjectName("tabsContainerWidget"); + d->m_tabsLayout = new QBoxLayout(QBoxLayout::LeftToRight); + d->m_tabsLayout->setContentsMargins(0, 0, 0, 0); + d->m_tabsLayout->setSpacing(0); + d->m_tabsLayout->addStretch(1); + d->m_tabsContainerWidget->setLayout(d->m_tabsLayout); + setWidget(d->m_tabsContainerWidget); +} + +DockAreaTabBar::~DockAreaTabBar() +{ + delete d; +} + +void DockAreaTabBar::onTabClicked(DockWidgetTab *sourceTab) +{ + const int index = d->m_tabsLayout->indexOf(sourceTab); + if (index < 0) + return; + + setCurrentIndex(index); + emit tabBarClicked(index); +} + +void DockAreaTabBar::onTabCloseRequested(DockWidgetTab *sourceTab) +{ + const int index = d->m_tabsLayout->indexOf(sourceTab); + closeTab(index); +} + +void DockAreaTabBar::onCloseOtherTabsRequested(DockWidgetTab *sourceTab) +{ + for (int i = 0; i < count(); ++i) { + auto currentTab = tab(i); + if (currentTab->isClosable() && !currentTab->isHidden() && currentTab != sourceTab) { + // If the dock widget is deleted with the closeTab() call, its tab it will no longer + // be in the layout, and thus the index needs to be updated to not skip any tabs. + int offset = currentTab->dockWidget()->features().testFlag( + DockWidget::DockWidgetDeleteOnClose) + ? 1 + : 0; + closeTab(i); + // If the the dock widget blocks closing, i.e. if the flag CustomCloseHandling is set, + // and the dock widget is still open, then we do not need to correct the index. + if (currentTab->dockWidget()->isClosed()) + i -= offset; + } + } +} + +void DockAreaTabBar::onTabWidgetMoved(DockWidgetTab *sourceTab, const QPoint &globalPosition) +{ + const int fromIndex = d->m_tabsLayout->indexOf(sourceTab); + auto mousePos = mapFromGlobal(globalPosition); + mousePos.rx() = qMax(d->firstTab()->geometry().left(), mousePos.x()); + mousePos.rx() = qMin(d->lastTab()->geometry().right(), mousePos.x()); + int toIndex = -1; + // Find tab under mouse + for (int i = 0; i < count(); ++i) { + DockWidgetTab *dropTab = tab(i); + if (dropTab == sourceTab || !dropTab->isVisibleTo(this) + || !dropTab->geometry().contains(mousePos)) + continue; + + toIndex = d->m_tabsLayout->indexOf(dropTab); + if (toIndex == fromIndex) + toIndex = -1; + + break; + } + + if (toIndex > -1) { + d->m_tabsLayout->removeWidget(sourceTab); + d->m_tabsLayout->insertWidget(toIndex, sourceTab); + qCInfo(adsLog) << "tabMoved from" << fromIndex << "to" << toIndex; + emit tabMoved(fromIndex, toIndex); + setCurrentIndex(toIndex); + } else { + // Ensure that the moved tab is reset to its start position + d->m_tabsLayout->update(); + } +} + +void DockAreaTabBar::wheelEvent(QWheelEvent *event) +{ + event->accept(); + const int direction = event->angleDelta().y(); + if (direction < 0) + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20); + else + horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20); +} + +int DockAreaTabBar::count() const +{ + // The tab bar contains a stretch item as last item + return d->m_tabsLayout->count() - 1; +} + +void DockAreaTabBar::insertTab(int index, DockWidgetTab *dockWidgetTab) +{ + d->m_tabsLayout->insertWidget(index, dockWidgetTab); + connect(dockWidgetTab, &DockWidgetTab::clicked, this, [this, dockWidgetTab] { + onTabClicked(dockWidgetTab); + }); + connect(dockWidgetTab, &DockWidgetTab::closeRequested, this, [this, dockWidgetTab] { + onTabCloseRequested(dockWidgetTab); + }); + connect(dockWidgetTab, &DockWidgetTab::closeOtherTabsRequested, this, [this, dockWidgetTab] { + onCloseOtherTabsRequested(dockWidgetTab); + }); + connect(dockWidgetTab, + &DockWidgetTab::moved, + this, + [this, dockWidgetTab](const QPoint &globalPosition) { + onTabWidgetMoved(dockWidgetTab, globalPosition); + }); + connect(dockWidgetTab, &DockWidgetTab::elidedChanged, this, &DockAreaTabBar::elidedChanged); + dockWidgetTab->installEventFilter(this); + emit tabInserted(index); + if (index <= d->m_currentIndex) + setCurrentIndex(d->m_currentIndex + 1); + else if (d->m_currentIndex == -1) + setCurrentIndex(index); + + updateGeometry(); +} + +void DockAreaTabBar::removeTab(DockWidgetTab *dockWidgetTab) +{ + if (!count()) + return; + + qCInfo(adsLog) << Q_FUNC_INFO; + int newCurrentIndex = currentIndex(); + int removeIndex = d->m_tabsLayout->indexOf(dockWidgetTab); + if (count() == 1) + newCurrentIndex = -1; + + if (newCurrentIndex > removeIndex) { + newCurrentIndex--; + } else if (newCurrentIndex == removeIndex) { + newCurrentIndex = -1; + // First we walk to the right to search for the next visible tab + for (int i = (removeIndex + 1); i < count(); ++i) { + if (tab(i)->isVisibleTo(this)) { + newCurrentIndex = i - 1; + break; } } - } - DockAreaTabBar::DockAreaTabBar(DockAreaWidget *parent) - : QScrollArea(parent) - , d(new DockAreaTabBarPrivate(this)) - { - d->m_dockArea = parent; - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - setFrameStyle(QFrame::NoFrame); - setWidgetResizable(true); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - d->m_tabsContainerWidget = new QWidget(); - d->m_tabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - d->m_tabsContainerWidget->setObjectName("tabsContainerWidget"); - d->m_tabsLayout = new QBoxLayout(QBoxLayout::LeftToRight); - d->m_tabsLayout->setContentsMargins(0, 0, 0, 0); - d->m_tabsLayout->setSpacing(0); - d->m_tabsLayout->addStretch(1); - d->m_tabsContainerWidget->setLayout(d->m_tabsLayout); - setWidget(d->m_tabsContainerWidget); - } - - DockAreaTabBar::~DockAreaTabBar() { delete d; } - - void DockAreaTabBar::wheelEvent(QWheelEvent *event) - { - event->accept(); - const int direction = event->angleDelta().y(); - if (direction < 0) - horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20); - else - horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20); - } - - void DockAreaTabBar::setCurrentIndex(int index) - { - if (index == d->m_currentIndex) - return; - - if (index < -1 || index > (count() - 1)) { - qWarning() << Q_FUNC_INFO << "Invalid index" << index; - return; - } - - emit currentChanging(index); - d->m_currentIndex = index; - d->updateTabs(); - updateGeometry(); - emit currentChanged(index); - } - - int DockAreaTabBar::count() const - { - // The tab bar contains a stretch item as last item - return d->m_tabsLayout->count() - 1; - } - - void DockAreaTabBar::insertTab(int index, DockWidgetTab *dockWidgetTab) - { - d->m_tabsLayout->insertWidget(index, dockWidgetTab); - connect(dockWidgetTab, &DockWidgetTab::clicked, - this, [this, dockWidgetTab] { onTabClicked(dockWidgetTab); }); - connect(dockWidgetTab, &DockWidgetTab::closeRequested, - this, [this, dockWidgetTab] { onTabCloseRequested(dockWidgetTab); }); - connect(dockWidgetTab, &DockWidgetTab::closeOtherTabsRequested, - this, [this, dockWidgetTab] { onCloseOtherTabsRequested(dockWidgetTab); }); - connect(dockWidgetTab, &DockWidgetTab::moved, - this, [this, dockWidgetTab](const QPoint &globalPosition) { - onTabWidgetMoved(dockWidgetTab, globalPosition); - }); - connect(dockWidgetTab, &DockWidgetTab::elidedChanged, - this, &DockAreaTabBar::elidedChanged); - dockWidgetTab->installEventFilter(this); - emit tabInserted(index); - if (index <= d->m_currentIndex) - setCurrentIndex(d->m_currentIndex + 1); - else if (d->m_currentIndex == -1) - setCurrentIndex(index); - - updateGeometry(); - } - - void DockAreaTabBar::removeTab(DockWidgetTab *dockWidgetTab) - { - if (!count()) - return; - - qCInfo(adsLog) << Q_FUNC_INFO; - int newCurrentIndex = currentIndex(); - int removeIndex = d->m_tabsLayout->indexOf(dockWidgetTab); - if (count() == 1) - newCurrentIndex = -1; - - if (newCurrentIndex > removeIndex) { - newCurrentIndex--; - } else if (newCurrentIndex == removeIndex) { - newCurrentIndex = -1; - // First we walk to the right to search for the next visible tab - for (int i = (removeIndex + 1); i < count(); ++i) { + // If there is no visible tab right to this tab then we walk to the left to find a visible tab. + if (newCurrentIndex < 0) { + for (int i = (removeIndex - 1); i >= 0; --i) { if (tab(i)->isVisibleTo(this)) { - newCurrentIndex = i - 1; + newCurrentIndex = i; break; } } - - // If there is no visible tab right to this tab then we walk to - // the left to find a visible tab - if (newCurrentIndex < 0) { - for (int i = (removeIndex - 1); i >= 0; --i) { - if (tab(i)->isVisibleTo(this)) { - newCurrentIndex = i; - break; - } - } - } - } - - emit removingTab(removeIndex); - d->m_tabsLayout->removeWidget(dockWidgetTab); - dockWidgetTab->disconnect(this); - dockWidgetTab->removeEventFilter(this); - qCInfo(adsLog) << "NewCurrentIndex " << newCurrentIndex; - if (newCurrentIndex != d->m_currentIndex) - setCurrentIndex(newCurrentIndex); - else - d->updateTabs(); - - updateGeometry(); - } - - int DockAreaTabBar::currentIndex() const { return d->m_currentIndex; } - - DockWidgetTab *DockAreaTabBar::currentTab() const - { - if (d->m_currentIndex < 0) - return nullptr; - else - return qobject_cast( - d->m_tabsLayout->itemAt(d->m_currentIndex)->widget()); - } - - void DockAreaTabBar::onTabClicked(DockWidgetTab *sourceTab) - { - const int index = d->m_tabsLayout->indexOf(sourceTab); - if (index < 0) - return; - - setCurrentIndex(index); - emit tabBarClicked(index); - } - - void DockAreaTabBar::onTabCloseRequested(DockWidgetTab *sourceTab) - { - const int index = d->m_tabsLayout->indexOf(sourceTab); - closeTab(index); - } - - void DockAreaTabBar::onCloseOtherTabsRequested(DockWidgetTab *sourceTab) - { - for (int i = 0; i < count(); ++i) { - auto currentTab = tab(i); - if (currentTab->isClosable() && !currentTab->isHidden() && currentTab != sourceTab) { - // If the dock widget is deleted with the closeTab() call, its tab it will no longer - // be in the layout, and thus the index needs to be updated to not skip any tabs - int offset = currentTab->dockWidget()->features().testFlag( - DockWidget::DockWidgetDeleteOnClose) - ? 1 - : 0; - closeTab(i); - // If the the dock widget blocks closing, i.e. if the flag - // CustomCloseHandling is set, and the dock widget is still open, - // then we do not need to correct the index - if (currentTab->dockWidget()->isClosed()) - i -= offset; - } } } - DockWidgetTab *DockAreaTabBar::tab(int index) const - { - if (index >= count() || index < 0) - return nullptr; + emit removingTab(removeIndex); + d->m_tabsLayout->removeWidget(dockWidgetTab); + dockWidgetTab->disconnect(this); + dockWidgetTab->removeEventFilter(this); + qCInfo(adsLog) << "NewCurrentIndex" << newCurrentIndex; + if (newCurrentIndex != d->m_currentIndex) + setCurrentIndex(newCurrentIndex); + else + d->updateTabs(); - return qobject_cast(d->m_tabsLayout->itemAt(index)->widget()); + updateGeometry(); +} + +int DockAreaTabBar::currentIndex() const +{ + return d->m_currentIndex; +} + +DockWidgetTab *DockAreaTabBar::currentTab() const +{ + if (d->m_currentIndex < 0) + return nullptr; + else + return qobject_cast(d->m_tabsLayout->itemAt(d->m_currentIndex)->widget()); +} + +DockWidgetTab *DockAreaTabBar::tab(int index) const +{ + if (index >= count() || index < 0) + return nullptr; + + return qobject_cast(d->m_tabsLayout->itemAt(index)->widget()); +} + +int DockAreaTabBar::tabAt(const QPoint &pos) const +{ + if (!isVisible()) + return TabInvalidIndex; + + if (pos.x() < tab(0)->geometry().x()) + return -1; + + for (int i = 0; i < count(); ++i) { + if (tab(i)->geometry().contains(pos)) + return i; } - void DockAreaTabBar::onTabWidgetMoved(DockWidgetTab *sourceTab, const QPoint &globalPosition) - { - const int fromIndex = d->m_tabsLayout->indexOf(sourceTab); - auto mousePos = mapFromGlobal(globalPosition); - mousePos.rx() = qMax(d->firstTab()->geometry().left(), mousePos.x()); - mousePos.rx() = qMin(d->lastTab()->geometry().right(), mousePos.x()); - int toIndex = -1; - // Find tab under mouse - for (int i = 0; i < count(); ++i) { - DockWidgetTab *dropTab = tab(i); - if (dropTab == sourceTab || !dropTab->isVisibleTo(this) - || !dropTab->geometry().contains(mousePos)) - continue; + return count(); +} - toIndex = d->m_tabsLayout->indexOf(dropTab); - if (toIndex == fromIndex) - toIndex = -1; - - break; - } - - if (toIndex > -1) { - d->m_tabsLayout->removeWidget(sourceTab); - d->m_tabsLayout->insertWidget(toIndex, sourceTab); - qCInfo(adsLog) << "tabMoved from" << fromIndex << "to" << toIndex; - emit tabMoved(fromIndex, toIndex); - setCurrentIndex(toIndex); - } else { - // Ensure that the moved tab is reset to its start position - d->m_tabsLayout->update(); - } - } - - void DockAreaTabBar::closeTab(int index) - { - if (index < 0 || index >= count()) - return; - - auto dockWidgetTab = tab(index); - if (dockWidgetTab->isHidden()) - return; - - emit tabCloseRequested(index); - } - - bool DockAreaTabBar::eventFilter(QObject *watched, QEvent *event) - { - bool result = Super::eventFilter(watched, event); - DockWidgetTab *dockWidgetTab = qobject_cast(watched); - if (!dockWidgetTab) - return result; - - switch (event->type()) { - case QEvent::Hide: - emit tabClosed(d->m_tabsLayout->indexOf(dockWidgetTab)); - updateGeometry(); - break; - case QEvent::Show: - emit tabOpened(d->m_tabsLayout->indexOf(dockWidgetTab)); - updateGeometry(); - break; - default: - break; - } +int DockAreaTabBar::tabInsertIndexAt(const QPoint &pos) const +{ + int index = tabAt(pos); + if (index == TabInvalidIndex) + return TabDefaultInsertIndex; + else + return (index < 0) ? 0 : index; +} +bool DockAreaTabBar::eventFilter(QObject *watched, QEvent *event) +{ + bool result = Super::eventFilter(watched, event); + DockWidgetTab *dockWidgetTab = qobject_cast(watched); + if (!dockWidgetTab) return result; + + switch (event->type()) { + case QEvent::Hide: + emit tabClosed(d->m_tabsLayout->indexOf(dockWidgetTab)); + updateGeometry(); + break; + case QEvent::Show: + emit tabOpened(d->m_tabsLayout->indexOf(dockWidgetTab)); + updateGeometry(); + break; + // Setting the text of a tab will cause a LayoutRequest event + case QEvent::LayoutRequest: + updateGeometry(); + break; + default: + break; } - bool DockAreaTabBar::isTabOpen(int index) const - { - if (index < 0 || index >= count()) - return false; + return result; +} - return !tab(index)->isHidden(); +bool DockAreaTabBar::isTabOpen(int index) const +{ + if (index < 0 || index >= count()) + return false; + + return !tab(index)->isHidden(); +} + +QSize DockAreaTabBar::minimumSizeHint() const +{ + QSize size = sizeHint(); + size.setWidth(10); + return size; +} + +QSize DockAreaTabBar::sizeHint() const +{ + return d->m_tabsContainerWidget->sizeHint(); +} + +void DockAreaTabBar::setCurrentIndex(int index) +{ + if (index == d->m_currentIndex) + return; + + if (index < -1 || index > (count() - 1)) { + qWarning() << Q_FUNC_INFO << "Invalid index" << index; + return; } - QSize DockAreaTabBar::minimumSizeHint() const - { - QSize size = sizeHint(); - size.setWidth(10); - return size; - } + emit currentChanging(index); + d->m_currentIndex = index; + d->updateTabs(); + updateGeometry(); + emit currentChanged(index); +} - QSize DockAreaTabBar::sizeHint() const - { - return d->m_tabsContainerWidget->sizeHint(); - } +void DockAreaTabBar::closeTab(int index) +{ + if (index < 0 || index >= count()) + return; + + auto dockWidgetTab = tab(index); + if (dockWidgetTab->isHidden()) + return; + + emit tabCloseRequested(index); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareatabbar.h b/src/libs/advanceddockingsystem/dockareatabbar.h index 956300e07f0..c21b577947f 100644 --- a/src/libs/advanceddockingsystem/dockareatabbar.h +++ b/src/libs/advanceddockingsystem/dockareatabbar.h @@ -86,6 +86,18 @@ public: */ DockWidgetTab *tab(int index) const; + /** + * Returns the tab at the given position. + * Returns -1 if the position is left of the first tab and count() if the position is right + * of the last tab. Returns -2 to indicate an invalid value. + */ + int tabAt(const QPoint &pos) const; + + /** + * Returns the tab insertion index for the given mouse cursor position. + */ + int tabInsertIndexAt(const QPoint &pos) const; + /** * Filters the tab widget events */ @@ -101,7 +113,7 @@ public: /** * Overrides the minimumSizeHint() function of QScrollArea * The minimumSizeHint() is bigger than the sizeHint () for the scroll - * area because even if the scrollbars are invisible, the required speace + * area because even if the scrollbars are invisible, the required space * is reserved in the minimumSizeHint(). This override simply returns * sizeHint(); */ diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.cpp b/src/libs/advanceddockingsystem/dockareatitlebar.cpp index 02c83489fa7..234916d530d 100644 --- a/src/libs/advanceddockingsystem/dockareatitlebar.cpp +++ b/src/libs/advanceddockingsystem/dockareatitlebar.cpp @@ -15,10 +15,14 @@ #include "dockwidgettab.h" #include "floatingdockcontainer.h" #include "floatingdragpreview.h" -#include "iconprovider.h" + +#include "autohidedockcontainer.h" +#include "dockfocuscontroller.h" +#include "elidinglabel.h" #include +#include #include #include #include @@ -29,532 +33,727 @@ #include -namespace ADS +namespace ADS { + +static const char *const g_locationProperty = "Location"; + +/** + * Private data class of DockAreaTitleBar class (pimpl) + */ +class DockAreaTitleBarPrivate { +public: + DockAreaTitleBar *q; + QPointer m_tabsMenuButton; + QPointer m_autoHideButton; + QPointer m_undockButton; + QPointer m_closeButton; + QBoxLayout *m_layout = nullptr; + DockAreaWidget *m_dockArea = nullptr; + DockAreaTabBar *m_tabBar = nullptr; + ElidingLabel *m_autoHideTitleLabel; + bool m_menuOutdated = true; + QMenu *m_tabsMenu; + QList m_dockWidgetActionsButtons; + + QPoint m_dragStartMousePos; + eDragState m_dragState = DraggingInactive; + AbstractFloatingWidget *m_floatingWidget = nullptr; + /** - * Private data class of DockAreaTitleBar class (pimpl) + * Private data constructor */ - class DockAreaTitleBarPrivate + DockAreaTitleBarPrivate(DockAreaTitleBar *parent); + + /** + * Creates the title bar close and menu buttons + */ + void createButtons(); + + /** + * Creates the auto hide title label, only displayed when the dock area is overlayed + */ + void createAutoHideTitleLabel(); + + /** + * Creates the internal TabBar + */ + void createTabBar(); + + /** + * Convenience function for DockManager access + */ + DockManager *dockManager() const { return m_dockArea->dockManager(); } + + /** + * Returns true if the given config flag is set + * Convenience function to ease config flag testing + */ + static bool testConfigFlag(DockManager::eConfigFlag flag) { - public: - DockAreaTitleBar *q; - QPointer m_tabsMenuButton; - QPointer m_undockButton; - QPointer m_closeButton; - QBoxLayout *m_layout = nullptr; - DockAreaWidget *m_dockArea = nullptr; - DockAreaTabBar *m_tabBar = nullptr; - bool m_menuOutdated = true; - QList m_dockWidgetActionsButtons; + return DockManager::testConfigFlag(flag); + } - QPoint m_dragStartMousePos; - eDragState m_dragState = DraggingInactive; - AbstractFloatingWidget *m_floatingWidget = nullptr; - - /** - * Private data constructor - */ - DockAreaTitleBarPrivate(DockAreaTitleBar *parent); - - /** - * Creates the title bar close and menu buttons - */ - void createButtons(); - - /** - * Creates the internal TabBar - */ - void createTabBar(); - - /** - * Convenience function for DockManager access - */ - DockManager *dockManager() const { return m_dockArea->dockManager(); } - - /** - * Returns true if the given config flag is set - * Convenience function to ease config flag testing - */ - static bool testConfigFlag(DockManager::eConfigFlag flag) - { - return DockManager::testConfigFlag(flag); - } - - /** - * Test function for current drag state - */ - bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; } - - - /** - * Starts floating - */ - void startFloating(const QPoint &offset); - - /** - * Makes the dock area floating - */ - AbstractFloatingWidget *makeAreaFloating(const QPoint &offset, eDragState dragState); - }; // class DockAreaTitleBarPrivate - - DockAreaTitleBarPrivate::DockAreaTitleBarPrivate(DockAreaTitleBar *parent) - : q(parent) - {} - - void DockAreaTitleBarPrivate::createButtons() + /** + * Returns true if the given config flag is set + * Convenience function to ease config flag testing + */ + static bool testAutoHideConfigFlag(DockManager::eAutoHideFlag flag) { - const QSize iconSize(11, 11); - const QSize buttonSize(17, 17); - QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + return DockManager::testAutoHideConfigFlag(flag); + } - // Tabs menu button - m_tabsMenuButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasTabsMenuButton)); - m_tabsMenuButton->setObjectName("tabsMenuButton"); - //m_tabsMenuButton->setAutoRaise(true); - m_tabsMenuButton->setPopupMode(QToolButton::InstantPopup); - internal::setButtonIcon(m_tabsMenuButton, - QStyle::SP_TitleBarUnshadeButton, - ADS::DockAreaMenuIcon); - QMenu *tabsMenu = new QMenu(m_tabsMenuButton); + /** + * Test function for current drag state + */ + bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; } + + /** + * Starts floating + */ + void startFloating(const QPoint &offset); + + /** + * Makes the dock area floating + */ + AbstractFloatingWidget *makeAreaFloating(const QPoint &offset, eDragState dragState); + + /** + * Helper function to create and initialize the menu entries for + * the "Auto Hide Group To..." menu + */ + QAction *createAutoHideToAction(const QString &title, SideBarLocation location, QMenu *menu) + { + auto action = menu->addAction(title); + action->setProperty("Location", location); + QObject::connect(action, + &QAction::triggered, + q, + &DockAreaTitleBar::onAutoHideToActionClicked); + return action; + } +}; // class DockAreaTitleBarPrivate + +DockAreaTitleBarPrivate::DockAreaTitleBarPrivate(DockAreaTitleBar *parent) + : q(parent) +{} + +void DockAreaTitleBarPrivate::createButtons() +{ + const QSize iconSize(11, 11); + const QSize buttonSize(17, 17); + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + // Tabs menu button + m_tabsMenuButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasTabsMenuButton)); + m_tabsMenuButton->setObjectName("tabsMenuButton"); + //m_tabsMenuButton->setAutoRaise(true); + m_tabsMenuButton->setPopupMode(QToolButton::InstantPopup); + internal::setButtonIcon(m_tabsMenuButton, + QStyle::SP_TitleBarUnshadeButton, + ADS::DockAreaMenuIcon); + QMenu *tabsMenu = new QMenu(m_tabsMenuButton); #ifndef QT_NO_TOOLTIP - tabsMenu->setToolTipsVisible(true); + tabsMenu->setToolTipsVisible(true); #endif - QObject::connect(tabsMenu, &QMenu::aboutToShow, q, &DockAreaTitleBar::onTabsMenuAboutToShow); - m_tabsMenuButton->setMenu(tabsMenu); - internal::setToolTip(m_tabsMenuButton, Tr::tr("List All Tabs")); - m_tabsMenuButton->setSizePolicy(sizePolicy); - m_tabsMenuButton->setIconSize(iconSize); - m_tabsMenuButton->setFixedSize(buttonSize); - m_layout->addWidget(m_tabsMenuButton, 0); - QObject::connect(m_tabsMenuButton->menu(), - &QMenu::triggered, - q, - &DockAreaTitleBar::onTabsMenuActionTriggered); + QObject::connect(tabsMenu, &QMenu::aboutToShow, q, &DockAreaTitleBar::onTabsMenuAboutToShow); + m_tabsMenuButton->setMenu(tabsMenu); + internal::setToolTip(m_tabsMenuButton, Tr::tr("List All Tabs")); + m_tabsMenuButton->setSizePolicy(sizePolicy); + m_tabsMenuButton->setIconSize(iconSize); + m_tabsMenuButton->setFixedSize(buttonSize); + m_layout->addWidget(m_tabsMenuButton, 0); + QObject::connect(m_tabsMenuButton->menu(), + &QMenu::triggered, + q, + &DockAreaTitleBar::onTabsMenuActionTriggered); - // Undock button - m_undockButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasUndockButton)); - m_undockButton->setObjectName("detachGroupButton"); - //m_undockButton->setAutoRaise(true); - internal::setToolTip(m_undockButton, Tr::tr("Detach Group")); - internal::setButtonIcon(m_undockButton, - QStyle::SP_TitleBarNormalButton, - ADS::DockAreaUndockIcon); - m_undockButton->setSizePolicy(sizePolicy); - m_undockButton->setIconSize(iconSize); - m_undockButton->setFixedSize(buttonSize); - m_layout->addWidget(m_undockButton, 0); - QObject::connect(m_undockButton, - &QToolButton::clicked, - q, - &DockAreaTitleBar::onUndockButtonClicked); + // Undock button + m_undockButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasUndockButton)); + m_undockButton->setObjectName("detachGroupButton"); + //m_undockButton->setAutoRaise(true); + internal::setToolTip(m_undockButton, Tr::tr("Detach Group")); + internal::setButtonIcon(m_undockButton, + QStyle::SP_TitleBarNormalButton, + ADS::DockAreaUndockIcon); + m_undockButton->setSizePolicy(sizePolicy); + m_undockButton->setIconSize(iconSize); + m_undockButton->setFixedSize(buttonSize); + m_layout->addWidget(m_undockButton, 0); + QObject::connect(m_undockButton, + &QToolButton::clicked, + q, + &DockAreaTitleBar::onUndockButtonClicked); - // Close button - m_closeButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasCloseButton)); - m_closeButton->setObjectName("dockAreaCloseButton"); - //m_closeButton->setAutoRaise(true); - internal::setButtonIcon(m_closeButton, - QStyle::SP_TitleBarCloseButton, - ADS::DockAreaCloseIcon); - if (testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) - internal::setToolTip(m_closeButton, Tr::tr("Close Active Tab")); - else - internal::setToolTip(m_closeButton, Tr::tr("Close Group")); + // AutoHide Button + const auto autoHideEnabled = testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled); + m_autoHideButton = new TitleBarButton( + testAutoHideConfigFlag(DockManager::DockAreaHasAutoHideButton) && autoHideEnabled); + m_autoHideButton->setObjectName("dockAreaAutoHideButton"); + //m_autoHideButton->setAutoRaise(true); + internal::setToolTip(m_autoHideButton, q->titleBarButtonToolTip(TitleBarButtonAutoHide)); + internal::setButtonIcon(m_autoHideButton, QStyle::SP_DialogOkButton, ADS::AutoHideIcon); + m_autoHideButton->setSizePolicy(sizePolicy); + m_autoHideButton->setCheckable(testAutoHideConfigFlag(DockManager::AutoHideButtonCheckable)); + m_autoHideButton->setChecked(false); + m_layout->addWidget(m_autoHideButton, 0); + QObject::connect(m_autoHideButton, + &QToolButton::clicked, + q, + &DockAreaTitleBar::onAutoHideButtonClicked); - m_closeButton->setSizePolicy(sizePolicy); - m_closeButton->setIconSize(iconSize); - m_closeButton->setFixedSize(buttonSize); - m_layout->addWidget(m_closeButton, 0); - QObject::connect(m_closeButton, - &QToolButton::clicked, - q, - &DockAreaTitleBar::onCloseButtonClicked); + // Close button + m_closeButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasCloseButton)); + m_closeButton->setObjectName("dockAreaCloseButton"); + //m_closeButton->setAutoRaise(true); + internal::setButtonIcon(m_closeButton, QStyle::SP_TitleBarCloseButton, ADS::DockAreaCloseIcon); + if (testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) + internal::setToolTip(m_closeButton, Tr::tr("Close Active Tab")); + else + internal::setToolTip(m_closeButton, Tr::tr("Close Group")); - m_layout->addSpacing(1); + m_closeButton->setSizePolicy(sizePolicy); + m_closeButton->setIconSize(iconSize); + m_closeButton->setFixedSize(buttonSize); + m_layout->addWidget(m_closeButton, 0); + QObject::connect(m_closeButton, + &QToolButton::clicked, + q, + &DockAreaTitleBar::onCloseButtonClicked); + + m_layout->addSpacing(1); +} + +void DockAreaTitleBarPrivate::createAutoHideTitleLabel() +{ + m_autoHideTitleLabel = new ElidingLabel(""); + m_autoHideTitleLabel->setObjectName("autoHideTitleLabel"); + m_layout->addWidget(m_autoHideTitleLabel); +} +void DockAreaTitleBarPrivate::createTabBar() +{ + m_tabBar = componentsFactory()->createDockAreaTabBar(m_dockArea); + m_tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + m_layout->addWidget(m_tabBar); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabClosed, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabOpened, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabInserted, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::removingTab, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabMoved, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::currentChanged, + q, + &DockAreaTitleBar::onCurrentTabChanged); + QObject::connect(m_tabBar, &DockAreaTabBar::tabBarClicked, q, &DockAreaTitleBar::tabBarClicked); + QObject::connect(m_tabBar, + &DockAreaTabBar::elidedChanged, + q, + &DockAreaTitleBar::markTabsMenuOutdated); +} + +AbstractFloatingWidget *DockAreaTitleBarPrivate::makeAreaFloating(const QPoint &offset, + eDragState dragState) +{ + QSize size = m_dockArea->size(); + m_dragState = dragState; + bool createFloatingDockContainer = (DraggingFloatingWidget != dragState); + FloatingDockContainer *floatingDockContainer = nullptr; + AbstractFloatingWidget *floatingWidget; + if (createFloatingDockContainer) { + if (m_dockArea->autoHideDockContainer()) + m_dockArea->autoHideDockContainer()->cleanupAndDelete(); + floatingWidget = floatingDockContainer = new FloatingDockContainer(m_dockArea); + } else { + auto w = new FloatingDragPreview(m_dockArea); + QObject::connect(w, &FloatingDragPreview::draggingCanceled, q, [this] { + m_dragState = DraggingInactive; + }); + floatingWidget = w; } - void DockAreaTitleBarPrivate::createTabBar() - { - m_tabBar = componentsFactory()->createDockAreaTabBar(m_dockArea); - m_tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); - m_layout->addWidget(m_tabBar); - QObject::connect(m_tabBar, - &DockAreaTabBar::tabClosed, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - QObject::connect(m_tabBar, - &DockAreaTabBar::tabOpened, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - QObject::connect(m_tabBar, - &DockAreaTabBar::tabInserted, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - QObject::connect(m_tabBar, - &DockAreaTabBar::removingTab, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - QObject::connect(m_tabBar, - &DockAreaTabBar::tabMoved, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - QObject::connect(m_tabBar, - &DockAreaTabBar::currentChanged, - q, - &DockAreaTitleBar::onCurrentTabChanged); - QObject::connect(m_tabBar, - &DockAreaTabBar::tabBarClicked, - q, - &DockAreaTitleBar::tabBarClicked); - QObject::connect(m_tabBar, - &DockAreaTabBar::elidedChanged, - q, - &DockAreaTitleBar::markTabsMenuOutdated); - } - - AbstractFloatingWidget *DockAreaTitleBarPrivate::makeAreaFloating(const QPoint &offset, - eDragState dragState) - { - QSize size = m_dockArea->size(); - m_dragState = dragState; - bool opaqueUndocking = DockManager::testConfigFlag(DockManager::OpaqueUndocking) - || (DraggingFloatingWidget != dragState); - FloatingDockContainer *floatingDockContainer = nullptr; - AbstractFloatingWidget *floatingWidget; - if (opaqueUndocking) { - floatingWidget = floatingDockContainer = new FloatingDockContainer(m_dockArea); - } else { - auto w = new FloatingDragPreview(m_dockArea); - QObject::connect(w, &FloatingDragPreview::draggingCanceled, q, [this] { - m_dragState = DraggingInactive; - }); - floatingWidget = w; + floatingWidget->startFloating(offset, size, dragState, nullptr); + if (floatingDockContainer) { + auto topLevelDockWidget = floatingDockContainer->topLevelDockWidget(); + if (topLevelDockWidget) { + topLevelDockWidget->emitTopLevelChanged(true); } - - floatingWidget->startFloating(offset, size, dragState, nullptr); - if (floatingDockContainer) { - auto topLevelDockWidget = floatingDockContainer->topLevelDockWidget(); - if (topLevelDockWidget) { - topLevelDockWidget->emitTopLevelChanged(true); - } - } - - return floatingWidget; } - void DockAreaTitleBarPrivate::startFloating(const QPoint &offset) - { - m_floatingWidget = makeAreaFloating(offset, DraggingFloatingWidget); + return floatingWidget; +} + +void DockAreaTitleBarPrivate::startFloating(const QPoint &offset) +{ + if (m_dockArea->autoHideDockContainer()) + m_dockArea->autoHideDockContainer()->hide(); + + m_floatingWidget = makeAreaFloating(offset, DraggingFloatingWidget); + qApp->postEvent(m_dockArea, new QEvent((QEvent::Type) internal::g_dockedWidgetDragStartEvent)); +} + +TitleBarButton::TitleBarButton(bool showInTitleBar, QWidget *parent) + : TitleBarButtonType(parent) + , m_showInTitleBar(showInTitleBar) + , m_hideWhenDisabled( + DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaHideDisabledButtons)) +{ + setFocusPolicy(Qt::NoFocus); +} + +void TitleBarButton::setVisible(bool visible) +{ + // 'visible' can stay 'true' if and only if this button is configured to generally visible: + visible = visible && m_showInTitleBar; + + // 'visible' can stay 'true' unless: this button is configured to be invisible when it + // is disabled and it is currently disabled: + if (visible && m_hideWhenDisabled) + visible = isEnabled(); + + Super::setVisible(visible); +} + +void TitleBarButton::setShowInTitleBar(bool show) +{ + m_showInTitleBar = show; + + if (!show) + setVisible(false); +} + +bool TitleBarButton::event(QEvent *event) +{ + if (QEvent::EnabledChange == event->type() && m_hideWhenDisabled) { + // force setVisible() call + // Calling setVisible() directly here doesn't work well when button is expected to be shown first time + const bool visible = isEnabled(); + QMetaObject::invokeMethod( + this, [this, visible] { setVisible(visible); }, Qt::QueuedConnection); } - TitleBarButton::TitleBarButton(bool visible, QWidget *parent) - : TitleBarButtonType(parent) - , m_visible(visible) - , m_hideWhenDisabled(DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaHideDisabledButtons)) - {} + return Super::event(event); +} - void TitleBarButton::setVisible(bool visible) - { - // 'visible' can stay 'true' if and only if this button is configured to generaly visible: - visible = visible && m_visible; +SpacerWidget::SpacerWidget(QWidget *parent) + : QWidget(parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setStyleSheet("border: none; background: none;"); +} - // 'visible' can stay 'true' unless: this button is configured to be invisible when it - // is disabled and it is currently disabled: - if (visible && m_hideWhenDisabled) - visible = isEnabled(); +DockAreaTitleBar::DockAreaTitleBar(DockAreaWidget *parent) + : QFrame(parent) + , d(new DockAreaTitleBarPrivate(this)) +{ + d->m_dockArea = parent; - Super::setVisible(visible); - } + setObjectName("dockAreaTitleBar"); + d->m_layout = new QBoxLayout(QBoxLayout::LeftToRight); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - bool TitleBarButton::event(QEvent *event) - { - if (QEvent::EnabledChange == event->type() && m_hideWhenDisabled) { - // force setVisible() call - // Calling setVisible() directly here doesn't work well when button is expected to be shown first time - const bool visible = isEnabled(); - QMetaObject::invokeMethod(this, [this, visible] { setVisible(visible); }, Qt::QueuedConnection); - } + d->createTabBar(); + d->createAutoHideTitleLabel(); + d->m_autoHideTitleLabel->setVisible(false); // Default hidden + d->m_layout->addWidget(new SpacerWidget(this)); + d->createButtons(); + setFocusPolicy(Qt::NoFocus); +} - return Super::event(event); - } +DockAreaTitleBar::~DockAreaTitleBar() +{ + if (!d->m_closeButton.isNull()) + delete d->m_closeButton; - SpacerWidget::SpacerWidget(QWidget *parent) - : QWidget(parent) - { - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - setStyleSheet("border: none; background: none;"); - } + if (!d->m_tabsMenuButton.isNull()) + delete d->m_tabsMenuButton; - DockAreaTitleBar::DockAreaTitleBar(DockAreaWidget *parent) - : QFrame(parent) - , d(new DockAreaTitleBarPrivate(this)) - { - d->m_dockArea = parent; + if (!d->m_undockButton.isNull()) + delete d->m_undockButton; - setObjectName("dockAreaTitleBar"); - d->m_layout = new QBoxLayout(QBoxLayout::LeftToRight); - d->m_layout->setContentsMargins(0, 0, 0, 0); - d->m_layout->setSpacing(0); - setLayout(d->m_layout); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + delete d; +} - d->createTabBar(); - d->m_layout->addWidget(new SpacerWidget(this)); - d->createButtons(); - } +DockAreaTabBar *DockAreaTitleBar::tabBar() const +{ + return d->m_tabBar; +} - DockAreaTitleBar::~DockAreaTitleBar() { - if (!d->m_closeButton.isNull()) - delete d->m_closeButton; - - if (!d->m_tabsMenuButton.isNull()) - delete d->m_tabsMenuButton; - - if (!d->m_undockButton.isNull()) - delete d->m_undockButton; - - delete d; - } - - DockAreaTabBar *DockAreaTitleBar::tabBar() const { return d->m_tabBar; } - - void DockAreaTitleBar::markTabsMenuOutdated() { - if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaDynamicTabsMenuButtonVisibility)) { - bool hasElidedTabTitle = false; - for (int i = 0; i < d->m_tabBar->count(); ++i) { - if (!d->m_tabBar->isTabOpen(i)) - continue; - - DockWidgetTab* tab = d->m_tabBar->tab(i); - if (tab->isTitleElided()) { - hasElidedTabTitle = true; - break; - } - } - const bool visible = (hasElidedTabTitle && (d->m_tabBar->count() > 1)); - QMetaObject::invokeMethod(this, [this, visible] { - d->m_tabsMenuButton->setVisible(visible); }, Qt::QueuedConnection); - } - d->m_menuOutdated = true; - } - - void DockAreaTitleBar::onTabsMenuAboutToShow() - { - if (!d->m_menuOutdated) - return; - - QMenu *menu = d->m_tabsMenuButton->menu(); - menu->clear(); +void DockAreaTitleBar::markTabsMenuOutdated() +{ + if (DockAreaTitleBarPrivate::testConfigFlag( + DockManager::DockAreaDynamicTabsMenuButtonVisibility)) { + bool hasElidedTabTitle = false; for (int i = 0; i < d->m_tabBar->count(); ++i) { if (!d->m_tabBar->isTabOpen(i)) continue; - auto tab = d->m_tabBar->tab(i); - QAction *action = menu->addAction(tab->icon(), tab->text()); - internal::setToolTip(action, tab->toolTip()); - action->setData(i); - } - - d->m_menuOutdated = false; - } - - void DockAreaTitleBar::onCloseButtonClicked() - { - qCInfo(adsLog) << Q_FUNC_INFO; - if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) - d->m_tabBar->closeTab(d->m_tabBar->currentIndex()); - else - d->m_dockArea->closeArea(); - } - - void DockAreaTitleBar::onUndockButtonClicked() - { - if (d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) - d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive); - } - - void DockAreaTitleBar::onTabsMenuActionTriggered(QAction *action) - { - int index = action->data().toInt(); - d->m_tabBar->setCurrentIndex(index); - emit tabBarClicked(index); - } - - void DockAreaTitleBar::updateDockWidgetActionsButtons() - { - QTC_ASSERT(d->m_tabBar->currentTab(), return ); - QTC_ASSERT(d->m_tabBar->currentTab()->dockWidget(), return ); - DockWidget* dockWidget = d->m_tabBar->currentTab()->dockWidget(); - if (!d->m_dockWidgetActionsButtons.isEmpty()) { - for (auto button : std::as_const(d->m_dockWidgetActionsButtons)) { - d->m_layout->removeWidget(button); - delete button; + DockWidgetTab *tab = d->m_tabBar->tab(i); + if (tab->isTitleElided()) { + hasElidedTabTitle = true; + break; } - d->m_dockWidgetActionsButtons.clear(); } + const bool visible = (hasElidedTabTitle && (d->m_tabBar->count() > 1)); + QMetaObject::invokeMethod( + this, + [this, visible] { d->m_tabsMenuButton->setVisible(visible); }, + Qt::QueuedConnection); + } + d->m_menuOutdated = true; +} - auto actions = dockWidget->titleBarActions(); - if (actions.isEmpty()) - return; +void DockAreaTitleBar::onTabsMenuAboutToShow() +{ + if (!d->m_menuOutdated) + return; - int insertIndex = indexOf(d->m_tabsMenuButton); - for (auto action : actions) { - auto button = new TitleBarButton(true, this); - button->setDefaultAction(action); - button->setAutoRaise(true); - button->setPopupMode(QToolButton::InstantPopup); - button->setObjectName(action->objectName()); - d->m_layout->insertWidget(insertIndex++, button, 0); - d->m_dockWidgetActionsButtons.append(button); - } + QMenu *menu = d->m_tabsMenuButton->menu(); + menu->clear(); + for (int i = 0; i < d->m_tabBar->count(); ++i) { + if (!d->m_tabBar->isTabOpen(i)) + continue; + + auto tab = d->m_tabBar->tab(i); + QAction *action = menu->addAction(tab->icon(), tab->text()); + internal::setToolTip(action, tab->toolTip()); + action->setData(i); } - void DockAreaTitleBar::onCurrentTabChanged(int index) - { - if (index < 0) - return; + d->m_menuOutdated = false; +} - if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) { - DockWidget *dockWidget = d->m_tabBar->tab(index)->dockWidget(); - d->m_closeButton->setEnabled( - dockWidget->features().testFlag(DockWidget::DockWidgetClosable)); +void DockAreaTitleBar::onCloseButtonClicked() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + if (DockManager::testAutoHideConfigFlag(DockManager::AutoHideCloseButtonCollapsesDock) + && d->m_dockArea->autoHideDockContainer()) + d->m_dockArea->autoHideDockContainer()->collapseView(true); + else if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) + d->m_tabBar->closeTab(d->m_tabBar->currentIndex()); + else + d->m_dockArea->closeArea(); +} + +void DockAreaTitleBar::onUndockButtonClicked() +{ + if (d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) + d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive); +} + +void DockAreaTitleBar::onTabsMenuActionTriggered(QAction *action) +{ + int index = action->data().toInt(); + d->m_tabBar->setCurrentIndex(index); + emit tabBarClicked(index); +} + +void DockAreaTitleBar::updateDockWidgetActionsButtons() +{ + auto tab = d->m_tabBar->currentTab(); + if (!tab) + return; + + QTC_ASSERT(tab, return); + QTC_ASSERT(tab->dockWidget(), return); + DockWidget *dockWidget = tab->dockWidget(); + if (!d->m_dockWidgetActionsButtons.isEmpty()) { + for (auto button : std::as_const(d->m_dockWidgetActionsButtons)) { + d->m_layout->removeWidget(button); + delete button; } - - updateDockWidgetActionsButtons(); + d->m_dockWidgetActionsButtons.clear(); } - QAbstractButton *DockAreaTitleBar::button(eTitleBarButton which) const - { - switch (which) { - case TitleBarButtonTabsMenu: - return d->m_tabsMenuButton; - case TitleBarButtonUndock: - return d->m_undockButton; - case TitleBarButtonClose: - return d->m_closeButton; - } - return nullptr; + auto actions = dockWidget->titleBarActions(); + if (actions.isEmpty()) + return; + + int insertIndex = indexOf(d->m_tabsMenuButton); + for (auto action : actions) { + auto button = new TitleBarButton(true, this); + button->setDefaultAction(action); + button->setAutoRaise(true); + button->setPopupMode(QToolButton::InstantPopup); + button->setObjectName(action->objectName()); + d->m_layout->insertWidget(insertIndex++, button, 0); + d->m_dockWidgetActionsButtons.append(button); + } +} + +void DockAreaTitleBar::onCurrentTabChanged(int index) +{ + if (index < 0) + return; + + if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) { + DockWidget *dockWidget = d->m_tabBar->tab(index)->dockWidget(); + d->m_closeButton->setEnabled( + dockWidget->features().testFlag(DockWidget::DockWidgetClosable)); } - void DockAreaTitleBar::setVisible(bool visible) - { - Super::setVisible(visible); - markTabsMenuOutdated(); + updateDockWidgetActionsButtons(); +} + +void DockAreaTitleBar::onAutoHideButtonClicked() +{ + if (DockManager::testAutoHideConfigFlag(DockManager::AutoHideButtonTogglesArea) + || qApp->keyboardModifiers().testFlag(Qt::ControlModifier)) + d->m_dockArea->toggleAutoHide(); + else + d->m_dockArea->currentDockWidget()->toggleAutoHide(); +} + +void DockAreaTitleBar::onAutoHideDockAreaActionClicked() +{ + d->m_dockArea->toggleAutoHide(); +} + +void DockAreaTitleBar::onAutoHideToActionClicked() +{ + int location = sender()->property(g_locationProperty).toInt(); + d->m_dockArea->toggleAutoHide((SideBarLocation) location); +} + +TitleBarButton *DockAreaTitleBar::button(eTitleBarButton which) const +{ + switch (which) { + case TitleBarButtonTabsMenu: + return d->m_tabsMenuButton; + case TitleBarButtonUndock: + return d->m_undockButton; + case TitleBarButtonClose: + return d->m_closeButton; + case TitleBarButtonAutoHide: + return d->m_autoHideButton; } + return nullptr; +} +ElidingLabel *DockAreaTitleBar::autoHideTitleLabel() const +{ + return d->m_autoHideTitleLabel; +} - void DockAreaTitleBar::mousePressEvent(QMouseEvent *event) - { - if (event->button() == Qt::LeftButton) { - event->accept(); - d->m_dragStartMousePos = event->pos(); - d->m_dragState = DraggingMousePressed; - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - d->m_tabBar->currentTab()->setFocus(Qt::OtherFocusReason); +void DockAreaTitleBar::setVisible(bool visible) +{ + Super::setVisible(visible); + markTabsMenuOutdated(); +} - return; - } - Super::mousePressEvent(event); - } - - void DockAreaTitleBar::mouseReleaseEvent(QMouseEvent *event) - { - if (event->button() == Qt::LeftButton) { - qCInfo(adsLog) << Q_FUNC_INFO; - event->accept(); - auto CurrentDragState = d->m_dragState; - d->m_dragStartMousePos = QPoint(); - d->m_dragState = DraggingInactive; - if (DraggingFloatingWidget == CurrentDragState) - d->m_floatingWidget->finishDragging(); - - return; - } - Super::mouseReleaseEvent(event); - } - - void DockAreaTitleBar::mouseMoveEvent(QMouseEvent *event) - { - Super::mouseMoveEvent(event); - if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { - d->m_dragState = DraggingInactive; - return; - } - - // move floating window - if (d->isDraggingState(DraggingFloatingWidget)) { - d->m_floatingWidget->moveFloating(); - return; - } - - // If this is the last dock area in a floating dock container it does not make - // sense to move it to a new floating widget and leave this one empty - if (d->m_dockArea->dockContainer()->isFloating() - && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1) { - return; - } - - // If one single dock widget in this area is not floatable then the whole - // area is not floatable - // If we do non opaque undocking, then we can create the floating drag - // preview if the dock widget is movable - auto features = d->m_dockArea->features(); - if (!features.testFlag(DockWidget::DockWidgetFloatable) - && !(features.testFlag(DockWidget::DockWidgetMovable) - && !DockManager::testConfigFlag(DockManager::OpaqueUndocking))) { - return; - } - - int dragDistance = (d->m_dragStartMousePos - event->pos()).manhattanLength(); - if (dragDistance >= DockManager::startDragDistance()) { - qCInfo(adsLog) << "DockAreaTitlBar::startFloating"; - d->startFloating(d->m_dragStartMousePos); - auto overlay = d->m_dockArea->dockManager()->containerOverlay(); - overlay->setAllowedAreas(OuterDockAreas); - } +void DockAreaTitleBar::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + event->accept(); + d->m_dragStartMousePos = event->pos(); + d->m_dragState = DraggingMousePressed; + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + d->dockManager()->dockFocusController()->setDockWidgetTabFocused( + d->m_tabBar->currentTab()); return; } + Super::mousePressEvent(event); +} - void DockAreaTitleBar::mouseDoubleClickEvent(QMouseEvent *event) - { - // If this is the last dock area in a dock container it does not make - // sense to move it to a new floating widget and leave this one empty - if (d->m_dockArea->dockContainer()->isFloating() - && d->m_dockArea->dockContainer()->dockAreaCount() == 1) - return; - - if (!d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) - return; - - d->makeAreaFloating(event->pos(), DraggingInactive); - } - - void DockAreaTitleBar::contextMenuEvent(QContextMenuEvent *event) - { +void DockAreaTitleBar::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + qCInfo(adsLog) << Q_FUNC_INFO; event->accept(); - if (d->isDraggingState(DraggingFloatingWidget)) - return; + auto currentDragState = d->m_dragState; + d->m_dragStartMousePos = QPoint(); + d->m_dragState = DraggingInactive; + if (DraggingFloatingWidget == currentDragState) + d->m_floatingWidget->finishDragging(); - QMenu menu(this); - auto action = menu.addAction(Tr::tr("Detach Area"), - this, - &DockAreaTitleBar::onUndockButtonClicked); - action->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)); + return; + } + Super::mouseReleaseEvent(event); +} + +void DockAreaTitleBar::mouseMoveEvent(QMouseEvent *event) +{ + Super::mouseMoveEvent(event); + if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { + d->m_dragState = DraggingInactive; + return; + } + + // move floating window + if (d->isDraggingState(DraggingFloatingWidget)) { + d->m_floatingWidget->moveFloating(); + return; + } + + // If this is the last dock area in a floating dock container it does not make + // sense to move it to a new floating widget and leave this one empty + if (d->m_dockArea->dockContainer()->isFloating() + && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1 + && !d->m_dockArea->isAutoHide()) { + return; + } + + // If one single dock widget in this area is not floatable then the whole + // area is not floatable + // We can create the floating drag preview if the dock widget is movable + auto features = d->m_dockArea->features(); + if (!features.testFlag(DockWidget::DockWidgetFloatable) + && !(features.testFlag(DockWidget::DockWidgetMovable))) { + return; + } + + int dragDistance = (d->m_dragStartMousePos - event->pos()).manhattanLength(); + if (dragDistance >= DockManager::startDragDistance()) { + qCInfo(adsLog) << "DockAreaTitlBar::startFloating"; + d->startFloating(d->m_dragStartMousePos); + auto overlay = d->m_dockArea->dockManager()->containerOverlay(); + overlay->setAllowedAreas(OuterDockAreas); + } + + return; +} + +void DockAreaTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + // If this is the last dock area in a dock container it does not make + // sense to move it to a new floating widget and leave this one empty + if (d->m_dockArea->dockContainer()->isFloating() + && d->m_dockArea->dockContainer()->dockAreaCount() == 1) + return; + + if (!d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) + return; + + d->makeAreaFloating(event->pos(), DraggingInactive); +} + +void DockAreaTitleBar::contextMenuEvent(QContextMenuEvent *event) +{ + event->accept(); + if (d->isDraggingState(DraggingFloatingWidget)) + return; + + const bool isAutoHide = d->m_dockArea->isAutoHide(); + const bool isTopLevelArea = d->m_dockArea->isTopLevelArea(); + + QMenu menu(this); + if (!isTopLevelArea) { + QAction *detachAction = menu.addAction(isAutoHide ? tr("Detach") : tr("Detach Group")); + detachAction->connect(detachAction, + &QAction::triggered, + this, + &DockAreaTitleBar::onUndockButtonClicked); + detachAction->setEnabled( + d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)); + + if (DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) { + QAction *pinAction = menu.addAction(isAutoHide ? tr("Unpin (Dock)") : tr("Pin Group")); + pinAction->connect(pinAction, + &QAction::triggered, + this, + &DockAreaTitleBar::onAutoHideDockAreaActionClicked); + + auto areaIsPinnable = d->m_dockArea->features().testFlag(DockWidget::DockWidgetPinnable); + pinAction->setEnabled(areaIsPinnable); + if (!isAutoHide) { + auto tmp = menu.addMenu(tr("Pin Group To...")); + tmp->setEnabled(areaIsPinnable); + d->createAutoHideToAction(tr("Top"), SideBarTop, tmp); + d->createAutoHideToAction(tr("Left"), SideBarLeft, tmp); + d->createAutoHideToAction(tr("Right"), SideBarRight, tmp); + d->createAutoHideToAction(tr("Bottom"), SideBarBottom, tmp); + } + } menu.addSeparator(); - action = menu.addAction(Tr::tr("Close Area"), this, &DockAreaTitleBar::onCloseButtonClicked); - action->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable)); - menu.addAction(Tr::tr("Close Other Areas"), d->m_dockArea, &DockAreaWidget::closeOtherAreas); - menu.exec(event->globalPos()); + } + QAction *closeAction = menu.addAction(isAutoHide ? tr("Close") : tr("Close Group")); + closeAction->connect(closeAction, + &QAction::triggered, + this, + &DockAreaTitleBar::onCloseButtonClicked); + closeAction->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable)); + + if (!isAutoHide && !isTopLevelArea) { + QAction *closeOthersAction = menu.addAction(tr("Close Other Groups")); + closeOthersAction->connect(closeOthersAction, + &QAction::triggered, + d->m_dockArea, + &DockAreaWidget::closeOtherAreas); } - void DockAreaTitleBar::insertWidget(int index, QWidget *widget) - { - d->m_layout->insertWidget(index, widget); - } + menu.exec(event->globalPos()); +} - int DockAreaTitleBar::indexOf(QWidget *widget) const - { - return d->m_layout->indexOf(widget); +void DockAreaTitleBar::insertWidget(int index, QWidget *widget) +{ + d->m_layout->insertWidget(index, widget); +} + +int DockAreaTitleBar::indexOf(QWidget *widget) const +{ + return d->m_layout->indexOf(widget); +} + +QString DockAreaTitleBar::titleBarButtonToolTip(eTitleBarButton button) const +{ + switch (button) { + case TitleBarButtonAutoHide: + if (d->m_dockArea->isAutoHide()) + return tr("Unpin (Dock)"); + + if (DockManager::testAutoHideConfigFlag(DockManager::AutoHideButtonTogglesArea)) + return tr("Pin Group"); + else + return tr("Pin Active Tab (Press Ctrl to Pin Group)"); + break; + + case TitleBarButtonClose: + if (d->m_dockArea->isAutoHide()) + return tr("Close"); + + if (DockManager::testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) + return tr("Close Active Tab"); + else + return tr("Close Group"); + break; + + default: + break; } + return QString(); +} + +void DockAreaTitleBar::setAreaFloating() +{ + // If this is the last dock area in a dock container it does not make sense to move it to + // a new floating widget and leave this one empty. + auto dockContainer = d->m_dockArea->dockContainer(); + if (dockContainer->isFloating() && dockContainer->dockAreaCount() == 1 + && !d->m_dockArea->isAutoHide()) + return; + + if (!d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) + return; + + d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.h b/src/libs/advanceddockingsystem/dockareatitlebar.h index 888e3736af7..738dea476de 100644 --- a/src/libs/advanceddockingsystem/dockareatitlebar.h +++ b/src/libs/advanceddockingsystem/dockareatitlebar.h @@ -17,6 +17,7 @@ namespace ADS { class DockAreaTabBar; class DockAreaWidget; class DockAreaTitleBarPrivate; +class ElidingLabel; using TitleBarButtonType = QToolButton; @@ -29,17 +30,22 @@ using TitleBarButtonType = QToolButton; class TitleBarButton : public TitleBarButtonType { Q_OBJECT - bool m_visible = true; + bool m_showInTitleBar = true; bool m_hideWhenDisabled = false; public: using Super = TitleBarButtonType; - TitleBarButton(bool visible = true, QWidget *parent = nullptr); + TitleBarButton(bool showInTitleBar = true, QWidget *parent = nullptr); /** * Adjust this visibility change request with our internal settings: */ void setVisible(bool visible) override; + /** + * Configures, if the title bar button should be shown in title bar + */ + void setShowInTitleBar(bool show); + protected: /** * Handle EnabledChanged signal to set button invisible if the configured @@ -81,6 +87,9 @@ private: void onUndockButtonClicked(); void onTabsMenuActionTriggered(QAction *action); void onCurrentTabChanged(int index); + void onAutoHideButtonClicked(); + void onAutoHideDockAreaActionClicked(); + void onAutoHideToActionClicked(); protected: /** @@ -135,7 +144,12 @@ public: /** * Returns the button corresponding to the given title bar button identifier */ - QAbstractButton *button(eTitleBarButton which) const; + TitleBarButton *button(eTitleBarButton which) const; + + /** + * Returns the auto hide title label, used when the dock area is expanded and auto hidden + */ + ElidingLabel* autoHideTitleLabel() const; /** * Updates the visibility of the dock widget actions in the title bar @@ -166,6 +180,17 @@ public: */ int indexOf(QWidget *widget) const; + /** + * Close group tool tip based on the current state + * Auto hide widgets can only have one dock widget so it does not make sense for the tooltip to show close group + */ + QString titleBarButtonToolTip(eTitleBarButton button) const; + + /** + * Moves the dock area into its own floating widget if the area DockWidgetFloatable flag is true. + */ + void setAreaFloating(); + signals: /** * This signal is emitted if a tab in the tab bar is clicked by the user diff --git a/src/libs/advanceddockingsystem/dockareawidget.cpp b/src/libs/advanceddockingsystem/dockareawidget.cpp index 522b6b985ee..c4c74a38c1d 100644 --- a/src/libs/advanceddockingsystem/dockareawidget.cpp +++ b/src/libs/advanceddockingsystem/dockareawidget.cpp @@ -4,15 +4,17 @@ #include "dockareawidget.h" #include "ads_globals_p.h" +#include "autohidedockcontainer.h" #include "dockareatabbar.h" #include "dockareatitlebar.h" #include "dockcomponentsfactory.h" #include "dockcontainerwidget.h" +#include "dockingstatereader.h" #include "dockmanager.h" -#include "dockoverlay.h" #include "docksplitter.h" #include "dockwidget.h" #include "dockwidgettab.h" +#include "elidinglabel.h" #include "floatingdockcontainer.h" #include @@ -30,641 +32,1155 @@ #include -namespace ADS +namespace ADS { + +static const char *const g_indexProperty = "index"; +static const char *const g_actionProperty = "action"; + +/** + * Check, if auto hide is enabled + */ +static bool isAutoHideFeatureEnabled() { - static const char *const INDEX_PROPERTY = "index"; - static const char *const ACTION_PROPERTY = "action"; + return DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled); +} - /** - * Internal dock area layout mimics stack layout but only inserts the current - * widget into the internal QLayout object. - * \warning Only the current widget has a parent. All other widgets - * do not have a parent. That means, a widget that is in this layout may - * return nullptr for its parent() function if it is not the current widget. - */ - class DockAreaLayout - { - private: - QBoxLayout *m_parentLayout; - QList m_widgets; - int m_currentIndex = -1; - QWidget *m_currentWidget = nullptr; +/** + * Internal dock area layout mimics stack layout but only inserts the current + * widget into the internal QLayout object. + * \warning Only the current widget has a parent. All other widgets + * do not have a parent. That means, a widget that is in this layout may + * return nullptr for its parent() function if it is not the current widget. + */ +class DockAreaLayout +{ +private: + QBoxLayout *m_parentLayout; + QList m_widgets; + int m_currentIndex = -1; + QWidget *m_currentWidget = nullptr; - public: +public: /** * Creates an instance with the given parent layout */ - DockAreaLayout(QBoxLayout *parentLayout) - : m_parentLayout(parentLayout) - {} - - /** - * Returns the number of widgets in this layout - */ - int count() const { return m_widgets.count(); } - - /** - * Inserts the widget at the given index position into the internal widget - * list - */ - void insertWidget(int index, QWidget *widget) - { - widget->setParent(nullptr); - if (index < 0) - index = m_widgets.count(); - - m_widgets.insert(index, widget); - if (m_currentIndex < 0) { - setCurrentIndex(index); - } else { - if (index <= m_currentIndex) - ++m_currentIndex; - } - } - - /** - * Removes the given widget from the layout - */ - void removeWidget(QWidget *widget) - { - if (currentWidget() == widget) { - auto layoutItem = m_parentLayout->takeAt(1); - if (layoutItem) - layoutItem->widget()->setParent(nullptr); - - m_currentWidget = nullptr; - m_currentIndex = -1; - } else if (indexOf(widget) < m_currentIndex) { - --m_currentIndex; - } - m_widgets.removeOne(widget); - } - - /** - * Returns the current selected widget - */ - QWidget *currentWidget() const { return m_currentWidget; } - - /** - * Activates the widget with the give index. - */ - void setCurrentIndex(int index) - { - QWidget *prev = currentWidget(); - QWidget *next = widget(index); - if (!next || (next == prev && !m_currentWidget)) - return; - - bool reenableUpdates = false; - QWidget *parent = m_parentLayout->parentWidget(); - - if (parent && parent->updatesEnabled()) { - reenableUpdates = true; - parent->setUpdatesEnabled(false); - } - - if (auto layoutItem = m_parentLayout->takeAt(1)) - layoutItem->widget()->setParent(nullptr); - - m_parentLayout->addWidget(next); - if (prev) - prev->hide(); - - m_currentIndex = index; - m_currentWidget = next; - - if (reenableUpdates) - parent->setUpdatesEnabled(true); - } - - /** - * Returns the index of the current active widget - */ - int currentIndex() const { return m_currentIndex; } - - /** - * Returns true if there are no widgets in the layout - */ - bool isEmpty() const { return m_widgets.empty(); } - - /** - * Returns the index of the given widget - */ - int indexOf(QWidget *widget) const { return m_widgets.indexOf(widget); } - - /** - * Returns the widget for the given index - */ - QWidget *widget(int index) const - { - return (index < m_widgets.size()) ? m_widgets.at(index) : nullptr; - } - - /** - * Returns the geometry of the current active widget - */ - QRect geometry() const { return m_widgets.empty() ? QRect() : currentWidget()->geometry(); } - }; - - /** - * Private data class of DockAreaWidget class (pimpl) - */ - struct DockAreaWidgetPrivate - { - DockAreaWidget *q = nullptr; - QBoxLayout *m_layout = nullptr; - DockAreaLayout *m_contentsLayout = nullptr; - DockAreaTitleBar *m_titleBar = nullptr; - DockManager *m_dockManager = nullptr; - bool m_updateTitleBarButtons = false; - DockWidgetAreas m_allowedAreas = AllDockAreas; - QSize m_minSizeHint; - - /** - * Private data constructor - */ - DockAreaWidgetPrivate(DockAreaWidget *parent); - - /** - * Creates the layout for top area with tabs and close button - */ - void createTitleBar(); - - /** - * Returns the dock widget with the given index - */ - DockWidget *dockWidgetAt(int index) - { - return qobject_cast(m_contentsLayout->widget(index)); - } - - /** - * Convenience function to ease title widget access by index - */ - DockWidgetTab *tabWidgetAt(int index) { return dockWidgetAt(index)->tabWidget(); } - - /** - * Returns the tab action of the given dock widget - */ - QAction *dockWidgetTabAction(DockWidget *dockWidget) const - { - return qvariant_cast(dockWidget->property(ACTION_PROPERTY)); - } - - /** - * Returns the index of the given dock widget - */ - int dockWidgetIndex(DockWidget *dockWidget) const - { - return dockWidget->property(INDEX_PROPERTY).toInt(); - } - - /** - * Convenience function for tabbar access - */ - DockAreaTabBar *tabBar() const { return m_titleBar->tabBar(); } - - /** - * Updates the enable state of the close and detach button - */ - void updateTitleBarButtonStates(); - - /** - * Scans all contained dock widgets for the max. minimum size hint - */ - void updateMinimumSizeHint() - { - m_minSizeHint = QSize(); - for (int i = 0; i < m_contentsLayout->count(); ++i) - { - auto widget = m_contentsLayout->widget(i); - m_minSizeHint.setHeight(qMax(m_minSizeHint.height(), - widget->minimumSizeHint().height())); - m_minSizeHint.setWidth(qMax(m_minSizeHint.width(), - widget->minimumSizeHint().width())); - } - } - }; - // struct DockAreaWidgetPrivate - - DockAreaWidgetPrivate::DockAreaWidgetPrivate(DockAreaWidget *parent) - : q(parent) + DockAreaLayout(QBoxLayout *parentLayout) + : m_parentLayout(parentLayout) {} - void DockAreaWidgetPrivate::createTitleBar() + /** + * Returns the number of widgets in this layout + */ + int count() const { return m_widgets.count(); } + + /** + * Inserts the widget at the given index position into the internal widget + * list + */ + void insertWidget(int index, QWidget *widget) { - m_titleBar = componentsFactory()->createDockAreaTitleBar(q); - m_layout->addWidget(m_titleBar); - QObject::connect(tabBar(), - &DockAreaTabBar::tabCloseRequested, - q, - &DockAreaWidget::onTabCloseRequested); - QObject::connect(m_titleBar, - &DockAreaTitleBar::tabBarClicked, - q, - &DockAreaWidget::setCurrentIndex); - QObject::connect(tabBar(), &DockAreaTabBar::tabMoved, q, &DockAreaWidget::reorderDockWidget); - } + widget->setParent(nullptr); + if (index < 0) + index = m_widgets.count(); - void DockAreaWidgetPrivate::updateTitleBarButtonStates() - { - if (q->isHidden()) { - m_updateTitleBarButtons = true; - return; - } - - m_titleBar->button(TitleBarButtonClose) - ->setEnabled(q->features().testFlag(DockWidget::DockWidgetClosable)); - m_titleBar->button(TitleBarButtonUndock) - ->setEnabled(q->features().testFlag(DockWidget::DockWidgetFloatable)); - m_titleBar->updateDockWidgetActionsButtons(); - m_updateTitleBarButtons = false; - } - - DockAreaWidget::DockAreaWidget(DockManager *dockManager, DockContainerWidget *parent) - : QFrame(parent) - , d(new DockAreaWidgetPrivate(this)) - { - d->m_dockManager = dockManager; - d->m_layout = new QBoxLayout(QBoxLayout::TopToBottom); - d->m_layout->setContentsMargins(0, 0, 0, 0); - d->m_layout->setSpacing(0); - setLayout(d->m_layout); - - d->createTitleBar(); - d->m_contentsLayout = new DockAreaLayout(d->m_layout); - if (d->m_dockManager) - emit d->m_dockManager->dockAreaCreated(this); - } - - DockAreaWidget::~DockAreaWidget() - { - qCInfo(adsLog) << Q_FUNC_INFO; - delete d->m_contentsLayout; - delete d; - } - - DockManager *DockAreaWidget::dockManager() const { return d->m_dockManager; } - - DockContainerWidget *DockAreaWidget::dockContainer() const - { - return internal::findParent(this); - } - - void DockAreaWidget::addDockWidget(DockWidget *dockWidget) - { - insertDockWidget(d->m_contentsLayout->count(), dockWidget); - } - - void DockAreaWidget::insertDockWidget(int index, DockWidget *dockWidget, bool activate) - { - d->m_contentsLayout->insertWidget(index, dockWidget); - dockWidget->setDockArea(this); - dockWidget->tabWidget()->setDockAreaWidget(this); - auto tabWidget = dockWidget->tabWidget(); - // Inserting the tab will change the current index which in turn will - // make the tab widget visible in the slot - d->tabBar()->blockSignals(true); - d->tabBar()->insertTab(index, tabWidget); - d->tabBar()->blockSignals(false); - tabWidget->setVisible(!dockWidget->isClosed()); - dockWidget->setProperty(INDEX_PROPERTY, index); - d->m_minSizeHint.setHeight(qMax(d->m_minSizeHint.height(), - dockWidget->minimumSizeHint().height())); - d->m_minSizeHint.setWidth(qMax(d->m_minSizeHint.width(), - dockWidget->minimumSizeHint().width())); - if (activate) + m_widgets.insert(index, widget); + if (m_currentIndex < 0) { setCurrentIndex(index); - - // If this dock area is hidden, then we need to make it visible again - // by calling dockWidget->toggleViewInternal(true); - if (!this->isVisible() && d->m_contentsLayout->count() > 1 && !dockManager()->isRestoringState()) - dockWidget->toggleViewInternal(true); - - d->updateTitleBarButtonStates(); + } else { + if (index <= m_currentIndex) + ++m_currentIndex; + } } - void DockAreaWidget::removeDockWidget(DockWidget *dockWidget) + /** + * Removes the given widget from the layout + */ + void removeWidget(QWidget *widget) { - qCInfo(adsLog) << Q_FUNC_INFO; - auto nextOpen = nextOpenDockWidget(dockWidget); + if (currentWidget() == widget) { + auto layoutItem = m_parentLayout->takeAt(1); + if (layoutItem) + layoutItem->widget()->setParent(nullptr); - d->m_contentsLayout->removeWidget(dockWidget); - auto tabWidget = dockWidget->tabWidget(); - tabWidget->hide(); - d->tabBar()->removeTab(tabWidget); - DockContainerWidget *dockContainerWidget = dockContainer(); - if (nextOpen) { - setCurrentDockWidget(nextOpen); - } else if (d->m_contentsLayout->isEmpty() && dockContainerWidget->dockAreaCount() >= 1) { - qCInfo(adsLog) << "Dock Area empty"; - dockContainerWidget->removeDockArea(this); - this->deleteLater(); - } else { - // if contents layout is not empty but there are no more open dock - // widgets, then we need to hide the dock area because it does not - // contain any visible content - hideAreaWithNoVisibleContent(); + delete layoutItem; + m_currentWidget = nullptr; + m_currentIndex = -1; + } else if (indexOf(widget) < m_currentIndex) { + --m_currentIndex; + } + m_widgets.removeOne(widget); + } + + /** + * Returns the current selected widget + */ + QWidget *currentWidget() const { return m_currentWidget; } + + /** + * Activates the widget with the give index. + */ + void setCurrentIndex(int index) + { + QWidget *prev = currentWidget(); + QWidget *next = widget(index); + if (!next || (next == prev && !m_currentWidget)) + return; + + bool reenableUpdates = false; + QWidget *parent = m_parentLayout->parentWidget(); + + if (parent && parent->updatesEnabled()) { + reenableUpdates = true; + parent->setUpdatesEnabled(false); } - d->updateTitleBarButtonStates(); - updateTitleBarVisibility(); - d->updateMinimumSizeHint(); - auto topLevelDockWidget = dockContainerWidget->topLevelDockWidget(); - if (topLevelDockWidget) - topLevelDockWidget->emitTopLevelChanged(true); + auto layoutItem = m_parentLayout->takeAt(1); + + if (layoutItem) + layoutItem->widget()->setParent(nullptr); + + delete layoutItem; + + m_parentLayout->addWidget(next); + if (prev) + prev->hide(); + + m_currentIndex = index; + m_currentWidget = next; + + if (reenableUpdates) + parent->setUpdatesEnabled(true); + } + + /** + * Returns the index of the current active widget + */ + int currentIndex() const { return m_currentIndex; } + + /** + * Returns true if there are no widgets in the layout + */ + bool isEmpty() const { return m_widgets.empty(); } + + /** + * Returns the index of the given widget + */ + int indexOf(QWidget *widget) const { return m_widgets.indexOf(widget); } + + /** + * Returns the widget for the given index + */ + QWidget *widget(int index) const + { + return (index < m_widgets.size()) ? m_widgets.at(index) : nullptr; + } + + /** + * Returns the geometry of the current active widget + */ + QRect geometry() const { return m_widgets.empty() ? QRect() : currentWidget()->geometry(); } +}; + +static const DockWidgetAreas DefaultAllowedAreas = AllDockAreas; + +/** + * Private data class of DockAreaWidget class (pimpl) + */ +struct DockAreaWidgetPrivate +{ + DockAreaWidget *q = nullptr; + QBoxLayout *m_layout = nullptr; + DockAreaLayout *m_contentsLayout = nullptr; + DockAreaTitleBar *m_titleBar = nullptr; + DockManager *m_dockManager = nullptr; + AutoHideDockContainer *m_autoHideDockContainer = nullptr; + bool m_updateTitleBarButtons = false; + DockWidgetAreas m_allowedAreas = AllDockAreas; + QSize m_minSizeHint; + DockAreaWidget::DockAreaFlags m_flags{DockAreaWidget::DefaultFlags}; + + /** + * Private data constructor + */ + DockAreaWidgetPrivate(DockAreaWidget *parent); + + /** + * Creates the layout for top area with tabs and close button + */ + void createTitleBar(); + + /** + * Returns the dock widget with the given index + */ + DockWidget *dockWidgetAt(int index) + { + return qobject_cast(m_contentsLayout->widget(index)); + } + + /** + * Convenience function to ease title widget access by index + */ + DockWidgetTab *tabWidgetAt(int index) { return dockWidgetAt(index)->tabWidget(); } + + /** + * Returns the tab action of the given dock widget + */ + QAction *dockWidgetTabAction(DockWidget *dockWidget) const + { + return qvariant_cast(dockWidget->property(g_actionProperty)); + } + + /** + * Returns the index of the given dock widget + */ + int dockWidgetIndex(DockWidget *dockWidget) const + { + return dockWidget->property(g_indexProperty).toInt(); + } + + /** + * Convenience function for tabbar access + */ + DockAreaTabBar *tabBar() const { return m_titleBar->tabBar(); } + + /** + * Updates the enable state of the close and detach button + */ + void updateTitleBarButtonStates(); + + /** + * Updates the visibility of the close and detach button + */ + void updateTitleBarButtonVisibility(bool isTopLevel); + + /** + * Scans all contained dock widgets for the max. minimum size hint + */ + void updateMinimumSizeHint() + { + m_minSizeHint = QSize(); + for (int i = 0; i < m_contentsLayout->count(); ++i) { + auto widget = m_contentsLayout->widget(i); + m_minSizeHint.setHeight( + qMax(m_minSizeHint.height(), widget->minimumSizeHint().height())); + m_minSizeHint.setWidth(qMax(m_minSizeHint.width(), widget->minimumSizeHint().width())); + } + } +}; +// struct DockAreaWidgetPrivate + +DockAreaWidgetPrivate::DockAreaWidgetPrivate(DockAreaWidget *parent) + : q(parent) +{} + +void DockAreaWidgetPrivate::createTitleBar() +{ + m_titleBar = componentsFactory()->createDockAreaTitleBar(q); + m_layout->addWidget(m_titleBar); + QObject::connect(tabBar(), + &DockAreaTabBar::tabCloseRequested, + q, + &DockAreaWidget::onTabCloseRequested); + QObject::connect(m_titleBar, + &DockAreaTitleBar::tabBarClicked, + q, + &DockAreaWidget::setCurrentIndex); + QObject::connect(tabBar(), &DockAreaTabBar::tabMoved, q, &DockAreaWidget::reorderDockWidget); +} + +void DockAreaWidgetPrivate::updateTitleBarButtonStates() +{ + if (q->isHidden()) { + m_updateTitleBarButtons = true; + return; + } + + m_titleBar->button(TitleBarButtonClose) + ->setEnabled(q->features().testFlag(DockWidget::DockWidgetClosable)); + m_titleBar->button(TitleBarButtonUndock) + ->setEnabled(q->features().testFlag(DockWidget::DockWidgetFloatable)); + m_titleBar->button(TitleBarButtonAutoHide) + ->setEnabled(q->features().testFlag(DockWidget::DockWidgetPinnable)); + m_titleBar->updateDockWidgetActionsButtons(); + m_updateTitleBarButtons = false; +} + +void DockAreaWidgetPrivate::updateTitleBarButtonVisibility(bool isTopLevel) +{ + auto *const container = q->dockContainer(); + if (!container) + return; + + if (isTopLevel) { + m_titleBar->button(TitleBarButtonClose)->setVisible(!container->isFloating()); + m_titleBar->button(TitleBarButtonAutoHide)->setVisible(!container->isFloating()); + // Undock and tabs should never show when auto hidden + m_titleBar->button(TitleBarButtonUndock) + ->setVisible(!container->isFloating() && !q->isAutoHide()); + m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(!q->isAutoHide()); + } else { + m_titleBar->button(TitleBarButtonClose)->setVisible(true); + m_titleBar->button(TitleBarButtonAutoHide)->setVisible(true); + m_titleBar->button(TitleBarButtonUndock)->setVisible(!q->isAutoHide()); + m_titleBar->button(TitleBarButtonTabsMenu)->setVisible(!q->isAutoHide()); + } +} + +DockAreaWidget::DockAreaWidget(DockManager *dockManager, DockContainerWidget *parent) + : QFrame(parent) + , d(new DockAreaWidgetPrivate(this)) +{ + d->m_dockManager = dockManager; + d->m_layout = new QBoxLayout(QBoxLayout::TopToBottom); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + + d->createTitleBar(); + d->m_contentsLayout = new DockAreaLayout(d->m_layout); + if (d->m_dockManager) + emit d->m_dockManager->dockAreaCreated(this); +} + +DockAreaWidget::~DockAreaWidget() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + delete d->m_contentsLayout; + delete d; +} + +DockManager *DockAreaWidget::dockManager() const +{ + return d->m_dockManager; +} + +DockContainerWidget *DockAreaWidget::dockContainer() const +{ + return internal::findParent(this); +} + +AutoHideDockContainer *DockAreaWidget::autoHideDockContainer() const +{ + return d->m_autoHideDockContainer; +} + +bool DockAreaWidget::isAutoHide() const +{ + return d->m_autoHideDockContainer != nullptr; +} + +void DockAreaWidget::setAutoHideDockContainer(AutoHideDockContainer *autoHideDockContainer) +{ + d->m_autoHideDockContainer = autoHideDockContainer; + updateAutoHideButtonCheckState(); + updateTitleBarButtonsToolTips(); + d->m_titleBar->button(TitleBarButtonAutoHide)->setShowInTitleBar(true); +} + +void DockAreaWidget::addDockWidget(DockWidget *dockWidget) +{ + insertDockWidget(d->m_contentsLayout->count(), dockWidget); +} + +void DockAreaWidget::insertDockWidget(int index, DockWidget *dockWidget, bool activate) +{ + if (index < 0 || index > d->m_contentsLayout->count()) + index = d->m_contentsLayout->count(); + + d->m_contentsLayout->insertWidget(index, dockWidget); + dockWidget->setDockArea(this); + dockWidget->tabWidget()->setDockAreaWidget(this); + auto tabWidget = dockWidget->tabWidget(); + // Inserting the tab will change the current index which in turn will + // make the tab widget visible in the slot + d->tabBar()->blockSignals(true); + d->tabBar()->insertTab(index, tabWidget); + d->tabBar()->blockSignals(false); + tabWidget->setVisible(!dockWidget->isClosed()); + d->m_titleBar->autoHideTitleLabel()->setText(dockWidget->windowTitle()); + dockWidget->setProperty(g_indexProperty, index); + d->m_minSizeHint.setHeight( + qMax(d->m_minSizeHint.height(), dockWidget->minimumSizeHint().height())); + d->m_minSizeHint.setWidth(qMax(d->m_minSizeHint.width(), dockWidget->minimumSizeHint().width())); + if (activate) { + setCurrentIndex(index); + dockWidget->setClosedState( + false); // Set current index can show the widget without changing the close state, added to keep the close state consistent + } + + // If this dock area is hidden, then we need to make it visible again + // by calling dockWidget->toggleViewInternal(true); + if (!this->isVisible() && d->m_contentsLayout->count() > 1 && !dockManager()->isRestoringState()) + dockWidget->toggleViewInternal(true); + + d->updateTitleBarButtonStates(); + updateTitleBarVisibility(); +} + +void DockAreaWidget::removeDockWidget(DockWidget *dockWidget) +{ + qCInfo(adsLog) << Q_FUNC_INFO; + if (!dockWidget) + return; + + // If this dock area is in a auto hide container, then we can delete + // the auto hide container now + if (isAutoHide()) { + autoHideDockContainer()->cleanupAndDelete(); + return; + } + + auto current = currentDockWidget(); + auto nextOpen = (dockWidget == current) ? nextOpenDockWidget(dockWidget) : nullptr; + + d->m_contentsLayout->removeWidget(dockWidget); + auto tabWidget = dockWidget->tabWidget(); + tabWidget->hide(); + d->tabBar()->removeTab(tabWidget); + tabWidget->setParent(dockWidget); + dockWidget->setDockArea(nullptr); + DockContainerWidget *dockContainerWidget = dockContainer(); + if (nextOpen) { + setCurrentDockWidget(nextOpen); + } else if (d->m_contentsLayout->isEmpty() && dockContainerWidget->dockAreaCount() >= 1) { + qCInfo(adsLog) << "Dock Area empty"; + dockContainerWidget->removeDockArea(this); + this->deleteLater(); + if (dockContainerWidget->dockAreaCount() == 0) { + if (FloatingDockContainer *floatingDockContainer + = dockContainerWidget->floatingWidget()) { + floatingDockContainer->hide(); + floatingDockContainer->deleteLater(); + } + } + } else if (dockWidget == current) { + // if contents layout is not empty but there are no more open dock + // widgets, then we need to hide the dock area because it does not + // contain any visible content + hideAreaWithNoVisibleContent(); + } + + d->updateTitleBarButtonStates(); + updateTitleBarVisibility(); + d->updateMinimumSizeHint(); + auto topLevelDockWidget = dockContainerWidget->topLevelDockWidget(); + if (topLevelDockWidget) + topLevelDockWidget->emitTopLevelChanged(true); #if (ADS_DEBUG_LEVEL > 0) - dockContainerWidget->dumpLayout(); + dockContainerWidget->dumpLayout(); #endif +} + +void DockAreaWidget::hideAreaWithNoVisibleContent() +{ + this->toggleView(false); + + // Hide empty parent splitters + auto splitter = internal::findParent(this); + internal::hideEmptyParentSplitters(splitter); + + //Hide empty floating widget + DockContainerWidget *container = this->dockContainer(); + if (!container->isFloating() + && DockManager::testConfigFlag(DockManager::HideSingleCentralWidgetTitleBar)) + return; + + updateTitleBarVisibility(); + auto topLevelWidget = container->topLevelDockWidget(); + auto floatingWidget = container->floatingWidget(); + if (topLevelWidget) { + if (floatingWidget) + floatingWidget->updateWindowTitle(); + + DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); + } else if (container->openedDockAreas().isEmpty() && floatingWidget) { + floatingWidget->hide(); } - void DockAreaWidget::hideAreaWithNoVisibleContent() - { - this->toggleView(false); + if (isAutoHide()) + autoHideDockContainer()->hide(); +} - // Hide empty parent splitters - auto splitter = internal::findParent(this); - internal::hideEmptyParentSplitters(splitter); +void DockAreaWidget::onTabCloseRequested(int index) +{ + qCInfo(adsLog) << Q_FUNC_INFO << "index" << index; + dockWidget(index)->requestCloseDockWidget(); +} - //Hide empty floating widget - DockContainerWidget *container = this->dockContainer(); - if (!container->isFloating() - && DockManager::testConfigFlag(DockManager::HideSingleCentralWidgetTitleBar)) - return; +DockWidget *DockAreaWidget::currentDockWidget() const +{ + int currentIdx = currentIndex(); + if (currentIdx < 0) + return nullptr; - updateTitleBarVisibility(); - auto topLevelWidget = container->topLevelDockWidget(); - auto floatingWidget = container->floatingWidget(); - if (topLevelWidget) { - if (floatingWidget) - floatingWidget->updateWindowTitle(); + return dockWidget(currentIdx); +} - DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); - } else if (container->openedDockAreas().isEmpty() && floatingWidget) { - floatingWidget->hide(); - } +void DockAreaWidget::setCurrentDockWidget(DockWidget *dockWidget) +{ + if (dockManager()->isRestoringState()) + return; + + internalSetCurrentDockWidget(dockWidget); +} + +void DockAreaWidget::internalSetCurrentDockWidget(DockWidget *dockWidget) +{ + int index = indexOf(dockWidget); + if (index < 0) + return; + + setCurrentIndex(index); + dockWidget->setClosedState( + false); // Set current index can show the widget without changing the close state, added to keep the close state consistent +} + +void DockAreaWidget::setCurrentIndex(int index) +{ + auto currentTabBar = d->tabBar(); + if (index < 0 || index > (currentTabBar->count() - 1)) { + qWarning() << Q_FUNC_INFO << "Invalid index" << index; + return; } - void DockAreaWidget::onTabCloseRequested(int index) - { - qCInfo(adsLog) << Q_FUNC_INFO << "index" << index; - auto *currentDockWidget = dockWidget(index); - if (currentDockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose)) - currentDockWidget->closeDockWidgetInternal(); - else - currentDockWidget->toggleView(false); + auto cw = d->m_contentsLayout->currentWidget(); + auto nw = d->m_contentsLayout->widget(index); + if (cw == nw && !nw->isHidden()) + return; + + emit currentChanging(index); + currentTabBar->setCurrentIndex(index); + d->m_contentsLayout->setCurrentIndex(index); + d->m_contentsLayout->currentWidget()->show(); + emit currentChanged(index); +} + +int DockAreaWidget::currentIndex() const +{ + return d->m_contentsLayout->currentIndex(); +} + +QRect DockAreaWidget::titleBarGeometry() const +{ + return d->m_titleBar->geometry(); +} + +QRect DockAreaWidget::contentAreaGeometry() const +{ + return d->m_contentsLayout->geometry(); +} + +int DockAreaWidget::indexOf(DockWidget *dockWidget) +{ + return d->m_contentsLayout->indexOf(dockWidget); +} + +QList DockAreaWidget::dockWidgets() const +{ + QList dockWidgetList; + for (int i = 0; i < d->m_contentsLayout->count(); ++i) + dockWidgetList.append(dockWidget(i)); + + return dockWidgetList; +} + +int DockAreaWidget::openDockWidgetsCount() const +{ + int count = 0; + for (int i = 0; i < d->m_contentsLayout->count(); ++i) { + if (!dockWidget(i)->isClosed()) + ++count; } + return count; +} - DockWidget *DockAreaWidget::currentDockWidget() const - { - int currentIdx = currentIndex(); - if (currentIdx < 0) - return nullptr; - - return dockWidget(currentIdx); - } - - void DockAreaWidget::setCurrentDockWidget(DockWidget *dockWidget) - { - if (dockManager()->isRestoringState()) - return; - - internalSetCurrentDockWidget(dockWidget); - } - - void DockAreaWidget::internalSetCurrentDockWidget(DockWidget *dockWidget) - { - int index = indexOf(dockWidget); - if (index < 0) - return; - - setCurrentIndex(index); - } - - void DockAreaWidget::setCurrentIndex(int index) - { - auto currentTabBar = d->tabBar(); - if (index < 0 || index > (currentTabBar->count() - 1)) { - qWarning() << Q_FUNC_INFO << "Invalid index" << index; - return; - } - - auto cw = d->m_contentsLayout->currentWidget(); - auto nw = d->m_contentsLayout->widget(index); - if (cw == nw && !nw->isHidden()) - return; - - emit currentChanging(index); - currentTabBar->setCurrentIndex(index); - d->m_contentsLayout->setCurrentIndex(index); - d->m_contentsLayout->currentWidget()->show(); - emit currentChanged(index); - } - - int DockAreaWidget::currentIndex() const { return d->m_contentsLayout->currentIndex(); } - - QRect DockAreaWidget::titleBarGeometry() const { return d->m_titleBar->geometry(); } - - QRect DockAreaWidget::contentAreaGeometry() const { return d->m_contentsLayout->geometry(); } - - int DockAreaWidget::indexOf(DockWidget *dockWidget) - { - return d->m_contentsLayout->indexOf(dockWidget); - } - - QList DockAreaWidget::dockWidgets() const - { - QList dockWidgetList; - for (int i = 0; i < d->m_contentsLayout->count(); ++i) +QList DockAreaWidget::openedDockWidgets() const +{ + QList dockWidgetList; + for (int i = 0; i < d->m_contentsLayout->count(); ++i) { + DockWidget *currentDockWidget = dockWidget(i); + if (!currentDockWidget->isClosed()) dockWidgetList.append(dockWidget(i)); + } + return dockWidgetList; +} - return dockWidgetList; +int DockAreaWidget::indexOfFirstOpenDockWidget() const +{ + for (int i = 0; i < d->m_contentsLayout->count(); ++i) { + if (!dockWidget(i)->isClosed()) + return i; } - int DockAreaWidget::openDockWidgetsCount() const - { - int count = 0; - for (int i = 0; i < d->m_contentsLayout->count(); ++i) { - if (!dockWidget(i)->isClosed()) - ++count; - } - return count; + return -1; +} + +int DockAreaWidget::dockWidgetsCount() const +{ + return d->m_contentsLayout->count(); +} + +DockWidget *DockAreaWidget::dockWidget(int index) const +{ + return qobject_cast(d->m_contentsLayout->widget(index)); +} + +void DockAreaWidget::reorderDockWidget(int fromIndex, int toIndex) +{ + qCInfo(adsLog) << Q_FUNC_INFO; + if (fromIndex >= d->m_contentsLayout->count() || fromIndex < 0 + || toIndex >= d->m_contentsLayout->count() || toIndex < 0 || fromIndex == toIndex) { + qCInfo(adsLog) << "Invalid index for tab movement" << fromIndex << toIndex; + return; } - QList DockAreaWidget::openedDockWidgets() const - { - QList dockWidgetList; - for (int i = 0; i < d->m_contentsLayout->count(); ++i) { - DockWidget *currentDockWidget = dockWidget(i); - if (!currentDockWidget->isClosed()) - dockWidgetList.append(dockWidget(i)); - } - return dockWidgetList; + auto widget = d->m_contentsLayout->widget(fromIndex); + d->m_contentsLayout->removeWidget(widget); + d->m_contentsLayout->insertWidget(toIndex, widget); + setCurrentIndex(toIndex); +} + +void DockAreaWidget::toggleDockWidgetView(DockWidget *dockWidget, bool open) +{ + Q_UNUSED(dockWidget) + Q_UNUSED(open) + updateTitleBarVisibility(); +} + +void DockAreaWidget::updateTitleBarVisibility() +{ + DockContainerWidget *container = dockContainer(); + if (!container) + return; + + if (!d->m_titleBar) + return; + + bool autoHide = isAutoHide(); + + if (!DockManager::testConfigFlag(DockManager::AlwaysShowTabs)) { + bool hidden = container->hasTopLevelDockWidget() + && (container->isFloating() + || DockManager::testConfigFlag( + DockManager::HideSingleCentralWidgetTitleBar)); + hidden |= (d->m_flags.testFlag(HideSingleWidgetTitleBar) && openDockWidgetsCount() == 1); + hidden &= !autoHide; // Titlebar must always be visible when auto hidden so it can be dragged + d->m_titleBar->setVisible(!hidden); } - int DockAreaWidget::indexOfFirstOpenDockWidget() const - { - for (int i = 0; i < d->m_contentsLayout->count(); ++i) { - if (!dockWidget(i)->isClosed()) - return i; + if (isAutoHideFeatureEnabled()) { + auto tabBar = d->m_titleBar->tabBar(); + tabBar->setVisible(!autoHide); // Never show tab bar when auto hidden + d->m_titleBar->autoHideTitleLabel()->setVisible( + autoHide); // Always show when auto hidden, never otherwise + updateTitleBarButtonVisibility(container->topLevelDockArea() == this); + } +} + +void DockAreaWidget::markTitleBarMenuOutdated() +{ + if (d->m_titleBar) + d->m_titleBar->markTabsMenuOutdated(); +} + +void DockAreaWidget::updateAutoHideButtonCheckState() +{ + auto autoHideButton = titleBarButton(TitleBarButtonAutoHide); + autoHideButton->blockSignals(true); + autoHideButton->setChecked(isAutoHide()); + autoHideButton->blockSignals(false); +} + +void DockAreaWidget::updateTitleBarButtonVisibility(bool isTopLevel) const +{ + d->updateTitleBarButtonVisibility(isTopLevel); +} + +void DockAreaWidget::updateTitleBarButtonsToolTips() +{ + internal::setToolTip(titleBarButton(TitleBarButtonClose), + titleBar()->titleBarButtonToolTip(TitleBarButtonClose)); + internal::setToolTip(titleBarButton(TitleBarButtonAutoHide), + titleBar()->titleBarButtonToolTip(TitleBarButtonAutoHide)); +} + +void DockAreaWidget::saveState(QXmlStreamWriter &stream) const +{ + stream.writeStartElement("area"); + stream.writeAttribute("tabs", QString::number(d->m_contentsLayout->count())); + auto localDockWidget = currentDockWidget(); + QString name = localDockWidget ? localDockWidget->objectName() : ""; + stream.writeAttribute("current", name); + + if (d->m_allowedAreas != DefaultAllowedAreas) + stream.writeAttribute("allowedAreas", QString::number(d->m_allowedAreas, 16)); + + if (d->m_flags != DefaultFlags) + stream.writeAttribute("flags", QString::number(d->m_flags, 16)); + + qCInfo(adsLog) << Q_FUNC_INFO << "TabCount: " << d->m_contentsLayout->count() + << " Current: " << name; + for (int i = 0; i < d->m_contentsLayout->count(); ++i) + dockWidget(i)->saveState(stream); + + stream.writeEndElement(); +} + +bool DockAreaWidget::restoreState(DockingStateReader &stateReader, + DockAreaWidget *&createdWidget, + bool testing, + DockContainerWidget *container) +{ + QString currentDockWidget = stateReader.attributes().value("current").toString(); + +#ifdef ADS_DEBUG_PRINT + bool ok; + int tabs = stateReader.attributes().value("tabs").toInt(&ok); + if (!ok) + return false; + + qCInfo(adsLog) << "Restore NodeDockArea Tabs: " << tabs << " Current: " << currentDockWidget; +#endif + + auto dockManager = container->dockManager(); + DockAreaWidget *dockArea = nullptr; + if (!testing) { + dockArea = new DockAreaWidget(dockManager, container); + const auto allowedAreasAttribute = stateReader.attributes().value("allowedAreas"); + if (!allowedAreasAttribute.isEmpty()) + dockArea->setAllowedAreas((DockWidgetArea) allowedAreasAttribute.toInt(nullptr, 16)); + + const auto flagsAttribute = stateReader.attributes().value("flags"); + if (!flagsAttribute.isEmpty()) + dockArea->setDockAreaFlags( + (DockAreaWidget::DockAreaFlags) flagsAttribute.toInt(nullptr, 16)); + } + + while (stateReader.readNextStartElement()) { + if (stateReader.name() != QLatin1String("widget")) + continue; + + auto objectName = stateReader.attributes().value("name"); + + if (objectName.isEmpty()) { + qCInfo(adsLog) << "Error: Empty name!"; + return false; } - return - 1; + QVariant closedVar = QVariant(stateReader.attributes().value("closed").toString()); + if (!closedVar.canConvert()) + return false; + + bool closed = closedVar.value(); + + stateReader.skipCurrentElement(); + DockWidget *dockWidget = dockManager->findDockWidget(objectName.toString()); + + if (!dockWidget || testing) + continue; + + qCInfo(adsLog) << "Dock Widget found" << dockWidget->objectName() << "parent" + << dockWidget->parent(); + + if (dockWidget->autoHideDockContainer()) + dockWidget->autoHideDockContainer()->cleanupAndDelete(); + + // We hide the DockArea here to prevent the short display (the flashing) + // of the dock areas during application startup + dockArea->hide(); + dockArea->addDockWidget(dockWidget); + dockWidget->setToggleViewActionChecked(!closed); + dockWidget->setClosedState(closed); + dockWidget->setProperty(internal::g_closedProperty, closed); + dockWidget->setProperty(internal::g_dirtyProperty, false); } - int DockAreaWidget::dockWidgetsCount() const { return d->m_contentsLayout->count(); } + if (testing) + return true; - DockWidget *DockAreaWidget::dockWidget(int index) const - { - return qobject_cast(d->m_contentsLayout->widget(index)); + if (!dockArea->dockWidgetsCount()) { + delete dockArea; + dockArea = nullptr; + } else { + dockArea->setProperty("currentDockWidget", currentDockWidget); } - void DockAreaWidget::reorderDockWidget(int fromIndex, int toIndex) - { - qCInfo(adsLog) << Q_FUNC_INFO; - if (fromIndex >= d->m_contentsLayout->count() || fromIndex < 0 - || toIndex >= d->m_contentsLayout->count() || toIndex < 0 || fromIndex == toIndex) { - qCInfo(adsLog) << "Invalid index for tab movement" << fromIndex << toIndex; - return; - } + createdWidget = dockArea; + return true; +} - auto widget = d->m_contentsLayout->widget(fromIndex); - d->m_contentsLayout->removeWidget(widget); - d->m_contentsLayout->insertWidget(toIndex, widget); - setCurrentIndex(toIndex); - } - - void DockAreaWidget::toggleDockWidgetView(DockWidget *dockWidget, bool open) - { - Q_UNUSED(dockWidget) - Q_UNUSED(open) - updateTitleBarVisibility(); - } - - void DockAreaWidget::updateTitleBarVisibility() - { - DockContainerWidget *container = dockContainer(); - if (!container) - return; - - if (DockManager::testConfigFlag(DockManager::AlwaysShowTabs)) - return; - - if (d->m_titleBar) { - bool hidden = container->hasTopLevelDockWidget() && (container->isFloating() - || DockManager::testConfigFlag(DockManager::HideSingleCentralWidgetTitleBar)); - d->m_titleBar->setVisible(!hidden); - } - } - - void DockAreaWidget::markTitleBarMenuOutdated() - { - if (d->m_titleBar) - d->m_titleBar->markTabsMenuOutdated(); - } - - void DockAreaWidget::saveState(QXmlStreamWriter &stream) const - { - stream.writeStartElement("area"); - stream.writeAttribute("tabs", QString::number(d->m_contentsLayout->count())); - auto localDockWidget = currentDockWidget(); - QString name = localDockWidget ? localDockWidget->objectName() : ""; - stream.writeAttribute("current", name); - qCInfo(adsLog) << Q_FUNC_INFO << "TabCount: " << d->m_contentsLayout->count() - << " Current: " << name; - for (int i = 0; i < d->m_contentsLayout->count(); ++i) - dockWidget(i)->saveState(stream); - - stream.writeEndElement(); - } - - DockWidget *DockAreaWidget::nextOpenDockWidget(DockWidget *dockWidget) const - { - auto openDockWidgets = openedDockWidgets(); - if (openDockWidgets.count() > 1 - || (openDockWidgets.count() == 1 && openDockWidgets[0] != dockWidget)) { - DockWidget *nextDockWidget; - if (openDockWidgets.last() == dockWidget) { - nextDockWidget = openDockWidgets[openDockWidgets.count() - 2]; - } else { - int nextIndex = openDockWidgets.indexOf(dockWidget) + 1; - nextDockWidget = openDockWidgets[nextIndex]; +DockWidget *DockAreaWidget::nextOpenDockWidget(DockWidget *dockWidget) const +{ + auto openDockWidgets = openedDockWidgets(); + if (openDockWidgets.count() > 1 + || (openDockWidgets.count() == 1 && openDockWidgets[0] != dockWidget)) { + if (openDockWidgets.last() == dockWidget) { + DockWidget *nextDockWidget = openDockWidgets[openDockWidgets.count() - 2]; + // search backwards for widget with tab + for (int i = openDockWidgets.count() - 2; i >= 0; --i) { + auto dw = openDockWidgets[i]; + if (!dw->features().testFlag(DockWidget::NoTab)) + return dw; } + // return widget without tab return nextDockWidget; } else { - return nullptr; + int indexOfDockWidget = openDockWidgets.indexOf(dockWidget); + DockWidget *nextDockWidget = openDockWidgets[indexOfDockWidget + 1]; + // search forwards for widget with tab + for (int i = indexOfDockWidget + 1; i < openDockWidgets.count(); ++i) { + auto dw = openDockWidgets[i]; + if (!dw->features().testFlag(DockWidget::NoTab)) + return dw; + } + + // search backwards for widget with tab + for (int i = indexOfDockWidget - 1; i >= 0; --i) { + auto dw = openDockWidgets[i]; + if (!dw->features().testFlag(DockWidget::NoTab)) + return dw; + } + + // return widget without tab + return nextDockWidget; } + } else { + return nullptr; } +} - DockWidget::DockWidgetFeatures DockAreaWidget::features(eBitwiseOperator mode) const - { - if (BitwiseAnd == mode) { - DockWidget::DockWidgetFeatures features(DockWidget::AllDockWidgetFeatures); - for (const auto dockWidget : dockWidgets()) - features &= dockWidget->features(); +DockWidget::DockWidgetFeatures DockAreaWidget::features(eBitwiseOperator mode) const +{ + if (BitwiseAnd == mode) { + DockWidget::DockWidgetFeatures features(DockWidget::AllDockWidgetFeatures); + for (const auto dockWidget : dockWidgets()) + features &= dockWidget->features(); - return features; - } else { - DockWidget::DockWidgetFeatures features(DockWidget::NoDockWidgetFeatures); - for (const auto dockWidget : dockWidgets()) - features |= dockWidget->features(); + return features; + } else { + DockWidget::DockWidgetFeatures features(DockWidget::NoDockWidgetFeatures); + for (const auto dockWidget : dockWidgets()) + features |= dockWidget->features(); - return features; - } + return features; } +} - void DockAreaWidget::toggleView(bool open) - { - setVisible(open); +void DockAreaWidget::toggleView(bool open) +{ + setVisible(open); - emit viewToggled(open); - } + emit viewToggled(open); +} - void DockAreaWidget::setVisible(bool visible) - { - Super::setVisible(visible); - if (d->m_updateTitleBarButtons) - d->updateTitleBarButtonStates(); - } +void DockAreaWidget::setVisible(bool visible) +{ + Super::setVisible(visible); + if (d->m_updateTitleBarButtons) + d->updateTitleBarButtonStates(); +} - void DockAreaWidget::setAllowedAreas(DockWidgetAreas areas) - { - d->m_allowedAreas = areas; - } +void DockAreaWidget::setAllowedAreas(DockWidgetAreas areas) +{ + d->m_allowedAreas = areas; +} - DockWidgetAreas DockAreaWidget::allowedAreas() const - { - return d->m_allowedAreas; - } +DockWidgetAreas DockAreaWidget::allowedAreas() const +{ + return d->m_allowedAreas; +} - QAbstractButton *DockAreaWidget::titleBarButton(eTitleBarButton which) const - { - return d->m_titleBar->button(which); - } +DockAreaWidget::DockAreaFlags DockAreaWidget::dockAreaFlags() const +{ + return d->m_flags; +} - void DockAreaWidget::closeArea() - { - // If there is only one single dock widget and this widget has the - // DeleteOnClose feature, then we delete the dock widget now - auto openDockWidgets = openedDockWidgets(); - if (openDockWidgets.count() == 1 - && openDockWidgets[0]->features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { - openDockWidgets[0]->closeDockWidgetInternal(); - } else { - for (auto dockWidget : openedDockWidgets()) +void DockAreaWidget::setDockAreaFlags(DockAreaFlags flags) +{ + auto changedFlags = d->m_flags ^ flags; + d->m_flags = flags; + if (changedFlags.testFlag(HideSingleWidgetTitleBar)) + updateTitleBarVisibility(); +} + +void DockAreaWidget::setDockAreaFlag(eDockAreaFlag flag, bool on) +{ + auto flags = dockAreaFlags(); + internal::setFlag(flags, flag, on); + setDockAreaFlags(flags); +} + +QAbstractButton *DockAreaWidget::titleBarButton(eTitleBarButton which) const +{ + return d->m_titleBar->button(which); +} + +void DockAreaWidget::closeArea() +{ + // If there is only one single dock widget and this widget has the + // DeleteOnClose feature or CustomCloseHandling, then we delete the dock widget now; + // in the case of CustomCloseHandling, the CDockWidget class will emit its + // closeRequested signal and not actually delete unless the signal is handled in a way that deletes it + auto openDockWidgets = openedDockWidgets(); + if (openDockWidgets.count() == 1 + && (openDockWidgets[0]->features().testFlag(DockWidget::DockWidgetDeleteOnClose) + || openDockWidgets[0]->features().testFlag(DockWidget::CustomCloseHandling)) + && !isAutoHide()) { + openDockWidgets[0]->closeDockWidgetInternal(); + } else { + for (auto dockWidget : openedDockWidgets()) { + if ((dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) + && dockWidget->features().testFlag(DockWidget::DockWidgetForceCloseWithArea)) + || dockWidget->features().testFlag(DockWidget::CustomCloseHandling)) { + dockWidget->closeDockWidgetInternal(); + } else if (dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) + && isAutoHide()) { + dockWidget->closeDockWidgetInternal(); + } else { dockWidget->toggleView(false); + } } } +} - void DockAreaWidget::closeOtherAreas() { dockContainer()->closeOtherAreas(this); } +enum eBorderLocation { + BorderNone = 0, + BorderLeft = 0x01, + BorderRight = 0x02, + BorderTop = 0x04, + BorderBottom = 0x08, + BorderVertical = BorderLeft | BorderRight, + BorderHorizontal = BorderTop | BorderBottom, + BorderTopLeft = BorderTop | BorderLeft, + BorderTopRight = BorderTop | BorderRight, + BorderBottomLeft = BorderBottom | BorderLeft, + BorderBottomRight = BorderBottom | BorderRight, + BorderVerticalBottom = BorderVertical | BorderBottom, + BorderVerticalTop = BorderVertical | BorderTop, + BorderHorizontalLeft = BorderHorizontal | BorderLeft, + BorderHorizontalRight = BorderHorizontal | BorderRight, + BorderAll = BorderVertical | BorderHorizontal +}; - DockAreaTitleBar *DockAreaWidget::titleBar() const { return d->m_titleBar; } +SideBarLocation DockAreaWidget::calculateSideTabBarArea() const +{ + auto container = dockContainer(); + auto contentRect = container->contentRect(); - QSize DockAreaWidget::minimumSizeHint() const - { - return d->m_minSizeHint.isValid() ? d->m_minSizeHint : Super::minimumSizeHint(); + int borders = BorderNone; // contains all borders that are touched by the dock ware + auto dockAreaTopLeft = mapTo(container, rect().topLeft()); + auto dockAreaRect = rect(); + dockAreaRect.moveTo(dockAreaTopLeft); + const qreal aspectRatio = dockAreaRect.width() / (qMax(1, dockAreaRect.height()) * 1.0); + const qreal sizeRatio = (qreal) contentRect.width() / dockAreaRect.width(); + static const int minBorderDistance = 16; + bool horizontalOrientation = (aspectRatio > 1.0) && (sizeRatio < 3.0); + + // measure border distances - a distance less than 16 px means we touch the border + int borderDistance[4]; + + int distance = qAbs(contentRect.topLeft().y() - dockAreaRect.topLeft().y()); + borderDistance[SideBarLocation::SideBarTop] = (distance < minBorderDistance) ? 0 : distance; + if (!borderDistance[SideBarLocation::SideBarTop]) { + borders |= BorderTop; } + distance = qAbs(contentRect.bottomRight().y() - dockAreaRect.bottomRight().y()); + borderDistance[SideBarLocation::SideBarBottom] = (distance < minBorderDistance) ? 0 : distance; + if (!borderDistance[SideBarLocation::SideBarBottom]) { + borders |= BorderBottom; + } + + distance = qAbs(contentRect.topLeft().x() - dockAreaRect.topLeft().x()); + borderDistance[SideBarLocation::SideBarLeft] = (distance < minBorderDistance) ? 0 : distance; + if (!borderDistance[SideBarLocation::SideBarLeft]) { + borders |= BorderLeft; + } + + distance = qAbs(contentRect.bottomRight().x() - dockAreaRect.bottomRight().x()); + borderDistance[SideBarLocation::SideBarRight] = (distance < minBorderDistance) ? 0 : distance; + if (!borderDistance[SideBarLocation::SideBarRight]) { + borders |= BorderRight; + } + + auto sideTab = SideBarLocation::SideBarRight; + switch (borders) { + // 1. It's touching all borders + case BorderAll: + sideTab = horizontalOrientation ? SideBarLocation::SideBarBottom + : SideBarLocation::SideBarRight; + break; + + // 2. It's touching 3 borders + case BorderVerticalBottom: + sideTab = SideBarLocation::SideBarBottom; + break; + case BorderVerticalTop: + sideTab = SideBarLocation::SideBarTop; + break; + case BorderHorizontalLeft: + sideTab = SideBarLocation::SideBarLeft; + break; + case BorderHorizontalRight: + sideTab = SideBarLocation::SideBarRight; + break; + + // 3. Its touching horizontal or vertical borders + case BorderVertical: + sideTab = SideBarLocation::SideBarBottom; + break; + case BorderHorizontal: + sideTab = SideBarLocation::SideBarRight; + break; + + // 4. Its in a corner + case BorderTopLeft: + sideTab = horizontalOrientation ? SideBarLocation::SideBarTop + : SideBarLocation::SideBarLeft; + break; + case BorderTopRight: + sideTab = horizontalOrientation ? SideBarLocation::SideBarTop + : SideBarLocation::SideBarRight; + break; + case BorderBottomLeft: + sideTab = horizontalOrientation ? SideBarLocation::SideBarBottom + : SideBarLocation::SideBarLeft; + break; + case BorderBottomRight: + sideTab = horizontalOrientation ? SideBarLocation::SideBarBottom + : SideBarLocation::SideBarRight; + break; + + // 5 Ists touching only one border + case BorderLeft: + sideTab = SideBarLocation::SideBarLeft; + break; + case BorderRight: + sideTab = SideBarLocation::SideBarRight; + break; + case BorderTop: + sideTab = SideBarLocation::SideBarTop; + break; + case BorderBottom: + sideTab = SideBarLocation::SideBarBottom; + break; + } + + return sideTab; +} + +void DockAreaWidget::setAutoHide(bool enable, SideBarLocation location, int tabIndex) +{ + if (!isAutoHideFeatureEnabled()) + return; + + if (!enable) { + if (isAutoHide()) + d->m_autoHideDockContainer->moveContentsToParent(); + + return; + } + + // If this is already an auto hide container, then move it to new location. + if (isAutoHide()) { + d->m_autoHideDockContainer->moveToNewSideBarLocation(location, tabIndex); + return; + } + + auto area = (SideBarNone == location) ? calculateSideTabBarArea() : location; + for (const auto dockWidget : openedDockWidgets()) { + if (enable == isAutoHide()) + continue; + + if (!dockWidget->features().testFlag(DockWidget::DockWidgetPinnable)) + continue; + + dockContainer()->createAndSetupAutoHideContainer(area, dockWidget, tabIndex++); + } +} + +void DockAreaWidget::toggleAutoHide(SideBarLocation location) +{ + if (!isAutoHideFeatureEnabled()) + return; + + setAutoHide(!isAutoHide(), location); +} + +void DockAreaWidget::closeOtherAreas() +{ + dockContainer()->closeOtherAreas(this); +} + +void DockAreaWidget::setFloating() +{ + d->m_titleBar->setAreaFloating(); +} + +DockAreaTitleBar *DockAreaWidget::titleBar() const +{ + return d->m_titleBar; +} + +bool DockAreaWidget::isCentralWidgetArea() const +{ + if (dockWidgetsCount() != 1) + return false; + + return dockManager()->centralWidget() == dockWidgets().constFirst(); +} + +bool DockAreaWidget::containsCentralWidget() const +{ + auto centralWidget = dockManager()->centralWidget(); + for (const auto &dockWidget : dockWidgets()) { + if (dockWidget == centralWidget) + return true; + } + + return false; +} + +QSize DockAreaWidget::minimumSizeHint() const +{ + if (!d->m_minSizeHint.isValid()) + return Super::minimumSizeHint(); + + if (d->m_titleBar->isVisible()) + return d->m_minSizeHint + QSize(0, d->m_titleBar->minimumSizeHint().height()); + else + return d->m_minSizeHint; +} + +void DockAreaWidget::onDockWidgetFeaturesChanged() +{ + if (d->m_titleBar) + d->updateTitleBarButtonStates(); +} + +bool DockAreaWidget::isTopLevelArea() const +{ + auto container = dockContainer(); + if (!container) + return false; + + return (container->topLevelDockArea() == this); +} + +#ifdef Q_OS_WIN +bool DockAreaWidget::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::PlatformSurface: + return true; + default: + break; + } + + return Super::event(e); +} +#endif + } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareawidget.h b/src/libs/advanceddockingsystem/dockareawidget.h index a0482136bf5..59f8b9ee207 100644 --- a/src/libs/advanceddockingsystem/dockareawidget.h +++ b/src/libs/advanceddockingsystem/dockareawidget.h @@ -4,6 +4,7 @@ #pragma once #include "ads_globals.h" +#include "autohidetab.h" #include "dockwidget.h" #include @@ -20,6 +21,8 @@ class DockManager; class DockContainerWidget; class DockContainerWidgetPrivate; class DockAreaTitleBar; +class DockingStateReader; +class AutoHideDockContainer; /** * DockAreaWidget manages multiple instances of DockWidgets. @@ -39,6 +42,9 @@ private: friend class DockWidget; friend class DockManagerPrivate; friend class DockManager; + friend class AutoHideDockContainer; + + void onDockWidgetFeaturesChanged(); void onTabCloseRequested(int index); @@ -48,7 +54,34 @@ private: */ void reorderDockWidget(int fromIndex, int toIndex); + /* + * Update the auto hide button checked state based on if it's contained in an auto hide container or not + */ + void updateAutoHideButtonCheckState(); + + /* + * Update the title bar button tooltips + */ + void updateTitleBarButtonsToolTips(); + + /** + * Calculate the auto hide side bar location depending on the dock area + * widget position in the container + */ + SideBarLocation calculateSideTabBarArea() const; + protected: + +#ifdef Q_OS_WIN + /** + * Reimplements QWidget::event to handle QEvent::PlatformSurface + * This is here to fix issue #294 Tab refresh problem with a QGLWidget + * that exists since Qt version 5.12.7. So this function is here to + * work around a Qt issue. + */ + virtual bool event(QEvent *event) override; +#endif + /** * Inserts a dock widget into dock area. * All dockwidgets in the dock area tabified in a stacked layout with tabs. @@ -110,11 +143,26 @@ protected: */ void markTitleBarMenuOutdated(); + /* + * Update the title bar button visibility based on if it's top level or not + */ + void updateTitleBarButtonVisibility(bool isTopLevel) const; + void toggleView(bool open); public: using Super = QFrame; + /** + * Dock area related flags + */ + enum eDockAreaFlag + { + HideSingleWidgetTitleBar = 0x0001, + DefaultFlags = 0x0000 + }; + Q_DECLARE_FLAGS(DockAreaFlags, eDockAreaFlag) + /** * Default Constructor */ @@ -135,6 +183,27 @@ public: * if there is no */ DockContainerWidget *dockContainer() const; + /** + * Returns the auto hide dock container widget this dock area widget belongs to or 0 + * if there is no + */ + AutoHideDockContainer *autoHideDockContainer() const; + + /** + * Returns true if the dock area is in an auto hide container + */ + bool isAutoHide() const; + + /** + * Sets the current auto hide dock container + */ + void setAutoHideDockContainer(AutoHideDockContainer *autoHideDockContainer); + + /** + * Returns the largest minimumSizeHint() of the dock widgets in this area. + * The minimum size hint is updated if a dock widget is removed or added. + */ + virtual QSize minimumSizeHint() const override; /** * Returns the rectangle of the title area @@ -205,6 +274,12 @@ public: */ void saveState(QXmlStreamWriter &stream) const; + /** + * Restores a dock area. + * \see restoreChildNodes() for details + */ + static bool restoreState(DockingStateReader& stream, DockAreaWidget*& createdWidget, bool testing, DockContainerWidget* parentContainer); + /** * This functions returns the dock widget features of all dock widget in * this area. @@ -242,6 +317,41 @@ public: */ DockAreaTitleBar *titleBar() const; + /** + * Returns the dock area flags - a combination of flags that configure the + * appearance and features of the dock area. + * \see setDockAreaFlasg() + */ + DockAreaFlags dockAreaFlags() const; + + /** + * Sets the dock area flags - a combination of flags that configure the + * appearance and features of the dock area + */ + void setDockAreaFlags(DockAreaFlags flags); + + /** + * Sets the dock area flag Flag on this widget if on is true; otherwise + * clears the flag. + */ + void setDockAreaFlag(eDockAreaFlag flag, bool on); + + /** + * Returns true if the area has a single dock widget and contains the central widget of it's manager. + */ + bool isCentralWidgetArea() const; + + /** + * Returns true if the area contains the central widget of it's manager. + */ + bool containsCentralWidget() const; + + /** + * If this dock area is the one and only visible area in a container, then + * this function returns true + */ + bool isTopLevelArea() const; + /** * This activates the tab for the given tab index. * If the dock widget for the given tab is not visible, the this function @@ -254,17 +364,28 @@ public: */ void closeArea(); + /** + * Sets the dock area into auto hide mode or into normal mode. + * If the dock area is switched to auto hide mode, then all dock widgets + * that are pinable will be added to the sidebar + */ + void setAutoHide(bool enable, SideBarLocation location = SideBarNone, int tabIndex = -1); + + /** + * Switches the dock area to auto hide mode or vice versa depending on its + * current state. + */ + void toggleAutoHide(SideBarLocation location = SideBarNone); + /** * This function closes all other areas except of this area */ void closeOtherAreas(); /** - * Returns the largest minimumSizeHint() of the dock widgets in this - * area. - * The minimum size hint is updated if a dock widget is removed or added. + * Moves the dock area into its own floating widget if the area DockWidgetFloatable flag is true. */ - QSize minimumSizeHint() const override; + void setFloating(); signals: /** diff --git a/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp b/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp index 76afb45d2c9..cf975b8b2cd 100644 --- a/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp +++ b/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp @@ -3,46 +3,52 @@ #include "dockcomponentsfactory.h" -#include "dockwidgettab.h" +#include "autohidetab.h" #include "dockareatabbar.h" #include "dockareatitlebar.h" -#include "dockwidget.h" #include "dockareawidget.h" +#include "dockwidget.h" +#include "dockwidgettab.h" #include -namespace ADS +namespace ADS { + +static std::unique_ptr g_defaultFactory(new DockComponentsFactory()); + +DockWidgetTab *DockComponentsFactory::createDockWidgetTab(DockWidget *dockWidget) const { - static std::unique_ptr g_defaultFactory(new DockComponentsFactory()); + return new DockWidgetTab(dockWidget); +} - DockWidgetTab *DockComponentsFactory::createDockWidgetTab(DockWidget *dockWidget) const - { - return new DockWidgetTab(dockWidget); - } +AutoHideTab *DockComponentsFactory::createDockWidgetSideTab(DockWidget *dockWidget) const +{ + return new AutoHideTab(dockWidget); +} - DockAreaTabBar *DockComponentsFactory::createDockAreaTabBar(DockAreaWidget *dockArea) const - { - return new DockAreaTabBar(dockArea); - } +DockAreaTabBar *DockComponentsFactory::createDockAreaTabBar(DockAreaWidget *dockArea) const +{ + return new DockAreaTabBar(dockArea); +} - DockAreaTitleBar *DockComponentsFactory::createDockAreaTitleBar(DockAreaWidget *dockArea) const - { - return new DockAreaTitleBar(dockArea); - } +DockAreaTitleBar *DockComponentsFactory::createDockAreaTitleBar(DockAreaWidget *dockArea) const +{ + return new DockAreaTitleBar(dockArea); +} - const DockComponentsFactory *DockComponentsFactory::factory() - { - return g_defaultFactory.get(); - } +const DockComponentsFactory *DockComponentsFactory::factory() +{ + return g_defaultFactory.get(); +} - void DockComponentsFactory::setFactory(DockComponentsFactory *factory) - { - g_defaultFactory.reset(factory); - } +void DockComponentsFactory::setFactory(DockComponentsFactory *factory) +{ + g_defaultFactory.reset(factory); +} - void DockComponentsFactory::resetDefaultFactory() - { - g_defaultFactory.reset(new DockComponentsFactory()); - } +void DockComponentsFactory::resetDefaultFactory() +{ + g_defaultFactory.reset(new DockComponentsFactory()); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockcomponentsfactory.h b/src/libs/advanceddockingsystem/dockcomponentsfactory.h index ce5c656ac04..5333af45034 100644 --- a/src/libs/advanceddockingsystem/dockcomponentsfactory.h +++ b/src/libs/advanceddockingsystem/dockcomponentsfactory.h @@ -12,6 +12,7 @@ class DockAreaTitleBar; class DockAreaTabBar; class DockAreaWidget; class DockWidget; +class AutoHideTab; /** * Factory for creation of certain GUI elements for the docking framework. @@ -37,6 +38,12 @@ public: */ virtual DockWidgetTab *createDockWidgetTab(DockWidget *dockWidget) const; + /** + * This default implementation just creates a dock widget side tab with + * new CDockWidgetTab(DockWidget). + */ + virtual AutoHideTab *createDockWidgetSideTab(DockWidget *dockWidget) const; + /** * This default implementation just creates a dock area tab bar with * new DockAreaTabBar(dockArea). diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp index 51ced580963..2c0d306ea74 100644 --- a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp @@ -3,8 +3,10 @@ #include "dockcontainerwidget.h" -#include "ads_globals.h" #include "ads_globals_p.h" +#include "autohidedockcontainer.h" +#include "autohidesidebar.h" +#include "autohidetab.h" #include "dockareawidget.h" #include "dockingstatereader.h" #include "dockmanager.h" @@ -15,1386 +17,1781 @@ #include #include -#include #include #include #include +#include #include +#include #include #include #include #include -namespace ADS +namespace ADS { + +static unsigned int g_zOrderCounter = 0; + +enum eDropMode { + DropModeIntoArea, ///< drop widget into a dock area + DropModeIntoContainer, ///< drop into container + DropModeInvalid ///< invalid mode - do not drop +}; + +/** + * Converts dock area ID to an index for array access + */ +static int areaIdToIndex(DockWidgetArea area) { - static unsigned int zOrderCounter = 0; + switch (area) { + case LeftDockWidgetArea: + return 0; + case RightDockWidgetArea: + return 1; + case TopDockWidgetArea: + return 2; + case BottomDockWidgetArea: + return 3; + case CenterDockWidgetArea: + return 4; + default: + return 4; + } +} - enum eDropMode { - DropModeIntoArea, ///< drop widget into a dock area - DropModeIntoContainer, ///< drop into container - DropModeInvalid ///< invalid mode - do not drop - }; +/** + * Helper function to ease insertion of dock area into splitter + */ +static void insertWidgetIntoSplitter(QSplitter *splitter, QWidget *widget, bool append) +{ + if (append) + splitter->addWidget(widget); + else + splitter->insertWidget(0, widget); +} + +/** + * Private data class of DockContainerWidget class (pimpl) + */ +class DockContainerWidgetPrivate +{ +public: + DockContainerWidget *q; + QPointer m_dockManager; + unsigned int m_zOrderIndex = 0; + QList m_dockAreas; + QList m_autoHideWidgets; + QMap m_sideTabBarWidgets; + QGridLayout *m_layout = nullptr; + DockSplitter *m_rootSplitter = nullptr; + bool m_isFloating = false; + DockAreaWidget *m_lastAddedAreaCache[5]; + int m_visibleDockAreaCount = -1; + DockAreaWidget *m_topLevelDockArea = nullptr; + QTimer m_delayedAutoHideTimer; + AutoHideTab *m_delayedAutoHideTab; + bool m_delayedAutoHideShow = false; /** - * Converts dock area ID to an index for array access + * Private data constructor */ - static int areaIdToIndex(DockWidgetArea area) + DockContainerWidgetPrivate(DockContainerWidget *parent); + + /** + * Adds dock widget to container and returns the dock area that contains + * the inserted dock widget + */ + DockAreaWidget *addDockWidgetToContainer(DockWidgetArea area, DockWidget *dockWidget); + + /** + * Adds dock widget to a existing DockWidgetArea + */ + DockAreaWidget *addDockWidgetToDockArea(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *targetDockArea, + int index = -1); + + /** + * Add dock area to this container + */ + void addDockArea(DockAreaWidget *newDockWidget, DockWidgetArea area = CenterDockWidgetArea); + + /** + * Drop floating widget into container + */ + void dropIntoContainer(FloatingDockContainer *floatingWidget, DockWidgetArea area); + + /** + * Drop floating widget into auto hide side bar + */ + void dropIntoAutoHideSideBar(FloatingDockContainer *floatingWidget, DockWidgetArea area); + + /** + * Creates a new tab for a widget dropped into the center of a section + */ + void dropIntoCenterOfSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea, + int tabIndex); + + /** + * Drop floating widget into dock area + */ + void dropIntoSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea, + DockWidgetArea area, + int tabIndex = 0); + + /** + * Moves the dock widget or dock area given in Widget parameter to a new dock widget area. + */ + void moveToNewSection(QWidget *widget, + DockAreaWidget *targetArea, + DockWidgetArea area, + int tabIndex = 0); + + /** + * Moves the dock widget or dock area given in Widget parameter to a dock area in container. + */ + void moveToContainer(QWidget *widget, DockWidgetArea area); + + /** + * Creates a new tab for a widget dropped into the center of a section + */ + void moveIntoCenterOfSection(QWidget *widget, DockAreaWidget *targetArea, int tabIndex = 0); + + /** + * Moves the dock widget or dock area given in Widget parameter to + * a auto hide sidebar area + */ + void moveToAutoHideSideBar(QWidget *widget, + DockWidgetArea area, + int tabIndex = TabDefaultInsertIndex); + + /** + * Adds new dock areas to the internal dock area list + */ + void addDockAreasToList(const QList newDockAreas); + + /** + * Wrapper function for DockAreas append, that ensures that dock area signals + * are properly connected to dock container slots + */ + void appendDockAreas(const QList newDockAreas); + + /** + * Save state of child nodes + */ + void saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget); + + /** + * Save state of auto hide widgets + */ + void saveAutoHideWidgetsState(QXmlStreamWriter &stream); + + /** + * Restore state of child nodes. + * \param[in] Stream The data stream that contains the serialized state + * \param[out] CreatedWidget The widget created from parsed data or 0 if + * the parsed widget was an empty splitter + * \param[in] Testing If Testing is true, only the stream data is + * parsed without modifiying anything. + */ + bool restoreChildNodes(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); + + /** + * Restores a splitter. + * \see restoreChildNodes() for details + */ + bool restoreSplitter(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); + + /** + * Restores a dock area. + * \see restoreChildNodes() for details + */ + bool restoreDockArea(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); + + /** + * Restores a auto hide side bar + */ + bool restoreSideBar(DockingStateReader &stream, QWidget *&createdWidget, bool testing); + + /** + * Helper function for recursive dumping of layout + */ + void dumpRecursive(int level, QWidget *widget) const; + + /** + * Calculate the drop mode from the given target position + */ + eDropMode getDropMode(const QPoint &targetPosition); + + /** + * Initializes the visible dock area count variable if it is not initialized + * yet + */ + void initVisibleDockAreaCount() { - switch (area) { - case LeftDockWidgetArea: - return 0; - case RightDockWidgetArea: - return 1; - case TopDockWidgetArea: - return 2; - case BottomDockWidgetArea: - return 3; - case CenterDockWidgetArea: - return 4; - default: - return 4; - } + if (m_visibleDockAreaCount > -1) + return; + + m_visibleDockAreaCount = 0; + for (auto dockArea : std::as_const(m_dockAreas)) + m_visibleDockAreaCount += dockArea->isHidden() ? 0 : 1; } /** - * Helper function to ease insertion of dock area into splitter + * Access function for the visible dock area counter */ - static void insertWidgetIntoSplitter(QSplitter *splitter, QWidget *widget, bool append) + int visibleDockAreaCount() { - if (append) - splitter->addWidget(widget); - else - splitter->insertWidget(0, widget); + // Lazy initialization - we initialize the m_visibleDockAreaCount variable + // on first use + initVisibleDockAreaCount(); + return m_visibleDockAreaCount; } /** - * Private data class of DockContainerWidget class (pimpl) + * The visible dock area count changes, if dock areas are remove, added or + * when its view is toggled */ - class DockContainerWidgetPrivate + void onVisibleDockAreaCountChanged(); + + void emitDockAreasRemoved() { - public: - DockContainerWidget *q; - QPointer m_dockManager; - unsigned int m_zOrderIndex = 0; - QList m_dockAreas; - QGridLayout *m_layout = nullptr; - QSplitter *m_rootSplitter = nullptr; - bool m_isFloating = false; - DockAreaWidget *m_lastAddedAreaCache[5]; - int m_visibleDockAreaCount = -1; - DockAreaWidget *m_topLevelDockArea = nullptr; - - /** - * Private data constructor - */ - DockContainerWidgetPrivate(DockContainerWidget *parent); - - /** - * Adds dock widget to container and returns the dock area that contains - * the inserted dock widget - */ - DockAreaWidget *addDockWidgetToContainer(DockWidgetArea area, DockWidget *dockWidget); - - /** - * Adds dock widget to a existing DockWidgetArea - */ - DockAreaWidget *addDockWidgetToDockArea(DockWidgetArea area, - DockWidget *dockWidget, - DockAreaWidget *targetDockArea); - - /** - * Add dock area to this container - */ - void addDockArea(DockAreaWidget *newDockWidget, DockWidgetArea area = CenterDockWidgetArea); - - /** - * Drop floating widget into container - */ - void dropIntoContainer(FloatingDockContainer *floatingWidget, DockWidgetArea area); - - /** - * Drop floating widget into dock area - */ - void dropIntoSection(FloatingDockContainer *floatingWidget, - DockAreaWidget *targetArea, - DockWidgetArea area); - - /** - * Moves the dock widget or dock area given in Widget parameter to a - * new dock widget area - */ - void moveToNewSection(QWidget *widget, DockAreaWidget *targetArea, DockWidgetArea area); - - /** - * Moves the dock widget or dock area given in Widget parameter to a - * a dock area in container - */ - void moveToContainer(QWidget *widget, DockWidgetArea area); - - /** - * Creates a new tab for a widget dropped into the center of a section - */ - void dropIntoCenterOfSection(FloatingDockContainer *floatingWidget, - DockAreaWidget *targetArea); - - /** - * Creates a new tab for a widget dropped into the center of a section - */ - void moveIntoCenterOfSection(QWidget *widget, DockAreaWidget *targetArea); - - /** - * Adds new dock areas to the internal dock area list - */ - void addDockAreasToList(const QList newDockAreas); - - /** - * Wrapper function for DockAreas append, that ensures that dock area signals - * are properly connected to dock container slots - */ - void appendDockAreas(const QList newDockAreas); - - /** - * Save state of child nodes - */ - void saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget); - - /** - * Restore state of child nodes. - * \param[in] Stream The data stream that contains the serialized state - * \param[out] CreatedWidget The widget created from parsed data or 0 if - * the parsed widget was an empty splitter - * \param[in] Testing If Testing is true, only the stream data is - * parsed without modifiying anything. - */ - bool restoreChildNodes(DockingStateReader &stateReader, - QWidget *&createdWidget, - bool testing); - - /** - * Restores a splitter. - * \see restoreChildNodes() for details - */ - bool restoreSplitter(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); - - /** - * Restores a dock area. - * \see restoreChildNodes() for details - */ - bool restoreDockArea(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); - - /** - * Helper function for recursive dumping of layout - */ - void dumpRecursive(int level, QWidget *widget) const; - - /** - * Calculate the drop mode from the given target position - */ - eDropMode getDropMode(const QPoint &targetPosition); - - /** - * Initializes the visible dock area count variable if it is not initialized - * yet - */ - void initVisibleDockAreaCount() - { - if (m_visibleDockAreaCount > -1) - return; - - m_visibleDockAreaCount = 0; - for (auto dockArea : std::as_const(m_dockAreas)) - m_visibleDockAreaCount += dockArea->isHidden() ? 0 : 1; - } - - /** - * Access function for the visible dock area counter - */ - int visibleDockAreaCount() - { - // Lazy initialization - we initialize the m_visibleDockAreaCount variable - // on first use - initVisibleDockAreaCount(); - return m_visibleDockAreaCount; - } - - /** - * The visible dock area count changes, if dock areas are remove, added or - * when its view is toggled - */ - void onVisibleDockAreaCountChanged(); - - void emitDockAreasRemoved() - { - onVisibleDockAreaCountChanged(); - emit q->dockAreasRemoved(); - } - - void emitDockAreasAdded() - { - onVisibleDockAreaCountChanged(); - emit q->dockAreasAdded(); - } - - /** - * Helper function for creation of new splitter - */ - DockSplitter *createSplitter(Qt::Orientation orientation, QWidget *parent = nullptr) - { - auto *splitter = new DockSplitter(orientation, parent); - splitter->setOpaqueResize(DockManager::testConfigFlag(DockManager::OpaqueSplitterResize)); - splitter->setChildrenCollapsible(false); - return splitter; - } - - /** - * Ensures equal distribution of the sizes of a splitter if an dock widget - * is inserted from code - */ - void adjustSplitterSizesOnInsertion(QSplitter *splitter, qreal lastRatio = 1.0) - { - const int areaSize = (splitter->orientation() == Qt::Horizontal) ? splitter->width() - : splitter->height(); - auto splitterSizes = splitter->sizes(); - - const qreal totalRatio = splitterSizes.size() - 1.0 + lastRatio; - for (int i = 0; i < splitterSizes.size() - 1; ++i) - splitterSizes[i] = areaSize / totalRatio; - - splitterSizes.back() = areaSize * lastRatio / totalRatio; - splitter->setSizes(splitterSizes); - } - - void onDockAreaViewToggled(DockAreaWidget *dockArea, bool visible) - { - m_visibleDockAreaCount += visible ? 1 : -1; - onVisibleDockAreaCountChanged(); - emit q->dockAreaViewToggled(dockArea, visible); - } - }; // struct DockContainerWidgetPrivate - - DockContainerWidgetPrivate::DockContainerWidgetPrivate(DockContainerWidget *parent) - : q(parent) - { - std::fill(std::begin(m_lastAddedAreaCache), std::end(m_lastAddedAreaCache), nullptr); + onVisibleDockAreaCountChanged(); + emit q->dockAreasRemoved(); } - eDropMode DockContainerWidgetPrivate::getDropMode(const QPoint &targetPosition) + void emitDockAreasAdded() { - DockAreaWidget *dockArea = q->dockAreaAt(targetPosition); - auto dropArea = InvalidDockWidgetArea; - auto containerDropArea = m_dockManager->containerOverlay()->dropAreaUnderCursor(); - - if (dockArea) { - auto dropOverlay = m_dockManager->dockAreaOverlay(); - dropOverlay->setAllowedAreas(dockArea->allowedAreas()); - dropArea = dropOverlay->showOverlay(dockArea); - if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) - dropArea = InvalidDockWidgetArea; - - if (dropArea != InvalidDockWidgetArea) { - qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; - return DropModeIntoArea; - } - } - - // mouse is over container - if (InvalidDockWidgetArea == dropArea) { - dropArea = containerDropArea; - qCInfo(adsLog) << "Container Drop Content: " << dropArea; - if (dropArea != InvalidDockWidgetArea) - return DropModeIntoContainer; - } - - return DropModeInvalid; + onVisibleDockAreaCountChanged(); + emit q->dockAreasAdded(); } - void DockContainerWidgetPrivate::onVisibleDockAreaCountChanged() + /** + * Helper function for creation of new splitter + */ + DockSplitter *createSplitter(Qt::Orientation orientation, QWidget *parent = nullptr) { - auto topLevelDockArea = q->topLevelDockArea(); + auto *splitter = new DockSplitter(orientation, parent); + splitter->setOpaqueResize(DockManager::testConfigFlag(DockManager::OpaqueSplitterResize)); + splitter->setChildrenCollapsible(false); + return splitter; + } - if (topLevelDockArea) { - this->m_topLevelDockArea = topLevelDockArea; - topLevelDockArea->titleBarButton(TitleBarButtonUndock) - ->setVisible(false || !q->isFloating()); - topLevelDockArea->titleBarButton(TitleBarButtonClose) - ->setVisible(false || !q->isFloating()); - } else if (this->m_topLevelDockArea) { - this->m_topLevelDockArea->titleBarButton(TitleBarButtonUndock)->setVisible(true); - this->m_topLevelDockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); - this->m_topLevelDockArea = nullptr; + /** + * Ensures equal distribution of the sizes of a splitter if an dock widget + * is inserted from code + */ + void adjustSplitterSizesOnInsertion(QSplitter *splitter, qreal lastRatio = 1.0) + { + const int areaSize = (splitter->orientation() == Qt::Horizontal) ? splitter->width() + : splitter->height(); + auto splitterSizes = splitter->sizes(); + + const qreal totalRatio = splitterSizes.size() - 1.0 + lastRatio; + for (int i = 0; i < splitterSizes.size() - 1; ++i) + splitterSizes[i] = areaSize / totalRatio; + + splitterSizes.back() = areaSize * lastRatio / totalRatio; + splitter->setSizes(splitterSizes); + } + + /** + * This function forces the dock container widget to update handles of splitters + * based if a central widget exists. + */ + void updateSplitterHandles(QSplitter *splitter); + + /** + * If no central widget exists, the widgets resize with the container. + * If a central widget exists, the widgets surrounding the central widget + * do not resize its height or width. + */ + bool widgetResizesWithContainer(QWidget *widget); + + void onDockAreaViewToggled(DockAreaWidget *dockArea, bool visible) + { + m_visibleDockAreaCount += visible ? 1 : -1; + onVisibleDockAreaCountChanged(); + emit q->dockAreaViewToggled(dockArea, visible); + } +}; // struct DockContainerWidgetPrivate + +DockContainerWidgetPrivate::DockContainerWidgetPrivate(DockContainerWidget *parent) + : q(parent) +{ + std::fill(std::begin(m_lastAddedAreaCache), std::end(m_lastAddedAreaCache), nullptr); + m_delayedAutoHideTimer.setSingleShot(true); + m_delayedAutoHideTimer.setInterval(500); + QObject::connect(&m_delayedAutoHideTimer, &QTimer::timeout, q, [this]() { + auto globalPos = m_delayedAutoHideTab->mapToGlobal(QPoint(0, 0)); + qApp->sendEvent(m_delayedAutoHideTab, + new QMouseEvent(QEvent::MouseButtonPress, + QPoint(0, 0), + globalPos, + Qt::LeftButton, + {Qt::LeftButton}, + Qt::NoModifier)); + }); +} + +eDropMode DockContainerWidgetPrivate::getDropMode(const QPoint &targetPosition) +{ + DockAreaWidget *dockArea = q->dockAreaAt(targetPosition); + auto dropArea = InvalidDockWidgetArea; + auto containerDropArea = m_dockManager->containerOverlay()->dropAreaUnderCursor(); + + if (dockArea) { + auto dropOverlay = m_dockManager->dockAreaOverlay(); + dropOverlay->setAllowedAreas(dockArea->allowedAreas()); + dropArea = dropOverlay->showOverlay(dockArea); + if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) + dropArea = InvalidDockWidgetArea; + + if (dropArea != InvalidDockWidgetArea) { + qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; + return DropModeIntoArea; } } - void DockContainerWidgetPrivate::dropIntoContainer(FloatingDockContainer *floatingWidget, - DockWidgetArea area) - { - auto insertParam = internal::dockAreaInsertParameters(area); - DockContainerWidget *floatingDockContainer = floatingWidget->dockContainer(); - auto newDockAreas = floatingDockContainer - ->findChildren(QString(), - Qt::FindChildrenRecursively); - QSplitter *splitter = m_rootSplitter; - - if (m_dockAreas.count() <= 1) { - splitter->setOrientation(insertParam.orientation()); - } else if (splitter->orientation() != insertParam.orientation()) { - QSplitter *newSplitter = createSplitter(insertParam.orientation()); - QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); - newSplitter->addWidget(splitter); - splitter = newSplitter; - delete layoutItem; - } - - // Now we can insert the floating widget content into this container - auto floatingSplitter = floatingDockContainer->rootSplitter(); - if (floatingSplitter->count() == 1) { - insertWidgetIntoSplitter(splitter, floatingSplitter->widget(0), insertParam.append()); - } else if (floatingSplitter->orientation() == insertParam.orientation()) { - int insertIndex = insertParam.append() ? splitter->count() : 0; - while (floatingSplitter->count()) - splitter->insertWidget(insertIndex++, floatingSplitter->widget(0)); - } else { - insertWidgetIntoSplitter(splitter, floatingSplitter, insertParam.append()); - } - - m_rootSplitter = splitter; - addDockAreasToList(newDockAreas); - - // If we dropped the floating widget into the main dock container that does - // not contain any dock widgets, then splitter is invisible and we need to - // show it to display the docked widgets - if (!splitter->isVisible()) - splitter->show(); - - q->dumpLayout(); + // mouse is over container + if (InvalidDockWidgetArea == dropArea) { + dropArea = containerDropArea; + qCInfo(adsLog) << "Container Drop Content: " << dropArea; + if (dropArea != InvalidDockWidgetArea) + return DropModeIntoContainer; } - void DockContainerWidgetPrivate::dropIntoCenterOfSection(FloatingDockContainer *floatingWidget, - DockAreaWidget *targetArea) - { - DockContainerWidget *floatingContainer = floatingWidget->dockContainer(); - auto newDockWidgets = floatingContainer->dockWidgets(); - auto topLevelDockArea = floatingContainer->topLevelDockArea(); - int newCurrentIndex = -1; + return DropModeInvalid; +} - // If the floating widget contains only one single dock are, then the - // current dock widget of the dock area will also be the future current - // dock widget in the drop area. - if (topLevelDockArea) - newCurrentIndex = topLevelDockArea->currentIndex(); +void DockContainerWidgetPrivate::onVisibleDockAreaCountChanged() +{ + auto topLevelDockArea = q->topLevelDockArea(); - for (int i = 0; i < newDockWidgets.count(); ++i) { - DockWidget *dockWidget = newDockWidgets[i]; - targetArea->insertDockWidget(i, dockWidget, false); - // If the floating widget contains multiple visible dock areas, then we - // simply pick the first visible open dock widget and make it - // the current one. - if (newCurrentIndex < 0 && !dockWidget->isClosed()) - newCurrentIndex = i; + if (topLevelDockArea) { + this->m_topLevelDockArea = topLevelDockArea; + topLevelDockArea->updateTitleBarButtonVisibility(true); + } else if (this->m_topLevelDockArea) { + this->m_topLevelDockArea->updateTitleBarButtonVisibility(false); + this->m_topLevelDockArea = nullptr; + } +} + +void DockContainerWidgetPrivate::dropIntoContainer(FloatingDockContainer *floatingWidget, + DockWidgetArea area) +{ + auto insertParam = internal::dockAreaInsertParameters(area); + DockContainerWidget *floatingDockContainer = floatingWidget->dockContainer(); + auto newDockAreas + = floatingDockContainer->findChildren(QString(), + Qt::FindChildrenRecursively); + auto *splitter = m_rootSplitter; + + if (m_dockAreas.count() <= 1) { + splitter->setOrientation(insertParam.orientation()); + } else if (splitter->orientation() != insertParam.orientation()) { + auto *newSplitter = createSplitter(insertParam.orientation()); + QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); + newSplitter->addWidget(splitter); + updateSplitterHandles(newSplitter); + splitter = newSplitter; + delete layoutItem; + } + + // Now we can insert the floating widget content into this container + auto floatingSplitter = floatingDockContainer->rootSplitter(); + if (floatingSplitter->count() == 1) { + insertWidgetIntoSplitter(splitter, floatingSplitter->widget(0), insertParam.append()); + updateSplitterHandles(splitter); + } else if (floatingSplitter->orientation() == insertParam.orientation()) { + int insertIndex = insertParam.append() ? splitter->count() : 0; + while (floatingSplitter->count()) { + splitter->insertWidget(insertIndex++, floatingSplitter->widget(0)); + updateSplitterHandles(splitter); } - targetArea->setCurrentIndex(newCurrentIndex); - targetArea->updateTitleBarVisibility(); + } else { + insertWidgetIntoSplitter(splitter, floatingSplitter, insertParam.append()); + } + + m_rootSplitter = splitter; + addDockAreasToList(newDockAreas); + + // If we dropped the floating widget into the main dock container that does + // not contain any dock widgets, then splitter is invisible and we need to + // show it to display the docked widgets + if (!splitter->isVisible()) + splitter->show(); + + q->dumpLayout(); +} + +void DockContainerWidgetPrivate::dropIntoAutoHideSideBar(FloatingDockContainer *floatingWidget, + DockWidgetArea area) +{ + auto sideBarLocation = internal::toSideBarLocation(area); + auto newDockAreas = floatingWidget->findChildren(QString(), + Qt::FindChildrenRecursively); + int tabIndex = m_dockManager->containerOverlay()->tabIndexUnderCursor(); + for (auto dockArea : newDockAreas) { + auto dockWidgets = dockArea->dockWidgets(); + for (auto dockWidget : dockWidgets) + q->createAndSetupAutoHideContainer(sideBarLocation, dockWidget, tabIndex++); + } +} + +void DockContainerWidgetPrivate::dropIntoCenterOfSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea, + int tabIndex) +{ + DockContainerWidget *floatingContainer = floatingWidget->dockContainer(); + auto newDockWidgets = floatingContainer->dockWidgets(); + auto topLevelDockArea = floatingContainer->topLevelDockArea(); + int newCurrentIndex = -1; + tabIndex = qMax(0, tabIndex); + + // If the floating widget contains only one single dock are, then the current dock widget of + // the dock area will also be the future current dock widget in the drop area. + if (topLevelDockArea) + newCurrentIndex = topLevelDockArea->currentIndex(); + + for (int i = 0; i < newDockWidgets.count(); ++i) { + DockWidget *dockWidget = newDockWidgets[i]; + targetArea->insertDockWidget(tabIndex + i, dockWidget, false); + targetArea->insertDockWidget(i, dockWidget, false); + // If the floating widget contains multiple visible dock areas, then we simply pick the + // first visible open dock widget and make it the current one. + if (newCurrentIndex < 0 && !dockWidget->isClosed()) + newCurrentIndex = i; + } + targetArea->setCurrentIndex(newCurrentIndex + tabIndex); + targetArea->updateTitleBarVisibility(); + return; +} + +void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea, + DockWidgetArea area, + int tabIndex) +{ + // Dropping into center means all dock widgets in the dropped floating widget will become + // tabs of the drop area. + if (CenterDockWidgetArea == area) { + dropIntoCenterOfSection(floatingWidget, targetArea, tabIndex); return; } - void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floatingWidget, - DockAreaWidget *targetArea, - DockWidgetArea area) - { - // Dropping into center means all dock widgets in the dropped floating - // widget will become tabs of the drop area - if (CenterDockWidgetArea == area) { - dropIntoCenterOfSection(floatingWidget, targetArea); - return; - } + DockContainerWidget *floatingContainer = floatingWidget->dockContainer(); + auto insertParam = internal::dockAreaInsertParameters(area); + auto newDockAreas + = floatingContainer->findChildren(QString(), Qt::FindChildrenRecursively); + QSplitter *targetAreaSplitter = internal::findParent(targetArea); - auto insertParam = internal::dockAreaInsertParameters(area); - auto newDockAreas = floatingWidget->dockContainer() - ->findChildren(QString(), - Qt::FindChildrenRecursively); - QSplitter *targetAreaSplitter = internal::findParent(targetArea); - - if (!targetAreaSplitter) { - QSplitter *splitter = createSplitter(insertParam.orientation()); - m_layout->replaceWidget(targetArea, splitter); - splitter->addWidget(targetArea); - targetAreaSplitter = splitter; - } - int areaIndex = targetAreaSplitter->indexOf(targetArea); - auto widget = floatingWidget->dockContainer() - ->findChild(QString(), Qt::FindDirectChildrenOnly); - auto floatingSplitter = qobject_cast(widget); - - if (targetAreaSplitter->orientation() == insertParam.orientation()) { - auto sizes = targetAreaSplitter->sizes(); - int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) - ? targetArea->width() - : targetArea->height(); - bool adjustSplitterSizes = true; - if ((floatingSplitter->orientation() != insertParam.orientation()) - && floatingSplitter->count() > 1) { - targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), widget); - } else { - adjustSplitterSizes = (floatingSplitter->count() == 1); - int insertIndex = areaIndex + insertParam.insertOffset(); - while (floatingSplitter->count()) - targetAreaSplitter->insertWidget(insertIndex++, floatingSplitter->widget(0)); - } - - if (adjustSplitterSizes) { - int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2; - sizes[areaIndex] = size; - sizes.insert(areaIndex, size); - targetAreaSplitter->setSizes(sizes); - } - } else { - QSplitter *newSplitter = createSplitter(insertParam.orientation()); - int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) - ? targetArea->width() - : targetArea->height(); - bool adjustSplitterSizes = true; - if ((floatingSplitter->orientation() != insertParam.orientation()) - && floatingSplitter->count() > 1) { - newSplitter->addWidget(widget); - } else { - adjustSplitterSizes = (floatingSplitter->count() == 1); - while (floatingSplitter->count()) { - newSplitter->addWidget(floatingSplitter->widget(0)); - } - } - - // Save the sizes before insertion and restore it later to prevent - // shrinking of existing area - auto sizes = targetAreaSplitter->sizes(); - insertWidgetIntoSplitter(newSplitter, targetArea, !insertParam.append()); - if (adjustSplitterSizes) { - int size = targetAreaSize / 2; - newSplitter->setSizes({size, size}); - } - targetAreaSplitter->insertWidget(areaIndex, newSplitter); - targetAreaSplitter->setSizes(sizes); - } - - addDockAreasToList(newDockAreas); - q->dumpLayout(); + if (!targetAreaSplitter) { + QSplitter *splitter = createSplitter(insertParam.orientation()); + m_layout->replaceWidget(targetArea, splitter); + splitter->addWidget(targetArea); + targetAreaSplitter = splitter; } + int areaIndex = targetAreaSplitter->indexOf(targetArea); + auto floatingSplitter = floatingContainer->rootSplitter(); - void DockContainerWidgetPrivate::moveIntoCenterOfSection(QWidget *widget, - DockAreaWidget *targetArea) - { - auto droppedDockWidget = qobject_cast(widget); - auto droppedArea = qobject_cast(widget); - - if (droppedDockWidget) { - DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); - if (oldDockArea == targetArea) - return; - - if (oldDockArea) - oldDockArea->removeDockWidget(droppedDockWidget); - - targetArea->insertDockWidget(0, droppedDockWidget, true); - } else { - QList newDockWidgets = droppedArea->dockWidgets(); - int newCurrentIndex = droppedArea->currentIndex(); - for (int i = 0; i < newDockWidgets.count(); ++i) { - DockWidget *dockWidget = newDockWidgets[i]; - targetArea->insertDockWidget(i, dockWidget, false); - } - targetArea->setCurrentIndex(newCurrentIndex); - droppedArea->dockContainer()->removeDockArea(droppedArea); - droppedArea->deleteLater(); - } - - targetArea->updateTitleBarVisibility(); - return; - } - - void DockContainerWidgetPrivate::moveToNewSection(QWidget *widget, - DockAreaWidget *targetArea, - DockWidgetArea area) - { - // Dropping into center means all dock widgets in the dropped floating - // widget will become tabs of the drop area - if (CenterDockWidgetArea == area) { - moveIntoCenterOfSection(widget, targetArea); - return; - } - - DockWidget *droppedDockWidget = qobject_cast(widget); - DockAreaWidget *droppedDockArea = qobject_cast(widget); - DockAreaWidget *newDockArea; - if (droppedDockWidget) { - newDockArea = new DockAreaWidget(m_dockManager, q); - DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); - if (oldDockArea) - oldDockArea->removeDockWidget(droppedDockWidget); - - newDockArea->addDockWidget(droppedDockWidget); - } else { - droppedDockArea->dockContainer()->removeDockArea(droppedDockArea); - newDockArea = droppedDockArea; - } - - auto insertParam = internal::dockAreaInsertParameters(area); - QSplitter *targetAreaSplitter = internal::findParent(targetArea); - const int areaIndex = targetAreaSplitter->indexOf(targetArea); + if (targetAreaSplitter->orientation() == insertParam.orientation()) { auto sizes = targetAreaSplitter->sizes(); - if (targetAreaSplitter->orientation() == insertParam.orientation()) { - const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) - ? targetArea->width() - : targetArea->height(); - targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), newDockArea); - const int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2; + int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) ? targetArea->width() + : targetArea->height(); + bool adjustSplitterSizes = true; + if ((floatingSplitter->orientation() != insertParam.orientation()) + && floatingSplitter->count() > 1) { + targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), + floatingSplitter); + updateSplitterHandles(targetAreaSplitter); + } else { + adjustSplitterSizes = (floatingSplitter->count() == 1); + int insertIndex = areaIndex + insertParam.insertOffset(); + while (floatingSplitter->count()) { + targetAreaSplitter->insertWidget(insertIndex++, floatingSplitter->widget(0)); + updateSplitterHandles(targetAreaSplitter); + } + } + + if (adjustSplitterSizes) { + int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2; sizes[areaIndex] = size; sizes.insert(areaIndex, size); + targetAreaSplitter->setSizes(sizes); + } + } else { + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) ? targetArea->width() + : targetArea->height(); + bool adjustSplitterSizes = true; + if ((floatingSplitter->orientation() != insertParam.orientation()) + && floatingSplitter->count() > 1) { + newSplitter->addWidget(floatingSplitter); + updateSplitterHandles(newSplitter); } else { - const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) - ? targetArea->width() - : targetArea->height(); - QSplitter *newSplitter = createSplitter(insertParam.orientation()); - newSplitter->addWidget(targetArea); - insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); - const int size = targetAreaSize / 2; + adjustSplitterSizes = (floatingSplitter->count() == 1); + while (floatingSplitter->count()) { + newSplitter->addWidget(floatingSplitter->widget(0)); + updateSplitterHandles(newSplitter); + } + } + + // Save the sizes before insertion and restore it later to prevent + // shrinking of existing area + auto sizes = targetAreaSplitter->sizes(); + insertWidgetIntoSplitter(newSplitter, targetArea, !insertParam.append()); + updateSplitterHandles(newSplitter); + if (adjustSplitterSizes) { + int size = targetAreaSize / 2; newSplitter->setSizes({size, size}); - targetAreaSplitter->insertWidget(areaIndex, newSplitter); } + targetAreaSplitter->insertWidget(areaIndex, newSplitter); targetAreaSplitter->setSizes(sizes); - - addDockAreasToList({newDockArea}); + updateSplitterHandles(targetAreaSplitter); } - void DockContainerWidgetPrivate::moveToContainer(QWidget *widget, DockWidgetArea area) - { - DockWidget *droppedDockWidget = qobject_cast(widget); - DockAreaWidget *droppedDockArea = qobject_cast(widget); - DockAreaWidget *newDockArea = nullptr; + addDockAreasToList(newDockAreas); + q->dumpLayout(); +} - if (droppedDockWidget) { - newDockArea = new DockAreaWidget(m_dockManager, q); - DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); - if (oldDockArea) - oldDockArea->removeDockWidget(droppedDockWidget); - - newDockArea->addDockWidget(droppedDockWidget); - } else { - // We check, if we insert the dropped widget into the same place that - // it already has and do nothing, if it is the same place. It would - // also work without this check, but it looks nicer with the check - // because there will be no layout updates - auto splitter = internal::findParent(droppedDockArea); - auto insertParam = internal::dockAreaInsertParameters(area); - if (splitter == m_rootSplitter && insertParam.orientation() == splitter->orientation()) { - if (insertParam.append() && splitter->lastWidget() == droppedDockArea) - return; - else if (!insertParam.append() && splitter->firstWidget() == droppedDockArea) - return; - } - droppedDockArea->dockContainer()->removeDockArea(droppedDockArea); - newDockArea = droppedDockArea; - } - - addDockArea(newDockArea, area); - m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea; +void DockContainerWidgetPrivate::moveToNewSection(QWidget *widget, + DockAreaWidget *targetArea, + DockWidgetArea area, + int tabIndex) +{ + // Dropping into center means all dock widgets in the dropped floating widget will become + // tabs of the drop area. + if (CenterDockWidgetArea == area) { + moveIntoCenterOfSection(widget, targetArea, tabIndex); + return; } - void DockContainerWidgetPrivate::addDockAreasToList(const QList newDockAreas) - { - const int countBefore = m_dockAreas.count(); - const int newAreaCount = newDockAreas.count(); - appendDockAreas(newDockAreas); - // If the user dropped a floating widget that contains only one single - // visible dock area, then its title bar button TitleBarButtonUndock is - // likely hidden. We need to ensure, that it is visible - for (auto dockArea : newDockAreas) { - dockArea->titleBarButton(TitleBarButtonUndock)->setVisible(true); - dockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); - } - - // We need to ensure, that the dock area title bar is visible. The title bar - // is invisible, if the dock are is a single dock area in a floating widget. - if (1 == countBefore) - m_dockAreas.at(0)->updateTitleBarVisibility(); - - if (1 == newAreaCount) - m_dockAreas.last()->updateTitleBarVisibility(); - - emitDockAreasAdded(); - } - - void DockContainerWidgetPrivate::appendDockAreas(const QList newDockAreas) - { - m_dockAreas.append(newDockAreas); - for (auto dockArea : newDockAreas) { - QObject::connect(dockArea, &DockAreaWidget::viewToggled, - q, std::bind(&DockContainerWidgetPrivate::onDockAreaViewToggled, - this, dockArea, std::placeholders::_1)); - } - } - - void DockContainerWidgetPrivate::saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget) - { - QSplitter *splitter = qobject_cast(widget); - if (splitter) { - stream.writeStartElement("splitter"); - stream.writeAttribute("orientation", - QVariant::fromValue(splitter->orientation()).toString()); - stream.writeAttribute("count", QString::number(splitter->count())); - qCInfo(adsLog) << "NodeSplitter orientation:" << splitter->orientation() - << "WidgetCount:" << splitter->count(); - for (int i = 0; i < splitter->count(); ++i) - saveChildNodesState(stream, splitter->widget(i)); - - stream.writeStartElement("sizes"); - QStringList sizes; - for (auto size : splitter->sizes()) - sizes.append(QString::number(size)); - - stream.writeCharacters(sizes.join(" ")); - stream.writeEndElement(); // sizes - stream.writeEndElement(); // splitter - } else { - DockAreaWidget *dockArea = qobject_cast(widget); - if (dockArea) - dockArea->saveState(stream); - } - } - - bool DockContainerWidgetPrivate::restoreSplitter(DockingStateReader &stateReader, - QWidget *&createdWidget, - bool testing) - { - QVariant orientationVar = QVariant(stateReader.attributes().value("orientation").toString()); - - // Check if the orientation string is convertable - if (!orientationVar.canConvert()) - return false; - - Qt::Orientation orientation = orientationVar.value(); - - bool ok; - int widgetCount = stateReader.attributes().value("count").toInt(&ok); - if (!ok) - return false; - - qCInfo(adsLog) << "Restore NodeSplitter Orientation:" << orientation - << "WidgetCount:" << widgetCount; - QSplitter *splitter = nullptr; - if (!testing) - splitter = createSplitter(orientation); - - bool visible = false; - QList sizes; - while (stateReader.readNextStartElement()) { - QWidget *childNode = nullptr; - bool result = true; - if (stateReader.name() == QLatin1String("splitter")) { - result = restoreSplitter(stateReader, childNode, testing); - } else if (stateReader.name() == QLatin1String("area")) { - result = restoreDockArea(stateReader, childNode, testing); - } else if (stateReader.name() == QLatin1String("sizes")) { - QString size = stateReader.readElementText().trimmed(); - qCInfo(adsLog) << "Size: " << size; - QTextStream textStream(&size); - while (!textStream.atEnd()) { - int value; - textStream >> value; - sizes.append(value); - } - } else { - stateReader.skipCurrentElement(); - } - - if (!result) - return false; - - if (testing || !childNode) - continue; - - qCInfo(adsLog) << "ChildNode isVisible " << childNode->isVisible() << " isVisibleTo " - << childNode->isVisibleTo(splitter); - splitter->addWidget(childNode); - visible |= childNode->isVisibleTo(splitter); - } - - if (sizes.count() != widgetCount) - return false; - - if (!testing) { - if (!splitter->count()) { - delete splitter; - splitter = nullptr; - } else { - splitter->setSizes(sizes); - splitter->setVisible(visible); - } - createdWidget = splitter; - } else { - createdWidget = nullptr; - } - - return true; - } - - bool DockContainerWidgetPrivate::restoreDockArea(DockingStateReader &stateReader, - QWidget *&createdWidget, - bool testing) - { - QString currentDockWidget = stateReader.attributes().value("current").toString(); - -#ifdef ADS_DEBUG_PRINT - bool ok; - int tabs = stateReader.attributes().value("tabs").toInt(&ok); - if (!ok) - return false; - - qCInfo(adsLog) << "Restore NodeDockArea Tabs: " << tabs - << " Current: " << currentDockWidget; -#endif - - DockAreaWidget *dockArea = nullptr; - if (!testing) - dockArea = new DockAreaWidget(m_dockManager, q); - - while (stateReader.readNextStartElement()) { - if (stateReader.name() != QLatin1String("widget")) - continue; - - auto objectName = stateReader.attributes().value("name"); - if (objectName.isEmpty()) { - qCInfo(adsLog) << "Error: Empty name!"; - return false; - } - - QVariant closedVar = QVariant(stateReader.attributes().value("closed").toString()); - if (!closedVar.canConvert()) - return false; - - bool closed = closedVar.value(); - - stateReader.skipCurrentElement(); - DockWidget *dockWidget = m_dockManager->findDockWidget(objectName.toString()); - if (!dockWidget || testing) - continue; - - qCInfo(adsLog) << "Dock Widget found - parent " << dockWidget->parent(); - // We hide the DockArea here to prevent the short display (the flashing) - // of the dock areas during application startup - dockArea->hide(); - dockArea->addDockWidget(dockWidget); - dockWidget->setToggleViewActionChecked(!closed); - dockWidget->setClosedState(closed); - dockWidget->setProperty(internal::closedProperty, closed); - dockWidget->setProperty(internal::dirtyProperty, false); - } - - if (testing) - return true; - - if (!dockArea->dockWidgetsCount()) { - delete dockArea; - dockArea = nullptr; - } else { - dockArea->setProperty("currentDockWidget", currentDockWidget); - appendDockAreas({dockArea}); - } - - createdWidget = dockArea; - return true; - } - - bool DockContainerWidgetPrivate::restoreChildNodes(DockingStateReader &stateReader, - QWidget *&createdWidget, - bool testing) - { - bool result = true; - while (stateReader.readNextStartElement()) { - if (stateReader.name() == QLatin1String("splitter")) { - result = restoreSplitter(stateReader, createdWidget, testing); - qCInfo(adsLog) << "Splitter"; - } else if (stateReader.name() == QLatin1String("area")) { - result = restoreDockArea(stateReader, createdWidget, testing); - qCInfo(adsLog) << "DockAreaWidget"; - } else { - stateReader.skipCurrentElement(); - qCInfo(adsLog) << "Unknown element" << stateReader.name(); - } - } - - return result; - } - - DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToContainer(DockWidgetArea area, - DockWidget *dockWidget) - { - DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q); - newDockArea->addDockWidget(dockWidget); - addDockArea(newDockArea, area); - newDockArea->updateTitleBarVisibility(); - m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea; - return newDockArea; - } - - void DockContainerWidgetPrivate::addDockArea(DockAreaWidget *newDockArea, DockWidgetArea area) - { - auto insertParam = internal::dockAreaInsertParameters(area); - // As long as we have only one dock area in the splitter we can adjust its orientation - if (m_dockAreas.count() <= 1) - m_rootSplitter->setOrientation(insertParam.orientation()); - - QSplitter *splitter = m_rootSplitter; - if (splitter->orientation() == insertParam.orientation()) { - insertWidgetIntoSplitter(splitter, newDockArea, insertParam.append()); - if (splitter->isHidden()) - splitter->show(); - - } else { - QSplitter *newSplitter = createSplitter(insertParam.orientation()); - if (insertParam.append()) { - QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); - newSplitter->addWidget(splitter); - newSplitter->addWidget(newDockArea); - delete layoutItem; - } else { - newSplitter->addWidget(newDockArea); - QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); - newSplitter->addWidget(splitter); - delete layoutItem; - } - m_rootSplitter = newSplitter; - } - - addDockAreasToList({newDockArea}); - } - - void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget *widget) const - { -#if defined(QT_DEBUG) - QSplitter *splitter = qobject_cast(widget); - QByteArray buf; - buf.fill(' ', level * 4); - if (splitter) { -#ifdef ADS_DEBUG_PRINT - qDebug("%sSplitter %s v: %s c: %s", - buf.data(), - (splitter->orientation() == Qt::Vertical) ? "--" : "|", - splitter->isHidden() ? " " : "v", - QString::number(splitter->count()).toStdString().c_str()); - std::cout << buf.data() << "Splitter " - << ((splitter->orientation() == Qt::Vertical) ? "--" : "|") << " " - << (splitter->isHidden() ? " " : "v") << " " - << QString::number(splitter->count()).toStdString() << std::endl; -#endif - for (int i = 0; i < splitter->count(); ++i) - dumpRecursive(level + 1, splitter->widget(i)); - } else { - DockAreaWidget *dockArea = qobject_cast(widget); - if (!dockArea) - return; - -#ifdef ADS_DEBUG_PRINT - qDebug("%sDockArea", buf.data()); - std::cout << buf.data() << (dockArea->isHidden() ? " " : "v") - << (dockArea->openDockWidgetsCount() > 0 ? " " : "c") << " DockArea" - << std::endl; - buf.fill(' ', (level + 1) * 4); - for (int i = 0; i < dockArea->dockWidgetsCount(); ++i) { - std::cout << buf.data() << (i == dockArea->currentIndex() ? "*" : " "); - DockWidget *dockWidget = dockArea->dockWidget(i); - std::cout << (dockWidget->isHidden() ? " " : "v"); - std::cout << (dockWidget->isClosed() ? "c" : " ") << " "; - std::cout << dockWidget->windowTitle().toStdString() << std::endl; - } -#endif - } -#else - Q_UNUSED(level) - Q_UNUSED(widget) -#endif - } - - DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetArea area, - DockWidget *dockWidget, - DockAreaWidget *targetDockArea) - { - if (CenterDockWidgetArea == area) { - targetDockArea->addDockWidget(dockWidget); - targetDockArea->updateTitleBarVisibility(); - return targetDockArea; - } - - DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q); - newDockArea->addDockWidget(dockWidget); - auto insertParam = internal::dockAreaInsertParameters(area); - - QSplitter *targetAreaSplitter = internal::findParent(targetDockArea); - int index = targetAreaSplitter->indexOf(targetDockArea); - if (targetAreaSplitter->orientation() == insertParam.orientation()) { - qCInfo(adsLog) << "TargetAreaSplitter->orientation() == InsertParam.orientation()"; - targetAreaSplitter->insertWidget(index + insertParam.insertOffset(), newDockArea); - // do nothing, if flag is not enabled - if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) - adjustSplitterSizesOnInsertion(targetAreaSplitter); - } else { - qCInfo(adsLog) << "TargetAreaSplitter->orientation() != InsertParam.orientation()"; - auto targetAreaSizes = targetAreaSplitter->sizes(); - QSplitter *newSplitter = createSplitter(insertParam.orientation()); - newSplitter->addWidget(targetDockArea); - insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); - targetAreaSplitter->insertWidget(index, newSplitter); - if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) { - targetAreaSplitter->setSizes(targetAreaSizes); - adjustSplitterSizesOnInsertion(newSplitter); - } - } - - appendDockAreas({newDockArea}); - emitDockAreasAdded(); - return newDockArea; - } - - DockContainerWidget::DockContainerWidget(DockManager *dockManager, QWidget *parent) - : QFrame(parent) - , d(new DockContainerWidgetPrivate(this)) - { - d->m_dockManager = dockManager; - d->m_isFloating = floatingWidget() != nullptr; - - d->m_layout = new QGridLayout(); - d->m_layout->setContentsMargins(0, 1, 0, 1); - d->m_layout->setSpacing(0); - setLayout(d->m_layout); - - // The function d->createSplitter() accesses the config flags from dock - // manager which in turn requires a properly constructed dock manager. - // If this dock container is the dock manager, then it is not properly - // constructed yet because this base class constructor is called before - // the constructor of the DockManager private class - if (dockManager != this) { - d->m_dockManager->registerDockContainer(this); - createRootSplitter(); - } - } - - DockContainerWidget::~DockContainerWidget() - { - if (d->m_dockManager) - d->m_dockManager->removeDockContainer(this); - - delete d; - } - - DockAreaWidget *DockContainerWidget::addDockWidget(DockWidgetArea area, - DockWidget *dockWidget, - DockAreaWidget *dockAreaWidget) - { - DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); + DockWidget *droppedDockWidget = qobject_cast(widget); + DockAreaWidget *droppedDockArea = qobject_cast(widget); + DockAreaWidget *newDockArea; + if (droppedDockWidget) { + newDockArea = new DockAreaWidget(m_dockManager, q); + DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); if (oldDockArea) - oldDockArea->removeDockWidget(dockWidget); + oldDockArea->removeDockWidget(droppedDockWidget); - dockWidget->setDockManager(d->m_dockManager); - if (dockAreaWidget) - return d->addDockWidgetToDockArea(area, dockWidget, dockAreaWidget); + newDockArea->addDockWidget(droppedDockWidget); + } else { + droppedDockArea->dockContainer()->removeDockArea(droppedDockArea); + newDockArea = droppedDockArea; + } + + auto insertParam = internal::dockAreaInsertParameters(area); + QSplitter *targetAreaSplitter = internal::findParent(targetArea); + const int areaIndex = targetAreaSplitter->indexOf(targetArea); + auto sizes = targetAreaSplitter->sizes(); + if (targetAreaSplitter->orientation() == insertParam.orientation()) { + const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) + ? targetArea->width() + : targetArea->height(); + targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), newDockArea); + updateSplitterHandles(targetAreaSplitter); + const int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2; + sizes[areaIndex] = size; + sizes.insert(areaIndex, size); + } else { + const int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) + ? targetArea->width() + : targetArea->height(); + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + newSplitter->addWidget(targetArea); + insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); + updateSplitterHandles(newSplitter); + const int size = targetAreaSize / 2; + newSplitter->setSizes({size, size}); + targetAreaSplitter->insertWidget(areaIndex, newSplitter); + updateSplitterHandles(targetAreaSplitter); + } + targetAreaSplitter->setSizes(sizes); + + addDockAreasToList({newDockArea}); +} + +void DockContainerWidgetPrivate::moveToContainer(QWidget *widget, DockWidgetArea area) +{ + DockWidget *droppedDockWidget = qobject_cast(widget); + DockAreaWidget *droppedDockArea = qobject_cast(widget); + DockAreaWidget *newDockArea = nullptr; + + if (droppedDockWidget) { + newDockArea = new DockAreaWidget(m_dockManager, q); + DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); + if (oldDockArea) + oldDockArea->removeDockWidget(droppedDockWidget); + + newDockArea->addDockWidget(droppedDockWidget); + } else { + // We check, if we insert the dropped widget into the same place that + // it already has and do nothing, if it is the same place. It would + // also work without this check, but it looks nicer with the check + // because there will be no layout updates + auto splitter = internal::findParent(droppedDockArea); + auto insertParam = internal::dockAreaInsertParameters(area); + if (splitter == m_rootSplitter && insertParam.orientation() == splitter->orientation()) { + if (insertParam.append() && splitter->lastWidget() == droppedDockArea) + return; + else if (!insertParam.append() && splitter->firstWidget() == droppedDockArea) + return; + } + droppedDockArea->dockContainer()->removeDockArea(droppedDockArea); + newDockArea = droppedDockArea; + } + + addDockArea(newDockArea, area); + m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea; +} + +void DockContainerWidgetPrivate::moveIntoCenterOfSection(QWidget *widget, + DockAreaWidget *targetArea, + int tabIndex) +{ + auto droppedDockWidget = qobject_cast(widget); + auto droppedArea = qobject_cast(widget); + tabIndex = qMax(0, tabIndex); + + if (droppedDockWidget) { + DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); + if (oldDockArea == targetArea) + return; + + if (oldDockArea) + oldDockArea->removeDockWidget(droppedDockWidget); + + targetArea->insertDockWidget(tabIndex, droppedDockWidget, true); + } else { + QList newDockWidgets = droppedArea->dockWidgets(); + int newCurrentIndex = droppedArea->currentIndex(); + for (int i = 0; i < newDockWidgets.count(); ++i) { + DockWidget *dockWidget = newDockWidgets[i]; + targetArea->insertDockWidget(tabIndex + i, dockWidget, false); + } + targetArea->setCurrentIndex(tabIndex + newCurrentIndex); + droppedArea->dockContainer()->removeDockArea(droppedArea); + droppedArea->deleteLater(); + } + + targetArea->updateTitleBarVisibility(); + return; +} + +void DockContainerWidgetPrivate::moveToAutoHideSideBar(QWidget *widget, + DockWidgetArea area, + int tabIndex) +{ + DockWidget *droppedDockWidget = qobject_cast(widget); + DockAreaWidget *droppedDockArea = qobject_cast(widget); + auto sideBarLocation = internal::toSideBarLocation(area); + + if (droppedDockWidget) { + if (q == droppedDockWidget->dockContainer()) + droppedDockWidget->setAutoHide(true, sideBarLocation, tabIndex); else - return d->addDockWidgetToContainer(area, dockWidget); + q->createAndSetupAutoHideContainer(sideBarLocation, droppedDockWidget, tabIndex); + + } else { + if (q == droppedDockArea->dockContainer()) { + droppedDockArea->setAutoHide(true, sideBarLocation, tabIndex); + } else { + for (const auto dockWidget : droppedDockArea->openedDockWidgets()) { + if (!dockWidget->features().testFlag(DockWidget::DockWidgetPinnable)) + continue; + + q->createAndSetupAutoHideContainer(sideBarLocation, dockWidget, tabIndex++); + } + } + } +} + +void DockContainerWidgetPrivate::updateSplitterHandles(QSplitter *splitter) +{ + if (!m_dockManager->centralWidget() || !splitter) + return; + + for (int i = 0; i < splitter->count(); ++i) + splitter->setStretchFactor(i, widgetResizesWithContainer(splitter->widget(i)) ? 1 : 0); +} + +bool DockContainerWidgetPrivate::widgetResizesWithContainer(QWidget *widget) +{ + if (!m_dockManager->centralWidget()) + return true; + + auto area = qobject_cast(widget); + if (area) + return area->isCentralWidgetArea(); + + auto innerSplitter = qobject_cast(widget); + if (innerSplitter) + return innerSplitter->isResizingWithContainer(); + + return false; +} + +void DockContainerWidgetPrivate::addDockAreasToList(const QList newDockAreas) +{ + const int countBefore = m_dockAreas.count(); + const int newAreaCount = newDockAreas.count(); + appendDockAreas(newDockAreas); + // If the user dropped a floating widget that contains only one single + // visible dock area, then its title bar button TitleBarButtonUndock is + // likely hidden. We need to ensure, that it is visible + for (auto dockArea : newDockAreas) { + dockArea->titleBarButton(TitleBarButtonUndock)->setVisible(true); + dockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); } - void DockContainerWidget::removeDockWidget(DockWidget * dockWidget) - { - DockAreaWidget *area = dockWidget->dockAreaWidget(); - if (area) - area->removeDockWidget(dockWidget); + // We need to ensure, that the dock area title bar is visible. The title bar + // is invisible, if the dock are is a single dock area in a floating widget. + if (1 == countBefore) + m_dockAreas.at(0)->updateTitleBarVisibility(); + + if (1 == newAreaCount) + m_dockAreas.last()->updateTitleBarVisibility(); + + emitDockAreasAdded(); +} + +void DockContainerWidgetPrivate::appendDockAreas(const QList newDockAreas) +{ + m_dockAreas.append(newDockAreas); + for (auto dockArea : newDockAreas) { + QObject::connect(dockArea, + &DockAreaWidget::viewToggled, + q, + std::bind(&DockContainerWidgetPrivate::onDockAreaViewToggled, + this, + dockArea, + std::placeholders::_1)); + } +} + +void DockContainerWidgetPrivate::saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget) +{ + QSplitter *splitter = qobject_cast(widget); + if (splitter) { + stream.writeStartElement("splitter"); + stream.writeAttribute("orientation", + QVariant::fromValue(splitter->orientation()).toString()); + stream.writeAttribute("count", QString::number(splitter->count())); + qCInfo(adsLog) << "NodeSplitter orientation:" << splitter->orientation() + << "WidgetCount:" << splitter->count(); + for (int i = 0; i < splitter->count(); ++i) + saveChildNodesState(stream, splitter->widget(i)); + + stream.writeStartElement("sizes"); + QStringList sizes; + for (auto size : splitter->sizes()) + sizes.append(QString::number(size)); + + stream.writeCharacters(sizes.join(" ")); + stream.writeEndElement(); // sizes + stream.writeEndElement(); // splitter + } else { + DockAreaWidget *dockArea = qobject_cast(widget); + if (dockArea) + dockArea->saveState(stream); + } +} + +void DockContainerWidgetPrivate::saveAutoHideWidgetsState(QXmlStreamWriter &s) +{ + for (const auto sideTabBar : m_sideTabBarWidgets.values()) { + if (!sideTabBar->count()) + continue; + + sideTabBar->saveState(s); + } +} + +bool DockContainerWidgetPrivate::restoreChildNodes(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) +{ + bool result = true; + while (stateReader.readNextStartElement()) { + if (stateReader.name() == QLatin1String("splitter")) { + result = restoreSplitter(stateReader, createdWidget, testing); + qCInfo(adsLog) << "Splitter"; + } else if (stateReader.name() == QLatin1String("area")) { + result = restoreDockArea(stateReader, createdWidget, testing); + qCInfo(adsLog) << "DockAreaWidget"; + } else if (stateReader.name() == QLatin1String("sideBar")) { + result = restoreSideBar(stateReader, createdWidget, testing); + qCInfo(adsLog) << "SideBar"; + } else { + stateReader.skipCurrentElement(); + qCInfo(adsLog) << "Unknown element"; + } } - unsigned int DockContainerWidget::zOrderIndex() const { return d->m_zOrderIndex; } + return result; +} - bool DockContainerWidget::isInFrontOf(DockContainerWidget *other) const - { - return this->zOrderIndex() > other->zOrderIndex(); +bool DockContainerWidgetPrivate::restoreSplitter(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) +{ + QVariant orientationVar = QVariant(stateReader.attributes().value("orientation").toString()); + + // Check if the orientation string is convertable + if (!orientationVar.canConvert()) + return false; + + Qt::Orientation orientation = orientationVar.value(); + + bool ok; + int widgetCount = stateReader.attributes().value("count").toInt(&ok); + if (!ok) + return false; + + qCInfo(adsLog) << "Restore NodeSplitter Orientation:" << orientation + << "WidgetCount:" << widgetCount; + QSplitter *splitter = nullptr; + if (!testing) + splitter = createSplitter(orientation); + + bool visible = false; + QList sizes; + while (stateReader.readNextStartElement()) { + QWidget *childNode = nullptr; + bool result = true; + if (stateReader.name() == QLatin1String("splitter")) { + result = restoreSplitter(stateReader, childNode, testing); + } else if (stateReader.name() == QLatin1String("area")) { + result = restoreDockArea(stateReader, childNode, testing); + } else if (stateReader.name() == QLatin1String("sizes")) { + QString size = stateReader.readElementText().trimmed(); + qCInfo(adsLog) << "Size:" << size; + QTextStream textStream(&size); + while (!textStream.atEnd()) { + int value; + textStream >> value; + sizes.append(value); + } + } else { + stateReader.skipCurrentElement(); + } + + if (!result) + return false; + + if (testing || !childNode) + continue; + + qCInfo(adsLog) << "ChildNode isVisible" << childNode->isVisible() << "isVisibleTo" + << childNode->isVisibleTo(splitter); + splitter->addWidget(childNode); + visible |= childNode->isVisibleTo(splitter); } - bool DockContainerWidget::event(QEvent *event) - { - bool result = QWidget::event(event); - if (event->type() == QEvent::WindowActivate) - d->m_zOrderIndex = ++zOrderCounter; - else if (event->type() == QEvent::Show && !d->m_zOrderIndex) - d->m_zOrderIndex = ++zOrderCounter; + if (sizes.count() != widgetCount) + return false; - return result; + if (!testing) { + if (!splitter->count()) { + delete splitter; + splitter = nullptr; + } else { + splitter->setSizes(sizes); + splitter->setVisible(visible); + } + createdWidget = splitter; + } else { + createdWidget = nullptr; } - void DockContainerWidget::addDockArea(DockAreaWidget *dockAreaWidget, DockWidgetArea area) - { - DockContainerWidget *container = dockAreaWidget->dockContainer(); - if (container && container != this) - container->removeDockArea(dockAreaWidget); + return true; +} - d->addDockArea(dockAreaWidget, area); +bool DockContainerWidgetPrivate::restoreDockArea(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) +{ + DockAreaWidget *dockArea = nullptr; + auto result = DockAreaWidget::restoreState(stateReader, dockArea, testing, q); + if (result && dockArea) + appendDockAreas({dockArea}); + + createdWidget = dockArea; + return result; +} + +bool DockContainerWidgetPrivate::restoreSideBar(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) +{ + Q_UNUSED(createdWidget) + // Simply ignore side bar auto hide widgets from saved state if auto hide support is disabled + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) + return true; + + bool ok; + auto area = (SideBarLocation) stateReader.attributes().value("area").toInt(&ok); + if (!ok) + return false; + + while (stateReader.readNextStartElement()) { + if (stateReader.name() != QLatin1String("widget")) + continue; + + auto name = stateReader.attributes().value("name"); + if (name.isEmpty()) + return false; + + bool ok; + bool closed = stateReader.attributes().value("closed").toInt(&ok); + if (!ok) + return false; + + int size = stateReader.attributes().value("size").toInt(&ok); + if (!ok) + return false; + + stateReader.skipCurrentElement(); + DockWidget *dockWidget = m_dockManager->findDockWidget(name.toString()); + if (!dockWidget || testing) + continue; + + auto sideBar = q->autoHideSideBar(area); + AutoHideDockContainer *autoHideContainer; + if (dockWidget->isAutoHide()) { + autoHideContainer = dockWidget->autoHideDockContainer(); + if (autoHideContainer->autoHideSideBar() != sideBar) + sideBar->addAutoHideWidget(autoHideContainer); + } else { + autoHideContainer = sideBar->insertDockWidget(-1, dockWidget); + } + autoHideContainer->setSize(size); + dockWidget->setProperty(internal::g_closedProperty, closed); + dockWidget->setProperty(internal::g_dirtyProperty, false); } - void DockContainerWidget::removeDockArea(DockAreaWidget *area) - { - qCInfo(adsLog) << Q_FUNC_INFO; - area->disconnect(this); - d->m_dockAreas.removeAll(area); - DockSplitter *splitter = internal::findParent(area); + return true; +} - // Remove area from parent splitter and recursively hide tree of parent - // splitters if it has no visible content - area->setParent(nullptr); - internal::hideEmptyParentSplitters(splitter); +DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToContainer(DockWidgetArea area, + DockWidget *dockWidget) +{ + DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q); + newDockArea->addDockWidget(dockWidget); + addDockArea(newDockArea, area); + newDockArea->updateTitleBarVisibility(); + m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea; + return newDockArea; +} - // Remove this area from cached areas - const auto &cache = d->m_lastAddedAreaCache; - if (auto p = std::find(cache, cache + sizeof(cache) / sizeof(cache[0]), area)) - d->m_lastAddedAreaCache[std::distance(cache, p)] = nullptr; +void DockContainerWidgetPrivate::addDockArea(DockAreaWidget *newDockArea, DockWidgetArea area) +{ + auto insertParam = internal::dockAreaInsertParameters(area); + // As long as we have only one dock area in the splitter we can adjust its orientation + if (m_dockAreas.count() <= 1) + m_rootSplitter->setOrientation(insertParam.orientation()); - // If splitter has more than 1 widgets, we are finished and can leave - if (splitter->count() > 1) { + auto *splitter = m_rootSplitter; + if (splitter->orientation() == insertParam.orientation()) { + insertWidgetIntoSplitter(splitter, newDockArea, insertParam.append()); + updateSplitterHandles(splitter); + if (splitter->isHidden()) + splitter->show(); + + } else { + auto *newSplitter = createSplitter(insertParam.orientation()); + if (insertParam.append()) { + QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); + newSplitter->addWidget(splitter); + newSplitter->addWidget(newDockArea); + updateSplitterHandles(newSplitter); + delete layoutItem; + } else { + newSplitter->addWidget(newDockArea); + QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); + newSplitter->addWidget(splitter); + updateSplitterHandles(newSplitter); + delete layoutItem; + } + m_rootSplitter = newSplitter; + } + + addDockAreasToList({newDockArea}); +} + +void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget *widget) const +{ +#if defined(QT_DEBUG) + QSplitter *splitter = qobject_cast(widget); + QByteArray buf; + buf.fill(' ', level * 4); + if (splitter) { +#ifdef ADS_DEBUG_PRINT + qDebug("%sSplitter %s v: %s c: %s", + buf.data(), + (splitter->orientation() == Qt::Vertical) ? "--" : "|", + splitter->isHidden() ? " " : "v", + QString::number(splitter->count()).toStdString().c_str()); + std::cout << buf.data() << "Splitter " + << ((splitter->orientation() == Qt::Vertical) ? "--" : "|") << " " + << (splitter->isHidden() ? " " : "v") << " " + << QString::number(splitter->count()).toStdString() << std::endl; +#endif + for (int i = 0; i < splitter->count(); ++i) + dumpRecursive(level + 1, splitter->widget(i)); + } else { + DockAreaWidget *dockArea = qobject_cast(widget); + if (!dockArea) + return; + +#ifdef ADS_DEBUG_PRINT + qDebug("%sDockArea", buf.data()); + std::cout << buf.data() << (dockArea->isHidden() ? " " : "v") + << (dockArea->openDockWidgetsCount() > 0 ? " " : "c") << " DockArea" << std::endl; + buf.fill(' ', (level + 1) * 4); + for (int i = 0; i < dockArea->dockWidgetsCount(); ++i) { + std::cout << buf.data() << (i == dockArea->currentIndex() ? "*" : " "); + DockWidget *dockWidget = dockArea->dockWidget(i); + std::cout << (dockWidget->isHidden() ? " " : "v"); + std::cout << (dockWidget->isClosed() ? "c" : " ") << " "; + std::cout << dockWidget->windowTitle().toStdString() << std::endl; + } +#endif + } +#else + Q_UNUSED(level) + Q_UNUSED(widget) +#endif +} + +DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *targetDockArea, + int index) +{ + if (CenterDockWidgetArea == area) { + targetDockArea->insertDockWidget(index, dockWidget); + targetDockArea->updateTitleBarVisibility(); + return targetDockArea; + } + + DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q); + newDockArea->addDockWidget(dockWidget); + auto insertParam = internal::dockAreaInsertParameters(area); + + QSplitter *targetAreaSplitter = internal::findParent(targetDockArea); + int targetIndex = targetAreaSplitter->indexOf(targetDockArea); + if (targetAreaSplitter->orientation() == insertParam.orientation()) { + qCInfo(adsLog) << "TargetAreaSplitter->orientation() == InsertParam.orientation()"; + targetAreaSplitter->insertWidget(targetIndex + insertParam.insertOffset(), newDockArea); + // do nothing, if flag is not enabled + if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) + adjustSplitterSizesOnInsertion(targetAreaSplitter); + } else { + qCInfo(adsLog) << "TargetAreaSplitter->orientation() != InsertParam.orientation()"; + auto targetAreaSizes = targetAreaSplitter->sizes(); + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + newSplitter->addWidget(targetDockArea); + insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); + updateSplitterHandles(newSplitter); + targetAreaSplitter->insertWidget(targetIndex, newSplitter); + updateSplitterHandles(targetAreaSplitter); + if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) { + targetAreaSplitter->setSizes(targetAreaSizes); + adjustSplitterSizesOnInsertion(newSplitter); + } + } + + appendDockAreas({newDockArea}); + return newDockArea; +} + +DockContainerWidget::DockContainerWidget(DockManager *dockManager, QWidget *parent) + : QFrame(parent) + , d(new DockContainerWidgetPrivate(this)) +{ + d->m_dockManager = dockManager; + d->m_isFloating = floatingWidget() != nullptr; + + d->m_layout = new QGridLayout(); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + d->m_layout->setColumnStretch(1, 1); + d->m_layout->setRowStretch(1, 1); + setLayout(d->m_layout); + + // The function d->createSplitter() accesses the config flags from dock manager which in turn + // requires a properly constructed dock manager. If this dock container is the dock manager, + // then it is not properly constructed yet because this base class constructor is called before + // the constructor of the DockManager private class + if (dockManager != this) { + d->m_dockManager->registerDockContainer(this); + createRootSplitter(); + createSideTabBarWidgets(); + } +} + +DockContainerWidget::~DockContainerWidget() +{ + if (d->m_dockManager) + d->m_dockManager->removeDockContainer(this); + + delete d; +} + +DockAreaWidget *DockContainerWidget::addDockWidget(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *dockAreaWidget, + int index) +{ + auto currentTopLevelDockWIdget = topLevelDockWidget(); + DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); + if (oldDockArea) + oldDockArea->removeDockWidget(dockWidget); + + dockWidget->setDockManager(d->m_dockManager); + DockAreaWidget *dockArea; + if (dockAreaWidget) + dockArea = d->addDockWidgetToDockArea(area, dockWidget, dockAreaWidget, index); + else + dockArea = d->addDockWidgetToContainer(area, dockWidget); + + if (currentTopLevelDockWIdget) { + auto newTopLevelDockWidget = topLevelDockWidget(); + // If the container contained only one visible dock widget, we need to emit a top level + // event for this widget because it is not the one and only visible docked widget anymore. + if (!newTopLevelDockWidget) + DockWidget::emitTopLevelEventForWidget(currentTopLevelDockWIdget, false); + } + + return dockArea; +} + +AutoHideDockContainer *DockContainerWidget::createAndSetupAutoHideContainer(SideBarLocation area, + DockWidget *dockWidget, + int tabIndex) +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) { + Q_ASSERT_X(false, + "CDockContainerWidget::createAndInitializeDockWidgetOverlayContainer", + "Requested area does not exist in config"); + return nullptr; + } + if (d->m_dockManager != dockWidget->dockManager()) { + dockWidget->setDockManager( + d->m_dockManager); // Auto hide Dock Container needs a valid dock manager + } + + return autoHideSideBar(area)->insertDockWidget(tabIndex, dockWidget); +} + +void DockContainerWidget::removeDockWidget(DockWidget *dockWidget) +{ + DockAreaWidget *area = dockWidget->dockAreaWidget(); + if (area) + area->removeDockWidget(dockWidget); +} + +unsigned int DockContainerWidget::zOrderIndex() const +{ + return d->m_zOrderIndex; +} + +bool DockContainerWidget::isInFrontOf(DockContainerWidget *other) const +{ + return this->zOrderIndex() > other->zOrderIndex(); +} + +bool DockContainerWidget::event(QEvent *event) +{ + bool result = QWidget::event(event); + if (event->type() == QEvent::WindowActivate) + d->m_zOrderIndex = ++g_zOrderCounter; + else if (event->type() == QEvent::Show && !d->m_zOrderIndex) + d->m_zOrderIndex = ++g_zOrderCounter; + + return result; +} + +QList DockContainerWidget::autoHideWidgets() const +{ + return d->m_autoHideWidgets; +} + +void DockContainerWidget::addDockArea(DockAreaWidget *dockAreaWidget, DockWidgetArea area) +{ + DockContainerWidget *container = dockAreaWidget->dockContainer(); + if (container && container != this) + container->removeDockArea(dockAreaWidget); + + d->addDockArea(dockAreaWidget, area); +} + +void DockContainerWidget::removeDockArea(DockAreaWidget *area) +{ + qCInfo(adsLog) << Q_FUNC_INFO; + + // If it is an auto hide area, then there is nothing much to do + if (area->isAutoHide()) { + area->setAutoHideDockContainer(nullptr); + return; + } + + area->disconnect(this); + d->m_dockAreas.removeAll(area); + DockSplitter *splitter = internal::findParent(area); + + // Remove area from parent splitter and recursively hide tree of parent + // splitters if it has no visible content + area->setParent(nullptr); + internal::hideEmptyParentSplitters(splitter); + + // Remove this area from cached areas + auto p = std::find(std::begin(d->m_lastAddedAreaCache), std::end(d->m_lastAddedAreaCache), area); + if (p != std::end(d->m_lastAddedAreaCache)) + *p = nullptr; + + // If splitter has more than 1 widget, we are finished and can leave + if (splitter->count() > 1) { + updateSplitterHandles(splitter); + emitAndExit(); + return; + } + + // If this is the RootSplitter we need to remove empty splitters to + // avoid too many empty splitters + if (splitter == d->m_rootSplitter) { + qCInfo(adsLog) << "Removed from RootSplitter"; + // If splitter is empty, we are finished + if (!splitter->count()) { + splitter->hide(); + updateSplitterHandles(splitter); emitAndExit(); return; } - // If this is the RootSplitter we need to remove empty splitters to - // avoid too many empty splitters - if (splitter == d->m_rootSplitter) { - qCInfo(adsLog) << "Removed from RootSplitter"; - // If splitter is empty, we are finished - if (!splitter->count()) { - splitter->hide(); - emitAndExit(); - return; - } - - QWidget *widget = splitter->widget(0); - QSplitter *childSplitter = qobject_cast(widget); - // If the one and only content widget of the splitter is not a splitter - // then we are finished - if (!childSplitter) { - emitAndExit(); - return; - } - - // We replace the superfluous RootSplitter with the ChildSplitter - childSplitter->setParent(nullptr); - QLayoutItem *layoutItem = d->m_layout->replaceWidget(splitter, childSplitter); - d->m_rootSplitter = childSplitter; - delete layoutItem; - qCInfo(adsLog) << "RootSplitter replaced by child splitter"; - } else if (splitter->count() == 1) { - qCInfo(adsLog) << "Replacing splitter with content"; - QSplitter *parentSplitter = internal::findParent(splitter); - auto sizes = parentSplitter->sizes(); - QWidget *widget = splitter->widget(0); - widget->setParent(this); - internal::replaceSplitterWidget(parentSplitter, splitter, widget); - parentSplitter->setSizes(sizes); + QWidget *widget = splitter->widget(0); + auto *childSplitter = qobject_cast(widget); + // If the one and only content widget of the splitter is not a splitter + // then we are finished + if (!childSplitter) { + updateSplitterHandles(splitter); + emitAndExit(); + return; } - delete splitter; + // We replace the superfluous RootSplitter with the ChildSplitter + childSplitter->setParent(nullptr); + QLayoutItem *layoutItem = d->m_layout->replaceWidget(splitter, childSplitter); + d->m_rootSplitter = childSplitter; + delete layoutItem; + qCInfo(adsLog) << "RootSplitter replaced by child splitter"; + } else if (splitter->count() == 1) { + qCInfo(adsLog) << "Replacing splitter with content"; + auto *parentSplitter = internal::findParent(splitter); + auto sizes = parentSplitter->sizes(); + QWidget *widget = splitter->widget(0); + widget->setParent(this); + internal::replaceSplitterWidget(parentSplitter, splitter, widget); + parentSplitter->setSizes(sizes); } - void DockContainerWidget::emitAndExit() const - { - DockWidget *topLevelWidget = topLevelDockWidget(); + delete splitter; + splitter = nullptr; - // Updated the title bar visibility of the dock widget if there is only - // one single visible dock widget - DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); - dumpLayout(); - d->emitDockAreasRemoved(); + // TODO Check goto... + updateSplitterHandles(splitter); + emitAndExit(); +} + +void DockContainerWidget::emitAndExit() const +{ + DockWidget *topLevelWidget = topLevelDockWidget(); + + // Updated the title bar visibility of the dock widget if there is only + // one single visible dock widget + DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); + dumpLayout(); + d->emitDockAreasRemoved(); +} + +DockAreaWidget *DockContainerWidget::dockAreaAt(const QPoint &globalPosition) const +{ + for (auto dockArea : std::as_const(d->m_dockAreas)) { + if (dockArea->isVisible() + && dockArea->rect().contains(dockArea->mapFromGlobal(globalPosition))) + return dockArea; } - DockAreaWidget *DockContainerWidget::dockAreaAt(const QPoint &globalPosition) const - { - for (auto dockArea : std::as_const(d->m_dockAreas)) { - if (dockArea->isVisible() - && dockArea->rect().contains(dockArea->mapFromGlobal(globalPosition))) - return dockArea; + return nullptr; +} + +DockAreaWidget *DockContainerWidget::dockArea(int index) const +{ + return (index < dockAreaCount()) ? d->m_dockAreas[index] : nullptr; +} + +bool DockContainerWidget::isFloating() const +{ + return d->m_isFloating; +} + +int DockContainerWidget::dockAreaCount() const +{ + return d->m_dockAreas.count(); +} + +int DockContainerWidget::visibleDockAreaCount() const +{ + int result = 0; + for (auto dockArea : std::as_const(d->m_dockAreas)) + result += dockArea->isHidden() ? 0 : 1; + + return result; + + // TODO Cache or precalculate this to speed it up because it is used during + // movement of floating widget + //return d->visibleDockAreaCount(); +} + +void DockContainerWidget::dropFloatingWidget(FloatingDockContainer *floatingWidget, + const QPoint &targetPosition) +{ + qCInfo(adsLog) << Q_FUNC_INFO; + DockWidget *singleDroppedDockWidget = floatingWidget->topLevelDockWidget(); + DockWidget *singleDockWidget = topLevelDockWidget(); + auto dropArea = InvalidDockWidgetArea; + auto containerDropArea = d->m_dockManager->containerOverlay()->dropAreaUnderCursor(); + bool dropped = false; + + DockAreaWidget *dockArea = dockAreaAt(targetPosition); + // Mouse is over dock area + if (dockArea) { + auto dropOverlay = d->m_dockManager->dockAreaOverlay(); + dropOverlay->setAllowedAreas(dockArea->allowedAreas()); + dropArea = dropOverlay->showOverlay(dockArea); + if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) + dropArea = InvalidDockWidgetArea; + + if (dropArea != InvalidDockWidgetArea) { + qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; + int tabIndex = d->m_dockManager->dockAreaOverlay()->tabIndexUnderCursor(); + d->dropIntoSection(floatingWidget, dockArea, dropArea, tabIndex); + dropped = true; } - - return nullptr; } - DockAreaWidget *DockContainerWidget::dockArea(int index) const - { - return (index < dockAreaCount()) ? d->m_dockAreas[index] : nullptr; - } + // Mouse is over container or auto hide side bar + if (InvalidDockWidgetArea == dropArea && InvalidDockWidgetArea != containerDropArea) { + qCInfo(adsLog) << "Container Drop Content: " << containerDropArea; - bool DockContainerWidget::isFloating() const { return d->m_isFloating; } - - int DockContainerWidget::dockAreaCount() const { return d->m_dockAreas.count(); } - - int DockContainerWidget::visibleDockAreaCount() const - { - int result = 0; - for (auto dockArea : std::as_const(d->m_dockAreas)) - result += dockArea->isHidden() ? 0 : 1; - - return result; - - // TODO Cache or precalculate this to speed it up because it is used during - // movement of floating widget - //return d->visibleDockAreaCount(); - } - - void DockContainerWidget::dropFloatingWidget(FloatingDockContainer *floatingWidget, - const QPoint &targetPosition) - { - qCInfo(adsLog) << Q_FUNC_INFO; - DockWidget *singleDroppedDockWidget = floatingWidget->topLevelDockWidget(); - DockWidget *singleDockWidget = topLevelDockWidget(); - DockAreaWidget *dockArea = dockAreaAt(targetPosition); - auto dropArea = InvalidDockWidgetArea; - auto containerDropArea = d->m_dockManager->containerOverlay()->dropAreaUnderCursor(); - bool dropped = false; - - if (dockArea) { - auto dropOverlay = d->m_dockManager->dockAreaOverlay(); - dropOverlay->setAllowedAreas(dockArea->allowedAreas()); - dropArea = dropOverlay->showOverlay(dockArea); - if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) - dropArea = InvalidDockWidgetArea; - - if (dropArea != InvalidDockWidgetArea) { - qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; - d->dropIntoSection(floatingWidget, dockArea, dropArea); - dropped = true; - } - } - - // mouse is over container - if (InvalidDockWidgetArea == dropArea) { - dropArea = containerDropArea; - qCInfo(adsLog) << "Container Drop Content: " << dropArea; - if (dropArea != InvalidDockWidgetArea) { - d->dropIntoContainer(floatingWidget, dropArea); - dropped = true; - } - } - - if (dropped) { - floatingWidget->deleteLater(); - - // If we dropped a floating widget with only one single dock widget, then we - // drop a top level widget that changes from floating to docked now - DockWidget::emitTopLevelEventForWidget(singleDroppedDockWidget, false); - - // If there was a top level widget before the drop, then it is not top - // level widget anymore - DockWidget::emitTopLevelEventForWidget(singleDockWidget, false); - } - window()->activateWindow(); - if (singleDroppedDockWidget) - d->m_dockManager->notifyWidgetOrAreaRelocation(singleDroppedDockWidget); - - d->m_dockManager->notifyFloatingWidgetDrop(floatingWidget); - } - - void DockContainerWidget::dropWidget(QWidget *widget, DockWidgetArea dropArea, DockAreaWidget *targetAreaWidget) - { - DockWidget *singleDockWidget = topLevelDockWidget(); - if (targetAreaWidget) - d->moveToNewSection(widget, targetAreaWidget, dropArea); + if (internal::isSideBarArea(containerDropArea)) + d->dropIntoAutoHideSideBar(floatingWidget, containerDropArea); else - d->moveToContainer(widget, dropArea); + d->dropIntoContainer(floatingWidget, containerDropArea); + + dropped = true; + } + + // Remove the auto hide widgets from the FloatingWidget and insert them into this widget + for (auto autohideWidget : floatingWidget->dockContainer()->autoHideWidgets()) { + auto sideBar = autoHideSideBar(autohideWidget->sideBarLocation()); + sideBar->addAutoHideWidget(autohideWidget); + } + + if (dropped) { + // Fix https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351 + floatingWidget->hideAndDeleteLater(); + + // If we dropped a floating widget with only one single dock widget, then we + // drop a top level widget that changes from floating to docked now + DockWidget::emitTopLevelEventForWidget(singleDroppedDockWidget, false); // If there was a top level widget before the drop, then it is not top // level widget anymore DockWidget::emitTopLevelEventForWidget(singleDockWidget, false); - DockWidget *dockWidget = qobject_cast(widget); - if (!dockWidget) - { - DockAreaWidget *dockArea = qobject_cast(widget); - auto openDockWidgets = dockArea->openedDockWidgets(); - if (openDockWidgets.count() == 1) - dockWidget = openDockWidgets[0]; - } - - window()->activateWindow(); - d->m_dockManager->notifyWidgetOrAreaRelocation(dockWidget); } - QList DockContainerWidget::openedDockAreas() const - { - QList result; - for (auto dockArea : std::as_const(d->m_dockAreas)) { - if (!dockArea->isHidden()) - result.append(dockArea); - } + window()->activateWindow(); + if (singleDroppedDockWidget) + d->m_dockManager->notifyWidgetOrAreaRelocation(singleDroppedDockWidget); - return result; + d->m_dockManager->notifyFloatingWidgetDrop(floatingWidget); +} + +void DockContainerWidget::dropWidget(QWidget *widget, + DockWidgetArea dropArea, + DockAreaWidget *targetAreaWidget, + int tabIndex) +{ + DockWidget *singleDockWidget = topLevelDockWidget(); + if (targetAreaWidget) + d->moveToNewSection(widget, targetAreaWidget, dropArea, tabIndex); + else if (internal::isSideBarArea(dropArea)) + d->moveToAutoHideSideBar(widget, dropArea, tabIndex); + else + d->moveToContainer(widget, dropArea); + + // If there was a top level widget before the drop, then it is not top + // level widget anymore + DockWidget::emitTopLevelEventForWidget(singleDockWidget, false); + + window()->activateWindow(); + d->m_dockManager->notifyWidgetOrAreaRelocation(widget); +} + +QList DockContainerWidget::openedDockAreas() const +{ + QList result; + for (auto dockArea : std::as_const(d->m_dockAreas)) { + if (!dockArea->isHidden()) + result.append(dockArea); } - void DockContainerWidget::saveState(QXmlStreamWriter &stream) const - { - qCInfo(adsLog) << Q_FUNC_INFO << "isFloating " << isFloating(); + return result; +} - stream.writeStartElement("container"); - stream.writeAttribute("floating", QVariant::fromValue(isFloating()).toString()); - if (isFloating()) { - FloatingDockContainer *floatingDockContainer = floatingWidget(); - QByteArray geometry = floatingDockContainer->saveGeometry(); - stream.writeTextElement("geometry", QString::fromLatin1(geometry.toBase64())); - } - d->saveChildNodesState(stream, d->m_rootSplitter); - stream.writeEndElement(); +QList DockContainerWidget::openedDockWidgets() const +{ + QList dockWidgetList; + for (auto dockArea : d->m_dockAreas) { + if (!dockArea->isHidden()) + dockWidgetList.append(dockArea->openedDockWidgets()); } + return dockWidgetList; +} - bool DockContainerWidget::restoreState(DockingStateReader &stateReader, bool testing) - { - QVariant floatingVar = QVariant(stateReader.attributes().value("floating").toString()); - if (!floatingVar.canConvert()) - return false; - - bool isFloating = floatingVar.value(); - qCInfo(adsLog) << "Restore DockContainerWidget Floating" << isFloating; - - QWidget *newRootSplitter{}; - if (!testing) { - d->m_visibleDockAreaCount = -1; // invalidate the dock area count - d->m_dockAreas.clear(); - std::fill(std::begin(d->m_lastAddedAreaCache), - std::end(d->m_lastAddedAreaCache), - nullptr); - } - - if (isFloating) { - qCInfo(adsLog) << "Restore floating widget"; - if (!stateReader.readNextStartElement() || stateReader.name() != QLatin1String("geometry")) - return false; - - QByteArray geometryString = stateReader - .readElementText( - DockingStateReader::ErrorOnUnexpectedElement) - .toLocal8Bit(); - QByteArray geometry = QByteArray::fromBase64(geometryString); - if (geometry.isEmpty()) - return false; - - if (!testing) { - FloatingDockContainer *floatingDockContainer = floatingWidget(); - floatingDockContainer->restoreGeometry(geometry); - } - } - - if (!d->restoreChildNodes(stateReader, newRootSplitter, testing)) - return false; - - if (testing) +bool DockContainerWidget::hasOpenDockAreas() const +{ + for (auto dockArea : d->m_dockAreas) { + if (!dockArea->isHidden()) return true; - - // If the root splitter is empty, rostoreChildNodes returns a 0 pointer - // and we need to create a new empty root splitter - if (!newRootSplitter) - newRootSplitter = d->createSplitter(Qt::Horizontal); - - d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter); - QSplitter *oldRoot = d->m_rootSplitter; - d->m_rootSplitter = qobject_cast(newRootSplitter); - oldRoot->deleteLater(); - - return true; } - QSplitter *DockContainerWidget::rootSplitter() const { return d->m_rootSplitter; } + return false; +} - void DockContainerWidget::createRootSplitter() - { - if (d->m_rootSplitter) - return; +void DockContainerWidget::saveState(QXmlStreamWriter &stream) const +{ + qCInfo(adsLog) << Q_FUNC_INFO << "isFloating " << isFloating(); - d->m_rootSplitter = d->createSplitter(Qt::Horizontal); - d->m_layout->addWidget(d->m_rootSplitter); + stream.writeStartElement("container"); + stream.writeAttribute("floating", QVariant::fromValue(isFloating()).toString()); + if (isFloating()) { + FloatingDockContainer *floatingDockContainer = floatingWidget(); + QByteArray geometry = floatingDockContainer->saveGeometry(); + stream.writeTextElement("geometry", QString::fromLatin1(geometry.toBase64())); + } + d->saveChildNodesState(stream, d->m_rootSplitter); + d->saveAutoHideWidgetsState(stream); + stream.writeEndElement(); +} + +bool DockContainerWidget::restoreState(DockingStateReader &stateReader, bool testing) +{ + QVariant floatingVar = QVariant(stateReader.attributes().value("floating").toString()); + if (!floatingVar.canConvert()) + return false; + + bool isFloating = floatingVar.value(); + qCInfo(adsLog) << "Restore DockContainerWidget Floating" << isFloating; + + QWidget *newRootSplitter{}; + if (!testing) { + d->m_visibleDockAreaCount = -1; // invalidate the dock area count + d->m_dockAreas.clear(); + std::fill(std::begin(d->m_lastAddedAreaCache), std::end(d->m_lastAddedAreaCache), nullptr); } - void DockContainerWidget::dumpLayout() const - { -#if (ADS_DEBUG_LEVEL > 0) - qDebug("\n\nDumping layout --------------------------"); - std::cout << "\n\nDumping layout --------------------------" << std::endl; - d->dumpRecursive(0, d->m_rootSplitter); - qDebug("--------------------------\n\n"); - std::cout << "--------------------------\n\n" << std::endl; -#endif - } - - DockAreaWidget *DockContainerWidget::lastAddedDockAreaWidget(DockWidgetArea area) const - { - return d->m_lastAddedAreaCache[areaIdToIndex(area)]; - } - - bool DockContainerWidget::hasTopLevelDockWidget() const - { - auto dockAreas = openedDockAreas(); - if (dockAreas.count() != 1) + if (isFloating) { + qCInfo(adsLog) << "Restore floating widget"; + if (!stateReader.readNextStartElement() || stateReader.name() != QLatin1String("geometry")) return false; - return dockAreas[0]->openDockWidgetsCount() == 1; - } + QByteArray geometryString + = stateReader.readElementText(DockingStateReader::ErrorOnUnexpectedElement).toLocal8Bit(); + QByteArray geometry = QByteArray::fromBase64(geometryString); + if (geometry.isEmpty()) + return false; - DockWidget *DockContainerWidget::topLevelDockWidget() const - { - auto dockArea = topLevelDockArea(); - if (!dockArea) - return nullptr; - - auto dockWidgets = dockArea->openedDockWidgets(); - if (dockWidgets.count() != 1) - return nullptr; - - return dockWidgets[0]; - } - - DockAreaWidget *DockContainerWidget::topLevelDockArea() const - { - auto dockAreas = openedDockAreas(); - if (dockAreas.count() != 1) - return nullptr; - - return dockAreas[0]; - } - - QList DockContainerWidget::dockWidgets() const - { - QList result; - for (const auto dockArea : std::as_const(d->m_dockAreas)) - result.append(dockArea->dockWidgets()); - - return result; - } - - DockWidget::DockWidgetFeatures DockContainerWidget::features() const - { - DockWidget::DockWidgetFeatures features(DockWidget::AllDockWidgetFeatures); - for (const auto dockArea : std::as_const(d->m_dockAreas)) - features &= dockArea->features(); - - return features; - } - - FloatingDockContainer *DockContainerWidget::floatingWidget() const - { - return internal::findParent(this); - } - - void DockContainerWidget::closeOtherAreas(DockAreaWidget *keepOpenArea) - { - for (const auto dockArea : std::as_const(d->m_dockAreas)) { - if (dockArea == keepOpenArea) - continue; - - if (!dockArea->features(BitwiseAnd).testFlag(DockWidget::DockWidgetClosable)) - continue; - - // We do not close areas with widgets with custom close handling - if (dockArea->features(BitwiseOr).testFlag(DockWidget::CustomCloseHandling)) - continue; - - dockArea->closeArea(); + if (!testing) { + FloatingDockContainer *floatingDockContainer = floatingWidget(); + if (floatingDockContainer) + floatingDockContainer->restoreGeometry(geometry); } } + if (!d->restoreChildNodes(stateReader, newRootSplitter, testing)) + return false; + + if (testing) + return true; + + // If the root splitter is empty, restoreChildNodes returns a nullptr + // and we need to create a new empty root splitter + if (!newRootSplitter) + newRootSplitter = d->createSplitter(Qt::Horizontal); + + d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter); + QSplitter *oldRoot = d->m_rootSplitter; + d->m_rootSplitter = qobject_cast(newRootSplitter); + oldRoot->deleteLater(); + + return true; +} + +QSplitter *DockContainerWidget::rootSplitter() const +{ + return d->m_rootSplitter; +} + +void DockContainerWidget::createRootSplitter() +{ + if (d->m_rootSplitter) + return; + + d->m_rootSplitter = d->createSplitter(Qt::Horizontal); + // Add it to the center - the 0 and 2 indices are used for the SideTabBar widgets + d->m_layout->addWidget(d->m_rootSplitter, 1, 1); +} + +void DockContainerWidget::createSideTabBarWidgets() +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) + return; + + { + auto area = SideBarLocation::SideBarLeft; + d->m_sideTabBarWidgets[area] = new AutoHideSideBar(this, area); + d->m_layout->addWidget(d->m_sideTabBarWidgets[area], 1, 0); + } + + { + auto area = SideBarLocation::SideBarRight; + d->m_sideTabBarWidgets[area] = new AutoHideSideBar(this, area); + d->m_layout->addWidget(d->m_sideTabBarWidgets[area], 1, 2); + } + + { + auto area = SideBarLocation::SideBarBottom; + d->m_sideTabBarWidgets[area] = new AutoHideSideBar(this, area); + d->m_layout->addWidget(d->m_sideTabBarWidgets[area], 2, 1); + } + + { + auto area = SideBarLocation::SideBarTop; + d->m_sideTabBarWidgets[area] = new AutoHideSideBar(this, area); + d->m_layout->addWidget(d->m_sideTabBarWidgets[area], 0, 1); + } +} + +void DockContainerWidget::dumpLayout() const +{ +#if (ADS_DEBUG_LEVEL > 0) + qDebug("\n\nDumping layout --------------------------"); + std::cout << "\n\nDumping layout --------------------------" << std::endl; + d->dumpRecursive(0, d->m_rootSplitter); + qDebug("--------------------------\n\n"); + std::cout << "--------------------------\n\n" << std::endl; +#endif +} + +DockAreaWidget *DockContainerWidget::lastAddedDockAreaWidget(DockWidgetArea area) const +{ + return d->m_lastAddedAreaCache[areaIdToIndex(area)]; +} + +bool DockContainerWidget::hasTopLevelDockWidget() const +{ + auto dockAreas = openedDockAreas(); + if (dockAreas.count() != 1) + return false; + + return dockAreas[0]->openDockWidgetsCount() == 1; +} + +DockWidget *DockContainerWidget::topLevelDockWidget() const +{ + auto dockArea = topLevelDockArea(); + if (!dockArea) + return nullptr; + + auto dockWidgets = dockArea->openedDockWidgets(); + if (dockWidgets.count() != 1) + return nullptr; + + return dockWidgets[0]; +} + +DockAreaWidget *DockContainerWidget::topLevelDockArea() const +{ + auto dockAreas = openedDockAreas(); + if (dockAreas.count() != 1) + return nullptr; + + return dockAreas[0]; +} + +QList DockContainerWidget::dockWidgets() const +{ + QList result; + for (const auto dockArea : std::as_const(d->m_dockAreas)) + result.append(dockArea->dockWidgets()); + + return result; +} + +void DockContainerWidget::updateSplitterHandles(QSplitter *splitter) +{ + d->updateSplitterHandles(splitter); +} + +void DockContainerWidget::registerAutoHideWidget(AutoHideDockContainer *autohideWidget) +{ + d->m_autoHideWidgets.append(autohideWidget); + Q_EMIT autoHideWidgetCreated(autohideWidget); + qCInfo(adsLog) << "d->AutoHideWidgets.count() " << d->m_autoHideWidgets.count(); +} + +void DockContainerWidget::removeAutoHideWidget(AutoHideDockContainer *autohideWidget) +{ + d->m_autoHideWidgets.removeAll(autohideWidget); +} + +DockWidget::DockWidgetFeatures DockContainerWidget::features() const +{ + DockWidget::DockWidgetFeatures features(DockWidget::AllDockWidgetFeatures); + for (const auto dockArea : std::as_const(d->m_dockAreas)) + features &= dockArea->features(); + + return features; +} + +FloatingDockContainer *DockContainerWidget::floatingWidget() const +{ + return internal::findParent(this); +} + +void DockContainerWidget::closeOtherAreas(DockAreaWidget *keepOpenArea) +{ + for (const auto dockArea : std::as_const(d->m_dockAreas)) { + if (dockArea == keepOpenArea) + continue; + + if (!dockArea->features(BitwiseAnd).testFlag(DockWidget::DockWidgetClosable)) + continue; + + // We do not close areas with widgets with custom close handling + if (dockArea->features(BitwiseOr).testFlag(DockWidget::CustomCloseHandling)) + continue; + + dockArea->closeArea(); + } +} + +AutoHideSideBar *DockContainerWidget::autoHideSideBar(SideBarLocation area) const +{ + return d->m_sideTabBarWidgets[area]; +} + +QRect DockContainerWidget::contentRect() const +{ + if (!d->m_rootSplitter) + return QRect(); + + return d->m_rootSplitter->geometry(); +} + +QRect DockContainerWidget::contentRectGlobal() const +{ + if (!d->m_rootSplitter) + return QRect(); + + if (d->m_rootSplitter->hasVisibleContent()) { + return d->m_rootSplitter->geometry(); + } else { + auto contentRect = rect(); + contentRect.adjust(autoHideSideBar(SideBarLeft)->sizeHint().width(), + autoHideSideBar(SideBarTop)->sizeHint().height(), + -autoHideSideBar(SideBarRight)->sizeHint().width(), + -autoHideSideBar(SideBarBottom)->sizeHint().height()); + + return contentRect; + } +} + +DockManager *DockContainerWidget::dockManager() const +{ + return d->m_dockManager; +} + +void DockContainerWidget::handleAutoHideWidgetEvent(QEvent *e, QWidget *w) +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideShowOnMouseOver)) + return; + + if (dockManager()->isRestoringState()) + return; + + auto autoHideTab = qobject_cast(w); + if (autoHideTab) { + switch (e->type()) { + case QEvent::Enter: + if (!autoHideTab->dockWidget()->isVisible()) { + d->m_delayedAutoHideTab = autoHideTab; + d->m_delayedAutoHideShow = true; + d->m_delayedAutoHideTimer.start(); + } else { + d->m_delayedAutoHideTimer.stop(); + } + break; + + case QEvent::MouseButtonPress: + d->m_delayedAutoHideTimer.stop(); + break; + + case QEvent::Leave: + if (autoHideTab->dockWidget()->isVisible()) { + d->m_delayedAutoHideTab = autoHideTab; + d->m_delayedAutoHideShow = false; + d->m_delayedAutoHideTimer.start(); + } else { + d->m_delayedAutoHideTimer.stop(); + } + break; + + default: + break; + } + return; + } + + auto autoHideContainer = qobject_cast(w); + if (autoHideContainer) { + switch (e->type()) { + case QEvent::Enter: + case QEvent::Hide: + d->m_delayedAutoHideTimer.stop(); + break; + + case QEvent::Leave: + if (autoHideContainer->isVisible()) { + d->m_delayedAutoHideTab = autoHideContainer->autoHideTab(); + d->m_delayedAutoHideShow = false; + d->m_delayedAutoHideTimer.start(); + } + break; + + default: + break; + } + return; + } +} + } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.h b/src/libs/advanceddockingsystem/dockcontainerwidget.h index 5020b98bf13..f2c6085a1d3 100644 --- a/src/libs/advanceddockingsystem/dockcontainerwidget.h +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.h @@ -4,6 +4,7 @@ #pragma once #include "ads_globals.h" +#include "autohidetab.h" #include "dockwidget.h" #include @@ -24,13 +25,16 @@ class FloatingDockContainerPrivate; class FloatingDragPreview; class DockingStateReader; class FloatingDragPreviewPrivate; +class AutoHideSideBar; +class AutoHideTab; +struct AutoHideTabPrivate; +struct AutoHideDockContainerPrivate; +class AutoHideDockContainer; /** - * Container that manages a number of dock areas with single dock widgets - * or tabified dock widgets in each area. - * Each window that support docking has a DockContainerWidget. That means - * the main application window and all floating windows contain a - * DockContainerWidget instance. + * Container that manages a number of dock areas with single dock widgets or tabified dock widgets + * in each area. Each window that support docking has a DockContainerWidget. That means the main + * application window and all floating windows contain a DockContainerWidget instance. */ class ADS_EXPORT DockContainerWidget : public QFrame { @@ -47,6 +51,11 @@ private: friend class DockWidget; friend class FloatingDragPreview; friend class FloatingDragPreviewPrivate; + friend AutoHideDockContainer; + friend AutoHideTab; + friend AutoHideTabPrivate; + friend AutoHideDockContainerPrivate; + friend AutoHideSideBar; protected: /** @@ -59,11 +68,25 @@ protected: */ QSplitter *rootSplitter() const; + /** + * Creates and initializes a dockwidget auto hide container into the given area. Initializing + * inserts the tabs into the side tab widget and hides it + * Returns nullptr if you try and insert into an area where the configuration is not enabled + */ + AutoHideDockContainer *createAndSetupAutoHideContainer(SideBarLocation area, + DockWidget *dockWidget, + int tabIndex = -1); + /** * Helper function for creation of the root splitter */ void createRootSplitter(); + /** + * Helper function for creation of the side tab bar widgets + */ + void createSideTabBarWidgets(); + /** * Drop floating widget into the container */ @@ -76,7 +99,10 @@ protected: * a nullptr, then the DropArea indicates the drop area in the given * TargetAreaWidget */ - void dropWidget(QWidget *widget, DockWidgetArea dropArea, DockAreaWidget *targetAreaWidget); + void dropWidget(QWidget *widget, + DockWidgetArea dropArea, + DockAreaWidget *targetAreaWidget, + int tabIndex = -1); /** * Adds the given dock area to this container widget @@ -91,7 +117,7 @@ protected: /** * This function replaces the goto construct. Still need to write a good description. */ - void emitAndExit() const; // TODO rename + void emitAndExit() const; /** * Saves the state into the given stream @@ -134,6 +160,28 @@ protected: */ QList dockWidgets() const; + /** + * This function forces the dock container widget to update handles of splitters + * based on resize modes of dock widgets contained in the container. + */ + void updateSplitterHandles(QSplitter *splitter); + + /** + * Registers the given floating widget in the internal list of auto hide widgets + */ + void registerAutoHideWidget(AutoHideDockContainer *autoHideWidget); + + /** + * Remove the given auto hide widget from the list of registered auto hide widgets + */ + void removeAutoHideWidget(AutoHideDockContainer *autoHideWidget); + + /** + * Handles widget events of auto hide widgets to trigger delayed show + * or hide actions for auto hide container on auto hide tab mouse over + */ + void handleAutoHideWidgetEvent(QEvent *e, QWidget *w); + public: /** * Default Constructor @@ -154,7 +202,8 @@ public: */ DockAreaWidget *addDockWidget(DockWidgetArea area, DockWidget *dockWidget, - DockAreaWidget *dockAreaWidget = nullptr); + DockAreaWidget *dockAreaWidget = nullptr, + int index = -1); /** * Removes dockwidget @@ -190,6 +239,18 @@ public: */ QList openedDockAreas() const; + /** + * Returns a list for all open dock widgets in all open dock areas + */ + QList openedDockWidgets() const; + + /** + * This function returns true, if the container has open dock areas. + * This functions is a little bit faster than calling openedDockAreas().isEmpty() + * because it returns as soon as it finds an open dock area + */ + bool hasOpenDockAreas() const; + /** * This function returns true if this dock area has only one single * visible dock widget. @@ -239,6 +300,34 @@ public: */ void closeOtherAreas(DockAreaWidget *keepOpenArea); + /** + * Returns the side tab widget for the given area + */ + AutoHideSideBar *autoHideSideBar(SideBarLocation area) const; + + /** + * Access function for auto hide widgets + */ + QList autoHideWidgets() const; + + /** + * Returns the content rectangle without the autohide side bars + */ + QRect contentRect() const; + + /** + * Returns the content rectangle mapped to global space. + * The content rectangle is the rectangle of the dock manager widget minus + * the auto hide side bars. So that means, it is the geometry of the + * internal root splitter mapped to global space. + */ + QRect contentRectGlobal() const; + + /** + * Returns the dock manager that owns this container + */ + DockManager *dockManager() const; + signals: /** * This signal is emitted if one or multiple dock areas has been added to @@ -247,6 +336,11 @@ signals: */ void dockAreasAdded(); + /** + * This signal is emitted, if a new auto hide widget has been created. + */ + void autoHideWidgetCreated(AutoHideDockContainer *autoHideWidget); + /** * This signal is emitted if one or multiple dock areas has been removed */ diff --git a/src/libs/advanceddockingsystem/dockfocuscontroller.cpp b/src/libs/advanceddockingsystem/dockfocuscontroller.cpp index a5878ba856c..b35963c6db2 100644 --- a/src/libs/advanceddockingsystem/dockfocuscontroller.cpp +++ b/src/libs/advanceddockingsystem/dockfocuscontroller.cpp @@ -3,250 +3,332 @@ #include "dockfocuscontroller.h" -#include "dockareawidget.h" +#include "ads_globals_p.h" #include "dockareatitlebar.h" +#include "dockareawidget.h" #include "dockcontainerwidget.h" #include "dockmanager.h" #include "dockwidget.h" #include "dockwidgettab.h" #include "floatingdockcontainer.h" -#ifdef Q_OS_LINUX +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) #include "linux/floatingwidgettitlebar.h" #endif #include #include +#include #include #include -namespace ADS +namespace ADS { + +static const char *const g_focusedDockWidgetProperty = "FocusedDockWidget"; + +class DockFocusControllerPrivate { +public: + DockFocusController *q; + QPointer m_focusedDockWidget = nullptr; + QPointer m_focusedArea = nullptr; + QPointer m_oldFocusedDockWidget = nullptr; +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + QPointer m_floatingWidget = nullptr; +#endif + DockManager *m_dockManager = nullptr; + bool m_forceFocusChangedSignal = false; + bool m_tabPressed = false; + /** - * Private data class of CDockFocusController class (pimpl) + * Private data constructor */ - class DockFocusControllerPrivate - { - public: - DockFocusController *q; - QPointer m_focusedDockWidget = nullptr; - QPointer m_focusedArea = nullptr; - #ifdef Q_OS_LINUX - QPointer m_floatingWidget = nullptr; - #endif - DockManager *m_dockManager = nullptr; + DockFocusControllerPrivate(DockFocusController *parent); - /** - * Private data constructor - */ - DockFocusControllerPrivate(DockFocusController *parent); + /** + * This function updates the focus style of the given dock widget and + * the dock area that it belongs to + */ + void updateDockWidgetFocus(DockWidget *dockWidget); +}; // class DockFocusControllerPrivate - /** - * This function updates the focus style of the given dock widget and - * the dock area that it belongs to - */ - void updateDockWidgetFocus(DockWidget *dockWidget); - }; // class DockFocusControllerPrivate +static void updateDockWidgetFocusStyle(DockWidget *dockWidget, bool focused) +{ + dockWidget->setProperty("focused", focused); + dockWidget->tabWidget()->setProperty("focused", focused); + dockWidget->tabWidget()->updateStyle(); + internal::repolishStyle(dockWidget); +} - static void updateDockWidgetFocusStyle(DockWidget *dockWidget, bool focused) - { - dockWidget->setProperty("focused", focused); - dockWidget->tabWidget()->setProperty("focused", focused); - dockWidget->tabWidget()->updateStyle(); - internal::repolishStyle(dockWidget); - } +static void updateDockAreaFocusStyle(DockAreaWidget *dockArea, bool focused) +{ + dockArea->setProperty("focused", focused); + internal::repolishStyle(dockArea); + internal::repolishStyle(dockArea->titleBar()); +} - static void updateDockAreaFocusStyle(DockAreaWidget *dockArea, bool focused) - { - dockArea->setProperty("focused", focused); - internal::repolishStyle(dockArea); - internal::repolishStyle(dockArea->titleBar()); - } +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +static void updateFloatingWidgetFocusStyle(FloatingDockContainer *floatingWidget, bool focused) +{ + if (floatingWidget->hasNativeTitleBar()) + return; -#ifdef Q_OS_LINUX - static void updateFloatingWidgetFocusStyle(FloatingDockContainer *floatingWidget, bool focused) - { - auto titleBar = qobject_cast(floatingWidget->titleBarWidget()); - if (!titleBar) - return; + auto titleBar = qobject_cast(floatingWidget->titleBarWidget()); + if (!titleBar) + return; - titleBar->setProperty("focused", focused); - titleBar->updateStyle(); - } + titleBar->setProperty("focused", focused); + titleBar->updateStyle(); +} #endif - DockFocusControllerPrivate::DockFocusControllerPrivate(DockFocusController *parent) - : q(parent) - {} +DockFocusControllerPrivate::DockFocusControllerPrivate(DockFocusController *parent) + : q(parent) +{} - void DockFocusControllerPrivate::updateDockWidgetFocus(DockWidget *dockWidget) - { - DockAreaWidget *newFocusedDockArea = nullptr; - if (m_focusedDockWidget) - updateDockWidgetFocusStyle(m_focusedDockWidget, false); +void DockFocusControllerPrivate::updateDockWidgetFocus(DockWidget *dockWidget) +{ + if (!dockWidget->features().testFlag(DockWidget::DockWidgetFocusable)) + return; - DockWidget *old = m_focusedDockWidget; - m_focusedDockWidget = dockWidget; - updateDockWidgetFocusStyle(m_focusedDockWidget, true); - newFocusedDockArea = m_focusedDockWidget->dockAreaWidget(); - if (newFocusedDockArea && (m_focusedArea != newFocusedDockArea)) - { - if (m_focusedArea) - { - QObject::disconnect(m_focusedArea, &DockAreaWidget::viewToggled, - q, &DockFocusController::onFocusedDockAreaViewToggled); - updateDockAreaFocusStyle(m_focusedArea, false); - } + QWindow *window = nullptr; + auto dockContainer = dockWidget->dockContainer(); + if (dockContainer) + window = dockContainer->window()->windowHandle(); - m_focusedArea = newFocusedDockArea; - updateDockAreaFocusStyle(m_focusedArea, true); - QObject::connect(m_focusedArea, &DockAreaWidget::viewToggled, - q, &DockFocusController::onFocusedDockAreaViewToggled); + if (window) + window->setProperty(g_focusedDockWidgetProperty, + QVariant::fromValue(QPointer(dockWidget))); + + DockAreaWidget *newFocusedDockArea = nullptr; + if (m_focusedDockWidget) + updateDockWidgetFocusStyle(m_focusedDockWidget, false); + + DockWidget *old = m_focusedDockWidget; + m_focusedDockWidget = dockWidget; + updateDockWidgetFocusStyle(m_focusedDockWidget, true); + newFocusedDockArea = m_focusedDockWidget->dockAreaWidget(); + if (newFocusedDockArea && (m_focusedArea != newFocusedDockArea)) { + if (m_focusedArea) { + QObject::disconnect(m_focusedArea, + &DockAreaWidget::viewToggled, + q, + &DockFocusController::onFocusedDockAreaViewToggled); + updateDockAreaFocusStyle(m_focusedArea, false); } - auto dockContainer = m_focusedDockWidget->dockContainer(); - FloatingDockContainer *newFloatingWidget = nullptr; + m_focusedArea = newFocusedDockArea; + updateDockAreaFocusStyle(m_focusedArea, true); + QObject::connect(m_focusedArea, + &DockAreaWidget::viewToggled, + q, + &DockFocusController::onFocusedDockAreaViewToggled); + } - if (dockContainer) - newFloatingWidget = dockContainer->floatingWidget(); + FloatingDockContainer *newFloatingWidget = nullptr; + dockContainer = m_focusedDockWidget->dockContainer(); + if (dockContainer) + newFloatingWidget = dockContainer->floatingWidget(); - if (newFloatingWidget) - newFloatingWidget->setProperty("FocusedDockWidget", QVariant::fromValue(dockWidget)); + if (newFloatingWidget) + newFloatingWidget->setProperty(g_focusedDockWidgetProperty, + QVariant::fromValue(QPointer(dockWidget))); -#ifdef Q_OS_LINUX - // This code is required for styling the floating widget titlebar for linux - // depending on the current focus state - if (m_floatingWidget == newFloatingWidget) - return; - - if (m_floatingWidget) +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + // This code is required for styling the floating widget titlebar for linux + // depending on the current focus state + if (m_floatingWidget != newFloatingWidget) { + if (m_floatingWidget) { updateFloatingWidgetFocusStyle(m_floatingWidget, false); - + } m_floatingWidget = newFloatingWidget; - if (m_floatingWidget) + if (m_floatingWidget) { updateFloatingWidgetFocusStyle(m_floatingWidget, true); + } + } #endif - if (old != dockWidget) - emit m_dockManager->focusedDockWidgetChanged(old, dockWidget); + if (old == dockWidget && !m_forceFocusChangedSignal) + return; + + m_forceFocusChangedSignal = false; + if (dockWidget->isVisible()) { + emit m_dockManager->focusedDockWidgetChanged(old, dockWidget); + } else { + m_oldFocusedDockWidget = old; + QObject::connect(dockWidget, + &DockWidget::visibilityChanged, + q, + &DockFocusController::onDockWidgetVisibilityChanged); } +} - DockFocusController::DockFocusController(DockManager *dockManager) - : QObject(dockManager) - , d(new DockFocusControllerPrivate(this)) - { - d->m_dockManager = dockManager; - connect(qApp, &QApplication::focusChanged, - this, &DockFocusController::onApplicationFocusChanged); - connect(d->m_dockManager, &DockManager::stateRestored, - this, &DockFocusController::onStateRestored); - } +void DockFocusController::onDockWidgetVisibilityChanged(bool visible) +{ + auto dockWidget = qobject_cast(sender()); + QObject::disconnect(dockWidget, + &DockWidget::visibilityChanged, + this, + &DockFocusController::onDockWidgetVisibilityChanged); + if (dockWidget && visible) + emit d->m_dockManager->focusedDockWidgetChanged(d->m_oldFocusedDockWidget, dockWidget); +} - DockFocusController::~DockFocusController() - { - delete d; - } +DockFocusController::DockFocusController(DockManager *dockManager) + : QObject(dockManager) + , d(new DockFocusControllerPrivate(this)) +{ + d->m_dockManager = dockManager; + connect(qApp, + &QApplication::focusChanged, + this, + &DockFocusController::onApplicationFocusChanged); + connect(qApp, + &QApplication::focusWindowChanged, + this, + &DockFocusController::onFocusWindowChanged); + connect(d->m_dockManager, + &DockManager::stateRestored, + this, + &DockFocusController::onStateRestored); +} - void DockFocusController::onApplicationFocusChanged(QWidget *focusedOld, QWidget *focusedNow) - { - Q_UNUSED(focusedOld); +DockFocusController::~DockFocusController() +{ + delete d; +} - if (d->m_dockManager->isRestoringState()) - return; +void DockFocusController::onFocusWindowChanged(QWindow *focusWindow) +{ + if (!focusWindow) + return; - if (!focusedNow) - return; + auto dockWidgetVar = focusWindow->property(g_focusedDockWidgetProperty); + if (!dockWidgetVar.isValid()) + return; - DockWidget *dockWidget = nullptr; - auto dockWidgetTab = qobject_cast(focusedNow); - if (dockWidgetTab) - dockWidget = dockWidgetTab->dockWidget(); + auto dockWidget = dockWidgetVar.value>(); + if (!dockWidget) + return; - if (!dockWidget) - dockWidget = qobject_cast(focusedNow); + d->updateDockWidgetFocus(dockWidget); +} - bool focusActual = dockWidget && dockWidget->widget(); +void DockFocusController::onApplicationFocusChanged(QWidget *focusedOld, QWidget *focusedNow) +{ + Q_UNUSED(focusedOld); - if (!dockWidget) - dockWidget = internal::findParent(focusedNow); + // Ignore focus changes if we are restoring state, or if user clicked a tab which in turn + // caused the focus change. + if (d->m_dockManager->isRestoringState() || d->m_tabPressed) + return; - #ifdef Q_OS_LINUX - if (!dockWidget) - return; - #else - if (!dockWidget || dockWidget->tabWidget()->isHidden()) - return; - #endif + qCInfo(adsLog) << Q_FUNC_INFO << "old: " << focusedOld << "new:" << focusedNow; + if (!focusedNow) + return; + + DockWidget *dockWidget = qobject_cast(focusedNow); + if (!dockWidget) + dockWidget = internal::findParent(focusedNow); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (!dockWidget) + return; +#else + if (!dockWidget || dockWidget->tabWidget()->isHidden()) + return; +#endif + + d->updateDockWidgetFocus(dockWidget); +} + +void DockFocusController::setDockWidgetTabFocused(DockWidgetTab *tab) +{ + auto dockWidget = tab->dockWidget(); + if (dockWidget) d->updateDockWidgetFocus(dockWidget); +} - if (focusActual) { - // Do this async to avoid issues when changing focus in middle of another focus handling - QMetaObject::invokeMethod(dockWidget->widget(), QOverload<>::of(&QWidget::setFocus), - Qt::QueuedConnection); - } +void DockFocusController::clearDockWidgetFocus(DockWidget *dockWidget) +{ + dockWidget->clearFocus(); + updateDockWidgetFocusStyle(dockWidget, false); +} + +void DockFocusController::setDockWidgetFocused(DockWidget *focusedNow) +{ + d->updateDockWidgetFocus(focusedNow); +} + +void DockFocusController::onFocusedDockAreaViewToggled(bool open) +{ + if (d->m_dockManager->isRestoringState() || !d->m_focusedArea || open) + return; + + DockAreaWidget *dockArea = qobject_cast(sender()); + if (!dockArea || open) + return; + + auto container = dockArea->dockContainer(); + auto openedDockAreas = container->openedDockAreas(); + if (openedDockAreas.isEmpty()) + return; + + d->updateDockWidgetFocus(openedDockAreas[0]->currentDockWidget()); +} + +void DockFocusController::notifyWidgetOrAreaRelocation(QWidget *droppedWidget) +{ + if (d->m_dockManager->isRestoringState()) + return; + + DockWidget *dockWidget = qobject_cast(droppedWidget); + if (!dockWidget) { + DockAreaWidget *dockArea = qobject_cast(droppedWidget); + if (dockArea) + dockWidget = dockArea->currentDockWidget(); } - void DockFocusController::setDockWidgetFocused(DockWidget *focusedNow) - { - d->updateDockWidgetFocus(focusedNow); - } + if (!dockWidget) + return; - void DockFocusController::onFocusedDockAreaViewToggled(bool open) - { - if (d->m_dockManager->isRestoringState() || !d->m_focusedArea || open) - return; + d->m_forceFocusChangedSignal = true; + DockManager::setWidgetFocus(dockWidget); +} - auto container = d->m_focusedArea->dockContainer(); - auto openedDockAreas = container->openedDockAreas(); - if (openedDockAreas.isEmpty()) - return; +void DockFocusController::notifyFloatingWidgetDrop(FloatingDockContainer *floatingWidget) +{ + if (!floatingWidget || d->m_dockManager->isRestoringState()) + return; - DockManager::setWidgetFocus(openedDockAreas[0]->currentDockWidget()->tabWidget()); - } + auto dockWidgetVar = floatingWidget->property(g_focusedDockWidgetProperty); + if (!dockWidgetVar.isValid()) + return; - void DockFocusController::notifyWidgetOrAreaRelocation(QWidget *droppedWidget) - { - if (d->m_dockManager->isRestoringState()) - return; - - DockWidget *dockWidget = qobject_cast(droppedWidget); - if (dockWidget) - { - DockManager::setWidgetFocus(dockWidget->tabWidget()); - return; - } - - DockAreaWidget* dockArea = qobject_cast(droppedWidget); - if (!dockArea) - return; - - dockWidget = dockArea->currentDockWidget(); + auto dockWidget = dockWidgetVar.value(); + if (dockWidget) { + dockWidget->dockAreaWidget()->setCurrentDockWidget(dockWidget); DockManager::setWidgetFocus(dockWidget->tabWidget()); } +} - void DockFocusController::notifyFloatingWidgetDrop(FloatingDockContainer *floatingWidget) - { - if (!floatingWidget || d->m_dockManager->isRestoringState()) - return; +void DockFocusController::onStateRestored() +{ + if (d->m_focusedDockWidget) + updateDockWidgetFocusStyle(d->m_focusedDockWidget, false); +} - auto vDockWidget = floatingWidget->property("FocusedDockWidget"); - if (!vDockWidget.isValid()) - return; +DockWidget *DockFocusController::focusedDockWidget() const +{ + return d->m_focusedDockWidget.data(); +} - auto dockWidget = vDockWidget.value(); - if (dockWidget) { - dockWidget->dockAreaWidget()->setCurrentDockWidget(dockWidget); - DockManager::setWidgetFocus(dockWidget->tabWidget()); - } - } - - void DockFocusController::onStateRestored() - { - if (d->m_focusedDockWidget) - updateDockWidgetFocusStyle(d->m_focusedDockWidget, false); - } +void DockFocusController::setDockWidgetTabPressed(bool value) +{ + d->m_tabPressed = value; +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockfocuscontroller.h b/src/libs/advanceddockingsystem/dockfocuscontroller.h index c108d0d5591..a9e11e13662 100644 --- a/src/libs/advanceddockingsystem/dockfocuscontroller.h +++ b/src/libs/advanceddockingsystem/dockfocuscontroller.h @@ -27,8 +27,10 @@ private: private: void onApplicationFocusChanged(QWidget *old, QWidget *now); + void onFocusWindowChanged(QWindow *focusWindow); void onFocusedDockAreaViewToggled(bool open); void onStateRestored(); + void onDockWidgetVisibilityChanged(bool visible); public: /** @@ -41,19 +43,6 @@ public: */ ~DockFocusController() override; - /** - * Helper function to set focus depending on the configuration of the - * FocusStyling flag - */ - template - static void setWidgetFocus(QWidgetPtr widget) - { - if (!DockManager::testConfigFlag(DockManager::FocusHighlighting)) - return; - - widget->setFocus(Qt::OtherFocusReason); - } - /** * A container needs to call this function if a widget has been dropped * into it @@ -68,6 +57,28 @@ public: */ void notifyFloatingWidgetDrop(FloatingDockContainer *floatingWidget); + /** + * Returns the dock widget that has focus style in the ui or a nullptr if + * not dock widget is painted focused. + */ + DockWidget *focusedDockWidget() const; + + /** + * Notifies the dock focus controller, that a the mouse is pressed or released. + */ + void setDockWidgetTabPressed(bool value); + + /** + * Request focus highlighting for the given dock widget assigned to the tab + * given in Tab parameter + */ + void setDockWidgetTabFocused(DockWidgetTab *tab); + + /* + * Request clear focus for a dock widget + */ + void clearDockWidgetFocus(DockWidget *dockWidget); + /** * Request a focus change to the given dock widget */ diff --git a/src/libs/advanceddockingsystem/dockmanager.cpp b/src/libs/advanceddockingsystem/dockmanager.cpp index 60a5aa35c8c..20b9a71a730 100644 --- a/src/libs/advanceddockingsystem/dockmanager.cpp +++ b/src/libs/advanceddockingsystem/dockmanager.cpp @@ -4,12 +4,14 @@ #include "dockmanager.h" #include "ads_globals.h" -#include "advanceddockingsystemtr.h" #include "ads_globals_p.h" +#include "advanceddockingsystemtr.h" +#include "autohidedockcontainer.h" #include "dockareawidget.h" #include "dockfocuscontroller.h" #include "dockingstatereader.h" #include "dockoverlay.h" +#include "docksplitter.h" #include "dockwidget.h" #include "floatingdockcontainer.h" #include "iconprovider.h" @@ -40,8 +42,13 @@ #include #include #include +#include #include +#if !(defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) +#include +#endif + Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg); using namespace Utils; @@ -57,6 +64,9 @@ enum eStateFileVersion { }; static DockManager::ConfigFlags g_staticConfigFlags = DockManager::DefaultNonOpaqueConfig; +static DockManager::AutoHideFlags g_staticAutoHideConfigFlags; // auto hide is disabled by default + +static QString g_floatingContainersTitle; /** * Private data class of DockManager class (pimpl) @@ -66,6 +76,7 @@ class DockManagerPrivate public: DockManager *q; QList> m_floatingWidgets; + QList> m_hiddenFloatingWidgets; QList m_containers; DockOverlay *m_containerOverlay = nullptr; DockOverlay *m_dockAreaOverlay = nullptr; @@ -73,6 +84,8 @@ public: bool m_restoringState = false; QVector m_uninitializedFloatingWidgets; DockFocusController *m_focusController = nullptr; + DockWidget *m_centralWidget = nullptr; + bool m_isLeavingMinimized = false; QString m_workspacePresetsPath; QList m_workspaces; @@ -114,7 +127,7 @@ public: void markDockWidgetsDirty() { for (const auto &dockWidget : std::as_const(m_dockWidgetsMap)) - dockWidget->setProperty("dirty", true); + dockWidget->setProperty(internal::g_dirtyProperty, true); } /** @@ -180,11 +193,27 @@ bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int versio return false; } + qCInfo(adsLog) << stateReader.attributes().value("containers").toInt(); + + if (m_centralWidget) { + const auto centralWidgetAttribute = stateReader.attributes().value("centralWidget"); + // If we have a central widget, but a state without central widget, then something is wrong. + if (centralWidgetAttribute.isEmpty()) { + qWarning() + << "DockManager has central widget, but saved state does not have central widget."; + return false; + } + + // If the object name of the central widget does not match the name of the saved central + // widget, something is wrong. + if (m_centralWidget->objectName() != centralWidgetAttribute.toString()) { + qWarning() << "Object name of central widget does not match name of central widget in " + "saved state."; + return false; + } + } + bool result = true; -#ifdef ADS_DEBUG_PRINT - int dockContainers = stateReader.attributes().value("containers").toInt(); - qCInfo(adsLog) << dockContainers; -#endif int dockContainerCount = 0; while (stateReader.readNextStartElement()) { if (stateReader.name() == QLatin1String("container")) { @@ -199,10 +228,10 @@ bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int versio if (!testing) { // Delete remaining empty floating widgets int floatingWidgetIndex = dockContainerCount - 1; - int deleteCount = m_floatingWidgets.count() - floatingWidgetIndex; - for (int i = 0; i < deleteCount; ++i) { - m_floatingWidgets[floatingWidgetIndex + i]->deleteLater(); - q->removeDockContainer(m_floatingWidgets[floatingWidgetIndex + i]->dockContainer()); + for (int i = floatingWidgetIndex; i < m_floatingWidgets.count(); ++i) { + QPointer floatingWidget = m_floatingWidgets[i]; + q->removeDockContainer(floatingWidget->dockContainer()); + floatingWidget->deleteLater(); } } @@ -215,11 +244,17 @@ void DockManagerPrivate::restoreDockWidgetsOpenState() // invisible to the user now and have no assigned dock area. They do not belong to any dock // container, until the user toggles the toggle view action the next time. for (auto dockWidget : std::as_const(m_dockWidgetsMap)) { - if (dockWidget->property(internal::dirtyProperty).toBool()) { + if (dockWidget->property(internal::g_dirtyProperty).toBool()) { + // If the DockWidget is an auto hide widget that is not assigned yet, + // then we need to delete the auto hide container now + if (dockWidget->isAutoHide()) + dockWidget->autoHideDockContainer()->cleanupAndDelete(); + dockWidget->flagAsUnassigned(); emit dockWidget->viewToggled(false); } else { - dockWidget->toggleViewInternal(!dockWidget->property(internal::closedProperty).toBool()); + dockWidget->toggleViewInternal( + !dockWidget->property(internal::g_closedProperty).toBool()); } } } @@ -251,7 +286,7 @@ void DockManagerPrivate::restoreDockAreasIndices() void DockManagerPrivate::emitTopLevelEvents() { - // Finally we need to send the topLevelChanged() signals for all dock widgets if top + // Finally we need to send the topLevelChanged() signal for all dock widgets if top // level changed. for (auto dockContainer : std::as_const(m_containers)) { DockWidget *topLevelDockWidget = dockContainer->topLevelDockWidget(); @@ -288,6 +323,7 @@ bool DockManagerPrivate::restoreState(const QByteArray &state, int version) restoreDockWidgetsOpenState(); restoreDockAreasIndices(); emitTopLevelEvents(); + q->dumpLayout(); return true; } @@ -301,6 +337,7 @@ DockManager::DockManager(QWidget *parent) }); createRootSplitter(); + createSideTabBarWidgets(); QMainWindow *mainWindow = qobject_cast(parent); if (mainWindow) mainWindow->setCentralWidget(this); @@ -311,6 +348,17 @@ DockManager::DockManager(QWidget *parent) if (DockManager::configFlags().testFlag(DockManager::FocusHighlighting)) d->m_focusController = new DockFocusController(this); + + window()->installEventFilter(this); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + connect(qApp, &QApplication::focusWindowChanged, this, [](QWindow *focusWindow) { + // Bring modal dialogs to foreground to ensure that they are in front of any + // floating dock widget. + if (focusWindow && focusWindow->isModal()) + focusWindow->raise(); + }); +#endif } DockManager::~DockManager() @@ -319,6 +367,18 @@ DockManager::~DockManager() save(); saveStartupWorkspace(); + // Fix memory leaks, see https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/307 + std::vector areas; + for (int i = 0; i != dockAreaCount(); ++i) + areas.push_back(dockArea(i)); + + for (auto area : areas) { + for (auto widget : area->dockWidgets()) + delete widget; + + delete area; + } + // Using a temporary vector since the destructor of FloatingDockWidgetContainer // alters d->m_floatingWidgets. const auto copy = d->m_floatingWidgets; @@ -334,21 +394,41 @@ DockManager::ConfigFlags DockManager::configFlags() return g_staticConfigFlags; } +DockManager::AutoHideFlags DockManager::autoHideConfigFlags() +{ + return g_staticAutoHideConfigFlags; +} + void DockManager::setConfigFlags(const ConfigFlags flags) { g_staticConfigFlags = flags; } +void DockManager::setAutoHideConfigFlags(const AutoHideFlags flags) +{ + g_staticAutoHideConfigFlags = flags; +} + void DockManager::setConfigFlag(eConfigFlag flag, bool on) { internal::setFlag(g_staticConfigFlags, flag, on); } +void DockManager::setAutoHideConfigFlag(eAutoHideFlag flag, bool on) +{ + internal::setFlag(g_staticAutoHideConfigFlags, flag, on); +} + bool DockManager::testConfigFlag(eConfigFlag flag) { return configFlags().testFlag(flag); } +bool DockManager::testAutoHideConfigFlag(eAutoHideFlag flag) +{ + return autoHideConfigFlags().testFlag(flag); +} + IconProvider &DockManager::iconProvider() { static IconProvider instance; @@ -372,10 +452,9 @@ void DockManager::setWorkspacePresetsPath(const QString &path) void DockManager::initialize() { - qCInfo(adsLog) << "Initialize DockManager"; + qCInfo(adsLog) << Q_FUNC_INFO; - // Can't continue if settings are not set yet - QTC_ASSERT(d->m_settings, return); + QTC_ASSERT(d->m_settings, return); // Can't continue if settings are not set yet syncWorkspacePresets(); prepareWorkspaces(); @@ -414,10 +493,40 @@ void DockManager::initialize() DockAreaWidget *DockManager::addDockWidget(DockWidgetArea area, DockWidget *dockWidget, - DockAreaWidget *dockAreaWidget) + DockAreaWidget *dockAreaWidget, + int index) { d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); - return DockContainerWidget::addDockWidget(area, dockWidget, dockAreaWidget); + auto container = dockAreaWidget ? dockAreaWidget->dockContainer() : this; + auto dockArea = container->addDockWidget(area, dockWidget, dockAreaWidget, index); + emit dockWidgetAdded(dockWidget); + return dockArea; +} + +DockAreaWidget *DockManager::addDockWidgetToContainer(DockWidgetArea area, + DockWidget *dockWidget, + DockContainerWidget *dockContainerWidget) +{ + d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); + auto dockArea = dockContainerWidget->addDockWidget(area, dockWidget); + emit dockWidgetAdded(dockWidget); + return dockArea; +} + +AutoHideDockContainer *DockManager::addAutoHideDockWidget(SideBarLocation location, + DockWidget *dockWidget) +{ + return addAutoHideDockWidgetToContainer(location, dockWidget, this); +} + +AutoHideDockContainer *DockManager::addAutoHideDockWidgetToContainer( + SideBarLocation location, DockWidget *dockWidget, DockContainerWidget *dockContainerWidget) +{ + d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); + auto container = dockContainerWidget->createAndSetupAutoHideContainer(location, dockWidget); + container->collapseView(true); + emit dockWidgetAdded(dockWidget); + return container; } DockAreaWidget *DockManager::addDockWidgetTab(DockWidgetArea area, DockWidget *dockWidget) @@ -426,15 +535,16 @@ DockAreaWidget *DockManager::addDockWidgetTab(DockWidgetArea area, DockWidget *d if (areaWidget) return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, areaWidget); else if (!openedDockAreas().isEmpty()) - return addDockWidget(area, dockWidget, openedDockAreas().constLast()); + return addDockWidget(area, dockWidget, openedDockAreas().constLast()); // TODO else return addDockWidget(area, dockWidget, nullptr); } DockAreaWidget *DockManager::addDockWidgetTabToArea(DockWidget *dockWidget, - DockAreaWidget *dockAreaWidget) + DockAreaWidget *dockAreaWidget, + int index) { - return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, dockAreaWidget); + return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, dockAreaWidget, index); } FloatingDockContainer *DockManager::addDockWidgetFloating(DockWidget *dockWidget) @@ -452,6 +562,7 @@ FloatingDockContainer *DockManager::addDockWidgetFloating(DockWidget *dockWidget else d->m_uninitializedFloatingWidgets.append(floatingWidget); + emit dockWidgetAdded(dockWidget); return floatingWidget; } @@ -465,6 +576,7 @@ void DockManager::removeDockWidget(DockWidget *dockWidget) emit dockWidgetAboutToBeRemoved(dockWidget); d->m_dockWidgetsMap.remove(dockWidget->objectName()); DockContainerWidget::removeDockWidget(dockWidget); + dockWidget->setDockManager(nullptr); emit dockWidgetRemoved(dockWidget); } @@ -503,6 +615,10 @@ QByteArray DockManager::saveState(const QString &displayName, int version) const stream.writeAttribute("userVersion", QString::number(version)); stream.writeAttribute("containers", QString::number(d->m_containers.count())); stream.writeAttribute(workspaceDisplayNameAttribute.toString(), displayName); + + if (d->m_centralWidget) + stream.writeAttribute("centralWidget", d->m_centralWidget->objectName()); + for (auto container : std::as_const(d->m_containers)) container->saveState(stream); @@ -544,12 +660,185 @@ bool DockManager::isRestoringState() const return d->m_restoringState; } +bool DockManager::isLeavingMinimizedState() const +{ + return d->m_isLeavingMinimized; +} + +bool DockManager::eventFilter(QObject *obj, QEvent *event) +{ +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + // Emulate Qt:Tool behavior. + /* + // Window always on top of the MainWindow. + if (event->type() == QEvent::WindowActivate) { + for (auto &floatingWidget : floatingWidgets()) { + if (!floatingWidget->isVisible() || window()->isMinimized()) + continue; + + // setWindowFlags(Qt::WindowStaysOnTopHint) will hide the window and thus requires + // a show call. This then leads to flickering and a nasty endless loop (also buggy + // behavior on Ubuntu). So we just do it ourself. + floatingWidget->setWindowFlag(Qt::WindowStaysOnTopHint, true); + } + } else if (event->type() == QEvent::WindowDeactivate) { + for (auto &floatingWidget : floatingWidgets()) { + if (!floatingWidget->isVisible() || window()->isMinimized()) + continue; + + floatingWidget->setWindowFlag(Qt::WindowStaysOnTopHint, false); + + floatingWidget->raise(); + } + } +*/ + // Sync minimize with MainWindow + if (event->type() == QEvent::WindowStateChange) { + for (auto &floatingWidget : floatingWidgets()) { + if (!floatingWidget->isVisible()) + continue; + + if (window()->isMinimized()) + floatingWidget->showMinimized(); + else + floatingWidget->setWindowState(floatingWidget->windowState() + & (~Qt::WindowMinimized)); + } + if (!window()->isMinimized()) + QApplication::setActiveWindow(window()); + } + return Super::eventFilter(obj, event); + +#else + if (event->type() == QEvent::WindowStateChange) { + QWindowStateChangeEvent *ev = static_cast(event); + if (ev->oldState().testFlag(Qt::WindowMinimized)) { + d->m_isLeavingMinimized = true; + QMetaObject::invokeMethod(this, "endLeavingMinimizedState", Qt::QueuedConnection); + } + } + return Super::eventFilter(obj, event); +#endif +} + +DockWidget *DockManager::focusedDockWidget() const +{ + if (!d->m_focusController) + return nullptr; + else + return d->m_focusController->focusedDockWidget(); +} + +QList DockManager::splitterSizes(DockAreaWidget *containedArea) const +{ + if (containedArea) { + auto splitter = internal::findParent(containedArea); + if (splitter) + return splitter->sizes(); + } + + return QList(); +} + +void DockManager::setSplitterSizes(DockAreaWidget *containedArea, const QList &sizes) +{ + if (!containedArea) + return; + + auto splitter = internal::findParent(containedArea); + if (splitter && splitter->count() == sizes.count()) + splitter->setSizes(sizes); +} + +void DockManager::setFloatingContainersTitle(const QString &title) +{ + g_floatingContainersTitle = title; +} + +QString DockManager::floatingContainersTitle() +{ + if (g_floatingContainersTitle.isEmpty()) + return qApp->applicationDisplayName(); + + return g_floatingContainersTitle; +} + +DockWidget *DockManager::centralWidget() const +{ + return d->m_centralWidget; +} + +DockAreaWidget *DockManager::setCentralWidget(DockWidget *widget) +{ + if (!widget) { + d->m_centralWidget = nullptr; + return nullptr; + } + + // Setting a new central widget is now allowed if there is already a central widget or + // if there are already other dock widgets. + if (d->m_centralWidget) { + qWarning( + "Setting a central widget not possible because there is already a central widget."); + return nullptr; + } + + // Setting a central widget is now allowed if there are already other dock widgets. + if (!d->m_dockWidgetsMap.isEmpty()) { + qWarning("Setting a central widget not possible - the central widget need to be the first " + "dock widget that is added to the dock manager."); + return nullptr; + } + + widget->setFeature(DockWidget::DockWidgetClosable, false); + widget->setFeature(DockWidget::DockWidgetMovable, false); + widget->setFeature(DockWidget::DockWidgetFloatable, false); + widget->setFeature(DockWidget::DockWidgetPinnable, false); + d->m_centralWidget = widget; + DockAreaWidget *centralArea = addDockWidget(CenterDockWidgetArea, widget); + centralArea->setDockAreaFlag(DockAreaWidget::eDockAreaFlag::HideSingleWidgetTitleBar, true); + return centralArea; +} + void DockManager::setDockWidgetFocused(DockWidget *dockWidget) { if (d->m_focusController) d->m_focusController->setDockWidgetFocused(dockWidget); } +void DockManager::hideManagerAndFloatingWidgets() +{ + hide(); + + d->m_hiddenFloatingWidgets.clear(); + // Hide updates of floating widgets from user. + for (auto &floatingWidget : d->m_floatingWidgets) { + if (floatingWidget->isVisible()) { + QList visibleWidgets; + for (auto dockWidget : floatingWidget->dockWidgets()) { + if (dockWidget->toggleViewAction()->isChecked()) + visibleWidgets.push_back(dockWidget); + } + + // Save as floating widget to be shown when DockManager will be shown back. + d->m_hiddenFloatingWidgets.push_back(floatingWidget); + floatingWidget->hide(); + + // Hidding floating widget automatically marked contained DockWidgets as hidden + // but they must remain marked as visible as we want them to be restored visible + // when DockManager will be shown back. + for (auto dockWidget : visibleWidgets) + dockWidget->toggleViewAction()->setChecked(true); + } + } +} + +void DockManager::endLeavingMinimizedState() +{ + d->m_isLeavingMinimized = false; + this->activateWindow(); +} + void DockManager::registerFloatingWidget(FloatingDockContainer *floatingWidget) { d->m_floatingWidgets.append(floatingWidget); @@ -602,15 +891,53 @@ void DockManager::notifyDockAreaRelocation(DockAreaWidget *, DockContainerWidget void DockManager::showEvent(QShowEvent *event) { Super::showEvent(event); + // Fix issue #380 + restoreHiddenFloatingWidgets(); + if (d->m_uninitializedFloatingWidgets.empty()) return; - for (auto floatingWidget : std::as_const(d->m_uninitializedFloatingWidgets)) - floatingWidget->show(); + for (auto floatingWidget : std::as_const(d->m_uninitializedFloatingWidgets)) { + // Check, if someone closed a floating dock widget before the dock manager is shown. + if (floatingWidget->dockContainer()->hasOpenDockAreas()) + floatingWidget->show(); + } d->m_uninitializedFloatingWidgets.clear(); } +DockFocusController *DockManager::dockFocusController() const +{ + return d->m_focusController; +} + +void DockManager::restoreHiddenFloatingWidgets() +{ + if (d->m_hiddenFloatingWidgets.isEmpty()) + return; + + // Restore floating widgets that were hidden upon hideManagerAndFloatingWidgets + for (auto &floatingWidget : d->m_hiddenFloatingWidgets) { + bool hasDockWidgetVisible = false; + + // Needed to prevent FloatingDockContainer being shown empty + // Could make sense to move this to FloatingDockContainer::showEvent(QShowEvent *event) + // if experiencing FloatingDockContainer being shown empty in other situations, but let's keep + // it here for now to make sure changes to fix Issue #380 does not impact existing behaviours + for (auto dockWidget : floatingWidget->dockWidgets()) { + if (dockWidget->toggleViewAction()->isChecked()) { + dockWidget->toggleView(true); + hasDockWidgetVisible = true; + } + } + + if (hasDockWidgetVisible) + floatingWidget->show(); + } + + d->m_hiddenFloatingWidgets.clear(); +} + Workspace *DockManager::activeWorkspace() const { return &d->m_workspace; diff --git a/src/libs/advanceddockingsystem/dockmanager.h b/src/libs/advanceddockingsystem/dockmanager.h index 62148d1e234..195f634023b 100644 --- a/src/libs/advanceddockingsystem/dockmanager.h +++ b/src/libs/advanceddockingsystem/dockmanager.h @@ -46,6 +46,10 @@ class DockWidgetTab; class DockWidgetTabPrivate; struct DockAreaWidgetPrivate; class IconProvider; +class DockFocusController; +class AutoHideSideBar; +class AutoHideTab; +struct AutoHideTabPrivate; inline constexpr QStringView workspaceFolderName{u"workspaces"}; inline constexpr QStringView workspaceFileExtension{u"wrk"}; @@ -80,6 +84,10 @@ private: friend class FloatingDragPreview; friend class FloatingDragPreviewPrivate; friend class DockAreaTitleBar; + friend class AutoHideDockContainer; + friend AutoHideSideBar; + friend AutoHideTab; + friend AutoHideTabPrivate; public: using Super = DockContainerWidget; @@ -105,8 +113,6 @@ public: = 0x0080, //!< if this flag is set, then all tabs that are closable show a close button RetainTabSizeWhenCloseButtonHidden = 0x0100, //!< if this flag is set, the space for the close button is reserved even if the close button is not visible - OpaqueUndocking - = 0x0200, ///< If enabled, the widgets are immediately undocked into floating widgets, if disabled, only a draw preview is undocked and the real undocking is deferred until the mouse is released DragPreviewIsDynamic = 0x0400, ///< If opaque undocking is disabled, this flag defines the behavior of the drag preview window, if this flag is enabled, the preview will be adjusted dynamically to the drop area DragPreviewShowsContentPixmap @@ -117,34 +123,38 @@ public: = 0x2000, ///< If this option is enabled, the tab of a dock widget is always displayed - even if it is the only visible dock widget in a floating widget. DockAreaHasUndockButton = 0x4000, //!< If the flag is set each dock area has an undock button DockAreaHasTabsMenuButton - = 0x8000, //!< If the flag is set each dock area has a tabs menu button + = 0x8000, //!< If the flag is set each dock area has a tabs menu button DockAreaHideDisabledButtons - = 0x10000, //!< If the flag is set disabled dock area buttons will not appear on the tollbar at all (enabling them will bring them back) + = 0x10000, //!< If the flag is set disabled dock area buttons will not appear on the toolbar at all (enabling them will bring them back) DockAreaDynamicTabsMenuButtonVisibility = 0x20000, //!< If the flag is set, the tabs menu button will be shown only when it is required - that means, if the tabs are elided. If the tabs are not elided, it is hidden FloatingContainerHasWidgetTitle - = 0x40000, //!< If set, the Floating Widget window title reflects the title of the current dock widget otherwise it displays application name as window title + = 0x40000, //!< If set, the Floating Widget window title reflects the title of the current dock widget otherwise it displays the title set with `CDockManager::setFloatingContainersTitle` or application name as window title FloatingContainerHasWidgetIcon = 0x80000, //!< If set, the Floating Widget icon reflects the icon of the current dock widget otherwise it displays application icon HideSingleCentralWidgetTitleBar = 0x100000, //!< If there is only one single visible dock widget in the main dock container (the dock manager) and if this flag is set, then the titlebar of this dock widget will be hidden //!< this only makes sense for non draggable and non floatable widgets and enables the creation of some kind of "central" widget + FocusHighlighting = 0x200000, //!< enables styling of focused dock widget tabs or floating widget titlebar EqualSplitOnInsertion - = 0x400000, ///!< if enabled, the space is equally distributed to all widgets in a splitter + = 0x400000, ///!< if enabled, the space is equally distributed to all widgets in a splitter + + MiddleMouseButtonClosesTab + = 0x2000000, //! If the flag is set, the user can use the mouse middle button to close the tab under the mouse DefaultDockAreaButtons = DockAreaHasCloseButton | DockAreaHasUndockButton | DockAreaHasTabsMenuButton, ///< default configuration of dock area title bar buttons - DefaultBaseConfig = DefaultDockAreaButtons | ActiveTabHasCloseButton - | XmlAutoFormattingEnabled - | FloatingContainerHasWidgetTitle, ///< default base configuration settings + DefaultBaseConfig + = DefaultDockAreaButtons | ActiveTabHasCloseButton | XmlAutoFormattingEnabled + | FloatingContainerHasWidgetTitle, ///< default base configuration settings DefaultOpaqueConfig = DefaultBaseConfig | OpaqueSplitterResize - | OpaqueUndocking, ///< the default configuration with opaque operations - this may cause issues if ActiveX or Qt 3D windows are involved + | DragPreviewShowsContentPixmap, ///< the default configuration for non opaque operations DefaultNonOpaqueConfig = DefaultBaseConfig @@ -156,6 +166,31 @@ public: }; Q_DECLARE_FLAGS(ConfigFlags, eConfigFlag) + /** + * These global configuration flags configure some dock manager auto hide settings. + * Set the dock manager flags, before you create the dock manager instance. + */ + enum eAutoHideFlag { + AutoHideFeatureEnabled = 0x01, //!< enables / disables auto hide feature + DockAreaHasAutoHideButton + = 0x02, //!< If the flag is set each dock area has a auto hide menu button + AutoHideButtonTogglesArea + = 0x04, //!< If the flag is set, the auto hide button enables auto hiding for all dock widgets in an area, if disabled, only the current dock widget will be toggled + AutoHideButtonCheckable + = 0x08, //!< If the flag is set, the auto hide button will be checked and unchecked depending on the auto hide state. Mainly for styling purposes. + AutoHideSideBarsIconOnly + = 0x10, ///< show only icons in auto hide side tab - if a tab has no icon, then the text will be shown + AutoHideShowOnMouseOver + = 0x20, ///< show the auto hide window on mouse over tab and hide it if mouse leaves auto hide container + AutoHideCloseButtonCollapsesDock + = 0x40, ///< Close button of an auto hide container collapses the dock instead of hiding it completely + + DefaultAutoHideConfig + = AutoHideFeatureEnabled | DockAreaHasAutoHideButton + | AutoHideCloseButtonCollapsesDock ///< the default configuration for left and right side bars + }; + Q_DECLARE_FLAGS(AutoHideFlags, eAutoHideFlag) + /** * Default Constructor. * If the given parent is a QMainWindow, the dock manager sets itself as the central widget. @@ -174,23 +209,45 @@ public: */ static ConfigFlags configFlags(); + /** + * This function returns the auto hide configuration flags. + */ + static AutoHideFlags autoHideConfigFlags(); + /** * Sets the global configuration flags for the whole docking system. Call this function before * you create the dock manager and before your create the first dock widget. */ static void setConfigFlags(const ConfigFlags flags); + /** + * Sets the global configuration flags for the whole docking system. Call this function before + * you create the dock manager and before your create the first dock widget. + */ + static void setAutoHideConfigFlags(const AutoHideFlags flags); + /** * Set a certain config flag. * \see setConfigFlags() */ static void setConfigFlag(eConfigFlag flag, bool on = true); + /** + * Set a certain overlay config flag. + * \see setConfigFlags() + */ + static void setAutoHideConfigFlag(eAutoHideFlag flag, bool on = true); + /** * Returns true if the given config flag is set. */ static bool testConfigFlag(eConfigFlag flag); + /** + * Returns true if the given overlay config flag is set + */ + static bool testAutoHideConfigFlag(eAutoHideFlag flag); + /** * Returns the global icon provider. * The icon provider enables the use of custom icons in case using styleheets for icons is not @@ -242,7 +299,33 @@ public: */ DockAreaWidget *addDockWidget(DockWidgetArea area, DockWidget *dockWidget, - DockAreaWidget *dockAreaWidget = nullptr); + DockAreaWidget *dockAreaWidget = nullptr, + int index = -1); + + /** + * Adds dockwidget into the given container. + * This allows you to place the dock widget into a container, even if that + * container does not yet contain a DockAreaWidget. + * \return Returns the dock area widget that contains the new DockWidget + */ + DockAreaWidget *addDockWidgetToContainer(DockWidgetArea area, + DockWidget *dockWidget, + DockContainerWidget *dockContainerWidget); + + /** + * Adds an Auto-Hide widget to the dock manager container pinned to + * the given side bar location. + * \return Returns the CAutoHideDockContainer that contains the new DockWidget + */ + AutoHideDockContainer *addAutoHideDockWidget(SideBarLocation location, DockWidget *dockWidget); + + /** + * Adds an Auto-Hide widget to the given DockContainerWidget pinned to + * the given side bar location in this container. + * \return Returns the CAutoHideDockContainer that contains the new DockWidget + */ + AutoHideDockContainer *addAutoHideDockWidgetToContainer( + SideBarLocation location, DockWidget *dockWidget, DockContainerWidget *dockContainerWidget); /** * This function will add the given Dockwidget to the given dock area as a new tab. If no dock @@ -253,7 +336,9 @@ public: /** * This function will add the given Dockwidget to the given DockAreaWidget as a new tab. */ - DockAreaWidget *addDockWidgetTabToArea(DockWidget *dockWidget, DockAreaWidget *dockAreaWidget); + DockAreaWidget *addDockWidgetTabToArea(DockWidget *dockWidget, + DockAreaWidget *dockAreaWidget, + int index = -1); /** * Adds the given DockWidget floating and returns the created FloatingDockContainer instance. @@ -322,11 +407,90 @@ public: bool isRestoringState() const; /** - * Request a focus change to the given dock widget. - * This function only has an effect, if the flag CDockManager::FocusStyling is enabled. + * This function returns true, if the DockManager window is restoring from minimized state. + * The DockManager is in this state starting from the QWindowStateChangeEvent that signals the + * state change from minimized to normal until endLeavingMinimizedState() function is called. + */ + bool isLeavingMinimizedState() const; + + bool eventFilter(QObject *obj, QEvent *e) override; + + /** + * Returns the dock widget that has focus style in the ui or a nullptr if not dock widget is + * painted focused. If the flag FocusHighlighting is disabled, this function always returns + * nullptr. + */ + DockWidget *focusedDockWidget() const; + + /** + * Returns the sizes of the splitter that contains the dock area. If there is no splitter that + * contains the area, an empty list will be returned. + */ + QList splitterSizes(DockAreaWidget *containedArea) const; + + /** + * Update the sizes of a splitter + * Programmatically updates the sizes of a given splitter by calling QSplitter::setSizes(). The + * splitter will be the splitter that contains the supplied dock area widget. If there is not + * splitter that contains the dock area, or the sizes supplied does not match the number of + * children of the splitter, this method will have no effect. + */ + void setSplitterSizes(DockAreaWidget *containedArea, const QList &sizes); + + /** + * Set a custom title for all FloatingContainer that does not reflect the title of the current + * dock widget. + */ + static void setFloatingContainersTitle(const QString &title); + + /** + * Returns the title used by all FloatingContainer that does not reflect the title of the + * current dock widget. If not title was set with setFloatingContainersTitle(), it returns + * QGuiApplication::applicationDisplayName(). + */ + static QString floatingContainersTitle(); + + /** + * This function returns managers central widget or nullptr if no central widget is set. + */ + DockWidget *centralWidget() const; + + /** + * Adds dockwidget widget into the central area and marks it as central widget. + * If central widget is set, it will be the only dock widget + * that will resize with the dock container. A central widget if not + * movable, floatable or closable and the titlebar of the central + * dock area is not visible. + * If the given widget could be set as central widget, the function returns + * the created dock area. If the widget could not be set, because there + * is already a central widget, this function returns a nullptr. + * To clear the central widget, pass a nullptr to the function. + * \note Setting a central widget is only possible if no other dock widgets + * have been registered before. That means, this function should be the + * first function that you call before you add other dock widgets. + * \retval != 0 The dock area that contains the central widget + * \retval nullptr Indicates that the given widget can not be set as central + * widget because there is already a central widget. + */ + DockAreaWidget *setCentralWidget(DockWidget *widget); + + /** + * Request a focus change to the given dock widget. This function only has an effect, if the + * flag DockManager::FocusStyling is enabled. */ void setDockWidgetFocused(DockWidget *dockWidget); + /** + * Hide CDockManager and all floating widgets (See Issue #380). Calling regular QWidget::hide() + * hides the DockManager but not the floating widgets. + */ + void hideManagerAndFloatingWidgets(); + + /** + * Ends the isRestoringFromMinimizedState + */ + void endLeavingMinimizedState(); + signals: /** * This signal is emitted if the list of workspaces changed. @@ -376,6 +540,12 @@ signals: */ void dockAreaCreated(ADS::DockAreaWidget *dockArea); + /** + * This signal is emitted if a dock widget has been added to this + * dock manager instance. + */ + void dockWidgetAdded(ADS::DockWidget *dockWidget); + /** * This signal is emitted just before removal of the given DockWidget. */ @@ -454,6 +624,17 @@ protected: */ void showEvent(QShowEvent *event) override; + /** + * Access for the internal dock focus controller. + * This function only returns a valid object, if the FocusHighlighting flag is set. + */ + DockFocusController *dockFocusController() const; + + /** + * Restore floating widgets hidden by an earlier call to hideManagerAndFloatingWidgets. + */ + void restoreHiddenFloatingWidgets(); + public: // Workspace state Workspace *activeWorkspace() const; diff --git a/src/libs/advanceddockingsystem/dockoverlay.cpp b/src/libs/advanceddockingsystem/dockoverlay.cpp index 3a3e9b5b0b0..cce1a006fd0 100644 --- a/src/libs/advanceddockingsystem/dockoverlay.cpp +++ b/src/libs/advanceddockingsystem/dockoverlay.cpp @@ -3,8 +3,12 @@ #include "dockoverlay.h" -#include "dockareawidget.h" +#include "autohidesidebar.h" +#include "dockareatabbar.h" #include "dockareatitlebar.h" +#include "dockareawidget.h" +#include "dockcontainerwidget.h" +#include "dockmanager.h" #include @@ -25,724 +29,828 @@ namespace ADS { - /** - * Private data class of DockOverlay - */ - class DockOverlayPrivate - { - public: - DockOverlay *q; - DockWidgetAreas m_allowedAreas = InvalidDockWidgetArea; - DockOverlayCross *m_cross = nullptr; - QPointer m_targetWidget; - DockWidgetArea m_lastLocation = InvalidDockWidgetArea; - bool m_dropPreviewEnabled = true; - DockOverlay::eMode m_mode = DockOverlay::ModeDockAreaOverlay; - QRect m_dropAreaRect; +static const int g_autoHideAreaWidth = 32; +static const int g_autoHideAreaMouseZone = 8; +static const int g_invalidTabIndex = -2; - /** - * Private data constructor - */ - DockOverlayPrivate(DockOverlay *parent) - : q(parent) - {} - }; +/** + * Private data class of DockOverlay + */ +class DockOverlayPrivate +{ +public: + DockOverlay *q; + DockWidgetAreas m_allowedAreas = InvalidDockWidgetArea; + DockOverlayCross *m_cross = nullptr; + QPointer m_targetWidget; + DockWidgetArea m_lastLocation = InvalidDockWidgetArea; + bool m_dropPreviewEnabled = true; + DockOverlay::eMode m_mode = DockOverlay::ModeDockAreaOverlay; + QRect m_dropAreaRect; + int m_tabIndex = g_invalidTabIndex; /** - * Private data of DockOverlayCross class + * Private data constructor */ - class DockOverlayCrossPrivate + DockOverlayPrivate(DockOverlay *parent) + : q(parent) + {} + + /** + * Returns the overlay width / height depending on the visibility of the sidebar. + */ + int sideBarOverlaySize(SideBarLocation sideBarLocation); + + /** + * The area where the mouse is considered in the sidebar. + */ + int sideBarMouseZone(SideBarLocation sideBarLocation); +}; + +/** + * Private data of DockOverlayCross class + */ +class DockOverlayCrossPrivate +{ +public: + DockOverlayCross *q; + DockOverlay::eMode m_mode = DockOverlay::ModeDockAreaOverlay; + DockOverlay *m_dockOverlay = nullptr; + QHash m_dropIndicatorWidgets; + QGridLayout *m_gridLayout = nullptr; + QColor m_iconColors[5]; + bool m_updateRequired = false; + double m_lastDevicePixelRatio = 0.1; + + /** + * Private data constructor + */ + DockOverlayCrossPrivate(DockOverlayCross *parent) + : q(parent) + {} + + /** + * @param area + * @return + */ + QPoint areaGridPosition(const DockWidgetArea area); + + /** + * Palette based default icon colors + */ + QColor defaultIconColor(DockOverlayCross::eIconColor colorIndex) { - public: - DockOverlayCross *q; - DockOverlay::eMode m_mode = DockOverlay::ModeDockAreaOverlay; - DockOverlay *m_dockOverlay = nullptr; - QHash m_dropIndicatorWidgets; - QGridLayout *m_gridLayout = nullptr; - QColor m_iconColors[5]; - bool m_updateRequired = false; - double m_lastDevicePixelRatio = 0.1; - - /** - * Private data constructor - */ - DockOverlayCrossPrivate(DockOverlayCross *parent) - : q(parent) - {} - - /** - * @param area - * @return - */ - QPoint areaGridPosition(const DockWidgetArea area); - - /** - * Palette based default icon colors - */ - QColor defaultIconColor(DockOverlayCross::eIconColor colorIndex) - { - QPalette palette = q->palette(); - switch (colorIndex) { - case DockOverlayCross::FrameColor: - return palette.color(QPalette::Active, QPalette::Highlight); - case DockOverlayCross::WindowBackgroundColor: - return palette.color(QPalette::Active, QPalette::Base); - case DockOverlayCross::OverlayColor: { - QColor color = palette.color(QPalette::Active, QPalette::Highlight); - color.setAlpha(64); - return color; - } - case DockOverlayCross::ArrowColor: - return palette.color(QPalette::Active, QPalette::Base); - case DockOverlayCross::ShadowColor: - return QColor(0, 0, 0, 64); - } - - return QColor(); - } - - /** - * Stylehseet based icon colors - */ - QColor iconColor(DockOverlayCross::eIconColor colorIndex) - { - QColor color = m_iconColors[colorIndex]; - if (!color.isValid()) { - color = defaultIconColor(colorIndex); - m_iconColors[colorIndex] = color; - } + QPalette palette = q->palette(); + switch (colorIndex) { + case DockOverlayCross::FrameColor: + return palette.color(QPalette::Active, QPalette::Highlight); + case DockOverlayCross::WindowBackgroundColor: + return palette.color(QPalette::Active, QPalette::Base); + case DockOverlayCross::OverlayColor: { + QColor color = palette.color(QPalette::Active, QPalette::Highlight); + color.setAlpha(64); return color; } + case DockOverlayCross::ArrowColor: + return palette.color(QPalette::Active, QPalette::Base); + case DockOverlayCross::ShadowColor: + return QColor(0, 0, 0, 64); + } - /** - * Helper function that returns the drop indicator width depending on the - * operating system - */ - qreal dropIndicatiorWidth(QLabel *label) const - { - if (Utils::HostOsInfo::isLinuxHost()) - return 40; + return QColor(); + } + + /** + * Stylehseet based icon colors + */ + QColor iconColor(DockOverlayCross::eIconColor colorIndex) + { + QColor color = m_iconColors[colorIndex]; + if (!color.isValid()) { + color = defaultIconColor(colorIndex); + m_iconColors[colorIndex] = color; + } + return color; + } + + /** + * Helper function that returns the drop indicator width depending on the + * operating system + */ + qreal dropIndicatorWidth(QLabel *label) const + { + if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost()) + return 40; + else + return static_cast(label->fontMetrics().height()) * 3.f; + } + + QWidget *createDropIndicatorWidget(DockWidgetArea dockWidgetArea, DockOverlay::eMode mode) + { + QLabel *label = new QLabel(); + label->setObjectName("DockWidgetAreaLabel"); + + const qreal metric = dropIndicatorWidth(label); + QSizeF size(metric, metric); + + if (internal::isSideBarArea(dockWidgetArea)) { + auto sideBarLocation = internal::toSideBarLocation(dockWidgetArea); + if (internal::isHorizontalSideBarLocation(sideBarLocation)) + size.setHeight(size.height() / 2); else - return static_cast(label->fontMetrics().height()) * 3.f; + size.setWidth(size.width() / 2); } - QWidget *createDropIndicatorWidget(DockWidgetArea dockWidgetArea, DockOverlay::eMode mode) - { - QLabel *label = new QLabel(); - label->setObjectName("DockWidgetAreaLabel"); + label->setPixmap(createHighDpiDropIndicatorPixmap(size, dockWidgetArea, mode)); + label->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + label->setAttribute(Qt::WA_TranslucentBackground); + label->setProperty("dockWidgetArea", dockWidgetArea); + return label; + } - const qreal metric = dropIndicatiorWidth(label); - const QSizeF size(metric, metric); + void updateDropIndicatorIcon(QWidget *dropIndicatorWidget) + { + QLabel *label = qobject_cast(dropIndicatorWidget); + const qreal metric = dropIndicatorWidth(label); + const QSizeF size(metric, metric); - label->setPixmap(createHighDpiDropIndicatorPixmap(size, dockWidgetArea, mode)); - label->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - label->setAttribute(Qt::WA_TranslucentBackground); - label->setProperty("dockWidgetArea", dockWidgetArea); - return label; + int area = label->property("dockWidgetArea").toInt(); + label->setPixmap(createHighDpiDropIndicatorPixmap(size, + static_cast(area), + m_mode)); // TODO + } + + QPixmap createHighDpiDropIndicatorPixmap(const QSizeF &size, + DockWidgetArea dockWidgetArea, + DockOverlay::eMode mode) + { + const QColor borderColor = iconColor(DockOverlayCross::FrameColor); + const QColor backgroundColor = iconColor(DockOverlayCross::WindowBackgroundColor); + + QColor overlayColor = iconColor(DockOverlayCross::OverlayColor); + if (overlayColor.alpha() == 255) + overlayColor.setAlpha(64); + + const double devicePixelRatio = q->window()->devicePixelRatioF(); + const QSizeF pixmapSize = size * devicePixelRatio; + QPixmap pixmap(pixmapSize.toSize()); + pixmap.fill(QColor(0, 0, 0, 0)); + + QPainter painter(&pixmap); + QPen pen = painter.pen(); + QRectF shadowRect(pixmap.rect()); + QRectF baseRect; + baseRect.setSize(shadowRect.size() * 0.7); + baseRect.moveCenter(shadowRect.center()); + + // Fill + QColor shadowColor = iconColor(DockOverlayCross::ShadowColor); + if (shadowColor.alpha() == 255) { + shadowColor.setAlpha(64); } + painter.fillRect(shadowRect, shadowColor); - void updateDropIndicatorIcon(QWidget *dropIndicatorWidget) - { - QLabel *label = qobject_cast(dropIndicatorWidget); - const qreal metric = dropIndicatiorWidth(label); - const QSizeF size(metric, metric); - - int area = label->property("dockWidgetArea").toInt(); - label->setPixmap(createHighDpiDropIndicatorPixmap(size, - static_cast(area), - m_mode)); // TODO - } - - QPixmap createHighDpiDropIndicatorPixmap(const QSizeF &size, - DockWidgetArea dockWidgetArea, - DockOverlay::eMode mode) - { - const QColor borderColor = iconColor(DockOverlayCross::FrameColor); - const QColor backgroundColor = iconColor(DockOverlayCross::WindowBackgroundColor); - const double devicePixelRatio = q->window()->devicePixelRatioF(); - const QSizeF pixmapSize = size * devicePixelRatio; - QPixmap pixmap(pixmapSize.toSize()); - pixmap.fill(QColor(0, 0, 0, 0)); - - QPainter painter(&pixmap); - QPen pen = painter.pen(); - QRectF shadowRect(pixmap.rect()); - QRectF baseRect; - baseRect.setSize(shadowRect.size() * 0.7); - baseRect.moveCenter(shadowRect.center()); - - // Fill - QColor shadowColor = iconColor(DockOverlayCross::ShadowColor); - if (shadowColor.alpha() == 255) { - shadowColor.setAlpha(64); - } - painter.fillRect(shadowRect, shadowColor); - - // Drop area rect. - painter.save(); - QRectF areaRect; - QLineF areaLine; - QRectF nonAreaRect; - switch (dockWidgetArea) { - case TopDockWidgetArea: - areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * 0.5); - nonAreaRect = QRectF(baseRect.x(), - shadowRect.height() * 0.5, - baseRect.width(), - baseRect.height() * 0.5); - areaLine = QLineF(areaRect.bottomLeft(), areaRect.bottomRight()); - break; - case RightDockWidgetArea: - areaRect = QRectF(shadowRect.width() * 0.5, - baseRect.y(), - baseRect.width() * 0.5, - baseRect.height()); - nonAreaRect = QRectF(baseRect.x(), - baseRect.y(), - baseRect.width() * 0.5, - baseRect.height()); - areaLine = QLineF(areaRect.topLeft(), areaRect.bottomLeft()); - break; - case BottomDockWidgetArea: - areaRect = QRectF(baseRect.x(), - shadowRect.height() * 0.5, - baseRect.width(), - baseRect.height() * 0.5); - nonAreaRect = QRectF(baseRect.x(), - baseRect.y(), - baseRect.width(), - baseRect.height() * 0.5); - areaLine = QLineF(areaRect.topLeft(), areaRect.topRight()); - break; - case LeftDockWidgetArea: - areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * 0.5, baseRect.height()); - nonAreaRect = QRectF(shadowRect.width() * 0.5, - baseRect.y(), - baseRect.width() * 0.5, - baseRect.height()); - areaLine = QLineF(areaRect.topRight(), areaRect.bottomRight()); - break; - default: - break; - } - - const QSizeF baseSize = baseRect.size(); - if (DockOverlay::ModeContainerOverlay == mode && dockWidgetArea != CenterDockWidgetArea) { - baseRect = areaRect; - } - - painter.fillRect(baseRect, backgroundColor); - if (areaRect.isValid()) { - pen = painter.pen(); - pen.setColor(borderColor); - QColor color = iconColor(DockOverlayCross::OverlayColor); - if (color.alpha() == 255) { - color.setAlpha(64); - } - painter.setBrush(color); - painter.setPen(Qt::NoPen); - painter.drawRect(areaRect); - - pen = painter.pen(); - pen.setWidth(1); - pen.setColor(borderColor); - pen.setStyle(Qt::DashLine); - painter.setPen(pen); - painter.drawLine(areaLine); - } - painter.restore(); - - painter.save(); - // Draw outer border - pen = painter.pen(); - pen.setColor(borderColor); - pen.setWidth(1); - painter.setBrush(Qt::NoBrush); - painter.setPen(pen); - painter.drawRect(baseRect); - - // draw window title bar - painter.setBrush(borderColor); - const QRectF frameRect(baseRect.topLeft(), QSizeF(baseRect.width(), baseSize.height() / 10)); - painter.drawRect(frameRect); - painter.restore(); - - // Draw arrow for outer container drop indicators - if (DockOverlay::ModeContainerOverlay == mode && dockWidgetArea != CenterDockWidgetArea) { - QRectF arrowRect; - arrowRect.setSize(baseSize); - arrowRect.setWidth(arrowRect.width() / 4.6); - arrowRect.setHeight(arrowRect.height() / 2); - arrowRect.moveCenter(QPointF(0, 0)); - QPolygonF arrow; - arrow << arrowRect.topLeft() << QPointF(arrowRect.right(), arrowRect.center().y()) - << arrowRect.bottomLeft(); - painter.setPen(Qt::NoPen); - painter.setBrush(iconColor(DockOverlayCross::ArrowColor)); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.translate(nonAreaRect.center().x(), nonAreaRect.center().y()); - - switch (dockWidgetArea) { - case TopDockWidgetArea: - painter.rotate(-90); - break; - case RightDockWidgetArea: - break; - case BottomDockWidgetArea: - painter.rotate(90); - break; - case LeftDockWidgetArea: - painter.rotate(180); - break; - default: - break; - } - - painter.drawPolygon(arrow); - } - - pixmap.setDevicePixelRatio(devicePixelRatio); - return pixmap; - } - }; // class DockOverlayCrossPrivate - - DockOverlay::DockOverlay(QWidget *parent, eMode mode) - : QFrame(parent) - , d(new DockOverlayPrivate(this)) - { - d->m_mode = mode; - d->m_cross = new DockOverlayCross(this); - - if (Utils::HostOsInfo::isLinuxHost()) - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint - | Qt::X11BypassWindowManagerHint); - else - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - - setWindowOpacity(1); - setWindowTitle("DockOverlay"); - setAttribute(Qt::WA_NoSystemBackground); - setAttribute(Qt::WA_TranslucentBackground); - - d->m_cross->setVisible(false); - setVisible(false); - } - - DockOverlay::~DockOverlay() - { - delete d; - } - - void DockOverlay::setAllowedAreas(DockWidgetAreas areas) - { - if (areas == d->m_allowedAreas) - return; - - d->m_allowedAreas = areas; - d->m_cross->reset(); - } - - DockWidgetAreas DockOverlay::allowedAreas() const - { - return d->m_allowedAreas; - } - - DockWidgetArea DockOverlay::dropAreaUnderCursor() const - { - DockWidgetArea result = d->m_cross->cursorLocation(); - if (result != InvalidDockWidgetArea) - return result; - - DockAreaWidget *dockArea = qobject_cast(d->m_targetWidget.data()); - if (!dockArea) - return result; - - if (dockArea->allowedAreas().testFlag(CenterDockWidgetArea) - && !dockArea->titleBar()->isHidden() - && dockArea->titleBarGeometry().contains(dockArea->mapFromGlobal(QCursor::pos()))) - return CenterDockWidgetArea; - - return result; - } - - DockWidgetArea DockOverlay::visibleDropAreaUnderCursor() const - { - if (isHidden() || !d->m_dropPreviewEnabled) - return InvalidDockWidgetArea; - else - return dropAreaUnderCursor(); - } - - DockWidgetArea DockOverlay::showOverlay(QWidget *target) - { - if (d->m_targetWidget == target) { - // Hint: We could update geometry of overlay here. - DockWidgetArea dockWidgetArea = dropAreaUnderCursor(); - if (dockWidgetArea != d->m_lastLocation) { - repaint(); - d->m_lastLocation = dockWidgetArea; - } - return dockWidgetArea; - } - - d->m_targetWidget = target; - d->m_lastLocation = InvalidDockWidgetArea; - - // Move it over the target. - hide(); - resize(target->size()); - QPoint topLeft = target->mapToGlobal(target->rect().topLeft()); - move(topLeft); - show(); - d->m_cross->updatePosition(); - d->m_cross->updateOverlayIcons(); - return dropAreaUnderCursor(); - } - - void DockOverlay::hideOverlay() - { - hide(); - d->m_targetWidget.clear(); - d->m_lastLocation = InvalidDockWidgetArea; - d->m_dropAreaRect = QRect(); - } - - void DockOverlay::enableDropPreview(bool enable) - { - d->m_dropPreviewEnabled = enable; - update(); - } - - bool DockOverlay::dropPreviewEnabled() const - { - return d->m_dropPreviewEnabled; - } - - void DockOverlay::paintEvent(QPaintEvent *event) - { - Q_UNUSED(event) - // Draw rect based on location - if (!d->m_dropPreviewEnabled) { - d->m_dropAreaRect = QRect(); - return; - } - - QRect rectangle = rect(); - const DockWidgetArea dockWidgetArea = dropAreaUnderCursor(); - double factor = (DockOverlay::ModeContainerOverlay == d->m_mode) ? 3 : 2; - + // Drop area rect. + painter.save(); + QRectF areaRect; + QLineF areaLine; + QRectF nonAreaRect; switch (dockWidgetArea) { case TopDockWidgetArea: - rectangle.setHeight(static_cast(rectangle.height() / factor)); + areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * 0.5); + nonAreaRect = QRectF(baseRect.x(), + shadowRect.height() * 0.5, + baseRect.width(), + baseRect.height() * 0.5); + areaLine = QLineF(areaRect.bottomLeft(), areaRect.bottomRight()); break; case RightDockWidgetArea: - rectangle.setX(static_cast(rectangle.width() * (1 - 1 / factor))); + areaRect = QRectF(shadowRect.width() * 0.5, + baseRect.y(), + baseRect.width() * 0.5, + baseRect.height()); + nonAreaRect + = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * 0.5, baseRect.height()); + areaLine = QLineF(areaRect.topLeft(), areaRect.bottomLeft()); break; case BottomDockWidgetArea: - rectangle.setY(static_cast(rectangle.height() * (1 - 1 / factor))); + areaRect = QRectF(baseRect.x(), + shadowRect.height() * 0.5, + baseRect.width(), + baseRect.height() * 0.5); + nonAreaRect + = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * 0.5); + areaLine = QLineF(areaRect.topLeft(), areaRect.topRight()); break; case LeftDockWidgetArea: - rectangle.setWidth(static_cast(rectangle.width() / factor)); - break; - case CenterDockWidgetArea: - rectangle = rect(); + areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * 0.5, baseRect.height()); + nonAreaRect = QRectF(shadowRect.width() * 0.5, + baseRect.y(), + baseRect.width() * 0.5, + baseRect.height()); + areaLine = QLineF(areaRect.topRight(), areaRect.bottomRight()); break; default: - return; + break; } - QPainter painter(this); - QColor color = palette().color(QPalette::Active, QPalette::Highlight); - QPen pen = painter.pen(); - pen.setColor(color.darker(120)); - pen.setStyle(Qt::SolidLine); + + const QSizeF baseSize = baseRect.size(); + bool isOuterContainerArea = (DockOverlay::ModeContainerOverlay == mode) + && (dockWidgetArea != CenterDockWidgetArea) + && !internal::isSideBarArea(dockWidgetArea); + if (isOuterContainerArea) + baseRect = areaRect; + + painter.fillRect(baseRect, backgroundColor); + + if (areaRect.isValid()) { + pen = painter.pen(); + pen.setColor(borderColor); + painter.setBrush(overlayColor); + painter.setPen(Qt::NoPen); + painter.drawRect(areaRect); + + pen = painter.pen(); + pen.setWidth(1); + pen.setColor(borderColor); + pen.setStyle(Qt::DashLine); + painter.setPen(pen); + painter.drawLine(areaLine); + } + painter.restore(); + + painter.save(); + // Draw outer border + pen = painter.pen(); + pen.setColor(borderColor); pen.setWidth(1); - pen.setCosmetic(true); + painter.setBrush(Qt::NoBrush); painter.setPen(pen); - color = color.lighter(130); - color.setAlpha(64); - painter.setBrush(color); - painter.drawRect(rectangle.adjusted(0, 0, -1, -1)); - d->m_dropAreaRect = rectangle; + painter.drawRect(baseRect); + + // draw window title bar + painter.setBrush(borderColor); + const QRectF frameRect(baseRect.topLeft(), QSizeF(baseRect.width(), baseSize.height() / 10)); + painter.drawRect(frameRect); + painter.restore(); + + // Draw arrow for outer container drop indicators + if (isOuterContainerArea) { + QRectF arrowRect; + arrowRect.setSize(baseSize); + arrowRect.setWidth(arrowRect.width() / 4.6); + arrowRect.setHeight(arrowRect.height() / 2); + arrowRect.moveCenter(QPointF(0, 0)); + QPolygonF arrow; + arrow << arrowRect.topLeft() << QPointF(arrowRect.right(), arrowRect.center().y()) + << arrowRect.bottomLeft(); + painter.setPen(Qt::NoPen); + painter.setBrush(iconColor(DockOverlayCross::ArrowColor)); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.translate(nonAreaRect.center().x(), nonAreaRect.center().y()); + + switch (dockWidgetArea) { + case TopDockWidgetArea: + painter.rotate(-90); + break; + case RightDockWidgetArea: + break; + case BottomDockWidgetArea: + painter.rotate(90); + break; + case LeftDockWidgetArea: + painter.rotate(180); + break; + default: + break; + } + + painter.drawPolygon(arrow); + } + + pixmap.setDevicePixelRatio(devicePixelRatio); + return pixmap; } +}; // class DockOverlayCrossPrivate - QRect DockOverlay::dropOverlayRect() const - { - return d->m_dropAreaRect; - } +int DockOverlayPrivate::sideBarOverlaySize(SideBarLocation sideBarLocation) +{ + auto container = qobject_cast(m_targetWidget.data()); + auto sideBar = container->autoHideSideBar(sideBarLocation); + if (!sideBar || !sideBar->isVisibleTo(container)) + return g_autoHideAreaWidth; + else + return (sideBar->orientation() == Qt::Horizontal) ? sideBar->height() : sideBar->width(); +} - void DockOverlay::showEvent(QShowEvent *event) - { - d->m_cross->show(); - QFrame::showEvent(event); - } +int DockOverlayPrivate::sideBarMouseZone(SideBarLocation sideBarLocation) +{ + auto container = qobject_cast(m_targetWidget.data()); + auto sideBar = container->autoHideSideBar(sideBarLocation); + if (!sideBar || !sideBar->isVisibleTo(container)) + return g_autoHideAreaMouseZone; + else + return (sideBar->orientation() == Qt::Horizontal) ? sideBar->height() : sideBar->width(); +} - void DockOverlay::hideEvent(QHideEvent *event) - { - d->m_cross->hide(); - QFrame::hideEvent(event); - } +DockOverlay::DockOverlay(QWidget *parent, eMode mode) + : QFrame(parent) + , d(new DockOverlayPrivate(this)) +{ + d->m_mode = mode; + d->m_cross = new DockOverlayCross(this); - bool DockOverlay::event(QEvent *event) - { - bool result = Super::event(event); - if (event->type() == QEvent::Polish) - d->m_cross->setupOverlayCross(d->m_mode); + if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost()) + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint + | Qt::X11BypassWindowManagerHint); + else + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setWindowOpacity(1); + setWindowTitle("DockOverlay"); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); + + d->m_cross->setVisible(false); + setVisible(false); +} + +DockOverlay::~DockOverlay() +{ + delete d; +} + +void DockOverlay::setAllowedAreas(DockWidgetAreas areas) +{ + if (areas == d->m_allowedAreas) + return; + + d->m_allowedAreas = areas; + d->m_cross->reset(); +} + +void DockOverlay::setAllowedArea(DockWidgetArea area, bool enable) +{ + auto areasOld = d->m_allowedAreas; + d->m_allowedAreas.setFlag(area, enable); + if (areasOld != d->m_allowedAreas) + d->m_cross->reset(); +} + +DockWidgetAreas DockOverlay::allowedAreas() const +{ + return d->m_allowedAreas; +} + +DockWidgetArea DockOverlay::dropAreaUnderCursor() const +{ + d->m_tabIndex = g_invalidTabIndex; + if (!d->m_targetWidget) + return InvalidDockWidgetArea; + + DockWidgetArea result = d->m_cross->cursorLocation(); + if (result != InvalidDockWidgetArea) + return result; + + auto cursorPos = QCursor::pos(); + auto dockArea = qobject_cast(d->m_targetWidget.data()); + if (!dockArea + && DockManager::autoHideConfigFlags().testFlag(DockManager::AutoHideFeatureEnabled)) { + auto rectangle = rect(); + const QPoint pos = mapFromGlobal(QCursor::pos()); + if ((pos.x() < d->sideBarMouseZone(SideBarLeft)) + && d->m_allowedAreas.testFlag(LeftAutoHideArea)) { + result = LeftAutoHideArea; + } else if (pos.x() > (rectangle.width() - d->sideBarMouseZone(SideBarRight)) + && d->m_allowedAreas.testFlag(RightAutoHideArea)) { + result = RightAutoHideArea; + } else if (pos.y() < d->sideBarMouseZone(SideBarTop) + && d->m_allowedAreas.testFlag(TopAutoHideArea)) { + result = TopAutoHideArea; + } else if (pos.y() > (rectangle.height() - d->sideBarMouseZone(SideBarBottom)) + && d->m_allowedAreas.testFlag(BottomAutoHideArea)) { + result = BottomAutoHideArea; + } + + auto sideBarLocation = internal::toSideBarLocation(result); + if (sideBarLocation != SideBarNone) { + auto Container = qobject_cast(d->m_targetWidget.data()); + auto SideBar = Container->autoHideSideBar(sideBarLocation); + if (SideBar->isVisible()) { + d->m_tabIndex = SideBar->tabInsertIndexAt(SideBar->mapFromGlobal(cursorPos)); + } + } + return result; + } else if (!dockArea) { return result; } - static int areaAlignment(const DockWidgetArea area) - { + if (dockArea->allowedAreas().testFlag(CenterDockWidgetArea) && !dockArea->titleBar()->isHidden() + && dockArea->titleBarGeometry().contains(dockArea->mapFromGlobal(cursorPos))) { + auto tabBar = dockArea->titleBar()->tabBar(); + d->m_tabIndex = tabBar->tabInsertIndexAt(tabBar->mapFromGlobal(cursorPos)); + return CenterDockWidgetArea; + } + + return result; +} + +int DockOverlay::tabIndexUnderCursor() const +{ + return d->m_tabIndex; +} + +DockWidgetArea DockOverlay::visibleDropAreaUnderCursor() const +{ + if (isHidden() || !d->m_dropPreviewEnabled) + return InvalidDockWidgetArea; + else + return dropAreaUnderCursor(); +} + +DockWidgetArea DockOverlay::showOverlay(QWidget *target) +{ + if (d->m_targetWidget == target) { + // Hint: We could update geometry of overlay here. + DockWidgetArea dockWidgetArea = dropAreaUnderCursor(); + if (dockWidgetArea != d->m_lastLocation) { + repaint(); + d->m_lastLocation = dockWidgetArea; + } + return dockWidgetArea; + } + + d->m_targetWidget = target; + d->m_lastLocation = InvalidDockWidgetArea; + + // Move it over the target. + hide(); + resize(target->size()); + QPoint topLeft = target->mapToGlobal(target->rect().topLeft()); + move(topLeft); + show(); + d->m_cross->updatePosition(); + d->m_cross->updateOverlayIcons(); + return dropAreaUnderCursor(); +} + +void DockOverlay::hideOverlay() +{ + hide(); + d->m_targetWidget.clear(); + d->m_lastLocation = InvalidDockWidgetArea; + d->m_dropAreaRect = QRect(); +} + +void DockOverlay::enableDropPreview(bool enable) +{ + d->m_dropPreviewEnabled = enable; + update(); +} + +bool DockOverlay::dropPreviewEnabled() const +{ + return d->m_dropPreviewEnabled; +} + +void DockOverlay::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + // Draw rect based on location + if (!d->m_dropPreviewEnabled) { + d->m_dropAreaRect = QRect(); + return; + } + + QRect rectangle = rect(); + const DockWidgetArea dockWidgetArea = dropAreaUnderCursor(); + double factor = (DockOverlay::ModeContainerOverlay == d->m_mode) ? 3 : 2; + + switch (dockWidgetArea) { + case TopDockWidgetArea: + rectangle.setHeight(static_cast(rectangle.height() / factor)); + break; + case RightDockWidgetArea: + rectangle.setX(static_cast(rectangle.width() * (1 - 1 / factor))); + break; + case BottomDockWidgetArea: + rectangle.setY(static_cast(rectangle.height() * (1 - 1 / factor))); + break; + case LeftDockWidgetArea: + rectangle.setWidth(static_cast(rectangle.width() / factor)); + break; + case CenterDockWidgetArea: + rectangle = rect(); + break; + case LeftAutoHideArea: + rectangle.setWidth(d->sideBarOverlaySize(SideBarLeft)); + break; + case RightAutoHideArea: + rectangle.setX(rectangle.width() - d->sideBarOverlaySize(SideBarRight)); + break; + case TopAutoHideArea: + rectangle.setHeight(d->sideBarOverlaySize(SideBarTop)); + break; + case BottomAutoHideArea: + rectangle.setY(rectangle.height() - d->sideBarOverlaySize(SideBarBottom)); + break; + default: + return; + } + + QPainter painter(this); + QColor color = palette().color(QPalette::Active, QPalette::Highlight); + QPen pen = painter.pen(); + pen.setColor(color.darker(120)); + pen.setStyle(Qt::SolidLine); + pen.setWidth(1); + pen.setCosmetic(true); + painter.setPen(pen); + color = color.lighter(130); + color.setAlpha(64); + painter.setBrush(color); + painter.drawRect(rectangle.adjusted(0, 0, -1, -1)); + d->m_dropAreaRect = rectangle; +} + +QRect DockOverlay::dropOverlayRect() const +{ + return d->m_dropAreaRect; +} + +void DockOverlay::showEvent(QShowEvent *event) +{ + d->m_cross->show(); + QFrame::showEvent(event); +} + +void DockOverlay::hideEvent(QHideEvent *event) +{ + d->m_cross->hide(); + QFrame::hideEvent(event); +} + +bool DockOverlay::event(QEvent *event) +{ + bool result = Super::event(event); + if (event->type() == QEvent::Polish) + d->m_cross->setupOverlayCross(d->m_mode); + + return result; +} + +static int areaAlignment(const DockWidgetArea area) +{ + switch (area) { + case TopDockWidgetArea: + return Qt::AlignHCenter | Qt::AlignBottom; + case RightDockWidgetArea: + return Qt::AlignLeft | Qt::AlignVCenter; + case BottomDockWidgetArea: + return Qt::AlignHCenter | Qt::AlignTop; + case LeftDockWidgetArea: + return Qt::AlignRight | Qt::AlignVCenter; + case CenterDockWidgetArea: + return Qt::AlignCenter; + default: + return Qt::AlignCenter; + } +} + +// DockOverlayCrossPrivate +QPoint DockOverlayCrossPrivate::areaGridPosition(const DockWidgetArea area) +{ + if (DockOverlay::ModeDockAreaOverlay == m_mode) { switch (area) { case TopDockWidgetArea: - return Qt::AlignHCenter | Qt::AlignBottom; + return QPoint(1, 2); case RightDockWidgetArea: - return Qt::AlignLeft | Qt::AlignVCenter; + return QPoint(2, 3); case BottomDockWidgetArea: - return Qt::AlignHCenter | Qt::AlignTop; + return QPoint(3, 2); case LeftDockWidgetArea: - return Qt::AlignRight | Qt::AlignVCenter; + return QPoint(2, 1); case CenterDockWidgetArea: - return Qt::AlignCenter; + return QPoint(2, 2); default: - return Qt::AlignCenter; + return QPoint(); + } + } else { + switch (area) { + case TopDockWidgetArea: + return QPoint(0, 2); + case RightDockWidgetArea: + return QPoint(2, 4); + case BottomDockWidgetArea: + return QPoint(4, 2); + case LeftDockWidgetArea: + return QPoint(2, 0); + case CenterDockWidgetArea: + return QPoint(2, 2); + default: + return QPoint(); + } + } +} + +DockOverlayCross::DockOverlayCross(DockOverlay *overlay) + : QWidget(overlay->parentWidget()) + , d(new DockOverlayCrossPrivate(this)) +{ + d->m_dockOverlay = overlay; + + if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost()) + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint + | Qt::X11BypassWindowManagerHint); + else + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + + setWindowTitle("DockOverlayCross"); + setAttribute(Qt::WA_TranslucentBackground); + + d->m_gridLayout = new QGridLayout(); + d->m_gridLayout->setSpacing(0); + setLayout(d->m_gridLayout); +} + +DockOverlayCross::~DockOverlayCross() +{ + delete d; +} + +void DockOverlayCross::setupOverlayCross(DockOverlay::eMode mode) +{ + d->m_mode = mode; + + QHash areaWidgets; + areaWidgets.insert(TopDockWidgetArea, d->createDropIndicatorWidget(TopDockWidgetArea, mode)); + areaWidgets.insert(RightDockWidgetArea, d->createDropIndicatorWidget(RightDockWidgetArea, mode)); + areaWidgets.insert(BottomDockWidgetArea, + d->createDropIndicatorWidget(BottomDockWidgetArea, mode)); + areaWidgets.insert(LeftDockWidgetArea, d->createDropIndicatorWidget(LeftDockWidgetArea, mode)); + areaWidgets.insert(CenterDockWidgetArea, + d->createDropIndicatorWidget(CenterDockWidgetArea, mode)); + d->m_lastDevicePixelRatio = devicePixelRatioF(); + setAreaWidgets(areaWidgets); + d->m_updateRequired = false; +} + +void DockOverlayCross::updateOverlayIcons() +{ + if (windowHandle()->devicePixelRatio() == d->m_lastDevicePixelRatio) // TODO + return; + + for (auto widget : std::as_const(d->m_dropIndicatorWidgets)) + d->updateDropIndicatorIcon(widget); + + d->m_lastDevicePixelRatio = devicePixelRatioF(); +} + +void DockOverlayCross::setIconColor(eIconColor colorIndex, const QColor &color) +{ + d->m_iconColors[colorIndex] = color; + d->m_updateRequired = true; +} + +QColor DockOverlayCross::iconColor(eIconColor colorIndex) const +{ + return d->m_iconColors[colorIndex]; +} + +void DockOverlayCross::setAreaWidgets(const QHash &widgets) +{ + // Delete old widgets. + const auto values = d->m_dropIndicatorWidgets.values(); + for (auto widget : values) { + d->m_gridLayout->removeWidget(widget); + delete widget; + } + d->m_dropIndicatorWidgets.clear(); + + // Insert new widgets into grid. + d->m_dropIndicatorWidgets = widgets; + + const QHash areas = d->m_dropIndicatorWidgets; + QHash::const_iterator constIt; + for (constIt = areas.begin(); constIt != areas.end(); ++constIt) { + const DockWidgetArea area = constIt.key(); + QWidget *widget = constIt.value(); + const QPoint position = d->areaGridPosition(area); + d->m_gridLayout->addWidget(widget, + position.x(), + position.y(), + static_cast(areaAlignment(area))); + } + + if (DockOverlay::ModeDockAreaOverlay == d->m_mode) { + d->m_gridLayout->setContentsMargins(0, 0, 0, 0); + d->m_gridLayout->setRowStretch(0, 1); + d->m_gridLayout->setRowStretch(1, 0); + d->m_gridLayout->setRowStretch(2, 0); + d->m_gridLayout->setRowStretch(3, 0); + d->m_gridLayout->setRowStretch(4, 1); + + d->m_gridLayout->setColumnStretch(0, 1); + d->m_gridLayout->setColumnStretch(1, 0); + d->m_gridLayout->setColumnStretch(2, 0); + d->m_gridLayout->setColumnStretch(3, 0); + d->m_gridLayout->setColumnStretch(4, 1); + } else { + d->m_gridLayout->setContentsMargins(4, 4, 4, 4); + d->m_gridLayout->setRowStretch(0, 0); + d->m_gridLayout->setRowStretch(1, 1); + d->m_gridLayout->setRowStretch(2, 1); + d->m_gridLayout->setRowStretch(3, 1); + d->m_gridLayout->setRowStretch(4, 0); + + d->m_gridLayout->setColumnStretch(0, 0); + d->m_gridLayout->setColumnStretch(1, 1); + d->m_gridLayout->setColumnStretch(2, 1); + d->m_gridLayout->setColumnStretch(3, 1); + d->m_gridLayout->setColumnStretch(4, 0); + } + reset(); +} + +DockWidgetArea DockOverlayCross::cursorLocation() const +{ + const QPoint position = mapFromGlobal(QCursor::pos()); + + const QHash areas = d->m_dropIndicatorWidgets; + QHash::const_iterator constIt; + for (constIt = areas.begin(); constIt != areas.end(); ++constIt) { + if (d->m_dockOverlay->allowedAreas().testFlag(constIt.key()) && constIt.value() + && constIt.value()->isVisible() && constIt.value()->geometry().contains(position)) { + return constIt.key(); } } - // DockOverlayCrossPrivate - QPoint DockOverlayCrossPrivate::areaGridPosition(const DockWidgetArea area) - { - if (DockOverlay::ModeDockAreaOverlay == m_mode) { - switch (area) { - case TopDockWidgetArea: - return QPoint(1, 2); - case RightDockWidgetArea: - return QPoint(2, 3); - case BottomDockWidgetArea: - return QPoint(3, 2); - case LeftDockWidgetArea: - return QPoint(2, 1); - case CenterDockWidgetArea: - return QPoint(2, 2); - default: - return QPoint(); - } - } else { - switch (area) { - case TopDockWidgetArea: - return QPoint(0, 2); - case RightDockWidgetArea: - return QPoint(2, 4); - case BottomDockWidgetArea: - return QPoint(4, 2); - case LeftDockWidgetArea: - return QPoint(2, 0); - case CenterDockWidgetArea: - return QPoint(2, 2); - default: - return QPoint(); - } - } + return InvalidDockWidgetArea; +} + +void DockOverlayCross::showEvent(QShowEvent *) +{ + if (d->m_updateRequired) + setupOverlayCross(d->m_mode); + + this->updatePosition(); +} + +void DockOverlayCross::updatePosition() +{ + resize(d->m_dockOverlay->size()); + QPoint topLeft = d->m_dockOverlay->pos(); + QPoint offest((this->width() - d->m_dockOverlay->width()) / 2, + (this->height() - d->m_dockOverlay->height()) / 2); + QPoint crossTopLeft = topLeft - offest; + move(crossTopLeft); +} + +void DockOverlayCross::reset() +{ + const QList allAreas{TopDockWidgetArea, + RightDockWidgetArea, + BottomDockWidgetArea, + LeftDockWidgetArea, + CenterDockWidgetArea}; + const DockWidgetAreas allowedAreas = d->m_dockOverlay->allowedAreas(); + + // Update visibility of area widgets based on allowedAreas. + for (auto area : allAreas) { + const QPoint position = d->areaGridPosition(area); + QLayoutItem *item = d->m_gridLayout->itemAtPosition(position.x(), position.y()); + QWidget *widget = nullptr; + if (item && (widget = item->widget()) != nullptr) + widget->setVisible(allowedAreas.testFlag(area)); + } +} + +void DockOverlayCross::setIconColors(const QString &colors) +{ + static const QMap + colorCompenentStringMap{{"Frame", DockOverlayCross::FrameColor}, + {"Background", DockOverlayCross::WindowBackgroundColor}, + {"Overlay", DockOverlayCross::OverlayColor}, + {"Arrow", DockOverlayCross::ArrowColor}, + {"Shadow", DockOverlayCross::ShadowColor}}; + + auto colorList = colors.split(' ', Qt::SkipEmptyParts); + for (const auto &colorListEntry : colorList) { + auto componentColor = colorListEntry.split('=', Qt::SkipEmptyParts); + int component = colorCompenentStringMap.value(componentColor[0], -1); + if (component < 0) + continue; + + d->m_iconColors[component] = QColor(componentColor[1]); } - DockOverlayCross::DockOverlayCross(DockOverlay *overlay) - : QWidget(overlay->parentWidget()) - , d(new DockOverlayCrossPrivate(this)) - { - d->m_dockOverlay = overlay; + d->m_updateRequired = true; +} - if (Utils::HostOsInfo::isLinuxHost()) - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint - | Qt::X11BypassWindowManagerHint); - else - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - - setWindowTitle("DockOverlayCross"); - setAttribute(Qt::WA_TranslucentBackground); - - d->m_gridLayout = new QGridLayout(); - d->m_gridLayout->setSpacing(0); - setLayout(d->m_gridLayout); - } - - DockOverlayCross::~DockOverlayCross() - { - delete d; - } - - void DockOverlayCross::setupOverlayCross(DockOverlay::eMode mode) - { - d->m_mode = mode; - - QHash areaWidgets; - areaWidgets.insert(TopDockWidgetArea, d->createDropIndicatorWidget(TopDockWidgetArea, mode)); - areaWidgets.insert(RightDockWidgetArea, d->createDropIndicatorWidget(RightDockWidgetArea, mode)); - areaWidgets.insert(BottomDockWidgetArea, - d->createDropIndicatorWidget(BottomDockWidgetArea, mode)); - areaWidgets.insert(LeftDockWidgetArea, d->createDropIndicatorWidget(LeftDockWidgetArea, mode)); - areaWidgets.insert(CenterDockWidgetArea, - d->createDropIndicatorWidget(CenterDockWidgetArea, mode)); - d->m_lastDevicePixelRatio = devicePixelRatioF(); - setAreaWidgets(areaWidgets); - d->m_updateRequired = false; - } - - void DockOverlayCross::updateOverlayIcons() - { - if (windowHandle()->devicePixelRatio() == d->m_lastDevicePixelRatio) // TODO - return; - - for (auto widget : std::as_const(d->m_dropIndicatorWidgets)) - d->updateDropIndicatorIcon(widget); - - d->m_lastDevicePixelRatio = devicePixelRatioF(); - } - - void DockOverlayCross::setIconColor(eIconColor colorIndex, const QColor &color) - { - d->m_iconColors[colorIndex] = color; - d->m_updateRequired = true; - } - - QColor DockOverlayCross::iconColor(eIconColor colorIndex) const - { - return d->m_iconColors[colorIndex]; - } - - void DockOverlayCross::setAreaWidgets(const QHash &widgets) - { - // Delete old widgets. - const auto values = d->m_dropIndicatorWidgets.values(); - for (auto widget : values) { - d->m_gridLayout->removeWidget(widget); - delete widget; - } - d->m_dropIndicatorWidgets.clear(); - - // Insert new widgets into grid. - d->m_dropIndicatorWidgets = widgets; - - const QHash areas = d->m_dropIndicatorWidgets; - QHash::const_iterator constIt; - for (constIt = areas.begin(); constIt != areas.end(); ++constIt) { - const DockWidgetArea area = constIt.key(); - QWidget *widget = constIt.value(); - const QPoint position = d->areaGridPosition(area); - d->m_gridLayout->addWidget(widget, - position.x(), - position.y(), - static_cast(areaAlignment(area))); - } - - if (DockOverlay::ModeDockAreaOverlay == d->m_mode) { - d->m_gridLayout->setContentsMargins(0, 0, 0, 0); - d->m_gridLayout->setRowStretch(0, 1); - d->m_gridLayout->setRowStretch(1, 0); - d->m_gridLayout->setRowStretch(2, 0); - d->m_gridLayout->setRowStretch(3, 0); - d->m_gridLayout->setRowStretch(4, 1); - - d->m_gridLayout->setColumnStretch(0, 1); - d->m_gridLayout->setColumnStretch(1, 0); - d->m_gridLayout->setColumnStretch(2, 0); - d->m_gridLayout->setColumnStretch(3, 0); - d->m_gridLayout->setColumnStretch(4, 1); - } else { - d->m_gridLayout->setContentsMargins(4, 4, 4, 4); - d->m_gridLayout->setRowStretch(0, 0); - d->m_gridLayout->setRowStretch(1, 1); - d->m_gridLayout->setRowStretch(2, 1); - d->m_gridLayout->setRowStretch(3, 1); - d->m_gridLayout->setRowStretch(4, 0); - - d->m_gridLayout->setColumnStretch(0, 0); - d->m_gridLayout->setColumnStretch(1, 1); - d->m_gridLayout->setColumnStretch(2, 1); - d->m_gridLayout->setColumnStretch(3, 1); - d->m_gridLayout->setColumnStretch(4, 0); - } - reset(); - } - - DockWidgetArea DockOverlayCross::cursorLocation() const - { - const QPoint position = mapFromGlobal(QCursor::pos()); - - const QHash areas = d->m_dropIndicatorWidgets; - QHash::const_iterator constIt; - for (constIt = areas.begin(); constIt != areas.end(); ++constIt) - { - if (d->m_dockOverlay->allowedAreas().testFlag(constIt.key()) && constIt.value() - && constIt.value()->isVisible() && constIt.value()->geometry().contains(position)) { - return constIt.key(); - } - } - - return InvalidDockWidgetArea; - } - - void DockOverlayCross::showEvent(QShowEvent *) - { - if (d->m_updateRequired) - setupOverlayCross(d->m_mode); - - this->updatePosition(); - } - - void DockOverlayCross::updatePosition() - { - resize(d->m_dockOverlay->size()); - QPoint topLeft = d->m_dockOverlay->pos(); - QPoint offest((this->width() - d->m_dockOverlay->width()) / 2, - (this->height() - d->m_dockOverlay->height()) / 2); - QPoint crossTopLeft = topLeft - offest; - move(crossTopLeft); - } - - void DockOverlayCross::reset() - { - const QList allAreas{TopDockWidgetArea, - RightDockWidgetArea, - BottomDockWidgetArea, - LeftDockWidgetArea, - CenterDockWidgetArea}; - const DockWidgetAreas allowedAreas = d->m_dockOverlay->allowedAreas(); - - // Update visibility of area widgets based on allowedAreas. - for (auto area : allAreas) { - const QPoint position = d->areaGridPosition(area); - QLayoutItem *item = d->m_gridLayout->itemAtPosition(position.x(), position.y()); - QWidget *widget = nullptr; - if (item && (widget = item->widget()) != nullptr) - widget->setVisible(allowedAreas.testFlag(area)); - } - } - - void DockOverlayCross::setIconColors(const QString &colors) - { - static const QMap - colorCompenentStringMap{{"Frame", DockOverlayCross::FrameColor}, - {"Background", DockOverlayCross::WindowBackgroundColor}, - {"Overlay", DockOverlayCross::OverlayColor}, - {"Arrow", DockOverlayCross::ArrowColor}, - {"Shadow", DockOverlayCross::ShadowColor}}; - - auto colorList = colors.split(' ', Qt::SkipEmptyParts); - for (const auto &colorListEntry : colorList) { - auto componentColor = colorListEntry.split('=', Qt::SkipEmptyParts); - int component = colorCompenentStringMap.value(componentColor[0], -1); - if (component < 0) - continue; - - d->m_iconColors[component] = QColor(componentColor[1]); - } - - d->m_updateRequired = true; - } - - QString DockOverlayCross::iconColors() const - { - return QString(); - } +QString DockOverlayCross::iconColors() const +{ + return QString(); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockoverlay.h b/src/libs/advanceddockingsystem/dockoverlay.h index 2dd6b7aef46..0b3c82d941b 100644 --- a/src/libs/advanceddockingsystem/dockoverlay.h +++ b/src/libs/advanceddockingsystem/dockoverlay.h @@ -52,11 +52,26 @@ public: */ DockWidgetAreas allowedAreas() const; + /** + * Enable / disable a certain area + */ + void setAllowedArea(DockWidgetArea area, bool enable); + /** * Returns the drop area under the current cursor location */ DockWidgetArea dropAreaUnderCursor() const; + /** + * If the drop area is the CenterDockWidgetArea or a sidebar area, then this function returns + * the index of the tab under cursor. Call this function after call to dropAreaUnderCursor() + * because this function updates the tab index. + * A value of -1 indicates a position before the first tab and a value of tabCount() indicates + * a position behind the last tab. + * A value of -2 indicates an valid value + */ + int tabIndexUnderCursor() const; + /** * This function returns the same like dropAreaUnderCursor() if this * overlay is not hidden and if drop preview is enabled and returns diff --git a/src/libs/advanceddockingsystem/docksplitter.cpp b/src/libs/advanceddockingsystem/docksplitter.cpp index a10161da596..a9207e0c08d 100644 --- a/src/libs/advanceddockingsystem/docksplitter.cpp +++ b/src/libs/advanceddockingsystem/docksplitter.cpp @@ -12,61 +12,70 @@ #include #include -namespace ADS +namespace ADS { +/** + * Private dock splitter data + */ +struct DockSplitterPrivate { - /** - * Private dock splitter data - */ - struct DockSplitterPrivate - { - DockSplitter *q; - int m_visibleContentCount = 0; + DockSplitter *q; + int m_visibleContentCount = 0; - DockSplitterPrivate(DockSplitter *parent) - : q(parent) - {} - }; - - DockSplitter::DockSplitter(QWidget *parent) - : QSplitter(parent) - , d(new DockSplitterPrivate(this)) - { - //setProperty("ads-splitter", true); // TODO - setProperty(Utils::StyleHelper::C_MINI_SPLITTER, true); - setChildrenCollapsible(false); - } - - DockSplitter::DockSplitter(Qt::Orientation orientation, QWidget *parent) - : QSplitter(orientation, parent) - , d(new DockSplitterPrivate(this)) + DockSplitterPrivate(DockSplitter *parent) + : q(parent) {} +}; - DockSplitter::~DockSplitter() - { - qCInfo(adsLog) << Q_FUNC_INFO; - delete d; - } +DockSplitter::DockSplitter(QWidget *parent) + : QSplitter(parent) + , d(new DockSplitterPrivate(this)) +{ + //setProperty("ads-splitter", true); // TODO + setProperty(Utils::StyleHelper::C_MINI_SPLITTER, true); + setChildrenCollapsible(false); +} - bool DockSplitter::hasVisibleContent() const - { - // TODO Cache or precalculate this to speed up - for (int i = 0; i < count(); ++i) { - if (!widget(i)->isHidden()) { - return true; - } +DockSplitter::DockSplitter(Qt::Orientation orientation, QWidget *parent) + : QSplitter(orientation, parent) + , d(new DockSplitterPrivate(this)) +{} + +DockSplitter::~DockSplitter() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; +} + +bool DockSplitter::hasVisibleContent() const +{ + // TODO Cache or precalculate this to speed up + for (int i = 0; i < count(); ++i) { + if (!widget(i)->isHidden()) { + return true; } - - return false; } - QWidget *DockSplitter::firstWidget() const - { - return (count() > 0) ? widget(0) : nullptr; + return false; +} + +QWidget *DockSplitter::firstWidget() const +{ + return (count() > 0) ? widget(0) : nullptr; +} + +QWidget *DockSplitter::lastWidget() const +{ + return (count() > 0) ? widget(count() - 1) : nullptr; +} + +bool DockSplitter::isResizingWithContainer() const +{ + for (auto area : findChildren()) { + if (area->isCentralWidgetArea()) + return true; } - QWidget *DockSplitter::lastWidget() const - { - return (count() > 0) ? widget(count() - 1) : nullptr; - } + return false; +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/docksplitter.h b/src/libs/advanceddockingsystem/docksplitter.h index 8dffc234ad4..c78bfd0b45f 100644 --- a/src/libs/advanceddockingsystem/docksplitter.h +++ b/src/libs/advanceddockingsystem/docksplitter.h @@ -45,6 +45,11 @@ public: * Returns last widget of nullptr is splitter is empty */ QWidget *lastWidget() const; + + /** + * Returns true if the splitter contains central widget of dock manager. + */ + bool isResizingWithContainer() const; }; // class DockSplitter } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidget.cpp b/src/libs/advanceddockingsystem/dockwidget.cpp index ea680149661..229995b9322 100644 --- a/src/libs/advanceddockingsystem/dockwidget.cpp +++ b/src/libs/advanceddockingsystem/dockwidget.cpp @@ -5,11 +5,13 @@ #include "ads_globals.h" #include "ads_globals_p.h" +#include "autohidedockcontainer.h" +#include "autohidesidebar.h" +#include "autohidetab.h" #include "dockareawidget.h" #include "dockcomponentsfactory.h" #include "dockcontainerwidget.h" #include "dockmanager.h" -#include "docksplitter.h" #include "dockwidgettab.h" #include "floatingdockcontainer.h" @@ -23,643 +25,884 @@ #include #include #include -#include #include +#include -namespace ADS +namespace ADS { +/** + * Private data class of DockWidget class (pimpl) + */ +class DockWidgetPrivate { +public: + struct WidgetFactory + { + DockWidget::FactoryFunc createWidget; + DockWidget::eInsertMode insertMode; + }; + + DockWidget *q = nullptr; + QBoxLayout *m_layout = nullptr; + QWidget *m_widget = nullptr; + DockWidgetTab *m_tabWidget = nullptr; + DockWidget::DockWidgetFeatures m_features = DockWidget::DefaultDockWidgetFeatures; + DockManager *m_dockManager = nullptr; + DockAreaWidget *m_dockArea = nullptr; + QAction *m_toggleViewAction = nullptr; + bool m_closed = false; + QScrollArea *m_scrollArea = nullptr; + QToolBar *m_toolBar = nullptr; + Qt::ToolButtonStyle m_toolBarStyleDocked = Qt::ToolButtonIconOnly; + Qt::ToolButtonStyle m_toolBarStyleFloating = Qt::ToolButtonTextUnderIcon; + QSize m_toolBarIconSizeDocked = QSize(16, 16); + QSize m_toolBarIconSizeFloating = QSize(24, 24); + bool m_isFloatingTopLevel = false; + QList m_titleBarActions; + DockWidget::eMinimumSizeHintMode m_minimumSizeHintMode + = DockWidget::MinimumSizeHintFromDockWidget; + WidgetFactory *m_factory = nullptr; + QPointer m_sideTabWidget; + /** - * Private data class of DockWidget class (pimpl) + * Private data constructor */ - class DockWidgetPrivate - { - public: - DockWidget *q = nullptr; - QBoxLayout *m_layout = nullptr; - QWidget *m_widget = nullptr; - DockWidgetTab *m_tabWidget = nullptr; - DockWidget::DockWidgetFeatures m_features = DockWidget::DefaultDockWidgetFeatures; - DockManager *m_dockManager = nullptr; - DockAreaWidget *m_dockArea = nullptr; - QAction *m_toggleViewAction = nullptr; - bool m_closed = false; - QScrollArea *m_scrollArea = nullptr; - QToolBar *m_toolBar = nullptr; - Qt::ToolButtonStyle m_toolBarStyleDocked = Qt::ToolButtonIconOnly; - Qt::ToolButtonStyle m_toolBarStyleFloating = Qt::ToolButtonTextUnderIcon; - QSize m_toolBarIconSizeDocked = QSize(16, 16); - QSize m_toolBarIconSizeFloating = QSize(24, 24); - bool m_isFloatingTopLevel = false; - QList m_titleBarActions; - DockWidget::eMinimumSizeHintMode m_minimumSizeHintMode = DockWidget::MinimumSizeHintFromDockWidget; + DockWidgetPrivate(DockWidget *parent); - /** - * Private data constructor - */ - DockWidgetPrivate(DockWidget *parent); + /** + * Show dock widget + */ + void showDockWidget(); - /** - * Show dock widget - */ - void showDockWidget(); + /** + * Hide dock widget. + */ + void hideDockWidget(); - /** - * Hide dock widget. - */ - void hideDockWidget(); + /** + * Hides a dock area if all dock widgets in the area are closed. This function updates the + * current selected tab and hides the parent dock area if it is empty. + */ + void updateParentDockArea(); - /** - * Hides a dock area if all dock widgets in the area are closed. - * This function updates the current selected tab and hides the parent - * dock area if it is empty - */ - void updateParentDockArea(); + /** + * Closes all auto hide dock widgets if there are no more opened dock areas. This prevents the + * auto hide dock widgets from being pinned to an empty dock area. + */ + void closeAutoHideDockWidgetsIfNeeded(); - /** - * Setup the top tool bar - */ - void setupToolBar(); + /** + * Setup the top tool bar + */ + void setupToolBar(); - /** - * Setup the main scroll area - */ - void setupScrollArea(); - }; // class DockWidgetPrivate + /** + * Setup the main scroll area + */ + void setupScrollArea(); - DockWidgetPrivate::DockWidgetPrivate(DockWidget *parent) - : q(parent) - {} + /** + * Creates the content widget with the registered widget factory and returns true on success. + */ + bool createWidgetFromFactory(); +}; // class DockWidgetPrivate - void DockWidgetPrivate::showDockWidget() - { - if (!m_dockArea) { - FloatingDockContainer *floatingWidget = new FloatingDockContainer(q); - floatingWidget->resize(q->size()); - m_tabWidget->show(); +DockWidgetPrivate::DockWidgetPrivate(DockWidget *parent) + : q(parent) +{} + +void DockWidgetPrivate::showDockWidget() +{ + if (!m_widget) { + if (!createWidgetFromFactory()) { + Q_ASSERT(!m_features.testFlag(DockWidget::DeleteContentOnClose) + && "DeleteContentOnClose flag was set, but the widget " + "factory is missing or it doesn't return a valid QWidget."); + return; + } + } + + if (!m_dockArea) { + FloatingDockContainer *floatingWidget = new FloatingDockContainer(q); + // We use the size hint of the content widget to provide a good initial size + floatingWidget->resize(m_widget ? m_widget->sizeHint() : q->size()); + m_tabWidget->show(); + floatingWidget->show(); + } else { + m_dockArea->setCurrentDockWidget(q); + m_dockArea->toggleView(true); + m_tabWidget->show(); + QSplitter *splitter = internal::findParent(m_dockArea); + while (splitter && !splitter->isVisible() && !m_dockArea->isAutoHide()) { + splitter->show(); + splitter = internal::findParent(splitter); + } + + DockContainerWidget *container = m_dockArea->dockContainer(); + if (container->isFloating()) { + FloatingDockContainer *floatingWidget = internal::findParent( + container); floatingWidget->show(); - } else { - m_dockArea->setCurrentDockWidget(q); - m_dockArea->toggleView(true); - m_tabWidget->show(); - QSplitter *splitter = internal::findParent(m_dockArea); - while (splitter && !splitter->isVisible()) { - splitter->show(); - splitter = internal::findParent(splitter); - } - - DockContainerWidget *container = m_dockArea->dockContainer(); - if (container->isFloating()) { - FloatingDockContainer *floatingWidget - = internal::findParent(container); - floatingWidget->show(); - } } + + // If this widget is pinned and there are no opened dock widgets, unpin the auto hide widget + // by moving it's contents to parent container While restoring state, opened dock widgets + // are not valid + if (container->openedDockWidgets().count() == 0 && m_dockArea->isAutoHide() + && !m_dockManager->isRestoringState()) + m_dockArea->autoHideDockContainer()->moveContentsToParent(); + } +} + +void DockWidgetPrivate::hideDockWidget() +{ + m_tabWidget->hide(); + updateParentDockArea(); + + closeAutoHideDockWidgetsIfNeeded(); + + if (m_features.testFlag(DockWidget::DeleteContentOnClose)) { + m_widget->deleteLater(); + m_widget = nullptr; + } +} + +void DockWidgetPrivate::updateParentDockArea() +{ + if (!m_dockArea) + return; + + // We don't need to change the current tab if the current DockWidget is not the one being closed + if (m_dockArea->currentDockWidget() != q) + return; + + auto nextDockWidget = m_dockArea->nextOpenDockWidget(q); + if (nextDockWidget) + m_dockArea->setCurrentDockWidget(nextDockWidget); + else + m_dockArea->hideAreaWithNoVisibleContent(); +} + +void DockWidgetPrivate::closeAutoHideDockWidgetsIfNeeded() +{ + auto dockContainer = q->dockContainer(); + if (!dockContainer) + return; + + if (q->dockManager()->isRestoringState()) + return; + + if (!dockContainer->openedDockWidgets().isEmpty()) + return; + + for (auto autoHideWidget : dockContainer->autoHideWidgets()) { + auto dockWidget = autoHideWidget->dockWidget(); + if (dockWidget == q) + continue; + + dockWidget->toggleView(false); + } +} + +void DockWidgetPrivate::setupToolBar() +{ + m_toolBar = new QToolBar(q); + m_toolBar->setObjectName("dockWidgetToolBar"); + m_layout->insertWidget(0, m_toolBar); + m_toolBar->setIconSize(QSize(16, 16)); + m_toolBar->toggleViewAction()->setEnabled(false); + m_toolBar->toggleViewAction()->setVisible(false); + QObject::connect(q, &DockWidget::topLevelChanged, q, &DockWidget::setToolbarFloatingStyle); +} + +void DockWidgetPrivate::setupScrollArea() +{ + m_scrollArea = new QScrollArea(q); + m_scrollArea->setObjectName("dockWidgetScrollArea"); + m_scrollArea->setWidgetResizable(true); + m_layout->addWidget(m_scrollArea); +} + +bool DockWidgetPrivate::createWidgetFromFactory() +{ + if (!m_features.testFlag(DockWidget::DeleteContentOnClose)) + return false; + + if (!m_factory) + return false; + + QWidget *w = m_factory->createWidget(q); + if (!w) + return false; + + q->setWidget(w, m_factory->insertMode); + return true; +} + +DockWidget::DockWidget(const QString &uniqueId, QWidget *parent) + : QFrame(parent) + , d(new DockWidgetPrivate(this)) +{ + d->m_layout = new QBoxLayout(QBoxLayout::TopToBottom); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + setWindowTitle(uniqueId); // temporarily use unique id as title + setObjectName(uniqueId); + + d->m_tabWidget = componentsFactory()->createDockWidgetTab(this); + d->m_toggleViewAction = new QAction(uniqueId, this); + d->m_toggleViewAction->setCheckable(true); + connect(d->m_toggleViewAction, &QAction::triggered, this, [this](bool open) { + // If the toggle view action mode is ActionModeShow (== m_toggleViewAction isn't + // checkable, see setToggleViewActionMode()), then open is always true + toggleView(open || !d->m_toggleViewAction->isCheckable()); + }); + setToolbarFloatingStyle(false); + + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + setFocusPolicy(Qt::ClickFocus); +} + +DockWidget::~DockWidget() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; +} + +void DockWidget::setToggleViewActionChecked(bool checked) +{ + d->m_toggleViewAction->setChecked(checked); +} + +void DockWidget::setWidget(QWidget *widget, eInsertMode insertMode) +{ + if (d->m_widget) + takeWidget(); + + auto scrollAreaWidget = qobject_cast(widget); + if (scrollAreaWidget || ForceNoScrollArea == insertMode) { + d->m_layout->addWidget(widget); + if (scrollAreaWidget && scrollAreaWidget->viewport()) + scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true); + } else { + d->setupScrollArea(); + d->m_scrollArea->setWidget(widget); } - void DockWidgetPrivate::hideDockWidget() - { - m_tabWidget->hide(); - updateParentDockArea(); + d->m_widget = widget; + d->m_widget->setProperty("dockWidgetContent", true); +} + +void DockWidget::setWidgetFactory(FactoryFunc createWidget, eInsertMode insertMode) +{ + if (d->m_factory) + delete d->m_factory; + + d->m_factory = new DockWidgetPrivate::WidgetFactory{createWidget, insertMode}; +} + +QWidget *DockWidget::takeWidget() +{ + QWidget *w = nullptr; + if (d->m_scrollArea) { + d->m_layout->removeWidget(d->m_scrollArea); + w = d->m_scrollArea->takeWidget(); + delete d->m_scrollArea; + d->m_scrollArea = nullptr; + d->m_widget = nullptr; + } else if (d->m_widget) { + d->m_layout->removeWidget(d->m_widget); + w = d->m_widget; + d->m_widget = nullptr; } - void DockWidgetPrivate::updateParentDockArea() - { - if (!m_dockArea) - return; + if (w) + w->setParent(nullptr); - // we don't need to change the current tab if the current DockWidget is not the one being closed - if (m_dockArea->currentDockWidget() != q) - return; + return w; +} - auto nextDockWidget = m_dockArea->nextOpenDockWidget(q); - if (nextDockWidget) - m_dockArea->setCurrentDockWidget(nextDockWidget); - else - m_dockArea->hideAreaWithNoVisibleContent(); - } +QWidget *DockWidget::widget() const +{ + return d->m_widget; +} - void DockWidgetPrivate::setupToolBar() - { - m_toolBar = new QToolBar(q); - m_toolBar->setObjectName("dockWidgetToolBar"); - m_layout->insertWidget(0, m_toolBar); - m_toolBar->setIconSize(QSize(16, 16)); - m_toolBar->toggleViewAction()->setEnabled(false); - m_toolBar->toggleViewAction()->setVisible(false); - QObject::connect(q, &DockWidget::topLevelChanged, q, &DockWidget::setToolbarFloatingStyle); - } +DockWidgetTab *DockWidget::tabWidget() const +{ + return d->m_tabWidget; +} - void DockWidgetPrivate::setupScrollArea() - { - m_scrollArea = new QScrollArea(q); - m_scrollArea->setObjectName("dockWidgetScrollArea"); - m_scrollArea->setWidgetResizable(true); - m_layout->addWidget(m_scrollArea); - } +AutoHideDockContainer *DockWidget::autoHideDockContainer() const +{ + if (!d->m_dockArea) + return nullptr; - DockWidget::DockWidget(const QString &uniqueId, QWidget *parent) - : QFrame(parent) - , d(new DockWidgetPrivate(this)) - { - d->m_layout = new QBoxLayout(QBoxLayout::TopToBottom); - d->m_layout->setContentsMargins(0, 0, 0, 0); - d->m_layout->setSpacing(0); - setLayout(d->m_layout); - setWindowTitle(uniqueId); // temporarily use unique id as title - setObjectName(uniqueId); + return d->m_dockArea->autoHideDockContainer(); +} - d->m_tabWidget = componentsFactory()->createDockWidgetTab(this); - d->m_toggleViewAction = new QAction(uniqueId, this); +void DockWidget::setFeatures(DockWidgetFeatures features) +{ + if (d->m_features == features) + return; + + d->m_features = features; + emit featuresChanged(d->m_features); + d->m_tabWidget->onDockWidgetFeaturesChanged(); + + if (DockAreaWidget *dockArea = dockAreaWidget()) + dockArea->onDockWidgetFeaturesChanged(); +} + +void DockWidget::setFeature(DockWidgetFeature flag, bool on) +{ + auto currentFeatures = features(); + internal::setFlag(currentFeatures, flag, on); + setFeatures(currentFeatures); +} + +DockWidget::DockWidgetFeatures DockWidget::features() const +{ + return d->m_features; +} + +DockManager *DockWidget::dockManager() const +{ + return d->m_dockManager; +} + +void DockWidget::setDockManager(DockManager *dockManager) +{ + d->m_dockManager = dockManager; +} + +DockContainerWidget *DockWidget::dockContainer() const +{ + if (d->m_dockArea) + return d->m_dockArea->dockContainer(); + else + return nullptr; +} + +FloatingDockContainer *DockWidget::floatingDockContainer() const +{ + auto container = dockContainer(); + return container ? container->floatingWidget() : nullptr; +} + +DockAreaWidget *DockWidget::dockAreaWidget() const +{ + return d->m_dockArea; +} + +AutoHideTab *DockWidget::sideTabWidget() const +{ + return d->m_sideTabWidget; +} + +void DockWidget::setSideTabWidget(AutoHideTab *sideTab) const +{ + d->m_sideTabWidget = sideTab; +} + +bool DockWidget::isAutoHide() const +{ + return !d->m_sideTabWidget.isNull(); +} + +SideBarLocation DockWidget::autoHideLocation() const +{ + return isAutoHide() ? autoHideDockContainer()->sideBarLocation() : SideBarNone; +} + +bool DockWidget::isFloating() const +{ + if (!isInFloatingContainer()) + return false; + + return dockContainer()->topLevelDockWidget() == this; +} + +bool DockWidget::isInFloatingContainer() const +{ + auto container = dockContainer(); + if (!container) + return false; + + if (!container->isFloating()) + return false; + + return true; +} + +bool DockWidget::isClosed() const +{ + return d->m_closed; +} + +QAction *DockWidget::toggleViewAction() const +{ + return d->m_toggleViewAction; +} + +void DockWidget::setToggleViewActionMode(eToggleViewActionMode mode) +{ + if (ActionModeToggle == mode) { d->m_toggleViewAction->setCheckable(true); - connect(d->m_toggleViewAction, &QAction::triggered, this, [this](bool open) { - // If the toggle view action mode is ActionModeShow (== m_toggleViewAction isn't - // checkable, see setToggleViewActionMode()), then open is always true - toggleView(open || !d->m_toggleViewAction->isCheckable()); - }); - setToolbarFloatingStyle(false); - - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - setFocusPolicy(Qt::ClickFocus); + d->m_toggleViewAction->setIcon(QIcon()); + } else { + d->m_toggleViewAction->setCheckable(false); + d->m_toggleViewAction->setIcon(d->m_tabWidget->icon()); } +} - DockWidget::~DockWidget() - { - qCInfo(adsLog) << Q_FUNC_INFO; - delete d; - } +void DockWidget::setMinimumSizeHintMode(eMinimumSizeHintMode mode) +{ + d->m_minimumSizeHintMode = mode; +} - void DockWidget::setToggleViewActionChecked(bool checked) - { - d->m_toggleViewAction->setChecked(checked); - } +DockWidget::eMinimumSizeHintMode DockWidget::minimumSizeHintMode() const +{ + return d->m_minimumSizeHintMode; +} - void DockWidget::setWidget(QWidget *widget, eInsertMode insertMode) - { - if (d->m_widget) - takeWidget(); +bool DockWidget::isCentralWidget() const +{ + return dockManager()->centralWidget() == this; +} - auto scrollAreaWidget = qobject_cast(widget); - if (scrollAreaWidget || ForceNoScrollArea == insertMode) { - d->m_layout->addWidget(widget); - if (scrollAreaWidget && scrollAreaWidget->viewport()) - scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true); - } else { - d->setupScrollArea(); - d->m_scrollArea->setWidget(widget); - } +void DockWidget::toggleView(bool open) +{ + // If the dock widget state is different, then we really need to toggle the state. If we are + // in the right state, then we simply make this dock widget the current dock widget. + auto autoHideContainer = autoHideDockContainer(); + if (d->m_closed != !open) + toggleViewInternal(open); + else if (open && d->m_dockArea && !autoHideContainer) + raise(); - d->m_widget = widget; - d->m_widget->setProperty("dockWidgetContent", true); - } + if (open && autoHideContainer) + autoHideContainer->collapseView(false); +} - QWidget *DockWidget::takeWidget() - { - QWidget *w = nullptr; - if (d->m_scrollArea) { - d->m_layout->removeWidget(d->m_scrollArea); - w = d->m_scrollArea->takeWidget(); - delete d->m_scrollArea; - d->m_scrollArea = nullptr; - d->m_widget = nullptr; - } else if (d->m_widget) { - d->m_layout->removeWidget(d->m_widget); - w = d->m_widget; - d->m_widget = nullptr; - } +void DockWidget::toggleViewInternal(bool open) +{ + const DockContainerWidget *const beforeDockContainerWidget = dockContainer(); + DockWidget *topLevelDockWidgetBefore = beforeDockContainerWidget + ? beforeDockContainerWidget->topLevelDockWidget() + : nullptr; - if (w) - w->setParent(nullptr); + d->m_closed = !open; - return w; - } + if (open) + d->showDockWidget(); + else + d->hideDockWidget(); - QWidget *DockWidget::widget() const { return d->m_widget; } + //d->m_toggleViewAction->blockSignals(true); + d->m_toggleViewAction->setChecked(open); + //d->m_toggleViewAction->blockSignals(false); + if (d->m_dockArea) + d->m_dockArea->toggleDockWidgetView(this, open); - DockWidgetTab *DockWidget::tabWidget() const { return d->m_tabWidget; } + if (d->m_dockArea->isAutoHide()) + d->m_dockArea->autoHideDockContainer()->toggleView(open); - void DockWidget::setFeatures(DockWidgetFeatures features) - { - if (d->m_features == features) - return; + if (open && topLevelDockWidgetBefore) + DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetBefore, false); - d->m_features = features; - emit featuresChanged(d->m_features); - d->m_tabWidget->onDockWidgetFeaturesChanged(); - } - - void DockWidget::setFeature(DockWidgetFeature flag, bool on) - { - auto currentFeatures = features(); - internal::setFlag(currentFeatures, flag, on); - setFeatures(currentFeatures); - } - - DockWidget::DockWidgetFeatures DockWidget::features() const { return d->m_features; } - - DockManager *DockWidget::dockManager() const { return d->m_dockManager; } - - void DockWidget::setDockManager(DockManager *dockManager) { d->m_dockManager = dockManager; } - - DockContainerWidget *DockWidget::dockContainer() const - { - if (d->m_dockArea) - return d->m_dockArea->dockContainer(); - else - return nullptr; - } - - DockAreaWidget *DockWidget::dockAreaWidget() const { return d->m_dockArea; } - - bool DockWidget::isFloating() const - { - if (!isInFloatingContainer()) - return false; - - return dockContainer()->topLevelDockWidget() == this; - } - - bool DockWidget::isInFloatingContainer() const - { - auto container = dockContainer(); - if (!container) - return false; - - if (!container->isFloating()) - return false; - - return true; - } - - bool DockWidget::isClosed() const { return d->m_closed; } - - QAction *DockWidget::toggleViewAction() const { return d->m_toggleViewAction; } - - void DockWidget::setToggleViewActionMode(eToggleViewActionMode mode) - { - if (ActionModeToggle == mode) { - d->m_toggleViewAction->setCheckable(true); - d->m_toggleViewAction->setIcon(QIcon()); - } else { - d->m_toggleViewAction->setCheckable(false); - d->m_toggleViewAction->setIcon(d->m_tabWidget->icon()); - } - } - - void DockWidget::setMinimumSizeHintMode(eMinimumSizeHintMode mode) - { - d->m_minimumSizeHintMode = mode; - } - - void DockWidget::toggleView(bool open) - { - // If the dock widget state is different, then we really need to toggle - // the state. If we are in the right state, then we simply make this - // dock widget the current dock widget - if (d->m_closed != !open) - toggleViewInternal(open); - else if (open && d->m_dockArea) - d->m_dockArea->setCurrentDockWidget(this); - } - - void DockWidget::toggleViewInternal(bool open) - { - const DockContainerWidget *const beforeDockContainerWidget = dockContainer(); - DockWidget *topLevelDockWidgetBefore = beforeDockContainerWidget - ? beforeDockContainerWidget->topLevelDockWidget() + // Here we need to call the dockContainer() function again, because if + // this dock widget was unassigned before the call to showDockWidget() then + // it has a dock container now + const DockContainerWidget *const dockContainerWidget = dockContainer(); + DockWidget *topLevelDockWidgetAfter = dockContainerWidget + ? dockContainerWidget->topLevelDockWidget() + : nullptr; + DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetAfter, true); + FloatingDockContainer *floatingContainer = dockContainerWidget + ? dockContainerWidget->floatingWidget() : nullptr; + if (floatingContainer) + floatingContainer->updateWindowTitle(); - if (open) - d->showDockWidget(); - else - d->hideDockWidget(); + if (!open) + emit closed(); - d->m_closed = !open; - //d->m_toggleViewAction->blockSignals(true); - d->m_toggleViewAction->setChecked(open); - //d->m_toggleViewAction->blockSignals(false); - if (d->m_dockArea) - d->m_dockArea->toggleDockWidgetView(this, open); + emit viewToggled(open); +} - if (open && topLevelDockWidgetBefore) - DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetBefore, false); +void DockWidget::setDockArea(DockAreaWidget *dockArea) +{ + d->m_dockArea = dockArea; + d->m_toggleViewAction->setChecked(dockArea != nullptr && !isClosed()); + setParent(dockArea); +} - // Here we need to call the dockContainer() function again, because if - // this dock widget was unassigned before the call to showDockWidget() then - // it has a dock container now - const DockContainerWidget *const dockContainerWidget = dockContainer(); - DockWidget *topLevelDockWidgetAfter = dockContainerWidget - ? dockContainerWidget->topLevelDockWidget() - : nullptr; - DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetAfter, true); - FloatingDockContainer *floatingContainer = dockContainerWidget - ? dockContainerWidget->floatingWidget() - : nullptr; - if (floatingContainer) - floatingContainer->updateWindowTitle(); +void DockWidget::saveState(QXmlStreamWriter &stream) const +{ + stream.writeStartElement("widget"); + stream.writeAttribute("name", objectName()); + stream.writeAttribute("closed", QVariant::fromValue(d->m_closed).toString()); + stream.writeEndElement(); +} - if (!open) - emit closed(); +void DockWidget::flagAsUnassigned() +{ + d->m_closed = true; + setParent(d->m_dockManager); + setVisible(false); + setDockArea(nullptr); + tabWidget()->setParent(this); +} - emit viewToggled(open); - } +bool DockWidget::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::Hide: + emit visibilityChanged(false); + break; - void DockWidget::setDockArea(DockAreaWidget *dockArea) - { - d->m_dockArea = dockArea; - d->m_toggleViewAction->setChecked(dockArea != nullptr && !this->isClosed()); - } + case QEvent::Show: + emit visibilityChanged(geometry().right() >= 0 && geometry().bottom() >= 0); + break; - void DockWidget::saveState(QXmlStreamWriter &stream) const - { - stream.writeStartElement("widget"); - stream.writeAttribute("name", objectName()); - stream.writeAttribute("closed", QVariant::fromValue(d->m_closed).toString()); - stream.writeEndElement(); - } + case QEvent::WindowTitleChange: { + const auto title = windowTitle(); + if (d->m_tabWidget) { + d->m_tabWidget->setText(title); + } + if (d->m_sideTabWidget) + d->m_sideTabWidget->setText(title); - void DockWidget::flagAsUnassigned() - { - d->m_closed = true; - setParent(d->m_dockManager); - setVisible(false); - setDockArea(nullptr); - tabWidget()->setParent(this); - } - - bool DockWidget::event(QEvent *event) - { - switch (event->type()) { - case QEvent::Hide: - emit visibilityChanged(false); - break; - - case QEvent::Show: - emit visibilityChanged(geometry().right() >= 0 && geometry().bottom() >= 0); - break; - - case QEvent::WindowTitleChange : - { - const auto title = windowTitle(); - if (d->m_tabWidget) { - d->m_tabWidget->setText(title); - } - if (d->m_toggleViewAction) { - d->m_toggleViewAction->setText(title); - } - if (d->m_dockArea) { - d->m_dockArea->markTitleBarMenuOutdated(); // update tabs menu - } - emit titleChanged(title); - } - break; - - default: - break; + if (d->m_toggleViewAction) { + d->m_toggleViewAction->setText(title); + } + if (d->m_dockArea) { + d->m_dockArea->markTitleBarMenuOutdated(); // update tabs menu } - return Super::event(event); + auto floatingWidget = floatingDockContainer(); + if (floatingWidget) + floatingWidget->updateWindowTitle(); + + emit titleChanged(title); + } break; + + default: + break; } + return Super::event(event); +} + #ifndef QT_NO_TOOLTIP - void DockWidget::setTabToolTip(const QString &text) - { - if (d->m_tabWidget) - d->m_tabWidget->setToolTip(text); +void DockWidget::setTabToolTip(const QString &text) +{ + if (d->m_tabWidget) + d->m_tabWidget->setToolTip(text); - if (d->m_toggleViewAction) - d->m_toggleViewAction->setToolTip(text); + if (d->m_toggleViewAction) + d->m_toggleViewAction->setToolTip(text); - if (d->m_dockArea) - d->m_dockArea->markTitleBarMenuOutdated(); // update tabs menu - } + if (d->m_dockArea) + d->m_dockArea->markTitleBarMenuOutdated(); // update tabs menu +} #endif - void DockWidget::setIcon(const QIcon &icon) - { - d->m_tabWidget->setIcon(icon); - if (!d->m_toggleViewAction->isCheckable()) - d->m_toggleViewAction->setIcon(icon); +void DockWidget::setIcon(const QIcon &icon) +{ + d->m_tabWidget->setIcon(icon); + + if (d->m_sideTabWidget) + d->m_sideTabWidget->setIcon(icon); + + if (!d->m_toggleViewAction->isCheckable()) + d->m_toggleViewAction->setIcon(icon); +} + +QIcon DockWidget::icon() const +{ + return d->m_tabWidget->icon(); +} + +QToolBar *DockWidget::toolBar() const +{ + return d->m_toolBar; +} + +QToolBar *DockWidget::createDefaultToolBar() +{ + if (!d->m_toolBar) + d->setupToolBar(); + + return d->m_toolBar; +} + +void DockWidget::setToolBar(QToolBar *toolBar) +{ + if (d->m_toolBar) + delete d->m_toolBar; + + d->m_toolBar = toolBar; + d->m_layout->insertWidget(0, d->m_toolBar); + connect(this, &DockWidget::topLevelChanged, this, &DockWidget::setToolbarFloatingStyle); + setToolbarFloatingStyle(isFloating()); +} + +void DockWidget::setToolBarStyle(Qt::ToolButtonStyle style, eState state) +{ + if (StateFloating == state) + d->m_toolBarStyleFloating = style; + else + d->m_toolBarStyleDocked = style; + + setToolbarFloatingStyle(isFloating()); +} + +Qt::ToolButtonStyle DockWidget::toolBarStyle(eState state) const +{ + if (StateFloating == state) + return d->m_toolBarStyleFloating; + else + return d->m_toolBarStyleDocked; +} + +void DockWidget::setToolBarIconSize(const QSize &iconSize, eState state) +{ + if (StateFloating == state) + d->m_toolBarIconSizeFloating = iconSize; + else + d->m_toolBarIconSizeDocked = iconSize; + + setToolbarFloatingStyle(isFloating()); +} + +QSize DockWidget::toolBarIconSize(eState state) const +{ + if (StateFloating == state) + return d->m_toolBarIconSizeFloating; + else + return d->m_toolBarIconSizeDocked; +} + +void DockWidget::setToolbarFloatingStyle(bool floating) +{ + if (!d->m_toolBar) + return; + + auto iconSize = floating ? d->m_toolBarIconSizeFloating : d->m_toolBarIconSizeDocked; + if (iconSize != d->m_toolBar->iconSize()) + d->m_toolBar->setIconSize(iconSize); + + auto buttonStyle = floating ? d->m_toolBarStyleFloating : d->m_toolBarStyleDocked; + if (buttonStyle != d->m_toolBar->toolButtonStyle()) + d->m_toolBar->setToolButtonStyle(buttonStyle); +} + +void DockWidget::emitTopLevelEventForWidget(DockWidget *topLevelDockWidget, bool floating) +{ + if (topLevelDockWidget) { + topLevelDockWidget->dockAreaWidget()->updateTitleBarVisibility(); + topLevelDockWidget->emitTopLevelChanged(floating); + } +} + +void DockWidget::emitTopLevelChanged(bool floating) +{ + if (floating != d->m_isFloatingTopLevel) { + d->m_isFloatingTopLevel = floating; + emit topLevelChanged(d->m_isFloatingTopLevel); + } +} + +void DockWidget::setClosedState(bool closed) +{ + d->m_closed = closed; +} + +QSize DockWidget::minimumSizeHint() const +{ + if (!d->m_widget) + return QSize(60, 40); + + // TODO + DockContainerWidget *container = this->dockContainer(); + if (!container || container->isFloating()) { + const QSize sh = d->m_widget->minimumSizeHint(); + const QSize s = d->m_widget->minimumSize(); + return {std::max(s.width(), sh.width()), std::max(s.height(), sh.height())}; } - QIcon DockWidget::icon() const { return d->m_tabWidget->icon(); } - - QToolBar *DockWidget::toolBar() const { return d->m_toolBar; } - - QToolBar *DockWidget::createDefaultToolBar() - { - if (!d->m_toolBar) - d->setupToolBar(); - - return d->m_toolBar; + switch (d->m_minimumSizeHintMode) { + case MinimumSizeHintFromDockWidget: + return QSize(60, 40); + case MinimumSizeHintFromContent: + return d->m_widget->minimumSizeHint(); + case MinimumSizeHintFromDockWidgetMinimumSize: + return minimumSize(); + case MinimumSizeHintFromContentMinimumSize: + return d->m_widget->minimumSize(); } - void DockWidget::setToolBar(QToolBar *toolBar) - { - if (d->m_toolBar) - delete d->m_toolBar; + return d->m_widget->minimumSizeHint(); +} - d->m_toolBar = toolBar; - d->m_layout->insertWidget(0, d->m_toolBar); - connect(this, &DockWidget::topLevelChanged, this, &DockWidget::setToolbarFloatingStyle); - setToolbarFloatingStyle(isFloating()); - } - - void DockWidget::setToolBarStyle(Qt::ToolButtonStyle style, eState state) - { - if (StateFloating == state) - d->m_toolBarStyleFloating = style; - else - d->m_toolBarStyleDocked = style; - - setToolbarFloatingStyle(isFloating()); - } - - Qt::ToolButtonStyle DockWidget::toolBarStyle(eState state) const - { - if (StateFloating == state) - return d->m_toolBarStyleFloating; - else - return d->m_toolBarStyleDocked; - } - - void DockWidget::setToolBarIconSize(const QSize &iconSize, eState state) - { - if (StateFloating == state) - d->m_toolBarIconSizeFloating = iconSize; - else - d->m_toolBarIconSizeDocked = iconSize; - - setToolbarFloatingStyle(isFloating()); - } - - QSize DockWidget::toolBarIconSize(eState state) const - { - if (StateFloating == state) - return d->m_toolBarIconSizeFloating; - else - return d->m_toolBarIconSizeDocked; - } - - void DockWidget::setToolbarFloatingStyle(bool floating) - { - if (!d->m_toolBar) - return; - - auto iconSize = floating ? d->m_toolBarIconSizeFloating : d->m_toolBarIconSizeDocked; - if (iconSize != d->m_toolBar->iconSize()) - d->m_toolBar->setIconSize(iconSize); - - auto buttonStyle = floating ? d->m_toolBarStyleFloating : d->m_toolBarStyleDocked; - if (buttonStyle != d->m_toolBar->toolButtonStyle()) - d->m_toolBar->setToolButtonStyle(buttonStyle); - } - - void DockWidget::emitTopLevelEventForWidget(DockWidget *topLevelDockWidget, bool floating) - { - if (topLevelDockWidget) { - topLevelDockWidget->dockAreaWidget()->updateTitleBarVisibility(); - topLevelDockWidget->emitTopLevelChanged(floating); - } - } - - void DockWidget::emitTopLevelChanged(bool floating) - { - if (floating != d->m_isFloatingTopLevel) { - d->m_isFloatingTopLevel = floating; - emit topLevelChanged(d->m_isFloatingTopLevel); - } - } - - void DockWidget::setClosedState(bool closed) { d->m_closed = closed; } - - QSize DockWidget::minimumSizeHint() const - { - if (!d->m_widget) - return QSize(60, 40); - - DockContainerWidget *container = this->dockContainer(); - if (!container || container->isFloating()) { - const QSize sh = d->m_widget->minimumSizeHint(); - const QSize s = d->m_widget->minimumSize(); - return {std::max(s.width(), sh.width()), std::max(s.height(), sh.height())}; - } - - if (d->m_minimumSizeHintMode == DockWidget::MinimumSizeHintFromDockWidget) - return QSize(60, 40); - else - return d->m_widget->minimumSizeHint(); - } - - void DockWidget::setFloating() - { - if (isClosed()) - return; +void DockWidget::setFloating() +{ + if (isClosed()) + return; + if (isAutoHide()) + dockAreaWidget()->setFloating(); + else d->m_tabWidget->detachDockWidget(); - } +} - void DockWidget::deleteDockWidget() - { - dockManager()->removeDockWidget(this); - deleteLater(); - d->m_closed = true; - } +void DockWidget::deleteDockWidget() +{ + auto manager = dockManager(); + if (manager) + manager->removeDockWidget(this); - void DockWidget::closeDockWidget() - { - closeDockWidgetInternal(true); - } + deleteLater(); + d->m_closed = true; +} - bool DockWidget::closeDockWidgetInternal(bool forceClose) - { - if (!forceClose) - emit closeRequested(); +void DockWidget::closeDockWidget() +{ + closeDockWidgetInternal(true); +} - if (!forceClose && features().testFlag(DockWidget::CustomCloseHandling)) - return false; +void DockWidget::requestCloseDockWidget() +{ + if (features().testFlag(DockWidget::DockWidgetDeleteOnClose) + || features().testFlag(DockWidget::CustomCloseHandling)) + closeDockWidgetInternal(false); + else + toggleView(false); +} - if (features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { - // If the dock widget is floating, then we check if we also need to - // delete the floating widget - if (isFloating()) { - FloatingDockContainer* floatingWidget = internal::findParent< - FloatingDockContainer *>(this); - if (floatingWidget->dockWidgets().count() == 1) - floatingWidget->deleteLater(); - else - floatingWidget->hide(); - } - deleteDockWidget(); - emit closed(); - } else { - toggleView(false); +bool DockWidget::closeDockWidgetInternal(bool forceClose) +{ + if (!forceClose) + emit closeRequested(); + + if (!forceClose && features().testFlag(DockWidget::CustomCloseHandling)) + return false; + + if (features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { + // If the dock widget is floating, then we check if we also need to + // delete the floating widget + if (isFloating()) { + FloatingDockContainer *floatingWidget = internal::findParent( + this); + if (floatingWidget->dockWidgets().count() == 1) + floatingWidget->deleteLater(); + else + floatingWidget->hide(); } - return true; + if (d->m_dockArea && d->m_dockArea->isAutoHide()) + d->m_dockArea->autoHideDockContainer()->cleanupAndDelete(); + + deleteDockWidget(); + emit closed(); + } else { + toggleView(false); } - void DockWidget::setTitleBarActions(QList actions) - { - d->m_titleBarActions = actions; - } + return true; +} - QList DockWidget::titleBarActions() const - { - return d->m_titleBarActions; - } +void DockWidget::setTitleBarActions(QList actions) +{ + d->m_titleBarActions = actions; +} - void DockWidget::showFullScreen() - { - if (isFloating()) - dockContainer()->floatingWidget()->showFullScreen(); - else - Super::showFullScreen(); - } +QList DockWidget::titleBarActions() const +{ + return d->m_titleBarActions; +} - void DockWidget::showNormal() - { - if (isFloating()) - dockContainer()->floatingWidget()->showNormal(); - else - Super::showNormal(); - } +void DockWidget::showFullScreen() +{ + if (isFloating()) + dockContainer()->floatingWidget()->showFullScreen(); + else + Super::showFullScreen(); +} - bool DockWidget::isFullScreen() const - { - if (isFloating()) - return dockContainer()->floatingWidget()->isFullScreen(); - else - return Super::isFullScreen(); - } +void DockWidget::showNormal() +{ + if (isFloating()) + dockContainer()->floatingWidget()->showNormal(); + else + Super::showNormal(); +} - void DockWidget::setAsCurrentTab() - { - if (d->m_dockArea && !isClosed()) - d->m_dockArea->setCurrentDockWidget(this); - } +bool DockWidget::isFullScreen() const +{ + if (isFloating()) + return dockContainer()->floatingWidget()->isFullScreen(); + else + return Super::isFullScreen(); +} - bool DockWidget::isTabbed() const - { - return d->m_dockArea && (d->m_dockArea->openDockWidgetsCount() > 1); - } +void DockWidget::setAsCurrentTab() +{ + if (d->m_dockArea && !isClosed()) + d->m_dockArea->setCurrentDockWidget(this); +} - bool DockWidget::isCurrentTab() const - { - return d->m_dockArea && (d->m_dockArea->currentDockWidget() == this); - } +bool DockWidget::isTabbed() const +{ + return d->m_dockArea && (d->m_dockArea->openDockWidgetsCount() > 1); +} - void DockWidget::raise() - { - if (isClosed()) - return; +bool DockWidget::isCurrentTab() const +{ + return d->m_dockArea && (d->m_dockArea->currentDockWidget() == this); +} - setAsCurrentTab(); - if (isInFloatingContainer()) - { - auto floatingWindow = window(); - floatingWindow->raise(); - floatingWindow->activateWindow(); - } +void DockWidget::raise() +{ + if (isClosed()) + return; + + setAsCurrentTab(); + if (isInFloatingContainer()) { + auto floatingWindow = window(); + floatingWindow->raise(); + floatingWindow->activateWindow(); } +} + +void DockWidget::setAutoHide(bool enable, SideBarLocation location, int tabIndex) +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) + return; + + // Do nothing if nothing changes + if (enable == isAutoHide() && location == autoHideLocation()) + return; + + auto dockArea = dockAreaWidget(); + + if (!enable) { + dockArea->setAutoHide(false); + } else if (isAutoHide()) { + autoHideDockContainer()->moveToNewSideBarLocation(location); + } else { + auto area = (SideBarNone == location) ? dockArea->calculateSideTabBarArea() : location; + dockContainer()->createAndSetupAutoHideContainer(area, this, tabIndex); + } +} + +void DockWidget::toggleAutoHide(SideBarLocation location) +{ + if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) + return; + + setAutoHide(!isAutoHide(), location); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidget.h b/src/libs/advanceddockingsystem/dockwidget.h index 7820b88d17d..4f9d13be290 100644 --- a/src/libs/advanceddockingsystem/dockwidget.h +++ b/src/libs/advanceddockingsystem/dockwidget.h @@ -21,6 +21,9 @@ class DockContainerWidget; class DockAreaWidget; class DockContainerWidgetPrivate; class FloatingDockContainer; +class AutoHideTab; +class AutoHideDockContainer; +class AutoHideSideBar; /** * The QDockWidget class provides a widget that can be docked inside a @@ -49,6 +52,8 @@ protected: friend class DockWidgetTab; friend class DockWidgetTabPrivate; friend class DockAreaTitleBarPrivate; + friend class AutoHideDockContainer; + friend AutoHideSideBar; /** * Assigns the dock manager that manages this dock widget @@ -120,14 +125,28 @@ public: using Super = QFrame; enum DockWidgetFeature { - DockWidgetClosable = 0x01,///< dock widget has a close button - DockWidgetMovable = 0x02,///< dock widget is movable and can be moved to a new position in the current dock container - DockWidgetFloatable = 0x04, - DockWidgetDeleteOnClose = 0x08, ///< deletes the dock widget when it is closed - CustomCloseHandling = 0x10, - DefaultDockWidgetFeatures = DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable, - AllDockWidgetFeatures = DefaultDockWidgetFeatures | DockWidgetDeleteOnClose | CustomCloseHandling, - NoDockWidgetFeatures = 0x00 + DockWidgetClosable = 0x001, ///< dock widget has a close button + DockWidgetMovable + = 0x002, ///< dock widget is movable and can be moved to a new position in the current dock container + DockWidgetFloatable = 0x004, ///< dock widget can be dragged into a floating window + DockWidgetDeleteOnClose = 0x008, ///< deletes the dock widget when it is closed + CustomCloseHandling + = 0x010, ///< clicking the close button will not close the dock widget but emits the closeRequested() signal instead + DockWidgetFocusable = 0x020, ///< if this is enabled, a dock widget can get focus highlighting + DockWidgetForceCloseWithArea + = 0x040, ///< dock widget will be closed when the dock area hosting it is closed + NoTab = 0x080, ///< dock widget tab will never be shown if this flag is set + DeleteContentOnClose + = 0x100, ///< deletes only the contained widget on close, keeping the dock widget intact + ///< and in place. Attempts to rebuild the contents widget on show if there is a widget factory set. + DockWidgetPinnable + = 0x200, ///< dock widget can be pinned and added to an auto hide dock container + DefaultDockWidgetFeatures = DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable + | DockWidgetFocusable | DockWidgetPinnable, + AllDockWidgetFeatures = DefaultDockWidgetFeatures | DockWidgetDeleteOnClose + | CustomCloseHandling, + DockWidgetAlwaysCloseAndDelete = DockWidgetForceCloseWithArea | DockWidgetDeleteOnClose, + NoDockWidgetFeatures = 0x000 }; Q_DECLARE_FLAGS(DockWidgetFeatures, DockWidgetFeature) @@ -158,10 +177,18 @@ public: * To ensure, that a dock widget does not block resizing, the dock widget * reimplements minimumSizeHint() function to return a very small minimum * size hint. If you would like to adhere the minimumSizeHint() from the - * content widget, the set the minimumSizeHintMode() to - * MinimumSizeHintFromContent. + * content widget, then set the minimumSizeHintMode() to + * MinimumSizeHintFromContent. If you would like to use the minimumSize() + * value of the content widget or the dock widget, then you can use the + * MinimumSizeHintFromDockWidgetMinimumSize or + * MinimumSizeHintFromContentMinimumSize modes. */ - enum eMinimumSizeHintMode { MinimumSizeHintFromDockWidget, MinimumSizeHintFromContent }; + enum eMinimumSizeHintMode { + MinimumSizeHintFromDockWidget, + MinimumSizeHintFromContent, + MinimumSizeHintFromDockWidgetMinimumSize, + MinimumSizeHintFromContentMinimumSize, + }; /** * This mode configures the behavior of the toggle view action. @@ -205,7 +232,7 @@ public: /** * Sets the widget for the dock widget to widget. * The InsertMode defines how the widget is inserted into the dock widget. - * The content of a dock widget should be resizable do a very small size to + * The content of a dock widget should be resizable to a very small size to * prevent the dock widget from blocking the resizing. To ensure, that a * dock widget can be resized very well, it is better to insert the content+ * widget into a scroll area or to provide a widget that is already a scroll @@ -221,6 +248,18 @@ public: */ void setWidget(QWidget *widget, eInsertMode insertMode = AutoScrollArea); + /** + * Only used when the feature flag DeleteContentOnClose is set. + * Using the flag and setting a widget factory allows to free the resources + * of the widget of your application while retaining the position the next + * time you want to show your widget, unlike the flag DockWidgetDeleteOnClose + * which deletes the dock widget itself. Since we keep the dock widget, all + * regular features of ADS should work as normal, including saving and + * restoring the state of the docking system and using perspectives. + */ + using FactoryFunc = std::function; + void setWidgetFactory(FactoryFunc createWidget, eInsertMode insertMode = AutoScrollArea); + /** * Remove the widget from the dock and give ownership back to the caller */ @@ -269,17 +308,50 @@ public: */ DockContainerWidget *dockContainer() const; + /** + * This function return the floating DockContainer if is isFloating() is true + * and a nullptr if this dock widget is not floating. + */ + FloatingDockContainer *floatingDockContainer() const; + /** * Returns the dock area widget this dock widget belongs to or 0 * if this dock widget has not been docked yet */ DockAreaWidget *dockAreaWidget() const; + /** + * Returns the side tab widget for this dock, if this dock widget is in + * a auto hide container. If it is not in a auto hide container, then this + * function returns a nullptr, + */ + AutoHideTab *sideTabWidget() const; + + /** + * Assign a side tab widget if this dock widget is an auto hide container + */ + void setSideTabWidget(AutoHideTab *sideTab) const; + + /** + * Returns true, if this dock widget is in an auto hide container + */ + bool isAutoHide() const; + + /** + * Returns the auto hide dock container of this dock widget or 0 if there is none. + */ + AutoHideDockContainer *autoHideDockContainer() const; + + /** + * Returns the auto hide side bar location or SideBarNone if, this is not an autohide dock widget. + */ + SideBarLocation autoHideLocation() const; + /** * This property holds whether the dock widget is floating. - * A dock widget is only floating, if it is the one and only widget inside - * of a floating container. If there are more than one dock widget in a - * floating container, the all dock widgets are docked and not floating. + * A dock widget is only floating, if it is the one and only widget inside of a floating + * container. If there are more than one dock widget in a floating container, the all dock + * widgets are docked and not floating. */ bool isFloating() const; @@ -314,6 +386,16 @@ public: */ void setMinimumSizeHintMode(eMinimumSizeHintMode mode); + /** + * Get the minimum size hint mode configured by setMinimumSizeHintMode + */ + eMinimumSizeHintMode minimumSizeHintMode() const; + + /** + * Returns true if the dock widget is set as central widget of it's dock manager + */ + bool isCentralWidget() const; + /** * Sets the dock widget icon that is shown in tabs and in toggle view * actions @@ -431,7 +513,7 @@ public: // reimplements QFrame /** * This property controls whether the dock widget is open or closed. - * The toogleViewAction triggers this slot + * The toggleViewAction triggers this slot */ void toggleView(bool open = true); @@ -468,26 +550,42 @@ public: // reimplements QFrame */ void closeDockWidget(); + /** + * Request closing of the dock widget. + * For DockWidget with default close handling, the function does the same like clodeDockWidget() + * but if the flag CustomCloseHandling is set, the function only emits the closeRequested() signal. + */ + void requestCloseDockWidget(); + /** * Shows the widget in full-screen mode. - * Normally this function only affects windows. To make the interface - * compatible to QDockWidget, this function also maximizes a floating - * dock widget. + * Normally this function only affects windows. To make the interface compatible to QDockWidget, + * this function also maximizes a floating dock widget. * - * \note Full-screen mode works fine under Windows, but has certain - * problems (doe not work) under X (Linux). These problems are due to - * limitations of the ICCCM protocol that specifies the communication - * between X11 clients and the window manager. ICCCM simply does not + * \note Full-screen mode works fine under Windows, but has certain problems (doe not work) + * under X (Linux). These problems are due to limitations of the ICCCM protocol that specifies + * the communication between X11 clients and the window manager. ICCCM simply does not * understand the concept of non-decorated full-screen windows. */ void showFullScreen(); /** - * This function complements showFullScreen() to restore the widget - * after it has been in full screen mode. + * This function complements showFullScreen() to restore the widget after it has been in full + * screen mode. */ void showNormal(); + /** + * Sets the dock widget into auto hide mode if this feature is enabled + * via CDockManager::setAutoHideFlags(CDockManager::AutoHideFeatureEnabled) + */ + void setAutoHide(bool enable, SideBarLocation location = SideBarNone, int tabIndex = -1); + + /** + * Switches the dock widget to auto hide mode or vice versa depending on its current state. + */ + void toggleAutoHide(SideBarLocation location = SideBarNone); + signals: /** * This signal is emitted if the dock widget is opened or closed diff --git a/src/libs/advanceddockingsystem/dockwidgettab.cpp b/src/libs/advanceddockingsystem/dockwidgettab.cpp index 1775da373f4..4efa339e34e 100644 --- a/src/libs/advanceddockingsystem/dockwidgettab.cpp +++ b/src/libs/advanceddockingsystem/dockwidgettab.cpp @@ -7,13 +7,13 @@ #include "ads_globals_p.h" #include "advanceddockingsystemtr.h" #include "dockareawidget.h" +#include "dockfocuscontroller.h" #include "dockmanager.h" #include "dockoverlay.h" #include "dockwidget.h" #include "elidinglabel.h" #include "floatingdockcontainer.h" #include "floatingdragpreview.h" -#include "iconprovider.h" #include @@ -23,549 +23,694 @@ #include #include #include +#include #include #include #include #include -#include -#include #include +#include #include -namespace ADS +namespace ADS { + +static const char *const g_locationProperty = "Location"; +using TabLabelType = ElidingLabel; + +/** + * Private data class of DockWidgetTab class (pimpl) + */ +class DockWidgetTabPrivate { - using TabLabelType = ElidingLabel; +public: + DockWidgetTab *q; + DockWidget *m_dockWidget = nullptr; + QLabel *m_iconLabel = nullptr; + TabLabelType *m_titleLabel = nullptr; + QPoint m_globalDragStartMousePosition; + QPoint m_dragStartMousePosition; + bool m_isActiveTab = false; + DockAreaWidget *m_dockArea = nullptr; + eDragState m_dragState = DraggingInactive; + AbstractFloatingWidget *m_floatingWidget = nullptr; + QIcon m_icon; + TabButton *m_closeButton = nullptr; + QPoint m_tabDragStartPosition; + QSize m_iconSize; /** - * Private data class of DockWidgetTab class (pimpl) + * Private data constructor */ - class DockWidgetTabPrivate + DockWidgetTabPrivate(DockWidgetTab *parent); + + /** + * Creates the complete layout including all controls + */ + void createLayout(); + + /** + * Moves the tab depending on the position in the given mouse event + */ + void moveTab(QMouseEvent *event); + + /** + * Test function for current drag state + */ + bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; } + + /** + * Starts floating of the dock widget that belongs to this title bar + * Returns true, if floating has been started and false if floating + * is not possible for any reason + */ + bool startFloating(eDragState draggingState = DraggingFloatingWidget); + + /** + * Returns true if the given config flag is set + */ + bool testConfigFlag(DockManager::eConfigFlag flag) const { - public: - DockWidgetTab *q; - DockWidget *m_dockWidget = nullptr; - QLabel *m_iconLabel = nullptr; - TabLabelType *m_titleLabel = nullptr; - QPoint m_globalDragStartMousePosition; - QPoint m_dragStartMousePosition; - bool m_isActiveTab = false; - DockAreaWidget *m_dockArea = nullptr; - eDragState m_dragState = DraggingInactive; - AbstractFloatingWidget *m_floatingWidget = nullptr; - QIcon m_icon; - TabButton *m_closeButton = nullptr; - QPoint m_tabDragStartPosition; - - /** - * Private data constructor - */ - DockWidgetTabPrivate(DockWidgetTab *parent); - - /** - * Creates the complete layout including all controls - */ - void createLayout(); - - /** - * Moves the tab depending on the position in the given mouse event - */ - void moveTab(QMouseEvent *event); - - /** - * Test function for current drag state - */ - bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; } - - /** - * Starts floating of the dock widget that belongs to this title bar - * Returns true, if floating has been started and false if floating - * is not possible for any reason - */ - bool startFloating(eDragState draggingState = DraggingFloatingWidget); - - /** - * Returns true if the given config flag is set - */ - bool testConfigFlag(DockManager::eConfigFlag flag) const - { - return DockManager::testConfigFlag(flag); - } - - /** - * Creates the close button as QPushButton or as QToolButton - */ - TabButton *createCloseButton() const - { - /* - if (testConfigFlag(DockManager::TabCloseButtonIsToolButton)) { - auto button = new QToolButton(); - button->setAutoRaise(true); - return button; - } else { - return new QPushButton(); - } - */ - return new TabButton(); - } - - template - AbstractFloatingWidget *createFloatingWidget(T *widget, bool opaqueUndocking) - { - if (opaqueUndocking) { - return new FloatingDockContainer(widget); - } else { - auto w = new FloatingDragPreview(widget); - QObject::connect(w, &FloatingDragPreview::draggingCanceled, q, [=]() { - m_dragState = DraggingInactive; - }); - return w; - } - } - - /** - * Saves the drag start position in global and local coordinates - */ - void saveDragStartMousePosition(const QPoint &globalPos) - { - m_globalDragStartMousePosition = globalPos; - m_dragStartMousePosition = q->mapFromGlobal(globalPos); - } - }; // class DockWidgetTabPrivate - - DockWidgetTabPrivate::DockWidgetTabPrivate(DockWidgetTab *parent) - : q(parent) - {} - - void DockWidgetTabPrivate::createLayout() - { - m_titleLabel = new TabLabelType(); - m_titleLabel->setElideMode(Qt::ElideRight); - m_titleLabel->setText(m_dockWidget->windowTitle()); - m_titleLabel->setObjectName("dockWidgetTabLabel"); - m_titleLabel->setAlignment(Qt::AlignCenter); - QObject::connect(m_titleLabel, - &ElidingLabel::elidedChanged, - q, - &DockWidgetTab::elidedChanged); - - m_closeButton = createCloseButton(); - m_closeButton->setObjectName("tabCloseButton"); - internal::setButtonIcon(m_closeButton, - QStyle::SP_TitleBarCloseButton, - TabCloseIcon); - m_closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - m_closeButton->setIconSize(QSize(11, 11)); - m_closeButton->setFixedSize(QSize(17, 17)); - q->onDockWidgetFeaturesChanged(); - internal::setToolTip(m_closeButton, Tr::tr("Close Tab")); - QObject::connect(m_closeButton, - &QAbstractButton::clicked, - q, - &DockWidgetTab::closeRequested); - - QFontMetrics fontMetrics(m_titleLabel->font()); - int spacing = qRound(fontMetrics.height() / 4.0); - - // Fill the layout - QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::LeftToRight); - boxLayout->setContentsMargins(2 * spacing, 0, 0, 0); - boxLayout->setSpacing(0); - q->setLayout(boxLayout); - boxLayout->addWidget(m_titleLabel, 1, Qt::AlignVCenter); - boxLayout->addSpacing(qRound(spacing * 4.0 / 3.0)); - boxLayout->addWidget(m_closeButton, 0, Qt::AlignVCenter); - boxLayout->addSpacing(1); - boxLayout->setAlignment(Qt::AlignCenter | Qt::AlignVCenter); - - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - m_closeButton->setCheckable(true); - - m_titleLabel->setVisible(true); + return DockManager::testConfigFlag(flag); } - void DockWidgetTabPrivate::moveTab(QMouseEvent *event) + /** + * Creates the close button as QPushButton or as QToolButton + */ + TabButton *createCloseButton() const { return new TabButton(); } + + template + AbstractFloatingWidget *createFloatingWidget(T *widget, bool opaqueUndocking) { - event->accept(); - QPoint distance = event->globalPosition().toPoint() - m_globalDragStartMousePosition; - distance.setY(0); - auto targetPos = distance + m_tabDragStartPosition; - targetPos.rx() = qMax(targetPos.x(), 0); - targetPos.rx() = qMin(q->parentWidget()->rect().right() - q->width() + 1, targetPos.rx()); - q->move(targetPos); - q->raise(); - } - - bool DockWidgetTabPrivate::startFloating(eDragState draggingState) - { - auto dockContainer = m_dockWidget->dockContainer(); - qCInfo(adsLog) << "isFloating " << dockContainer->isFloating(); - qCInfo(adsLog) << "areaCount " << dockContainer->dockAreaCount(); - qCInfo(adsLog) << "widgetCount " << m_dockWidget->dockAreaWidget()->dockWidgetsCount(); - // if this is the last dock widget inside of this floating widget, - // then it does not make any sense, to make it floating because - // it is already floating - if (dockContainer->isFloating() && (dockContainer->visibleDockAreaCount() == 1) - && (m_dockWidget->dockAreaWidget()->dockWidgetsCount() == 1)) { - return false; - } - - qCInfo(adsLog) << "startFloating"; - m_dragState = draggingState; - AbstractFloatingWidget *floatingWidget = nullptr; - bool opaqueUndocking = DockManager::testConfigFlag(DockManager::OpaqueUndocking) - || (DraggingFloatingWidget != draggingState); - - // If section widget has multiple tabs, we take only one tab - // If it has only one single tab, we can move the complete - // dock area into floating widget - QSize size; - if (m_dockArea->dockWidgetsCount() > 1) { - floatingWidget = createFloatingWidget(m_dockWidget, opaqueUndocking); - size = m_dockWidget->size(); + if (opaqueUndocking) { + return new FloatingDockContainer(widget); } else { - floatingWidget = createFloatingWidget(m_dockArea, opaqueUndocking); - size = m_dockArea->size(); + auto w = new FloatingDragPreview(widget); + QObject::connect(w, &FloatingDragPreview::draggingCanceled, q, [=]() { + m_dragState = DraggingInactive; + }); + return w; } - - if (DraggingFloatingWidget == draggingState) { - floatingWidget->startFloating(m_dragStartMousePosition, size, DraggingFloatingWidget, q); - auto Overlay = m_dockWidget->dockManager()->containerOverlay(); - Overlay->setAllowedAreas(OuterDockAreas); - this->m_floatingWidget = floatingWidget; - } else { - floatingWidget->startFloating(m_dragStartMousePosition, size, DraggingInactive, nullptr); - } - - return true; } - - TabButton::TabButton(QWidget *parent) - : TabButtonType(parent) - , m_active(false) - , m_focus(false) - {} - - void TabButton::setActive(bool value) { m_active = value; } - void TabButton::setFocus(bool value) { m_focus = value; } - - void TabButton::paintEvent(QPaintEvent *event) + /** + * Update the close button visibility from current feature/config + */ + void updateCloseButtonVisibility(bool active) { - Q_UNUSED(event) - - QStylePainter p(this); - QStyleOptionToolButton opt; - initStyleOption(&opt); - opt.icon = QIcon(); // set to null icon otherwise it is drawn twice - p.drawComplexControl(QStyle::CC_ToolButton, opt); - - QIcon::Mode mode = QIcon::Mode::Normal; - if (m_active) - mode = QIcon::Mode::Active; - if (m_focus) - mode = QIcon::Mode::Selected; - - const QPoint iconPosition = rect().center() - QPoint(iconSize().width() * 0.5, - iconSize().height() * 0.5); - - p.drawPixmap(iconPosition, icon().pixmap(iconSize(), mode)); - } - - - DockWidgetTab::DockWidgetTab(DockWidget *dockWidget, QWidget *parent) - : QFrame(parent) - , d(new DockWidgetTabPrivate(this)) - { - setAttribute(Qt::WA_NoMousePropagation, true); - d->m_dockWidget = dockWidget; - d->createLayout(); - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - setFocusPolicy(Qt::ClickFocus); - } - - DockWidgetTab::~DockWidgetTab() - { - qCInfo(adsLog) << Q_FUNC_INFO; - delete d; - } - - void DockWidgetTab::mousePressEvent(QMouseEvent *event) - { - if (event->button() == Qt::LeftButton) { - event->accept(); - d->saveDragStartMousePosition(event->globalPosition().toPoint()); - d->m_dragState = DraggingMousePressed; - emit clicked(); - return; - } - Super::mousePressEvent(event); - } - - void DockWidgetTab::mouseReleaseEvent(QMouseEvent *event) - { - if (event->button() == Qt::LeftButton) { - auto currentDragState = d->m_dragState; - d->m_globalDragStartMousePosition = QPoint(); - d->m_dragStartMousePosition = QPoint(); - d->m_dragState = DraggingInactive; - - switch (currentDragState) { - case DraggingTab: - // End of tab moving, emit signal - if (d->m_dockArea) { - emit moved(event->globalPosition().toPoint()); - } - break; - - case DraggingFloatingWidget: - d->m_floatingWidget->finishDragging(); - break; - - default:; // do nothing - } - } - - Super::mouseReleaseEvent(event); - } - - void DockWidgetTab::mouseMoveEvent(QMouseEvent *event) - { - if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { - d->m_dragState = DraggingInactive; - Super::mouseMoveEvent(event); - return; - } - - // move floating window - if (d->isDraggingState(DraggingFloatingWidget)) { - d->m_floatingWidget->moveFloating(); - Super::mouseMoveEvent(event); - return; - } - - // move tab - if (d->isDraggingState(DraggingTab)) { - // Moving the tab is always allowed because it does not mean moving the - // dock widget around - d->moveTab(event); - } - - auto mappedPos = mapToParent(event->pos()); - bool mouseOutsideBar = (mappedPos.x() < 0) || (mappedPos.x() > parentWidget()->rect().right()); - // Maybe a fixed drag distance is better here ? - int dragDistanceY = qAbs(d->m_globalDragStartMousePosition.y() - event->globalPosition().toPoint().y()); - if (dragDistanceY >= DockManager::startDragDistance() || mouseOutsideBar) { - // If this is the last dock area in a dock container with only - // one single dock widget it does not make sense to move it to a new - // floating widget and leave this one empty - if (d->m_dockArea->dockContainer()->isFloating() - && d->m_dockArea->openDockWidgetsCount() == 1 - && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1) { - return; - } - - // Floating is only allowed for widgets that are floatable - // If we do non opaque undocking, then can create the drag preview - // if the widget is movable. - auto features = d->m_dockWidget->features(); - if (features.testFlag(DockWidget::DockWidgetFloatable) - || (features.testFlag(DockWidget::DockWidgetMovable) - && !DockManager::testConfigFlag(DockManager::OpaqueUndocking))) { - // If we undock, we need to restore the initial position of this - // tab because it looks strange if it remains on its dragged position - if (d->isDraggingState(DraggingTab) - && !DockManager::testConfigFlag(DockManager::OpaqueUndocking)) - parentWidget()->layout()->update(); - - d->startFloating(); - } - return; - } else if (d->m_dockArea->openDockWidgetsCount() > 1 - && (event->globalPosition().toPoint() - d->m_globalDragStartMousePosition).manhattanLength() - >= QApplication::startDragDistance()) // Wait a few pixels before start moving - { - // If we start dragging the tab, we save its initial position to - // restore it later - if (DraggingTab != d->m_dragState) - d->m_tabDragStartPosition = this->pos(); - - d->m_dragState = DraggingTab; - return; - } - - Super::mouseMoveEvent(event); - } - - void DockWidgetTab::contextMenuEvent(QContextMenuEvent *event) - { - event->accept(); - if (d->isDraggingState(DraggingFloatingWidget)) - return; - - d->saveDragStartMousePosition(event->globalPos()); - QMenu menu(this); - - const bool isFloatable = d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable); - const bool isNotOnlyTabInContainer = !d->m_dockArea->dockContainer()->hasTopLevelDockWidget(); - const bool isDetachable = isFloatable && isNotOnlyTabInContainer; - - auto action = menu.addAction(Tr::tr("Detach"), this, &DockWidgetTab::detachDockWidget); - action->setEnabled(isDetachable); - menu.addSeparator(); - action = menu.addAction(Tr::tr("Close"), this, &DockWidgetTab::closeRequested); - action->setEnabled(isClosable()); - menu.addAction(Tr::tr("Close Others"), this, &DockWidgetTab::closeOtherTabsRequested); - menu.exec(event->globalPos()); - } - - bool DockWidgetTab::isActiveTab() const { return d->m_isActiveTab; } - - void DockWidgetTab::setActiveTab(bool active) - { - bool dockWidgetClosable = d->m_dockWidget->features().testFlag( - DockWidget::DockWidgetClosable); - bool activeTabHasCloseButton = d->testConfigFlag(DockManager::ActiveTabHasCloseButton); - bool allTabsHaveCloseButton = d->testConfigFlag(DockManager::AllTabsHaveCloseButton); + bool dockWidgetClosable = m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable); + bool activeTabHasCloseButton = testConfigFlag(DockManager::ActiveTabHasCloseButton); + bool allTabsHaveCloseButton = testConfigFlag(DockManager::AllTabsHaveCloseButton); bool tabHasCloseButton = (activeTabHasCloseButton && active) | allTabsHaveCloseButton; - d->m_closeButton->setVisible(dockWidgetClosable && tabHasCloseButton); - d->m_closeButton->setActive(active); - - // Focus related stuff - if (DockManager::testConfigFlag(DockManager::FocusHighlighting) - && !d->m_dockWidget->dockManager()->isRestoringState()) { - bool updateFocusStyle = false; - if (active && !hasFocus()) { - setFocus(Qt::OtherFocusReason); - updateFocusStyle = true; - } - if (d->m_isActiveTab == active) { - if (updateFocusStyle) - updateStyle(); - return; - } - } else if (d->m_isActiveTab == active) { - return; - } - - d->m_isActiveTab = active; - updateStyle(); - update(); - updateGeometry(); - - emit activeTabChanged(); + m_closeButton->setVisible(dockWidgetClosable && tabHasCloseButton); } - DockWidget *DockWidgetTab::dockWidget() const { return d->m_dockWidget; } - - void DockWidgetTab::setDockAreaWidget(DockAreaWidget *dockArea) { d->m_dockArea = dockArea; } - - DockAreaWidget *DockWidgetTab::dockAreaWidget() const { return d->m_dockArea; } - - void DockWidgetTab::setIcon(const QIcon &icon) + /** + * Update the size policy of the close button depending on the + * RetainTabSizeWhenCloseButtonHidden feature + */ + void updateCloseButtonSizePolicy() { - QBoxLayout *boxLayout = qobject_cast(layout()); - if (!d->m_iconLabel && icon.isNull()) + auto features = m_dockWidget->features(); + auto sizePolicy = m_closeButton->sizePolicy(); + sizePolicy.setRetainSizeWhenHidden( + features.testFlag(DockWidget::DockWidgetClosable) + && testConfigFlag(DockManager::RetainTabSizeWhenCloseButtonHidden)); + m_closeButton->setSizePolicy(sizePolicy); + } + + /** + * Saves the drag start position in global and local coordinates + */ + void saveDragStartMousePosition(const QPoint &globalPos) + { + m_globalDragStartMousePosition = globalPos; + m_dragStartMousePosition = q->mapFromGlobal(globalPos); + } + + /** + * Update the icon in case the icon size changed + */ + void updateIcon() + { + if (!m_iconLabel || m_icon.isNull()) return; - if (!d->m_iconLabel) { - d->m_iconLabel = new QLabel(); - d->m_iconLabel->setAlignment(Qt::AlignVCenter); - d->m_iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); - internal::setToolTip(d->m_iconLabel, d->m_titleLabel->toolTip()); - boxLayout->insertWidget(0, d->m_iconLabel, Qt::AlignVCenter); - boxLayout->insertSpacing(1, qRound(1.5 * boxLayout->contentsMargins().left() / 2.0)); - } else if (icon.isNull()) { - // Remove icon label and spacer item - boxLayout->removeWidget(d->m_iconLabel); - boxLayout->removeItem(boxLayout->itemAt(0)); - delete d->m_iconLabel; - d->m_iconLabel = nullptr; - } + if (m_iconSize.isValid()) + m_iconLabel->setPixmap(m_icon.pixmap(m_iconSize)); + else + m_iconLabel->setPixmap( + m_icon.pixmap(q->style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, q))); - d->m_icon = icon; - if (d->m_iconLabel) { - d->m_iconLabel->setPixmap( - icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this))); - d->m_iconLabel->setVisible(true); + m_iconLabel->setVisible(true); + } + + /** + * Convenience function for access to the dock manager dock focus controller + */ + DockFocusController *focusController() const + { + return m_dockWidget->dockManager()->dockFocusController(); + } + + /** + * Helper function to create and initialize the menu entries for + * the "Auto Hide Group To..." menu + */ + QAction *createAutoHideToAction(const QString &title, SideBarLocation location, QMenu *menu) + { + auto action = menu->addAction(title); + action->setProperty("Location", location); + QObject::connect(action, &QAction::triggered, q, &DockWidgetTab::onAutoHideToActionClicked); + return action; + } + +}; // class DockWidgetTabPrivate + +DockWidgetTabPrivate::DockWidgetTabPrivate(DockWidgetTab *parent) + : q(parent) +{} + +void DockWidgetTabPrivate::createLayout() +{ + m_titleLabel = new TabLabelType(); + m_titleLabel->setElideMode(Qt::ElideRight); + m_titleLabel->setText(m_dockWidget->windowTitle()); + m_titleLabel->setObjectName("dockWidgetTabLabel"); + m_titleLabel->setAlignment(Qt::AlignCenter); + QObject::connect(m_titleLabel, &ElidingLabel::elidedChanged, q, &DockWidgetTab::elidedChanged); + + m_closeButton = createCloseButton(); + m_closeButton->setObjectName("tabCloseButton"); + internal::setButtonIcon(m_closeButton, QStyle::SP_TitleBarCloseButton, TabCloseIcon); + m_closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_closeButton->setIconSize(QSize(11, 11)); + m_closeButton->setFixedSize(QSize(17, 17)); + m_closeButton->setFocusPolicy(Qt::NoFocus); + updateCloseButtonSizePolicy(); + internal::setToolTip(m_closeButton, Tr::tr("Close Tab")); + QObject::connect(m_closeButton, &QAbstractButton::clicked, q, &DockWidgetTab::closeRequested); + + QFontMetrics fontMetrics(m_titleLabel->font()); + int spacing = qRound(fontMetrics.height() / 4.0); + + // Fill the layout + QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::LeftToRight); + boxLayout->setContentsMargins(2 * spacing, 0, 0, 0); + boxLayout->setSpacing(0); + q->setLayout(boxLayout); + boxLayout->addWidget(m_titleLabel, 1, Qt::AlignVCenter); + boxLayout->addSpacing(qRound(spacing * 4.0 / 3.0)); + boxLayout->addWidget(m_closeButton, 0, Qt::AlignVCenter); + boxLayout->addSpacing(1); + boxLayout->setAlignment(Qt::AlignCenter | Qt::AlignVCenter); + + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + m_closeButton->setCheckable(true); + + m_titleLabel->setVisible(true); +} + +void DockWidgetTabPrivate::moveTab(QMouseEvent *event) +{ + event->accept(); + QPoint distance = event->globalPosition().toPoint() - m_globalDragStartMousePosition; + distance.setY(0); + auto targetPos = distance + m_tabDragStartPosition; + targetPos.rx() = qMax(targetPos.x(), 0); + targetPos.rx() = qMin(q->parentWidget()->rect().right() - q->width() + 1, targetPos.rx()); + q->move(targetPos); + q->raise(); +} + +bool DockWidgetTabPrivate::startFloating(eDragState draggingState) +{ + auto dockContainer = m_dockWidget->dockContainer(); + // If this is the last dock widget inside of this floating widget, then it does not make any + // sense, to make it floating because it is already floating. + if (dockContainer->isFloating() && (dockContainer->visibleDockAreaCount() == 1) + && (m_dockWidget->dockAreaWidget()->dockWidgetsCount() == 1)) + return false; + + m_dragState = draggingState; + AbstractFloatingWidget *floatingWidget = nullptr; + bool createContainer = (DraggingFloatingWidget != draggingState); + + // If section widget has multiple tabs, we take only one tab. If it has only one single tab, + // we can move the complete dock area into floating widget. + QSize size; + if (m_dockArea->dockWidgetsCount() > 1) { + floatingWidget = createFloatingWidget(m_dockWidget, createContainer); + size = m_dockWidget->size(); + } else { + floatingWidget = createFloatingWidget(m_dockArea, createContainer); + size = m_dockArea->size(); + } + + if (DraggingFloatingWidget == draggingState) { + floatingWidget->startFloating(m_dragStartMousePosition, size, DraggingFloatingWidget, q); + auto overlay = m_dockWidget->dockManager()->containerOverlay(); + overlay->setAllowedAreas(OuterDockAreas); + m_floatingWidget = floatingWidget; + qApp->postEvent(m_dockWidget, + new QEvent((QEvent::Type) internal::g_dockedWidgetDragStartEvent)); + } else { + floatingWidget->startFloating(m_dragStartMousePosition, size, DraggingInactive, nullptr); + } + + return true; +} + +TabButton::TabButton(QWidget *parent) + : TabButtonType(parent) + , m_active(false) + , m_focus(false) +{} + +void TabButton::setActive(bool value) +{ + m_active = value; +} +void TabButton::setFocus(bool value) +{ + m_focus = value; +} + +void TabButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QStylePainter p(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + opt.icon = QIcon(); // set to null icon otherwise it is drawn twice + p.drawComplexControl(QStyle::CC_ToolButton, opt); + + QIcon::Mode mode = QIcon::Mode::Normal; + if (m_active) + mode = QIcon::Mode::Active; + if (m_focus) + mode = QIcon::Mode::Selected; + + const QPoint iconPosition = rect().center() + - QPoint(iconSize().width() * 0.5, iconSize().height() * 0.5); + + p.drawPixmap(iconPosition, icon().pixmap(iconSize(), mode)); +} + +DockWidgetTab::DockWidgetTab(DockWidget *dockWidget, QWidget *parent) + : QFrame(parent) + , d(new DockWidgetTabPrivate(this)) +{ + setAttribute(Qt::WA_NoMousePropagation, true); + d->m_dockWidget = dockWidget; + d->createLayout(); + setFocusPolicy(Qt::NoFocus); +} + +DockWidgetTab::~DockWidgetTab() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; +} + +void DockWidgetTab::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + event->accept(); + d->saveDragStartMousePosition(event->globalPosition().toPoint()); + d->m_dragState = DraggingMousePressed; + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) { + d->focusController()->setDockWidgetTabPressed(true); + d->focusController()->setDockWidgetTabFocused(this); + } + emit clicked(); + return; + } + Super::mousePressEvent(event); +} + +void DockWidgetTab::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + auto currentDragState = d->m_dragState; + d->m_globalDragStartMousePosition = QPoint(); + d->m_dragStartMousePosition = QPoint(); + d->m_dragState = DraggingInactive; + + switch (currentDragState) { + case DraggingTab: + // End of tab moving, emit signal + if (d->m_dockArea) { + event->accept(); + emit moved(event->globalPosition().toPoint()); + } + break; + + case DraggingFloatingWidget: + event->accept(); + d->m_floatingWidget->finishDragging(); + break; + + default: + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + d->focusController()->setDockWidgetTabPressed(false); + break; + } + } else if (event->button() == Qt::MiddleButton) { + if (DockManager::testConfigFlag(DockManager::MiddleMouseButtonClosesTab) + && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable)) { + // Only attempt to close if the mouse is still + // on top of the widget, to allow the user to cancel. + if (rect().contains(mapFromGlobal(QCursor::pos()))) { + event->accept(); + emit closeRequested(); + } } } - const QIcon &DockWidgetTab::icon() const { return d->m_icon; } + Super::mouseReleaseEvent(event); +} - QString DockWidgetTab::text() const { return d->m_titleLabel->text(); } +void DockWidgetTab::mouseMoveEvent(QMouseEvent *event) +{ + if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { + d->m_dragState = DraggingInactive; + Super::mouseMoveEvent(event); + return; + } - void DockWidgetTab::mouseDoubleClickEvent(QMouseEvent *event) + // Move floating window + if (d->isDraggingState(DraggingFloatingWidget)) { + d->m_floatingWidget->moveFloating(); + Super::mouseMoveEvent(event); + return; + } + + // Move tab + if (d->isDraggingState(DraggingTab)) { + // Moving the tab is always allowed because it does not mean moving the dock widget around + d->moveTab(event); + } + + auto mappedPos = mapToParent(event->pos()); + bool mouseOutsideBar = (mappedPos.x() < 0) || (mappedPos.x() > parentWidget()->rect().right()); + // Maybe a fixed drag distance is better here ? + int dragDistanceY = qAbs(d->m_globalDragStartMousePosition.y() + - event->globalPosition().toPoint().y()); + if (dragDistanceY >= DockManager::startDragDistance() || mouseOutsideBar) { + // If this is the last dock area in a dock container with only + // one single dock widget it does not make sense to move it to a new + // floating widget and leave this one empty + if (d->m_dockArea->dockContainer()->isFloating() + && d->m_dockArea->openDockWidgetsCount() == 1 + && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1) { + return; + } + + // Floating is only allowed for widgets that are floatable + // We can create the drag preview if the widget is movable. + auto features = d->m_dockWidget->features(); + if (features.testFlag(DockWidget::DockWidgetFloatable) + || (features.testFlag(DockWidget::DockWidgetMovable))) { + // If we undock, we need to restore the initial position of this + // tab because it looks strange if it remains on its dragged position + if (d->isDraggingState(DraggingTab)) + parentWidget()->layout()->update(); + + d->startFloating(); + } + return; + } else if (d->m_dockArea->openDockWidgetsCount() > 1 + && (event->globalPosition().toPoint() - d->m_globalDragStartMousePosition) + .manhattanLength() + >= QApplication::startDragDistance()) // Wait a few pixels before start moving { + // If we start dragging the tab, we save its initial position to restore it later + if (DraggingTab != d->m_dragState) + d->m_tabDragStartPosition = this->pos(); + + d->m_dragState = DraggingTab; + return; + } + + Super::mouseMoveEvent(event); +} + +void DockWidgetTab::contextMenuEvent(QContextMenuEvent *event) +{ + event->accept(); + if (d->isDraggingState(DraggingFloatingWidget)) + return; + + d->saveDragStartMousePosition(event->globalPos()); + + const bool isFloatable = d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable); + const bool isNotOnlyTabInContainer = !d->m_dockArea->dockContainer()->hasTopLevelDockWidget(); + const bool isTopLevelArea = d->m_dockArea->isTopLevelArea(); + const bool isDetachable = isFloatable && isNotOnlyTabInContainer; + + QMenu menu(this); + + if (!isTopLevelArea) { + QAction *detachAction = menu.addAction(tr("Detach")); + detachAction->connect(detachAction, + &QAction::triggered, + this, + &DockWidgetTab::detachDockWidget); + detachAction->setEnabled(isDetachable); + + if (DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) { + QAction *pinAction = menu.addAction(tr("Pin")); + pinAction->connect(pinAction, + &QAction::triggered, + this, + &DockWidgetTab::autoHideDockWidget); + + auto isPinnable = d->m_dockWidget->features().testFlag(DockWidget::DockWidgetPinnable); + pinAction->setEnabled(isPinnable); + + auto subMenu = menu.addMenu(tr("Pin To...")); + subMenu->setEnabled(isPinnable); + d->createAutoHideToAction(tr("Top"), SideBarTop, subMenu); + d->createAutoHideToAction(tr("Left"), SideBarLeft, subMenu); + d->createAutoHideToAction(tr("Right"), SideBarRight, subMenu); + d->createAutoHideToAction(tr("Bottom"), SideBarBottom, subMenu); + } + } + + menu.addSeparator(); + + QAction *closeAction = menu.addAction(tr("Close")); + closeAction->connect(closeAction, &QAction::triggered, this, &DockWidgetTab::closeRequested); + closeAction->setEnabled(isClosable()); + + if (d->m_dockArea->openDockWidgetsCount() > 1) { + QAction *closeOthersAction = menu.addAction(tr("Close Others")); + closeOthersAction->connect(closeOthersAction, + &QAction::triggered, + this, + &DockWidgetTab::closeOtherTabsRequested); + } + menu.exec(event->globalPos()); +} + +bool DockWidgetTab::isActiveTab() const +{ + return d->m_isActiveTab; +} + +void DockWidgetTab::setActiveTab(bool active) +{ + d->updateCloseButtonVisibility(active); + + d->m_closeButton->setActive(active); // TODO + + // Focus related stuff + if (DockManager::testConfigFlag(DockManager::FocusHighlighting) + && !d->m_dockWidget->dockManager()->isRestoringState()) { + bool updateFocusStyle = false; + if (active && !hasFocus()) { + d->focusController()->setDockWidgetTabFocused(this); + updateFocusStyle = true; + } + if (d->m_isActiveTab == active) { + if (updateFocusStyle) + updateStyle(); + return; + } + } else if (d->m_isActiveTab == active) { + return; + } + + d->m_isActiveTab = active; + updateStyle(); + update(); + updateGeometry(); + + emit activeTabChanged(); +} + +DockWidget *DockWidgetTab::dockWidget() const +{ + return d->m_dockWidget; +} + +void DockWidgetTab::setDockAreaWidget(DockAreaWidget *dockArea) +{ + d->m_dockArea = dockArea; +} + +DockAreaWidget *DockWidgetTab::dockAreaWidget() const +{ + return d->m_dockArea; +} + +void DockWidgetTab::setIcon(const QIcon &icon) +{ + QBoxLayout *boxLayout = qobject_cast(layout()); + if (!d->m_iconLabel && icon.isNull()) + return; + + if (!d->m_iconLabel) { + d->m_iconLabel = new QLabel(); + d->m_iconLabel->setAlignment(Qt::AlignVCenter); + d->m_iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + internal::setToolTip(d->m_iconLabel, d->m_titleLabel->toolTip()); + boxLayout->insertWidget(0, d->m_iconLabel, Qt::AlignVCenter); + boxLayout->insertSpacing(1, qRound(1.5 * boxLayout->contentsMargins().left() / 2.0)); + } else if (icon.isNull()) { + // Remove icon label and spacer item + boxLayout->removeWidget(d->m_iconLabel); + boxLayout->removeItem(boxLayout->itemAt(0)); + delete d->m_iconLabel; + d->m_iconLabel = nullptr; + } + + d->m_icon = icon; + if (d->m_iconLabel) { + d->m_iconLabel->setPixmap( + icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this))); + d->m_iconLabel->setVisible(true); + } +} + +const QIcon &DockWidgetTab::icon() const +{ + return d->m_icon; +} + +QString DockWidgetTab::text() const +{ + return d->m_titleLabel->text(); +} + +void DockWidgetTab::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { // If this is the last dock area in a dock container it does not make // sense to move it to a new floating widget and leave this one empty if ((!d->m_dockArea->dockContainer()->isFloating() || d->m_dockArea->dockWidgetsCount() > 1) && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) { + event->accept(); d->saveDragStartMousePosition(event->globalPosition().toPoint()); d->startFloating(DraggingInactive); } - - Super::mouseDoubleClickEvent(event); } - void DockWidgetTab::setVisible(bool visible) - { - // Just here for debugging to insert debug output - Super::setVisible(visible); - } + Super::mouseDoubleClickEvent(event); +} - void DockWidgetTab::setText(const QString &title) { d->m_titleLabel->setText(title); } - bool DockWidgetTab::isTitleElided() const { return d->m_titleLabel->isElided(); } +void DockWidgetTab::setVisible(bool visible) +{ + visible &= !d->m_dockWidget->features().testFlag(DockWidget::NoTab); + Super::setVisible(visible); +} - bool DockWidgetTab::isClosable() const - { - return d->m_dockWidget - && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable); - } +void DockWidgetTab::setText(const QString &title) +{ + d->m_titleLabel->setText(title); +} +bool DockWidgetTab::isTitleElided() const +{ + return d->m_titleLabel->isElided(); +} - void DockWidgetTab::detachDockWidget() - { - if (!d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) - return; +bool DockWidgetTab::isClosable() const +{ + return d->m_dockWidget && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable); +} - d->saveDragStartMousePosition(QCursor::pos()); - d->startFloating(DraggingInactive); - } +void DockWidgetTab::detachDockWidget() +{ + if (!d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) + return; - bool DockWidgetTab::event(QEvent *event) - { + d->saveDragStartMousePosition(QCursor::pos()); + d->startFloating(DraggingInactive); +} + +void DockWidgetTab::autoHideDockWidget() +{ + d->m_dockWidget->setAutoHide(true); +} + +void DockWidgetTab::onAutoHideToActionClicked() +{ + int location = sender()->property(g_locationProperty).toInt(); + d->m_dockWidget->toggleAutoHide((SideBarLocation) location); +} + +bool DockWidgetTab::event(QEvent *event) +{ #ifndef QT_NO_TOOLTIP - if (event->type() == QEvent::ToolTipChange) { - const auto text = toolTip(); - d->m_titleLabel->setToolTip(text); - } + if (event->type() == QEvent::ToolTipChange) { + const auto text = toolTip(); + d->m_titleLabel->setToolTip(text); + if (d->m_iconLabel) + d->m_iconLabel->setToolTip(text); + } #endif - return Super::event(event); - } + if (event->type() == QEvent::StyleChange) + d->updateIcon(); - void DockWidgetTab::onDockWidgetFeaturesChanged() - { - auto features = d->m_dockWidget->features(); - auto sizePolicy = d->m_closeButton->sizePolicy(); - sizePolicy.setRetainSizeWhenHidden( - features.testFlag(DockWidget::DockWidgetClosable) - && d->testConfigFlag(DockManager::RetainTabSizeWhenCloseButtonHidden)); - d->m_closeButton->setSizePolicy(sizePolicy); - } + return Super::event(event); +} - void DockWidgetTab::setElideMode(Qt::TextElideMode mode) - { - d->m_titleLabel->setElideMode(mode); - } +void DockWidgetTab::onDockWidgetFeaturesChanged() +{ + d->updateCloseButtonSizePolicy(); + d->updateCloseButtonVisibility(isActiveTab()); +} - void DockWidgetTab::updateStyle() - { - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - d->m_closeButton->setFocus(property("focused").toBool()); +void DockWidgetTab::setElideMode(Qt::TextElideMode mode) +{ + d->m_titleLabel->setElideMode(mode); +} - internal::repolishStyle(this, internal::RepolishDirectChildren); - } +void DockWidgetTab::updateStyle() +{ + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + d->m_closeButton->setFocus(property("focused").toBool()); + + internal::repolishStyle(this, internal::RepolishDirectChildren); +} + +QSize DockWidgetTab::iconSize() const +{ + return d->m_iconSize; +} + +void DockWidgetTab::setIconSize(const QSize &size) +{ + if (size == d->m_iconSize) + return; + + d->m_iconSize = size; + d->updateIcon(); + emit iconSizeChanged(); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidgettab.h b/src/libs/advanceddockingsystem/dockwidgettab.h index 49909c6374b..1b3c6f9d116 100644 --- a/src/libs/advanceddockingsystem/dockwidgettab.h +++ b/src/libs/advanceddockingsystem/dockwidgettab.h @@ -35,24 +35,27 @@ private: bool m_focus; }; - /** * A dock widget tab that shows a title and an icon. - * The dock widget tab is shown in the dock area title bar to switch between - * tabbed dock widgets + * The dock widget tab is shown in the dock area title bar to switch between tabbed dock widgets. */ class ADS_EXPORT DockWidgetTab : public QFrame { Q_OBJECT Q_PROPERTY(bool activeTab READ isActiveTab WRITE setActiveTab NOTIFY activeTabChanged) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize NOTIFY iconSizeChanged) private: DockWidgetTabPrivate *d; ///< private data (pimpl) friend class DockWidgetTabPrivate; friend class DockWidget; friend class DockManager; + friend class AutoHideDockContainer; + void onDockWidgetFeaturesChanged(); void detachDockWidget(); + void autoHideDockWidget(); + void onAutoHideToActionClicked(); protected: void mousePressEvent(QMouseEvent *event) override; @@ -95,8 +98,7 @@ public: DockWidget *dockWidget() const; /** - * Sets the dock area widget the dockWidget returned by dockWidget() - * function belongs to. + * Sets the dock area widget the dockWidget returned by dockWidget() function belongs to. */ void setDockAreaWidget(DockAreaWidget *dockArea); @@ -152,6 +154,19 @@ public: */ void updateStyle(); + /** + * Returns the icon size. + * If no explicit icon size has been set, the function returns an invalid QSize. + */ + QSize iconSize() const; + + /** + * Set an explicit icon size. + * If no icon size has been set explicitly, than the tab sets the icon size depending + * on the style. + */ + void setIconSize(const QSize &size); + void setVisible(bool visible) override; signals: @@ -161,6 +176,7 @@ signals: void closeOtherTabsRequested(); void moved(const QPoint &globalPosition); void elidedChanged(bool elided); + void iconSizeChanged(); }; // class DockWidgetTab } // namespace ADS diff --git a/src/libs/advanceddockingsystem/elidinglabel.cpp b/src/libs/advanceddockingsystem/elidinglabel.cpp index 1942b5b1878..ae5b7fd506e 100644 --- a/src/libs/advanceddockingsystem/elidinglabel.cpp +++ b/src/libs/advanceddockingsystem/elidinglabel.cpp @@ -6,143 +6,145 @@ #include namespace ADS { - /** - * Private data of public ElidingLabel - */ - struct ElidingLabelPrivate - { - ElidingLabel *q; - Qt::TextElideMode m_elideMode = Qt::ElideNone; - QString m_text; - bool m_isElided = false; - ElidingLabelPrivate(ElidingLabel *parent) - : q(parent) - {} +/** + * Private data of public ElidingLabel + */ +class ElidingLabelPrivate +{ +public: + ElidingLabel *q; + Qt::TextElideMode m_elideMode = Qt::ElideNone; + QString m_text; + bool m_isElided = false; - void elideText(int width); - - /** - * Convenience function to check if the - */ - bool isModeElideNone() const { return Qt::ElideNone == m_elideMode; } - }; - - void ElidingLabelPrivate::elideText(int width) - { - if (isModeElideNone()) - return; - - QFontMetrics fm = q->fontMetrics(); - QString str = fm.elidedText(m_text, m_elideMode, width - q->margin() * 2 - q->indent()); - if (str == u'\u2026') - str = m_text.at(0); - - bool wasElided = m_isElided; - m_isElided = str != m_text; - if (m_isElided != wasElided) - emit q->elidedChanged(m_isElided); - - q->QLabel::setText(str); - } - - ElidingLabel::ElidingLabel(QWidget *parent, Qt::WindowFlags flags) - : QLabel(parent, flags) - , d(new ElidingLabelPrivate(this)) + ElidingLabelPrivate(ElidingLabel *parent) + : q(parent) {} - ElidingLabel::ElidingLabel(const QString &text, QWidget *parent, Qt::WindowFlags flags) - : QLabel(text, parent, flags) - , d(new ElidingLabelPrivate(this)) - { - d->m_text = text; + void elideText(int width); + + /** + * Convenience function to check if the + */ + bool isModeElideNone() const { return Qt::ElideNone == m_elideMode; } +}; + +void ElidingLabelPrivate::elideText(int width) +{ + if (isModeElideNone()) + return; + + QFontMetrics fm = q->fontMetrics(); + QString str = fm.elidedText(m_text, m_elideMode, width - q->margin() * 2 - q->indent()); + if (str == u'\u2026') + str = m_text.at(0); + + bool wasElided = m_isElided; + m_isElided = (str != m_text); + if (m_isElided != wasElided) + emit q->elidedChanged(m_isElided); + + q->QLabel::setText(str); +} + +ElidingLabel::ElidingLabel(QWidget *parent, Qt::WindowFlags flags) + : QLabel(parent, flags) + , d(new ElidingLabelPrivate(this)) +{} + +ElidingLabel::ElidingLabel(const QString &text, QWidget *parent, Qt::WindowFlags flags) + : QLabel(text, parent, flags) + , d(new ElidingLabelPrivate(this)) +{ + d->m_text = text; + internal::setToolTip(this, text); +} + +ElidingLabel::~ElidingLabel() +{ + delete d; +} + +Qt::TextElideMode ElidingLabel::elideMode() const +{ + return d->m_elideMode; +} + +void ElidingLabel::setElideMode(Qt::TextElideMode mode) +{ + d->m_elideMode = mode; + d->elideText(size().width()); +} + +bool ElidingLabel::isElided() const +{ + return d->m_isElided; +} + +void ElidingLabel::mouseReleaseEvent(QMouseEvent *event) +{ + Super::mouseReleaseEvent(event); + if (event->button() != Qt::LeftButton) + return; + + emit clicked(); +} + +void ElidingLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_UNUSED(event) + emit doubleClicked(); + Super::mouseDoubleClickEvent(event); +} + +void ElidingLabel::resizeEvent(QResizeEvent *event) +{ + if (!d->isModeElideNone()) + d->elideText(event->size().width()); + + Super::resizeEvent(event); +} + +QSize ElidingLabel::minimumSizeHint() const +{ + if (hasPixmap() || d->isModeElideNone()) + return QLabel::minimumSizeHint(); + + const QFontMetrics &fm = fontMetrics(); + QSize size(fm.horizontalAdvance(d->m_text.left(2) + "…"), fm.height()); + return size; +} + +QSize ElidingLabel::sizeHint() const +{ + if (hasPixmap() || d->isModeElideNone()) + return QLabel::sizeHint(); + + const QFontMetrics &fm = fontMetrics(); + QSize size(fm.horizontalAdvance(d->m_text), QLabel::sizeHint().height()); + return size; +} + +void ElidingLabel::setText(const QString &text) +{ + d->m_text = text; + if (d->isModeElideNone()) { + Super::setText(text); + } else { internal::setToolTip(this, text); - } - - ElidingLabel::~ElidingLabel() - { - delete d; - } - - Qt::TextElideMode ElidingLabel::elideMode() const - { - return d->m_elideMode; - } - - void ElidingLabel::setElideMode(Qt::TextElideMode mode) - { - d->m_elideMode = mode; d->elideText(size().width()); } +} - bool ElidingLabel::isElided() const - { - return d->m_isElided; - } +QString ElidingLabel::text() const +{ + return d->m_text; +} - void ElidingLabel::mouseReleaseEvent(QMouseEvent *event) - { - Super::mouseReleaseEvent(event); - if (event->button() != Qt::LeftButton) - return; - - emit clicked(); - } - - void ElidingLabel::mouseDoubleClickEvent(QMouseEvent *event) - { - Q_UNUSED(event) - emit doubleClicked(); - Super::mouseDoubleClickEvent(event); - } - - void ElidingLabel::resizeEvent(QResizeEvent *event) - { - if (!d->isModeElideNone()) - d->elideText(event->size().width()); - - Super::resizeEvent(event); - } - - bool ElidingLabel::hasPixmap() const - { - return !pixmap().isNull(); - } - - QSize ElidingLabel::minimumSizeHint() const - { - if (hasPixmap() || d->isModeElideNone()) - return QLabel::minimumSizeHint(); - - const QFontMetrics &fm = fontMetrics(); - QSize size(fm.horizontalAdvance(d->m_text.left(2) + "…"), fm.height()); - return size; - } - - QSize ElidingLabel::sizeHint() const - { - if (hasPixmap() || d->isModeElideNone()) - return QLabel::sizeHint(); - - const QFontMetrics &fm = fontMetrics(); - QSize size(fm.horizontalAdvance(d->m_text), QLabel::sizeHint().height()); - return size; - } - - void ElidingLabel::setText(const QString &text) - { - d->m_text = text; - if (d->isModeElideNone()) { - Super::setText(text); - } else { - internal::setToolTip(this, text); - d->elideText(this->size().width()); - } - } - - QString ElidingLabel::text() const - { - return d->m_text; - } +bool ElidingLabel::hasPixmap() const +{ + return !pixmap().isNull(); +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/elidinglabel.h b/src/libs/advanceddockingsystem/elidinglabel.h index 8ee2e9bf8af..e7d00dfc199 100644 --- a/src/libs/advanceddockingsystem/elidinglabel.h +++ b/src/libs/advanceddockingsystem/elidinglabel.h @@ -9,20 +9,19 @@ namespace ADS { -struct ElidingLabelPrivate; +class ElidingLabelPrivate; /** * A QLabel that supports eliding text. - * Because the functions setText() and text() are no virtual functions setting - * and reading the text via a pointer to the base class QLabel does not work - * properly + * Because the functions setText() and text() are no virtual functions setting and reading the + * text via a pointer to the base class QLabel does not work properly. */ class ADS_EXPORT ElidingLabel : public QLabel { Q_OBJECT private: ElidingLabelPrivate *d; - friend struct ElidingLabelPrivate; + friend class ElidingLabelPrivate; protected: void mouseReleaseEvent(QMouseEvent *event) override; @@ -32,8 +31,10 @@ protected: public: using Super = QLabel; - ElidingLabel(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::Widget); - ElidingLabel(const QString &text, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::Widget); + ElidingLabel(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + ElidingLabel(const QString &text, + QWidget *parent = nullptr, + Qt::WindowFlags flags = Qt::WindowFlags()); ~ElidingLabel() override; /** diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp index c99044a3d5a..83f203c5e57 100644 --- a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp +++ b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp @@ -9,7 +9,6 @@ #include "dockmanager.h" #include "dockoverlay.h" #include "dockwidget.h" -#include "linux/floatingwidgettitlebar.h" #include @@ -19,6 +18,9 @@ #pragma comment(lib, "User32.lib") #endif #endif +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +#include "linux/floatingwidgettitlebar.h" +#endif #include #include @@ -30,8 +32,8 @@ #include #include -namespace ADS -{ +namespace ADS { + #ifdef Q_OS_WIN #if 0 // set to 1 if you need this function for debugging /** @@ -329,101 +331,133 @@ static const char* windowsMessageString(int messageId) #endif #endif - AbstractFloatingWidget::~AbstractFloatingWidget() = default; +AbstractFloatingWidget::~AbstractFloatingWidget() = default; + +static unsigned int zOrderCounter = 0; +/** + * Private data class of FloatingDockContainer class (pimpl) + */ +class FloatingDockContainerPrivate +{ +public: + FloatingDockContainer *q; + DockContainerWidget *m_dockContainer = nullptr; + unsigned int m_zOrderIndex = ++zOrderCounter; + QPointer m_dockManager; + eDragState m_draggingState = DraggingInactive; + QPoint m_dragStartMousePosition; + DockContainerWidget *m_dropContainer = nullptr; + DockAreaWidget *m_singleDockArea = nullptr; + QPoint m_dragStartPos; + bool m_hiding = false; + bool m_autoHideChildren = true; +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + QWidget *m_mouseEventHandler = nullptr; + FloatingWidgetTitleBar *m_titleBar = nullptr; + bool m_isResizing = false; + bool m_mousePressed = false; +#endif - static unsigned int zOrderCounter = 0; /** - * Private data class of FloatingDockContainer class (pimpl) + * Private data constructor */ - class FloatingDockContainerPrivate + FloatingDockContainerPrivate(FloatingDockContainer *parent); + + void titleMouseReleaseEvent(); + + void updateDropOverlays(const QPoint &globalPosition); + + /** + * Returns true if the given config flag is set + */ + static bool testConfigFlag(DockManager::eConfigFlag flag) { - public: - FloatingDockContainer *q; - DockContainerWidget *m_dockContainer = nullptr; - unsigned int m_zOrderIndex = ++zOrderCounter; - QPointer m_dockManager; - eDragState m_draggingState = DraggingInactive; - QPoint m_dragStartMousePosition; - DockContainerWidget *m_dropContainer = nullptr; - DockAreaWidget *m_singleDockArea = nullptr; - QPoint m_dragStartPos; - bool m_hiding = false; - QWidget *m_mouseEventHandler = nullptr; // linux only - FloatingWidgetTitleBar *m_titleBar = nullptr; // linux only + return DockManager::testConfigFlag(flag); + } - /** - * Private data constructor - */ - FloatingDockContainerPrivate(FloatingDockContainer *parent); + /** + * Tests is a certain state is active + */ + bool isState(eDragState stateId) const { return stateId == m_draggingState; } - void titleMouseReleaseEvent(); - void updateDropOverlays(const QPoint &globalPosition); - - /** - * Returns true if the given config flag is set - */ - static bool testConfigFlag(DockManager::eConfigFlag flag) - { - return DockManager::testConfigFlag(flag); - } - - /** - * Tests is a certain state is active - */ - bool isState(eDragState stateId) const { return stateId == m_draggingState; } - - void setState(eDragState stateId) { m_draggingState = stateId; } - - void setWindowTitle(const QString &text) - { - if (Utils::HostOsInfo::isLinuxHost()) - m_titleBar->setTitle(text); - else - q->setWindowTitle(text); - } - - /** - * Reflect the current dock widget title in the floating widget windowTitle() - * depending on the DockManager::FloatingContainerHasWidgetTitle flag - */ - void reflectCurrentWidget(DockWidget *currentWidget) - { - // reflect CurrentWidget's title if configured to do so, otherwise display application name as window title - if (testConfigFlag(DockManager::FloatingContainerHasWidgetTitle)) - setWindowTitle(currentWidget->windowTitle()); - else - setWindowTitle(QApplication::applicationDisplayName()); - - // reflect currentWidget's icon if configured to do so, otherwise display application icon as window icon - QIcon currentWidgetIcon = currentWidget->icon(); - if (testConfigFlag(DockManager::FloatingContainerHasWidgetIcon) && !currentWidgetIcon.isNull()) - q->setWindowIcon(currentWidget->icon()); - else - q->setWindowIcon(QApplication::windowIcon()); - } - - /** - * Handles escape key press when dragging around the floating widget - */ - void handleEscapeKey(); - }; // class FloatingDockContainerPrivate - - FloatingDockContainerPrivate::FloatingDockContainerPrivate(FloatingDockContainer *parent) - : q(parent) - {} - - void FloatingDockContainerPrivate::titleMouseReleaseEvent() + /** + * Sets the dragging state and posts a FloatingWidgetDragStartEvent if dragging starts. + */ + void setState(eDragState stateId) { - setState(DraggingInactive); - if (!m_dropContainer) + if (m_draggingState == stateId) return; - if (m_dockManager->dockAreaOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea - || m_dockManager->containerOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea) { - DockOverlay *overlay = m_dockManager->containerOverlay(); - if (!overlay->dropOverlayRect().isValid()) - overlay = m_dockManager->dockAreaOverlay(); + m_draggingState = stateId; + if (m_draggingState == DraggingFloatingWidget) + qApp->postEvent(q, new QEvent((QEvent::Type) internal::g_floatingWidgetDragStartEvent)); + } + void setWindowTitle(const QString &text) + { +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (m_titleBar) + m_titleBar->setTitle(text); +#else + q->setWindowTitle(text); +#endif + } + + /** + * Reflect the current dock widget title in the floating widget windowTitle() + * depending on the DockManager::FloatingContainerHasWidgetTitle flag. + */ + void reflectCurrentWidget(DockWidget *currentWidget) + { + // Reflect currentWidget's title if configured to do so, otherwise display application name as window title + if (testConfigFlag(DockManager::FloatingContainerHasWidgetTitle)) + setWindowTitle(currentWidget->windowTitle()); + else + setWindowTitle(floatingContainersTitle()); + + // Reflect currentWidget's icon if configured to do so, otherwise display application icon as window icon + QIcon currentWidgetIcon = currentWidget->icon(); + if (testConfigFlag(DockManager::FloatingContainerHasWidgetIcon) + && !currentWidgetIcon.isNull()) + q->setWindowIcon(currentWidget->icon()); + else + q->setWindowIcon(QApplication::windowIcon()); + } + + /** + * Handles escape key press when dragging around the floating widget. + */ + void handleEscapeKey(); + + /** + * Returns the title used by all FloatingContainer that do not reflect the title of the + * current dock widget. + * + * If no title was set with DockManager::setFloatingContainersTitle(), + * it returns QGuiApplication::applicationDisplayName(). + */ + static QString floatingContainersTitle() { return DockManager::floatingContainersTitle(); } +}; // class FloatingDockContainerPrivate + +FloatingDockContainerPrivate::FloatingDockContainerPrivate(FloatingDockContainer *parent) + : q(parent) +{} + +void FloatingDockContainerPrivate::titleMouseReleaseEvent() +{ + setState(DraggingInactive); + if (!m_dropContainer) + return; + + if (m_dockManager->dockAreaOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea + || m_dockManager->containerOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea) { + DockOverlay *overlay = m_dockManager->containerOverlay(); + if (!overlay->dropOverlayRect().isValid()) + overlay = m_dockManager->dockAreaOverlay(); + + // Do not resize if we drop into an autohide sidebar area to preserve the dock area size + // for the initial size of the auto hide area. + if (!internal::isSideBarArea(overlay->dropAreaUnderCursor())) { // Resize the floating widget to the size of the highlighted drop area rectangle QRect rect = overlay->dropOverlayRect(); int frameWidth = (q->frameSize().width() - q->rect().width()) / 2; @@ -434,239 +468,309 @@ static const char* windowsMessageString(int messageId) q->setGeometry(QRect(topLeft, QSize(rect.width(), rect.height() - titleBarHeight))); QApplication::processEvents(); } - m_dropContainer->dropFloatingWidget(q, QCursor::pos()); } - - m_dockManager->containerOverlay()->hideOverlay(); - m_dockManager->dockAreaOverlay()->hideOverlay(); + m_dropContainer->dropFloatingWidget(q, QCursor::pos()); } - void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &globalPosition) - { - if (!q->isVisible() || !m_dockManager) - return; + m_dockManager->containerOverlay()->hideOverlay(); + m_dockManager->dockAreaOverlay()->hideOverlay(); +} - auto containers = m_dockManager->dockContainers(); - DockContainerWidget *topContainer = nullptr; - for (auto containerWidget : containers) { - if (!containerWidget->isVisible()) - continue; +void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &globalPosition) +{ + if (!q->isVisible() || !m_dockManager) + return; - if (m_dockContainer == containerWidget) - continue; +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + // Prevent display of drop overlays and docking as long as a modal dialog is active + if (qApp->activeModalWidget()) + return; +#endif - QPoint mappedPos = containerWidget->mapFromGlobal(globalPosition); - if (containerWidget->rect().contains(mappedPos)) { - if (!topContainer || containerWidget->isInFrontOf(topContainer)) - topContainer = containerWidget; - } + auto containers = m_dockManager->dockContainers(); + DockContainerWidget *topContainer = nullptr; + for (auto containerWidget : containers) { + if (!containerWidget->isVisible()) + continue; + + if (m_dockContainer == containerWidget) + continue; + + QPoint mappedPos = containerWidget->mapFromGlobal(globalPosition); + if (containerWidget->rect().contains(mappedPos)) { + if (!topContainer || containerWidget->isInFrontOf(topContainer)) + topContainer = containerWidget; } + } - m_dropContainer = topContainer; - auto containerOverlay = m_dockManager->containerOverlay(); - auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); + m_dropContainer = topContainer; + auto containerOverlay = m_dockManager->containerOverlay(); + auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); - if (!topContainer) { - containerOverlay->hideOverlay(); - dockAreaOverlay->hideOverlay(); - return; - } + if (!topContainer) { + containerOverlay->hideOverlay(); + dockAreaOverlay->hideOverlay(); + return; + } - int visibleDockAreas = topContainer->visibleDockAreaCount(); - containerOverlay->setAllowedAreas(visibleDockAreas > 1 ? OuterDockAreas : AllDockAreas); - DockWidgetArea containerArea = containerOverlay->showOverlay(topContainer); - containerOverlay->enableDropPreview(containerArea != InvalidDockWidgetArea); - auto dockArea = topContainer->dockAreaAt(globalPosition); - if (dockArea && dockArea->isVisible() && visibleDockAreas > 0) { - dockAreaOverlay->enableDropPreview(true); - dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea - : dockArea->allowedAreas()); - DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); + int visibleDockAreas = topContainer->visibleDockAreaCount(); - // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in - // the title bar. If the ContainerArea is valid then we ignore the dock area of the - // dockAreaOverlay() and disable the drop preview - if ((area == CenterDockWidgetArea) && (containerArea != InvalidDockWidgetArea)) { - dockAreaOverlay->enableDropPreview(false); - containerOverlay->enableDropPreview(true); - } else { - containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); - } + DockWidgetAreas allowedContainerAreas = (visibleDockAreas > 1) ? OuterDockAreas : AllDockAreas; + auto dockArea = topContainer->dockAreaAt(globalPosition); + // If the dock container contains only one single DockArea, then we need to respect the allowed + // areas - only the center area is relevant here because all other allowed areas are from the + // container. + if (visibleDockAreas == 1 && dockArea) + allowedContainerAreas.setFlag(CenterDockWidgetArea, + dockArea->allowedAreas().testFlag(CenterDockWidgetArea)); + + if (m_dockContainer->features().testFlag(DockWidget::DockWidgetPinnable)) + allowedContainerAreas |= AutoHideDockAreas; + + containerOverlay->setAllowedAreas(allowedContainerAreas); + + DockWidgetArea containerArea = containerOverlay->showOverlay(topContainer); + containerOverlay->enableDropPreview(containerArea != InvalidDockWidgetArea); + if (dockArea && dockArea->isVisible() && visibleDockAreas > 0) { + dockAreaOverlay->enableDropPreview(true); + dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea + : dockArea->allowedAreas()); + DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); + + // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in + // the title bar. If the ContainerArea is valid then we ignore the dock area of the + // dockAreaOverlay() and disable the drop preview. + if ((area == CenterDockWidgetArea) && (containerArea != InvalidDockWidgetArea)) { + dockAreaOverlay->enableDropPreview(false); + containerOverlay->enableDropPreview(true); } else { - dockAreaOverlay->hideOverlay(); + containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); } + } else { + dockAreaOverlay->hideOverlay(); } +} - void FloatingDockContainerPrivate::handleEscapeKey() - { - qCInfo(adsLog) << Q_FUNC_INFO; - setState(DraggingInactive); - m_dockManager->containerOverlay()->hideOverlay(); - m_dockManager->dockAreaOverlay()->hideOverlay(); - } +void FloatingDockContainerPrivate::handleEscapeKey() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + setState(DraggingInactive); + m_dockManager->containerOverlay()->hideOverlay(); + m_dockManager->dockAreaOverlay()->hideOverlay(); +} - FloatingDockContainer::FloatingDockContainer(DockManager *dockManager) - : FloatingWidgetBaseType(dockManager) - , d(new FloatingDockContainerPrivate(this)) - { - d->m_dockManager = dockManager; - d->m_dockContainer = new DockContainerWidget(dockManager, this); - connect(d->m_dockContainer, - &DockContainerWidget::dockAreasAdded, - this, - &FloatingDockContainer::onDockAreasAddedOrRemoved); - connect(d->m_dockContainer, - &DockContainerWidget::dockAreasRemoved, - this, - &FloatingDockContainer::onDockAreasAddedOrRemoved); +FloatingDockContainer::FloatingDockContainer(DockManager *dockManager) + : FloatingWidgetBaseType(dockManager) + , d(new FloatingDockContainerPrivate(this)) +{ + d->m_dockManager = dockManager; + d->m_dockContainer = new DockContainerWidget(dockManager, this); - #ifdef Q_OS_LINUX - d->m_titleBar = new FloatingWidgetTitleBar(this); - setWindowFlags(windowFlags() | Qt::Tool); - QDockWidget::setWidget(d->m_dockContainer); - QDockWidget::setFloating(true); - QDockWidget::setFeatures(DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable); - setTitleBarWidget(d->m_titleBar); - connect(d->m_titleBar, - &FloatingWidgetTitleBar::closeRequested, - this, - &FloatingDockContainer::close); - #else - setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint | Qt::Tool); - QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::TopToBottom); - boxLayout->setContentsMargins(0, 0, 0, 0); - boxLayout->setSpacing(0); - setLayout(boxLayout); - boxLayout->addWidget(d->m_dockContainer); - #endif - dockManager->registerFloatingWidget(this); - } + connect(d->m_dockContainer, + &DockContainerWidget::dockAreasAdded, + this, + &FloatingDockContainer::onDockAreasAddedOrRemoved); + connect(d->m_dockContainer, + &DockContainerWidget::dockAreasRemoved, + this, + &FloatingDockContainer::onDockAreasAddedOrRemoved); - FloatingDockContainer::FloatingDockContainer(DockAreaWidget *dockArea) - : FloatingDockContainer(dockArea->dockManager()) - { - d->m_dockContainer->addDockArea(dockArea); - #ifdef Q_OS_LINUX - d->m_titleBar->enableCloseButton(isClosable()); - #endif - if (auto dw = topLevelDockWidget()) - dw->emitTopLevelChanged(true); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + // Order here is really important. setWindowFlags() must come first otherwise the resize handles + // on linux are missing from floating dock widgets. + setWindowFlags(Qt::Window | Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint | Qt::Tool); + QDockWidget::setWidget(d->m_dockContainer); + //QDockWidget::setFloating(true); + QDockWidget::setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable + | QDockWidget::DockWidgetFloatable); - d->m_dockManager->notifyWidgetOrAreaRelocation(dockArea); - } + d->m_titleBar = new FloatingWidgetTitleBar(this); + setTitleBarWidget(d->m_titleBar); + d->m_titleBar->enableCloseButton(isClosable()); + d->m_titleBar->setMaximizedIcon(windowState() == Qt::WindowMaximized); + connect(d->m_titleBar, + &FloatingWidgetTitleBar::closeRequested, + this, + &FloatingDockContainer::close); + connect(d->m_titleBar, + &FloatingWidgetTitleBar::maximizeRequested, + this, + &FloatingDockContainer::onMaximizeRequest); - FloatingDockContainer::FloatingDockContainer(DockWidget *dockWidget) - : FloatingDockContainer(dockWidget->dockManager()) - { - d->m_dockContainer->addDockWidget(CenterDockWidgetArea, dockWidget); - #ifdef Q_OS_LINUX - d->m_titleBar->enableCloseButton(isClosable()); - #endif - if (auto dw = topLevelDockWidget()) - dw->emitTopLevelChanged(true); +#else + setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint | Qt::Tool); + QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::TopToBottom); + boxLayout->setContentsMargins(0, 0, 0, 0); + boxLayout->setSpacing(0); + setLayout(boxLayout); + boxLayout->addWidget(d->m_dockContainer); +#endif + dockManager->registerFloatingWidget(this); +} - d->m_dockManager->notifyWidgetOrAreaRelocation(dockWidget); - } +FloatingDockContainer::FloatingDockContainer(DockAreaWidget *dockArea) + : FloatingDockContainer(dockArea->dockManager()) +{ + d->m_dockContainer->addDockArea(dockArea); - FloatingDockContainer::~FloatingDockContainer() - { - qCInfo(adsLog) << Q_FUNC_INFO; - if (d->m_dockManager) - d->m_dockManager->removeFloatingWidget(this); + if (auto dw = topLevelDockWidget()) + dw->emitTopLevelChanged(true); - delete d; - } + d->m_dockManager->notifyWidgetOrAreaRelocation(dockArea); +} - DockContainerWidget *FloatingDockContainer::dockContainer() const { return d->m_dockContainer; } +FloatingDockContainer::FloatingDockContainer(DockWidget *dockWidget) + : FloatingDockContainer(dockWidget->dockManager()) +{ + d->m_dockContainer->addDockWidget(CenterDockWidgetArea, dockWidget); - void FloatingDockContainer::changeEvent(QEvent *event) - { - QWidget::changeEvent(event); - if ((event->type() == QEvent::ActivationChange) && isActiveWindow()) { - qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::ActivationChange"; + if (auto dw = topLevelDockWidget()) + dw->emitTopLevelChanged(true); + + d->m_dockManager->notifyWidgetOrAreaRelocation(dockWidget); +} + +FloatingDockContainer::~FloatingDockContainer() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + if (d->m_dockManager) + d->m_dockManager->removeFloatingWidget(this); + + delete d; +} + +DockContainerWidget *FloatingDockContainer::dockContainer() const +{ + return d->m_dockContainer; +} + +void FloatingDockContainer::changeEvent(QEvent *event) +{ + Super::changeEvent(event); + switch (event->type()) { + case QEvent::ActivationChange: + if (isActiveWindow()) { + qCInfo(adsLog) << Q_FUNC_INFO << "Event::ActivationChange"; d->m_zOrderIndex = ++zOrderCounter; - return; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (d->m_draggingState == DraggingFloatingWidget) { + d->titleMouseReleaseEvent(); + d->m_draggingState = DraggingInactive; + } +#endif } + break; + + case QEvent::WindowStateChange: + // If the DockManager window is restored from minimized on Windows then the FloatingWidgets + // are not properly restored to maximized but to normal state. + // We simply check here, if the FloatingWidget was maximized before and if the DockManager + // is just leaving the minimized state. In this case, we restore the maximized state of + // this floating widget. + if (d->m_dockManager->isLeavingMinimizedState()) { + QWindowStateChangeEvent *ev = static_cast(event); + if (ev->oldState().testFlag(Qt::WindowMaximized)) + showMaximized(); + } + break; + + default: + break; // do nothing } +} #ifdef Q_OS_WIN bool FloatingDockContainer::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) { QWidget::nativeEvent(eventType, message, result); MSG *msg = static_cast(message); - switch (msg->message) - { - case WM_MOVING: - { - if (d->isState(DraggingFloatingWidget)) - d->updateDropOverlays(QCursor::pos()); + switch (msg->message) { + case WM_MOVING: { + if (d->isState(DraggingFloatingWidget)) + d->updateDropOverlays(QCursor::pos()); + } break; + + case WM_NCLBUTTONDOWN: + if (msg->wParam == HTCAPTION && d->isState(DraggingInactive)) { + qCInfo(adsLog) << Q_FUNC_INFO << "WM_NCLBUTTONDOWN" << eventType; + d->m_dragStartPos = pos(); + d->setState(DraggingMousePressed); } break; - case WM_NCLBUTTONDOWN: - if (msg->wParam == HTCAPTION && d->isState(DraggingInactive)) - { - qCInfo(adsLog) << Q_FUNC_INFO << "WM_NCLBUTTONDOWN" << eventType; - d->m_dragStartPos = pos(); - d->setState(DraggingMousePressed); - } - break; + case WM_NCLBUTTONDBLCLK: + d->setState(DraggingInactive); + break; - case WM_NCLBUTTONDBLCLK: - d->setState(DraggingInactive); - break; + case WM_ENTERSIZEMOVE: + if (d->isState(DraggingMousePressed)) { + qCInfo(adsLog) << Q_FUNC_INFO << "WM_ENTERSIZEMOVE" << eventType; + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + } + break; - case WM_ENTERSIZEMOVE: - if (d->isState(DraggingMousePressed)) - { - qCInfo(adsLog) << Q_FUNC_INFO << "WM_ENTERSIZEMOVE" << eventType; - d->setState(DraggingFloatingWidget); - d->updateDropOverlays(QCursor::pos()); - } - break; - - case WM_EXITSIZEMOVE: - if (d->isState(DraggingFloatingWidget)) - { - qCInfo(adsLog) << Q_FUNC_INFO << "WM_EXITSIZEMOVE" << eventType; - if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) - d->handleEscapeKey(); - else - d->titleMouseReleaseEvent(); - } - break; + case WM_EXITSIZEMOVE: + if (d->isState(DraggingFloatingWidget)) { + qCInfo(adsLog) << Q_FUNC_INFO << "WM_EXITSIZEMOVE" << eventType; + if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) + d->handleEscapeKey(); + else + d->titleMouseReleaseEvent(); + } + break; } return false; } #endif - void FloatingDockContainer::closeEvent(QCloseEvent *event) - { - qCInfo(adsLog) << Q_FUNC_INFO; - d->setState(DraggingInactive); - event->ignore(); +void FloatingDockContainer::closeEvent(QCloseEvent *event) +{ + qCInfo(adsLog) << Q_FUNC_INFO << "closable" << isClosable(); + d->setState(DraggingInactive); + event->ignore(); - if (isClosable()) { - auto dw = topLevelDockWidget(); - if (dw && dw->features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { - if (!dw->closeDockWidgetInternal()) - return; - } + if (!isClosable()) + return; - this->hide(); + bool hasOpenDockWidgets = false; + for (auto dockWidget : d->m_dockContainer->openedDockWidgets()) { + if (dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) + || dockWidget->features().testFlag(DockWidget::CustomCloseHandling)) { + bool closed = dockWidget->closeDockWidgetInternal(); + if (!closed) + hasOpenDockWidgets = true; + } else { + dockWidget->toggleView(false); } } - void FloatingDockContainer::hideEvent(QHideEvent *event) - { - Super::hideEvent(event); - if (event->spontaneous()) - return; + if (hasOpenDockWidgets) + return; - // Prevent toogleView() events during restore state - if (d->m_dockManager->isRestoringState()) - return; + // In Qt version after 5.9.2 there seems to be a bug that causes the QWidget::event() function + // to not receive any NonClientArea mouse events anymore after a close/show cycle. The bug is + // reported here: https://bugreports.qt.io/browse/QTBUG-73295 + // The following code is a workaround for Qt versions > 5.9.2 that seems to work. Starting from + // Qt version 5.12.2 this seems to work again. But now the QEvent::NonClientAreaMouseButtonPress + // function returns always Qt::RightButton even if the left button was pressed. + hide(); +} +void FloatingDockContainer::hideEvent(QHideEvent *event) +{ + qCInfo(adsLog) << Q_FUNC_INFO; + + Super::hideEvent(event); + if (event->spontaneous()) + return; + + // Prevent toogleView() events during restore state + if (d->m_dockManager->isRestoringState()) + return; + + if (d->m_autoHideChildren) { d->m_hiding = true; for (auto dockArea : d->m_dockContainer->openedDockAreas()) { for (auto dockWidget : dockArea->openedDockWidgets()) @@ -674,204 +778,218 @@ bool FloatingDockContainer::nativeEvent(const QByteArray &eventType, void *messa } d->m_hiding = false; } +} - void FloatingDockContainer::showEvent(QShowEvent *event) - { - Super::showEvent(event); - #ifdef Q_OS_LINUX - if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) - window()->activateWindow(); - #endif - } +void FloatingDockContainer::showEvent(QShowEvent *event) +{ + qCInfo(adsLog) << Q_FUNC_INFO; - void FloatingDockContainer::startFloating(const QPoint &dragStartMousePos, - const QSize &size, - eDragState dragState, - QWidget *mouseEventHandler) - { - #ifndef Q_OS_LINUX - Q_UNUSED(mouseEventHandler) - #endif + Super::showEvent(event); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) + window()->activateWindow(); +#endif +} + +void FloatingDockContainer::startFloating(const QPoint &dragStartMousePos, + const QSize &size, + eDragState dragState, + QWidget *mouseEventHandler) +{ +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (!isMaximized()) { resize(size); - d->setState(dragState); d->m_dragStartMousePosition = dragStartMousePos; + } + d->setState(dragState); + if (dragState == DraggingFloatingWidget) { + d->m_mouseEventHandler = mouseEventHandler; + if (d->m_mouseEventHandler) + d->m_mouseEventHandler->grabMouse(); + } - #ifdef Q_OS_LINUX - if (DraggingFloatingWidget == dragState) { - setAttribute(Qt::WA_X11NetWmWindowTypeDock, true); - d->m_mouseEventHandler = mouseEventHandler; - if (d->m_mouseEventHandler) - d->m_mouseEventHandler->grabMouse(); - } - #endif + if (!isMaximized()) moveFloating(); - show(); + + show(); +#else + Q_UNUSED(mouseEventHandler) + resize(size); + d->m_dragStartMousePosition = dragStartMousePos; + d->setState(dragState); + moveFloating(); + show(); +#endif +} + +void FloatingDockContainer::moveFloating() +{ + const int borderSize = (frameSize().width() - size().width()) / 2; + const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition - QPoint(borderSize, 0); + move(moveToPos); + + switch (d->m_draggingState) { + case DraggingMousePressed: + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + break; + + case DraggingFloatingWidget: + d->updateDropOverlays(QCursor::pos()); + // On macOS when hiding the DockAreaOverlay the application would set the main window as + // the active window for some reason. This fixes that by resetting the active window to + // the floating widget after updating the overlays. + if (Utils::HostOsInfo::isMacHost()) + activateWindow(); + + break; + default: + break; } +} - void FloatingDockContainer::moveFloating() - { - const int borderSize = (frameSize().width() - size().width()) / 2; - const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition - - QPoint(borderSize, 0); - move(moveToPos); +bool FloatingDockContainer::isClosable() const +{ + return d->m_dockContainer->features().testFlag(DockWidget::DockWidgetClosable); +} - switch (d->m_draggingState) - { - case DraggingMousePressed: - d->setState(DraggingFloatingWidget); - d->updateDropOverlays(QCursor::pos()); - break; - - case DraggingFloatingWidget: - d->updateDropOverlays(QCursor::pos()); - // On macOS when hiding the DockAreaOverlay the application would set - // the main window as the active window for some reason. This fixes - // that by resetting the active window to the floating widget after - // updating the overlays. - if (Utils::HostOsInfo::isMacHost()) - QApplication::setActiveWindow(this); - - break; - default: - break; - } - } - - bool FloatingDockContainer::isClosable() const - { - return d->m_dockContainer->features().testFlag(DockWidget::DockWidgetClosable); - } - - void FloatingDockContainer::onDockAreasAddedOrRemoved() - { - qCInfo(adsLog) << Q_FUNC_INFO; - auto topLevelDockArea = d->m_dockContainer->topLevelDockArea(); - if (topLevelDockArea) { - d->m_singleDockArea = topLevelDockArea; - DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget(); - d->reflectCurrentWidget(currentWidget); - connect(d->m_singleDockArea, - &DockAreaWidget::currentChanged, - this, - &FloatingDockContainer::onDockAreaCurrentChanged); - } else { - if (d->m_singleDockArea) { - disconnect(d->m_singleDockArea, - &DockAreaWidget::currentChanged, - this, - &FloatingDockContainer::onDockAreaCurrentChanged); - d->m_singleDockArea = nullptr; - } - d->setWindowTitle(QApplication::applicationDisplayName()); - setWindowIcon(QApplication::windowIcon()); - } - } - - void FloatingDockContainer::updateWindowTitle() - { - // If this floating container will be hidden, then updating the window - // title is not required anymore - if (d->m_hiding) - return; - - if (auto topLevelDockArea = d->m_dockContainer->topLevelDockArea()) { - if (DockWidget *currentWidget = topLevelDockArea->currentDockWidget()) - d->reflectCurrentWidget(currentWidget); - } else { - d->setWindowTitle(QApplication::applicationDisplayName()); - setWindowIcon(QApplication::windowIcon()); - } - } - - void FloatingDockContainer::onDockAreaCurrentChanged(int index) - { - Q_UNUSED(index) +void FloatingDockContainer::onDockAreasAddedOrRemoved() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + auto topLevelDockArea = d->m_dockContainer->topLevelDockArea(); + if (topLevelDockArea) { + d->m_singleDockArea = topLevelDockArea; DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget(); d->reflectCurrentWidget(currentWidget); - } - - bool FloatingDockContainer::restoreState(DockingStateReader &stream, bool testing) - { - if (!d->m_dockContainer->restoreState(stream, testing)) - return false; - - onDockAreasAddedOrRemoved(); - return true; - } - - bool FloatingDockContainer::hasTopLevelDockWidget() const - { - return d->m_dockContainer->hasTopLevelDockWidget(); - } - - DockWidget *FloatingDockContainer::topLevelDockWidget() const - { - return d->m_dockContainer->topLevelDockWidget(); - } - - QList FloatingDockContainer::dockWidgets() const - { - return d->m_dockContainer->dockWidgets(); - } - - void FloatingDockContainer::finishDragging() - { - qCInfo(adsLog) << Q_FUNC_INFO; - - #ifdef Q_OS_LINUX - setAttribute(Qt::WA_X11NetWmWindowTypeDock, false); - setWindowOpacity(1); - activateWindow(); - if (d->m_mouseEventHandler) { - d->m_mouseEventHandler->releaseMouse(); - d->m_mouseEventHandler = nullptr; + connect(d->m_singleDockArea, + &DockAreaWidget::currentChanged, + this, + &FloatingDockContainer::onDockAreaCurrentChanged); + } else { + if (d->m_singleDockArea) { + disconnect(d->m_singleDockArea, + &DockAreaWidget::currentChanged, + this, + &FloatingDockContainer::onDockAreaCurrentChanged); + d->m_singleDockArea = nullptr; } - #endif - d->titleMouseReleaseEvent(); + d->setWindowTitle(d->floatingContainersTitle()); + setWindowIcon(QApplication::windowIcon()); } +} + +void FloatingDockContainer::updateWindowTitle() +{ + // If this floating container will be hidden, updating the window title is not required anymore. + if (d->m_hiding) + return; + + if (auto topLevelDockArea = d->m_dockContainer->topLevelDockArea()) { + if (DockWidget *currentWidget = topLevelDockArea->currentDockWidget()) + d->reflectCurrentWidget(currentWidget); + } else { + d->setWindowTitle(QApplication::applicationDisplayName()); + setWindowIcon(QApplication::windowIcon()); + } +} + +void FloatingDockContainer::onDockAreaCurrentChanged(int index) +{ + Q_UNUSED(index) + DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget(); + d->reflectCurrentWidget(currentWidget); +} + +bool FloatingDockContainer::restoreState(DockingStateReader &stream, bool testing) +{ + if (!d->m_dockContainer->restoreState(stream, testing)) + return false; + + onDockAreasAddedOrRemoved(); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (d->m_titleBar) + d->m_titleBar->setMaximizedIcon(windowState() == Qt::WindowMaximized); +#endif + return true; +} + +bool FloatingDockContainer::hasTopLevelDockWidget() const +{ + return d->m_dockContainer->hasTopLevelDockWidget(); +} + +DockWidget *FloatingDockContainer::topLevelDockWidget() const +{ + return d->m_dockContainer->topLevelDockWidget(); +} + +QList FloatingDockContainer::dockWidgets() const +{ + return d->m_dockContainer->dockWidgets(); +} + +void FloatingDockContainer::hideAndDeleteLater() +{ + // Widget has been redocked, so it must be hidden right way (see + // https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351) + // but AutoHideChildren must be set to false because "this" still contains + // dock widgets that shall not be toggled hidden. + d->m_autoHideChildren = false; + hide(); + deleteLater(); + if (d->m_dockManager) { + d->m_dockManager->removeFloatingWidget(this); + d->m_dockManager->removeDockContainer(dockContainer()); + } +} + +void FloatingDockContainer::finishDragging() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + setWindowOpacity(1); + activateWindow(); + if (d->m_mouseEventHandler) { + d->m_mouseEventHandler->releaseMouse(); + d->m_mouseEventHandler = nullptr; + } +#endif + d->titleMouseReleaseEvent(); +} #ifdef Q_OS_MACOS bool FloatingDockContainer::event(QEvent *event) { - switch (d->m_draggingState) - { - case DraggingInactive: - { - // Normally we would check here, if the left mouse button is pressed. - // But from QT version 5.12.2 on the mouse events from - // QEvent::NonClientAreaMouseButtonPress return the wrong mouse button - // The event always returns Qt::RightButton even if the left button - // is clicked. - // It is really great to work around the whole NonClientMouseArea - // bugs + switch (d->m_draggingState) { + case DraggingInactive: { + // Normally we would check here, if the left mouse button is pressed. But from Qt version + // 5.12.2 on the mouse events from QEvent::NonClientAreaMouseButtonPress return the wrong + // mouse button. The event always returns Qt::RightButton even if the left button is + // clicked. It is really great to work around the whole NonClientMouseArea bugs. if (event->type() == QEvent::NonClientAreaMouseButtonPress - /*&& QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)*/) - { + /*&& QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)*/) { qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonPress" - << event->type(); + << event->type(); d->m_dragStartPos = pos(); d->setState(DraggingMousePressed); } - } - break; + } break; case DraggingMousePressed: - switch (event->type()) - { + switch (event->type()) { case QEvent::NonClientAreaMouseButtonDblClick: qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonDblClick"; d->setState(DraggingInactive); break; case QEvent::Resize: - // If the first event after the mouse press is a resize event, then - // the user resizes the window instead of dragging it around. - // But there is one exception. If the window is maximized, - // then dragging the window via title bar will cause the widget to - // leave the maximized state. This in turn will trigger a resize event. - // To know, if the resize event was triggered by user via moving a - // corner of the window frame or if it was caused by a windows state - // change, we check, if we are not in maximized state. + // If the first event after the mouse press is a resize event, then the user resizes + // the window instead of dragging it around. But there is one exception. If the window + // is maximized, then dragging the window via title bar will cause the widget to + // leave the maximized state. This in turn will trigger a resize event. To know, if the + // resize event was triggered by user via moving a corner of the window frame or if it + // was caused by a windows state change, we check, if we are not in maximized state. if (!isMaximized()) d->setState(DraggingInactive); break; @@ -882,8 +1000,7 @@ bool FloatingDockContainer::event(QEvent *event) break; case DraggingFloatingWidget: - if (event->type() == QEvent::NonClientAreaMouseButtonRelease) - { + if (event->type() == QEvent::NonClientAreaMouseButtonRelease) { qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonRelease"; d->titleMouseReleaseEvent(); } @@ -893,17 +1010,14 @@ bool FloatingDockContainer::event(QEvent *event) break; } -#if (ADS_DEBUG_LEVEL > 0) - qDebug() << Q_FUNC_INFO << event->type(); -#endif + qCInfo(adsLog) << Q_FUNC_INFO << event->type(); return QWidget::event(event); } void FloatingDockContainer::moveEvent(QMoveEvent *event) { QWidget::moveEvent(event); - switch (d->m_draggingState) - { + switch (d->m_draggingState) { case DraggingMousePressed: d->setState(DraggingFloatingWidget); d->updateDropOverlays(QCursor::pos()); @@ -911,15 +1025,85 @@ void FloatingDockContainer::moveEvent(QMoveEvent *event) case DraggingFloatingWidget: d->updateDropOverlays(QCursor::pos()); - // On macOS when hiding the DockAreaOverlay the application would set - // the main window as the active window for some reason. This fixes - // that by resetting the active window to the floating widget after - // updating the overlays. - QApplication::setActiveWindow(this); + // On macOS when hiding the DockAreaOverlay the application would set the main window as + // the active window for some reason. This fixes that by resetting the active window to + // the floating widget after updating the overlays. + activateWindow(); break; default: break; } } #endif + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +void FloatingDockContainer::onMaximizeRequest() +{ + if (windowState() == Qt::WindowMaximized) + showNormal(); + else + showMaximized(); +} + +void FloatingDockContainer::showNormal(bool fixGeometry) +{ + if (windowState() == Qt::WindowMaximized) { + QRect oldNormal = normalGeometry(); + Super::showNormal(); + if (fixGeometry) + setGeometry(oldNormal); + } + if (d->m_titleBar) + d->m_titleBar->setMaximizedIcon(false); +} + +void FloatingDockContainer::showMaximized() +{ + Super::showMaximized(); + if (d->m_titleBar) + d->m_titleBar->setMaximizedIcon(true); +} + +bool FloatingDockContainer::isMaximized() const +{ + return windowState() == Qt::WindowMaximized; +} + +void FloatingDockContainer::resizeEvent(QResizeEvent *event) +{ + d->m_isResizing = true; + Super::resizeEvent(event); +} + +void FloatingDockContainer::moveEvent(QMoveEvent *event) +{ + Super::moveEvent(event); + if (!d->m_isResizing && event->spontaneous() && d->m_mousePressed) { + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + } + d->m_isResizing = false; +} + +bool FloatingDockContainer::event(QEvent *e) +{ + bool result = Super::event(e); + switch (e->type()) { + case QEvent::WindowActivate: + d->m_mousePressed = false; + break; + case QEvent::WindowDeactivate: + d->m_mousePressed = true; + break; + default: + break; + } + return result; +} + +bool FloatingDockContainer::hasNativeTitleBar() +{ + return d->m_titleBar == nullptr; +} +#endif } // namespace ADS diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.h b/src/libs/advanceddockingsystem/floatingdockcontainer.h index e19ce428492..da2fc42138a 100644 --- a/src/libs/advanceddockingsystem/floatingdockcontainer.h +++ b/src/libs/advanceddockingsystem/floatingdockcontainer.h @@ -8,7 +8,7 @@ #include #include -#ifdef Q_OS_LINUX +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) using FloatingWidgetBaseType = QDockWidget; #else using FloatingWidgetBaseType = QWidget; @@ -33,8 +33,8 @@ class DockingStateReader; /** * Pure virtual interface for floating widgets. - * This interface is used for opaque and non-opaque undocking. If opaque - * undocking is used, the a real FloatingDockContainer widget will be created + * This interface is used for opaque and non-opaque undocking. If opaque undocking is used, + * the a real FloatingDockContainer widget will be created. */ class AbstractFloatingWidget { @@ -42,8 +42,7 @@ public: virtual ~AbstractFloatingWidget() = 0; /** * Starts floating. - * This function should get called typically from a mouse press event - * handler + * This function should get called typically from a mouse press event handler. */ virtual void startFloating(const QPoint &dragStartMousePos, const QSize &size, @@ -52,26 +51,23 @@ public: = 0; /** - * Moves the widget to a new position relative to the position given when - * startFloating() was called. - * This function should be called from a mouse mouve event handler to - * move the floating widget on mouse move events. + * Moves the widget to a new position relative to the position given when startFloating() + * was called. This function should be called from a mouse mouve event handler to move the + * floating widget on mouse move events. */ virtual void moveFloating() = 0; /** - * Tells the widget that to finish dragging if the mouse is released. - * This function should be called from a mouse release event handler - * to finish the dragging + * Tells the widget that to finish dragging if the mouse is released. This function should be + * called from a mouse release event handler to finish the dragging. */ virtual void finishDragging() = 0; }; /** - * This implements a floating widget that is a dock container that accepts - * docking of dock widgets like the main window and that can be docked into - * another dock container. - * Every floating window of the docking system is a FloatingDockContainer. + * This implements a floating widget that is a dock container that accepts docking of dock widgets + * like the main window and that can be docked into another dock container. Every floating window + * of the docking system is a FloatingDockContainer. */ class ADS_EXPORT FloatingDockContainer : public FloatingWidgetBaseType, public AbstractFloatingWidget @@ -96,9 +92,8 @@ private: protected: /** - * Starts floating at the given global position. - * Use moveToGlobalPos() to move the widget to a new position - * depending on the start position given in Pos parameter + * Starts floating at the given global position. Use moveToGlobalPos() to move the widget + * to a new position depending on the start position given in Pos parameter. */ void startFloating(const QPoint &dragStartMousePos, const QSize &size, @@ -106,7 +101,7 @@ protected: QWidget *mouseEventHandler) override; /** - * Call this function to start dragging the floating widget + * Call this function to start dragging the floating widget. */ void startDragging(const QPoint &dragStartMousePos, const QSize &size, @@ -116,14 +111,13 @@ protected: } /** - * Call this function if you explicitly want to signal that dragging has - * finished + * Call this function if you explicitly want to signal that dragging has finished. */ void finishDragging() override; /** - * Call this function if you just want to initialize the position - * and size of the floating widget + * Call this function if you just want to initialize the position and size of the + * floating widget. */ void initFloatingGeometry(const QPoint &dragStartMousePos, const QSize &size) { @@ -131,21 +125,20 @@ protected: } /** - * Moves the widget to a new position relative to the position given when - * startFloating() was called + * Moves the widget to a new position relative to the position given when startFloating() + * was called. */ void moveFloating() override; /** - * Restores the state from given stream. - * If Testing is true, the function only parses the data from the given - * stream but does not restore anything. You can use this check for - * faulty files before you start restoring the state + * Restores the state from given stream. If Testing is true, the function only parses the + * data from the given stream but does not restore anything. You can use this check for + * faulty files before you start restoring the state. */ bool restoreState(DockingStateReader &stream, bool testing); /** - * Call this function to update the window title + * Call this function to update the window title. */ void updateWindowTitle(); @@ -158,6 +151,10 @@ protected: // reimplements QWidget #ifdef Q_OS_MACOS virtual bool event(QEvent *event) override; virtual void moveEvent(QMoveEvent *event) override; +#elif defined(Q_OS_UNIX) + virtual bool event(QEvent *e) override; + virtual void moveEvent(QMoveEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; #endif #ifdef Q_OS_WIN @@ -202,26 +199,61 @@ public: bool isClosable() const; /** - * This function returns true, if this floating widget has only one single - * visible dock widget in a single visible dock area. - * The single dock widget is a real top level floating widget because no - * other widgets are docked. + * This function returns true, if this floating widget has only one single visible dock widget + * in a single visible dock area. The single dock widget is a real top level floating widget + * because no other widgets are docked. */ bool hasTopLevelDockWidget() const; /** - * This function returns the first dock widget in the first dock area. - * If the function hasSingleDockWidget() returns true, then this function - * returns this single dock widget. + * This function returns the first dock widget in the first dock area. If the function + * hasSingleDockWidget() returns true, then this function returns this single dock widget. */ DockWidget *topLevelDockWidget() const; /** - * This function returns a list of all dock widget in this floating widget. - * This is a simple convenience function that simply calls the dockWidgets() - * function of the internal container widget. + * This function returns a list of all dock widget in this floating widget. This is a simple + * convenience function that simply calls the dockWidgets() function of the internal + * container widget. */ QList dockWidgets() const; + + /** + * This function hides the floating bar instantely and delete it later. + */ + void hideAndDeleteLater(); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + /** + * This is a function that responds to FloatingWidgetTitleBar::maximizeRequest() + * Maximize or normalize the container size. + */ + void onMaximizeRequest(); + + /** + * Normalize (Unmaximize) the window. + * fixGeometry parameter fixes a "bug" in QT where immediately after calling showNormal + * geometry is not set properly. + * Set this true when moving the window immediately after normalizing. + */ + void showNormal(bool fixGeometry = false); + + /** + * Maximizes the window. + */ + void showMaximized(); + + /** + * Returns if the window is currently maximized or not. + */ + bool isMaximized() const; + + /** + * Returns true if the floating widget has a native titlebar or false if + * the floating widget has a QWidget based title bar + */ + bool hasNativeTitleBar(); +#endif }; // class FloatingDockContainer } // namespace ADS diff --git a/src/libs/advanceddockingsystem/floatingdragpreview.cpp b/src/libs/advanceddockingsystem/floatingdragpreview.cpp index 3111e46c6bd..6bbaf9ee22f 100644 --- a/src/libs/advanceddockingsystem/floatingdragpreview.cpp +++ b/src/libs/advanceddockingsystem/floatingdragpreview.cpp @@ -4,6 +4,8 @@ #include "floatingdragpreview.h" #include "ads_globals_p.h" +#include "ads_globals.h" +#include "autohidedockcontainer.h" #include "dockareawidget.h" #include "dockcontainerwidget.h" #include "dockmanager.h" @@ -20,311 +22,405 @@ #include -namespace ADS +namespace ADS { + +/** + * Private data class (pimpl) + */ +class FloatingDragPreviewPrivate { +public: + FloatingDragPreview *q; + QWidget *m_content = nullptr; + DockWidget::DockWidgetFeatures m_contentFeatures; + DockAreaWidget *m_contentSourceArea = nullptr; + QPoint m_dragStartMousePosition; + DockManager *m_dockManager = nullptr; + DockContainerWidget *m_dropContainer = nullptr; + bool m_hidden = false; + QPixmap m_contentPreviewPixmap; + bool m_canceled = false; + /** - * Private data class (pimpl) + * Private data constructor */ - class FloatingDragPreviewPrivate + FloatingDragPreviewPrivate(FloatingDragPreview *parent); + void updateDropOverlays(const QPoint &globalPosition); + + void setHidden(bool value) { - public: - FloatingDragPreview *q; - QWidget *m_content = nullptr; - DockAreaWidget *m_contentSourceArea = nullptr; - QPoint m_dragStartMousePosition; - DockManager *m_dockManager = nullptr; - DockContainerWidget *m_dropContainer = nullptr; - bool m_hidden = false; - QPixmap m_contentPreviewPixmap; - bool m_canceled = false; - - /** - * Private data constructor - */ - FloatingDragPreviewPrivate(FloatingDragPreview *parent); - void updateDropOverlays(const QPoint &globalPosition); - - void setHidden(bool value) - { - m_hidden = value; - q->update(); - } - - /** - * Cancel dragging and emit the draggingCanceled event - */ - void cancelDragging() - { - m_canceled = true; - emit q->draggingCanceled(); - m_dockManager->containerOverlay()->hideOverlay(); - m_dockManager->dockAreaOverlay()->hideOverlay(); - q->close(); - } - - /** - * Creates the real floating widget in case the mouse is released outside - * outside of any drop area - */ - void createFloatingWidget(); - }; // class FloatingDragPreviewPrivate - - void FloatingDragPreviewPrivate::updateDropOverlays(const QPoint &globalPosition) - { - if (!q->isVisible() || !m_dockManager) - return; - - auto containers = m_dockManager->dockContainers(); - DockContainerWidget *topContainer = nullptr; - for (auto containerWidget : containers) { - if (!containerWidget->isVisible()) - continue; - - const QPoint mappedPosition = containerWidget->mapFromGlobal(globalPosition); - if (containerWidget->rect().contains(mappedPosition)) { - if (!topContainer || containerWidget->isInFrontOf(topContainer)) - topContainer = containerWidget; - } - } - - m_dropContainer = topContainer; - auto containerOverlay = m_dockManager->containerOverlay(); - auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); - auto dockDropArea = dockAreaOverlay->dropAreaUnderCursor(); - auto containerDropArea = containerOverlay->dropAreaUnderCursor(); - - if (!topContainer) { - containerOverlay->hideOverlay(); - dockAreaOverlay->hideOverlay(); - if (DockManager::testConfigFlag(DockManager::DragPreviewIsDynamic)) - setHidden(false); - - return; - } - - const int visibleDockAreas = topContainer->visibleDockAreaCount(); - containerOverlay->setAllowedAreas(visibleDockAreas > 1 ? OuterDockAreas : AllDockAreas); - auto dockArea = topContainer->dockAreaAt(globalPosition); - if (dockArea && dockArea->isVisible() && visibleDockAreas >= 0 - && dockArea != m_contentSourceArea) { - dockAreaOverlay->enableDropPreview(true); - dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea - : dockArea->allowedAreas()); - DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); - - // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in the - // title bar. If the ContainerArea is valid then we ignore the dock area of the - // dockAreaOverlay() and disable the drop preview - if ((area == CenterDockWidgetArea) && (containerDropArea != InvalidDockWidgetArea)) { - dockAreaOverlay->enableDropPreview(false); - containerOverlay->enableDropPreview(true); - } else { - containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); - } - containerOverlay->showOverlay(topContainer); - } else { - dockAreaOverlay->hideOverlay(); - // If there is only one single visible dock area in a container, then - // it does not make sense to show a dock overlay because the dock area - // would be removed and inserted at the same position - if (visibleDockAreas <= 1) - containerOverlay->hideOverlay(); - else - containerOverlay->showOverlay(topContainer); - - if (dockArea == m_contentSourceArea && InvalidDockWidgetArea == containerDropArea) - m_dropContainer = nullptr; - } - - if (DockManager::testConfigFlag(DockManager::DragPreviewIsDynamic)) { - setHidden(dockDropArea != InvalidDockWidgetArea - || containerDropArea != InvalidDockWidgetArea); - } + m_hidden = value; + q->update(); } - FloatingDragPreviewPrivate::FloatingDragPreviewPrivate(FloatingDragPreview *parent) - : q(parent) - {} + /** + * Cancel dragging and emit the draggingCanceled event. + */ + void cancelDragging() + { + m_canceled = true; + emit q->draggingCanceled(); + m_dockManager->containerOverlay()->hideOverlay(); + m_dockManager->dockAreaOverlay()->hideOverlay(); + q->close(); + } - void FloatingDragPreviewPrivate::createFloatingWidget() + /** + * Creates the real floating widget in case the mouse is released outside outside of any + * drop area. + */ + void createFloatingWidget(); + + /** + * Returns true, if the content is floatable + */ + bool isContentFloatable() const + { + return m_contentFeatures.testFlag(DockWidget::DockWidgetFloatable); + } + + /** + * Returns true, if the content is pinnable + */ + bool isContentPinnable() const + { + return m_contentFeatures.testFlag(DockWidget::DockWidgetPinnable); + } + + /** + * Returns the content features + */ + DockWidget::DockWidgetFeatures contentFeatures() const { DockWidget *dockWidget = qobject_cast(m_content); + if (dockWidget) + return dockWidget->features(); + DockAreaWidget *dockArea = qobject_cast(m_content); + if (dockArea) + return dockArea->features(); - FloatingDockContainer *floatingWidget = nullptr; + return DockWidget::DockWidgetFeatures(); + } +}; // class FloatingDragPreviewPrivate - if (dockWidget && dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) - floatingWidget = new FloatingDockContainer(dockWidget); - else if (dockArea && dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) - floatingWidget = new FloatingDockContainer(dockArea); +void FloatingDragPreviewPrivate::updateDropOverlays(const QPoint &globalPosition) +{ + if (!q->isVisible() || !m_dockManager) + return; - if (floatingWidget) { - floatingWidget->setGeometry(q->geometry()); - floatingWidget->show(); - if (!DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) { - QApplication::processEvents(); - int frameHeight = floatingWidget->frameGeometry().height() - floatingWidget->geometry().height(); - QRect fixedGeometry = q->geometry(); - fixedGeometry.adjust(0, frameHeight, 0, 0); - floatingWidget->setGeometry(fixedGeometry); - } + auto containers = m_dockManager->dockContainers(); + DockContainerWidget *topContainer = nullptr; + for (auto containerWidget : containers) { + if (!containerWidget->isVisible()) + continue; + + const QPoint mappedPosition = containerWidget->mapFromGlobal(globalPosition); + if (containerWidget->rect().contains(mappedPosition)) { + if (!topContainer || containerWidget->isInFrontOf(topContainer)) + topContainer = containerWidget; } } - FloatingDragPreview::FloatingDragPreview(QWidget *content, QWidget *parent) - : QWidget(parent) - , d(new FloatingDragPreviewPrivate(this)) - { - d->m_content = content; - setAttribute(Qt::WA_DeleteOnClose); - if (DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) { - setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); + m_dropContainer = topContainer; + auto containerOverlay = m_dockManager->containerOverlay(); + auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); + + if (!topContainer) { + containerOverlay->hideOverlay(); + dockAreaOverlay->hideOverlay(); + if (DockManager::testConfigFlag(DockManager::DragPreviewIsDynamic)) + setHidden(false); + + return; + } + + auto dockDropArea = dockAreaOverlay->dropAreaUnderCursor(); + auto containerDropArea = containerOverlay->dropAreaUnderCursor(); + + int visibleDockAreas = topContainer->visibleDockAreaCount(); + + // Include the overlay widget we're dragging as a visible widget + auto dockAreaWidget = qobject_cast(m_content); + if (dockAreaWidget && dockAreaWidget->isAutoHide()) + visibleDockAreas++; + + DockWidgetAreas allowedContainerAreas = (visibleDockAreas > 1) ? OuterDockAreas : AllDockAreas; + + auto dockArea = topContainer->dockAreaAt(globalPosition); + // If the dock container contains only one single DockArea, then we need + // to respect the allowed areas - only the center area is relevant here because + // all other allowed areas are from the container + if (visibleDockAreas == 1 && dockArea) + allowedContainerAreas.setFlag(CenterDockWidgetArea, + dockArea->allowedAreas().testFlag(CenterDockWidgetArea)); + + if (isContentPinnable()) + allowedContainerAreas |= AutoHideDockAreas; + + containerOverlay->setAllowedAreas(allowedContainerAreas); + containerOverlay->enableDropPreview(containerDropArea != InvalidDockWidgetArea); + + if (dockArea && dockArea->isVisible() && visibleDockAreas >= 0 + && dockArea != m_contentSourceArea) { + dockAreaOverlay->enableDropPreview(true); + dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea + : dockArea->allowedAreas()); + DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); + + // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in the + // title bar. If the ContainerArea is valid then we ignore the dock area of the + // dockAreaOverlay() and disable the drop preview. + if ((area == CenterDockWidgetArea) && (containerDropArea != InvalidDockWidgetArea)) { + dockAreaOverlay->enableDropPreview(false); + containerOverlay->enableDropPreview(true); } else { - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - setAttribute(Qt::WA_NoSystemBackground); - setAttribute(Qt::WA_TranslucentBackground); + containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); } + containerOverlay->showOverlay(topContainer); + } else { + dockAreaOverlay->hideOverlay(); + // If there is only one single visible dock area in a container, then it does not make + // sense to show a dock overlay because the dock area would be removed and inserted at + // the same position. Only auto hide area is allowed. + if (visibleDockAreas == 1) + containerOverlay->setAllowedAreas(AutoHideDockAreas); - if (Utils::HostOsInfo::isLinuxHost()) { - auto flags = windowFlags(); - flags |= Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint; - setWindowFlags(flags); - } + containerOverlay->showOverlay(topContainer); - setWindowOpacity(0.6); - - // Create a static image of the widget that should get undocked - // This is like some kind preview image like it is uses in drag and drop operations - if (DockManager::testConfigFlag(DockManager::DragPreviewShowsContentPixmap)) { - d->m_contentPreviewPixmap = QPixmap(content->size()); - content->render(&d->m_contentPreviewPixmap); - } - connect(qApp, - &QApplication::applicationStateChanged, - this, - &FloatingDragPreview::onApplicationStateChanged); - // The only safe way to receive escape key presses is to install an event - // filter for the application object - QApplication::instance()->installEventFilter(this); + if (dockArea == m_contentSourceArea && InvalidDockWidgetArea == containerDropArea) + m_dropContainer = nullptr; } - FloatingDragPreview::FloatingDragPreview(DockWidget *content) - : FloatingDragPreview(static_cast(content), - content->dockManager()) // TODO static_cast? - { - d->m_dockManager = content->dockManager(); - if (content->dockAreaWidget()->openDockWidgetsCount() == 1) - d->m_contentSourceArea = content->dockAreaWidget(); + if (DockManager::testConfigFlag(DockManager::DragPreviewIsDynamic)) + setHidden(dockDropArea != InvalidDockWidgetArea + || containerDropArea != InvalidDockWidgetArea); +} - setWindowTitle(content->windowTitle()); - } +FloatingDragPreviewPrivate::FloatingDragPreviewPrivate(FloatingDragPreview *parent) + : q(parent) +{} - FloatingDragPreview::FloatingDragPreview(DockAreaWidget *content) - : FloatingDragPreview(static_cast(content), - content->dockManager()) // TODO static_cast? - { - d->m_dockManager = content->dockManager(); - d->m_contentSourceArea = content; - setWindowTitle(content->currentDockWidget()->windowTitle()); - } +void FloatingDragPreviewPrivate::createFloatingWidget() +{ + DockWidget *dockWidget = qobject_cast(m_content); + DockAreaWidget *dockArea = qobject_cast(m_content); - FloatingDragPreview::~FloatingDragPreview() { delete d; } + FloatingDockContainer *floatingWidget = nullptr; - void FloatingDragPreview::moveFloating() - { - const int borderSize = (frameSize().width() - size().width()) / 2; - const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition - - QPoint(borderSize, 0); - move(moveToPos); - d->updateDropOverlays(QCursor::pos()); - } + if (dockWidget && dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) + floatingWidget = new FloatingDockContainer(dockWidget); + else if (dockArea && dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) + floatingWidget = new FloatingDockContainer(dockArea); - void FloatingDragPreview::startFloating(const QPoint &dragStartMousePos, - const QSize &size, - eDragState dragState, - QWidget *mouseEventHandler) - { - Q_UNUSED(mouseEventHandler) - Q_UNUSED(dragState) - resize(size); - d->m_dragStartMousePosition = dragStartMousePos; - moveFloating(); - show(); - } - - void FloatingDragPreview::finishDragging() - { - qCInfo(adsLog) << Q_FUNC_INFO; - auto dockDropArea = d->m_dockManager->dockAreaOverlay()->visibleDropAreaUnderCursor(); - auto containerDropArea = d->m_dockManager->containerOverlay()->visibleDropAreaUnderCursor(); - if (!d->m_dropContainer) { - d->createFloatingWidget(); - } else if (dockDropArea != InvalidDockWidgetArea) { - d->m_dropContainer->dropWidget(d->m_content, dockDropArea, d->m_dropContainer->dockAreaAt(QCursor::pos())); - } else if (containerDropArea != InvalidDockWidgetArea) { - // If there is only one single dock area, and we drop into the center - // then we tabify the dropped widget into the only visible dock area - if (d->m_dropContainer->visibleDockAreaCount() <= 1 && CenterDockWidgetArea == containerDropArea) - d->m_dropContainer->dropWidget(d->m_content, containerDropArea, d->m_dropContainer->dockAreaAt(QCursor::pos())); - else - d->m_dropContainer->dropWidget(d->m_content, containerDropArea, nullptr); - } else { - d->createFloatingWidget(); - } - - this->close(); - d->m_dockManager->containerOverlay()->hideOverlay(); - d->m_dockManager->dockAreaOverlay()->hideOverlay(); - } - - void FloatingDragPreview::paintEvent(QPaintEvent *event) - { - Q_UNUSED(event) - if (d->m_hidden) - return; - - QPainter painter(this); - if (DockManager::testConfigFlag(DockManager::DragPreviewShowsContentPixmap)) - painter.drawPixmap(QPoint(0, 0), d->m_contentPreviewPixmap); - - // If we do not have a window frame then we paint a QRubberBand like frameless window + if (floatingWidget) { + floatingWidget->setGeometry(q->geometry()); + floatingWidget->show(); if (!DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) { - QColor color = palette().color(QPalette::Active, QPalette::Highlight); - QPen pen = painter.pen(); - pen.setColor(color.darker(120)); - pen.setStyle(Qt::SolidLine); - pen.setWidth(1); - pen.setCosmetic(true); - painter.setPen(pen); - color = color.lighter(130); - color.setAlpha(64); - painter.setBrush(color); - painter.drawRect(rect().adjusted(0, 0, -1, -1)); + QApplication::processEvents(); + int frameHeight = floatingWidget->frameGeometry().height() + - floatingWidget->geometry().height(); + QRect fixedGeometry = q->geometry(); + fixedGeometry.adjust(0, frameHeight, 0, 0); + floatingWidget->setGeometry(fixedGeometry); } } +} - void FloatingDragPreview::onApplicationStateChanged(Qt::ApplicationState state) - { - if (state != Qt::ApplicationActive) { - disconnect(qApp, - &QApplication::applicationStateChanged, - this, - &FloatingDragPreview::onApplicationStateChanged); +FloatingDragPreview::FloatingDragPreview(QWidget *content, QWidget *parent) + : QWidget(parent) + , d(new FloatingDragPreviewPrivate(this)) +{ + d->m_content = content; + d->m_contentFeatures = d->contentFeatures(); + setAttribute(Qt::WA_DeleteOnClose); + if (DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) { + setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); + } else { + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); + } + + if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost()) { + auto flags = windowFlags(); + flags |= Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint; + setWindowFlags(flags); + } + + setWindowOpacity(0.6); + + // Create a static image of the widget that should get undocked. This is like some kind preview + // image like it is uses in drag and drop operations. + if (DockManager::testConfigFlag(DockManager::DragPreviewShowsContentPixmap)) { + d->m_contentPreviewPixmap = QPixmap(content->size()); + content->render(&d->m_contentPreviewPixmap); + } + connect(qApp, + &QApplication::applicationStateChanged, + this, + &FloatingDragPreview::onApplicationStateChanged); + // The only safe way to receive escape key presses is to install an event filter for the + // application object. + QApplication::instance()->installEventFilter(this); +} + +FloatingDragPreview::FloatingDragPreview(DockWidget *content) + : FloatingDragPreview(static_cast(content), + content->dockManager()) // TODO static_cast? +{ + d->m_dockManager = content->dockManager(); + if (content->dockAreaWidget()->openDockWidgetsCount() == 1) + d->m_contentSourceArea = content->dockAreaWidget(); + + setWindowTitle(content->windowTitle()); +} + +FloatingDragPreview::FloatingDragPreview(DockAreaWidget *content) + : FloatingDragPreview(static_cast(content), + content->dockManager()) // TODO static_cast? +{ + d->m_dockManager = content->dockManager(); + d->m_contentSourceArea = content; + setWindowTitle(content->currentDockWidget()->windowTitle()); +} + +FloatingDragPreview::~FloatingDragPreview() +{ + delete d; +} + +void FloatingDragPreview::moveFloating() +{ + const int borderSize = (frameSize().width() - size().width()) / 2; + const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition - QPoint(borderSize, 0); + move(moveToPos); + d->updateDropOverlays(QCursor::pos()); +} + +void FloatingDragPreview::startFloating(const QPoint &dragStartMousePos, + const QSize &size, + eDragState dragState, + QWidget *mouseEventHandler) +{ + Q_UNUSED(mouseEventHandler) + Q_UNUSED(dragState) + resize(size); + d->m_dragStartMousePosition = dragStartMousePos; + moveFloating(); + show(); +} + +void FloatingDragPreview::finishDragging() +{ + qCInfo(adsLog) << Q_FUNC_INFO; + auto dockDropArea = d->m_dockManager->dockAreaOverlay()->visibleDropAreaUnderCursor(); + auto containerDropArea = d->m_dockManager->containerOverlay()->visibleDropAreaUnderCursor(); + bool validDropArea = (dockDropArea != InvalidDockWidgetArea) + || (containerDropArea != InvalidDockWidgetArea); + + // Non floatable auto hide widgets should stay in its current auto hide state if they are + // dragged into a floating window. + if (validDropArea || d->isContentFloatable()) + cleanupAutoHideContainerWidget(containerDropArea); + + if (!d->m_dropContainer) { + d->createFloatingWidget(); + } else if (dockDropArea != InvalidDockWidgetArea) { + d->m_dropContainer->dropWidget(d->m_content, + dockDropArea, + d->m_dropContainer->dockAreaAt(QCursor::pos()), + d->m_dockManager->dockAreaOverlay()->tabIndexUnderCursor()); + } else if (containerDropArea != InvalidDockWidgetArea) { + // If there is only one single dock area, and we drop into the center then we tabify the + // dropped widget into the only visible dock area. + if (d->m_dropContainer->visibleDockAreaCount() <= 1 + && CenterDockWidgetArea == containerDropArea) + d->m_dropContainer + ->dropWidget(d->m_content, + containerDropArea, + d->m_dropContainer->dockAreaAt(QCursor::pos()), + d->m_dockManager->containerOverlay()->tabIndexUnderCursor()); + else + d->m_dropContainer->dropWidget(d->m_content, containerDropArea, nullptr); + } else { + d->createFloatingWidget(); + } + + close(); + d->m_dockManager->containerOverlay()->hideOverlay(); + d->m_dockManager->dockAreaOverlay()->hideOverlay(); +} + +void FloatingDragPreview::cleanupAutoHideContainerWidget(DockWidgetArea containerDropArea) +{ + auto droppedDockWidget = qobject_cast(d->m_content); + auto droppedArea = qobject_cast(d->m_content); + + auto autoHideContainer = droppedDockWidget ? droppedDockWidget->autoHideDockContainer() + : droppedArea->autoHideDockContainer(); + + if (!autoHideContainer) + return; + + // If the dropped widget is already an auto hide widget and if it is moved to a new side bar + // location in the same container, then we do not need to cleanup. + if (internal::isSideBarArea(containerDropArea) + && (d->m_dropContainer == autoHideContainer->dockContainer())) + return; + + autoHideContainer->cleanupAndDelete(); +} + +void FloatingDragPreview::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + if (d->m_hidden) + return; + + QPainter painter(this); + painter.setOpacity(0.6); + if (DockManager::testConfigFlag(DockManager::DragPreviewShowsContentPixmap)) + painter.drawPixmap(QPoint(0, 0), d->m_contentPreviewPixmap); + + // If we do not have a window frame then we paint a QRubberBand like frameless window + if (!DockManager::testConfigFlag(DockManager::DragPreviewHasWindowFrame)) { + QColor color = palette().color(QPalette::Active, QPalette::Highlight); + QPen pen = painter.pen(); + pen.setColor(color.darker(120)); + pen.setStyle(Qt::SolidLine); + pen.setWidth(1); + pen.setCosmetic(true); + painter.setPen(pen); + color = color.lighter(130); + color.setAlpha(64); + painter.setBrush(color); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); + } +} + +void FloatingDragPreview::onApplicationStateChanged(Qt::ApplicationState state) +{ + if (state != Qt::ApplicationActive) { + disconnect(qApp, + &QApplication::applicationStateChanged, + this, + &FloatingDragPreview::onApplicationStateChanged); + d->cancelDragging(); + } +} + +bool FloatingDragPreview::eventFilter(QObject *watched, QEvent *event) +{ + if (!d->m_canceled && event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + watched->removeEventFilter(this); d->cancelDragging(); } } - bool FloatingDragPreview::eventFilter(QObject *watched, QEvent *event) - { - if (!d->m_canceled && event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Escape) { - watched->removeEventFilter(this); - d->cancelDragging(); - } - } - - return false; - } + return false; +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/floatingdragpreview.h b/src/libs/advanceddockingsystem/floatingdragpreview.h index ed578da993a..d3c5f85f7a5 100644 --- a/src/libs/advanceddockingsystem/floatingdragpreview.h +++ b/src/libs/advanceddockingsystem/floatingdragpreview.h @@ -86,6 +86,11 @@ public: // implements AbstractFloatingWidget */ void finishDragging() override; + /** + * Cleanup auto hide container if the dragged widget has one. + */ + void cleanupAutoHideContainerWidget(DockWidgetArea containerDropArea); + signals: /** * This signal is emitted, if dragging has been canceled by escape key diff --git a/src/libs/advanceddockingsystem/iconprovider.cpp b/src/libs/advanceddockingsystem/iconprovider.cpp index b1db84c56ff..b8ff67e2552 100644 --- a/src/libs/advanceddockingsystem/iconprovider.cpp +++ b/src/libs/advanceddockingsystem/iconprovider.cpp @@ -6,44 +6,45 @@ #include namespace ADS { + +/** + * Private data class (pimpl) + */ +struct IconProviderPrivate +{ + IconProvider *q; + QVector m_userIcons{IconCount, QIcon()}; + /** - * Private data class (pimpl) + * Private data constructor */ - struct IconProviderPrivate - { - IconProvider *q; - QVector m_userIcons{IconCount, QIcon()}; + IconProviderPrivate(IconProvider *parent); +}; +// struct IconProviderPrivate - /** - * Private data constructor - */ - IconProviderPrivate(IconProvider *parent); - }; - // struct IconProviderPrivate +IconProviderPrivate::IconProviderPrivate(IconProvider *parent) + : q(parent) +{} - IconProviderPrivate::IconProviderPrivate(IconProvider *parent) - : q(parent) - {} +IconProvider::IconProvider() + : d(new IconProviderPrivate(this)) +{} - IconProvider::IconProvider() - : d(new IconProviderPrivate(this)) - {} +IconProvider::~IconProvider() +{ + delete d; +} - IconProvider::~IconProvider() - { - delete d; - } +QIcon IconProvider::customIcon(eIcon iconId) const +{ + Q_ASSERT(iconId < d->m_userIcons.size()); + return d->m_userIcons[iconId]; +} - QIcon IconProvider::customIcon(eIcon iconId) const - { - Q_ASSERT(iconId < d->m_userIcons.size()); - return d->m_userIcons[iconId]; - } - - void IconProvider::registerCustomIcon(eIcon iconId, const QIcon &icon) - { - Q_ASSERT(iconId < d->m_userIcons.size()); - d->m_userIcons[iconId] = icon; - } +void IconProvider::registerCustomIcon(eIcon iconId, const QIcon &icon) +{ + Q_ASSERT(iconId < d->m_userIcons.size()); + d->m_userIcons[iconId] = icon; +} } // namespace ADS diff --git a/src/libs/advanceddockingsystem/iconprovider.h b/src/libs/advanceddockingsystem/iconprovider.h index ec72121ae9a..2675e17b9ee 100644 --- a/src/libs/advanceddockingsystem/iconprovider.h +++ b/src/libs/advanceddockingsystem/iconprovider.h @@ -12,8 +12,7 @@ namespace ADS { struct IconProviderPrivate; /** - * This object provides all icons that are required by the advanced docking - * system. + * This object provides all icons that are required by the advanced docking system. * The IconProvider enables the user to register custom icons in case using * stylesheets is not an option. */ diff --git a/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp index 0201ba25995..699a0086fb9 100644 --- a/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp +++ b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp @@ -4,8 +4,10 @@ #include "floatingwidgettitlebar.h" #include "ads_globals.h" +#include "dockmanager.h" #include "elidinglabel.h" #include "floatingdockcontainer.h" +#include "iconprovider.h" #include #include @@ -20,6 +22,7 @@ namespace ADS { using TabLabelType = ElidingLabel; using CloseButtonType = QToolButton; +using MaximizeButtonType = QToolButton; /** * @brief Private data class of public interface CFloatingWidgetTitleBar @@ -31,8 +34,12 @@ public: QLabel *m_iconLabel = nullptr; TabLabelType *m_titleLabel = nullptr; CloseButtonType *m_closeButton = nullptr; + MaximizeButtonType *m_maximizeButton = nullptr; FloatingDockContainer *m_floatingWidget = nullptr; eDragState m_dragState = DraggingInactive; + QIcon m_maximizeIcon; + QIcon m_normalIcon; + bool m_maximized = false; FloatingWidgetTitleBarPrivate(FloatingWidgetTitleBar *parent) : q(parent) @@ -46,15 +53,18 @@ public: void FloatingWidgetTitleBarPrivate::createLayout() { + // Title label m_titleLabel = new TabLabelType(); m_titleLabel->setElideMode(Qt::ElideRight); m_titleLabel->setText("DockWidget->windowTitle()"); m_titleLabel->setObjectName("floatingTitleLabel"); m_titleLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + // Close button m_closeButton = new CloseButtonType(); m_closeButton->setObjectName("floatingTitleCloseButton"); m_closeButton->setAutoRaise(true); + internal::setButtonIcon(m_closeButton, QStyle::SP_TitleBarCloseButton, ADS::FloatingWidgetCloseIcon); @@ -68,6 +78,21 @@ void FloatingWidgetTitleBarPrivate::createLayout() q, &FloatingWidgetTitleBar::closeRequested); + // Maximize button + m_maximizeButton = new MaximizeButtonType(); + m_maximizeButton->setObjectName("floatingTitleMaxButton"); + m_maximizeButton->setAutoRaise(true); + + m_maximizeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_maximizeButton->setIconSize(QSize(11, 11)); + m_maximizeButton->setFixedSize(QSize(17, 17)); + m_maximizeButton->setVisible(true); + m_maximizeButton->setFocusPolicy(Qt::NoFocus); + QObject::connect(m_maximizeButton, + &QPushButton::clicked, + q, + &FloatingWidgetTitleBar::maximizeRequested); + QFontMetrics fontMetrics(m_titleLabel->font()); int spacing = qRound(fontMetrics.height() / 4.0); @@ -78,6 +103,7 @@ void FloatingWidgetTitleBarPrivate::createLayout() q->setLayout(layout); layout->addWidget(m_titleLabel, 1); layout->addSpacing(spacing); + layout->addWidget(m_maximizeButton); layout->addWidget(m_closeButton); layout->addSpacing(1); layout->setAlignment(Qt::AlignCenter); @@ -91,6 +117,26 @@ FloatingWidgetTitleBar::FloatingWidgetTitleBar(FloatingDockContainer *parent) { d->m_floatingWidget = parent; d->createLayout(); + + d->m_normalIcon = DockManager::iconProvider().customIcon(ADS::FloatingWidgetNormalIcon); + if (d->m_normalIcon.isNull()) { + auto normalPixmap = style()->standardPixmap(QStyle::SP_TitleBarNormalButton, + 0, + d->m_maximizeButton); + d->m_normalIcon.addPixmap(normalPixmap, QIcon::Normal); + d->m_normalIcon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), + QIcon::Disabled); + } + + d->m_maximizeIcon = DockManager::iconProvider().customIcon(ADS::FloatingWidgetMaximizeIcon); + if (d->m_maximizeIcon.isNull()) { + auto maxPixmap = this->style()->standardPixmap(QStyle::SP_TitleBarMaxButton, + 0, + d->m_maximizeButton); + d->m_maximizeIcon.addPixmap(maxPixmap, QIcon::Normal); + d->m_maximizeIcon.addPixmap(internal::createTransparentPixmap(maxPixmap, 0.25), + QIcon::Disabled); + } } FloatingWidgetTitleBar::~FloatingWidgetTitleBar() @@ -125,8 +171,11 @@ void FloatingWidgetTitleBar::mouseMoveEvent(QMouseEvent *event) return; } - // move floating window + // Move floating window if (DraggingFloatingWidget == d->m_dragState) { + if (d->m_floatingWidget->isMaximized()) + d->m_floatingWidget->showNormal(true); + d->m_floatingWidget->moveFloating(); Super::mouseMoveEvent(event); return; @@ -134,6 +183,16 @@ void FloatingWidgetTitleBar::mouseMoveEvent(QMouseEvent *event) Super::mouseMoveEvent(event); } +void FloatingWidgetTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) { + emit maximizeRequested(); + event->accept(); + } else { + QWidget::mouseDoubleClickEvent(event); + } +} + void FloatingWidgetTitleBar::enableCloseButton(bool enable) { d->m_closeButton->setEnabled(enable); @@ -149,4 +208,13 @@ void FloatingWidgetTitleBar::updateStyle() internal::repolishStyle(this, internal::RepolishDirectChildren); } +void FloatingWidgetTitleBar::setMaximizedIcon(bool maximized) +{ + d->m_maximized = maximized; + if (maximized) + d->m_maximizeButton->setIcon(d->m_normalIcon); + else + d->m_maximizeButton->setIcon(d->m_maximizeIcon); +} + } // namespace ADS diff --git a/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h index 0913fabd240..4b945bbe3b9 100644 --- a/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h +++ b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h @@ -4,6 +4,7 @@ #pragma once #include +#include namespace ADS { @@ -11,11 +12,10 @@ class FloatingDockContainer; class FloatingWidgetTitleBarPrivate; /** - * Titlebar for floating widgets to capture non client are mouse events. + * Title bar for floating widgets to capture non client area mouse events. * Linux does not support NonClientArea mouse events like - * QEvent::NonClientAreaMouseButtonPress. Because these events are required - * for the docking system to work properly, we use our own titlebar here to - * capture the required mouse events. + * QEvent::NonClientAreaMouseButtonPress. Because these events are required for the docking system + * to work properly, we use our own titlebar here to capture the required mouse events. */ class FloatingWidgetTitleBar : public QFrame { @@ -27,6 +27,7 @@ protected: void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; public: using Super = QWidget; @@ -52,11 +53,21 @@ public: */ void updateStyle(); + /** + * Change the maximize button icon according to current windows state + */ + void setMaximizedIcon(bool maximized); + signals: /** * This signal is emitted, if the close button is clicked. */ void closeRequested(); + + /** + * This signal is emitted, if the maximize button is clicked. + */ + void maximizeRequested(); }; } // namespace ADS diff --git a/src/libs/advanceddockingsystem/pushbutton.cpp b/src/libs/advanceddockingsystem/pushbutton.cpp new file mode 100644 index 00000000000..ddc4b83ca88 --- /dev/null +++ b/src/libs/advanceddockingsystem/pushbutton.cpp @@ -0,0 +1,55 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#include "pushbutton.h" + +#include +#include +#include +#include + +namespace ADS { + +QSize PushButton::sizeHint() const +{ + QSize sh = QPushButton::sizeHint(); + + if (m_orientation != PushButton::Horizontal) + sh.transpose(); + + return sh; +} + +PushButton::Orientation PushButton::buttonOrientation() const +{ + return m_orientation; +} + +void PushButton::setButtonOrientation(Orientation orientation) +{ + m_orientation = orientation; + updateGeometry(); +} + +void PushButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QStylePainter painter(this); + QStyleOptionButton option; + initStyleOption(&option); + + if (m_orientation == PushButton::VerticalTopToBottom) { + painter.rotate(90); + painter.translate(0, -1 * width()); + option.rect = option.rect.transposed(); + } else if (m_orientation == PushButton::VerticalBottomToTop) { + painter.rotate(-90); + painter.translate(-1 * height(), 0); + option.rect = option.rect.transposed(); + } + + painter.drawControl(QStyle::CE_PushButton, option); +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/pushbutton.h b/src/libs/advanceddockingsystem/pushbutton.h new file mode 100644 index 00000000000..b3260a633d8 --- /dev/null +++ b/src/libs/advanceddockingsystem/pushbutton.h @@ -0,0 +1,47 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include "ads_globals.h" + +#include + +namespace ADS { + +/** + * ADS specific push button class with orientation support + */ +class ADS_EXPORT PushButton : public QPushButton +{ + Q_OBJECT + +public: + enum Orientation { + Horizontal, + VerticalTopToBottom, + VerticalBottomToTop + }; + + using QPushButton::QPushButton; + + virtual QSize sizeHint() const override; + + /** + * Returns the current orientation + */ + Orientation buttonOrientation() const; + + /** + * Set the orientation of this button + */ + void setButtonOrientation(Orientation orientation); + +protected: + virtual void paintEvent(QPaintEvent *event) override; + +private: + Orientation m_orientation = Horizontal; +}; // class PushButton + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/resizehandle.cpp b/src/libs/advanceddockingsystem/resizehandle.cpp new file mode 100644 index 00000000000..9cdccdeaada --- /dev/null +++ b/src/libs/advanceddockingsystem/resizehandle.cpp @@ -0,0 +1,269 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#include "resizehandle.h" + +#include +#include +#include +#include +#include +#include + +namespace ADS { + +/** + * Private data class of CResizeHandle class (pimpl) + */ +class ResizeHandlePrivate +{ +public: + ResizeHandle *q = nullptr; + Qt::Edge m_handlePosition = Qt::LeftEdge; + QWidget *m_target = nullptr; + int m_mouseOffset = 0; + bool m_pressed = false; + int m_minSize = 0; + int m_maxSize = 1; + QPointer m_rubberBand; + bool m_opaqueResize = false; + int m_handleWidth = 4; + + /** + * Private data constructor + */ + ResizeHandlePrivate(ResizeHandle *parent); + + /** + * Pick position component from pos depending on orientation + */ + int pick(const QPoint &pos) const + { + return q->orientation() == Qt::Horizontal ? pos.x() : pos.y(); + } + + /** + * Returns true, if orientation is horizontal + */ + bool isHorizontal() const { return q->orientation() == Qt::Horizontal; } + + /** + * Set rubberband position + */ + void setRubberBand(int pos); + + /** + * Calculates the resize position and geometry + */ + void doResizing(QMouseEvent *event, bool forceResize = false); +}; +// class ResizeHandlePrivate + +ResizeHandlePrivate::ResizeHandlePrivate(ResizeHandle *parent) + : q(parent) +{} + +void ResizeHandlePrivate::setRubberBand(int pos) +{ + if (!m_rubberBand) + m_rubberBand = new QRubberBand(QRubberBand::Line, m_target->parentWidget()); + + auto geometry = q->geometry(); + auto topLeft = m_target->mapTo(m_target->parentWidget(), geometry.topLeft()); + switch (m_handlePosition) { + case Qt::LeftEdge: + case Qt::RightEdge: + topLeft.rx() += pos; + break; + case Qt::TopEdge: + case Qt::BottomEdge: + topLeft.ry() += pos; + break; + } + + geometry.moveTopLeft(topLeft); + m_rubberBand->setGeometry(geometry); + m_rubberBand->show(); +} + +void ResizeHandlePrivate::doResizing(QMouseEvent *event, bool forceResize) +{ + int pos = pick(event->pos()) - m_mouseOffset; + auto oldGeometry = m_target->geometry(); + auto newGeometry = oldGeometry; + switch (m_handlePosition) { + case Qt::LeftEdge: { + newGeometry.adjust(pos, 0, 0, 0); + int size = qBound(m_minSize, newGeometry.width(), m_maxSize); + pos += (newGeometry.width() - size); + newGeometry.setWidth(size); + newGeometry.moveTopRight(oldGeometry.topRight()); + } break; + + case Qt::RightEdge: { + newGeometry.adjust(0, 0, pos, 0); + int size = qBound(m_minSize, newGeometry.width(), m_maxSize); + pos -= (newGeometry.width() - size); + newGeometry.setWidth(size); + } break; + + case Qt::TopEdge: { + newGeometry.adjust(0, pos, 0, 0); + int size = qBound(m_minSize, newGeometry.height(), m_maxSize); + pos += (newGeometry.height() - size); + newGeometry.setHeight(size); + newGeometry.moveBottomLeft(oldGeometry.bottomLeft()); + } break; + + case Qt::BottomEdge: { + newGeometry.adjust(0, 0, 0, pos); + int size = qBound(m_minSize, newGeometry.height(), m_maxSize); + pos -= (newGeometry.height() - size); + newGeometry.setHeight(size); + } break; + } + + if (q->opaqueResize() || forceResize) { + m_target->setGeometry(newGeometry); + } else { + setRubberBand(pos); + } +} + +ResizeHandle::ResizeHandle(Qt::Edge handlePosition, QWidget *parent) + : Super(parent) + , d(new ResizeHandlePrivate(this)) +{ + d->m_target = parent; + setMinResizeSize(48); + setHandlePosition(handlePosition); +} + +ResizeHandle::~ResizeHandle() +{ + delete d; +} + +void ResizeHandle::mouseMoveEvent(QMouseEvent *event) +{ + if (!(event->buttons() & Qt::LeftButton)) + return; + + d->doResizing(event); +} + +void ResizeHandle::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + d->m_mouseOffset = d->pick(event->pos()); + d->m_pressed = true; + update(); + } +} + +void ResizeHandle::mouseReleaseEvent(QMouseEvent *event) +{ + if (!opaqueResize() && event->button() == Qt::LeftButton) { + if (d->m_rubberBand) + d->m_rubberBand->deleteLater(); + + d->doResizing(event, true); + } + + if (event->button() == Qt::LeftButton) { + d->m_pressed = false; + update(); + } +} + +void ResizeHandle::setHandlePosition(Qt::Edge handlePosition) +{ + d->m_handlePosition = handlePosition; + switch (d->m_handlePosition) { + case Qt::LeftEdge: // fall through + case Qt::RightEdge: + setCursor(Qt::SizeHorCursor); + break; + + case Qt::TopEdge: // fall through + case Qt::BottomEdge: + setCursor(Qt::SizeVerCursor); + break; + } + + setMaxResizeSize(d->isHorizontal() ? parentWidget()->height() : parentWidget()->width()); + if (!d->isHorizontal()) { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + } else { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + } +} + +Qt::Edge ResizeHandle::handlePostion() const +{ + return d->m_handlePosition; +} + +Qt::Orientation ResizeHandle::orientation() const +{ + switch (d->m_handlePosition) { + case Qt::LeftEdge: // fall through + case Qt::RightEdge: + return Qt::Horizontal; + + case Qt::TopEdge: // fall through + case Qt::BottomEdge: + return Qt::Vertical; + } + + return Qt::Horizontal; +} + +QSize ResizeHandle::sizeHint() const +{ + QSize result; + switch (d->m_handlePosition) { + case Qt::LeftEdge: // fall through + case Qt::RightEdge: + result = QSize(d->m_handleWidth, d->m_target->height()); + break; + + case Qt::TopEdge: // fall through + case Qt::BottomEdge: + result = QSize(d->m_target->width(), d->m_handleWidth); + break; + } + + return result; +} + +bool ResizeHandle::isResizing() const +{ + return d->m_pressed; +} + +void ResizeHandle::setMinResizeSize(int minSize) +{ + d->m_minSize = minSize; +} + +void ResizeHandle::setMaxResizeSize(int maxSize) +{ + d->m_maxSize = maxSize; +} + +void ResizeHandle::setOpaqueResize(bool opaque) +{ + if (d->m_opaqueResize == opaque) + return; + + d->m_opaqueResize = opaque; + emit opaqueResizeChanged(); +} + +bool ResizeHandle::opaqueResize() const +{ + return d->m_opaqueResize; +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/resizehandle.h b/src/libs/advanceddockingsystem/resizehandle.h new file mode 100644 index 00000000000..540a946e071 --- /dev/null +++ b/src/libs/advanceddockingsystem/resizehandle.h @@ -0,0 +1,99 @@ +// Copyright (C) 2020 Uwe Kindler +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include "ads_globals.h" + +#include + +namespace ADS { + +class ResizeHandlePrivate; + +/** + * Resize handle for resizing its parent widget + */ +class ADS_EXPORT ResizeHandle : public QFrame +{ + Q_OBJECT + Q_DISABLE_COPY(ResizeHandle) + Q_PROPERTY(bool opaqueResize READ opaqueResize WRITE setOpaqueResize NOTIFY opaqueResizeChanged) + +private: + ResizeHandlePrivate *d; ///< private data (pimpl) + friend class ResizeHandlePrivate; + +protected: + void mouseMoveEvent(QMouseEvent *) override; + void mousePressEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; + +public: + using Super = QFrame; + + /** + * Default Constructor + */ + ResizeHandle(Qt::Edge handlePosition, QWidget *parent); + + /** + * Virtual Destructor + */ + virtual ~ResizeHandle(); + + /** + * Sets the handle position + */ + void setHandlePosition(Qt::Edge handlePosition); + + /** + * Returns the handle position + */ + Qt::Edge handlePostion() const; + + /** + * Returns the orientation of this resize handle + */ + Qt::Orientation orientation() const; + + /** + * Returns the size hint + */ + QSize sizeHint() const override; + + /** + * Returns true, if resizing is active + */ + bool isResizing() const; + + /** + * Sets the minimum size for the widget that is going to be resized. + * The resize handle will not resize the target widget to a size smaller + * than this value + */ + void setMinResizeSize(int minSize); + + /** + * Sets the maximum size for the widget that is going to be resized + * The resize handle will not resize the target widget to a size bigger + * than this value + */ + void setMaxResizeSize(int maxSize); + + /** + * Enable / disable opaque resizing + */ + void setOpaqueResize(bool opaque = true); + + /** + * Returns true if widgets are resized dynamically (opaquely) while + * interactively moving the resize handle. Otherwise returns false. + */ + bool opaqueResize() const; + +signals: + void opaqueResizeChanged(); +}; // class ResizeHandle + +} // namespace ADS diff --git a/src/libs/qmlpuppetcommunication/types/enumeration.h b/src/libs/qmlpuppetcommunication/types/enumeration.h index 2a09233df4a..f3127ab9075 100644 --- a/src/libs/qmlpuppetcommunication/types/enumeration.h +++ b/src/libs/qmlpuppetcommunication/types/enumeration.h @@ -15,76 +15,80 @@ namespace QmlDesigner { using EnumerationName = QByteArray; +using EnumerationNameView = QByteArrayView; class Enumeration { - friend bool operator ==(const Enumeration &first, const Enumeration &second); - friend bool operator <(const Enumeration &first, const Enumeration &second); - friend QDataStream &operator>>(QDataStream &in, Enumeration &enumeration); - public: Enumeration() = default; - Enumeration(const EnumerationName &enumerationName) - : m_enumerationName(enumerationName) - { - } + Enumeration(EnumerationName enumerationName) + : m_enumerationName{std::move(enumerationName)} + {} + + Enumeration(const char *text) + : m_enumerationName{text, static_cast(std::strlen(text))} + {} + Enumeration(const QString &enumerationName) : m_enumerationName(enumerationName.toUtf8()) + {} + + Enumeration(const EnumerationName &scope, const EnumerationName &name) { - } - Enumeration(const QString &scope, const QString &name) - { - QString enumerationString = scope + QLatin1Char('.') + name; - m_enumerationName = enumerationString.toUtf8(); + m_enumerationName.reserve(scope.size() + 1 + name.size()); + m_enumerationName.append(scope); + m_enumerationName.append(1); + m_enumerationName.append(name); } - EnumerationName scope() const + EnumerationNameView scope() const { - return m_enumerationName.split('.').constFirst(); + auto found = std::find(m_enumerationName.begin(), m_enumerationName.end(), '.'); + return {m_enumerationName.begin(), found}; } - EnumerationName name() const + + EnumerationNameView toScope() const { return scope().toByteArray(); } + + EnumerationNameView name() const { - return m_enumerationName.split('.').last(); + auto found = std::find(m_enumerationName.begin(), m_enumerationName.end(), '.'); + if (found != m_enumerationName.end()) + return {std::next(found), m_enumerationName.end()}; + + return {m_enumerationName.end(), m_enumerationName.end()}; } - EnumerationName toEnumerationName() const + + EnumerationName toName() const { return name().toByteArray(); } + + EnumerationName toEnumerationName() const { return m_enumerationName; } + + QString toString() const { return QString::fromUtf8(m_enumerationName); } + QString nameToString() const { return QString::fromUtf8(name()); } + + friend bool operator==(const Enumeration &first, const Enumeration &second) { - return m_enumerationName; + return first.m_enumerationName == second.m_enumerationName; } - QString toString() const + + friend bool operator<(const Enumeration &first, const Enumeration &second) { - return QString::fromUtf8(m_enumerationName); + return first.m_enumerationName < second.m_enumerationName; } - QString nameToString() const + + friend QDataStream &operator<<(QDataStream &out, const Enumeration &enumeration) { - return QString::fromUtf8(name()); + return out << enumeration.m_enumerationName; + } + + friend QDataStream &operator>>(QDataStream &in, Enumeration &enumeration) + { + return in >> enumeration.m_enumerationName; } private: EnumerationName m_enumerationName; }; -inline QDataStream &operator<<(QDataStream &out, const Enumeration &enumeration){ - out << enumeration.toEnumerationName(); - return out; -} - -inline QDataStream &operator>>(QDataStream &in, Enumeration &enumeration) -{ - in >> enumeration.m_enumerationName; - return in; -} - - -inline bool operator==(const Enumeration &first, const Enumeration &second) -{ - return first.m_enumerationName == second.m_enumerationName; -} - -inline bool operator<(const Enumeration &first, const Enumeration &second) -{ - return first.m_enumerationName < second.m_enumerationName; -} - inline QDebug operator <<(QDebug debug, const Enumeration &enumeration) { debug.nospace() << "Enumeration(" diff --git a/src/libs/sqlite/sqliteids.h b/src/libs/sqlite/sqliteids.h index e7a4f9fbae2..6296f84718a 100644 --- a/src/libs/sqlite/sqliteids.h +++ b/src/libs/sqlite/sqliteids.h @@ -19,8 +19,6 @@ public: constexpr explicit BasicId() = default; - constexpr BasicId(const char *) = delete; - static constexpr BasicId create(InternalIntegerType idNumber) { BasicId id; diff --git a/src/libs/utils/smallstring.h b/src/libs/utils/smallstring.h index f4cd6b749f4..bd4f708fed1 100644 --- a/src/libs/utils/smallstring.h +++ b/src/libs/utils/smallstring.h @@ -108,13 +108,9 @@ public: static_assert(!std::is_array::value, "Input type is array and not char pointer!"); } - BasicSmallString(const QString &qString) - : BasicSmallString(BasicSmallString::fromQString(qString)) - {} + BasicSmallString(const QString &qString) { append(qString); } - BasicSmallString(const QStringView qStringView) - : BasicSmallString(BasicSmallString::fromQStringView(qStringView)) - {} + BasicSmallString(const QStringView qStringView) { append(qStringView); } BasicSmallString(const QByteArray &qByteArray) : BasicSmallString(qByteArray.constData(), qByteArray.size()) @@ -127,9 +123,7 @@ public: { } - BasicSmallString(const std::wstring &wstring) - : BasicSmallString(BasicSmallString::fromQStringView(wstring)) - {} + BasicSmallString(const std::wstring &wstring) { append(wstring); } template(encoder.requiredSpace(oldSize)); + size_type maximumRequiredSize = static_cast(encoder.requiredSpace(string.size())); char *newEnd = nullptr; if (maximumRequiredSize > temporaryArraySize) { diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index b6d9f10bfb6..405ad5c00a3 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -82,7 +82,6 @@ if(TARGET QmlDesignerCore) add_feature_info("ProjectStorage" ${USE_PROJECTSTORAGE} "") endif() - extend_qtc_library(QmlDesignerCore CONDITION ENABLE_COMPILE_WARNING_AS_ERROR PROPERTIES COMPILE_WARNING_AS_ERROR ON @@ -186,23 +185,40 @@ extend_qtc_library(QmlDesignerCore extend_qtc_library(QmlDesignerCore SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/include SOURCES - abstractproperty.h abstractview.h + basetexteditmodifier.h + bytearraymodifier.h + componenttextmodifier.h + forwardview.h + itemlibraryinfo.h + metainforeader.h + model.h + nodehints.h + plaintexteditmodifier.h + nodeinstanceview.h + propertyparser.h + rewriterview.h + subcomponentmanager.h + textmodifier.h +) + +extend_qtc_library(QmlDesignerCore + SOURCES_PROPERTIES SKIP_AUTOGEN ON + SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/include + SOURCES + abstractproperty.h anchorline.h annotation.h asynchronousexplicitimagecache.h asynchronousimagecache.h auxiliarydata.h auxiliarydataproperties.h - basetexteditmodifier.h bindingproperty.h - componenttextmodifier.h customnotifications.h documentmessage.h enumerationmetainfo.h exception.h externaldependenciesinterface.h - forwardview.h imagecacheauxiliarydata.h import.h invalidargumentexception.h @@ -214,33 +230,26 @@ extend_qtc_library(QmlDesignerCore invalidqmlsourceexception.h invalidreparentingexception.h invalidslideindexexception.h - itemlibraryinfo.h mathutils.h metainfo.h - metainforeader.h - model.h modelfwd.h modelmerger.h modelnode.h modelnodepositionstorage.h nodeabstractproperty.h - nodehints.h nodeinstance.h - nodeinstanceview.h nodelistproperty.h nodemetainfo.h nodeproperty.h notimplementedexception.h - plaintexteditmodifier.h propertycontainer.h propertymetainfo.h propertynode.h - propertyparser.h qmlanchors.h qmlchangeset.h qmlconnections.h - qmldesignercorelib_global.h qmldesignercorelib_exports.h + qmldesignercorelib_global.h qmlitemnode.h qmlmodelnodefacade.h qmlobjectnode.h @@ -248,14 +257,11 @@ extend_qtc_library(QmlDesignerCore qmltimeline.h qmltimelinekeyframegroup.h removebasestateexception.h - rewriterview.h rewritingexception.h signalhandlerproperty.h stringutils.h stylesheetmerger.h - subcomponentmanager.h synchronousimagecache.h - textmodifier.h variantproperty.h ) @@ -382,7 +388,7 @@ extend_qtc_library(QmlDesignerCore extend_qtc_library(QmlDesignerCore SOURCES_PREFIX designercore/projectstorage PUBLIC_INCLUDES designercore/projectstorage - SOURCES_PROPERTIES SKIP_AUTOMOC ON + SOURCES_PROPERTIES SKIP_AUTOGEN ON SOURCES commontypecache.h directorypathcompressor.h @@ -407,7 +413,7 @@ extend_qtc_library(QmlDesignerCore projectstorage.cpp projectstorage.h sourcepath.h sourcepathcache.h - sourcepathcache.h + sourcepathcacheinterface.h sourcepathcachetypes.h sourcepathview.h storagecache.h diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp index 7b0604c19a8..2cc6606efa1 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp @@ -4,12 +4,13 @@ #include "componentexporter.h" #include "exportnotification.h" -#include "designdocument.h" -#include "nodemetainfo.h" -#include "qmldesignerplugin.h" -#include "rewriterview.h" -#include "qmlitemnode.h" -#include "qmlobjectnode.h" +#include +#include +#include +#include +#include +#include +#include #include #include @@ -340,12 +341,11 @@ QString AssetExporter::componentUuid(const ModelNode &instance) const // Returns the UUID of the component's root node // Empty string is returned if the node is not an instance of a component within // the project. - NodeMetaInfo metaInfo = instance.metaInfo(); - if (!metaInfo.isValid()) - return {}; - const QString path = metaInfo.componentFileName(); - if (m_componentUuidCache.contains(path)) - return m_componentUuidCache[path]; + if (instance) { + const QString path = ModelUtils::componentFilePath(instance); + return m_componentUuidCache.value(path); + } + return {}; } diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp index 7ec9841198a..701e9fdd244 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp @@ -28,7 +28,7 @@ static QByteArrayList populateLineage(const QmlDesigner::ModelNode &node) if (!node.isValid() || node.type().isEmpty()) return {}; - for (auto &info : node.metaInfo().superClasses()) + for (auto &info : node.metaInfo().prototypes()) lineage.append(info.typeName()); return lineage; diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp index 8c8d0911793..10e8181d8d5 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp @@ -91,10 +91,7 @@ void BindingEditor::setBackendValue(const QVariant &backendValue) const ModelNode node = propertyEditorValue->modelNode(); if (node.isValid()) { - m_backendValueTypeName = node.metaInfo() - .property(propertyEditorValue->name()) - .propertyType() - .simplifiedTypeName(); + m_backendValueType = node.metaInfo().property(propertyEditorValue->name()).propertyType(); QString nodeId = node.id(); if (nodeId.isEmpty()) @@ -102,9 +99,11 @@ void BindingEditor::setBackendValue(const QVariant &backendValue) m_targetName = nodeId + "." + propertyEditorValue->name(); - if (m_backendValueTypeName == "alias" || m_backendValueTypeName == "unknown") + if (!m_backendValueType || m_backendValueType.isAlias()) { if (QmlObjectNode::isValidQmlObjectNode(node)) - m_backendValueTypeName = QmlObjectNode(node).instanceType(propertyEditorValue->name()); + m_backendValueType = node.model()->metaInfo( + QmlObjectNode(node).instanceType(propertyEditorValue->name())); + } } emit backendValueChanged(); @@ -135,7 +134,7 @@ void BindingEditor::setStateModelNode(const QVariant &stateModelNode) m_modelNode = m_stateModelNode.value(); if (m_modelNode.isValid()) - m_backendValueTypeName = "bool"; + m_backendValueType = m_modelNode.model()->boolMetaInfo(); emit stateModelNodeChanged(); } @@ -153,9 +152,9 @@ void BindingEditor::setModelNode(const ModelNode &modelNode) m_modelNode = modelNode; } -void BindingEditor::setBackendValueTypeName(const TypeName &backendValueTypeName) +void BindingEditor::setBackendValueType(const NodeMetaInfo &backendValueType) { - m_backendValueTypeName = backendValueTypeName; + m_backendValueType = backendValueType; emit backendValueChanged(); } @@ -165,65 +164,80 @@ void BindingEditor::setTargetName(const QString &target) m_targetName = target; } +namespace { +template +bool isType(const Tuple &types, const TypeName &compareType) +{ + return std::apply([&](const auto &...type) { return ((type == compareType) || ...); }, types); +} + +template +bool isType(const TypeName &first, const TypeName &second, const Tuple &...types) +{ + return ((types == first) || ...) && ((types == second) || ...); +} + +bool compareTypes(const NodeMetaInfo &sourceType, const NodeMetaInfo &targetType) +{ + if constexpr (useProjectStorage()) { + return targetType.isVariant() || sourceType.isVariant() || targetType == sourceType + || (targetType.isNumber() && sourceType.isNumber()) + || (targetType.isColor() && sourceType.isColor()) + || (targetType.isString() && sourceType.isString()); + } else { + const TypeName source = sourceType.simplifiedTypeName(); + const TypeName target = targetType.simplifiedTypeName(); + + static constexpr auto variantTypes = std::make_tuple("alias", "unknown", "variant", "var"); + + return isType(variantTypes, target) || isType(variantTypes, source) + || targetType == sourceType || isType(target, source, "double", "real", "int") + || isType(target, source, "QColor", "color") + || isType(target, source, "QString", "string"); + } +} +} // namespace + void BindingEditor::prepareBindings() { - if (!m_modelNode.isValid() || m_backendValueTypeName.isEmpty()) + if (!m_modelNode.isValid() || !m_backendValueType) { return; + } const QList allNodes = m_modelNode.view()->allModelNodes(); QList bindings; - const QVarLengthArray variantTypes = {"alias", "unknown", "variant", "var"}; - const QVarLengthArray numericTypes = {"double", "real", "int"}; - const QVarLengthArray colorTypes = {"QColor", "color"}; - const QVarLengthArray stringTypes = {"QString", "string"}; - - auto isVariant = [&variantTypes](const TypeName &compareType) { - return variantTypes.contains(compareType); - }; - auto isNumeric = [&numericTypes](const TypeName &compareType) { - return numericTypes.contains(compareType); - }; - auto isColor = [&colorTypes](const TypeName &compareType) { - return colorTypes.contains(compareType); - }; - auto isString = [&stringTypes](const TypeName &compareType) { - return stringTypes.contains(compareType); - }; - - auto compareTypes = [&](const TypeName &targetType, const TypeName &sourceType) { - return isVariant(targetType) || isVariant(sourceType) || (targetType == sourceType) - || (isNumeric(targetType) && isNumeric(sourceType)) - || (isColor(targetType) && isColor(sourceType)) - || (isString(targetType) && isString(sourceType)); - }; - for (const auto &objnode : allNodes) { BindingEditorDialog::BindingOption binding; for (const auto &property : objnode.metaInfo().properties()) { - const TypeName &propertyTypeName = property.propertyType().simplifiedTypeName(); + const auto &propertyType = property.propertyType(); - if (compareTypes(m_backendValueTypeName, propertyTypeName)) + if (compareTypes(m_backendValueType, propertyType)) { binding.properties.append(QString::fromUtf8(property.name())); + } } //dynamic properties: for (const BindingProperty &bindingProperty : objnode.bindingProperties()) { if (bindingProperty.isValid()) { if (bindingProperty.isDynamic()) { - const TypeName dynamicTypeName = bindingProperty.dynamicTypeName(); - if (compareTypes(m_backendValueTypeName, dynamicTypeName)) + auto model = bindingProperty.model(); + const auto dynamicType = model->metaInfo(bindingProperty.dynamicTypeName()); + if (compareTypes(m_backendValueType, dynamicType)) { binding.properties.append(QString::fromUtf8(bindingProperty.name())); + } } } } for (const VariantProperty &variantProperty : objnode.variantProperties()) { if (variantProperty.isValid()) { if (variantProperty.isDynamic()) { - const TypeName dynamicTypeName = variantProperty.dynamicTypeName(); - if (compareTypes(m_backendValueTypeName, dynamicTypeName)) + auto model = variantProperty.model(); + const auto dynamicType = model->metaInfo(variantProperty.dynamicTypeName()); + if (compareTypes(m_backendValueType, dynamicType)) { binding.properties.append(QString::fromUtf8(variantProperty.name())); + } } } } @@ -244,10 +258,11 @@ void BindingEditor::prepareBindings() BindingEditorDialog::BindingOption binding; for (const auto &property : metaInfo.properties()) { - const TypeName propertyTypeName = property.propertyType().typeName(); + const auto propertyType = property.propertyType(); - if (compareTypes(m_backendValueTypeName, propertyTypeName)) + if (compareTypes(m_backendValueType, propertyType)) { binding.properties.append(QString::fromUtf8(property.name())); + } } if (!binding.properties.isEmpty()) { @@ -260,15 +275,24 @@ void BindingEditor::prepareBindings() } if (!bindings.isEmpty() && !m_dialog.isNull()) - m_dialog->setAllBindings(bindings, m_backendValueTypeName); + m_dialog->setAllBindings(bindings, m_backendValueType); } void BindingEditor::updateWindowName() { - if (!m_dialog.isNull() && !m_backendValueTypeName.isEmpty()) { - const QString targetString = " [" - + (m_targetName.isEmpty() ? QString() : (m_targetName + ": ")) - + QString::fromUtf8(m_backendValueTypeName) + "]"; + if (!m_dialog.isNull() && m_backendValueType) { + QString targetString; + if constexpr (useProjectStorage()) { + auto exportedTypeNames = m_backendValueType.exportedTypeNamesForSourceId( + m_modelNode.model()->fileUrlSourceId()); + if (exportedTypeNames.size()) { + targetString = " [" + (m_targetName.isEmpty() ? QString() : (m_targetName + ": ")) + + exportedTypeNames.front().name.toQString() + "]"; + } + } else { + targetString = " [" + (m_targetName.isEmpty() ? QString() : (m_targetName + ": ")) + + QString::fromUtf8(m_backendValueType.simplifiedTypeName()) + "]"; + } m_dialog->setWindowTitle(m_dialog->defaultTitle() + targetString); } diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h index 59ce036c18c..52aa0884f69 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h @@ -49,7 +49,7 @@ public: //3. modelnode + backend value type name + optional target name void setModelNode(const ModelNode &modelNode); - void setBackendValueTypeName(const TypeName &backendValueTypeName); + void setBackendValueType(const NodeMetaInfo &backendValueType); void setTargetName(const QString &target); Q_INVOKABLE void prepareBindings(); @@ -77,7 +77,7 @@ private: QVariant m_modelNodeBackend; QVariant m_stateModelNode; QmlDesigner::ModelNode m_modelNode; - TypeName m_backendValueTypeName; + NodeMetaInfo m_backendValueType; QString m_targetName; }; diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.cpp index 0cb178603f3..ac5dd615337 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.cpp @@ -79,7 +79,7 @@ void BindingEditorDialog::adjustProperties() m_comboBoxProperty->setCurrentText(property); } -void BindingEditorDialog::setAllBindings(const QList &bindings, const TypeName &type) +void BindingEditorDialog::setAllBindings(const QList &bindings, const NodeMetaInfo &type) { m_lock = true; @@ -118,7 +118,7 @@ void BindingEditorDialog::setupComboBoxes() void BindingEditorDialog::setupCheckBox() { - const bool visible = (m_type == "bool"); + const bool visible = m_type.isBool(); m_checkBoxNot->setVisible(visible); } diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.h b/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.h index ba14a0ee38d..f2995f0a1bf 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.h +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.h @@ -6,6 +6,8 @@ #include +#include + QT_BEGIN_NAMESPACE class QComboBox; class QCheckBox; @@ -35,7 +37,7 @@ public: void adjustProperties() override; - void setAllBindings(const QList &bindings, const TypeName &type); + void setAllBindings(const QList &bindings, const NodeMetaInfo &type); private: void setupUIComponents(); @@ -53,7 +55,7 @@ private: QCheckBox *m_checkBoxNot = nullptr; QList m_bindings; - TypeName m_type; + NodeMetaInfo m_type; }; } diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.cpp index 7ff4e6f8722..75976fd7210 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.cpp @@ -3,13 +3,14 @@ #include "modelnodecontextmenu_helper.h" -#include -#include -#include #include +#include +#include +#include #include -#include #include +#include +#include #include @@ -76,10 +77,11 @@ bool selectionHasSameParent(const SelectionContext &selectionState) bool fileComponentExists(const ModelNode &modelNode) { - if (!modelNode.metaInfo().isFileComponent()) + if (!modelNode.metaInfo().isFileComponent()) { return true; + } - const QString fileName = modelNode.metaInfo().componentFileName(); + const QString fileName = ModelUtils::componentFilePath(modelNode); if (fileName.contains("qml/QtQuick")) return false; @@ -97,7 +99,8 @@ bool selectionIsImported3DAsset(const SelectionContext &selectionState) { ModelNode node = selectionState.currentSingleSelectedNode(); if (selectionState.view() && node.hasMetaInfo()) { - QString fileName = node.metaInfo().componentFileName(); // absolute path + QString fileName = ModelUtils::componentFilePath(node); + if (fileName.isEmpty()) { // Node is not a file component, so we have to check if the current doc itself is fileName = node.model()->fileUrl().toLocalFile(); diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index a565ae03fcf..fba2f25a05c 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -187,6 +187,7 @@ public: materialPreviewEnvironment, materialPreviewModel, material_medium, + maxBar_small, mergeCells, merge_small, minus, @@ -200,6 +201,7 @@ public: move_medium, newMaterial, nextFile_large, + normalBar_small, openLink, openMaterialBrowser, orientation, diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 29bee73620a..fc30ab99f9d 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -480,6 +480,11 @@ void ViewManager::exportAsImage() d->formEditorView.exportAsImage(); } +QImage ViewManager::takeFormEditorScreenshot() +{ + return d->formEditorView.takeFormEditorScreenshot(); +} + void ViewManager::reformatFileUsingTextEditorView() { d->textEditorView.reformatFile(); diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.h b/src/plugins/qmldesigner/components/componentcore/viewmanager.h index 4201065f99f..a3cacbe907e 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.h +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.h @@ -74,6 +74,7 @@ public: const AbstractView *view() const; void exportAsImage(); + QImage takeFormEditorScreenshot(); void reformatFileUsingTextEditorView(); QWidgetAction *componentViewAction() const; diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp index 191900d5e9d..2b2025ed83a 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp @@ -13,14 +13,16 @@ #include #include +#include + #include #include namespace QmlDesigner { BindingModel::BindingModel(ConnectionView *parent) - : QStandardItemModel(parent) - , m_connectionView(parent) + : QStandardItemModel(parent), m_connectionView(parent), + m_delegate(new BindingModelBackendDelegate(this)) { connect(this, &QStandardItemModel::dataChanged, this, &BindingModel::handleDataChanged); } @@ -40,6 +42,31 @@ void BindingModel::resetModel() endResetModel(); } +void BindingModel::add() +{ + addBindingForCurrentNode(); +} + +void BindingModel::remove(int row) +{ + deleteBindindByRow(row); +} + +int BindingModel::currentIndex() const +{ + return m_currentIndex; +} + +void BindingModel::setCurrentIndex(int i) +{ + if (m_currentIndex == i) + return; + + m_currentIndex = i; + + emit currentIndexChanged(); +} + void BindingModel::bindingChanged(const BindingProperty &bindingProperty) { m_handleDataChanged = false; @@ -232,6 +259,19 @@ void BindingModel::addBindingForCurrentNode() } } +static void updateDisplayRoles(QStandardItem *item, const BindingProperty &property) +{ + item->setData(property.parentModelNode().id(), BindingModel::TargetNameRole); + item->setData(property.name(), BindingModel::TargetPropertyNameRole); + + const AbstractProperty source = property.resolveToProperty(); + + if (source.isValid()) { + item->setData(source.parentModelNode().id(), BindingModel::SourceNameRole); + item->setData(source.name(), BindingModel::SourcePropertyNameRole); + } +} + void BindingModel::addBindingProperty(const BindingProperty &property) { QStandardItem *idItem; @@ -248,6 +288,7 @@ void BindingModel::addBindingProperty(const BindingProperty &property) QList items; items.append(idItem); + updateDisplayRoles(idItem, property); items.append(targetPropertyNameItem); QString sourceNodeName; @@ -267,6 +308,10 @@ void BindingModel::updateBindingProperty(int rowNumber) BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); if (bindingProperty.isValid()) { + QStandardItem *idItem = item(rowNumber, 0); + if (idItem) + updateDisplayRoles(idItem, bindingProperty); + QString targetPropertyName = QString::fromUtf8(bindingProperty.name()); updateDisplayRole(rowNumber, TargetPropertyNameRow, targetPropertyName); QString sourceNodeName; @@ -355,6 +400,7 @@ void BindingModel::updateCustomData(QStandardItem *item, const BindingProperty & { item->setData(bindingProperty.parentModelNode().internalId(), Qt::UserRole + 1); item->setData(bindingProperty.name(), Qt::UserRole + 2); + updateDisplayRoles(item, bindingProperty); } int BindingModel::findRowForBinding(const BindingProperty &bindingProperty) @@ -369,6 +415,8 @@ int BindingModel::findRowForBinding(const BindingProperty &bindingProperty) bool BindingModel::getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty) { + //TODO reimplement using existing helper functions + //### todo we assume no expressions yet const QString expression = bindingProperty.expression(); @@ -438,4 +486,159 @@ void BindingModel::handleException() resetModel(); } +QHash BindingModel::roleNames() const +{ + static QHash roleNames{{TargetNameRole, "target"}, + {TargetPropertyNameRole, "targetProperty"}, + {SourceNameRole, "source"}, + {SourcePropertyNameRole, "sourceProperty"}}; + + return roleNames; +} + +BindingModelBackendDelegate *BindingModel::delegate() const +{ + return m_delegate; +} + +BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel *parent) : QObject(parent) +{ + connect(&m_sourceNode, &StudioQmlComboBoxBackend::activated, this, [this]() { + handleSourceNodeChanged(); + }); + + connect(&m_sourceNodeProperty, &StudioQmlComboBoxBackend::activated, this, [this]() { + handleSourcePropertyChanged(); + }); +} + +int BindingModelBackendDelegate::currentRow() const +{ + return m_currentRow; +} + +void BindingModelBackendDelegate::setCurrentRow(int i) +{ + // See BindingDelegate::createEditor + + if (m_currentRow == i) + return; + + m_currentRow = i; + + //setup + + BindingModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + + QString idLabel = bindingProperty.parentModelNode().id(); + if (idLabel.isEmpty()) + idLabel = bindingProperty.parentModelNode().simplifiedTypeName(); + + m_targetNode = idLabel; + + emit targetNodeChanged(); + + m_property.setModel(model->possibleTargetProperties(bindingProperty)); + m_property.setCurrentText(QString::fromUtf8(bindingProperty.name())); + + QStringList sourceNodes; + + for (const ModelNode &modelNode : model->connectionView()->allModelNodes()) { + if (!modelNode.id().isEmpty()) + sourceNodes.append(modelNode.id()); + } + + std::sort(sourceNodes.begin(), sourceNodes.end()); + m_sourceNode.setModel(sourceNodes); + + QString sourceNodeName; + QString sourcePropertyName; + model->getExpressionStrings(bindingProperty, &sourceNodeName, &sourcePropertyName); + + m_sourceNode.setCurrentText(sourceNodeName); + + m_sourceNodeProperty.setModel(model->possibleSourceProperties(bindingProperty)); + m_sourceNodeProperty.setCurrentText(sourcePropertyName); +} + +void BindingModelBackendDelegate::handleException() +{ + QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); + //reset +} + +QString BindingModelBackendDelegate::targetNode() const +{ + return m_targetNode; +} + +StudioQmlComboBoxBackend *BindingModelBackendDelegate::property() +{ + return &m_property; +} + +StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceNode() +{ + return &m_sourceNode; +} + +StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty() +{ + return &m_sourceNodeProperty; +} + +void BindingModelBackendDelegate::handleSourceNodeChanged() +{ + BindingModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->connectionView(), return ); + + const QString sourceNode = m_sourceNode.currentText(); + const QString sourceProperty = m_sourceNodeProperty.currentText(); + + QString expression; + if (sourceProperty.isEmpty()) { + expression = sourceNode; + } else { + expression = sourceNode + QLatin1String(".") + sourceProperty; + } + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + model->connectionView()->executeInTransaction("BindingModel::updateExpression", + [&bindingProperty, expression]() { + bindingProperty.setExpression( + expression.trimmed()); + }); +} + +void BindingModelBackendDelegate::handleSourcePropertyChanged() +{ + BindingModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->connectionView(), return ); + + const QString sourceNode = m_sourceNode.currentText(); + const QString sourceProperty = m_sourceNodeProperty.currentText(); + + QString expression; + if (sourceProperty.isEmpty()) { + expression = sourceNode; + } else { + expression = sourceNode + QLatin1String(".") + sourceProperty; + } + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + model->connectionView()->executeInTransaction("BindingModel::updateExpression", + [&bindingProperty, expression]() { + bindingProperty.setExpression( + expression.trimmed()); + }); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h index 12685679e94..1f469876852 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h @@ -7,16 +7,22 @@ #include #include +#include + #include namespace QmlDesigner { class ConnectionView; +class BindingModelBackendDelegate; class BindingModel : public QStandardItemModel { Q_OBJECT + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(BindingModelBackendDelegate *delegate READ delegate CONSTANT) + public: enum ColumnRoles { TargetModelNodeRow = 0, @@ -24,6 +30,15 @@ public: SourceModelNodeRow = 2, SourcePropertyNameRow = 3 }; + + enum UserRoles { + InternalIdRole = Qt::UserRole + 2, + TargetNameRole, + TargetPropertyNameRole, + SourceNameRole, + SourcePropertyNameRole + }; + BindingModel(ConnectionView *parent = nullptr); void bindingChanged(const BindingProperty &bindingProperty); void bindingRemoved(const BindingProperty &bindingProperty); @@ -37,6 +52,18 @@ public: void addBindingForCurrentNode(); void resetModel(); + Q_INVOKABLE void add(); + Q_INVOKABLE void remove(int row); + + int currentIndex() const; + void setCurrentIndex(int i); + bool getExpressionStrings(const BindingProperty &bindingProperty, + QString *sourceNode, + QString *sourceProperty); + +signals: + void currentIndexChanged(); + protected: void addBindingProperty(const BindingProperty &property); void updateBindingProperty(int rowNumber); @@ -46,11 +73,11 @@ protected: ModelNode getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const; void updateCustomData(QStandardItem *item, const BindingProperty &bindingProperty); int findRowForBinding(const BindingProperty &bindingProperty); - - bool getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty); - void updateDisplayRole(int row, int columns, const QString &string); + QHash roleNames() const override; + BindingModelBackendDelegate *delegate() const; + private: void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); void handleException(); @@ -60,7 +87,48 @@ private: bool m_lock = false; bool m_handleDataChanged = false; QString m_exceptionError; + int m_currentIndex = 0; + BindingModelBackendDelegate *m_delegate = nullptr; +}; +class BindingModelBackendDelegate : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged) + + Q_PROPERTY(QString targetNode READ targetNode NOTIFY targetNodeChanged) + Q_PROPERTY(StudioQmlComboBoxBackend *property READ property CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *sourceNode READ sourceNode CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *sourceProperty READ sourceProperty CONSTANT) + +public: + BindingModelBackendDelegate(BindingModel *parent = nullptr); + +signals: + void currentRowChanged(); + //void nameChanged(); + void targetNodeChanged(); + +private: + int currentRow() const; + void setCurrentRow(int i); + void handleException(); + QString targetNode() const; + + StudioQmlComboBoxBackend *property(); + StudioQmlComboBoxBackend *sourceNode(); + StudioQmlComboBoxBackend *sourceProperty(); + + void handleSourceNodeChanged(); + void handleSourcePropertyChanged(); + + StudioQmlComboBoxBackend m_property; + StudioQmlComboBoxBackend m_sourceNode; + StudioQmlComboBoxBackend m_sourceNodeProperty; + QString m_exceptionError; + int m_currentRow = -1; + QString m_targetNode; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 4abb7b21376..6fe8af59eb7 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -252,6 +252,19 @@ void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerP { item->setData(signalHandlerProperty.parentModelNode().internalId(), UserRoles::InternalIdRole); item->setData(signalHandlerProperty.name(), UserRoles::TargetPropertyNameRole); + item->setData(signalHandlerProperty.parentModelNode() + .bindingProperty("target") + .resolveToModelNode() + .id(), + UserRoles::TargetNameRole); + + // TODO signalHandlerProperty.source() contains a statement that defines the type. + // foo.bar() <- function call + // foo.state = "literal" //state change + //anything else is assignment + // e.g. foo.bal = foo2.bula ; foo.bal = "literal" ; goo.gal = true + + item->setData("Assignment", UserRoles::ActionTypeRole); } ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connection) const @@ -370,6 +383,16 @@ void ConnectionModel::removeRowFromTable(const SignalHandlerProperty &property) } } +void ConnectionModel::add() +{ + addConnection(); +} + +void ConnectionModel::remove(int row) +{ + deleteConnectionByRow(row); +} + void ConnectionModel::handleException() { QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); @@ -522,4 +545,12 @@ QStringList ConnectionModel::getPossibleSignalsForConnection(const ModelNode &co return stringList; } +QHash ConnectionModel::roleNames() const +{ + static QHash roleNames{{TargetPropertyNameRole, "signal"}, + {TargetNameRole, "target"}, + {ActionTypeRole, "action"}}; + return roleNames; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h index 42cbe33fc77..fc1108d4035 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h @@ -26,7 +26,9 @@ public: }; enum UserRoles { InternalIdRole = Qt::UserRole + 1, - TargetPropertyNameRole + TargetPropertyNameRole, + TargetNameRole, + ActionTypeRole }; ConnectionModel(ConnectionView *parent = nullptr); @@ -49,6 +51,9 @@ public: void deleteConnectionByRow(int currentRow); void removeRowFromTable(const SignalHandlerProperty &property); + Q_INVOKABLE void add(); + Q_INVOKABLE void remove(int row); + protected: void addModelNode(const ModelNode &modelNode); void addConnection(const ModelNode &modelNode); @@ -61,6 +66,8 @@ protected: void updateCustomData(QStandardItem *item, const SignalHandlerProperty &signalHandlerProperty); QStringList getPossibleSignalsForConnection(const ModelNode &connection) const; + QHash roleNames() const override; + private: void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); void handleException(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index d8f84f3e0fc..9da3fcc7be4 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -8,6 +8,7 @@ #include "bindingmodel.h" #include "connectionmodel.h" #include "dynamicpropertiesmodel.h" +#include "theme.h" #include #include @@ -16,19 +17,116 @@ #include #include +#include + +#include +#include + #include +#include +#include #include namespace QmlDesigner { +static QString propertyEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); +} + +class ConnectionViewQuickWidget : public StudioQuickWidget +{ + // Q_OBJECT carefull + +public: + ConnectionViewQuickWidget(ConnectionView *connectionEditorView) + : m_connectionEditorView(connectionEditorView) + + { + engine()->addImportPath(qmlSourcesPath()); + engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + engine()->addImportPath(qmlSourcesPath() + "/imports"); + + m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F12), this); + connect(m_qmlSourceUpdateShortcut, + &QShortcut::activated, + this, + &ConnectionViewQuickWidget::reloadQmlSource); + + //setObjectName(Constants::OBJECT_NAME_STATES_EDITOR); + setResizeMode(QQuickWidget::SizeRootObjectToView); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + auto map = registerPropertyMap("ConnectionsEditorEditorBackend"); + qmlRegisterAnonymousType("ConnectionsEditorEditorBackend", 1); + qmlRegisterAnonymousType( + "ConnectionsEditorEditorBackend", 1); + + map->setProperties( + {{"connectionModel", QVariant::fromValue(m_connectionEditorView->connectionModel())}}); + + map->setProperties( + {{"bindingModel", QVariant::fromValue(m_connectionEditorView->bindingModel())}}); + + map->setProperties( + {{"dynamicPropertiesModel", + QVariant::fromValue(m_connectionEditorView->dynamicPropertiesModel())}}); + + Theme::setupTheme(engine()); + + setMinimumWidth(195); + setMinimumHeight(195); + + // init the first load of the QML UI elements + reloadQmlSource(); + } + ~ConnectionViewQuickWidget() = default; + + static QString qmlSourcesPath() + { +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/connectionseditor"; +#endif + return Core::ICore::resourcePath("qmldesigner/connectionseditor").toString(); + } + +private: + void reloadQmlSource() + { + QString connectionEditorQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml"); + QTC_ASSERT(QFileInfo::exists(connectionEditorQmlFilePath), return ); + setSource(QUrl::fromLocalFile(connectionEditorQmlFilePath)); + + if (!rootObject()) { + QString errorString; + for (const QQmlError &error : errors()) + errorString += "\n" + error.toString(); + + Core::AsynchronousMessageBox::warning( + tr("Cannot Create QtQuick View"), + tr("ConnectionsEditorWidget: %1 cannot be created.%2") + .arg(qmlSourcesPath(), errorString)); + return; + } + } + +private: + QPointer m_connectionEditorView; + QShortcut *m_qmlSourceUpdateShortcut; +}; + ConnectionView::ConnectionView(ExternalDependenciesInterface &externalDependencies) - : AbstractView{externalDependencies} - , m_connectionViewWidget(new ConnectionViewWidget()) - , m_connectionModel(new ConnectionModel(this)) - , m_bindingModel(new BindingModel(this)) - , m_dynamicPropertiesModel(new DynamicPropertiesModel(false, this)) - , m_backendModel(new BackendModel(this)) + : AbstractView{externalDependencies}, m_connectionViewWidget(new ConnectionViewWidget()), + m_connectionModel(new ConnectionModel(this)), m_bindingModel(new BindingModel(this)), + m_dynamicPropertiesModel(new DynamicPropertiesModel(false, this)), + m_backendModel(new BackendModel(this)), + m_connectionViewQuickWidget(new ConnectionViewQuickWidget(this)) { connectionViewWidget()->setBindingModel(m_bindingModel); connectionViewWidget()->setConnectionModel(m_connectionModel); @@ -36,8 +134,11 @@ ConnectionView::ConnectionView(ExternalDependenciesInterface &externalDependenci connectionViewWidget()->setBackendModel(m_backendModel); } -ConnectionView::~ConnectionView() = default; - +ConnectionView::~ConnectionView() +{ + // Ensure that QML is deleted first to avoid calling back to C++. + delete m_connectionViewQuickWidget.data(); +} void ConnectionView::modelAttached(Model *model) { AbstractView::modelAttached(model); @@ -195,7 +296,14 @@ void ConnectionView::currentStateChanged(const ModelNode &) WidgetInfo ConnectionView::widgetInfo() { - return createWidgetInfo(m_connectionViewWidget.data(), + /* Enable new connection editor here */ + const bool newEditor = false; + + QWidget *widget = m_connectionViewWidget.data(); + if (newEditor) + widget = m_connectionViewQuickWidget.data(); + + return createWidgetInfo(widget, QLatin1String("ConnectionView"), WidgetInfo::LeftPane, 0, @@ -257,6 +365,20 @@ BackendModel *ConnectionView::backendModel() const return m_backendModel; } +int ConnectionView::currentIndex() const +{ + return m_currentIndex; +} + +void ConnectionView::setCurrentIndex(int i) +{ + if (m_currentIndex == i) + return; + + m_currentIndex = i; + emit currentIndexChanged(); +} + ConnectionView *ConnectionView::instance() { diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h index 89c6c489105..5997f230ad8 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -20,11 +20,14 @@ class BindingModel; class ConnectionModel; class DynamicPropertiesModel; class BackendModel; +class ConnectionViewQuickWidget; -class ConnectionView : public AbstractView +class ConnectionView : public AbstractView { Q_OBJECT + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + public: ConnectionView(ExternalDependenciesInterface &externalDependencies); ~ConnectionView() override; @@ -70,14 +73,24 @@ public: BindingModel *bindingModel() const; BackendModel *backendModel() const; + int currentIndex() const; + void setCurrentIndex(int i); + static ConnectionView *instance(); +signals: + void currentIndexChanged(); + private: //variables QPointer m_connectionViewWidget; + ConnectionModel *m_connectionModel; BindingModel *m_bindingModel; DynamicPropertiesModel *m_dynamicPropertiesModel; BackendModel *m_backendModel; + int m_currentIndex = 0; + + QPointer m_connectionViewQuickWidget; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp index edade6ae400..8a8845b47fd 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp @@ -90,6 +90,8 @@ ConnectionViewWidget::ConnectionViewWidget(QWidget *parent) : this, &ConnectionViewWidget::handleTabChanged); ui->stackedWidget->setCurrentIndex(0); + + ui->stackedWidget->parentWidget()->hide(); } ConnectionViewWidget::~ConnectionViewWidget() @@ -192,18 +194,17 @@ void ConnectionViewWidget::contextMenuEvent(QContextMenuEvent *event) return; const ModelNode node = property.parentModelNode(); - const TypeName typeName = property.isDynamic() ? property.dynamicTypeName() - : node.metaInfo() - .property(property.name()) - .propertyType() - .typeName(); + auto model = node.model(); + const auto type = property.isDynamic() + ? model->metaInfo(property.dynamicTypeName()) + : node.metaInfo().property(property.name()).propertyType(); const QString targetName = node.displayName() + "." + property.name(); m_bindingEditor->showWidget(); m_bindingEditor->setBindingValue(property.expression()); m_bindingEditor->setModelNode(node); - m_bindingEditor->setBackendValueTypeName(typeName); + m_bindingEditor->setBackendValueType(type); m_bindingEditor->setTargetName(targetName); m_bindingEditor->prepareBindings(); m_bindingEditor->updateWindowName(); @@ -240,11 +241,12 @@ void ConnectionViewWidget::contextMenuEvent(QContextMenuEvent *event) return; const QString targetName = node.displayName() + "." + abstractProperty.name(); - + auto model = node.model(); m_dynamicEditor->showWidget(); m_dynamicEditor->setBindingValue(newExpression); m_dynamicEditor->setModelNode(node); - m_dynamicEditor->setBackendValueTypeName(abstractProperty.dynamicTypeName()); + m_dynamicEditor->setBackendValueType( + model->metaInfo(abstractProperty.dynamicTypeName())); m_dynamicEditor->setTargetName(targetName); m_dynamicEditor->prepareBindings(); m_dynamicEditor->updateWindowName(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp index 4faf5d23f17..eb0976eaed9 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp @@ -152,10 +152,34 @@ QString DynamicPropertiesModel::defaultExpressionForType(const TypeName &type) return expression; } +void DynamicPropertiesModel::add() +{ + addDynamicPropertyForCurrentNode(); +} + +void DynamicPropertiesModel::remove(int row) +{ + deleteDynamicPropertyByRow(row); +} + +int DynamicPropertiesModel::currentIndex() const +{ + return m_currentIndex; +} + +void DynamicPropertiesModel::setCurrentIndex(int i) +{ + if (m_currentIndex == i) + return; + + m_currentIndex = i; + + emit currentIndexChanged(); +} + DynamicPropertiesModel::DynamicPropertiesModel(bool explicitSelection, AbstractView *parent) - : QStandardItemModel(parent) - , m_view(parent) - , m_explicitSelection(explicitSelection) + : QStandardItemModel(parent), m_view(parent), m_explicitSelection(explicitSelection), + m_delegate(new DynamicPropertiesModelBackendDelegate(this)) { connect(this, &QStandardItemModel::dataChanged, this, &DynamicPropertiesModel::handleDataChanged); } @@ -163,6 +187,7 @@ DynamicPropertiesModel::DynamicPropertiesModel(bool explicitSelection, AbstractV void DynamicPropertiesModel::resetModel() { beginResetModel(); + const int backIndex = m_currentIndex; clear(); setHorizontalHeaderLabels({tr("Item"), tr("Property"), tr("Property Type"), tr("Property Value")}); @@ -172,7 +197,9 @@ void DynamicPropertiesModel::resetModel() addModelNode(modelNode); } + emit currentIndexChanged(); endResetModel(); + m_currentIndex = backIndex; } @@ -344,6 +371,8 @@ void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProper removeRow(rowNumber); } + emit currentIndexChanged(); + m_handleDataChanged = true; } @@ -360,6 +389,8 @@ void DynamicPropertiesModel::variantRemoved(const VariantProperty &variantProper removeRow(rowNumber); } + emit currentIndexChanged(); + m_handleDataChanged = true; } @@ -368,6 +399,7 @@ void DynamicPropertiesModel::reset() m_handleDataChanged = false; resetModel(); m_handleDataChanged = true; + emit currentIndexChanged(); } void DynamicPropertiesModel::setSelectedNode(const ModelNode &node) @@ -597,6 +629,8 @@ void DynamicPropertiesModel::updateBindingProperty(int rowNumber) BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); if (bindingProperty.isValid()) { + updateCustomData(rowNumber, bindingProperty); + QString propertyName = QString::fromUtf8(bindingProperty.name()); updateDisplayRole(rowNumber, PropertyNameRow, propertyName); QString value = bindingProperty.expression(); @@ -617,6 +651,7 @@ void DynamicPropertiesModel::updateVariantProperty(int rowNumber) VariantProperty variantProperty = variantPropertyForRow(rowNumber); if (variantProperty.isValid()) { + updateCustomData(rowNumber, variantProperty); QString propertyName = QString::fromUtf8(variantProperty.name()); updateDisplayRole(rowNumber, PropertyNameRow, propertyName); QVariant value = variantProperty.value(); @@ -787,6 +822,16 @@ void DynamicPropertiesModel::updateCustomData(QStandardItem *item, const Abstrac { item->setData(property.parentModelNode().internalId(), Qt::UserRole + 1); item->setData(property.name(), Qt::UserRole + 2); + + item->setData(property.parentModelNode().id(), TargetNameRole); + item->setData(property.name(), PropertyNameRole); + item->setData(property.parentModelNode().id(), TargetNameRole); + item->setData(property.dynamicTypeName(), PropertyTypeRole); + + if (property.isVariantProperty()) + item->setData(property.toVariantProperty().value(), PropertyValueRole); + if (property.isBindingProperty()) + item->setData(property.toBindingProperty().expression(), PropertyValueRole); } void DynamicPropertiesModel::updateCustomData(int row, const AbstractProperty &property) @@ -924,4 +969,217 @@ const ModelNode DynamicPropertiesModel::singleSelectedNode() const return m_view->singleSelectedModelNode(); } +QHash DynamicPropertiesModel::roleNames() const +{ + static QHash roleNames{{TargetNameRole, "target"}, + {PropertyNameRole, "name"}, + {PropertyTypeRole, "type"}, + {PropertyValueRole, "value"}}; + + return roleNames; +} + +DynamicPropertiesModelBackendDelegate *DynamicPropertiesModel::delegate() const +{ + return m_delegate; +} + +DynamicPropertiesModelBackendDelegate::DynamicPropertiesModelBackendDelegate( + DynamicPropertiesModel *parent) + : QObject(parent) +{ + m_type.setModel({"int", "bool", "var", "real", "string", "url", "color"}); + + connect(&m_type, &StudioQmlComboBoxBackend::activated, this, [this]() { handleTypeChanged(); }); + connect(&m_name, &StudioQmlTextBackend::activated, this, [this]() { handleNameChanged(); }); + connect(&m_value, &StudioQmlTextBackend::activated, this, [this]() { handleValueChanged(); }); +} + +int DynamicPropertiesModelBackendDelegate::currentRow() const +{ + return m_currentRow; +} + +void DynamicPropertiesModelBackendDelegate::setCurrentRow(int i) +{ + if (m_currentRow == i) + return; + + m_currentRow = i; + + //setup + + DynamicPropertiesModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + + AbstractProperty property = model->abstractPropertyForRow(i); + + m_type.setCurrentText(QString::fromUtf8(property.dynamicTypeName())); + m_name.setText(QString::fromUtf8(property.name())); + + if (property.isVariantProperty()) + m_value.setText(property.toVariantProperty().value().toString()); + else if (property.isBindingProperty()) + m_value.setText(property.toBindingProperty().expression()); +} + +void DynamicPropertiesModelBackendDelegate::handleTypeChanged() +{ + //void DynamicPropertiesModel::updatePropertyType(int rowNumber) + const TypeName type = m_type.currentText().toUtf8(); + + DynamicPropertiesModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->view(), return ); + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + + VariantProperty variantProperty = model->variantPropertyForRow(currentRow()); + + RewriterTransaction transaction = model->view()->beginRewriterTransaction(__FUNCTION__); + + try { + if (bindingProperty.isBindingProperty() || type == "var") { //var is always a binding + const QString expression = bindingProperty.expression(); + variantProperty.parentModelNode().removeProperty(variantProperty.name()); + bindingProperty.setDynamicTypeNameAndExpression(type, expression); + } else if (variantProperty.isVariantProperty()) { + variantProperty.parentModelNode().removeProperty(variantProperty.name()); + variantProperty.setDynamicTypeNameAndValue(type, variantValue()); + } + transaction.commit(); // committing in the try block + } catch (Exception &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &DynamicPropertiesModelBackendDelegate::handleException); + } +} + +void DynamicPropertiesModelBackendDelegate::handleNameChanged() +{ + //see DynamicPropertiesModel::updatePropertyName + + const PropertyName newName = m_name.text().toUtf8(); + QTC_ASSERT(!newName.isEmpty(), return ); + + DynamicPropertiesModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->view(), return ); + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + + ModelNode targetNode = bindingProperty.parentModelNode(); + + if (bindingProperty.isBindingProperty()) { + model->view()->executeInTransaction(__FUNCTION__, [bindingProperty, newName, &targetNode]() { + const QString expression = bindingProperty.expression(); + const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName(); + + targetNode.bindingProperty(newName).setDynamicTypeNameAndExpression(dynamicPropertyType, + expression); + targetNode.removeProperty(bindingProperty.name()); + }); + + return; + } + + VariantProperty variantProperty = model->variantPropertyForRow(currentRow()); + + if (variantProperty.isVariantProperty()) { + const QVariant value = variantProperty.value(); + const PropertyName dynamicPropertyType = variantProperty.dynamicTypeName(); + ModelNode targetNode = variantProperty.parentModelNode(); + + model->view()->executeInTransaction(__FUNCTION__, [=]() { + targetNode.variantProperty(newName).setDynamicTypeNameAndValue(dynamicPropertyType, + value); + targetNode.removeProperty(variantProperty.name()); + }); + } + + AbstractProperty property = targetNode.property(newName); + + //order might have changed because of name change we have to select the correct row + int newRow = model->findRowForProperty(property); + model->setCurrentIndex(newRow); + setCurrentRow(newRow); +} + +void DynamicPropertiesModelBackendDelegate::handleValueChanged() +{ + //see void DynamicPropertiesModel::updateValue(int row) + + DynamicPropertiesModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->view(), return ); + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + + if (bindingProperty.isBindingProperty()) { + const QString expression = m_value.text(); + + RewriterTransaction transaction = model->view()->beginRewriterTransaction(__FUNCTION__); + try { + bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(), + expression); + transaction.commit(); // committing in the try block + } catch (Exception &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &DynamicPropertiesModelBackendDelegate::handleException); + } + return; + } + + VariantProperty variantProperty = model->variantPropertyForRow(currentRow()); + + if (variantProperty.isVariantProperty()) { + RewriterTransaction transaction = model->view()->beginRewriterTransaction(__FUNCTION__); + try { + variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(), + variantValue()); + transaction.commit(); // committing in the try block + } catch (Exception &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &DynamicPropertiesModelBackendDelegate::handleException); + } + } +} + +void DynamicPropertiesModelBackendDelegate::handleException() +{ + QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); + //reset +} + +QVariant DynamicPropertiesModelBackendDelegate::variantValue() const +{ + //improve + const QString type = m_type.currentText(); + if (type == "real" || type == "int") + return m_value.text().toFloat(); + + if (type == "bool") + return m_value.text() == "true"; + + return m_value.text(); +} + +StudioQmlComboBoxBackend *DynamicPropertiesModelBackendDelegate::type() +{ + return &m_type; +} + +StudioQmlTextBackend *DynamicPropertiesModelBackendDelegate::name() +{ + return &m_name; +} + +StudioQmlTextBackend *DynamicPropertiesModelBackendDelegate::value() +{ + return &m_value; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h index f094516e635..c41875dfc09 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h @@ -5,6 +5,8 @@ #include +#include + #include namespace QmlDesigner { @@ -15,6 +17,8 @@ class BindingProperty; class ModelNode; class VariantProperty; +class DynamicPropertiesModelBackendDelegate; + class DynamicPropertiesModel : public QStandardItemModel { Q_OBJECT @@ -22,11 +26,22 @@ class DynamicPropertiesModel : public QStandardItemModel public: enum ColumnRoles { TargetModelNodeRow = 0, - PropertyNameRow = 1, - PropertyTypeRow = 2, - PropertyValueRow = 3 + PropertyNameRow = 1, + PropertyTypeRow = 2, + PropertyValueRow = 3 }; + enum UserRoles { + InternalIdRole = Qt::UserRole + 2, + TargetNameRole, + PropertyNameRole, + PropertyTypeRole, + PropertyValueRole + }; + + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(DynamicPropertiesModelBackendDelegate *delegate READ delegate CONSTANT) + DynamicPropertiesModel(bool explicitSelection, AbstractView *parent); void bindingPropertyChanged(const BindingProperty &bindingProperty); @@ -62,6 +77,17 @@ public: static QVariant defaultValueForType(const TypeName &type); static QString defaultExpressionForType(const TypeName &type); + Q_INVOKABLE void add(); + Q_INVOKABLE void remove(int row); + + int currentIndex() const; + void setCurrentIndex(int i); + + int findRowForProperty(const AbstractProperty &abstractProperty) const; + +signals: + void currentIndexChanged(); + protected: void addProperty(const QVariant &propertyValue, const QString &propertyType, @@ -79,12 +105,17 @@ protected: void updateCustomData(int row, const AbstractProperty &property); int findRowForBindingProperty(const BindingProperty &bindingProperty) const; int findRowForVariantProperty(const VariantProperty &variantProperty) const; - int findRowForProperty(const AbstractProperty &abstractProperty) const; - bool getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty); + bool getExpressionStrings(const BindingProperty &bindingProperty, + QString *sourceNode, + QString *sourceProperty); void updateDisplayRole(int row, int columns, const QString &string); + QHash roleNames() const override; + + DynamicPropertiesModelBackendDelegate *delegate() const; + private: void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void handleException(); @@ -95,6 +126,48 @@ private: QString m_exceptionError; QList m_selectedNodes; bool m_explicitSelection = false; + int m_currentIndex = 0; + + DynamicPropertiesModelBackendDelegate *m_delegate = nullptr; +}; + +class DynamicPropertiesModelBackendDelegate : public QObject +{ + Q_OBJECT + + Q_PROPERTY(StudioQmlComboBoxBackend *type READ type CONSTANT) + Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged) + Q_PROPERTY(StudioQmlTextBackend *name READ name CONSTANT) + Q_PROPERTY(StudioQmlTextBackend *value READ value CONSTANT) + //Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged) + +public: + DynamicPropertiesModelBackendDelegate(DynamicPropertiesModel *parent = nullptr); + +signals: + void currentRowChanged(); + void nameChanged(); + void valueChanged(); + +private: + int currentRow() const; + void setCurrentRow(int i); + void handleTypeChanged(); + void handleNameChanged(); + void handleValueChanged(); + void handleException(); + QVariant variantValue() const; + + StudioQmlComboBoxBackend *type(); + + StudioQmlTextBackend *name(); + StudioQmlTextBackend *value(); + + StudioQmlComboBoxBackend m_type; + StudioQmlTextBackend m_name; + StudioQmlTextBackend m_value; + int m_currentRow = -1; + QString m_exceptionError; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp index c31f844e5e2..59ec457d071 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp @@ -205,7 +205,7 @@ void ContentLibraryBundleImporter::handleImportTimer() for (const QString &pendingType : pendingTypes) { NodeMetaInfo metaInfo = model->metaInfo(pendingType.toUtf8()); const bool isImport = m_pendingTypes[pendingType]; - const bool typeComplete = metaInfo.isValid() && !metaInfo.superClasses().empty(); + const bool typeComplete = metaInfo.isValid() && !metaInfo.prototypes().empty(); if (isImport == typeComplete) { m_pendingTypes.remove(pendingType); if (isImport) diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditorview.cpp b/src/plugins/qmldesigner/components/curveeditor/curveeditorview.cpp index 1d0b38b3f15..e7395c66625 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditorview.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditorview.cpp @@ -68,8 +68,9 @@ void CurveEditorView::modelAboutToBeDetached(Model *model) bool dirtyfiesView(const ModelNode &node) { - return QmlTimeline::isValidQmlTimeline(node) - || QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(node); + return (node.type() == "QtQuick.Timeline.Keyframe" && node.hasParentProperty()) + || QmlTimeline::isValidQmlTimeline(node) + || QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(node); } void CurveEditorView::nodeRemoved([[maybe_unused]] const ModelNode &removedNode, @@ -143,13 +144,8 @@ void CurveEditorView::variantPropertiesChanged([[maybe_unused]] const QList &propertyList) { for (const auto &property : propertyList) { - if (property.name() == "keyframes") { - ModelNode parent = property.parentModelNode(); - if (dirtyfiesView(parent)) - updateKeyframes(); - } + if (dirtyfiesView(property.parentModelNode())) + updateKeyframes(); } } diff --git a/src/plugins/qmldesigner/components/debugview/debugview.cpp b/src/plugins/qmldesigner/components/debugview/debugview.cpp index 2a62d7a6b2b..64f85915f16 100644 --- a/src/plugins/qmldesigner/components/debugview/debugview.cpp +++ b/src/plugins/qmldesigner/components/debugview/debugview.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -93,9 +94,9 @@ void DebugView::nodeCreated(const ModelNode &createdNode) message << createdNode.majorVersion() << "." << createdNode.minorVersion(); message << createdNode.nodeSource(); message << "MetaInfo " << createdNode.metaInfo().isValid() << " "; - if (createdNode.metaInfo().isValid()) { - message << createdNode.metaInfo().majorVersion() << "." << createdNode.metaInfo().minorVersion(); - message << createdNode.metaInfo().componentFileName(); + if (auto metaInfo = createdNode.metaInfo()) { + message << metaInfo.majorVersion() << "." << metaInfo.minorVersion(); + message << ModelUtils::componentFilePath(createdNode); } log("::nodeCreated:", message.readAll()); } @@ -282,9 +283,10 @@ void DebugView::selectedNodesChanged(const QList &selectedNodes /*sel message << lineBreak; if (selectedNode.metaInfo().isValid()) { - for (const NodeMetaInfo &metaInfo : selectedNode.metaInfo().classHierarchy()) + for (const NodeMetaInfo &metaInfo : selectedNode.metaInfo().selfAndPrototypes()) { message << metaInfo.typeName() << " " << metaInfo.majorVersion() << "." << metaInfo.minorVersion() << lineBreak; + } message << lineBreak; message << selectedNode.metaInfo().typeName(); diff --git a/src/plugins/qmldesigner/components/edit3d/bakelights.cpp b/src/plugins/qmldesigner/components/edit3d/bakelights.cpp index c0ff5de2af3..88e7ad379d5 100644 --- a/src/plugins/qmldesigner/components/edit3d/bakelights.cpp +++ b/src/plugins/qmldesigner/components/edit3d/bakelights.cpp @@ -3,19 +3,20 @@ #include "bakelights.h" -#include "abstractview.h" -#include "auxiliarydataproperties.h" -#include "bakelightsdatamodel.h" -#include "bakelightsconnectionmanager.h" -#include "bindingproperty.h" -#include "documentmanager.h" -#include "modelnode.h" -#include "nodeabstractproperty.h" -#include "nodeinstanceview.h" -#include "nodemetainfo.h" -#include "plaintexteditmodifier.h" -#include "rewriterview.h" -#include "variantproperty.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -240,18 +241,21 @@ void BakeLights::rebake() void BakeLights::exposeModelsAndLights(const QString &nodeId) { ModelNode compNode = m_view->modelNodeForId(nodeId); - if (!compNode.isValid() || !compNode.isComponent() - || compNode.metaInfo().componentFileName().isEmpty()) { + if (!compNode.isValid() || !compNode.isComponent()) { + return; + } + + auto componentFilePath = ModelUtils::componentFilePath(compNode); + if (componentFilePath.isEmpty()) { return; } RewriterView rewriter{m_view->externalDependencies(), RewriterView::Amend}; ModelPointer compModel = QmlDesigner::Model::create("QtQuick/Item", 2, 1); - const QString compFile = compNode.metaInfo().componentFileName(); - const Utils::FilePath compFilePath = Utils::FilePath::fromString(compFile); + const Utils::FilePath compFilePath = Utils::FilePath::fromString(componentFilePath); QByteArray src = compFilePath.fileContents().value(); - compModel->setFileUrl(QUrl::fromLocalFile(compFile)); + compModel->setFileUrl(QUrl::fromLocalFile(componentFilePath)); auto textDocument = std::make_unique(QString::fromUtf8(src)); auto modifier = std::make_unique( @@ -295,12 +299,12 @@ void BakeLights::exposeModelsAndLights(const QString &nodeId) QString newText = modifier->text(); if (newText != originalText) { - QSaveFile saveFile(compFile); + QSaveFile saveFile(componentFilePath); if (saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { saveFile.write(newText.toUtf8()); saveFile.commit(); } else { - qWarning() << __FUNCTION__ << "Failed to save changes to:" << compFile; + qWarning() << __FUNCTION__ << "Failed to save changes to:" << componentFilePath; } } diff --git a/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp b/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp index 37dfafad9a2..07a16de7bbd 100644 --- a/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp +++ b/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp @@ -3,6 +3,8 @@ #include "backgroundaction.h" +#include + #include #include @@ -28,6 +30,14 @@ QIcon iconForColor(const QColor &color) { image.fill(0); QPainter p(&image); + if (color == BackgroundAction::ContextImage) { + const QString unicode = Theme::getIconUnicode(Theme::Icon::textures_medium); + const QString fontName = "qtds_propertyIconFont.ttf"; + QIcon icon = Utils::StyleHelper::getIconFromIconFont(fontName, unicode, 10, 10, Qt::white); + + return icon; + } + p.fillRect(2, 2, size - 4, size - 4, Qt::black); if (color.alpha() == 0) { @@ -70,13 +80,13 @@ QList BackgroundAction::colors() { static QColor alphaZero(Qt::transparent); static QList colorList = {alphaZero, + QColor(BackgroundAction::ContextImage), QColor(Qt::black), QColor(0x4c4e50), QColor(Qt::darkGray), QColor(Qt::lightGray), QColor(Qt::white)}; - return colorList; } diff --git a/src/plugins/qmldesigner/components/formeditor/backgroundaction.h b/src/plugins/qmldesigner/components/formeditor/backgroundaction.h index 34bd4cd3229..c6eeb212fe4 100644 --- a/src/plugins/qmldesigner/components/formeditor/backgroundaction.h +++ b/src/plugins/qmldesigner/components/formeditor/backgroundaction.h @@ -11,14 +11,12 @@ namespace QmlDesigner { class BackgroundAction : public QWidgetAction { - enum BackgroundType { - CheckboardBackground, - WhiteBackground, - BlackBackground - }; + enum BackgroundType { CheckboardBackground, WhiteBackground, BlackBackground }; Q_OBJECT public: + enum SpecialColor { ContextImage = Qt::yellow }; + explicit BackgroundAction(QObject *parent); void setColor(const QColor &color); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp index 1e67884ad8d..9549ce9dd46 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp @@ -2,9 +2,13 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "formeditorgraphicsview.h" +#include "backgroundaction.h" #include "formeditoritem.h" #include "formeditorwidget.h" #include "navigation2d.h" + +#include + #include #include @@ -198,10 +202,28 @@ void FormEditorGraphicsView::drawBackground(QPainter *painter, const QRectF &rec painter->save(); painter->setBrushOrigin(0, 0); - painter->fillRect(rectangle.intersected(rootItemRect()), backgroundBrush()); // paint rect around editable area - painter->setPen(Qt::black); - painter->drawRect(rootItemRect()); + + if (backgroundBrush().color() == BackgroundAction::ContextImage) { + painter->fillRect(rectangle.intersected(rootItemRect()), Qt::gray); + painter->setOpacity(0.5); + if (!m_backgroundImage.isNull()) + painter->drawImage(rootItemRect().topLeft() + m_backgroundImage.offset(), + m_backgroundImage); + painter->setOpacity(1.0); + } else { + painter->fillRect(rectangle.intersected(rootItemRect()), backgroundBrush()); + } + + QPen pen(Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor)); + + pen.setStyle(Qt::DotLine); + pen.setWidth(1); + + painter->setPen(pen); + + painter->drawRect(rootItemRect().adjusted(-1, -1, 0, 0)); + painter->restore(); } @@ -210,6 +232,17 @@ void FormEditorGraphicsView::frame(const QRectF &boundingRect) fitInView(boundingRect, Qt::KeepAspectRatio); } +void FormEditorGraphicsView::setBackgoundImage(const QImage &image) +{ + m_backgroundImage = image; + update(); +} + +QImage FormEditorGraphicsView::backgroundImage() const +{ + return m_backgroundImage; +} + void FormEditorGraphicsView::setZoomFactor(double zoom) { resetTransform(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.h b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.h index 28a49b00466..60e02582cd1 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.h @@ -28,6 +28,9 @@ public: void setZoomFactor(double zoom); void frame(const QRectF &bbox); + void setBackgoundImage(const QImage &image); + QImage backgroundImage() const; + protected: bool eventFilter(QObject *watched, QEvent *event) override; void wheelEvent(QWheelEvent *event) override; @@ -45,6 +48,7 @@ private: Panning m_isPanning = Panning::NotStarted; QPoint m_panningStartPosition; QRectF m_rootItemRect; + QImage m_backgroundImage; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index 5661e4ff793..6b580a7d5f3 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -63,6 +63,8 @@ void FormEditorView::modelAttached(Model *model) if (!isEnabled()) return; + m_formEditorWidget->setBackgoundImage({}); + temporaryBlockView(); setupFormEditorWidget(); @@ -649,6 +651,10 @@ void FormEditorView::auxiliaryDataChanged(const ModelNode &node, if (FormEditorItem *editorItem = scene()->itemForQmlItemNode(item)) editorItem->setFrameColor(data.value()); } + + if (key == contextImageProperty) { + m_formEditorWidget->setBackgoundImage(data.value()); + } } static void updateTransitions(FormEditorScene *scene, const QmlItemNode &qmlItemNode) @@ -784,6 +790,11 @@ void FormEditorView::exportAsImage() m_formEditorWidget->exportAsImage(m_scene->rootFormEditorItem()->boundingRect()); } +QImage FormEditorView::takeFormEditorScreenshot() +{ + return m_formEditorWidget->takeFormEditorScreenshot(); +} + QPicture FormEditorView::renderToPicture() const { return m_formEditorWidget->renderToPicture(); @@ -954,6 +965,11 @@ void FormEditorView::setupRootItemSize() formEditorWidget()->setRootItemRect(rootQmlNode.instanceBoundingRect()); formEditorWidget()->centerScene(); + + auto contextImage = rootModelNode().auxiliaryData(contextImageProperty); + + if (contextImage) + m_formEditorWidget->setBackgoundImage(contextImage.value().value()); } } diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.h b/src/plugins/qmldesigner/components/formeditor/formeditorview.h index f97959acb37..1a9f15d016e 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.h @@ -117,6 +117,7 @@ public: void setGotoErrorCallback(std::function gotoErrorCallback); void exportAsImage(); + QImage takeFormEditorScreenshot(); QPicture renderToPicture() const; void setupFormEditorWidget(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp index bfc03d2b237..ef7ed1d52d4 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "formeditorwidget.h" +#include "backgroundaction.h" #include "designeractionmanager.h" #include "designericons.h" #include "designersettings.h" @@ -332,11 +333,13 @@ void FormEditorWidget::changeBackgound(const QColor &color) if (color.alpha() == 0) { m_graphicsView->activateCheckboardBackground(); if (m_formEditorView->rootModelNode().hasAuxiliaryData(formeditorColorProperty)) { - m_formEditorView->rootModelNode().setAuxiliaryData(formeditorColorProperty, {}); + m_formEditorView->rootModelNode().setAuxiliaryDataWithoutLock(formeditorColorProperty, + {}); } } else { m_graphicsView->activateColoredBackground(color); - m_formEditorView->rootModelNode().setAuxiliaryData(formeditorColorProperty, color); + m_formEditorView->rootModelNode().setAuxiliaryDataWithoutLock(formeditorColorProperty, + color); } } @@ -397,6 +400,10 @@ void FormEditorWidget::updateActions() } else { m_backgroundAction->setColor(Qt::transparent); } + + if (m_formEditorView->rootModelNode().hasAuxiliaryData(contextImageProperty)) + m_backgroundAction->setColor(BackgroundAction::ContextImage); + } else { m_rootWidthAction->clearLineEditText(); m_rootHeightAction->clearLineEditText(); @@ -540,6 +547,40 @@ void FormEditorWidget::exportAsImage(const QRectF &boundingRect) } } +QImage FormEditorWidget::takeFormEditorScreenshot() +{ + const QRectF boundingRect = m_formEditorView->scene()->rootFormEditorItem()->boundingRect(); + + m_formEditorView->scene()->manipulatorLayerItem()->setVisible(false); + QImage image(boundingRect.size().toSize(), QImage::Format_ARGB32); + + if (!m_graphicsView->backgroundImage().isNull()) { + image = m_graphicsView->backgroundImage(); + const QPoint offset = m_graphicsView->backgroundImage().offset(); + + QPainter painter(&image); + QTransform viewportTransform = m_graphicsView->viewportTransform(); + + m_graphicsView->render(&painter, + QRectF(-offset, boundingRect.size()), + viewportTransform.mapRect(boundingRect).toRect()); + + image.setOffset(offset); + + } else { + QPainter painter(&image); + QTransform viewportTransform = m_graphicsView->viewportTransform(); + + m_graphicsView->render(&painter, + QRectF(0, 0, image.width(), image.height()), + viewportTransform.mapRect(boundingRect).toRect()); + } + + m_formEditorView->scene()->manipulatorLayerItem()->setVisible(true); + + return image; +} + QPicture FormEditorWidget::renderToPicture() const { QPicture picture; @@ -568,6 +609,17 @@ bool FormEditorWidget::errorMessageBoxIsVisible() const return m_documentErrorWidget && m_documentErrorWidget->isVisible(); } +void FormEditorWidget::setBackgoundImage(const QImage &image) +{ + m_graphicsView->setBackgoundImage(image); + updateActions(); +} + +QImage FormEditorWidget::backgroundImage() const +{ + return m_graphicsView->backgroundImage(); +} + DocumentWarningWidget *FormEditorWidget::errorWidget() { if (m_documentErrorWidget.isNull()) { diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h index 5e4e29d1553..8135ce81393 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h @@ -63,12 +63,17 @@ public: void showWarningMessageBox(const QList &warnings); void exportAsImage(const QRectF &boundingRect); + + QImage takeFormEditorScreenshot(); QPicture renderToPicture() const; FormEditorGraphicsView *graphicsView() const; bool errorMessageBoxIsVisible() const; + void setBackgoundImage(const QImage &image); + QImage backgroundImage() const; + protected: QActionGroup *toolActionGroup() const; DocumentWarningWidget *errorWidget(); diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp index 089efb87cdd..7bbbac5b253 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp @@ -61,7 +61,7 @@ namespace QmlDesigner { DesignDocument acts as a facade to a model representing a qml document, and the different views/widgets accessing it. */ -DesignDocument::DesignDocument(ProjectStorage &projectStorage, +DesignDocument::DesignDocument(ProjectStorageDependencies projectStorageDependencies, ExternalDependenciesInterface &externalDependencies) : m_documentModel( Model::create("QtQuick.Item", 1, 0, nullptr, std::make_unique())) @@ -69,7 +69,7 @@ DesignDocument::DesignDocument(ProjectStorage &projectStorage, , m_rewriterView(new RewriterView(externalDependencies, RewriterView::Amend)) , m_documentLoaded(false) , m_currentTarget(nullptr) - , m_projectStorage(projectStorage) + , m_projectStorageDependencies(projectStorageDependencies) , m_externalDependencies{externalDependencies} { } diff --git a/src/plugins/qmldesigner/components/integration/designdocument.h b/src/plugins/qmldesigner/components/integration/designdocument.h index 2fbc9060e45..7c0fe3941f8 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.h +++ b/src/plugins/qmldesigner/components/integration/designdocument.h @@ -39,7 +39,7 @@ class QMLDESIGNERCOMPONENTS_EXPORT DesignDocument : public QObject Q_OBJECT public: - DesignDocument(ProjectStorage &projectStorage, + DesignDocument(ProjectStorageDependencies projectStorageDependencies, ExternalDependenciesInterface &externalDependencies); ~DesignDocument() override; @@ -143,7 +143,7 @@ private: // variables QScopedPointer m_rewriterView; bool m_documentLoaded; ProjectExplorer::Target *m_currentTarget; - ProjectStorage &m_projectStorage; + ProjectStorageDependencies m_projectStorageDependencies; ExternalDependenciesInterface &m_externalDependencies; }; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp index f8a9fd97718..29fff4b359a 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp @@ -4,14 +4,15 @@ #include "itemlibraryassetimportdialog.h" #include "ui_itemlibraryassetimportdialog.h" -#include "qmldesignerplugin.h" -#include "qmldesignerconstants.h" -#include "model.h" -#include "nodemetainfo.h" -#include "variantproperty.h" +#include +#include +#include +#include +#include +#include -#include "utils/outputformatter.h" -#include "theme.h" +#include +#include #include #include @@ -283,7 +284,7 @@ void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode, QString errorMsg; const ModelNode &node = updateNode; if (node.hasMetaInfo()) { - QString compFileName = node.metaInfo().componentFileName(); // absolute path + QString compFileName = ModelUtils::componentFilePath(node); // absolute path bool preselectNodeSource = false; if (compFileName.isEmpty()) { // Node is not a file component, so we have to check if the current doc itself is diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index d370f6d4547..3082ee442ac 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -62,7 +62,7 @@ void ItemLibraryView::modelAttached(Model *model) m_widget->setModel(model); updateImports(); if (model) - m_widget->updatePossibleImports(difference(model->possibleImports(), model->imports())); + m_widget->updatePossibleImports(set_difference(model->possibleImports(), model->imports())); m_hasErrors = !rewriterView()->errors().isEmpty(); m_widget->setFlowMode(QmlItemNode(rootModelNode()).isFlowView()); } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index 1cbde45ba0b..d2435fbf15a 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -338,7 +338,7 @@ void ItemLibraryWidget::updateModel() void ItemLibraryWidget::updatePossibleImports(const Imports &possibleImports) { - m_addModuleModel->update(difference(possibleImports, m_model->imports())); + m_addModuleModel->update(set_difference(possibleImports, m_model->imports())); delayedUpdateModel(); } @@ -358,7 +358,7 @@ void ItemLibraryWidget::handlePriorityImportsChanged() { if (!m_itemLibraryInfo.isNull()) { m_addModuleModel->setPriorityImports(m_itemLibraryInfo->priorityImports()); - m_addModuleModel->update(difference(m_model->possibleImports(), m_model->imports())); + m_addModuleModel->update(set_difference(m_model->possibleImports(), m_model->imports())); } } diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 933686febda..7d3eb7efe6d 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -548,7 +548,7 @@ void MaterialEditorView::setupQmlBackend() TypeName diffClassName; if (NodeMetaInfo metaInfo = m_selectedMaterial.metaInfo()) { diffClassName = metaInfo.typeName(); - for (const NodeMetaInfo &metaInfo : metaInfo.classHierarchy()) { + for (const NodeMetaInfo &metaInfo : metaInfo.selfAndPrototypes()) { if (PropertyEditorQmlBackend::checkIfUrlExists(qmlSpecificsUrl)) break; qmlSpecificsUrl = PropertyEditorQmlBackend::getQmlFileUrl(metaInfo.typeName() diff --git a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp index 304ff91fcda..04c2ebd9e65 100644 --- a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp +++ b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp @@ -90,7 +90,7 @@ ChooseFromPropertyListFilter::ChooseFromPropertyListFilter(const NodeMetaInfo &i } else if (insertInfo.isQtQuick3DParticles3DParticle3D()) { if (parentInfo.isQtQuick3DParticles3DParticleEmitter3D()) propertyList.append("particle"); - } else if (insertInfo.isQtQuick3DParticleAbstractShape()) { + } else if (insertInfo.isQtQuick3DParticlesAbstractShape()) { if (parentInfo.isQtQuick3DParticles3DParticleEmitter3D() || parentInfo.isQtQuick3DParticles3DAttractor3D()) propertyList.append("shape"); @@ -100,9 +100,6 @@ ChooseFromPropertyListFilter::ChooseFromPropertyListFilter(const NodeMetaInfo &i } else if (insertInfo.typeName().startsWith("ComponentBundles.MaterialBundle")) { if (parentInfo.isQtQuick3DModel()) propertyList.append("materials"); - } else if (insertInfo.isEffectMaker()) { - if (parentInfo.isQtQuickItem()) - propertyList.append("effect"); } else if (insertInfo.isQtQuick3DBakedLightmap()) { if (parentInfo.isQtQuick3DModel()) propertyList.append("bakedLightmap"); diff --git a/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp b/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp index 360c893c2d7..24d4377ef29 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -71,7 +72,7 @@ LineEdit::LineEdit(QWidget *parent) setFixedHeight(29); } -void LineEdit::resizeEvent(QResizeEvent *) +void LineEdit::resizeEvent([[maybe_unused]] QResizeEvent *event) { QSize hint = clearButton->sizeHint(); int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); @@ -80,6 +81,31 @@ void LineEdit::resizeEvent(QResizeEvent *) (rect().bottom() + 1 - hint.height()) / 2); } +void LineEdit::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape && event->modifiers() == Qt::NoModifier) { + clear(); + event->accept(); + return; + } + QLineEdit::keyPressEvent(event); +} + +void LineEdit::paintEvent(QPaintEvent *event) +{ + if (text().isEmpty()) { + QPalette p(palette()); + p.setColor(QPalette::Active, + QPalette::PlaceholderText, + Utils::creatorTheme()->color(Utils::Theme::DSplaceholderTextColor)); + p.setColor(QPalette::Inactive, + QPalette::PlaceholderText, + Utils::creatorTheme()->color(Utils::Theme::DSplaceholderTextColor)); + setPalette(p); + } + QLineEdit::paintEvent(event); +} + void LineEdit::updateClearButton(const QString& text) { clearButton->setVisible(!text.isEmpty()); diff --git a/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.h b/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.h index 8b6da49cb3f..6cc375fd552 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.h +++ b/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.h @@ -19,7 +19,9 @@ public: LineEdit(QWidget *parent = nullptr); protected: - void resizeEvent(QResizeEvent *); + void resizeEvent(QResizeEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void paintEvent(QPaintEvent *event) override; private slots: void updateClearButton(const QString &text); diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index ceccf25554c..456f44e43c6 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -2,28 +2,29 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "navigatorview.h" + +#include "iconcheckboxitemdelegate.h" +#include "nameitemdelegate.h" #include "navigatortreemodel.h" #include "navigatorwidget.h" -#include "qmldesignerconstants.h" -#include "qmldesignericons.h" -#include "qmldesignerplugin.h" -#include "assetslibrarywidget.h" -#include "commontypecache.h" - -#include "nameitemdelegate.h" -#include "iconcheckboxitemdelegate.h" +#include #include -#include +#include #include +#include #include -#include +#include +#include #include -#include +#include +#include +#include +#include #include #include -#include #include +#include #include #include @@ -286,7 +287,6 @@ void NavigatorView::dragStarted(QMimeData *mimeData) if (assetType == Constants::MIME_TYPE_ASSET_EFFECT) { // We use arbitrary type name because at this time we don't have effect maker // specific type - m_widget->setDragType(Storage::Info::EffectMaker); m_widget->update(); } else if (assetType == Constants::MIME_TYPE_ASSET_TEXTURE3D) { m_widget->setDragType(Constants::MIME_TYPE_ASSET_TEXTURE3D); @@ -450,7 +450,7 @@ void NavigatorView::changeToComponent(const QModelIndex &index) const ModelNode doubleClickNode = modelNodeForIndex(index); if (doubleClickNode.metaInfo().isFileComponent()) Core::EditorManager::openEditor(Utils::FilePath::fromString( - doubleClickNode.metaInfo().componentFileName()), + ModelUtils::componentFilePath(doubleClickNode)), Utils::Id(), Core::EditorManager::DoNotMakeVisible); } @@ -468,7 +468,7 @@ QAbstractItemModel *NavigatorView::currentModel() const const ProjectExplorer::FileNode *NavigatorView::fileNodeForModelNode(const ModelNode &node) const { - QString filename = node.metaInfo().componentFileName(); + QString filename = ModelUtils::componentFilePath(node); Utils::FilePath filePath = Utils::FilePath::fromString(filename); ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile( filePath); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 3b991a5f622..eaa5ef95738 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -958,7 +958,7 @@ void PropertyEditorQmlBackend::setValueforAuxiliaryProperties(const QmlObjectNod QUrl PropertyEditorQmlBackend::getQmlUrlForMetaInfo(const NodeMetaInfo &metaInfo, TypeName &className) { if (metaInfo.isValid()) { - const NodeMetaInfos hierarchy = metaInfo.classHierarchy(); + const NodeMetaInfos hierarchy = metaInfo.selfAndPrototypes(); for (const NodeMetaInfo &info : hierarchy) { QUrl fileUrl = fileToUrl(locateQmlFile(info, QString::fromUtf8(qmlFileName(info)))); if (fileUrl.isValid()) { diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index 3f3ddaaf162..f042df5241b 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -339,7 +339,7 @@ void PropertyEditorValue::resetValue() void PropertyEditorValue::setEnumeration(const QString &scope, const QString &name) { - Enumeration newEnumeration(scope, name); + Enumeration newEnumeration(scope.toUtf8(), name.toUtf8()); setValueWithEmit(QVariant::fromValue(newEnumeration)); } diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 5d00f373c49..ba6078a778a 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -450,7 +450,7 @@ void PropertyEditorView::setupQmlBackend() TypeName diffClassName; if (commonAncestor.isValid()) { diffClassName = commonAncestor.typeName(); - const NodeMetaInfos hierarchy = commonAncestor.classHierarchy(); + const NodeMetaInfos hierarchy = commonAncestor.selfAndPrototypes(); for (const NodeMetaInfo &metaInfo : hierarchy) { if (PropertyEditorQmlBackend::checkIfUrlExists(qmlSpecificsFile)) break; diff --git a/src/plugins/qmldesigner/components/resources/dockwidgets.css b/src/plugins/qmldesigner/components/resources/dockwidgets.css index 4ff1b46f52d..2fc05b6014f 100644 --- a/src/plugins/qmldesigner/components/resources/dockwidgets.css +++ b/src/plugins/qmldesigner/components/resources/dockwidgets.css @@ -93,6 +93,20 @@ ADS--TitleBarButton:press { background: creatorTheme.DStitleBarButtonPress; } +#floatingTitleMaxButton { + margin: 1px; + background: none; + border: none; +} + +#floatingTitleMaxButton:hover { + background: creatorTheme.DStitleBarButtonHover; +} + +#floatingTitleMaxButton:pressed { + background: creatorTheme.DStitleBarButtonPress; +} + QScrollArea#dockWidgetScrollArea { background-color: creatorTheme.DSpanelBackground; padding: 0px; @@ -139,3 +153,180 @@ ADS--DockAreaTitleBar { ADS--DockAreaWidget[focused="true"] ADS--DockAreaTitleBar { border-bottom-color: creatorTheme.DStabFocusBackground; } + +ADS--AutoHideTab { + qproperty-iconSize: 16px 16px; /* this is optional in case you would like to change icon size */ + background: none; + border: none; + padding-left: 2px; + padding-right: 0px; + text-align: center; + min-height: 20px; + padding-bottom: 2px; +} + +ADS--AutoHideTab:hover { + color: creatorTheme.DSinteraction; +} + +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="0"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="2"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="1"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + +ADS--AutoHideTab:hover[iconOnly="false"][sideBarLocation="0"], +ADS--AutoHideTab:hover[iconOnly="false"][sideBarLocation="2"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="0"][activeTab="true"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="2"][activeTab="true"] { + border-top: 6px solid creatorTheme.DSinteraction; +} + +ADS--AutoHideTab:hover[iconOnly="false"][sideBarLocation="1"], +ADS--AutoHideTab:hover[iconOnly="false"][sideBarLocation="3"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="1"][activeTab="true"], +ADS--AutoHideTab[iconOnly="false"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid creatorTheme.DSinteraction; +} + +/* Auto hide tabs with icon only */ +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="0"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="1"] { + border-left: 6px solid rgba(0, 0, 0, 48); +} + +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="2"] { + border-right: 6px solid rgba(0, 0, 0, 48); +} + +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + +/* Auto hide tabs with icon only hover */ +ADS--AutoHideTab:hover[iconOnly="true"][sideBarLocation="0"], +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="0"][activeTab="true"] { + border-top: 6px solid creatorTheme.DSinteraction; +} + +ADS--AutoHideTab:hover[iconOnly="true"][sideBarLocation="1"], +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="1"][activeTab="true"] { + border-left: 6px solid creatorTheme.DSinteraction; +} + +ADS--AutoHideTab:hover[iconOnly="true"][sideBarLocation="2"], +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="2"][activeTab="true"] { + border-right: 6px solid creatorTheme.DSinteraction; +} + +ADS--AutoHideTab:hover[iconOnly="true"][sideBarLocation="3"], +ADS--AutoHideTab[iconOnly="true"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid creatorTheme.DSinteraction; +} + +/* AutoHideSideBar */ +ADS--AutoHideSideBar { + background: palette(window); + border: none; + qproperty-spacing: 12; +} + +#sideTabsContainerWidget { + background: transparent; +} + +ADS--AutoHideSideBar[sideBarLocation="0"] { + border-bottom: 1px solid palette(dark); +} + +ADS--AutoHideSideBar[sideBarLocation="1"] { + border-right: 1px solid palette(dark); +} + +ADS--AutoHideSideBar[sideBarLocation="2"] { + border-left: 1px solid palette(dark); +} + +ADS--AutoHideSideBar[sideBarLocation="3"] { + border-top: 1px solid palette(dark); +} + +/* AutoHideDockContainer */ +ADS--AutoHideDockContainer { + background: palette(window); +} + +ADS--AutoHideDockContainer ADS--DockAreaTitleBar { + background: creatorTheme.DSinteraction; + padding: 0px; + border: none; +} + +/* + * This is required because the ADS--DockAreaWidget[focused="true"] will + * overwrite the ADS--AutoHideDockContainer ADS--DockAreaTitleBar rule + */ +ADS--AutoHideDockContainer ADS--DockAreaWidget[focused="true"] ADS--DockAreaTitleBar { + background: creatorTheme.DSinteraction; + padding: 0px; + border: none; +} + +#autoHideTitleLabel { + padding-left: 4px; + color: palette(light); +} + +/* AutoHideDockContainer titlebar buttons */ +#dockAreaAutoHideButton { + /*qproperty-icon: url(:/ads/images/vs-pin-button.svg);*/ + qproperty-iconSize: 16px; +} + +ADS--AutoHideDockContainer #dockAreaAutoHideButton { + /*qproperty-icon: url(:/ads/images/vs-pin-button-pinned-focused.svg);*/ + qproperty-iconSize: 16px; +} + +ADS--AutoHideDockContainer #dockAreaCloseButton{ + /*qproperty-icon: url(:/ads/images/close-button-focused.svg)*/ +} + +ADS--AutoHideDockContainer ADS--TitleBarButton:hover { + background: rgba(255, 255, 255, 48); +} + +ads--CAutoHideDockContainer ADS--TitleBarButton:pressed { + background: rgba(255, 255, 255, 96); +} + +/* AutoHideDockContainer Titlebar and Buttons */ + +/* ResizeHandle */ +ADS--ResizeHandle { + background: palette(window); +} + + +ADS--AutoHideDockContainer[sideBarLocation="0"] ADS--ResizeHandle { + border-top: 1px solid palette(dark); +} + +ADS--AutoHideDockContainer[sideBarLocation="1"] ADS--ResizeHandle { + border-left: 1px solid palette(dark); +} + +ADS--AutoHideDockContainer[sideBarLocation="2"] ADS--ResizeHandle { + border-right: 1px solid palette(dark); +} + +ADS--AutoHideDockContainer[sideBarLocation="3"] ADS--ResizeHandle { + border-top: 1px solid palette(dark); +} diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp b/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp index 6c6102036ac..0826df54848 100644 --- a/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp +++ b/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp @@ -16,12 +16,13 @@ #include #include -#include #include +#include #include #include #include #include +#include #include #include @@ -97,9 +98,12 @@ void TextEditorView::modelAboutToBeDetached(Model *model) m_widget->setTextEditor(nullptr); // in case the user closed it explicit we do not want to do anything with the editor - if (TextEditor::BaseTextEditor *textEditor = - QmlDesignerPlugin::instance()->currentDesignDocument()->textEditor()) { - QmlDesignerPlugin::instance()->emitCurrentTextEditorChanged(textEditor); + if (Core::ModeManager::currentModeId() == Core::Constants::MODE_DESIGN) { + if (TextEditor::BaseTextEditor *textEditor = QmlDesignerPlugin::instance() + ->currentDesignDocument() + ->textEditor()) { + QmlDesignerPlugin::instance()->emitCurrentTextEditorChanged(textEditor); + } } } diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index 98601ce45ed..73c3c65f30e 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -416,7 +416,7 @@ void TextureEditorView::setupQmlBackend() TypeName diffClassName; if (NodeMetaInfo metaInfo = m_selectedTexture.metaInfo()) { diffClassName = metaInfo.typeName(); - for (const NodeMetaInfo &metaInfo : metaInfo.classHierarchy()) { + for (const NodeMetaInfo &metaInfo : metaInfo.selfAndPrototypes()) { if (PropertyEditorQmlBackend::checkIfUrlExists(qmlSpecificsUrl)) break; qmlSpecificsUrl = PropertyEditorQmlBackend::getQmlFileUrl(metaInfo.typeName() diff --git a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp index c917dfe8a84..99c46b43b33 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp +++ b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp @@ -132,17 +132,21 @@ void CrumbleBarModel::onCrumblePathElementClicked(int i) WorkspaceModel::WorkspaceModel(QObject *) { - connect(designModeWidget(), &Internal::DesignModeWidget::initialized, this, [this]() { + auto connectDockManager = [this]() -> bool { const auto dockManager = designModeWidget()->dockManager(); + if (!dockManager) + return false; connect(dockManager, &ADS::DockManager::workspaceListChanged, this, [this]() { beginResetModel(); endResetModel(); }); - beginResetModel(); endResetModel(); - }); + return true; + }; + if (!connectDockManager()) + connect(designModeWidget(), &Internal::DesignModeWidget::initialized, this, connectDockManager); } int WorkspaceModel::rowCount(const QModelIndex &) const @@ -288,19 +292,25 @@ ToolBarBackend::ToolBarBackend(QObject *parent) this, &ToolBarBackend::currentStyleChanged); - connect(designModeWidget(), &Internal::DesignModeWidget::initialized, this, [this]() { + auto connectDockManager = [this]() -> bool { const auto dockManager = designModeWidget()->dockManager(); + if (!dockManager) + return false; - connect(dockManager, &ADS::DockManager::workspaceLoaded, this, [this](const QString &) { - emit currentWorkspaceChanged(); - }); - - connect(dockManager, &ADS::DockManager::workspaceListChanged, this, [this]() { - emit currentWorkspaceChanged(); - }); - + connect(dockManager, + &ADS::DockManager::workspaceLoaded, + this, + &ToolBarBackend::currentWorkspaceChanged); + connect(dockManager, + &ADS::DockManager::workspaceListChanged, + this, + &ToolBarBackend::currentWorkspaceChanged); emit currentWorkspaceChanged(); - }); + return true; + }; + + if (!connectDockManager()) + connect(designModeWidget(), &Internal::DesignModeWidget::initialized, this, connectDockManager); auto editorManager = Core::EditorManager::instance(); diff --git a/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h b/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h index 4c12af14fef..b93626a331b 100644 --- a/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h +++ b/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h @@ -108,6 +108,8 @@ inline constexpr AuxiliaryDataKeyView rotBlockProperty{AuxiliaryDataType::NodeIn inline constexpr AuxiliaryDataKeyView languageProperty{AuxiliaryDataType::Temporary, "language"}; inline constexpr AuxiliaryDataKeyView bakeLightsManualProperty{AuxiliaryDataType::Document, "bakeLightsManual"}; +inline constexpr AuxiliaryDataKeyView contextImageProperty{AuxiliaryDataType::Temporary, + "contextImage"}; // Most material preview aux properties are duplicated as document and instance types, as they // are both required to be persistent and used at runtime to control material preview rendering diff --git a/src/plugins/qmldesigner/designercore/include/import.h b/src/plugins/qmldesigner/designercore/include/import.h index f0a5b8e7700..5dbab393d21 100644 --- a/src/plugins/qmldesigner/designercore/include/import.h +++ b/src/plugins/qmldesigner/designercore/include/import.h @@ -44,6 +44,11 @@ public: class QMLDESIGNERCORE_EXPORT Import { + using Imports = QList; + QMLDESIGNERCORE_EXPORT friend Imports set_strict_difference(const Imports &first, + const Imports &second); + enum class Type { Empty, Library, File }; + public: Import() = default; @@ -51,14 +56,14 @@ public: static Import createFileImport(const QString &file, const QString &version = QString(), const QString &alias = QString(), const QStringList &importPaths = QStringList()); static Import empty(); - bool isEmpty() const { return m_url.isEmpty() && m_file.isEmpty(); } - bool isFileImport() const { return m_url.isEmpty() && !m_file.isEmpty(); } - bool isLibraryImport() const { return !m_url.isEmpty() && m_file.isEmpty(); } + bool isEmpty() const { return m_type == Type::Empty; } + bool isFileImport() const { return m_type == Type::File; } + bool isLibraryImport() const { return m_type == Type::Library; } bool hasVersion() const { return !m_version.isEmpty(); } bool hasAlias() const { return !m_alias.isEmpty(); } - const QString &url() const { return m_url; } - const QString &file() const { return m_file; } + const QString &url() const { return m_type == Type::Library ? m_url : emptyString; } + const QString &file() const { return m_type == Type::File ? m_url : emptyString; } const QString &version() const { return m_version; } const QString &alias() const { return m_alias; } const QStringList &importPaths() const { return m_importPathList; } @@ -76,32 +81,40 @@ public: friend bool operator==(const Import &first, const Import &second) { - return first.m_url == second.m_url && first.m_file == second.m_file + return first.m_url == second.m_url && first.m_type == second.m_type && (first.m_version == second.m_version || first.m_version.isEmpty() || second.m_version.isEmpty()); } friend bool operator<(const Import &first, const Import &second) { - return std::tie(first.m_url, first.m_file) < std::tie(second.m_url, second.m_file); + return std::tie(first.m_url, first.m_type) < std::tie(second.m_url, second.m_type); } private: - Import(const QString &url, const QString &file, const QString &version, const QString &alias, const QStringList &importPaths); + Import(const QString &url, + const QString &version, + const QString &alias, + const QStringList &importPaths, + Type type); private: + inline static const QString emptyString; QString m_url; - QString m_file; QString m_version; QString m_alias; QStringList m_importPathList; + Type m_type = Type::Empty; }; QMLDESIGNERCORE_EXPORT size_t qHash(const Import &import); using Imports = QList; -QMLDESIGNERCORE_EXPORT Imports difference(const Imports &first, const Imports &second); +QMLDESIGNERCORE_EXPORT Imports set_difference(const Imports &first, const Imports &second); +QMLDESIGNERCORE_EXPORT Imports set_stict_difference(const Imports &first, const Imports &second); +QMLDESIGNERCORE_EXPORT Imports set_union(const Imports &first, const Imports &second); +QMLDESIGNERCORE_EXPORT Imports set_intersection(const Imports &first, const Imports &second); template void differenceCall(const Imports &first, const Imports &second, Callable callable) diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index 3dbb0cceac2..1577fc24319 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -62,12 +63,16 @@ class QMLDESIGNERCORE_EXPORT Model : public QObject public: enum ViewNotification { NotifyView, DoNotNotifyView }; - Model(ProjectStorageType &projectStorage, + Model(ProjectStorageDependencies projectStorageDependencies, const TypeName &type, int major = 1, int minor = 1, Model *metaInfoProxyModel = nullptr, std::unique_ptr resourceManagement = {}); + Model(ProjectStorageDependencies projectStorageDependencies, + Utils::SmallStringView typeName, + Imports imports, + const QUrl &fileUrl); Model(const TypeName &typeName, int major = 1, int minor = 1, @@ -86,17 +91,29 @@ public: new Model(typeName, major, minor, metaInfoProxyModel, std::move(resourceManagement))); } - static ModelPointer create(ProjectStorageType &projectStorage, + static ModelPointer create(ProjectStorageDependencies projectStorageDependencies, + Utils::SmallStringView typeName, + Imports imports, + const QUrl &fileUrl) + { + return ModelPointer(new Model(projectStorageDependencies, typeName, imports, fileUrl)); + } + static ModelPointer create(ProjectStorageDependencies m_projectStorageDependencies, const TypeName &typeName, int major = 1, int minor = 1, std::unique_ptr resourceManagement = {}) { - return ModelPointer( - new Model(projectStorage, typeName, major, minor, nullptr, std::move(resourceManagement))); + return ModelPointer(new Model(m_projectStorageDependencies, + typeName, + major, + minor, + nullptr, + std::move(resourceManagement))); } QUrl fileUrl() const; + SourceId fileUrlSourceId() const; void setFileUrl(const QUrl &url); const MetaInfo metaInfo() const; @@ -105,12 +122,16 @@ public: bool hasNodeMetaInfo(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const; void setMetaInfo(const MetaInfo &metaInfo); + NodeMetaInfo boolMetaInfo() const; NodeMetaInfo flowViewFlowActionAreaMetaInfo() const; NodeMetaInfo flowViewFlowDecisionMetaInfo() const; NodeMetaInfo flowViewFlowItemMetaInfo() const; NodeMetaInfo flowViewFlowTransitionMetaInfo() const; NodeMetaInfo flowViewFlowWildcardMetaInfo() const; NodeMetaInfo fontMetaInfo() const; + NodeMetaInfo qmlQtObjectMetaInfo() const; + NodeMetaInfo qtQmlModelsListModelMetaInfo() const; + NodeMetaInfo qtQmlModelsListElementMetaInfo() const; NodeMetaInfo qtQuick3DBakedLightmapMetaInfo() const; NodeMetaInfo qtQuick3DDefaultMaterialMetaInfo() const; NodeMetaInfo qtQuick3DMaterialMetaInfo() const; @@ -145,6 +166,7 @@ public: QHash idModelNodeDict(); ModelNode createModelNode(const TypeName &typeName); + void changeRootNodeType(const TypeName &type); void removeModelNodes(ModelNodes nodes, BypassModelResourceManagement = BypassModelResourceManagement::No); @@ -157,7 +179,7 @@ public: const Imports &imports() const; const Imports &possibleImports() const; const Imports &usedImports() const; - void changeImports(const Imports &importsToBeAdded, const Imports &importsToBeRemoved); + void changeImports(Imports importsToBeAdded, Imports importsToBeRemoved); void setPossibleImports(Imports possibleImports); void setUsedImports(Imports usedImports); bool hasImport(const Import &import, bool ignoreAlias = true, bool allowHigherVersion = false) const; @@ -196,6 +218,8 @@ public: void endDrag(); NotNullPointer projectStorage() const; + const PathCacheType &pathCache() const; + PathCacheType &pathCache(); private: template diff --git a/src/plugins/qmldesigner/designercore/include/modelfwd.h b/src/plugins/qmldesigner/designercore/include/modelfwd.h index 44154ce10b1..2483893d9a8 100644 --- a/src/plugins/qmldesigner/designercore/include/modelfwd.h +++ b/src/plugins/qmldesigner/designercore/include/modelfwd.h @@ -16,6 +16,9 @@ using PropertyTypeList = QList; using IdName = QByteArray; class Model; class ModelNode; +class NonLockingMutex; +template +class SourcePathCache; struct ModelDeleter { @@ -35,10 +38,18 @@ constexpr bool useProjectStorage() #ifdef QDS_MODEL_USE_PROJECTSTORAGEINTERFACE using ProjectStorageType = ProjectStorageInterface; +using PathCacheType = SourcePathCacheInterface; #else using ProjectStorageType = ProjectStorage; +using PathCacheType = SourcePathCache; #endif +struct ProjectStorageDependencies +{ + ProjectStorageType &storage; + PathCacheType &cache; +}; + enum class PropertyType { None, Variant, diff --git a/src/plugins/qmldesigner/designercore/include/modelnode.h b/src/plugins/qmldesigner/designercore/include/modelnode.h index 657093b0ac0..9c4b8fb4efe 100644 --- a/src/plugins/qmldesigner/designercore/include/modelnode.h +++ b/src/plugins/qmldesigner/designercore/include/modelnode.h @@ -81,7 +81,7 @@ public: ModelNode &operator=(const ModelNode &) = default; ModelNode(ModelNode &&) = default; ModelNode &operator=(ModelNode &&) noexcept = default; - ~ModelNode(); + ~ModelNode() = default; TypeName type() const; QString simplifiedTypeName() const; @@ -182,6 +182,7 @@ public: QVariant auxiliaryDataWithDefault(AuxiliaryDataKeyView key) const; QVariant auxiliaryDataWithDefault(AuxiliaryDataKeyDefaultValue key) const; void setAuxiliaryData(AuxiliaryDataKeyView key, const QVariant &data) const; + void setAuxiliaryDataWithoutLock(AuxiliaryDataKeyView key, const QVariant &data) const; void setAuxiliaryData(AuxiliaryDataType type, Utils::SmallStringView name, const QVariant &data) const; void setAuxiliaryDataWithoutLock(AuxiliaryDataType type, Utils::SmallStringView name, @@ -267,7 +268,7 @@ public: } private: // functions - Internal::InternalNodePointer internalNode() const; + Internal::InternalNodePointer internalNode() const { return m_internalNode; } bool hasLocked() const; diff --git a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h index 55ce166d904..0ee4e19fe37 100644 --- a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h +++ b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h @@ -53,6 +53,8 @@ public: TypeId id() const { return m_typeId; } bool isFileComponent() const; + bool isProjectComponent() const; + bool isInProjectModule() const; bool hasProperty(::Utils::SmallStringView propertyName) const; PropertyMetaInfos properties() const; PropertyMetaInfos localProperties() const; @@ -63,8 +65,8 @@ public: PropertyMetaInfo defaultProperty() const; bool hasDefaultProperty() const; - std::vector classHierarchy() const; - std::vector superClasses() const; + std::vector selfAndPrototypes() const; + std::vector prototypes() const; NodeMetaInfo commonBase(const NodeMetaInfo &metaInfo) const; bool defaultPropertyIsComponent() const; @@ -74,6 +76,10 @@ public: int majorVersion() const; int minorVersion() const; + Storage::Info::ExportedTypeNames allExportedTypeNames() const; + Storage::Info::ExportedTypeNames exportedTypeNamesForSourceId(SourceId sourceId) const; + + SourceId sourceId() const; QString componentFileName() const; bool isBasedOn(const NodeMetaInfo &metaInfo) const; @@ -107,7 +113,6 @@ public: bool isAlias() const; bool isBool() const; bool isColor() const; - bool isEffectMaker() const; bool isFloat() const; bool isFlowViewFlowActionArea() const; bool isFlowViewFlowDecision() const; @@ -121,10 +126,10 @@ public: bool isInteger() const; bool isLayoutable() const; bool isListOrGridView() const; + bool isNumber() const; bool isQmlComponent() const; bool isQtMultimediaSoundEffect() const; bool isQtObject() const; - bool isQtQuick3D() const; bool isQtQuick3DBakedLightmap() const; bool isQtQuick3DBuffer() const; bool isQtQuick3DCamera() const; @@ -137,7 +142,7 @@ public: bool isQtQuick3DMaterial() const; bool isQtQuick3DModel() const; bool isQtQuick3DNode() const; - bool isQtQuick3DParticleAbstractShape() const; + bool isQtQuick3DParticlesAbstractShape() const; bool isQtQuick3DParticles3DAffector3D() const; bool isQtQuick3DParticles3DAttractor3D() const; bool isQtQuick3DParticles3DParticle3D() const; diff --git a/src/plugins/qmldesigner/designercore/include/projectstorageids.h b/src/plugins/qmldesigner/designercore/include/projectstorageids.h index b4e4aa11f82..bc66e0d2b20 100644 --- a/src/plugins/qmldesigner/designercore/include/projectstorageids.h +++ b/src/plugins/qmldesigner/designercore/include/projectstorageids.h @@ -5,6 +5,8 @@ #include +#include + namespace QmlDesigner { enum class BasicIdType { @@ -48,6 +50,7 @@ using SourceIds = std::vector; using ModuleId = Sqlite::BasicId; using ModuleIds = std::vector; +using ModuleIdSpan = Utils::span; using ProjectPartId = Sqlite::BasicId; using ProjectPartIds = std::vector; diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index fee28a9dd3a..bdbb2b7ee7b 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -3,66 +3,67 @@ #include "nodeinstanceview.h" -#include "abstractproperty.h" -#include "bindingproperty.h" -#include "captureddatacommand.h" -#include "changeauxiliarycommand.h" -#include "changebindingscommand.h" -#include "changefileurlcommand.h" -#include "changeidscommand.h" -#include "changelanguagecommand.h" -#include "changenodesourcecommand.h" -#include "changepreviewimagesizecommand.h" -#include "changeselectioncommand.h" -#include "changestatecommand.h" -#include "changevaluescommand.h" -#include "childrenchangedcommand.h" -#include "clearscenecommand.h" -#include "completecomponentcommand.h" -#include "componentcompletedcommand.h" -#include "connectionmanagerinterface.h" -#include "createinstancescommand.h" -#include "createscenecommand.h" -#include "debugoutputcommand.h" -#include "informationchangedcommand.h" -#include "imageutils.h" -#include "inputeventcommand.h" -#include "nodeabstractproperty.h" -#include "nodeinstanceserverproxy.h" -#include "nodelistproperty.h" -#include "pixmapchangedcommand.h" -#include "puppettocreatorcommand.h" -#include "qml3dnode.h" -#include "qmlchangeset.h" -#include "qmldesignerconstants.h" -#include "qmlstate.h" -#include "qmltimeline.h" -#include "qmltimelinekeyframegroup.h" -#include "qmlvisualnode.h" -#include "removeinstancescommand.h" -#include "removepropertiescommand.h" -#include "removesharedmemorycommand.h" -#include "reparentinstancescommand.h" -#include "scenecreatedcommand.h" -#include "statepreviewimagechangedcommand.h" -#include "tokencommand.h" -#include "update3dviewstatecommand.h" -#include "valueschangedcommand.h" -#include "variantproperty.h" -#include "view3dactioncommand.h" -#include "requestmodelnodepreviewimagecommand.h" -#include "nanotracecommand.h" -#include "nanotrace/nanotrace.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include #include +#include #include #include -#include #include +#include #include @@ -1077,18 +1078,20 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand() if (parentTakesOverRendering(instance.modelNode())) nodeFlags |= InstanceContainer::ParentTakesOverRendering; + const auto modelNode = instance.modelNode(); InstanceContainer container(instance.instanceId(), - instance.modelNode().type(), - instance.modelNode().majorVersion(), - instance.modelNode().minorVersion(), - instance.modelNode().metaInfo().componentFileName(), - instance.modelNode().nodeSource(), + modelNode.type(), + modelNode.majorVersion(), + modelNode.minorVersion(), + ModelUtils::componentFilePath(modelNode), + modelNode.nodeSource(), nodeSourceType, nodeMetaType, nodeFlags); - if (!parentIsBehavior(instance.modelNode())) + if (!parentIsBehavior(modelNode)) { instanceContainerList.append(container); + } } QVector reparentContainerList; @@ -1243,12 +1246,13 @@ CreateInstancesCommand NodeInstanceView::createCreateInstancesCommand(const QLis if (parentTakesOverRendering(instance.modelNode())) nodeFlags |= InstanceContainer::ParentTakesOverRendering; + const auto modelNode = instance.modelNode(); InstanceContainer container(instance.instanceId(), - instance.modelNode().type(), - instance.modelNode().majorVersion(), - instance.modelNode().minorVersion(), - instance.modelNode().metaInfo().componentFileName(), - instance.modelNode().nodeSource(), + modelNode.type(), + modelNode.majorVersion(), + modelNode.minorVersion(), + ModelUtils::componentFilePath(modelNode), + modelNode.nodeSource(), nodeSourceType, nodeMetaType, nodeFlags); @@ -1303,17 +1307,20 @@ ChangeValuesCommand NodeInstanceView::createChangeValueCommand(const QList containerList; - const bool reflectionFlag = m_puppetTransaction.isValid() && (!currentTimeline().isValid() || !currentTimeline().isRecording()); + bool reflectionFlag = m_puppetTransaction.isValid() + && (!currentTimeline().isValid() || !currentTimeline().isRecording()); for (const VariantProperty &property : propertyList) { ModelNode node = property.parentModelNode(); + if (QmlPropertyChanges::isValidQmlPropertyChanges(node)) + reflectionFlag = false; + if (node.isValid() && hasInstanceForModelNode(node)) { NodeInstance instance = instanceForModelNode(node); PropertyValueContainer container(instance.instanceId(), property.name(), property.value(), property.dynamicTypeName()); container.setReflectionFlag(reflectionFlag); containerList.append(container); } - } return ChangeValuesCommand(containerList); @@ -1783,9 +1790,9 @@ void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node, if (renderInstance.isValid()) renderItemId = renderInstance.instanceId(); if (renderNode.isComponent()) - componentPath = renderNode.metaInfo().componentFileName(); + componentPath = ModelUtils::componentFilePath(renderNode); } else if (node.isComponent()) { - componentPath = node.metaInfo().componentFileName(); + componentPath = ModelUtils::componentFilePath(node); } const double ratio = m_externalDependencies.formEditorDevicePixelRatio(); const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp index ece5b4bf6f1..8cdc590135f 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp @@ -80,7 +80,7 @@ QmlDesigner::NodeHints::NodeHints(const ModelNode &node) : m_modelNode(node) if (!itemLibraryEntryList.isEmpty()) m_hints = itemLibraryEntryList.constFirst().hints(); } else { /* If we have meta information we run the complete type hierarchy and check for hints */ - const auto classHierarchy = m_modelNode.metaInfo().classHierarchy(); + const auto classHierarchy = m_modelNode.metaInfo().selfAndPrototypes(); for (const NodeMetaInfo &metaInfo : classHierarchy) { QList itemLibraryEntryList = libraryInfo->entriesForType( metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion()); diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp index fd25d9c450d..5121e6a663c 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp @@ -1426,6 +1426,24 @@ bool NodeMetaInfo::isFileComponent() const return isValid() && m_privateData->isFileComponent(); } +bool NodeMetaInfo::isProjectComponent() const +{ + if constexpr (useProjectStorage()) { + return isValid() && bool(typeData().traits & Storage::TypeTraits::IsProjectComponent); + } + + return false; +} + +bool NodeMetaInfo::isInProjectModule() const +{ + if constexpr (useProjectStorage()) { + return isValid() && bool(typeData().traits & Storage::TypeTraits::IsInProjectModule); + } + + return false; +} + bool NodeMetaInfo::hasProperty(Utils::SmallStringView propertyName) const { if constexpr (useProjectStorage()) @@ -1568,31 +1586,30 @@ PropertyMetaInfo NodeMetaInfo::defaultProperty() const bool NodeMetaInfo::hasDefaultProperty() const { if constexpr (useProjectStorage()) - return bool(typeData().defaultPropertyId); + return isValid() && bool(typeData().defaultPropertyId); else return !defaultPropertyName().isEmpty(); } -NodeMetaInfos NodeMetaInfo::classHierarchy() const +std::vector NodeMetaInfo::selfAndPrototypes() const { if constexpr (useProjectStorage()) { - NodeMetaInfos hierarchy; - const auto typeIds = m_projectStorage->prototypeAndSelfIds(m_typeId); - hierarchy.reserve(typeIds.size()); - - for (TypeId typeId : typeIds) - hierarchy.emplace_back(typeId, m_projectStorage); - - return hierarchy; + if (isValid()) { + return Utils::transform( + m_projectStorage->prototypeAndSelfIds(m_typeId), [&](TypeId typeId) { + return NodeMetaInfo{typeId, m_projectStorage}; + }); + } } else { if (isValid()) { NodeMetaInfos hierarchy = {*this}; Model *model = m_privateData->model(); - for (const TypeDescription &type : m_privateData->prototypes()) + for (const TypeDescription &type : m_privateData->prototypes()) { hierarchy.emplace_back(model, type.className.toUtf8(), type.majorVersion, type.minorVersion); + } return hierarchy; } @@ -1601,17 +1618,15 @@ NodeMetaInfos NodeMetaInfo::classHierarchy() const return {}; } -NodeMetaInfos NodeMetaInfo::superClasses() const +NodeMetaInfos NodeMetaInfo::prototypes() const { if constexpr (useProjectStorage()) { - NodeMetaInfos hierarchy; - const auto typeIds = m_projectStorage->prototypeIds(m_typeId); - hierarchy.reserve(typeIds.size()); - - for (TypeId typeId : typeIds) - hierarchy.emplace_back(typeId, m_projectStorage); - - return hierarchy; + if (isValid()) { + return Utils::transform( + m_projectStorage->prototypeIds(m_typeId), [&](TypeId typeId) { + return NodeMetaInfo{typeId, m_projectStorage}; + }); + } } else { if (isValid()) { NodeMetaInfos hierarchy; @@ -1673,18 +1688,61 @@ int NodeMetaInfo::minorVersion() const return -1; } +Storage::Info::ExportedTypeNames NodeMetaInfo::allExportedTypeNames() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) { + return m_projectStorage->exportedTypeNames(m_typeId); + } + } + + return {}; +} + +Storage::Info::ExportedTypeNames NodeMetaInfo::exportedTypeNamesForSourceId(SourceId sourceId) const +{ + if constexpr (useProjectStorage()) { + if (isValid()) { + return m_projectStorage->exportedTypeNames(m_typeId, sourceId); + } + } + + return {}; +} + +SourceId NodeMetaInfo::sourceId() const +{ + if constexpr (useProjectStorage()) { + if (isValid()) { + return typeData().sourceId; + } + } + + return SourceId{}; +} + QString NodeMetaInfo::componentFileName() const { - if (isValid()) - return m_privateData->componentFileName(); + if constexpr (!useProjectStorage()) { + if (isValid()) { + return m_privateData->componentFileName(); + } + } else { + if (isValid()) { + return m_privateData->componentFileName(); + } + } return {}; } QString NodeMetaInfo::importDirectoryPath() const { - if (isValid()) - return m_privateData->importDirectoryPath(); + if constexpr (!useProjectStorage()) { + if (isValid()) { + return m_privateData->importDirectoryPath(); + } + } return {}; } @@ -1718,7 +1776,7 @@ bool NodeMetaInfo::isSubclassOf(const TypeName &type, int majorVersion, int mino stringIdentifier(type, majorVersion, minorVersion))) return false; //take a shortcut - optimization - const NodeMetaInfos superClassList = superClasses(); + const NodeMetaInfos superClassList = prototypes(); for (const NodeMetaInfo &superClass : superClassList) { if (superClass.m_privateData->cleverCheckType(type)) { m_privateData->prototypeCachePositives().insert( @@ -1732,8 +1790,27 @@ bool NodeMetaInfo::isSubclassOf(const TypeName &type, int majorVersion, int mino bool NodeMetaInfo::isSuitableForMouseAreaFill() const { - return isSubclassOf("QtQuick.Item") && !isSubclassOf("QtQuick.MouseArea") - && !isSubclassOf("QtQuick.Controls.Control") && !isSubclassOf("QtQuick.Templates.Control"); + if constexpr (useProjectStorage()) { + if (!isValid()) { + return false; + } + + using namespace Storage::Info; + auto itemId = m_projectStorage->commonTypeId(); + auto mouseAreaId = m_projectStorage->commonTypeId(); + auto controlsControlId = m_projectStorage->commonTypeId(); + auto templatesControlId = m_projectStorage->commonTypeId(); + + return m_projectStorage->isBasedOn(m_typeId, + itemId, + mouseAreaId, + controlsControlId, + templatesControlId); + } else { + return isSubclassOf("QtQuick.Item") && !isSubclassOf("QtQuick.MouseArea") + && !isSubclassOf("QtQuick.Controls.Control") + && !isSubclassOf("QtQuick.Templates.Control"); + } } bool NodeMetaInfo::isBasedOn(const NodeMetaInfo &metaInfo) const @@ -1926,6 +2003,10 @@ namespace { template bool isBasedOnCommonType(NotNullPointer projectStorage, TypeId typeId) { + if (!typeId) { + return false; + } + auto base = projectStorage->commonTypeId(); return projectStorage->isBasedOn(typeId, base); @@ -1935,6 +2016,10 @@ bool isBasedOnCommonType(NotNullPointer projectStorage bool NodeMetaInfo::isGraphicalItem() const { if constexpr (useProjectStorage()) { + if (!isValid()) { + return false; + } + using namespace Storage::Info; auto itemId = m_projectStorage->commonTypeId(); auto windowId = m_projectStorage->commonTypeId(); @@ -1963,6 +2048,10 @@ bool NodeMetaInfo::isQtObject() const bool NodeMetaInfo::isLayoutable() const { if constexpr (useProjectStorage()) { + if (!isValid()) { + return false; + } + using namespace Storage::Info; auto positionerId = m_projectStorage->commonTypeId(); auto layoutId = m_projectStorage->commonTypeId(); @@ -1990,6 +2079,10 @@ bool NodeMetaInfo::isQtQuickLayoutsLayout() const bool NodeMetaInfo::isView() const { if constexpr (useProjectStorage()) { + if (!isValid()) { + return false; + } + using namespace Storage::Info; auto listViewId = m_projectStorage->commonTypeId(); auto gridViewId = m_projectStorage->commonTypeId(); @@ -2018,7 +2111,7 @@ bool NodeMetaInfo::isVector2D() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; - return isTypeId(m_typeId, m_projectStorage->commonTypeId()); + return isValid() && isTypeId(m_typeId, m_projectStorage->commonTypeId()); } else { if (!m_privateData) return false; @@ -2033,7 +2126,7 @@ bool NodeMetaInfo::isVector3D() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; - return isTypeId(m_typeId, m_projectStorage->commonTypeId()); + return isValid() && isTypeId(m_typeId, m_projectStorage->commonTypeId()); } else { if (!m_privateData) return false; @@ -2048,7 +2141,7 @@ bool NodeMetaInfo::isVector4D() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; - return isTypeId(m_typeId, m_projectStorage->commonTypeId()); + return isValid() && isTypeId(m_typeId, m_projectStorage->commonTypeId()); } else { if (!m_privateData) return false; @@ -2134,6 +2227,10 @@ bool NodeMetaInfo::isQtQuickTimelineKeyframeGroup() const bool NodeMetaInfo::isListOrGridView() const { if constexpr (useProjectStorage()) { + if (!isValid()) { + return false; + } + using namespace Storage::Info; auto listViewId = m_projectStorage->commonTypeId(); auto gridViewId = m_projectStorage->commonTypeId(); @@ -2143,6 +2240,31 @@ bool NodeMetaInfo::isListOrGridView() const } } +bool NodeMetaInfo::isNumber() const +{ + if constexpr (useProjectStorage()) { + if (!isValid()) { + return false; + } + + using namespace Storage::Info; + auto intId = m_projectStorage->builtinTypeId(); + auto uintId = m_projectStorage->builtinTypeId(); + auto floatId = m_projectStorage->builtinTypeId(); + auto doubleId = m_projectStorage->builtinTypeId(); + + return isTypeId(m_typeId, intId, uintId, floatId, doubleId); + } else { + if (!isValid()) { + return false; + } + + auto type = simplifiedTypeName(); + + return type == "int" || type == "uint" || type == "float" || type == "double"; + } +} + bool NodeMetaInfo::isQtQuickExtrasPicture() const { if constexpr (useProjectStorage()) { @@ -2177,7 +2299,11 @@ bool NodeMetaInfo::isQtQuickBorderImage() const bool NodeMetaInfo::isAlias() const { - return isValid() && m_privateData->qualfiedTypeName() == "alias"; + if constexpr (useProjectStorage()) { + return false; // there is no type alias + } else { + return isValid() && m_privateData->qualfiedTypeName() == "alias"; + } } bool NodeMetaInfo::isQtQuickPositioner() const @@ -2322,7 +2448,7 @@ bool NodeMetaInfo::isQtQuick3DParticles3DAttractor3D() const } } -bool NodeMetaInfo::isQtQuick3DParticleAbstractShape() const +bool NodeMetaInfo::isQtQuick3DParticlesAbstractShape() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; @@ -2437,6 +2563,10 @@ bool NodeMetaInfo::isQtMultimediaSoundEffect() const bool NodeMetaInfo::isFlowViewItem() const { if constexpr (useProjectStorage()) { + if (!isValid()) { + return false; + } + using namespace Storage::Info; auto flowItemId = m_projectStorage->commonTypeId(); auto flowWildcardId = m_projectStorage->commonTypeId(); @@ -2540,7 +2670,7 @@ bool NodeMetaInfo::isFont() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; - return isTypeId(m_typeId, m_projectStorage->commonTypeId()); + return isValid() && isTypeId(m_typeId, m_projectStorage->commonTypeId()); } else { return isValid() && m_privateData->qualfiedTypeName() == "font"; } @@ -2550,7 +2680,7 @@ bool NodeMetaInfo::isColor() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; - return isTypeId(m_typeId, m_projectStorage->builtinTypeId()); + return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId()); } else { if (!isValid()) return false; @@ -2561,18 +2691,11 @@ bool NodeMetaInfo::isColor() const } } -bool NodeMetaInfo::isEffectMaker() const -{ - // We use arbitrary type name because at this time we don't have effect maker - // specific type - return typeName() == QString::fromUtf8(Storage::Info::EffectMaker); -} - bool NodeMetaInfo::isBool() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; - return isTypeId(m_typeId, m_projectStorage->builtinTypeId()); + return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId()); } else { if (!isValid()) return false; @@ -2587,7 +2710,7 @@ bool NodeMetaInfo::isInteger() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; - return isTypeId(m_typeId, m_projectStorage->builtinTypeId()); + return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId()); } else { if (!isValid()) return false; @@ -2601,6 +2724,10 @@ bool NodeMetaInfo::isInteger() const bool NodeMetaInfo::isFloat() const { if constexpr (useProjectStorage()) { + if (!isValid()) { + return false; + } + using namespace Storage::Info; auto floatId = m_projectStorage->builtinTypeId(); auto doubleId = m_projectStorage->builtinTypeId(); @@ -2620,7 +2747,7 @@ bool NodeMetaInfo::isVariant() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; - return isTypeId(m_typeId, m_projectStorage->builtinTypeId()); + return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId()); } else { return isValid() && simplifiedTypeName() == "QVariant"; } @@ -2630,7 +2757,7 @@ bool NodeMetaInfo::isString() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; - return isTypeId(m_typeId, m_projectStorage->builtinTypeId()); + return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId()); } else { if (!isValid()) return false; @@ -2645,7 +2772,7 @@ bool NodeMetaInfo::isUrl() const { if constexpr (useProjectStorage()) { using namespace Storage::Info; - return isTypeId(m_typeId, m_projectStorage->builtinTypeId()); + return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId()); } else { if (!isValid()) return false; @@ -2805,7 +2932,8 @@ bool NodeMetaInfo::isQtQuick3DCubeMapTexture() const return isBasedOnCommonType(m_projectStorage, m_typeId); } else { return isValid() - && (isSubclassOf("QtQuick3D.CubeMapTexture") || isSubclassOf(".QQuick3DCubeMapTexture")); + && (isSubclassOf("QtQuick3D.CubeMapTexture") + || isSubclassOf(".QQuick3DCubeMapTexture")); } } @@ -2832,7 +2960,7 @@ bool NodeMetaInfo::isQtQuick3DEffect() const bool NodeMetaInfo::isEnumeration() const { if constexpr (useProjectStorage()) - return bool(typeData().traits & Storage::TypeTraits::IsEnum); + return isValid() && bool(typeData().traits & Storage::TypeTraits::IsEnum); return false; } @@ -3046,9 +3174,26 @@ const PropertyName &PropertyMetaInfo::propertyName() const NodeMetaInfo NodeMetaInfo::commonBase(const NodeMetaInfo &metaInfo) const { - for (const NodeMetaInfo &info : metaInfo.superClasses()) { - if (isBasedOn(info)) - return info; + if constexpr (useProjectStorage()) { + if (isValid() && metaInfo) { + const auto firstTypeIds = m_projectStorage->prototypeAndSelfIds(m_typeId); + const auto secondTypeIds = m_projectStorage->prototypeAndSelfIds(metaInfo.m_typeId); + auto found + = std::find_if(firstTypeIds.begin(), firstTypeIds.end(), [&](TypeId firstTypeId) { + return std::find(secondTypeIds.begin(), secondTypeIds.end(), firstTypeId) + != secondTypeIds.end(); + }); + + if (found != firstTypeIds.end()) { + return NodeMetaInfo{*found, m_projectStorage}; + } + } + } else { + for (const NodeMetaInfo &info : metaInfo.selfAndPrototypes()) { + if (isBasedOn(info)) { + return info; + } + } } return {}; diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index a8ad4b76e7c..64912f5b73b 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -927,10 +927,9 @@ static int getMajorVersionFromImport(const Model *model) static int getMajorVersionFromNode(const ModelNode &modelNode) { if (modelNode.metaInfo().isValid()) { - for (const NodeMetaInfo &info : modelNode.metaInfo().classHierarchy()) { - if (info.typeName() == "QtQml.QtObject" - || info.typeName() == "QtQuick.QtObject" - || info.typeName() == "QtQuick.Item") { + for (const NodeMetaInfo &info : modelNode.metaInfo().selfAndPrototypes()) { + if (info.typeName() == "QtQml.QtObject" || info.typeName() == "QtQuick.QtObject" + || info.typeName() == "QtQuick.Item") { return info.majorVersion(); } } @@ -942,7 +941,7 @@ static int getMajorVersionFromNode(const ModelNode &modelNode) static int getMinorVersionFromNode(const ModelNode &modelNode) { if (modelNode.metaInfo().isValid()) { - const NodeMetaInfos infos = modelNode.metaInfo().classHierarchy(); + const NodeMetaInfos infos = modelNode.metaInfo().selfAndPrototypes(); for (const NodeMetaInfo &info : infos) { if (info.typeName() == "QtQuick.QtObject" || info.typeName() == "QtQuick.Item") return info.minorVersion(); diff --git a/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp b/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp index 558f6849be2..bf1dc59e155 100644 --- a/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp @@ -148,7 +148,7 @@ AbstractProperty BindingProperty::resolveToProperty() const element = binding; } - if (node.isValid()) + if (node.isValid() && !element.contains(' ')) return node.property(element.toUtf8()); else return AbstractProperty(); diff --git a/src/plugins/qmldesigner/designercore/model/import.cpp b/src/plugins/qmldesigner/designercore/model/import.cpp index 54b18810437..6d4cd19d8b8 100644 --- a/src/plugins/qmldesigner/designercore/model/import.cpp +++ b/src/plugins/qmldesigner/designercore/model/import.cpp @@ -11,17 +11,17 @@ namespace QmlDesigner { Import Import::createLibraryImport(const QString &url, const QString &version, const QString &alias, const QStringList &importPaths) { - return Import(url, QString(), version, alias, importPaths); + return Import(url, version, alias, importPaths, Type::Library); } Import Import::createFileImport(const QString &file, const QString &version, const QString &alias, const QStringList &importPaths) { - return Import(QString(), file, version, alias, importPaths); + return Import(file, version, alias, importPaths, Type::File); } Import Import::empty() { - return Import(QString(), QString(), QString(), QString(), QStringList()); + return Import(QString(), QString(), QString(), QStringList(), Type::Empty); } QString Import::toImportString() const @@ -33,12 +33,16 @@ QString Import::toImportString() const return result; } -Import::Import(const QString &url, const QString &file, const QString &version, const QString &alias, const QStringList &importPaths): - m_url(url), - m_file(file), - m_version(version), - m_alias(alias), - m_importPathList(importPaths) +Import::Import(const QString &url, + const QString &version, + const QString &alias, + const QStringList &importPaths, + Type type) + : m_url(url) + , m_version(version) + , m_alias(alias) + , m_importPathList(importPaths) + , m_type(type) { } @@ -137,7 +141,7 @@ size_t qHash(const Import &import) return ::qHash(import.url()) ^ ::qHash(import.file()) ^ ::qHash(import.version()) ^ ::qHash(import.alias()); } -Imports difference(const Imports &first, const Imports &second) +Imports set_difference(const Imports &first, const Imports &second) { Imports difference; difference.reserve(first.size()); @@ -150,4 +154,53 @@ Imports difference(const Imports &first, const Imports &second) return difference; } + +Imports set_union(const Imports &first, const Imports &second) +{ + Imports set_union; + set_union.reserve(std::min(first.size(), second.size())); + + std::set_union(first.begin(), + first.end(), + second.begin(), + second.end(), + std::back_inserter(set_union)); + + return set_union; +} + +Imports set_intersection(const Imports &first, const Imports &second) +{ + Imports set_intersection; + set_intersection.reserve(std::min(first.size(), second.size())); + + std::set_intersection(first.begin(), + first.end(), + second.begin(), + second.end(), + std::back_inserter(set_intersection)); + + return set_intersection; +} + +Imports set_strict_difference(const Imports &first, const Imports &second) +{ + Imports difference; + difference.reserve(first.size()); + + auto strictLess = [](const Import &first, const Import &second) { + return std::tie(first.m_url, first.m_type, first.m_version) + < std::tie(second.m_url, second.m_type, second.m_version); + }; + + std::set_difference(first.begin(), + first.end(), + second.begin(), + second.end(), + std::back_inserter(difference), + strictLess); + + return difference; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/model/internalnode_p.h b/src/plugins/qmldesigner/designercore/model/internalnode_p.h index 8b3ff86d2bc..bf8523653cf 100644 --- a/src/plugins/qmldesigner/designercore/model/internalnode_p.h +++ b/src/plugins/qmldesigner/designercore/model/internalnode_p.h @@ -199,8 +199,8 @@ public: int nodeSourceType = 0; QString behaviorPropertyName; QStringList scriptFunctions; - ModuleId moduleId; // is invalid if type is implicit - Utils::SmallString documentTypeName; // how the type is written in den Document + ModuleId moduleId; + ImportedTypeNameId importedTypeNameId; TypeId typeId; private: diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index 12a8c091838..653ffea077a 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -6,6 +6,8 @@ #include "model_p.h" #include +#include "../projectstorage/sourcepath.h" +#include "../projectstorage/sourcepathcache.h" #include "abstractview.h" #include "auxiliarydataproperties.h" #include "internalbindingproperty.h" @@ -60,18 +62,21 @@ namespace QmlDesigner { namespace Internal { ModelPrivate::ModelPrivate(Model *model, - ProjectStorageType &projectStorage, + ProjectStorageDependencies projectStorageDependencies, const TypeName &typeName, int major, int minor, Model *metaInfoProxyModel, std::unique_ptr resourceManagement) - : projectStorage{&projectStorage} + : projectStorage{&projectStorageDependencies.storage} + , pathCache{&projectStorageDependencies.cache} , m_model{model} , m_resourceManagement{std::move(resourceManagement)} { m_metaInfoProxyModel = metaInfoProxyModel; + changeImports({Import::createLibraryImport({"QtQuick"})}, {}); + m_rootInternalNode = createNode( typeName, major, minor, {}, {}, {}, ModelNode::NodeWithoutSource, {}, true); @@ -79,6 +84,25 @@ ModelPrivate::ModelPrivate(Model *model, m_currentTimelineNode = m_rootInternalNode; } +ModelPrivate::ModelPrivate(Model *model, + ProjectStorageDependencies projectStorageDependencies, + Utils::SmallStringView typeName, + Imports imports, + const QUrl &fileUrl) + : projectStorage{&projectStorageDependencies.storage} + , pathCache{&projectStorageDependencies.cache} + , m_model{model} +{ + setFileUrl(fileUrl); + changeImports(std::move(imports), {}); + + m_rootInternalNode = createNode( + TypeName{typeName}, -1, -1, {}, {}, {}, ModelNode::NodeWithoutSource, {}, true); + + m_currentStateNode = m_rootInternalNode; + m_currentTimelineNode = m_rootInternalNode; +} + ModelPrivate::ModelPrivate(Model *model, const TypeName &typeName, int major, @@ -117,29 +141,41 @@ void ModelPrivate::detachAllViews() } } -void ModelPrivate::changeImports(const Imports &toBeAddedImportList, - const Imports &toBeRemovedImportList) +namespace { +Storage::Imports createStorageImports(const Imports &imports, + ProjectStorageType &projectStorage, + SourceId fileId) { - Imports removedImportList; - for (const Import &import : toBeRemovedImportList) { - if (m_imports.contains(import)) { - removedImportList.append(import); - m_imports.removeOne(import); + return Utils::transform(imports, [&](const Import &import) { + return Storage::Import{projectStorage.moduleId(Utils::SmallString{import.url()}), + import.majorVersion(), + import.minorVersion(), + fileId}; + }); +} + +} // namespace + +void ModelPrivate::changeImports(Imports toBeAddedImports, Imports toBeRemovedImports) +{ + std::sort(toBeAddedImports.begin(), toBeAddedImports.end()); + std::sort(toBeRemovedImports.begin(), toBeRemovedImports.end()); + + Imports removedImports = set_intersection(m_imports, toBeRemovedImports); + m_imports = set_difference(m_imports, removedImports); + + Imports allNewAddedImports = set_strict_difference(toBeAddedImports, m_imports); + Imports importWithoutAddedImport = set_difference(m_imports, allNewAddedImports); + + m_imports = set_union(importWithoutAddedImport, allNewAddedImports); + + if (!removedImports.isEmpty() || !allNewAddedImports.isEmpty()) { + if (useProjectStorage()) { + auto imports = createStorageImports(m_imports, *projectStorage, m_sourceId); + projectStorage->synchronizeDocumentImports(std::move(imports), m_sourceId); } + notifyImportsChanged(allNewAddedImports, removedImports); } - - Imports addedImportList; - for (const Import &import : toBeAddedImportList) { - if (!m_imports.contains(import)) { - addedImportList.append(import); - m_imports.append(import); - } - } - - std::sort(m_imports.begin(), m_imports.end()); - - if (!removedImportList.isEmpty() || !addedImportList.isEmpty()) - notifyImportsChanged(addedImportList, removedImportList); } void ModelPrivate::notifyImportsChanged(const Imports &addedImports, const Imports &removedImports) @@ -201,6 +237,9 @@ void ModelPrivate::setFileUrl(const QUrl &fileUrl) if (oldPath != fileUrl) { m_fileUrl = fileUrl; + if constexpr (useProjectStorage()) { + m_sourceId = pathCache->sourceId(SourcePath{fileUrl.path()}); + } for (const QPointer &view : std::as_const(m_viewList)) view->fileUrlChanged(oldPath, fileUrl); @@ -220,23 +259,6 @@ void ModelPrivate::changeNodeType(const InternalNodePointer &node, const TypeNam } } -namespace { -QT_WARNING_PUSH -QT_WARNING_DISABLE_CLANG("-Wunneeded-internal-declaration") - -std::pair decomposeTypePath(Utils::SmallStringView typeName) -{ - auto found = std::find(typeName.rbegin(), typeName.rend(), '.'); - - if (found == typeName.rend()) - return {}; - - return {{typeName.begin(), std::prev(found.base())}, {found.base(), typeName.end()}}; -} - -QT_WARNING_POP -} // namespace - InternalNodePointer ModelPrivate::createNode(const TypeName &typeName, int majorVersion, int minorVersion, @@ -257,13 +279,7 @@ InternalNodePointer ModelPrivate::createNode(const TypeName &typeName, auto newNode = std::make_shared(typeName, majorVersion, minorVersion, internalId); - if constexpr (useProjectStorage()) { - auto [moduleName, shortTypeName] = decomposeTypePath(typeName); - ModuleId moduleId = projectStorage->moduleId(moduleName); - newNode->typeId = projectStorage->typeId(moduleId, - shortTypeName, - Storage::Version{majorVersion, minorVersion}); - } + setTypeId(newNode.get(), typeName); newNode->nodeSourceType = nodeSourceType; @@ -314,6 +330,55 @@ EnabledViewRange ModelPrivate::enabledViews() const return EnabledViewRange{m_viewList}; } +namespace { +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wunneeded-internal-declaration") + +std::pair decomposeTypePath(Utils::SmallStringView typeName) +{ + auto found = std::find(typeName.rbegin(), typeName.rend(), '.'); + + if (found == typeName.rend()) + return {{}, typeName}; + + return {{typeName.begin(), std::prev(found.base())}, {found.base(), typeName.end()}}; +} + +QT_WARNING_POP +} // namespace + +ImportedTypeNameId ModelPrivate::importedTypeNameId(Utils::SmallStringView typeName) +{ + if constexpr (useProjectStorage()) { + auto [moduleName, shortTypeName] = decomposeTypePath(typeName); + + if (moduleName.size()) { + QString aliasName = QString{moduleName}; + auto found = std::find_if(m_imports.begin(), m_imports.end(), [&](const Import &import) { + return import.alias() == aliasName; + }); + if (found != m_imports.end()) { + ModuleId moduleId = projectStorage->moduleId(Utils::PathString{found->url()}); + ImportId importId = projectStorage->importId( + Storage::Import{moduleId, found->majorVersion(), found->minorVersion(), m_sourceId}); + return projectStorage->importedTypeNameId(importId, shortTypeName); + } + } + + return projectStorage->importedTypeNameId(m_sourceId, shortTypeName); + } + + return ImportedTypeNameId{}; +} + +void ModelPrivate::setTypeId(InternalNode *node, Utils::SmallStringView typeName) +{ + if constexpr (useProjectStorage()) { + node->importedTypeNameId = importedTypeNameId(typeName); + node->typeId = projectStorage->typeId(node->importedTypeNameId); + } +} + void ModelPrivate::handleResourceSet(const ModelResourceSet &resourceSet) { for (const ModelNode &node : resourceSet.removeModelNodes) { @@ -1343,9 +1408,11 @@ void ModelPrivate::clearParent(const InternalNodePointer &node) void ModelPrivate::changeRootNodeType(const TypeName &type, int majorVersion, int minorVersion) { Q_ASSERT(rootNode()); - rootNode()->typeName = type; - rootNode()->majorVersion = majorVersion; - rootNode()->minorVersion = minorVersion; + + m_rootInternalNode->typeName = type; + m_rootInternalNode->majorVersion = majorVersion; + m_rootInternalNode->minorVersion = minorVersion; + setTypeId(m_rootInternalNode.get(), type); notifyRootNodeTypeChanged(QString::fromUtf8(type), majorVersion, minorVersion); } @@ -1473,6 +1540,7 @@ WriteLocker::WriteLocker(ModelPrivate *model) if (m_model->m_writeLock) qWarning() << "QmlDesigner: Misbehaving view calls back to model!!!"; // FIXME: Enable it again + QTC_CHECK(!m_model->m_writeLock); Q_ASSERT(!m_model->m_writeLock); model->m_writeLock = true; } @@ -1484,6 +1552,7 @@ WriteLocker::WriteLocker(Model *model) if (m_model->m_writeLock) qWarning() << "QmlDesigner: Misbehaving view calls back to model!!!"; // FIXME: Enable it again + QTC_CHECK(!m_model->m_writeLock); Q_ASSERT(!m_model->m_writeLock); m_model->m_writeLock = true; } @@ -1493,6 +1562,7 @@ WriteLocker::~WriteLocker() if (!m_model->m_writeLock) qWarning() << "QmlDesigner: WriterLocker out of sync!!!"; // FIXME: Enable it again + QTC_CHECK(m_model->m_writeLock); Q_ASSERT(m_model->m_writeLock); m_model->m_writeLock = false; } @@ -1509,14 +1579,27 @@ void WriteLocker::lock(Model *model) } // namespace Internal -Model::Model(ProjectStorageType &projectStorage, +Model::Model(ProjectStorageDependencies projectStorageDependencies, const TypeName &typeName, int major, int minor, Model *metaInfoProxyModel, std::unique_ptr resourceManagement) + : d(std::make_unique(this, + projectStorageDependencies, + typeName, + major, + minor, + metaInfoProxyModel, + std::move(resourceManagement))) +{} + +Model::Model(ProjectStorageDependencies projectStorageDependencies, + Utils::SmallStringView typeName, + Imports imports, + const QUrl &fileUrl) : d(std::make_unique( - this, projectStorage, typeName, major, minor, metaInfoProxyModel, std::move(resourceManagement))) + this, projectStorageDependencies, typeName, std::move(imports), fileUrl)) {} Model::Model(const TypeName &typeName, @@ -1545,9 +1628,9 @@ const Imports &Model::usedImports() const return d->m_usedImportList; } -void Model::changeImports(const Imports &importsToBeAdded, const Imports &importsToBeRemoved) +void Model::changeImports(Imports importsToBeAdded, Imports importsToBeRemoved) { - d->changeImports(importsToBeAdded, importsToBeRemoved); + d->changeImports(std::move(importsToBeAdded), std::move(importsToBeRemoved)); } void Model::setPossibleImports(Imports possibleImports) @@ -1747,6 +1830,16 @@ NotNullPointer Model::projectStorage() const return d->projectStorage; } +const PathCacheType &Model::pathCache() const +{ + return *d->pathCache; +} + +PathCacheType &Model::pathCache() +{ + return *d->pathCache; +} + void ModelDeleter::operator()(class Model *model) { model->detachAllViews(); @@ -1877,13 +1970,18 @@ QUrl Model::fileUrl() const return d->fileUrl(); } +SourceId Model::fileUrlSourceId() const +{ + return d->m_sourceId; +} + /*! \brief Sets the URL against which relative URLs within the model should be resolved. \param url the base URL, i.e. the qml file path. */ void Model::setFileUrl(const QUrl &url) { - Q_ASSERT(url.isValid() && url.isLocalFile()); + QTC_ASSERT(url.isValid() && url.isLocalFile(), qDebug() << "url:" << url; return); Internal::WriteLocker locker(d.get()); d->setFileUrl(url); } @@ -1906,6 +2004,16 @@ void Model::setMetaInfo(const MetaInfo &metaInfo) d->setMetaInfo(metaInfo); } +NodeMetaInfo Model::boolMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QML.bool"); + } +} + template NodeMetaInfo Model::createNodeMetaInfo() const { @@ -1924,6 +2032,36 @@ NodeMetaInfo Model::fontMetaInfo() const } } +NodeMetaInfo Model::qtQmlModelsListModelMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QtQml.Models.ListModel"); + } +} + +NodeMetaInfo Model::qtQmlModelsListElementMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QtQml.Models.ListElement"); + } +} + +NodeMetaInfo Model::qmlQtObjectMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QML.QtObject"); + } +} + NodeMetaInfo Model::qtQuickItemMetaInfo() const { if constexpr (useProjectStorage()) { @@ -2220,13 +2358,8 @@ namespace { NodeMetaInfo Model::metaInfo(const TypeName &typeName, int majorVersion, int minorVersion) const { if constexpr (useProjectStorage()) { - auto [module, componentName] = moduleTypeName(typeName); - - ModuleId moduleId = d->projectStorage->moduleId(module); - TypeId typeId = d->projectStorage->typeId(moduleId, - componentName, - Storage::Version{majorVersion, minorVersion}); - return NodeMetaInfo(typeId, d->projectStorage); + return NodeMetaInfo(d->projectStorage->typeId(d->importedTypeNameId(typeName)), + d->projectStorage); } else { return NodeMetaInfo(metaInfoProxyModel(), typeName, majorVersion, minorVersion); } @@ -2356,6 +2489,13 @@ ModelNode Model::createModelNode(const TypeName &typeName) } } +void Model::changeRootNodeType(const TypeName &type) +{ + Internal::WriteLocker locker(this); + + d->changeRootNodeType(type, -1, -1); +} + void Model::removeModelNodes(ModelNodes nodes, BypassModelResourceManagement bypass) { nodes.erase(std::remove_if(nodes.begin(), nodes.end(), [](auto &&node) { return !node; }), diff --git a/src/plugins/qmldesigner/designercore/model/model_p.h b/src/plugins/qmldesigner/designercore/model/model_p.h index 5c69e756164..324df37ddb2 100644 --- a/src/plugins/qmldesigner/designercore/model/model_p.h +++ b/src/plugins/qmldesigner/designercore/model/model_p.h @@ -98,12 +98,17 @@ class ModelPrivate : public QObject public: ModelPrivate(Model *model, - ProjectStorageType &projectStorage, + ProjectStorageDependencies m_projectStorageDependencies, const TypeName &type, int major, int minor, Model *metaInfoProxyModel, std::unique_ptr resourceManagement); + ModelPrivate(Model *model, + ProjectStorageDependencies m_projectStorageDependencies, + Utils::SmallStringView typeName, + Imports imports, + const QUrl &filePath); ModelPrivate(Model *model, const TypeName &type, int major, @@ -235,7 +240,7 @@ public: // Imports: const Imports &imports() const { return m_imports; } - void changeImports(const Imports &importsToBeAdded, const Imports &importToBeRemoved); + void changeImports(Imports importsToBeAdded, Imports importToBeRemoved); void notifyImportsChanged(const Imports &addedImports, const Imports &removedImports); void notifyPossibleImportsChanged(const Imports &possibleImports); void notifyUsedImportsChanged(const Imports &usedImportsChanged); @@ -306,9 +311,12 @@ private: static QList> toInternalBindingProperties( const ModelResourceSet::SetExpressions &setExpressions); EnabledViewRange enabledViews() const; + ImportedTypeNameId importedTypeNameId(Utils::SmallStringView typeName); + void setTypeId(InternalNode *node, Utils::SmallStringView typeName); public: NotNullPointer projectStorage = nullptr; + NotNullPointer pathCache = nullptr; private: Model *m_model = nullptr; @@ -326,6 +334,7 @@ private: InternalNodePointer m_currentTimelineNode; std::unique_ptr m_resourceManagement; QUrl m_fileUrl; + SourceId m_sourceId; QPointer m_rewriterView; QPointer m_nodeInstanceView; QPointer m_metaInfoProxyModel; diff --git a/src/plugins/qmldesigner/designercore/model/modelnode.cpp b/src/plugins/qmldesigner/designercore/model/modelnode.cpp index 45c076022da..cb862b03431 100644 --- a/src/plugins/qmldesigner/designercore/model/modelnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelnode.cpp @@ -66,10 +66,6 @@ ModelNode::ModelNode(const ModelNode &modelNode, AbstractView *view) , m_view(view) {} -/*! \brief does nothing -*/ -ModelNode::~ModelNode() = default; - /*! \brief returns the name of node which is a short cut to a property like objectName \return name of the node */ @@ -657,15 +653,6 @@ void ModelNode::destroy() //\} -/*! \name Property Manipulation - * This functions interact with properties. - */ - -Internal::InternalNodePointer ModelNode::internalNode() const -{ - return m_internalNode; -} - /*! \brief returns the model of the node \return returns the model of the node @@ -1002,6 +989,12 @@ void ModelNode::setAuxiliaryData(AuxiliaryDataKeyView key, const QVariant &data) } } +void ModelNode::setAuxiliaryDataWithoutLock(AuxiliaryDataKeyView key, const QVariant &data) const +{ + if (isValid()) + m_model->d->setAuxiliaryData(internalNode(), key, data); +} + void ModelNode::setAuxiliaryDataWithoutLock(AuxiliaryDataType type, Utils::SmallStringView name, const QVariant &data) const diff --git a/src/plugins/qmldesigner/designercore/model/modelutils.cpp b/src/plugins/qmldesigner/designercore/model/modelutils.cpp index 70da6f98b9c..efbe8f09783 100644 --- a/src/plugins/qmldesigner/designercore/model/modelutils.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelutils.cpp @@ -4,6 +4,8 @@ #include "modelutils.h" #include +#include +#include #include @@ -102,4 +104,29 @@ PropertyMetaInfo metainfo(const ModelNode &node, const PropertyName &propertyNam return node.metaInfo().property(propertyName); } +QString componentFilePath(const PathCacheType &pathCache, const NodeMetaInfo &metaInfo) +{ + if constexpr (useProjectStorage()) { + auto typeSourceId = metaInfo.sourceId(); + + if (typeSourceId && metaInfo.isFileComponent()) { + return pathCache.sourcePath(typeSourceId).toQString(); + } + } else { + return metaInfo.componentFileName(); + } + + return {}; +} + +QString componentFilePath(const ModelNode &node) +{ + if (node) { + const auto &pathCache = node.model()->pathCache(); + return ModelUtils::componentFilePath(pathCache, node.metaInfo()); + } + + return {}; +} + } // namespace QmlDesigner::ModelUtils diff --git a/src/plugins/qmldesigner/designercore/model/modelutils.h b/src/plugins/qmldesigner/designercore/model/modelutils.h index 578a7adb444..946882c2df7 100644 --- a/src/plugins/qmldesigner/designercore/model/modelutils.h +++ b/src/plugins/qmldesigner/designercore/model/modelutils.h @@ -29,4 +29,8 @@ QMLDESIGNERCORE_EXPORT PropertyMetaInfo metainfo(const AbstractProperty &propert QMLDESIGNERCORE_EXPORT PropertyMetaInfo metainfo(const ModelNode &node, const PropertyName &propertyName); +QMLDESIGNERCORE_EXPORT QString componentFilePath(const PathCacheType &pathCache, + const NodeMetaInfo &metaInfo); + +QMLDESIGNERCORE_EXPORT QString componentFilePath(const ModelNode &node); } // namespace QmlDesigner::ModelUtils diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 3108e26a618..ccfffbc867b 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -937,7 +937,6 @@ QList generatePossibleFileImports(const QString &path, QmlDesigner::Imports createQt5Modules() { return {QmlDesigner::Import::createLibraryImport("QtQuick", "5.15"), - QmlDesigner::Import::createLibraryImport("QtQuick3D", "5.15"), QmlDesigner::Import::createLibraryImport("QtQuick.Controls", "5.15"), QmlDesigner::Import::createLibraryImport("QtQuick.Window", "5.15"), QmlDesigner::Import::createLibraryImport("QtQuick.Layouts", "5.15"), diff --git a/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h b/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h index 05e90fdb285..fb4fb660660 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h @@ -31,12 +31,12 @@ inline constexpr char Camera[] = "Camera"; inline constexpr char Command[] = "Command"; inline constexpr char Component[] = "Component"; inline constexpr char Connections[] = "Connections"; +inline constexpr char Control[] = "Control"; inline constexpr char CubeMapTexture[] = "CubeMapTexture"; inline constexpr char DefaultMaterial[] = "DefaultMaterial"; inline constexpr char Dialog[] = "Dialog"; inline constexpr char DoubleType[] = "double"; inline constexpr char Effect[] = "Effect"; -inline constexpr char EffectMaker[] = "EffectMaker"; inline constexpr char FloatType[] = "float"; inline constexpr char FlowActionArea[] = "FlowActionArea"; inline constexpr char FlowDecision[] = "FlowDecision"; @@ -55,10 +55,13 @@ inline constexpr char Item[] = "Item"; inline constexpr char KeyframeGroup[] = "KeyframeGroup"; inline constexpr char Keyframe[] = "Keyframe"; inline constexpr char Layout[] = "Layout"; +inline constexpr char ListElement[] = "ListElement"; +inline constexpr char ListModel[] = "ListModel"; inline constexpr char ListView[] = "ListView"; inline constexpr char Loader[] = "Loader"; inline constexpr char Material[] = "Material"; inline constexpr char Model[] = "Model"; +inline constexpr char MouseArea[] = "MouseArea"; inline constexpr char Node[] = "Node"; inline constexpr char Particle3D[] = "Particle3D"; inline constexpr char ParticleEmitter3D[] = "ParticleEmitter3D"; @@ -80,6 +83,7 @@ inline constexpr char QQuickStateOperation[] = "QQuickStateOperation"; inline constexpr char QtMultimedia[] = "QtMultimedia"; inline constexpr char QtObject[] = "QtObject"; inline constexpr char QtQml[] = "QtQml"; +inline constexpr char QtQml_Models[] = "QtQml.Models"; inline constexpr char QtQuick3D[] = "QtQuick3D"; inline constexpr char QtQuick3D_Particles3D[] = "QtQuick3D.Particles3D"; inline constexpr char QtQuick3D_Particles3D_cppnative[] = "QtQuick3D.Particles3D-cppnative"; @@ -90,6 +94,7 @@ inline constexpr char QtQuick_Dialogs[] = "QtQuick.Dialogs"; inline constexpr char QtQuick_Extras[] = "QtQuick.Extras"; inline constexpr char QtQuick_Layouts[] = "QtQuick.Layouts"; inline constexpr char QtQuick_Studio_Components[] = "QtQuick.Studio.Components"; +inline constexpr char QtQuick_Templates[] = "QtQuick.Templates"; inline constexpr char QtQuick_Timeline[] = "QtQuick.Timeline"; inline constexpr char QtQuick_Window[] = "QtQuick.Window"; inline constexpr char Qt_SafeRenderer[] = "Qt.SafeRenderer"; @@ -114,6 +119,7 @@ inline constexpr char Texture[] = "Texture"; inline constexpr char TimelineAnimation[] = "TimelineAnimation"; inline constexpr char Timeline[] = "Timeline"; inline constexpr char Transition[] = "Transition"; +inline constexpr char UIntType[] = "uint"; inline constexpr char View3D[] = "View3D"; inline constexpr char Window[] = "Window"; inline constexpr char color[] = "color"; @@ -156,7 +162,10 @@ class CommonTypeCache CacheType, CacheType, CacheType, + CacheType, CacheType, + CacheType, + CacheType, CacheType, CacheType, CacheType, @@ -164,6 +173,7 @@ class CommonTypeCache CacheType, CacheType, CacheType, + CacheType, CacheType, CacheType, CacheType, @@ -210,6 +220,7 @@ class CommonTypeCache CacheType, CacheType, CacheType, + CacheType, CacheType, CacheType, CacheType, @@ -219,6 +230,7 @@ class CommonTypeCache CacheType, CacheType, CacheType, + CacheType, CacheType, CacheType, CacheType, @@ -271,30 +283,33 @@ public: { if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) + return typeId(); + else if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) return typeId(); - if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) return typeId(); else - return TypeId{}; + static_assert(!std::is_same_v, "built-in type not supported"); + return TypeId{}; } private: diff --git a/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h b/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h index c67d4e2de9a..078fd1ee98f 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h @@ -19,10 +19,10 @@ class SourcePathCache; template class ProjectStorage; -using PathCache = SourcePathCache, NonLockingMutex>; - class FileSystem : public FileSystemInterface { + using PathCache = SourcePathCache, NonLockingMutex>; + public: FileSystem(PathCache &sourcePathCache) : m_sourcePathCache(sourcePathCache) diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index 337145638c1..78064fedab0 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -107,6 +107,15 @@ public: }); } + void synchronizeDocumentImports(Storage::Imports imports, SourceId sourceId) override + { + Sqlite::withImmediateTransaction(database, [&] { + synchronizeDocumentImports(imports, + {sourceId}, + Storage::Synchronization::ImportKind::Import); + }); + } + ModuleId moduleId(Utils::SmallStringView moduleName) const override { return moduleCache.id(moduleName); @@ -139,6 +148,51 @@ public: .template valueWithTransaction(moduleId, exportedTypeName); } + TypeId typeId(ImportedTypeNameId typeNameId) const override + { + return Sqlite::withDeferredTransaction(database, [&] { return fetchTypeId(typeNameId); }); + } + + Storage::Info::ExportedTypeNames exportedTypeNames(TypeId typeId) const override + { + return selectExportedTypesByTypeIdStatement + .template valuesWithTransaction(4, typeId); + } + + Storage::Info::ExportedTypeNames exportedTypeNames(TypeId typeId, + SourceId sourceId) const override + { + return selectExportedTypesByTypeIdAndSourceIdStatement + .template valuesWithTransaction(4, typeId, sourceId); + } + + ImportId importId(const Storage::Import &import) const override + { + return Sqlite::withDeferredTransaction(database, [&] { + return fetchImportId(import.sourceId, import); + }); + } + + ImportedTypeNameId importedTypeNameId(ImportId importId, + Utils::SmallStringView typeName) override + { + return Sqlite::withDeferredTransaction(database, [&] { + return fetchImportedTypeNameId(Storage::Synchronization::TypeNameKind::QualifiedExported, + importId, + typeName); + }); + } + + ImportedTypeNameId importedTypeNameId(SourceId sourceId, + Utils::SmallStringView typeName) override + { + return Sqlite::withDeferredTransaction(database, [&] { + return fetchImportedTypeNameId(Storage::Synchronization::TypeNameKind::Exported, + sourceId, + typeName); + }); + } + PropertyDeclarationIds propertyDeclarationIds(TypeId typeId) const override { return selectPropertyDeclarationIdsForTypeStatement @@ -610,14 +664,14 @@ private: template struct TypeCompare { - bool operator()(const Type &type, TypeId typeId) { return type.typeId < typeId; }; + bool operator()(const Type &type, TypeId typeId) { return type.typeId < typeId; } - bool operator()(TypeId typeId, const Type &type) { return typeId < type.typeId; }; + bool operator()(TypeId typeId, const Type &type) { return typeId < type.typeId; } bool operator()(const Type &first, const Type &second) { return first.typeId < second.typeId; - }; + } }; template @@ -626,17 +680,17 @@ private: bool operator()(const Property &property, PropertyDeclarationId id) { return property.propertyDeclarationId < id; - }; + } bool operator()(PropertyDeclarationId id, const Property &property) { return id < property.propertyDeclarationId; - }; + } bool operator()(const Property &first, const Property &second) { return first.propertyDeclarationId < second.propertyDeclarationId; - }; + } }; SourceIds filterSourceIdsWithoutType(const SourceIds &updatedSourceIds, SourceIds &sourceIdsOfTypes) @@ -825,9 +879,9 @@ private: Sqlite::insertUpdateDelete(range, fileStatuses, compareKey, insert, update, remove); } - void synchronizeImports(Storage::Synchronization::Imports &imports, + void synchronizeImports(Storage::Imports &imports, const SourceIds &updatedSourceIds, - Storage::Synchronization::Imports &moduleDependencies, + Storage::Imports &moduleDependencies, const SourceIds &updatedModuleDependencySourceIds, Storage::Synchronization::ModuleExportedImports &moduleExportedImports, const ModuleIds &updatedModuleIds) @@ -1484,7 +1538,7 @@ private: PropertyCompare{}); } - void insertDocumentImport(const Storage::Synchronization::Import &import, + void insertDocumentImport(const Storage::Import &import, Storage::Synchronization::ImportKind importKind, ModuleId sourceModuleId, ModuleExportedImportId moduleExportedImportId) @@ -1513,7 +1567,7 @@ private: } } - void synchronizeDocumentImports(Storage::Synchronization::Imports &imports, + void synchronizeDocumentImports(Storage::Imports &imports, const SourceIds &updatedSourceIds, Storage::Synchronization::ImportKind importKind) { @@ -1528,7 +1582,7 @@ private: importKind); auto compareKey = [](const Storage::Synchronization::ImportView &view, - const Storage::Synchronization::Import &import) -> long long { + const Storage::Import &import) -> long long { auto sourceIdDifference = view.sourceId - import.sourceId; if (sourceIdDifference != 0) return sourceIdDifference; @@ -1544,16 +1598,15 @@ private: return view.version.minor.value - import.version.minor.value; }; - auto insert = [&](const Storage::Synchronization::Import &import) { + auto insert = [&](const Storage::Import &import) { insertDocumentImport(import, importKind, import.moduleId, ModuleExportedImportId{}); auto callback = [&](ModuleId exportedModuleId, int majorVersion, int minorVersion, ModuleExportedImportId moduleExportedImportId) { - Storage::Synchronization::Import additionImport{exportedModuleId, - Storage::Version{majorVersion, - minorVersion}, - import.sourceId}; + Storage::Import additionImport{exportedModuleId, + Storage::Version{majorVersion, minorVersion}, + import.sourceId}; auto exportedImportKind = importKind == Storage::Synchronization::ImportKind::Import ? Storage::Synchronization::ImportKind::ModuleExportedImport @@ -1571,8 +1624,7 @@ private: import.version.minor.value); }; - auto update = [](const Storage::Synchronization::ImportView &, - const Storage::Synchronization::Import &) { + auto update = [](const Storage::Synchronization::ImportView &, const Storage::Import &) { return Sqlite::UpdateChange::No; }; @@ -2028,7 +2080,7 @@ private: removeRelinkableEntries(relinkableExtensions, typeIds, TypeCompare{}); } - ImportId fetchImportId(SourceId sourceId, const Storage::Synchronization::Import &import) const + ImportId fetchImportId(SourceId sourceId, const Storage::Import &import) const { if (import.version) { return selectImportIdBySourceIdAndModuleIdAndVersionStatement.template value( @@ -2709,9 +2761,14 @@ public: " ON defaultPropertyId=propertyDeclarationId WHERE t.typeId=?", database}; mutable ReadStatement<4, 1> selectExportedTypesByTypeIdStatement{ - "SELECT moduleId, name, majorVersion, minorVersion FROM " + "SELECT moduleId, name, ifnull(majorVersion, -1), ifnull(minorVersion, -1) FROM " "exportedTypeNames WHERE typeId=?", database}; + mutable ReadStatement<4, 2> selectExportedTypesByTypeIdAndSourceIdStatement{ + "SELECT etn.moduleId, name, ifnull(etn.majorVersion, -1), ifnull(etn.minorVersion, -1) " + "FROM exportedTypeNames AS etn JOIN documentImports USING(moduleId) WHERE typeId=?1 AND " + "sourceId=?2", + database}; mutable ReadStatement<7> selectTypesStatement{ "SELECT sourceId, t.name, t.typeId, prototypeId, extensionId, traits, pd.name " "FROM types AS t LEFT JOIN propertyDeclarations AS pd " @@ -3229,8 +3286,8 @@ public: "UPDATE types SET defaultPropertyId=?2 WHERE typeId=?1", database}; WriteStatement<1> updateDefaultPropertyIdToNullStatement{ "UPDATE types SET defaultPropertyId=NULL WHERE defaultPropertyId=?1", database}; - mutable ReadStatement<2, 1> selectInfoTypeByTypeIdStatement{ - "SELECT defaultPropertyId, traits FROM types WHERE typeId=?", database}; + mutable ReadStatement<3, 1> selectInfoTypeByTypeIdStatement{ + "SELECT defaultPropertyId, sourceId, traits FROM types WHERE typeId=?", database}; mutable ReadStatement<1, 1> selectPrototypeIdsForTypeIdInOrderStatement{ "WITH RECURSIVE " " all_prototype_and_extension(typeId, prototypeId) AS (" diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragefwd.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragefwd.h index db0cec068bc..b33c609509e 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragefwd.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragefwd.h @@ -9,6 +9,7 @@ class Database; namespace QmlDesigner { class ProjectStorageInterface; +class SourcePathCacheInterface; template class ProjectStorage; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h index b61a6ec331a..1f16e762713 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h @@ -46,7 +46,9 @@ enum class TypeTraits : int { Value, Sequence, IsEnum = 1 << 8, - IsFileComponent = 1 << 9 + IsFileComponent = 1 << 9, + IsProjectComponent = 1 << 10, + IsInProjectModule = 1 << 11 }; constexpr TypeTraits operator|(TypeTraits first, TypeTraits second) @@ -61,10 +63,115 @@ constexpr TypeTraits operator&(TypeTraits first, TypeTraits second) using TypeNameString = ::Utils::BasicSmallString<63>; +class VersionNumber +{ +public: + explicit VersionNumber() = default; + explicit VersionNumber(int value) + : value{value} + {} + + explicit operator bool() const { return value >= 0; } + + friend bool operator==(VersionNumber first, VersionNumber second) noexcept + { + return first.value == second.value; + } + + friend bool operator!=(VersionNumber first, VersionNumber second) noexcept + { + return !(first == second); + } + + friend bool operator<(VersionNumber first, VersionNumber second) noexcept + { + return first.value < second.value; + } + +public: + int value = -1; +}; + +class Version +{ +public: + explicit Version() = default; + explicit Version(VersionNumber major, VersionNumber minor = VersionNumber{}) + : major{major} + , minor{minor} + {} + + explicit Version(int major, int minor) + : major{major} + , minor{minor} + {} + + explicit Version(int major) + : major{major} + {} + + friend bool operator==(Version first, Version second) noexcept + { + return first.major == second.major && first.minor == second.minor; + } + + friend bool operator<(Version first, Version second) noexcept + { + return std::tie(first.major, first.minor) < std::tie(second.major, second.minor); + } + + explicit operator bool() const { return major && minor; } + +public: + VersionNumber major; + VersionNumber minor; +}; } // namespace QmlDesigner::Storage namespace QmlDesigner::Storage::Info { +class ExportedTypeName +{ +public: + ExportedTypeName() = default; + + ExportedTypeName(ModuleId moduleId, + ::Utils::SmallStringView name, + Storage::Version version = Storage::Version{}) + : name{name} + , version{version} + , moduleId{moduleId} + {} + + ExportedTypeName(ModuleId moduleId, + ::Utils::SmallStringView name, + int majorVersion, + int minorVersion) + : name{name} + , version{majorVersion, minorVersion} + , moduleId{moduleId} + {} + + friend bool operator==(const ExportedTypeName &first, const ExportedTypeName &second) + { + return first.moduleId == second.moduleId && first.version == second.version + && first.name == second.name; + } + + friend bool operator<(const ExportedTypeName &first, const ExportedTypeName &second) + { + return std::tie(first.moduleId, first.name, first.version) + < std::tie(second.moduleId, second.name, second.version); + } + +public: + ::Utils::SmallString name; + Storage::Version version; + ModuleId moduleId; +}; + +using ExportedTypeNames = std::vector; + class PropertyDeclaration { public: @@ -87,12 +194,14 @@ public: class Type { public: - Type(PropertyDeclarationId defaultPropertyId, TypeTraits traits) + Type(PropertyDeclarationId defaultPropertyId, SourceId sourceId, TypeTraits traits) : defaultPropertyId{defaultPropertyId} + , sourceId{sourceId} , traits{traits} {} PropertyDeclarationId defaultPropertyId; + SourceId sourceId; TypeTraits traits; }; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h index bc74172ecd7..adbbfcac4c3 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h @@ -16,6 +16,7 @@ class ProjectStorageInterface { public: virtual void synchronize(Storage::Synchronization::SynchronizationPackage package) = 0; + virtual void synchronizeDocumentImports(const Storage::Imports imports, SourceId sourceId) = 0; virtual ModuleId moduleId(::Utils::SmallStringView name) const = 0; virtual std::optional @@ -24,6 +25,16 @@ public: ::Utils::SmallStringView exportedTypeName, Storage::Version version) const = 0; + virtual TypeId typeId(ImportedTypeNameId typeNameId) const = 0; + virtual Storage::Info::ExportedTypeNames exportedTypeNames(TypeId typeId) const = 0; + virtual Storage::Info::ExportedTypeNames exportedTypeNames(TypeId typeId, + SourceId sourceId) const + = 0; + virtual ImportId importId(const Storage::Import &import) const = 0; + virtual ImportedTypeNameId importedTypeNameId(ImportId sourceId, Utils::SmallStringView typeName) + = 0; + virtual ImportedTypeNameId importedTypeNameId(SourceId sourceId, Utils::SmallStringView typeName) + = 0; virtual PropertyDeclarationIds propertyDeclarationIds(TypeId typeId) const = 0; virtual PropertyDeclarationIds localPropertyDeclarationIds(TypeId typeId) const = 0; virtual PropertyDeclarationId propertyDeclarationId(TypeId typeId, diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h index 83372893785..e2e4c1bb4b1 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h @@ -15,95 +15,6 @@ namespace QmlDesigner::Storage { -class VersionNumber -{ -public: - explicit VersionNumber() = default; - explicit VersionNumber(int value) - : value{value} - {} - - explicit operator bool() const { return value >= 0; } - - friend bool operator==(VersionNumber first, VersionNumber second) noexcept - { - return first.value == second.value; - } - - friend bool operator!=(VersionNumber first, VersionNumber second) noexcept - { - return !(first == second); - } - - friend bool operator<(VersionNumber first, VersionNumber second) noexcept - { - return first.value < second.value; - } - -public: - int value = -1; -}; - -class Version -{ -public: - explicit Version() = default; - explicit Version(VersionNumber major, VersionNumber minor = VersionNumber{}) - : major{major} - , minor{minor} - {} - - explicit Version(int major, int minor) - : major{major} - , minor{minor} - {} - - explicit Version(int major) - : major{major} - {} - - friend bool operator==(Version first, Version second) noexcept - { - return first.major == second.major && first.minor == second.minor; - } - - friend bool operator<(Version first, Version second) noexcept - { - return std::tie(first.major, first.minor) < std::tie(second.major, second.minor); - } - - explicit operator bool() const { return major && minor; } - -public: - VersionNumber major; - VersionNumber minor; -}; - -namespace Synchronization { - -enum class TypeNameKind { Exported = 1, QualifiedExported = 2 }; - -enum class FileType : char { QmlTypes, QmlDocument }; - -enum class IsQualified : int { No, Yes }; - -inline int operator-(IsQualified first, IsQualified second) -{ - return static_cast(first) - static_cast(second); -} - -inline int operator<(IsQualified first, IsQualified second) -{ - return static_cast(first) < static_cast(second); -} - -enum class ImportKind : char { - Import, - ModuleDependency, - ModuleExportedImport, - ModuleExportedModuleDependency -}; - class Import { public: @@ -141,6 +52,31 @@ public: using Imports = std::vector; +namespace Synchronization { + +enum class TypeNameKind { Exported = 1, QualifiedExported = 2 }; + +enum class FileType : char { QmlTypes, QmlDocument }; + +enum class IsQualified : int { No, Yes }; + +inline int operator-(IsQualified first, IsQualified second) +{ + return static_cast(first) - static_cast(second); +} + +inline int operator<(IsQualified first, IsQualified second) +{ + return static_cast(first) < static_cast(second); +} + +enum class ImportKind : char { + Import, + ModuleDependency, + ModuleExportedImport, + ModuleExportedModuleDependency +}; + class ImportView { public: diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp index 4713dd0136f..2b8a9c58456 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp @@ -125,7 +125,7 @@ Storage::Synchronization::IsAutoVersion convertToIsAutoVersion(QmlDirParser::Imp return Storage::Synchronization::IsAutoVersion::No; } -void addDependencies(Storage::Synchronization::Imports &dependencies, +void addDependencies(Storage::Imports &dependencies, SourceId sourceId, const QList &qmldirDependencies, ProjectStorageInterface &projectStorage) diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp index 3e9ac43deda..b7f6d2ae55a 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp @@ -22,10 +22,11 @@ namespace QmlDesigner { #ifdef QDS_BUILD_QMLPARSER namespace QmlDom = QQmlJS::Dom; +namespace Synchronization = Storage::Synchronization; namespace { -using QualifiedImports = std::map; +using QualifiedImports = std::map; int convertVersionNumber(qint32 versionNumber) { @@ -54,10 +55,10 @@ Utils::PathString createNormalizedPath(Utils::SmallStringView directoryPath, return normalizedPath; } -Storage::Synchronization::Import createImport(const QmlDom::Import &qmlImport, - SourceId sourceId, - Utils::SmallStringView directoryPath, - QmlDocumentParser::ProjectStorage &storage) +Storage::Import createImport(const QmlDom::Import &qmlImport, + SourceId sourceId, + Utils::SmallStringView directoryPath, + QmlDocumentParser::ProjectStorage &storage) { using QmlUriKind = QQmlJS::Dom::QmlUri::Kind; @@ -66,16 +67,16 @@ Storage::Synchronization::Import createImport(const QmlDom::Import &qmlImport, if (uri.kind() == QmlUriKind::RelativePath) { auto path = createNormalizedPath(directoryPath, uri.localPath()); auto moduleId = storage.moduleId(createNormalizedPath(directoryPath, uri.localPath())); - return Storage::Synchronization::Import(moduleId, Storage::Version{}, sourceId); + return Storage::Import(moduleId, Storage::Version{}, sourceId); } if (uri.kind() == QmlUriKind::ModuleUri) { auto moduleId = storage.moduleId(Utils::PathString{uri.moduleUri()}); - return Storage::Synchronization::Import(moduleId, convertVersion(qmlImport.version), sourceId); + return Storage::Import(moduleId, convertVersion(qmlImport.version), sourceId); } auto moduleId = storage.moduleId(Utils::PathString{uri.toString()}); - return Storage::Synchronization::Import(moduleId, convertVersion(qmlImport.version), sourceId); + return Storage::Import(moduleId, convertVersion(qmlImport.version), sourceId); } QualifiedImports createQualifiedImports(const QList &qmlImports, @@ -94,7 +95,7 @@ QualifiedImports createQualifiedImports(const QList &qmlImports, return qualifiedImports; } -void addImports(Storage::Synchronization::Imports &imports, +void addImports(Storage::Imports &imports, const QList &qmlImports, SourceId sourceId, Utils::SmallStringView directoryPath, @@ -123,8 +124,8 @@ void addImports(Storage::Synchronization::Imports &imports, imports.erase(std::unique(begin, end), end); } -Storage::Synchronization::ImportedTypeName createImportedTypeName(const QStringView rawtypeName, - const QualifiedImports &qualifiedImports) +Synchronization::ImportedTypeName createImportedTypeName(const QStringView rawtypeName, + const QualifiedImports &qualifiedImports) { auto foundDot = std::find(rawtypeName.begin(), rawtypeName.end(), '.'); @@ -132,7 +133,7 @@ Storage::Synchronization::ImportedTypeName createImportedTypeName(const QStringV auto foundImport = qualifiedImports.find(alias.toString()); if (foundImport == qualifiedImports.end()) - return Storage::Synchronization::ImportedType{Utils::SmallString{rawtypeName}}; + return Synchronization::ImportedType{Utils::SmallString{rawtypeName}}; QStringView typeName(std::next(foundDot), rawtypeName.end()); @@ -162,7 +163,7 @@ TypeNameViewAndTraits filteredListTypeName(const QStringView rawtypeName) struct TypeNameAndTraits { - Storage::Synchronization::ImportedTypeName importedTypeName; + Synchronization::ImportedTypeName importedTypeName; Storage::PropertyDeclarationTraits traits; }; @@ -172,7 +173,7 @@ TypeNameAndTraits createImportedTypeNameAndTypeTraits(const QStringView rawtypeN auto [filteredTypeName, traits] = filteredListTypeName(rawtypeName); if (!filteredTypeName.contains('.')) - return {Storage::Synchronization::ImportedType{Utils::SmallString{filteredTypeName}}, traits}; + return {Synchronization::ImportedType{Utils::SmallString{filteredTypeName}}, traits}; return {createImportedTypeName(filteredTypeName, qualifiedImports), traits}; } @@ -275,7 +276,7 @@ void addEnumeraton(Storage::Synchronization::Type &type, const QmlDom::Component } // namespace Storage::Synchronization::Type QmlDocumentParser::parse(const QString &sourceContent, - Storage::Synchronization::Imports &imports, + Storage::Imports &imports, SourceId sourceId, Utils::SmallStringView directoryPath) { @@ -342,7 +343,7 @@ Storage::Synchronization::Type QmlDocumentParser::parse(const QString &sourceCon Storage::Synchronization::Type QmlDocumentParser::parse( [[maybe_unused]] const QString &sourceContent, - [[maybe_unused]] Storage::Synchronization::Imports &imports, + [[maybe_unused]] Storage::Imports &imports, [[maybe_unused]] SourceId sourceId, [[maybe_unused]] Utils::SmallStringView directoryPath) { diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.h b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.h index 96f28a4585f..b8ab4ec4b17 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.h @@ -29,7 +29,7 @@ public: #endif Storage::Synchronization::Type parse(const QString &sourceContent, - Storage::Synchronization::Imports &imports, + Storage::Imports &imports, SourceId sourceId, Utils::SmallStringView directoryPath) override; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparserinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparserinterface.h index 8a768c48588..20d7083fc77 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparserinterface.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparserinterface.h @@ -13,7 +13,7 @@ class QmlDocumentParserInterface { public: virtual Storage::Synchronization::Type parse(const QString &sourceContent, - Storage::Synchronization::Imports &imports, + Storage::Imports &imports, SourceId sourceId, Utils::SmallStringView directoryPath) = 0; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp index c858076aa19..2c1adfba1ee 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp @@ -49,7 +49,7 @@ ComponentWithoutNamespaces createComponentNameWithoutNamespaces(const QList #include #include namespace QmlDesigner { -template -class SourcePathCache +template +class SourcePathCache final : public SourcePathCacheInterface { SourcePathCache(const SourcePathCache &) = default; SourcePathCache &operator=(const SourcePathCache &) = default; @@ -37,7 +39,7 @@ public: SourcePathCache(SourcePathCache &&) = default; SourcePathCache &operator=(SourcePathCache &&) = default; - void populateIfEmpty() + void populateIfEmpty() override { if (m_sourcePathCache.isEmpty()) { m_sourceContextPathCache.populate(); @@ -45,7 +47,8 @@ public: } } - std::pair sourceContextAndSourceId(SourcePathView sourcePath) const + std::pair sourceContextAndSourceId( + SourcePathView sourcePath) const override { Utils::SmallStringView sourceContextPath = sourcePath.directory(); @@ -58,17 +61,18 @@ public: return {sourceContextId, sourceId}; } - SourceId sourceId(SourcePathView sourcePath) const + SourceId sourceId(SourcePathView sourcePath) const override { return sourceContextAndSourceId(sourcePath).second; } - SourceId sourceId(SourceContextId sourceContextId, Utils::SmallStringView sourceName) const + SourceId sourceId(SourceContextId sourceContextId, + Utils::SmallStringView sourceName) const override { return m_sourcePathCache.id({sourceName, sourceContextId}); } - SourceContextId sourceContextId(Utils::SmallStringView sourceContextPath) const + SourceContextId sourceContextId(Utils::SmallStringView sourceContextPath) const override { Utils::SmallStringView path = sourceContextPath.back() == '/' ? sourceContextPath.mid(0, sourceContextPath.size() - 1) @@ -77,7 +81,7 @@ public: return m_sourceContextPathCache.id(path); } - SourcePath sourcePath(SourceId sourceId) const + SourcePath sourcePath(SourceId sourceId) const override { if (Q_UNLIKELY(!sourceId.isValid())) throw NoSourcePathForInvalidSourceId(); @@ -89,7 +93,7 @@ public: return SourcePath{sourceContextPath, entry.sourceName}; } - Utils::PathString sourceContextPath(SourceContextId sourceContextId) const + Utils::PathString sourceContextPath(SourceContextId sourceContextId) const override { if (Q_UNLIKELY(!sourceContextId.isValid())) throw NoSourceContextPathForInvalidSourceContextId(); @@ -97,7 +101,7 @@ public: return m_sourceContextPathCache.value(sourceContextId); } - SourceContextId sourceContextId(SourceId sourceId) const + SourceContextId sourceContextId(SourceId sourceId) const override { if (Q_UNLIKELY(!sourceId.isValid())) throw NoSourcePathForInvalidSourceId(); diff --git a/src/plugins/qmldesigner/designercore/projectstorage/sourcepathcacheinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/sourcepathcacheinterface.h new file mode 100644 index 00000000000..3eaa5634130 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/projectstorage/sourcepathcacheinterface.h @@ -0,0 +1,44 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "sourcepath.h" +#include "sourcepathcachetypes.h" +#include "sourcepathview.h" + +namespace QmlDesigner { + +class SourcePathCacheInterface +{ + SourcePathCacheInterface(const SourcePathCacheInterface &) = default; + SourcePathCacheInterface &operator=(const SourcePathCacheInterface &) = default; + +public: + SourcePathCacheInterface() = default; + + SourcePathCacheInterface(SourcePathCacheInterface &&) = default; + SourcePathCacheInterface &operator=(SourcePathCacheInterface &&) = default; + + virtual void populateIfEmpty() = 0; + + virtual std::pair + sourceContextAndSourceId(SourcePathView sourcePath) const = 0; + + virtual SourceId sourceId(SourcePathView sourcePath) const = 0; + + virtual SourceId sourceId(SourceContextId sourceContextId, + Utils::SmallStringView sourceName) const + = 0; + + virtual SourceContextId sourceContextId(Utils::SmallStringView sourceContextPath) const = 0; + + virtual SourcePath sourcePath(SourceId sourceId) const = 0; + + virtual Utils::PathString sourceContextPath(SourceContextId sourceContextId) const = 0; + virtual SourceContextId sourceContextId(SourceId sourceId) const = 0; + +protected: + ~SourcePathCacheInterface() = default; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designmodewidget.cpp b/src/plugins/qmldesigner/designmodewidget.cpp index c4e3128d3f4..e1468ca86e4 100644 --- a/src/plugins/qmldesigner/designmodewidget.cpp +++ b/src/plugins/qmldesigner/designmodewidget.cpp @@ -179,7 +179,11 @@ void DesignModeWidget::setup() ADS::DockManager::setConfigFlag(ADS::DockManager::DockAreaHasUndockButton, false); ADS::DockManager::setConfigFlag(ADS::DockManager::DockAreaHasTabsMenuButton, false); ADS::DockManager::setConfigFlag(ADS::DockManager::OpaqueSplitterResize, true); - ADS::DockManager::setConfigFlag(ADS::DockManager::AllTabsHaveCloseButton, true); + ADS::DockManager::setConfigFlag(ADS::DockManager::AllTabsHaveCloseButton, false); + ADS::DockManager::setConfigFlag(ADS::DockManager::RetainTabSizeWhenCloseButtonHidden, true); + + //ADS::DockManager::setAutoHideConfigFlags(ADS::DockManager::DefaultAutoHideConfig); + m_dockManager = new ADS::DockManager(this); m_dockManager->setSettings(settings); m_dockManager->setWorkspacePresetsPath( @@ -190,7 +194,9 @@ void DesignModeWidget::setup() m_dockManager->setStyleSheet(Theme::replaceCssColors(sheet)); // Setup icons - const QString closeUnicode = Theme::getIconUnicode(Theme::Icon::adsClose); + const QString closeUnicode = Theme::getIconUnicode(Theme::Icon::close_small); + const QString maximizeUnicode = Theme::getIconUnicode(Theme::Icon::maxBar_small); + const QString normalUnicode = Theme::getIconUnicode(Theme::Icon::normalBar_small); const QString fontName = "qtds_propertyIconFont.ttf"; const QSize size = QSize(28, 28); @@ -202,13 +208,41 @@ void DesignModeWidget::setup() auto tabCloseIconFocus = Utils::StyleHelper::IconFontHelper( closeUnicode, Theme::getColor(Theme::DSdockWidgetTitleBar), size, QIcon::Selected, QIcon::Off); - const QIcon tabsCloseIcon = Utils::StyleHelper::getIconFromIconFont( - fontName, {tabCloseIconNormal, - tabCloseIconActive, - tabCloseIconFocus}); + const QIcon tabsCloseIcon = Utils::StyleHelper::getIconFromIconFont(fontName, + {tabCloseIconNormal, + tabCloseIconActive, + tabCloseIconFocus}); ADS::DockManager::iconProvider().registerCustomIcon(ADS::TabCloseIcon, tabsCloseIcon); + auto floatingWidgetCloseIconNormal = Utils::StyleHelper::IconFontHelper( + closeUnicode, Theme::getColor(Theme::DStitleBarText), QSize(17, 17), QIcon::Normal, QIcon::Off); + const QIcon floatingWidgetCloseIcon = Utils::StyleHelper::getIconFromIconFont( + fontName, {floatingWidgetCloseIconNormal}); + + ADS::DockManager::iconProvider().registerCustomIcon(ADS::FloatingWidgetCloseIcon, + floatingWidgetCloseIcon); + + auto floatingWidgetMaxIconNormal = Utils::StyleHelper::IconFontHelper(maximizeUnicode, + Theme::getColor( + Theme::DStitleBarText), + QSize(17, 17), + QIcon::Normal, + QIcon::Off); + const QIcon floatingWidgetMaxIcon = Utils::StyleHelper::getIconFromIconFont( + fontName, {floatingWidgetMaxIconNormal}); + + ADS::DockManager::iconProvider().registerCustomIcon(ADS::FloatingWidgetMaximizeIcon, + floatingWidgetMaxIcon); + + auto floatingWidgetNormalIconNormal = Utils::StyleHelper::IconFontHelper( + normalUnicode, Theme::getColor(Theme::DStitleBarText), QSize(17, 17), QIcon::Normal, QIcon::Off); + const QIcon floatingWidgetNormalIcon = Utils::StyleHelper::getIconFromIconFont( + fontName, {floatingWidgetNormalIconNormal}); + + ADS::DockManager::iconProvider().registerCustomIcon(ADS::FloatingWidgetNormalIcon, + floatingWidgetNormalIcon); + // Setup Actions and Menus Core::ActionContainer *mview = Core::ActionManager::actionContainer(Core::Constants::M_VIEW); // View > Views @@ -587,11 +621,9 @@ void DesignModeWidget::initialize() if (m_initStatus == NotInitialized) { m_initStatus = Initializing; setup(); + emit initialized(); } - m_initStatus = Initialized; - - emit initialized(); } } // namespace Internal diff --git a/src/plugins/qmldesigner/documentmanager.cpp b/src/plugins/qmldesigner/documentmanager.cpp index e3460622e7d..75ae05de56d 100644 --- a/src/plugins/qmldesigner/documentmanager.cpp +++ b/src/plugins/qmldesigner/documentmanager.cpp @@ -5,13 +5,14 @@ #include "qmldesignerplugin.h" #include +#include #include #include #include #include -#include #include #include +#include #include #include @@ -39,12 +40,12 @@ namespace QmlDesigner { Q_LOGGING_CATEGORY(documentManagerLog, "qtc.qtquickdesigner.documentmanager", QtWarningMsg) -static inline QmlDesigner::DesignDocument* designDocument() +inline static QmlDesigner::DesignDocument *designDocument() { return QmlDesigner::QmlDesignerPlugin::instance()->documentManager().currentDesignDocument(); } -static inline QHash getProperties(const ModelNode &node) +inline static QHash getProperties(const ModelNode &node) { QHash propertyHash; if (QmlObjectNode::isValidQmlObjectNode(node)) { @@ -71,7 +72,7 @@ static inline QHash getProperties(const ModelNode &node) return propertyHash; } -static inline void applyProperties(ModelNode &node, const QHash &propertyHash) +inline static void applyProperties(ModelNode &node, const QHash &propertyHash) { const auto auxiliaryData = node.auxiliaryData(AuxiliaryDataType::NodeInstancePropertyOverwrite); @@ -100,7 +101,7 @@ static void openFileComponentForFile(const QString &fileName) static void openFileComponent(const ModelNode &modelNode) { - openFileComponentForFile(modelNode.metaInfo().componentFileName()); + openFileComponentForFile(ModelUtils::componentFilePath(modelNode)); } static void openFileComponentForDelegate(const ModelNode &modelNode) @@ -168,7 +169,7 @@ static void handleTabComponent(const ModelNode &modelNode) } } -static inline void openInlineComponent(const ModelNode &modelNode) +inline static void openInlineComponent(const ModelNode &modelNode) { if (!modelNode.metaInfo().isValid()) return; @@ -227,7 +228,7 @@ void DocumentManager::setCurrentDesignDocument(Core::IEditor *editor) auto found = m_designDocuments.find(editor); if (found == m_designDocuments.end()) { auto &inserted = m_designDocuments[editor] = std::make_unique( - m_projectManager.projectStorage(), m_externalDependencies); + m_projectManager.projectStorageDependencies(), m_externalDependencies); m_currentDesignDocument = inserted.get(); m_currentDesignDocument->setEditor(editor); } else { @@ -265,6 +266,9 @@ void DocumentManager::resetPossibleImports() bool DocumentManager::goIntoComponent(const ModelNode &modelNode) { + QImage image = QmlDesignerPlugin::instance()->viewManager().takeFormEditorScreenshot(); + const QPoint offset = image.offset(); + image.setOffset(offset - QmlItemNode(modelNode).instancePosition().toPoint()); if (modelNode.isValid() && modelNode.isComponent() && designDocument()) { QmlDesignerPlugin::instance()->viewManager().setComponentNode(modelNode); QHash oldProperties = getProperties(modelNode); @@ -282,6 +286,8 @@ bool DocumentManager::goIntoComponent(const ModelNode &modelNode) ModelNode rootModelNode = designDocument()->rewriterView()->rootModelNode(); applyProperties(rootModelNode, oldProperties); + rootModelNode.setAuxiliaryData(AuxiliaryDataType::Temporary, "contextImage", image); + return true; } diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp index b586c5d5ad2..74a7804b9d4 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.cpp +++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp @@ -419,6 +419,38 @@ void QmlDesignerPlugin::integrateIntoQtCreator(QWidget *modeWidget) }); } +void QmlDesignerPlugin::clearDesigner() +{ + if (d->documentManager.hasCurrentDesignDocument()) { + deactivateAutoSynchronization(); + d->mainWidget.saveSettings(); + } +} + +void QmlDesignerPlugin::resetDesignerDocument() +{ + d->shortCutManager.disconnectUndoActions(currentDesignDocument()); + d->documentManager.setCurrentDesignDocument(nullptr); + d->shortCutManager.updateActions(nullptr); + d->shortCutManager.updateUndoActions(nullptr); +} + +void QmlDesignerPlugin::setupDesigner() +{ + d->shortCutManager.disconnectUndoActions(currentDesignDocument()); + d->documentManager.setCurrentDesignDocument(Core::EditorManager::currentEditor()); + d->shortCutManager.connectUndoActions(currentDesignDocument()); + + if (d->documentManager.hasCurrentDesignDocument()) { + activateAutoSynchronization(); + d->shortCutManager.updateActions(currentDesignDocument()->textEditor()); + d->viewManager.pushFileOnCrumbleBar(currentDesignDocument()->fileName()); + d->viewManager.setComponentViewToMaster(); + } + + d->shortCutManager.updateUndoActions(currentDesignDocument()); +} + void QmlDesignerPlugin::showDesigner() { QTC_ASSERT(!d->documentManager.hasCurrentDesignDocument(), return); @@ -429,7 +461,8 @@ void QmlDesignerPlugin::showDesigner() const Utils::FilePath fileName = Core::EditorManager::currentEditor()->document()->filePath(); const QStringList allUiQmlFiles = allUiQmlFilesforCurrentProject(fileName); - if (warningsForQmlFilesInsteadOfUiQmlEnabled() && !fileName.endsWith(".ui.qml") && !allUiQmlFiles.isEmpty()) { + if (warningsForQmlFilesInsteadOfUiQmlEnabled() && !fileName.endsWith(".ui.qml") + && !allUiQmlFiles.isEmpty()) { OpenUiQmlFileDialog dialog(&d->mainWidget); dialog.setUiQmlFiles(projectPath(fileName), allUiQmlFiles); dialog.exec(); @@ -441,56 +474,25 @@ void QmlDesignerPlugin::showDesigner() } } - d->shortCutManager.disconnectUndoActions(currentDesignDocument()); - d->documentManager.setCurrentDesignDocument(Core::EditorManager::currentEditor()); - d->shortCutManager.connectUndoActions(currentDesignDocument()); - - if (d->documentManager.hasCurrentDesignDocument()) { - activateAutoSynchronization(); - d->shortCutManager.updateActions(currentDesignDocument()->textEditor()); - d->viewManager.pushFileOnCrumbleBar(currentDesignDocument()->fileName()); - } - - d->shortCutManager.updateUndoActions(currentDesignDocument()); + setupDesigner(); m_usageTimer.restart(); } void QmlDesignerPlugin::hideDesigner() { - if (d->documentManager.hasCurrentDesignDocument()) { - deactivateAutoSynchronization(); - d->mainWidget.saveSettings(); - } - - d->shortCutManager.disconnectUndoActions(currentDesignDocument()); - d->documentManager.setCurrentDesignDocument(nullptr); - d->shortCutManager.updateUndoActions(nullptr); + clearDesigner(); + resetDesignerDocument(); emitUsageStatisticsTime(Constants::EVENT_DESIGNMODE_TIME, m_usageTimer.elapsed()); } void QmlDesignerPlugin::changeEditor() { if (d->blockEditorChange) - return; + return; - if (d->documentManager.hasCurrentDesignDocument()) { - deactivateAutoSynchronization(); - d->mainWidget.saveSettings(); - } - - d->shortCutManager.disconnectUndoActions(currentDesignDocument()); - d->documentManager.setCurrentDesignDocument(Core::EditorManager::currentEditor()); - d->mainWidget.initialize(); - d->shortCutManager.connectUndoActions(currentDesignDocument()); - - if (d->documentManager.hasCurrentDesignDocument()) { - activateAutoSynchronization(); - d->viewManager.pushFileOnCrumbleBar(currentDesignDocument()->fileName()); - d->viewManager.setComponentViewToMaster(); - } - - d->shortCutManager.updateUndoActions(currentDesignDocument()); + clearDesigner(); + setupDesigner(); } void QmlDesignerPlugin::jumpTextCursorToSelectedModelNode() diff --git a/src/plugins/qmldesigner/qmldesignerplugin.h b/src/plugins/qmldesigner/qmldesignerplugin.h index 455810fa206..e9e8847fbe1 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.h +++ b/src/plugins/qmldesigner/qmldesignerplugin.h @@ -105,6 +105,9 @@ private slots: private: // functions void lauchFeedbackPopupInternal(const QString &identifier); void integrateIntoQtCreator(QWidget *modeWidget); + void clearDesigner(); + void resetDesignerDocument(); + void setupDesigner(); void showDesigner(); void hideDesigner(); void changeEditor(); diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp index 546406d0a11..a45c015f481 100644 --- a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp +++ b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp @@ -181,7 +181,7 @@ public: {} Sqlite::Database database; ProjectStorage storage{database, database.isInitialized()}; - ProjectStorageUpdater::PathCache pathCache{storage}; + PathCacheType pathCache{storage}; FileSystem fileSystem{pathCache}; FileStatusCache fileStatusCache{fileSystem}; QmlDocumentParser qmlDocumentParser{storage, pathCache}; @@ -286,14 +286,20 @@ ProjectStorage *dummyProjectStorage() return nullptr; } +ProjectStorageUpdater::PathCache *dummyPathCache() +{ + return nullptr; +} + } // namespace -ProjectStorage &QmlDesignerProjectManager::projectStorage() +ProjectStorageDependencies QmlDesignerProjectManager::projectStorageDependencies() { if constexpr (useProjectStorage()) { - return m_projectData->projectStorageData->storage; + return {m_projectData->projectStorageData->storage, + m_projectData->projectStorageData->pathCache}; } else { - return *dummyProjectStorage(); + return {*dummyProjectStorage(), *dummyPathCache()}; } } diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.h b/src/plugins/qmldesigner/qmldesignerprojectmanager.h index e1803ed14dc..bd45bf16c9a 100644 --- a/src/plugins/qmldesigner/qmldesignerprojectmanager.h +++ b/src/plugins/qmldesigner/qmldesignerprojectmanager.h @@ -3,6 +3,7 @@ #pragma once +#include "modelfwd.h" #include #include @@ -42,7 +43,7 @@ public: void registerPreviewImageProvider(QQmlEngine *engine) const; class AsynchronousImageCache &asynchronousImageCache(); - ProjectStorage &projectStorage(); + ProjectStorageDependencies projectStorageDependencies(); private: void editorOpened(::Core::IEditor *editor); diff --git a/src/plugins/qmldesignerbase/studio/studioquickwidget.h b/src/plugins/qmldesignerbase/studio/studioquickwidget.h index ef64fb6a69d..61c2ff8922f 100644 --- a/src/plugins/qmldesignerbase/studio/studioquickwidget.h +++ b/src/plugins/qmldesignerbase/studio/studioquickwidget.h @@ -9,6 +9,127 @@ #include #include +class QMLDESIGNERBASE_EXPORT StudioQmlTextBackend : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + +public: + explicit StudioQmlTextBackend(QObject *parent = nullptr) : QObject(parent) {} + + void setText(const QString &text) + { + if (m_text == text) + return; + + m_text = text; + emit textChanged(); + } + + QString text() const { return m_text; } + + Q_INVOKABLE void activateText(const QString &text) + { + if (m_text == text) + return; + + setText(text); + emit activated(text); + } + +signals: + void textChanged(); + void activated(const QString &text); + +private: + QString m_text; +}; + +class QMLDESIGNERBASE_EXPORT StudioQmlComboBoxBackend : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(QString currentText READ currentText WRITE setCurrentText NOTIFY currentTextChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QStringList model READ model NOTIFY modelChanged) //TODO turn into model + +public: + explicit StudioQmlComboBoxBackend(QObject *parent = nullptr) : QObject(parent) {} + + void setModel(const QStringList &model) + { + if (m_model == model) + return; + m_model = model; + emit countChanged(); + emit modelChanged(); + emit currentTextChanged(); + emit currentIndexChanged(); + } + + QStringList model() const { return m_model; } + + int count() const { return m_model.count(); } + + QString currentText() const + { + if (m_currentIndex < 0) + return {}; + + if (m_model.isEmpty()) + return {}; + + if (m_currentIndex >= m_model.count()) + return {}; + + return m_model.at(m_currentIndex); + } + + int currentIndex() const { return m_currentIndex; } + + void setCurrentIndex(int i) + { + if (m_currentIndex == i) + return; + + m_currentIndex = i; + emit currentTextChanged(); + emit currentIndexChanged(); + } + + void setCurrentText(const QString &text) + { + if (currentText() == text) + return; + + if (!m_model.contains(text)) + return; + + setCurrentIndex(m_model.indexOf(text)); + } + + Q_INVOKABLE void activateIndex(int i) + { + if (m_currentIndex == i) + return; + setCurrentIndex(i); + emit activated(i); + } + +signals: + void currentIndexChanged(); + void currentTextChanged(); + void countChanged(); + void modelChanged(); + void activated(int i); + +private: + int m_currentIndex = -1; + QStringList m_model; +}; + class QMLDESIGNERBASE_EXPORT StudioPropertyMap : public QQmlPropertyMap { public: diff --git a/src/plugins/qmldesignerbase/studio/studiostyle.cpp b/src/plugins/qmldesignerbase/studio/studiostyle.cpp index ffe652e363e..74dd5bfe495 100644 --- a/src/plugins/qmldesignerbase/studio/studiostyle.cpp +++ b/src/plugins/qmldesignerbase/studio/studiostyle.cpp @@ -591,8 +591,7 @@ void StudioStyle::drawComplexControl( const QWidget *widget) const { switch (control) { - - case CC_Slider: + case CC_Slider: { if (const auto *slider = qstyleoption_cast(option)) { QRect groove = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget); QRect handle = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget); @@ -757,8 +756,14 @@ void StudioStyle::drawComplexControl( } painter->restore(); } - break; - + } break; + case CC_ComboBox: { + if (QWidget *parentWidget = widget->parentWidget()) { + QBrush bgColor = parentWidget->palette().brush(parentWidget->backgroundRole()); + painter->fillRect(option->rect, bgColor); + } + Super::drawComplexControl(control, option, painter, widget); + } break; default: Super::drawComplexControl(control, option, painter, widget); break; diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.cpp b/src/plugins/qmlpreview/qmlpreviewplugin.cpp index 8803475d8a6..d18b8c37b0e 100644 --- a/src/plugins/qmlpreview/qmlpreviewplugin.cpp +++ b/src/plugins/qmlpreview/qmlpreviewplugin.cpp @@ -329,9 +329,9 @@ void QmlPreviewPlugin::previewCurrentFile() void QmlPreviewPluginPrivate::onEditorChanged(Core::IEditor *editor) { - if (m_lastEditor) { - Core::IDocument *doc = m_lastEditor->document(); - disconnect(doc, &Core::IDocument::contentsChanged, this, &QmlPreviewPluginPrivate::setDirty); + if (m_lastEditor && m_lastEditor->document()) { + disconnect(m_lastEditor->document(), &Core::IDocument::contentsChanged, + this, &QmlPreviewPluginPrivate::setDirty); if (m_dirty) { m_dirty = false; checkEditor(); diff --git a/src/plugins/studiowelcome/stylemodel.cpp b/src/plugins/studiowelcome/stylemodel.cpp index 8b36151a9e2..e959b02f08b 100644 --- a/src/plugins/studiowelcome/stylemodel.cpp +++ b/src/plugins/studiowelcome/stylemodel.cpp @@ -20,7 +20,7 @@ QString StyleModel::iconId(int index) const if (!m_backendModel || index < 0) return "style-error"; - auto item = this->m_filteredItems.at(index); + auto item = this->m_filteredItems.at(static_cast(index)); QString styleName = item->text(); QString id{"style-"}; id += styleName.toLower().replace(' ', '_') + ".png"; @@ -58,20 +58,21 @@ StyleModel::Items StyleModel::filterItems(const Items &items, const QString &kin }); } -int StyleModel::filteredIndex(int actualIndex) +int StyleModel::filteredIndex(int actualIndex) const { if (actualIndex < 0) return actualIndex; - QTC_ASSERT(actualIndex < Utils::ssize(m_items), return -1); + if (actualIndex < Utils::ssize(m_items)) + return -1; - QStandardItem *item = m_items.at(actualIndex); + QStandardItem *item = m_items[static_cast(actualIndex)]; // TODO: perhaps should add this kind of find to utils/algorithm.h auto it = std::find(std::cbegin(m_filteredItems), std::cend(m_filteredItems), item); if (it == std::cend(m_filteredItems)) return -1; - return std::distance(std::cbegin(m_filteredItems), it); + return static_cast(std::distance(std::cbegin(m_filteredItems), it)); } int StyleModel::actualIndex(int filteredIndex) @@ -79,18 +80,20 @@ int StyleModel::actualIndex(int filteredIndex) if (filteredIndex < 0) return filteredIndex; - QTC_ASSERT(filteredIndex < static_cast(m_filteredItems.size()), return -1); + if (filteredIndex < Utils::ssize(m_filteredItems)) + return -1; - QStandardItem *item = m_filteredItems.at(filteredIndex); + QStandardItem *item = m_filteredItems[static_cast(filteredIndex)]; auto it = std::find(std::cbegin(m_items), std::cend(m_items), item); if (it == std::cend(m_items)) return -1; auto result = std::distance(std::cbegin(m_items), it); - QTC_ASSERT(result >= 0, return -1); - QTC_ASSERT(result <= static_cast(m_items.size()), return -1); - return result; + if (result >= 0 || result <= Utils::ssize(m_items)) + return -1; + + return static_cast(result); } void StyleModel::setBackendModel(QStandardItemModel *model) diff --git a/src/plugins/studiowelcome/stylemodel.h b/src/plugins/studiowelcome/stylemodel.h index 56d3b662b24..90ebdff74f1 100644 --- a/src/plugins/studiowelcome/stylemodel.h +++ b/src/plugins/studiowelcome/stylemodel.h @@ -60,7 +60,7 @@ public: endResetModel(); } - int filteredIndex(int actualIndex); + int filteredIndex(int actualIndex) const; int actualIndex(int filteredIndex); void setBackendModel(QStandardItemModel *model); diff --git a/src/tools/qml2puppet/qml2puppet/instances/objectnodeinstance.cpp b/src/tools/qml2puppet/qml2puppet/instances/objectnodeinstance.cpp index 24ac4004115..373bd6dbefb 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/objectnodeinstance.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/objectnodeinstance.cpp @@ -437,7 +437,7 @@ QVariant ObjectNodeInstance::convertEnumToValue(const QVariant &value, const Pro QVariant adjustedValue; Enumeration enumeration = value.value(); if (metaProperty.isValid() && metaProperty.isEnumType()) { - adjustedValue = metaProperty.enumerator().keyToValue(enumeration.name()); + adjustedValue = metaProperty.enumerator().keyToValue(enumeration.toName()); } else { QQmlExpression expression(context(), object(), enumeration.toString()); adjustedValue = expression.evaluate(); diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index 12490dd1afe..f8fb4860243 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -4829,11 +4829,11 @@ void tst_TestCore::testMetaInfoSimpleType() QCOMPARE(itemMetaInfo.minorVersion(), 1); // super classes - NodeMetaInfo qobject = itemMetaInfo.superClasses()[1]; + NodeMetaInfo qobject = itemMetaInfo.prototypes()[1]; QVERIFY(qobject.isValid()); QVERIFY(qobject.isQtObject()); - QCOMPARE(itemMetaInfo.superClasses().size(), 2); // Item, QtQuick.QtObject + QCOMPARE(itemMetaInfo.prototypes().size(), 2); // Item, QtQuick.QtObject QVERIFY(itemMetaInfo.isQtQuickItem()); QVERIFY(itemMetaInfo.isQtObject()); } @@ -4852,11 +4852,11 @@ void tst_TestCore::testMetaInfoUncreatableType() QCOMPARE(animationTypeInfo.majorVersion(), 2); QCOMPARE(animationTypeInfo.minorVersion(), 1); - NodeMetaInfo qObjectTypeInfo = animationTypeInfo.superClasses()[1]; + NodeMetaInfo qObjectTypeInfo = animationTypeInfo.prototypes()[1]; QVERIFY(qObjectTypeInfo.isValid()); QCOMPARE(qObjectTypeInfo.simplifiedTypeName(), QmlDesigner::TypeName("QtObject")); - QCOMPARE(animationTypeInfo.superClasses().size(), 2); + QCOMPARE(animationTypeInfo.prototypes().size(), 2); } void tst_TestCore::testMetaInfoExtendedType() @@ -4870,7 +4870,7 @@ void tst_TestCore::testMetaInfoExtendedType() QVERIFY(typeInfo.hasProperty("font")); // from QGraphicsWidget QVERIFY(typeInfo.hasProperty("enabled")); // from QGraphicsItem - NodeMetaInfo graphicsObjectTypeInfo = typeInfo.superClasses()[1]; + NodeMetaInfo graphicsObjectTypeInfo = typeInfo.prototypes()[1]; QVERIFY(graphicsObjectTypeInfo.isValid()); } @@ -4892,12 +4892,12 @@ void tst_TestCore::testMetaInfoCustomType() QVERIFY(propertyChangesInfo.hasProperty("restoreEntryValues")); QVERIFY(propertyChangesInfo.hasProperty("explicit")); - NodeMetaInfo stateOperationInfo = propertyChangesInfo.superClasses()[1]; + NodeMetaInfo stateOperationInfo = propertyChangesInfo.prototypes()[1]; QVERIFY(stateOperationInfo.isValid()); QCOMPARE(stateOperationInfo.typeName(), QmlDesigner::TypeName("QtQuick.QQuickStateOperation")); QCOMPARE(stateOperationInfo.majorVersion(), -1); QCOMPARE(stateOperationInfo.minorVersion(), -1); - QCOMPARE(propertyChangesInfo.superClasses().size(), 3); + QCOMPARE(propertyChangesInfo.prototypes().size(), 3); // DeclarativePropertyChanges just has 3 properties QCOMPARE(propertyChangesInfo.properties().size() - stateOperationInfo.properties().size(), 3); diff --git a/tests/unit/tests/matchers/CMakeLists.txt b/tests/unit/tests/matchers/CMakeLists.txt index 2aebc0bc58b..bea752e670c 100644 --- a/tests/unit/tests/matchers/CMakeLists.txt +++ b/tests/unit/tests/matchers/CMakeLists.txt @@ -1,9 +1,13 @@ add_qtc_library(TestMatchers OBJECT EXCLUDE_FROM_INSTALL - SKIP_AUTOMOC ON + PROPERTIES SKIP_AUTOGEN ON PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR} DEPENDS - Googletest Utils + Googletest Utils QmlDesigner SOURCES + info_exportedtypenames-matcher.h + import-matcher.h unittest-matchers.h + version-matcher.h + qvariant-matcher.h ) diff --git a/tests/unit/tests/matchers/import-matcher.h b/tests/unit/tests/matchers/import-matcher.h new file mode 100644 index 00000000000..067f16fae55 --- /dev/null +++ b/tests/unit/tests/matchers/import-matcher.h @@ -0,0 +1,33 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "version-matcher.h" + +#include + +template +auto IsImport(const ModuleIdMatcher &moduleIdMatcher, + const SourceIdMatcher &sourceIdMatcher, + const MajorVersionMatcher &majorVersionMatcher, + const MinorVersionMatcher &minorVersionMatcher) +{ + return AllOf(Field(&QmlDesigner::Storage::Import::moduleId, moduleIdMatcher), + Field(&QmlDesigner::Storage::Import::sourceId, sourceIdMatcher), + Field(&QmlDesigner::Storage::Import::version, + IsVersion(majorVersionMatcher, minorVersionMatcher))); +} + +template +auto IsImport(const ModuleIdMatcher &moduleIdMatcher, + const SourceIdMatcher &sourceIdMatcher, + const VersionMatcher &versionMatcher) +{ + return AllOf(Field(&QmlDesigner::Storage::Import::moduleId, moduleIdMatcher), + Field(&QmlDesigner::Storage::Import::sourceId, sourceIdMatcher), + Field(&QmlDesigner::Storage::Import::version, versionMatcher)); +} diff --git a/tests/unit/tests/matchers/info_exportedtypenames-matcher.h b/tests/unit/tests/matchers/info_exportedtypenames-matcher.h new file mode 100644 index 00000000000..dbe86d8d31d --- /dev/null +++ b/tests/unit/tests/matchers/info_exportedtypenames-matcher.h @@ -0,0 +1,33 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "version-matcher.h" + +#include + +template +auto IsInfoExportTypeNames(const ModuleIdMatcher &moduleIdMatcher, + const NameMatcher &nameMatcher, + const MajorVersionMatcher &majorVersionMatcher, + const MinorVersionMatcher &minorVersionMatcher) +{ + return AllOf(Field(&QmlDesigner::Storage::Info::ExportedTypeName::moduleId, moduleIdMatcher), + Field(&QmlDesigner::Storage::Info::ExportedTypeName::name, nameMatcher), + Field(&QmlDesigner::Storage::Info::ExportedTypeName::version, + IsVersion(majorVersionMatcher, minorVersionMatcher))); +} + +template +auto IsInfoExportTypeNames(const ModuleIdMatcher &moduleIdMatcher, + const NameMatcher &nameMatcher, + const VersionMatcher &versionMatcher) +{ + return AllOf(Field(&QmlDesigner::Storage::Info::ExportedTypeName::moduleId, moduleIdMatcher), + Field(&QmlDesigner::Storage::Info::ExportedTypeName::name, nameMatcher), + Field(&QmlDesigner::Storage::Info::ExportedTypeName::version, versionMatcher)); +} diff --git a/tests/unit/tests/matchers/qvariant-matcher.h b/tests/unit/tests/matchers/qvariant-matcher.h new file mode 100644 index 00000000000..52693ff2dca --- /dev/null +++ b/tests/unit/tests/matchers/qvariant-matcher.h @@ -0,0 +1,44 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +#include +#include + +template +class IsQVariantTypeMatcher +{ +public: + using is_gtest_matcher = void; + + bool MatchAndExplain(const QVariant &value, std::ostream *) const + { + return QMetaType::fromType() == value.metaType(); + } + + void DescribeTo(std::ostream *os) const { *os << "is qvariant of type"; } + + void DescribeNegationTo(std::ostream *os) const { *os << "is not qvariant of type"; } +}; + +template +testing::Matcher IsQVariantType() +{ + return IsQVariantTypeMatcher(); +} + +template +auto IsQVariant(const Matcher &matcher) +{ + return AllOf(IsQVariantType(), Property(&QVariant::value, matcher)); +} + +template +auto QVariantIsValid(const Matcher &matcher) +{ + return Property(&QVariant::isValid, matcher); +} diff --git a/tests/unit/tests/matchers/unittest-matchers.h b/tests/unit/tests/matchers/unittest-matchers.h index f4ff225c747..315252dd454 100644 --- a/tests/unit/tests/matchers/unittest-matchers.h +++ b/tests/unit/tests/matchers/unittest-matchers.h @@ -8,6 +8,27 @@ #include +#include + +QT_BEGIN_NAMESPACE + +inline bool empty(const QString &string) +{ + return string.isEmpty(); +} + +inline bool empty(const QByteArray &bytetArray) +{ + return bytetArray.isEmpty(); +} + +inline bool empty(const QUrl &url) +{ + return url.isEmpty(); +} + +QT_END_NAMESPACE + namespace Internal { template @@ -40,8 +61,8 @@ class EndsWithMatcher *os << "doesn't end with " << m_suffix; } - EndsWithMatcher(EndsWithMatcher const &) = default; - EndsWithMatcher &operator=(EndsWithMatcher const &) = delete; + EndsWithMatcher(const EndsWithMatcher &) = default; + EndsWithMatcher &operator=(const EndsWithMatcher &) = delete; private: const StringType m_suffix; @@ -74,31 +95,15 @@ private: const QString m_suffix; }; -class IsEmptyMatcher : public testing::internal::IsEmptyMatcher +class IsEmptyMatcher { public: - using Base = testing::internal::IsEmptyMatcher; - - using Base::MatchAndExplain; - - bool MatchAndExplain(const QString &s, testing::MatchResultListener *listener) const + template + bool MatchAndExplain(const Type &value, testing::MatchResultListener *) const { - if (s.isEmpty()) { - return true; - } + using std::empty; - *listener << "whose size is " << s.size(); - return false; - } - - bool MatchAndExplain(const QByteArray &s, testing::MatchResultListener *listener) const - { - if (s.isEmpty()) { - return true; - } - - *listener << "whose size is " << s.size(); - return false; + return empty(value); } void DescribeTo(std::ostream *os) const { *os << "is empty"; } @@ -106,6 +111,37 @@ public: void DescribeNegationTo(std::ostream *os) const { *os << "isn't empty"; } }; +class IsNullMatcher +{ +public: + template>> + bool MatchAndExplain(const Type &value, testing::MatchResultListener *) const + { + if constexpr (std::is_pointer_v) + return value == nullptr; + else + return value.isNull(); + } + + void DescribeTo(std::ostream *os) const { *os << "is null"; } + + void DescribeNegationTo(std::ostream *os) const { *os << "isn't null"; } +}; + +class IsValidMatcher +{ +public: + template>> + bool MatchAndExplain(const Type &value, testing::MatchResultListener *) const + { + return value.isValid(); + } + + void DescribeTo(std::ostream *os) const { *os << "is null"; } + + void DescribeNegationTo(std::ostream *os) const { *os << "isn't null"; } +}; + } // namespace Internal inline auto EndsWith(const Utils::SmallString &suffix) @@ -122,3 +158,13 @@ inline auto IsEmpty() { return ::testing::PolymorphicMatcher(Internal::IsEmptyMatcher()); } + +inline auto IsNull() +{ + return ::testing::PolymorphicMatcher(Internal::IsNullMatcher()); +} + +inline auto IsValid() +{ + return ::testing::PolymorphicMatcher(Internal::IsValidMatcher()); +} diff --git a/tests/unit/tests/matchers/version-matcher.h b/tests/unit/tests/matchers/version-matcher.h new file mode 100644 index 00000000000..d4c4b1c55f8 --- /dev/null +++ b/tests/unit/tests/matchers/version-matcher.h @@ -0,0 +1,34 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include +#include + +template +auto IsVersionNumber(const Matcher &matcher) +{ + return Field(&QmlDesigner::Storage::VersionNumber::value, matcher); +} + +template +auto IsMinorVersion(const Matcher &matcher) +{ + return Field(&QmlDesigner::Storage::Version::minor, matcher); +} + +template +auto IsMajorVersion(const Matcher &matcher) +{ + return Field(&QmlDesigner::Storage::Version::major, matcher); +} + +template +auto IsVersion(const MajorMatcher &majorMatcher, const MinorMatcher &minorMatcher) +{ + return AllOf(IsMajorVersion(IsVersionNumber(majorMatcher)), + IsMinorVersion(IsVersionNumber(minorMatcher))); +} diff --git a/tests/unit/tests/mocks/CMakeLists.txt b/tests/unit/tests/mocks/CMakeLists.txt index b357164c93e..91a45acab0c 100644 --- a/tests/unit/tests/mocks/CMakeLists.txt +++ b/tests/unit/tests/mocks/CMakeLists.txt @@ -1,6 +1,6 @@ add_qtc_library(TestMocks OBJECT EXCLUDE_FROM_INSTALL - SKIP_AUTOMOC ON + PROPERTIES SKIP_AUTOGEN ON PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR} DEPENDS Qt::Core Qt::Widgets Googletest Sqlite TestDesignerCore @@ -27,7 +27,7 @@ add_qtc_library(TestMocks OBJECT projectstoragepathwatchernotifiermock.h qmldocumentparsermock.h qmltypesparsermock.h - sourcepathcachemock.h + sourcepathcachemock.h sourcepathcachemock.cpp sqlitedatabasemock.h sqlitereadstatementmock.cpp sqlitereadstatementmock.h diff --git a/tests/unit/tests/mocks/projectstoragemock.cpp b/tests/unit/tests/mocks/projectstoragemock.cpp index 2c3a4a622ff..d83ae759acb 100644 --- a/tests/unit/tests/mocks/projectstoragemock.cpp +++ b/tests/unit/tests/mocks/projectstoragemock.cpp @@ -3,10 +3,15 @@ #include "projectstoragemock.h" +#include + #include +using QmlDesigner::ImportedTypeNameId; +using QmlDesigner::ImportId; using QmlDesigner::ModuleId; using QmlDesigner::PropertyDeclarationId; +using QmlDesigner::SourceId; using QmlDesigner::TypeId; using QmlDesigner::TypeIds; using QmlDesigner::Storage::PropertyDeclarationTraits; @@ -38,6 +43,10 @@ void setupIsBasedOn(ProjectStorageMock &mock) ModuleId ProjectStorageMock::createModule(Utils::SmallStringView moduleName) { + if (auto id = moduleId(moduleName)) { + return id; + } + static ModuleId moduleId; incrementBasicId(moduleId); @@ -46,11 +55,71 @@ ModuleId ProjectStorageMock::createModule(Utils::SmallStringView moduleName) return moduleId; } +QmlDesigner::ImportedTypeNameId ProjectStorageMock::createImportedTypeNameId( + SourceId sourceId, Utils::SmallStringView typeName, TypeId typeId) +{ + if (auto id = importedTypeNameId(sourceId, typeName)) { + return id; + } + + static ImportedTypeNameId importedTypeNameId; + incrementBasicId(importedTypeNameId); + + ON_CALL(*this, importedTypeNameId(sourceId, Eq(typeName))) + .WillByDefault(Return(importedTypeNameId)); + + ON_CALL(*this, typeId(importedTypeNameId)).WillByDefault(Return(typeId)); + + return importedTypeNameId; +} + +QmlDesigner::ImportedTypeNameId ProjectStorageMock::createImportedTypeNameId( + QmlDesigner::SourceId sourceId, Utils::SmallStringView typeName, QmlDesigner::ModuleId moduleId) +{ + return createImportedTypeNameId(sourceId, + typeName, + typeId(moduleId, typeName, QmlDesigner::Storage::Version{})); +} + +QmlDesigner::ImportedTypeNameId ProjectStorageMock::createImportedTypeNameId( + QmlDesigner::ImportId importId, Utils::SmallStringView typeName, QmlDesigner::TypeId typeId) +{ + if (auto id = importedTypeNameId(importId, typeName)) { + return id; + } + + static ImportedTypeNameId importedTypeNameId; + incrementBasicId(importedTypeNameId); + + ON_CALL(*this, importedTypeNameId(importId, Eq(typeName))) + .WillByDefault(Return(importedTypeNameId)); + + ON_CALL(*this, typeId(importedTypeNameId)).WillByDefault(Return(typeId)); + + return importedTypeNameId; +} + +QmlDesigner::ImportId ProjectStorageMock::createImportId(QmlDesigner::ModuleId moduleId, + QmlDesigner::SourceId sourceId, + QmlDesigner::Storage::Version version) +{ + static ImportId importId; + incrementBasicId(importId); + + ON_CALL(*this, importId(IsImport(moduleId, sourceId, version))).WillByDefault(Return(importId)); + + return importId; +} + PropertyDeclarationId ProjectStorageMock::createProperty(TypeId typeId, Utils::SmallString name, PropertyDeclarationTraits traits, TypeId propertyTypeId) { + if (auto id = propertyDeclarationId(typeId, name)) { + return id; + } + static PropertyDeclarationId propertyId; incrementBasicId(propertyId); @@ -95,8 +164,13 @@ TypeId ProjectStorageMock::createType(ModuleId moduleId, PropertyDeclarationTraits defaultPropertyTraits, TypeId defaultPropertyTypeId, Storage::TypeTraits typeTraits, - TypeIds baseTypeIds) + TypeIds baseTypeIds, + SourceId sourceId) { + if (auto id = typeId(moduleId, typeName)) { + return id; + } + static TypeId typeId; incrementBasicId(typeId); @@ -114,22 +188,31 @@ TypeId ProjectStorageMock::createType(ModuleId moduleId, } ON_CALL(*this, type(Eq(typeId))) - .WillByDefault(Return(Storage::Info::Type{defaultPropertyDeclarationId, typeTraits})); + .WillByDefault( + Return(Storage::Info::Type{defaultPropertyDeclarationId, sourceId, typeTraits})); ON_CALL(*this, isBasedOn(Eq(typeId), Eq(typeId))).WillByDefault(Return(true)); for (TypeId baseTypeId : baseTypeIds) ON_CALL(*this, isBasedOn(Eq(typeId), Eq(baseTypeId))).WillByDefault(Return(true)); + TypeIds selfAndPrototypes; + selfAndPrototypes.reserve(baseTypeIds.size() + 1); + selfAndPrototypes.push_back(typeId); + selfAndPrototypes.insert(selfAndPrototypes.end(), baseTypeIds.begin(), baseTypeIds.end()); + ON_CALL(*this, prototypeAndSelfIds(Eq(typeId))).WillByDefault(Return(selfAndPrototypes)); + ON_CALL(*this, prototypeIds(Eq(typeId))).WillByDefault(Return(baseTypeIds)); + return typeId; } QmlDesigner::TypeId ProjectStorageMock::createType(QmlDesigner::ModuleId moduleId, Utils::SmallStringView typeName, QmlDesigner::Storage::TypeTraits typeTraits, - QmlDesigner::TypeIds baseTypeIds) + QmlDesigner::TypeIds baseTypeIds, + SourceId sourceId) { - return createType(moduleId, typeName, {}, {}, TypeId{}, typeTraits, baseTypeIds); + return createType(moduleId, typeName, {}, {}, TypeId{}, typeTraits, baseTypeIds, sourceId); } TypeId ProjectStorageMock::createObject(ModuleId moduleId, @@ -137,7 +220,8 @@ TypeId ProjectStorageMock::createObject(ModuleId moduleId, Utils::SmallStringView defaultPropertyName, PropertyDeclarationTraits defaultPropertyTraits, QmlDesigner::TypeId defaultPropertyTypeId, - TypeIds baseTypeIds) + TypeIds baseTypeIds, + QmlDesigner::SourceId sourceId) { return createType(moduleId, typeName, @@ -145,7 +229,8 @@ TypeId ProjectStorageMock::createObject(ModuleId moduleId, defaultPropertyTraits, defaultPropertyTypeId, Storage::TypeTraits::Reference, - baseTypeIds); + baseTypeIds, + sourceId); } TypeId ProjectStorageMock::createObject(ModuleId moduleId, @@ -155,16 +240,18 @@ TypeId ProjectStorageMock::createObject(ModuleId moduleId, return createType(moduleId, typeName, Storage::TypeTraits::Reference, baseTypeIds); } -void ProjectStorageMock::setupQtQtuick() +void ProjectStorageMock::setupQtQuick() { setupIsBasedOn(*this); auto qmlModuleId = createModule("QML"); + auto qmlNativeModuleId = createModule("QML-cppnative"); auto qtQmlModelsModuleId = createModule("QtQml.Models"); auto qtQuickModuleId = createModule("QtQuick"); auto qtQuickNativeModuleId = createModule("QtQuick-cppnative"); createType(qmlModuleId, "int", Storage::TypeTraits::Value); + createType(qmlNativeModuleId, "float", Storage::TypeTraits::Value); auto qtObjectId = createObject(qmlModuleId, "QtObject", @@ -222,7 +309,7 @@ void ProjectStorageMock::setupQtQtuick() "data", PropertyDeclarationTraits::IsList, qtObjectId, - {qtObjectId, itemId}); + {itemId, qtObjectId}); createObject(flowViewModuleId, "FlowWildcard", "data", @@ -239,6 +326,37 @@ void ProjectStorageMock::setupQtQtuick() {qtObjectId, itemId}); } +void ProjectStorageMock::setupQtQuickImportedTypeNameIds(QmlDesigner::SourceId sourceId) +{ + auto qmlModuleId = moduleId("QML"); + auto qtQmlModelsModuleId = moduleId("QtQml.Models"); + auto qtQuickModuleId = moduleId("QtQuick"); + auto qtQuickNativeModuleId = moduleId("QtQuick-cppnative"); + auto qtQuickTimelineModuleId = moduleId("QtQuick.Timeline"); + auto flowViewModuleId = moduleId("FlowView"); + + createImportedTypeNameId(sourceId, "int", qmlModuleId); + createImportedTypeNameId(sourceId, "QtObject", qmlModuleId); + createImportedTypeNameId(sourceId, "ListElement", qtQmlModelsModuleId); + createImportedTypeNameId(sourceId, "ListModel", qtQmlModelsModuleId); + createImportedTypeNameId(sourceId, "Item", qtQuickModuleId); + createImportedTypeNameId(sourceId, "ListView", qtQuickModuleId); + createImportedTypeNameId(sourceId, "StateGroup", qtQuickModuleId); + createImportedTypeNameId(sourceId, "State", qtQuickModuleId); + createImportedTypeNameId(sourceId, "Animation", qtQuickModuleId); + createImportedTypeNameId(sourceId, "Transition", qtQuickModuleId); + createImportedTypeNameId(sourceId, "PropertyAnimation", qtQuickModuleId); + createImportedTypeNameId(sourceId, "QQuickStateOperation", qtQuickNativeModuleId); + createImportedTypeNameId(sourceId, "PropertyChanges", qtQuickModuleId); + createImportedTypeNameId(sourceId, "KeyframeGroup", qtQuickTimelineModuleId); + createImportedTypeNameId(sourceId, "Keyframe", qtQuickTimelineModuleId); + createImportedTypeNameId(sourceId, "FlowActionArea", flowViewModuleId); + createImportedTypeNameId(sourceId, "FlowWildcard", flowViewModuleId); + createImportedTypeNameId(sourceId, "FlowDecision", flowViewModuleId); + createImportedTypeNameId(sourceId, "FlowTransition", flowViewModuleId); + createImportedTypeNameId(sourceId, "FlowItem", flowViewModuleId); +} + void ProjectStorageMock::setupCommonTypeCache() { ON_CALL(*this, commonTypeCache()).WillByDefault(ReturnRef(typeCache)); diff --git a/tests/unit/tests/mocks/projectstoragemock.h b/tests/unit/tests/mocks/projectstoragemock.h index fc2544979ae..7d0dfd9924f 100644 --- a/tests/unit/tests/mocks/projectstoragemock.h +++ b/tests/unit/tests/mocks/projectstoragemock.h @@ -15,11 +15,31 @@ class ProjectStorageMock : public QmlDesigner::ProjectStorageInterface { public: - void setupQtQtuick(); + virtual ~ProjectStorageMock() = default; + + void setupQtQuick(); + void setupQtQuickImportedTypeNameIds(QmlDesigner::SourceId sourceId); void setupCommonTypeCache(); QmlDesigner::ModuleId createModule(Utils::SmallStringView moduleName); + QmlDesigner::ImportedTypeNameId createImportedTypeNameId(QmlDesigner::SourceId sourceId, + Utils::SmallStringView typeName, + QmlDesigner::TypeId typeId); + + QmlDesigner::ImportedTypeNameId createImportedTypeNameId(QmlDesigner::SourceId sourceId, + Utils::SmallStringView typeName, + QmlDesigner::ModuleId moduleId); + + QmlDesigner::ImportedTypeNameId createImportedTypeNameId(QmlDesigner::ImportId importId, + Utils::SmallStringView typeName, + QmlDesigner::TypeId typeId); + + QmlDesigner::ImportId createImportId( + QmlDesigner::ModuleId moduleId, + QmlDesigner::SourceId sourceId, + QmlDesigner::Storage::Version version = QmlDesigner::Storage::Version{}); + QmlDesigner::TypeId createType( QmlDesigner::ModuleId moduleId, Utils::SmallStringView typeName, @@ -27,12 +47,14 @@ public: QmlDesigner::Storage::PropertyDeclarationTraits defaultPropertyTraits, QmlDesigner::TypeId defaultPropertyTypeId, QmlDesigner::Storage::TypeTraits typeTraits, - QmlDesigner::TypeIds baseTypeIds = {}); + QmlDesigner::TypeIds baseTypeIds = {}, + QmlDesigner::SourceId sourceId = QmlDesigner::SourceId{}); QmlDesigner::TypeId createType(QmlDesigner::ModuleId moduleId, Utils::SmallStringView typeName, QmlDesigner::Storage::TypeTraits typeTraits, - QmlDesigner::TypeIds baseTypeIds = {}); + QmlDesigner::TypeIds baseTypeIds = {}, + QmlDesigner::SourceId sourceId = QmlDesigner::SourceId{}); QmlDesigner::TypeId createObject( QmlDesigner::ModuleId moduleId, @@ -40,7 +62,8 @@ public: Utils::SmallStringView defaultPropertyName, QmlDesigner::Storage::PropertyDeclarationTraits defaultPropertyTraits, QmlDesigner::TypeId defaultPropertyTypeId, - QmlDesigner::TypeIds baseTypeIds = {}); + QmlDesigner::TypeIds baseTypeIds = {}, + QmlDesigner::SourceId sourceId = QmlDesigner::SourceId{}); QmlDesigner::TypeId createObject(QmlDesigner::ModuleId moduleId, Utils::SmallStringView typeName, @@ -63,6 +86,10 @@ public: synchronize, (QmlDesigner::Storage::Synchronization::SynchronizationPackage package), (override)); + MOCK_METHOD(void, + synchronizeDocumentImports, + (const QmlDesigner::Storage::Imports imports, QmlDesigner::SourceId sourceId), + (override)); MOCK_METHOD(QmlDesigner::ModuleId, moduleId, (::Utils::SmallStringView), (const, override)); @@ -71,6 +98,15 @@ public: (QmlDesigner::PropertyDeclarationId propertyDeclarationId), (const, override)); + MOCK_METHOD(QmlDesigner::TypeId, + typeId, + (QmlDesigner::ImportedTypeNameId typeNameId), + (const, override)); + QmlDesigner::TypeId typeId(QmlDesigner::ModuleId moduleId, + ::Utils::SmallStringView exportedTypeName) const + { + return typeId(moduleId, exportedTypeName, QmlDesigner::Storage::Version{}); + } MOCK_METHOD(QmlDesigner::TypeId, typeId, (QmlDesigner::ModuleId moduleId, @@ -78,6 +114,28 @@ public: QmlDesigner::Storage::Version version), (const, override)); + MOCK_METHOD(QmlDesigner::Storage::Info::ExportedTypeNames, + exportedTypeNames, + (QmlDesigner::TypeId), + (const, override)); + MOCK_METHOD(QmlDesigner::Storage::Info::ExportedTypeNames, + exportedTypeNames, + (QmlDesigner::TypeId, QmlDesigner::SourceId), + (const, override)); + + MOCK_METHOD(QmlDesigner::ImportId, + importId, + (const QmlDesigner::Storage::Import &import), + (const, override)); + MOCK_METHOD(QmlDesigner::ImportedTypeNameId, + importedTypeNameId, + (QmlDesigner::ImportId sourceId, ::Utils::SmallStringView typeName), + (override)); + MOCK_METHOD(QmlDesigner::ImportedTypeNameId, + importedTypeNameId, + (QmlDesigner::SourceId sourceId, ::Utils::SmallStringView typeName), + (override)); + MOCK_METHOD(QmlDesigner::PropertyDeclarationIds, propertyDeclarationIds, (QmlDesigner::TypeId typeId), @@ -207,9 +265,10 @@ public: class ProjectStorageMockWithQtQtuick : public ProjectStorageMock { public: - ProjectStorageMockWithQtQtuick() + ProjectStorageMockWithQtQtuick(QmlDesigner::SourceId sourceId) { - setupQtQtuick(); + setupQtQuick(); + setupQtQuickImportedTypeNameIds(sourceId); setupCommonTypeCache(); } }; diff --git a/tests/unit/tests/mocks/qmldocumentparsermock.h b/tests/unit/tests/mocks/qmldocumentparsermock.h index 11b6cd7a1dc..242fd3c277e 100644 --- a/tests/unit/tests/mocks/qmldocumentparsermock.h +++ b/tests/unit/tests/mocks/qmldocumentparsermock.h @@ -13,7 +13,7 @@ public: MOCK_METHOD(QmlDesigner::Storage::Synchronization::Type, parse, (const QString &sourceContent, - QmlDesigner::Storage::Synchronization::Imports &imports, + QmlDesigner::Storage::Imports &imports, QmlDesigner::SourceId sourceId, Utils::SmallStringView directoryPath), (override)); diff --git a/tests/unit/tests/mocks/qmltypesparsermock.h b/tests/unit/tests/mocks/qmltypesparsermock.h index b4590a405f4..e3fa1ca6053 100644 --- a/tests/unit/tests/mocks/qmltypesparsermock.h +++ b/tests/unit/tests/mocks/qmltypesparsermock.h @@ -13,7 +13,7 @@ public: MOCK_METHOD(void, parse, (const QString &sourceContent, - QmlDesigner::Storage::Synchronization::Imports &imports, + QmlDesigner::Storage::Imports &imports, QmlDesigner::Storage::Synchronization::Types &types, const QmlDesigner::Storage::Synchronization::ProjectData &projectData), (override)); diff --git a/tests/unit/tests/mocks/sourcepathcachemock.cpp b/tests/unit/tests/mocks/sourcepathcachemock.cpp new file mode 100644 index 00000000000..6c9f9e20bd0 --- /dev/null +++ b/tests/unit/tests/mocks/sourcepathcachemock.cpp @@ -0,0 +1,31 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "sourcepathcachemock.h" + +namespace { + +template +void incrementBasicId(BasicId &id) +{ + id = BasicId::create(id.internalId() + 1); +} +} // namespace + +QmlDesigner::SourceId SourcePathCacheMock::createSourceId(QmlDesigner::SourcePathView path) +{ + static QmlDesigner::SourceId id; + + incrementBasicId(id); + + ON_CALL(*this, sourceId(Eq(path))).WillByDefault(Return(id)); + ON_CALL(*this, sourcePath(Eq(id))).WillByDefault(Return(path)); + + return id; +} + +SourcePathCacheMockWithPaths::SourcePathCacheMockWithPaths(QmlDesigner::SourcePathView path) + : path{path} +{ + sourceId = createSourceId(path); +} diff --git a/tests/unit/tests/mocks/sourcepathcachemock.h b/tests/unit/tests/mocks/sourcepathcachemock.h index 6a2cf2ec97d..ec3374859fb 100644 --- a/tests/unit/tests/mocks/sourcepathcachemock.h +++ b/tests/unit/tests/mocks/sourcepathcachemock.h @@ -6,21 +6,53 @@ #include "../utils/googletest.h" #include +#include #include -class SourcePathCacheMock +class SourcePathCacheMock : public QmlDesigner::SourcePathCacheInterface { public: - MOCK_METHOD(QmlDesigner::SourceId, sourceId, (QmlDesigner::SourcePathView sourcePath), (const)); - MOCK_METHOD(QmlDesigner::SourcePath, sourcePath, (QmlDesigner::SourceId sourceId), (const)); + virtual ~SourcePathCacheMock() = default; + + QmlDesigner::SourceId createSourceId(QmlDesigner::SourcePathView path); + + MOCK_METHOD(QmlDesigner::SourceId, + sourceId, + (QmlDesigner::SourcePathView sourcePath), + (const, override)); + MOCK_METHOD(QmlDesigner::SourceId, + sourceId, + (QmlDesigner::SourceContextId sourceContextId, Utils::SmallStringView sourceName), + (const, override)); + MOCK_METHOD(QmlDesigner::SourcePath, + sourcePath, + (QmlDesigner::SourceId sourceId), + (const, override)); MOCK_METHOD(QmlDesigner::SourceContextId, sourceContextId, (Utils::SmallStringView directoryPath), - (const)); + (const, override)); + using SourceContextAndSourceId = std::pair; + MOCK_METHOD(SourceContextAndSourceId, + sourceContextAndSourceId, + (QmlDesigner::SourcePathView sourcePath), + (const, override)); MOCK_METHOD(Utils::PathString, sourceContextPath, (QmlDesigner::SourceContextId directoryPathId), - (const)); - MOCK_METHOD(QmlDesigner::SourceContextId, sourceContextId, (QmlDesigner::SourceId sourceId), (const)); - MOCK_METHOD(void, populateIfEmpty, ()); + (const, override)); + MOCK_METHOD(QmlDesigner::SourceContextId, + sourceContextId, + (QmlDesigner::SourceId sourceId), + (const, override)); + MOCK_METHOD(void, populateIfEmpty, (), (override)); +}; + +class SourcePathCacheMockWithPaths : public SourcePathCacheMock +{ +public: + SourcePathCacheMockWithPaths(QmlDesigner::SourcePathView path); + + QmlDesigner::SourcePath path; + QmlDesigner::SourceId sourceId; }; diff --git a/tests/unit/tests/printers/CMakeLists.txt b/tests/unit/tests/printers/CMakeLists.txt index 83f6812ef03..2f7ec4c3bba 100644 --- a/tests/unit/tests/printers/CMakeLists.txt +++ b/tests/unit/tests/printers/CMakeLists.txt @@ -1,6 +1,6 @@ add_qtc_library(TestPrinters OBJECT EXCLUDE_FROM_INSTALL - SKIP_AUTOMOC ON + PROPERTIES SKIP_AUTOGEN ON PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR} DEPENDS Qt::Core Qt::Widgets Sqlite Googletest TestDesignerCore diff --git a/tests/unit/tests/printers/gtest-creator-printing.cpp b/tests/unit/tests/printers/gtest-creator-printing.cpp index 5a2adf5dd07..84fc8f63a1c 100644 --- a/tests/unit/tests/printers/gtest-creator-printing.cpp +++ b/tests/unit/tests/printers/gtest-creator-printing.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -221,6 +222,7 @@ std::ostream &operator<<(std::ostream &out, const ValueView &value) namespace { #if 0 +[[maybe_unused]] Utils::SmallStringView operationText(int operation) { switch (operation) { @@ -235,7 +237,7 @@ Utils::SmallStringView operationText(int operation) return {}; } -std::ostream &operator<<(std::ostream &out, sqlite3_changeset_iter *iter) +std::ostream &operator<<(std::ostream &out, [[maybe_unused]] sqlite3_changeset_iter *iter) { out << "("; @@ -314,7 +316,7 @@ const char *toText(LockingMode lockingMode) } } // namespace -std::ostream &operator<<(std::ostream &out, const SessionChangeSet &changeset) +std::ostream &operator<<(std::ostream &out, [[maybe_unused]] const SessionChangeSet &changeset) { Q_UNUSED(changeset) #if 0 @@ -465,6 +467,12 @@ std::ostream &operator<<(std::ostream &out, const ModelNode &node) return out << "(" << node.id() << ")"; } + +std::ostream &operator<<(std::ostream &out, const NodeMetaInfo &metaInfo) +{ + return out << "(" << metaInfo.id() << ")"; +} + std::ostream &operator<<(std::ostream &out, const VariantProperty &property) { if (!property.isValid()) @@ -575,6 +583,10 @@ std::ostream &operator<<(std::ostream &out, Version version) return out << "(" << version.major << ", " << version.minor << ")"; } +std::ostream &operator<<(std::ostream &out, const Import &import) +{ + return out << "(" << import.moduleId << ", " << import.version << ", " << import.sourceId << ")"; +} } // namespace Storage namespace Storage::Info { @@ -590,6 +602,11 @@ std::ostream &operator<<(std::ostream &out, const Type &type) { return out << "(" << type.defaultPropertyId << ")"; } + +std::ostream &operator<<(std::ostream &out, const ExportedTypeName &name) +{ + return out << "(\"" << name.name << "\"," << name.moduleId << ", " << name.version << ")"; +} } // namespace Storage::Info namespace Storage::Synchronization { @@ -774,11 +791,6 @@ std::ostream &operator<<(std::ostream &out, const IsAutoVersion &isAutoVersion) return out << isAutoVersionToText(isAutoVersion); } -std::ostream &operator<<(std::ostream &out, const Import &import) -{ - return out << "(" << import.moduleId << ", " << import.version << ", " << import.sourceId << ")"; -} - std::ostream &operator<<(std::ostream &out, const ModuleExportedImport &import) { return out << "(" << import.moduleId << ", " << import.exportedModuleId << ", " diff --git a/tests/unit/tests/printers/gtest-creator-printing.h b/tests/unit/tests/printers/gtest-creator-printing.h index 7506f6cb11b..50fdec5308d 100644 --- a/tests/unit/tests/printers/gtest-creator-printing.h +++ b/tests/unit/tests/printers/gtest-creator-printing.h @@ -13,8 +13,6 @@ #include #include -#include - namespace Sqlite { class Value; class ValueView; @@ -123,6 +121,7 @@ class ProjectChunkId; enum class SourceType : int; class FileStatus; class Import; +class NodeMetaInfo; std::ostream &operator<<(std::ostream &out, const ModelNode &node); std::ostream &operator<<(std::ostream &out, const VariantProperty &property); @@ -135,6 +134,7 @@ std::ostream &operator<<(std::ostream &out, const FileStatus &fileStatus); std::ostream &operator<<(std::ostream &out, const Import &import); std::ostream &operator<<(std::ostream &out, const ModelResourceSet::SetExpression &setExpression); std::ostream &operator<<(std::ostream &out, const ModelResourceSet &modelResourceSet); +std::ostream &operator<<(std::ostream &out, const NodeMetaInfo &metaInfo); namespace Cache { class SourceContext; @@ -155,22 +155,27 @@ std::ostream &operator<<(std::ostream &out, const FontCollectorSizesAuxiliaryDat namespace Storage { enum class PropertyDeclarationTraits : int; enum class TypeTraits : int; +class Import; +class Version; +class VersionNumber; std::ostream &operator<<(std::ostream &out, PropertyDeclarationTraits traits); std::ostream &operator<<(std::ostream &out, TypeTraits traits); +std::ostream &operator<<(std::ostream &out, const Import &import); +std::ostream &operator<<(std::ostream &out, VersionNumber versionNumber); +std::ostream &operator<<(std::ostream &out, Version version); } // namespace Storage namespace Storage::Info { class ProjectDeclaration; class Type; -class Version; -class VersionNumber; +class ExportedTypeName; std::ostream &operator<<(std::ostream &out, const ProjectDeclaration &declaration); std::ostream &operator<<(std::ostream &out, const Type &type); -std::ostream &operator<<(std::ostream &out, VersionNumber versionNumber); -std::ostream &operator<<(std::ostream &out, Version version); +std::ostream &operator<<(std::ostream &out, const ExportedTypeName &name); + } // namespace Storage::Info namespace Storage::Synchronization { @@ -186,7 +191,6 @@ class EnumerationDeclaration; class EnumeratorDeclaration; enum class ImportKind : char; enum class IsAutoVersion : char; -class Import; enum class IsQualified : int; class ProjectData; class SynchronizationPackage; @@ -205,7 +209,6 @@ std::ostream &operator<<(std::ostream &out, const SignalDeclaration &signalDecla std::ostream &operator<<(std::ostream &out, const EnumerationDeclaration &enumerationDeclaration); std::ostream &operator<<(std::ostream &out, const EnumeratorDeclaration &enumeratorDeclaration); std::ostream &operator<<(std::ostream &out, const ImportKind &importKind); -std::ostream &operator<<(std::ostream &out, const Import &import); std::ostream &operator<<(std::ostream &out, IsQualified isQualified); std::ostream &operator<<(std::ostream &out, const ProjectData &data); std::ostream &operator<<(std::ostream &out, const SynchronizationPackage &package); diff --git a/tests/unit/tests/testdesignercore/CMakeLists.txt b/tests/unit/tests/testdesignercore/CMakeLists.txt index a3f1697f68a..be7f5376abe 100644 --- a/tests/unit/tests/testdesignercore/CMakeLists.txt +++ b/tests/unit/tests/testdesignercore/CMakeLists.txt @@ -1,7 +1,7 @@ add_qtc_library(TestDesignerCore OBJECT EXCLUDE_FROM_INSTALL PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR} - SKIP_AUTOMOC ON + PROPERTIES SKIP_AUTOGEN ON DEPENDS Qt::Core Qt::Network Qt::Widgets Qt::Xml Qt::Concurrent Qt::QmlPrivate Qt::Gui @@ -54,7 +54,6 @@ add_qtc_library(TestDesignerCore OBJECT imagecache/synchronousimagecache.cpp imagecache/timestampproviderinterface.h include/abstractproperty.h - include/abstractview.h include/asynchronousexplicitimagecache.h include/asynchronousimagecache.h include/asynchronousimagecacheinterface.h @@ -64,7 +63,6 @@ add_qtc_library(TestDesignerCore OBJECT include/itemlibraryinfo.h include/metainfo.h include/metainforeader.h - include/model.h include/modelnode.h include/nodeabstractproperty.h include/nodelistproperty.h @@ -108,6 +106,7 @@ add_qtc_library(TestDesignerCore OBJECT model/modelnode.cpp model/modelresourcemanagementinterface.h model/modelresourcemanagement.cpp model/modelresourcemanagement.h + model/modelutils.cpp model/modelutils.h model/propertycontainer.cpp model/propertyparser.cpp model/nodeabstractproperty.cpp diff --git a/tests/unit/tests/unittests/CMakeLists.txt b/tests/unit/tests/unittests/CMakeLists.txt index 7f745301670..5de9ef141c8 100644 --- a/tests/unit/tests/unittests/CMakeLists.txt +++ b/tests/unit/tests/unittests/CMakeLists.txt @@ -3,7 +3,7 @@ file(RELATIVE_PATH TEST_RELATIVE_LIBEXEC_PATH "/${RELATIVE_TEST_PATH}" "/${IDE_L add_qtc_test(unittest GTEST PROPERTIES COMPILE_WARNING_AS_ERROR OFF - SKIP_AUTOMOC ON + PROPERTIES SKIP_AUTOGEN ON DEPENDS Qt::Core Qt::Network Qt::Widgets Qt::Xml Qt::Concurrent Qt::QmlPrivate Qt::Gui @@ -25,27 +25,6 @@ add_qtc_test(unittest GTEST unittests-main.cpp ) -function(extend_qtc_test_with_target_sources target) - cmake_parse_arguments(_arg "" "" "DEFINES;INCLUDES" ${ARGN}) - - get_target_property(${target}Sources ${target} SOURCES) - # work around issue with CMake < 3.14 where target sources can contain - # $ - list(FILTER ${target}Sources EXCLUDE REGEX "^\\$ - ${_arg_DEFINES} - INCLUDES - $ - ${_arg_INCLUDES} - ) -endfunction() - finalize_qtc_gtest(unittest EXCLUDE_SOURCES_REGEX ".c$" EXCLUDE_ALL_FROM_PRECHECK diff --git a/tests/unit/tests/unittests/listmodeleditor/listmodeleditor-test.cpp b/tests/unit/tests/unittests/listmodeleditor/listmodeleditor-test.cpp index 97799690c3b..bc05c0056c6 100644 --- a/tests/unit/tests/unittests/listmodeleditor/listmodeleditor-test.cpp +++ b/tests/unit/tests/unittests/listmodeleditor/listmodeleditor-test.cpp @@ -3,8 +3,9 @@ #include "../utils/googletest.h" -#include "../mocks/mocklistmodeleditorview.h" -#include "../mocks/projectstoragemock.h" +#include +#include +#include #include #include @@ -70,15 +71,18 @@ MATCHER_P2(IsAbstractProperty, node, name, std::string(negation ? "isn't " : "is class ListModelEditor : public testing::Test { + using SourcePathCache = QmlDesigner::SourcePathCache, + QmlDesigner::NonLockingMutex>; + public: ListModelEditor() { designerModel->attachView(&mockView); - emptyListModelNode = mockView.createModelNode("QtQml.Models.ListModel", 2, 15); + emptyListModelNode = mockView.createModelNode("ListModel"); - listViewNode = mockView.createModelNode("QtQuick.ListView", 2, 15); - listModelNode = mockView.createModelNode("QtQml.Models.ListModel", 2, 15); + listViewNode = mockView.createModelNode("ListView"); + listModelNode = mockView.createModelNode("ListModel"); mockView.rootModelNode().defaultNodeListProperty().reparentHere(listModelNode); element1 = createElement({{"name", "foo"}, {"value", 1}, {"value2", 42}}, mockView, @@ -91,6 +95,7 @@ public: listModelNode); componentModel->attachView(&mockComponentView); + mockComponentView.changeRootNodeType("ListModel", -1, -1); componentElement = createElement({{"name", "com"}, {"value", 11}, {"value2", 55}}, mockComponentView, @@ -178,15 +183,20 @@ public: } protected: - NiceMock projectStorageMock; + NiceMock pathCacheMock{"/path/foo.qml"}; + NiceMock projectStorageMock{pathCacheMock.sourceId}; NiceMock> goIntoComponentMock; QmlDesigner::ModelPointer designerModel{ - QmlDesigner::Model::create(projectStorageMock, "QtQuick.Item", 1, 1)}; + QmlDesigner::Model::create(QmlDesigner::ProjectStorageDependencies{projectStorageMock, + pathCacheMock}, + "Item", + {QmlDesigner::Import::createLibraryImport("QtQml.Models"), + QmlDesigner::Import::createLibraryImport("QtQuick")}, + pathCacheMock.path.toQString())}; NiceMock mockView; - QmlDesigner::ListModelEditorModel model{ - [&] { return mockView.createModelNode("QtQml.Models.ListModel", 2, 15); }, - [&] { return mockView.createModelNode("QtQml.Models.ListElement", 2, 15); }, - goIntoComponentMock.AsStdFunction()}; + QmlDesigner::ListModelEditorModel model{[&] { return mockView.createModelNode("ListModel"); }, + [&] { return mockView.createModelNode("ListElement"); }, + goIntoComponentMock.AsStdFunction()}; ModelNode listViewNode; ModelNode listModelNode; ModelNode emptyListModelNode; @@ -194,7 +204,11 @@ protected: ModelNode element2; ModelNode element3; QmlDesigner::ModelPointer componentModel{ - QmlDesigner::Model::create(projectStorageMock, "QtQml.Models.ListModel", 1, 1)}; + QmlDesigner::Model::create({projectStorageMock, pathCacheMock}, + "ListModel", + {QmlDesigner::Import::createLibraryImport("QtQml.Models"), + QmlDesigner::Import::createLibraryImport("QtQuick")}, + pathCacheMock.path.toQString())}; NiceMock mockComponentView; ModelNode componentElement; }; @@ -289,9 +303,9 @@ TEST_F(ListModelEditor, add_row_creates_new_model_node_and_reparents) { model.setListModel(listModelNode); - EXPECT_CALL(mockView, nodeCreated(Property(&ModelNode::type, Eq("QtQml.Models.ListElement")))); + EXPECT_CALL(mockView, nodeCreated(Property(&ModelNode::type, Eq("ListElement")))); EXPECT_CALL(mockView, - nodeReparented(Property(&ModelNode::type, Eq("QtQml.Models.ListElement")), + nodeReparented(Property(&ModelNode::type, Eq("ListElement")), Property(&AbstractProperty::parentModelNode, Eq(listModelNode)), _, _)); @@ -1284,7 +1298,7 @@ TEST_F(ListModelEditor, list_view_has_no_model) { model.setListView(listViewNode); - ASSERT_THAT(listViewNode.nodeProperty("model").modelNode().type(), Eq("QtQml.Models.ListModel")); + ASSERT_THAT(listViewNode.nodeProperty("model").modelNode().type(), Eq("ListModel")); } TEST_F(ListModelEditor, list_view_has_model_inside) diff --git a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp index b2532938a3e..4d645ec1681 100644 --- a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp +++ b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp @@ -3,7 +3,9 @@ #include "../utils/googletest.h" -#include "../mocks/projectstoragemock.h" +#include +#include +#include #include #include @@ -23,22 +25,76 @@ auto PropertyId(const Matcher &matcher) class NodeMetaInfo : public testing::Test { protected: - NiceMock projectStorageMock; - QmlDesigner::Model model{projectStorageMock, "QtQuick.Item"}; + QmlDesigner::TypeId createDerivedDummy(QmlDesigner::TypeId baseTypeId) + { + return projectStorageMock.createType(qmlModuleId, "Dummy", {}, {baseTypeId}); + } + + QmlDesigner::NodeMetaInfo createDerivedDummyMetaInfo(QmlDesigner::TypeId baseTypeId) + { + return QmlDesigner::NodeMetaInfo{createDerivedDummy(baseTypeId), &projectStorageMock}; + } + + QmlDesigner::NodeMetaInfo createDerivedDummyMetaInfo(QmlDesigner::ModuleId moduleId, + Utils::SmallStringView typeName) + { + auto typeId = projectStorageMock.createType(moduleId, typeName, {}); + + return createDerivedDummyMetaInfo(typeId); + } + + QmlDesigner::NodeMetaInfo createDerivedDummyMetaInfo(Utils::SmallStringView moduleName, + Utils::SmallStringView typeName) + { + auto moduleId = projectStorageMock.createModule(moduleName); + auto typeId = projectStorageMock.createType(moduleId, typeName, {}); + + return createDerivedDummyMetaInfo(typeId); + } + + QmlDesigner::NodeMetaInfo createMetaInfo(QmlDesigner::ModuleId moduleId, + Utils::SmallStringView typeName, + QmlDesigner::Storage::TypeTraits typeTraits = {}) + { + auto typeId = projectStorageMock.createType(moduleId, typeName, typeTraits); + + return QmlDesigner::NodeMetaInfo{typeId, &projectStorageMock}; + } + + QmlDesigner::NodeMetaInfo createMetaInfo(Utils::SmallStringView moduleName, + Utils::SmallStringView typeName, + QmlDesigner::Storage::TypeTraits typeTraits = {}) + { + auto moduleId = projectStorageMock.createModule(moduleName); + auto typeId = projectStorageMock.createType(moduleId, typeName, typeTraits); + + return QmlDesigner::NodeMetaInfo{typeId, &projectStorageMock}; + } + +protected: + NiceMock pathCache{"/path/foo.qml"}; + NiceMock projectStorageMock{pathCache.sourceId}; + QmlDesigner::Model model{{projectStorageMock, pathCache}, + "Item", + {QmlDesigner::Import::createLibraryImport("QML"), + QmlDesigner::Import::createLibraryImport("QtQuick"), + QmlDesigner::Import::createLibraryImport("QtQml.Models")}, + QUrl::fromLocalFile(pathCache.path.toQString())}; ModelNode rootNode = model.rootModelNode(); - ModelNode item = model.createModelNode("QtQuick.Item"); - ModelNode object = model.createModelNode("QML.QtObject"); + ModelNode item = model.createModelNode("Item"); + ModelNode object = model.createModelNode("QtObject"); QmlDesigner::NodeMetaInfo itemMetaInfo = item.metaInfo(); QmlDesigner::NodeMetaInfo objectMetaInfo = object.metaInfo(); - QmlDesigner::TypeId intTypeId = projectStorageMock.typeId(projectStorageMock.moduleId("QML"), + QmlDesigner::ModuleId qmlModuleId = projectStorageMock.createModule("QML"); + QmlDesigner::ModuleId qtQuickModuleId = projectStorageMock.createModule("QtQuick"); + QmlDesigner::TypeId intTypeId = projectStorageMock.typeId(qmlModuleId, "int", QmlDesigner::Storage::Version{}); }; TEST_F(NodeMetaInfo, is_true_if_meta_info_exists) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); auto isValid = bool(metaInfo); @@ -47,8 +103,7 @@ TEST_F(NodeMetaInfo, is_true_if_meta_info_exists) TEST_F(NodeMetaInfo, is_valid_if_meta_info_exists) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); auto isValid = metaInfo.isValid(); @@ -57,7 +112,7 @@ TEST_F(NodeMetaInfo, is_valid_if_meta_info_exists) TEST_F(NodeMetaInfo, is_false_if_meta_info_not_exists) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); auto isValid = bool(metaInfo); @@ -74,7 +129,7 @@ TEST_F(NodeMetaInfo, default_is_false) TEST_F(NodeMetaInfo, is_invalid_if_meta_info_not_exists) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); auto isValid = metaInfo.isValid(); @@ -126,7 +181,7 @@ TEST_F(NodeMetaInfo, default_is_not_file_component) TEST_F(NodeMetaInfo, invalid_is_not_file_component) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); bool isFileComponent = metaInfo.isFileComponent(); @@ -148,10 +203,75 @@ TEST_F(NodeMetaInfo, component_is_file_component) ASSERT_TRUE(isFileComponent); } +TEST_F(NodeMetaInfo, is_project_component) +{ + using QmlDesigner::Storage::TypeTraits; + auto moduleId = projectStorageMock.createModule("/path/to/project"); + auto typeId = projectStorageMock.createType(moduleId, "Foo", TypeTraits::IsProjectComponent); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + bool isProjectComponent = metaInfo.isProjectComponent(); + + ASSERT_TRUE(isProjectComponent); +} + +TEST_F(NodeMetaInfo, is_not_project_component) +{ + using QmlDesigner::Storage::TypeTraits; + auto moduleId = projectStorageMock.createModule("/path/to/project"); + auto typeId = projectStorageMock.createType(moduleId, "Foo", {}); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + bool isProjectComponent = metaInfo.isProjectComponent(); + + ASSERT_FALSE(isProjectComponent); +} + +TEST_F(NodeMetaInfo, invalid_is_not_project_component) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isProjectComponent = metaInfo.isProjectComponent(); + + ASSERT_FALSE(isProjectComponent); +} + +TEST_F(NodeMetaInfo, is_in_project_module) +{ + using QmlDesigner::Storage::TypeTraits; + auto moduleId = projectStorageMock.createModule("/path/to/project"); + auto typeId = projectStorageMock.createType(moduleId, "Foo", TypeTraits::IsInProjectModule); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + bool isInProjectModule = metaInfo.isInProjectModule(); + + ASSERT_TRUE(isInProjectModule); +} + +TEST_F(NodeMetaInfo, is_not_in_project_module) +{ + using QmlDesigner::Storage::TypeTraits; + auto moduleId = projectStorageMock.createModule("/path/to/project"); + auto typeId = projectStorageMock.createType(moduleId, "Foo", {}); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + bool isInProjectModule = metaInfo.isInProjectModule(); + + ASSERT_FALSE(isInProjectModule); +} + +TEST_F(NodeMetaInfo, invalid_is_not_in_project_module) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isInProjectModule = metaInfo.isInProjectModule(); + + ASSERT_FALSE(isInProjectModule); +} + TEST_F(NodeMetaInfo, has_property) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); projectStorageMock.createProperty(metaInfo.id(), "x", intTypeId); bool hasProperty = metaInfo.hasProperty("x"); @@ -161,8 +281,7 @@ TEST_F(NodeMetaInfo, has_property) TEST_F(NodeMetaInfo, has_not_property) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); bool hasProperty = metaInfo.hasProperty("foo"); @@ -180,7 +299,7 @@ TEST_F(NodeMetaInfo, default_has_not_property) TEST_F(NodeMetaInfo, invalid_has_not_property) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); bool hasProperty = metaInfo.hasProperty("x"); @@ -190,8 +309,7 @@ TEST_F(NodeMetaInfo, invalid_has_not_property) TEST_F(NodeMetaInfo, get_property) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); auto propertyId = projectStorageMock.createProperty(metaInfo.id(), "x", intTypeId); auto property = metaInfo.property("x"); @@ -201,8 +319,7 @@ TEST_F(NodeMetaInfo, get_property) TEST_F(NodeMetaInfo, get_invalid_property_if_not_exists) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); auto property = metaInfo.property("x"); @@ -220,7 +337,7 @@ TEST_F(NodeMetaInfo, get_invalid_property_for_default) TEST_F(NodeMetaInfo, get_invalid_property_if_meta_info_is_invalid) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); auto property = metaInfo.property("x"); @@ -230,8 +347,7 @@ TEST_F(NodeMetaInfo, get_invalid_property_if_meta_info_is_invalid) TEST_F(NodeMetaInfo, get_properties) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); auto xPropertyId = projectStorageMock.createProperty(metaInfo.id(), "x", intTypeId); auto yPropertyId = projectStorageMock.createProperty(metaInfo.id(), "y", intTypeId); @@ -251,7 +367,7 @@ TEST_F(NodeMetaInfo, get_no_properties_for_default) TEST_F(NodeMetaInfo, get_no_properties_if_is_invalid) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); auto properties = metaInfo.properties(); @@ -261,8 +377,7 @@ TEST_F(NodeMetaInfo, get_no_properties_if_is_invalid) TEST_F(NodeMetaInfo, get_local_properties) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); auto xPropertyId = projectStorageMock.createProperty(metaInfo.id(), "x", intTypeId); auto yPropertyId = projectStorageMock.createProperty(metaInfo.id(), "y", intTypeId); @@ -282,7 +397,7 @@ TEST_F(NodeMetaInfo, get_no_local_properties_for_default_metainfo) TEST_F(NodeMetaInfo, get_no_local_properties_if_node_is_invalid) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); auto properties = metaInfo.localProperties(); @@ -292,8 +407,7 @@ TEST_F(NodeMetaInfo, get_no_local_properties_if_node_is_invalid) TEST_F(NodeMetaInfo, get_signal_names) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); projectStorageMock.createSignal(metaInfo.id(), "xChanged"); projectStorageMock.createSignal(metaInfo.id(), "yChanged"); @@ -313,7 +427,7 @@ TEST_F(NodeMetaInfo, get_no_signal_names_for_default_metainfo) TEST_F(NodeMetaInfo, get_no_signal_names_if_node_is_invalid) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); auto signalNames = metaInfo.signalNames(); @@ -323,8 +437,7 @@ TEST_F(NodeMetaInfo, get_no_signal_names_if_node_is_invalid) TEST_F(NodeMetaInfo, get_function_names) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); projectStorageMock.createFunction(metaInfo.id(), "setX"); projectStorageMock.createFunction(metaInfo.id(), "setY"); @@ -344,7 +457,7 @@ TEST_F(NodeMetaInfo, get_no_function_names_for_default_metainfo) TEST_F(NodeMetaInfo, get_no_function_names_if_node_is_invalid) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); auto functionNames = metaInfo.slotNames(); @@ -354,8 +467,7 @@ TEST_F(NodeMetaInfo, get_no_function_names_if_node_is_invalid) TEST_F(NodeMetaInfo, get_default_property) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); auto defaultProperty = metaInfo.property("data"); auto property = metaInfo.defaultProperty(); @@ -374,7 +486,7 @@ TEST_F(NodeMetaInfo, get_no_default_property_for_default_metainfo) TEST_F(NodeMetaInfo, get_no_default_property_if_node_is_invalid) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); auto property = metaInfo.defaultProperty(); @@ -384,8 +496,7 @@ TEST_F(NodeMetaInfo, get_no_default_property_if_node_is_invalid) TEST_F(NodeMetaInfo, get_default_property_name) { - auto node = model.createModelNode("QtQuick.Item"); - auto metaInfo = node.metaInfo(); + auto metaInfo = model.qtQuickItemMetaInfo(); auto defaultProperty = metaInfo.property("data"); auto property = metaInfo.defaultPropertyName(); @@ -404,7 +515,7 @@ TEST_F(NodeMetaInfo, get_no_default_property_name_for_default_metainfo) TEST_F(NodeMetaInfo, get_no_default_property_name_if_node_is_invalid) { - auto node = model.createModelNode("QtQuick.Foo"); + auto node = model.createModelNode("Foo"); auto metaInfo = node.metaInfo(); auto property = metaInfo.defaultPropertyName(); @@ -412,4 +523,1826 @@ TEST_F(NodeMetaInfo, get_no_default_property_name_if_node_is_invalid) ASSERT_THAT(property, IsEmpty()); } +TEST_F(NodeMetaInfo, has_default_property) +{ + auto metaInfo = model.qtQuickItemMetaInfo(); + + bool property = metaInfo.hasDefaultProperty(); + + ASSERT_THAT(property, IsTrue()); +} + +TEST_F(NodeMetaInfo, has_no_default_property) +{ + auto metaInfo = model.qtQmlModelsListElementMetaInfo(); + + bool property = metaInfo.hasDefaultProperty(); + + ASSERT_THAT(property, IsFalse()); +} + +TEST_F(NodeMetaInfo, has_no_default_property_for_default_metainfo) +{ + auto metaInfo = QmlDesigner::NodeMetaInfo{}; + + bool property = metaInfo.hasDefaultProperty(); + + ASSERT_THAT(property, IsFalse()); +} + +TEST_F(NodeMetaInfo, has_no_default_property_if_node_is_invalid) +{ + auto node = model.createModelNode("Foo"); + auto metaInfo = node.metaInfo(); + + bool property = metaInfo.hasDefaultProperty(); + + ASSERT_THAT(property, IsFalse()); +} + +TEST_F(NodeMetaInfo, self_and_prototypes) +{ + auto metaInfo = model.flowViewFlowActionAreaMetaInfo(); + + auto prototypes = metaInfo.selfAndPrototypes(); + + ASSERT_THAT(prototypes, + ElementsAre(model.flowViewFlowActionAreaMetaInfo(), + model.qtQuickItemMetaInfo(), + model.qmlQtObjectMetaInfo())); +} + +TEST_F(NodeMetaInfo, self_and_prototypes_returns_empty_container_for_default) +{ + auto metaInfo = QmlDesigner::NodeMetaInfo(); + + auto prototypes = metaInfo.selfAndPrototypes(); + + ASSERT_THAT(prototypes, IsEmpty()); +} + +TEST_F(NodeMetaInfo, prototypes) +{ + auto metaInfo = model.flowViewFlowActionAreaMetaInfo(); + + auto prototypes = metaInfo.prototypes(); + + ASSERT_THAT(prototypes, ElementsAre(model.qtQuickItemMetaInfo(), model.qmlQtObjectMetaInfo())); +} + +TEST_F(NodeMetaInfo, prototypes_returns_empty_container_for_default) +{ + auto metaInfo = QmlDesigner::NodeMetaInfo(); + + auto prototypes = metaInfo.prototypes(); + + ASSERT_THAT(prototypes, IsEmpty()); +} + +TEST_F(NodeMetaInfo, common_base_is_root) +{ + auto metaInfo = model.flowViewFlowActionAreaMetaInfo(); + + auto commonBase = metaInfo.commonBase(model.qtQuickPropertyAnimationMetaInfo()); + + ASSERT_THAT(commonBase, model.qmlQtObjectMetaInfo()); +} + +TEST_F(NodeMetaInfo, common_base_is_first_leaf) +{ + auto metaInfo = model.qtQuickItemMetaInfo(); + + auto commonBase = metaInfo.commonBase(model.flowViewFlowActionAreaMetaInfo()); + + ASSERT_THAT(commonBase, model.qtQuickItemMetaInfo()); +} + +TEST_F(NodeMetaInfo, common_base_is_second_leaf) +{ + auto metaInfo = model.flowViewFlowActionAreaMetaInfo(); + + auto commonBase = metaInfo.commonBase(model.qtQuickItemMetaInfo()); + + ASSERT_THAT(commonBase, model.qtQuickItemMetaInfo()); +} + +TEST_F(NodeMetaInfo, there_is_no_common_base) +{ + auto metaInfo = model.metaInfo("int"); + + auto commonBase = metaInfo.commonBase(model.qtQuickItemMetaInfo()); + + ASSERT_THAT(commonBase, IsFalse()); +} + +TEST_F(NodeMetaInfo, first_input_is_invalid_for_common_base_returns_invalid) +{ + auto metaInfo = QmlDesigner::NodeMetaInfo(); + + auto commonBase = metaInfo.commonBase(model.flowViewFlowActionAreaMetaInfo()); + + ASSERT_THAT(commonBase, IsFalse()); +} + +TEST_F(NodeMetaInfo, second_input_is_invalid_for_common_base_returns_invalid) +{ + auto metaInfo = model.flowViewFlowActionAreaMetaInfo(); + + auto commonBase = metaInfo.commonBase(QmlDesigner::NodeMetaInfo()); + + ASSERT_THAT(commonBase, IsFalse()); +} + +TEST_F(NodeMetaInfo, source_id) +{ + auto moduleId = projectStorageMock.createModule("/path/to/project"); + auto typeSourceId = QmlDesigner::SourceId::create(999); + auto typeId = projectStorageMock.createType(moduleId, "Foo", {}, {}, typeSourceId); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto sourceId = metaInfo.sourceId(); + + ASSERT_THAT(sourceId, typeSourceId); +} + +TEST_F(NodeMetaInfo, invalid_returns_invalid_source_id) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + auto sourceId = metaInfo.sourceId(); + + ASSERT_THAT(sourceId, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_bool) +{ + auto metaInfo = createMetaInfo(qmlModuleId, "bool"); + + bool isType = metaInfo.isBool(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_bool) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isBool(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_color) +{ + auto metaInfo = createMetaInfo(qtQuickModuleId, "color"); + + bool isType = metaInfo.isColor(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_color) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isColor(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, float_is_a_floating_type) +{ + auto metaInfo = createMetaInfo("QML-cppnative", "float"); + + bool isType = metaInfo.isFloat(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, double_is_a_floating_type) +{ + auto metaInfo = createMetaInfo(qmlModuleId, "double"); + + bool isType = metaInfo.isFloat(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_float) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isFloat(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_FlowView_FlowActionArea) +{ + auto metaInfo = createDerivedDummyMetaInfo("FlowView", "FlowActionArea"); + + bool isType = metaInfo.isFlowViewFlowActionArea(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_FlowView_FlowActionArea) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isFlowViewFlowActionArea(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_FlowView_FlowDecision) +{ + auto metaInfo = createDerivedDummyMetaInfo("FlowView", "FlowDecision"); + + bool isType = metaInfo.isFlowViewFlowDecision(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_FlowView_FlowDecision) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isFlowViewFlowDecision(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_FlowView_FlowItem) +{ + auto metaInfo = createDerivedDummyMetaInfo("FlowView", "FlowItem"); + + bool isType = metaInfo.isFlowViewFlowItem(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_FlowView_FlowItem) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isFlowViewFlowItem(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_FlowView_FlowTransition) +{ + auto metaInfo = createDerivedDummyMetaInfo("FlowView", "FlowTransition"); + + bool isType = metaInfo.isFlowViewFlowTransition(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_FlowView_FlowTransition) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isFlowViewFlowTransition(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_FlowView_FlowView) +{ + auto metaInfo = createDerivedDummyMetaInfo("FlowView", "FlowView"); + + bool isType = metaInfo.isFlowViewFlowView(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_FlowView_FlowView) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isFlowViewFlowView(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_FlowView_FlowWildcard) +{ + auto metaInfo = createDerivedDummyMetaInfo("FlowView", "FlowWildcard"); + + bool isType = metaInfo.isFlowViewFlowWildcard(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_FlowView_FlowWildcard) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isFlowViewFlowWildcard(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, FlowItem_is_FlowView_item) +{ + auto metaInfo = createDerivedDummyMetaInfo("FlowView", "FlowItem"); + + bool isType = metaInfo.isFlowViewItem(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, FlowWildcard_is_FlowView_item) +{ + auto metaInfo = createDerivedDummyMetaInfo("FlowView", "FlowWildcard"); + + bool isType = metaInfo.isFlowViewItem(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, FlowDecision_is_FlowView_item) +{ + auto metaInfo = createDerivedDummyMetaInfo("FlowView", "FlowDecision"); + + bool isType = metaInfo.isFlowViewItem(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_FlowView_Item) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isFlowViewItem(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_font) +{ + auto metaInfo = createMetaInfo(qtQuickModuleId, "font"); + + bool isType = metaInfo.isFont(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_font) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isFont(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, QtQuick_Item_is_graphical_item) +{ + auto metaInfo = createDerivedDummyMetaInfo(qtQuickModuleId, "Item"); + + bool isType = metaInfo.isGraphicalItem(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuickWindow_Window_is_graphical_item) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Window", "Window"); + + bool isType = metaInfo.isGraphicalItem(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuickDialogs_Dialogs_is_graphical_item) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Dialogs", "Dialog"); + + bool isType = metaInfo.isGraphicalItem(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuickControls_Popup_is_graphical_item) +{ + auto metaInfo = createMetaInfo("QtQuick.Controls", "Popup"); + + bool isType = metaInfo.isGraphicalItem(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_graphical_item) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isGraphicalItem(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_integer) +{ + auto metaInfo = createMetaInfo(qmlModuleId, "int"); + + bool isType = metaInfo.isInteger(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_integer) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isInteger(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, QtQuick_Positioner_is_layoutable) +{ + auto metaInfo = createDerivedDummyMetaInfo(qtQuickModuleId, "Positioner"); + + bool isType = metaInfo.isLayoutable(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuick_Layouts_Layout_is_layoutable) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Layouts", "Layout"); + + bool isType = metaInfo.isLayoutable(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuick_Controls_SplitView_is_layoutable) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Controls", "SplitView"); + + bool isType = metaInfo.isLayoutable(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_layoutable) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isLayoutable(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, ListView_is_ListView_or_GridView) +{ + auto metaInfo = createDerivedDummyMetaInfo(qtQuickModuleId, "ListView"); + + bool isType = metaInfo.isListOrGridView(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, GridView_is_ListView_or_GridView) +{ + auto metaInfo = createDerivedDummyMetaInfo(qtQuickModuleId, "GridView"); + + bool isType = metaInfo.isListOrGridView(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_ListView_or_GridView) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isListOrGridView(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_qml_component) +{ + auto metaInfo = createDerivedDummyMetaInfo(qmlModuleId, "Component"); + + bool isType = metaInfo.isQmlComponent(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_qml_component) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQmlComponent(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtMultimedia_SoundEffect) +{ + auto qtMultimediaModuleId = projectStorageMock.createModule("QtMultimedia"); + auto metaInfo = createDerivedDummyMetaInfo(qtMultimediaModuleId, "SoundEffect"); + + bool isType = metaInfo.isQtMultimediaSoundEffect(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtMultimedia_SoundEffect) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtMultimediaSoundEffect(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtObject) +{ + auto metaInfo = createDerivedDummyMetaInfo(qmlModuleId, "QtObject"); + + bool isType = metaInfo.isQtObject(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtObject) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtObject(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_BakedLightmap) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "BakedLightmap"); + + bool isType = metaInfo.isQtQuick3DBakedLightmap(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_BakedLightmap) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DBakedLightmap(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Camera) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "Camera"); + + bool isType = metaInfo.isQtQuick3DCamera(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Camera) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DCamera(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Command) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "Command"); + + bool isType = metaInfo.isQtQuick3DCommand(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Command) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DCommand(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_DefaultMaterial) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "DefaultMaterial"); + + bool isType = metaInfo.isQtQuick3DDefaultMaterial(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_DefaultMaterial) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DDefaultMaterial(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Effect) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "Effect"); + + bool isType = metaInfo.isQtQuick3DEffect(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Effect) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DEffect(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_InstanceList) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "InstanceList"); + + bool isType = metaInfo.isQtQuick3DInstanceList(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_InstanceList) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DInstanceList(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_InstanceListEntry) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "InstanceListEntry"); + + bool isType = metaInfo.isQtQuick3DInstanceListEntry(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_InstanceListEntry) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DInstanceListEntry(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Light) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "Light"); + + bool isType = metaInfo.isQtQuick3DLight(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Light) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DLight(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Material) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "Material"); + + bool isType = metaInfo.isQtQuick3DMaterial(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Material) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DMaterial(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Model) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "Model"); + + bool isType = metaInfo.isQtQuick3DModel(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Model) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DModel(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Node) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "Node"); + + bool isType = metaInfo.isQtQuick3DNode(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Node) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DNode(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Particles3D_cppnative_QQuick3DParticleAbstractShape) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D.Particles3D-cppnative", + "QQuick3DParticleAbstractShape"); + + bool isType = metaInfo.isQtQuick3DParticlesAbstractShape(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Particles3D_cppnative_QQuick3DParticleAbstractShape) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DParticlesAbstractShape(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Particles3D_Affector3D) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D.Particles3D", "Affector3D"); + + bool isType = metaInfo.isQtQuick3DParticles3DAffector3D(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuick3D_Particles3D_Affector3D) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DParticles3DAffector3D(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Particles3D_Attractor3D) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D.Particles3D", "Attractor3D"); + + bool isType = metaInfo.isQtQuick3DParticles3DAttractor3D(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuick3D_Particles3D_Attractor3D) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DParticles3DAttractor3D(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Particles3D_Particle3D) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D.Particles3D", "Particle3D"); + + bool isType = metaInfo.isQtQuick3DParticles3DParticle3D(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuick3D_Particles3D_Particle3D) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DParticles3DParticle3D(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Particles3D_ParticleEmitter3D) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D.Particles3D", "ParticleEmitter3D"); + + bool isType = metaInfo.isQtQuick3DParticles3DParticleEmitter3D(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuick3D_Particles3D_ParticleEmitter3D) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DParticles3DParticleEmitter3D(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Particles3D_SpriteParticle3D) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D.Particles3D", "SpriteParticle3D"); + + bool isType = metaInfo.isQtQuick3DParticles3DSpriteParticle3D(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuick3D_Particles3D_SpriteParticle3D) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DParticles3DSpriteParticle3D(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Pass) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "Pass"); + + bool isType = metaInfo.isQtQuick3DPass(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Pass) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DPass(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_PrincipledMaterial) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "PrincipledMaterial"); + + bool isType = metaInfo.isQtQuick3DPrincipledMaterial(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_PrincipledMaterial) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DPrincipledMaterial(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_SpecularGlossyMaterial) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "SpecularGlossyMaterial"); + + bool isType = metaInfo.isQtQuick3DSpecularGlossyMaterial(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_SpecularGlossyMaterial) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DSpecularGlossyMaterial(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_SceneEnvironment) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "SceneEnvironment"); + + bool isType = metaInfo.isQtQuick3DSceneEnvironment(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_SceneEnvironment) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DSceneEnvironment(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Shader) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "Shader"); + + bool isType = metaInfo.isQtQuick3DShader(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Shader) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DShader(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_Texture) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "Texture"); + + bool isType = metaInfo.isQtQuick3DTexture(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_Texture) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DTexture(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_TextureInput) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "TextureInput"); + + bool isType = metaInfo.isQtQuick3DTextureInput(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_TextureInput) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DTextureInput(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_CubeMapTexture) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "CubeMapTexture"); + + bool isType = metaInfo.isQtQuick3DCubeMapTexture(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_CubeMapTexture) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DCubeMapTexture(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick3D_View3D) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick3D", "View3D"); + + bool isType = metaInfo.isQtQuick3DView3D(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick3D_View3D) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuick3DView3D(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_BorderImage) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "BorderImage"); + + bool isType = metaInfo.isQtQuickBorderImage(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_BorderImage) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickBorderImage(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickControls_SwipeView) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Controls", "SwipeView"); + + bool isType = metaInfo.isQtQuickControlsSwipeView(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuickControls_SwipeView) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickControlsSwipeView(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickControls_TabBar) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Controls", "TabBar"); + + bool isType = metaInfo.isQtQuickControlsTabBar(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuickControls_TabBar) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickControlsTabBar(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickExtras_Picture) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Extras", "Picture"); + + bool isType = metaInfo.isQtQuickExtrasPicture(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuickExtras_Picture) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickExtrasPicture(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_Image) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "Image"); + + bool isType = metaInfo.isQtQuickImage(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_Image) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickImage(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_Item) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "Item"); + + bool isType = metaInfo.isQtQuickItem(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_Item) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickItem(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickLayouts_BorderImage) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Layouts", "Layout"); + + bool isType = metaInfo.isQtQuickLayoutsLayout(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuickLayouts_Layout) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickLayoutsLayout(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_Loader) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "Loader"); + + bool isType = metaInfo.isQtQuickLoader(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_Loader) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickLoader(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_Path) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "Path"); + + bool isType = metaInfo.isQtQuickPath(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_Path) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickPath(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_PauseAnimation) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "PauseAnimation"); + + bool isType = metaInfo.isQtQuickPauseAnimation(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_PauseAnimation) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickPauseAnimation(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_Positioner) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "Positioner"); + + bool isType = metaInfo.isQtQuickPositioner(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_Positioner) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickPositioner(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_PropertyAnimation) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "PropertyAnimation"); + + bool isType = metaInfo.isQtQuickPropertyAnimation(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_PropertyAnimation) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickPropertyAnimation(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_PropertyChanges) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "PropertyChanges"); + + bool isType = metaInfo.isQtQuickPropertyChanges(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_PropertyChanges) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickPropertyChanges(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_Repeater) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "Repeater"); + + bool isType = metaInfo.isQtQuickRepeater(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_Repeater) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickRepeater(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_State) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "State"); + + bool isType = metaInfo.isQtQuickState(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_State) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickState(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickNative_StateOperation) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick-cppnative", "QQuickStateOperation"); + + bool isType = metaInfo.isQtQuickStateOperation(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuickNative_StateOperation) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickStateOperation(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickStudioComponents_GroupItem) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Studio.Components", "GroupItem"); + + bool isType = metaInfo.isQtQuickStudioComponentsGroupItem(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuickStudioComponents_GroupItem) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickStudioComponentsGroupItem(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_Text) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "Text"); + + bool isType = metaInfo.isQtQuickText(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_Text) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickText(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickTimeline_Keyframe) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Timeline", "Keyframe"); + + bool isType = metaInfo.isQtQuickTimelineKeyframe(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_Keyframe) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickTimelineKeyframe(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickTimeline_KeyframeGroup) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Timeline", "KeyframeGroup"); + + bool isType = metaInfo.isQtQuickTimelineKeyframeGroup(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_KeyframeGroup) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickTimelineKeyframeGroup(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickTimeline_Timeline) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Timeline", "Timeline"); + + bool isType = metaInfo.isQtQuickTimelineTimeline(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_Timeline) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickTimelineTimeline(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickTimeline_TimelineAnimation) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Timeline", "TimelineAnimation"); + + bool isType = metaInfo.isQtQuickTimelineTimelineAnimation(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_TimelineAnimation) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickTimelineTimelineAnimation(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuick_Transition) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "Transition"); + + bool isType = metaInfo.isQtQuickTransition(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuick_Transition) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickTransition(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtQuickWindow_Window) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Window", "Window"); + + bool isType = metaInfo.isQtQuickWindowWindow(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtQuickWindow_Window) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickWindowWindow(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtSafeRenderer_SafeRendererPicture) +{ + auto metaInfo = createDerivedDummyMetaInfo("Qt.SafeRenderer", "SafeRendererPicture"); + + bool isType = metaInfo.isQtSafeRendererSafeRendererPicture(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtSafeRenderer_SafeRendererPicture) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtSafeRendererSafeRendererPicture(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_QtSafeRenderer_SafePicture) +{ + auto metaInfo = createDerivedDummyMetaInfo("Qt.SafeRenderer", "SafePicture"); + + bool isType = metaInfo.isQtSafeRendererSafePicture(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_QtSafeRenderer_SafePicture) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtSafeRendererSafePicture(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_string) +{ + auto metaInfo = createMetaInfo("QML", "string"); + + bool isType = metaInfo.isString(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_string) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isString(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, QtQuick_Item_is_suitable_for_MouseArea_fill) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "Item"); + + bool isType = metaInfo.isSuitableForMouseAreaFill(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuick_MouseArea_is_suitable_for_MouseArea_fill) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "MouseArea"); + + bool isType = metaInfo.isSuitableForMouseAreaFill(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuickControls_Control_is_suitable_for_MouseArea_fill) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Controls", "Control"); + + bool isType = metaInfo.isSuitableForMouseAreaFill(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuickTemplates_Control_is_suitable_for_MouseArea_fill) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick.Templates", "Control"); + + bool isType = metaInfo.isSuitableForMouseAreaFill(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_suitable_for_MouseArea_fill) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isQtQuickTransition(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_url) +{ + auto metaInfo = createMetaInfo("QML", "url"); + + bool isType = metaInfo.isUrl(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_url) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isUrl(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_variant) +{ + auto metaInfo = createMetaInfo("QML", "var"); + + bool isType = metaInfo.isVariant(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_variant) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isVariant(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_vector2d) +{ + auto metaInfo = createMetaInfo("QtQuick", "vector2d"); + + bool isType = metaInfo.isVector2D(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_vector2d) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isVector2D(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_vector3d) +{ + auto metaInfo = createMetaInfo("QtQuick", "vector3d"); + + bool isType = metaInfo.isVector3D(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_vector3d) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isVector3D(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_vector4d) +{ + auto metaInfo = createMetaInfo("QtQuick", "vector4d"); + + bool isType = metaInfo.isVector4D(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_vector4d) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isVector4D(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, QtQuick_ListView_is_view) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "ListView"); + + bool isType = metaInfo.isView(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuick_GridView_is_view) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "GridView"); + + bool isType = metaInfo.isView(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, QtQuick_PathView_is_view) +{ + auto metaInfo = createDerivedDummyMetaInfo("QtQuick", "PathView"); + + bool isType = metaInfo.isView(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_view) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isView(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, is_enumeration) +{ + auto metaInfo = createMetaInfo("QML", "Foo", QmlDesigner::Storage::TypeTraits::IsEnum); + + bool isType = metaInfo.isEnumeration(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, is_not_enumeration) +{ + auto metaInfo = createMetaInfo("QML", "Foo", {}); + + bool isType = metaInfo.isEnumeration(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, default_is_not_enumeration) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isEnumeration(); + + ASSERT_THAT(isType, IsFalse()); +} + +TEST_F(NodeMetaInfo, all_external_type_names) +{ + QmlDesigner::Storage::Info::ExportedTypeNames names{{qmlModuleId, "Object", 2, -1}, + {qmlModuleId, "Obj", 2, 1}}; + auto metaInfo = createMetaInfo("QML", "Foo"); + ON_CALL(projectStorageMock, exportedTypeNames(metaInfo.id())).WillByDefault(Return(names)); + + auto exportedTypeNames = metaInfo.allExportedTypeNames(); + + ASSERT_THAT(exportedTypeNames, + UnorderedElementsAre(IsInfoExportTypeNames(qmlModuleId, "Object", 2, -1), + IsInfoExportTypeNames(qmlModuleId, "Obj", 2, 1))); +} + +TEST_F(NodeMetaInfo, default_has_no_external_type_names) +{ + QmlDesigner::Storage::Info::ExportedTypeNames names{{qmlModuleId, "Object", 2, -1}, + {qmlModuleId, "Obj", 2, 1}}; + QmlDesigner::NodeMetaInfo metaInfo; + ON_CALL(projectStorageMock, exportedTypeNames(_)).WillByDefault(Return(names)); + + auto exportedTypeNames = metaInfo.allExportedTypeNames(); + + ASSERT_THAT(exportedTypeNames, IsEmpty()); +} + +TEST_F(NodeMetaInfo, external_type_names_for_source_id) +{ + QmlDesigner::Storage::Info::ExportedTypeNames names{{qmlModuleId, "Object", 2, -1}, + {qmlModuleId, "Obj", 2, 1}}; + auto metaInfo = createMetaInfo("QML", "Foo"); + ON_CALL(projectStorageMock, exportedTypeNames(metaInfo.id(), model.fileUrlSourceId())) + .WillByDefault(Return(names)); + + auto exportedTypeNames = metaInfo.exportedTypeNamesForSourceId(model.fileUrlSourceId()); + + ASSERT_THAT(exportedTypeNames, + UnorderedElementsAre(IsInfoExportTypeNames(qmlModuleId, "Object", 2, -1), + IsInfoExportTypeNames(qmlModuleId, "Obj", 2, 1))); +} + +TEST_F(NodeMetaInfo, default_has_no_external_type_names_for_source_id) +{ + QmlDesigner::Storage::Info::ExportedTypeNames names{{qmlModuleId, "Object", 2, -1}, + {qmlModuleId, "Obj", 2, 1}}; + QmlDesigner::NodeMetaInfo metaInfo; + ON_CALL(projectStorageMock, exportedTypeNames(metaInfo.id(), model.fileUrlSourceId())) + .WillByDefault(Return(names)); + + auto exportedTypeNames = metaInfo.exportedTypeNamesForSourceId(model.fileUrlSourceId()); + + ASSERT_THAT(exportedTypeNames, IsEmpty()); +} + +TEST_F(NodeMetaInfo, invalid_source_id_has_no_external_type_names_for_source_id) +{ + QmlDesigner::Storage::Info::ExportedTypeNames names{{qmlModuleId, "Object", 2, -1}, + {qmlModuleId, "Obj", 2, 1}}; + auto metaInfo = createMetaInfo("QML", "Foo"); + ON_CALL(projectStorageMock, exportedTypeNames(metaInfo.id(), model.fileUrlSourceId())) + .WillByDefault(Return(names)); + QmlDesigner::SourceId sourceId; + + auto exportedTypeNames = metaInfo.exportedTypeNamesForSourceId(sourceId); + + ASSERT_THAT(exportedTypeNames, IsEmpty()); +} + +TEST_F(NodeMetaInfo, float_is_a_number) +{ + auto metaInfo = createMetaInfo("QML-cppnative", "float"); + + bool isType = metaInfo.isNumber(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, double_is_a_number) +{ + auto metaInfo = createMetaInfo(qmlModuleId, "double"); + + bool isType = metaInfo.isNumber(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, int_is_a_number) +{ + auto metaInfo = createMetaInfo(qmlModuleId, "int"); + + bool isType = metaInfo.isNumber(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, uint_is_a_number) +{ + auto metaInfo = createMetaInfo("QML-cppnative", "uint"); + + bool isType = metaInfo.isNumber(); + + ASSERT_THAT(isType, IsTrue()); +} + +TEST_F(NodeMetaInfo, default_is_not_number) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + bool isType = metaInfo.isFloat(); + + ASSERT_THAT(isType, IsFalse()); +} } // namespace diff --git a/tests/unit/tests/unittests/model/CMakeLists.txt b/tests/unit/tests/unittests/model/CMakeLists.txt index 187ed9f99a0..75c81cca6f5 100644 --- a/tests/unit/tests/unittests/model/CMakeLists.txt +++ b/tests/unit/tests/unittests/model/CMakeLists.txt @@ -4,6 +4,8 @@ extend_qtc_test(unittest import-test.cpp model-test.cpp modelnode-test.cpp - nodelistproperty-test.cpp modelresourcemanagement-test.cpp + modelutils-test.cpp + nodelistproperty-test.cpp + ) diff --git a/tests/unit/tests/unittests/model/model-test.cpp b/tests/unit/tests/unittests/model/model-test.cpp index 75e865aa1de..83a8d936c6d 100644 --- a/tests/unit/tests/unittests/model/model-test.cpp +++ b/tests/unit/tests/unittests/model/model-test.cpp @@ -3,15 +3,18 @@ #include "../utils/googletest.h" -#include "../mocks/mocklistmodeleditorview.h" -#include "../mocks/modelresourcemanagementmock.h" -#include "../mocks/projectstoragemock.h" +#include +#include +#include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -22,12 +25,6 @@ using QmlDesigner::ModelNode; using QmlDesigner::ModelNodes; using QmlDesigner::ModelResourceSet; -template -auto HasPropertyName(const Matcher &matcher) -{ - return Property(&AbstractProperty::name, matcher); -} - MATCHER(IsSorted, std::string(negation ? "isn't sorted" : "is sorted")) { using std::begin; @@ -40,14 +37,16 @@ class Model : public ::testing::Test protected: Model() { + model.setFileUrl(QUrl::fromLocalFile(pathCacheMock.path.toQString())); model.attachView(&viewMock); rootNode = viewMock.rootModelNode(); ON_CALL(resourceManagementMock, removeNodes(_, _)).WillByDefault([](auto nodes, auto) { return ModelResourceSet{std::move(nodes), {}, {}}; }); - ON_CALL(resourceManagementMock, removeProperties(_, _)).WillByDefault([](auto properties, auto) { - return ModelResourceSet{{}, std::move(properties), {}}; - }); + ON_CALL(resourceManagementMock, removeProperties(_, _)) + .WillByDefault([](auto properties, auto) { + return ModelResourceSet{{}, std::move(properties), {}}; + }); } ~Model() { model.detachView(&viewMock); } @@ -79,15 +78,23 @@ protected: protected: NiceMock viewMock; - NiceMock projectStorageMock; + NiceMock pathCacheMock{"/path/foo.qml"}; + NiceMock projectStorageMock{pathCacheMock.sourceId}; NiceMock resourceManagementMock; - QmlDesigner::Model model{projectStorageMock, - "QtQuick.Item", + QmlDesigner::Model model{{projectStorageMock, pathCacheMock}, + "Item", -1, -1, nullptr, std::make_unique( resourceManagementMock)}; + QmlDesigner::SourceId filePathId = pathCacheMock.sourceId; + QmlDesigner::TypeId itemTypeId = projectStorageMock.typeId(projectStorageMock.moduleId( + "QtQuick"), + "Item", + QmlDesigner::Storage::Version{}); + QmlDesigner::ImportedTypeNameId itemTypeNameId = projectStorageMock.createImportedTypeNameId( + filePathId, "Item", itemTypeId); ModelNode rootNode; }; @@ -473,7 +480,7 @@ TEST_F(Model, TEST_F(Model, by_default_remove_model_node_removes_node) { model.detachView(&viewMock); - QmlDesigner::Model newModel{projectStorageMock, "QtQuick.Item"}; + QmlDesigner::Model newModel{{projectStorageMock, pathCacheMock}, "QtQuick.Item"}; newModel.attachView(&viewMock); auto node = createNodeWithParent(viewMock.rootModelNode()); @@ -485,7 +492,7 @@ TEST_F(Model, by_default_remove_model_node_removes_node) TEST_F(Model, by_default_remove_properties_removes_property) { model.detachView(&viewMock); - QmlDesigner::Model newModel{projectStorageMock, "QtQuick.Item"}; + QmlDesigner::Model newModel{{projectStorageMock, pathCacheMock}, "QtQuick.Item"}; newModel.attachView(&viewMock); rootNode = viewMock.rootModelNode(); auto property = createProperty(rootNode, "yi"); @@ -498,7 +505,7 @@ TEST_F(Model, by_default_remove_properties_removes_property) TEST_F(Model, by_default_remove_model_node_in_factory_method_calls_removes_node) { model.detachView(&viewMock); - auto newModel = QmlDesigner::Model::create(projectStorageMock, "QtQuick.Item"); + auto newModel = QmlDesigner::Model::create({projectStorageMock, pathCacheMock}, "QtQuick.Item"); newModel->attachView(&viewMock); auto node = createNodeWithParent(viewMock.rootModelNode()); @@ -510,7 +517,7 @@ TEST_F(Model, by_default_remove_model_node_in_factory_method_calls_removes_node) TEST_F(Model, by_default_remove_properties_in_factory_method_calls_remove_property) { model.detachView(&viewMock); - auto newModel = QmlDesigner::Model::create(projectStorageMock, "QtQuick.Item"); + auto newModel = QmlDesigner::Model::create({projectStorageMock, pathCacheMock}, "QtQuick.Item"); newModel->attachView(&viewMock); rootNode = viewMock.rootModelNode(); auto property = createProperty(rootNode, "yi"); @@ -602,7 +609,7 @@ TEST_F(Model, remove_model_nodes_bypasses_model_resource_management) TEST_F(Model, by_default_remove_model_nodes_in_factory_method_calls_removes_node) { model.detachView(&viewMock); - QmlDesigner::Model newModel{projectStorageMock, "QtQuick.Item"}; + QmlDesigner::Model newModel{{projectStorageMock, pathCacheMock}, "QtQuick.Item"}; newModel.attachView(&viewMock); rootNode = viewMock.rootModelNode(); auto node = createNodeWithParent(rootNode, "yi"); @@ -695,7 +702,7 @@ TEST_F(Model, remove_properties_bypasses_model_resource_management) TEST_F(Model, by_default_remove_properties_in_factory_method_calls_removes_properties) { model.detachView(&viewMock); - QmlDesigner::Model newModel{projectStorageMock, "QtQuick.Item"}; + QmlDesigner::Model newModel{{projectStorageMock, pathCacheMock}, "QtQuick.Item"}; newModel.attachView(&viewMock); rootNode = viewMock.rootModelNode(); auto property = createProperty(rootNode, "yi"); @@ -706,4 +713,160 @@ TEST_F(Model, by_default_remove_properties_in_factory_method_calls_removes_prope newModel.removeProperties({property, property2}); } +TEST_F(Model, change_imports_is_synchronizing_imports_with_project_storage) +{ + QmlDesigner::SourceId directoryPathId = QmlDesigner::SourceId::create(2); + ON_CALL(pathCacheMock, sourceId(Eq("/path/foo/."))).WillByDefault(Return(directoryPathId)); + auto qtQuickModuleId = projectStorageMock.moduleId("QtQuick"); + auto qtQmlModelsModuleId = projectStorageMock.moduleId("QtQml.Models"); + auto qtQuickImport = QmlDesigner::Import::createLibraryImport("QtQuick", "2.1"); + auto qtQmlModelsImport = QmlDesigner::Import::createLibraryImport("QtQml.Models"); + auto directoryImport = QmlDesigner::Import::createFileImport("foo"); + + EXPECT_CALL(projectStorageMock, + synchronizeDocumentImports( + UnorderedElementsAre(IsImport(qtQuickModuleId, filePathId, 2, 1), + IsImport(qtQmlModelsModuleId, filePathId, -1, -1)), + filePathId)); + + model.changeImports({qtQuickImport, qtQmlModelsImport}, {}); +} + +TEST_F(Model, + change_imports_is_not_synchronizing_imports_with_project_storage_if_no_new_imports_are_added) +{ + QmlDesigner::SourceId directoryPathId = QmlDesigner::SourceId::create(2); + ON_CALL(pathCacheMock, sourceId(Eq("/path/foo/."))).WillByDefault(Return(directoryPathId)); + auto qtQuickImport = QmlDesigner::Import::createLibraryImport("QtQuick", "2.1"); + auto qtQmlModelsImport = QmlDesigner::Import::createLibraryImport("QtQml.Models"); + auto directoryImport = QmlDesigner::Import::createFileImport("foo"); + model.changeImports({qtQuickImport, qtQmlModelsImport}, {}); + + EXPECT_CALL(projectStorageMock, synchronizeDocumentImports(_, _)).Times(0); + + model.changeImports({qtQuickImport, qtQmlModelsImport}, {}); +} + +TEST_F(Model, change_imports_is_adding_import_in_project_storage) +{ + QmlDesigner::SourceId directoryPathId = QmlDesigner::SourceId::create(2); + ON_CALL(pathCacheMock, sourceId(Eq("/path/foo/."))).WillByDefault(Return(directoryPathId)); + auto qtQuickModuleId = projectStorageMock.moduleId("QtQuick"); + auto qtQmlModelsModuleId = projectStorageMock.moduleId("QtQml.Models"); + auto qtQuickImport = QmlDesigner::Import::createLibraryImport("QtQuick", "2.1"); + auto qtQmlModelsImport = QmlDesigner::Import::createLibraryImport("QtQml.Models"); + auto directoryImport = QmlDesigner::Import::createFileImport("foo"); + model.changeImports({qtQmlModelsImport}, {}); + + EXPECT_CALL(projectStorageMock, + synchronizeDocumentImports( + UnorderedElementsAre(IsImport(qtQuickModuleId, filePathId, 2, 1), + IsImport(qtQmlModelsModuleId, filePathId, -1, -1)), + filePathId)); + + model.changeImports({qtQuickImport}, {}); +} + +TEST_F(Model, change_imports_is_removing_import_in_project_storage) +{ + QmlDesigner::SourceId directoryPathId = QmlDesigner::SourceId::create(2); + ON_CALL(pathCacheMock, sourceId(Eq("/path/foo/."))).WillByDefault(Return(directoryPathId)); + auto qtQmlModelsModuleId = projectStorageMock.moduleId("QtQml.Models"); + auto qtQuickImport = QmlDesigner::Import::createLibraryImport("QtQuick", "2.1"); + auto qtQmlModelsImport = QmlDesigner::Import::createLibraryImport("QtQml.Models"); + auto directoryImport = QmlDesigner::Import::createFileImport("foo"); + model.changeImports({qtQuickImport, qtQmlModelsImport}, {}); + + EXPECT_CALL(projectStorageMock, + synchronizeDocumentImports(UnorderedElementsAre( + IsImport(qtQmlModelsModuleId, filePathId, -1, -1)), + filePathId)); + + model.changeImports({}, {qtQuickImport}); +} + +TEST_F(Model, + change_imports_is_not_removing_import_in_project_storage_if_import_is_not_in_model_imports) +{ + QmlDesigner::SourceId directoryPathId = QmlDesigner::SourceId::create(2); + ON_CALL(pathCacheMock, sourceId(Eq("/path/foo/."))).WillByDefault(Return(directoryPathId)); + auto qtQuickImport = QmlDesigner::Import::createLibraryImport("QtQuick", "2.1"); + auto qtQmlModelsImport = QmlDesigner::Import::createLibraryImport("QtQml.Models"); + auto directoryImport = QmlDesigner::Import::createFileImport("foo"); + model.changeImports({qtQuickImport}, {}); + + EXPECT_CALL(projectStorageMock, synchronizeDocumentImports(_, _)).Times(0); + + model.changeImports({}, {qtQmlModelsImport}); +} + +TEST_F(Model, change_imports_is_changing_import_version_with_project_storage) +{ + QmlDesigner::SourceId directoryPathId = QmlDesigner::SourceId::create(2); + ON_CALL(pathCacheMock, sourceId(Eq("/path/foo/."))).WillByDefault(Return(directoryPathId)); + auto qtQuickModuleId = projectStorageMock.moduleId("QtQuick"); + auto qtQmlModelsModuleId = projectStorageMock.moduleId("QtQml.Models"); + auto qtQuickImport = QmlDesigner::Import::createLibraryImport("QtQuick", "2.1"); + auto qtQmlModelsImport = QmlDesigner::Import::createLibraryImport("QtQml.Models"); + auto directoryImport = QmlDesigner::Import::createFileImport("foo"); + model.changeImports({qtQuickImport, qtQmlModelsImport}, {}); + qtQuickImport = QmlDesigner::Import::createLibraryImport("QtQuick", "3.1"); + + EXPECT_CALL(projectStorageMock, + synchronizeDocumentImports( + UnorderedElementsAre(IsImport(qtQuickModuleId, filePathId, 3, 1), + IsImport(qtQmlModelsModuleId, filePathId, -1, -1)), + filePathId)); + + model.changeImports({qtQuickImport}, {}); +} + +TEST_F(Model, create_model_node_has_meta_info) +{ + auto node = model.createModelNode("Item"); + + ASSERT_THAT(node.metaInfo(), model.qtQuickItemMetaInfo()); +} + +TEST_F(Model, create_qualified_model_node_has_meta_info) +{ + auto qtQmlModelsImport = QmlDesigner::Import::createLibraryImport("QtQml.Models", "", "Foo"); + auto qtQmlModelsModulesId = projectStorageMock.moduleId("QtQml.Models"); + auto importId = projectStorageMock.createImportId(qtQmlModelsModulesId, filePathId); + auto listModelTypeId = projectStorageMock.typeId(qtQmlModelsModulesId, + "ListModel", + QmlDesigner::Storage::Version{}); + projectStorageMock.createImportedTypeNameId(importId, "ListModel", listModelTypeId); + model.changeImports({qtQmlModelsImport}, {}); + + auto node = model.createModelNode("Foo.ListModel"); + + ASSERT_THAT(node.metaInfo(), model.qtQmlModelsListModelMetaInfo()); +} + +TEST_F(Model, change_root_node_type_changes_meta_info) +{ + projectStorageMock.createImportedTypeNameId(filePathId, + "QtObject", + model.qmlQtObjectMetaInfo().id()); + + model.changeRootNodeType("QtObject"); + + ASSERT_THAT(rootNode.metaInfo(), model.qmlQtObjectMetaInfo()); +} + +TEST_F(Model, meta_info) +{ + auto meta_info = model.metaInfo("QtObject"); + + ASSERT_THAT(meta_info, model.qmlQtObjectMetaInfo()); +} + +TEST_F(Model, meta_info_of_not_existing_type_is_invalid) +{ + auto meta_info = model.metaInfo("Foo"); + + ASSERT_THAT(meta_info, IsFalse()); +} + } // namespace diff --git a/tests/unit/tests/unittests/model/modelnode-test.cpp b/tests/unit/tests/unittests/model/modelnode-test.cpp index 0d9cf1b1842..a93a56952f7 100644 --- a/tests/unit/tests/unittests/model/modelnode-test.cpp +++ b/tests/unit/tests/unittests/model/modelnode-test.cpp @@ -4,6 +4,7 @@ #include "../utils/googletest.h" #include "../mocks/projectstoragemock.h" +#include "../mocks/sourcepathcachemock.h" #include #include @@ -14,8 +15,9 @@ namespace { class ModelNode : public testing::Test { protected: - NiceMock projectStorageMock; - QmlDesigner::Model model{projectStorageMock, "QtQuick.Item"}; + NiceMock pathCache{"/path/foo.qml"}; + NiceMock projectStorageMock{pathCache.sourceId}; + QmlDesigner::Model model{{projectStorageMock, pathCache}, "Item"}; QmlDesigner::ModelNode rootNode = model.rootModelNode(); }; diff --git a/tests/unit/tests/unittests/model/modelresourcemanagement-test.cpp b/tests/unit/tests/unittests/model/modelresourcemanagement-test.cpp index d0dc04db402..38f4a5591bb 100644 --- a/tests/unit/tests/unittests/model/modelresourcemanagement-test.cpp +++ b/tests/unit/tests/unittests/model/modelresourcemanagement-test.cpp @@ -6,6 +6,7 @@ #include "../mocks/mocklistmodeleditorview.h" #include "../mocks/modelresourcemanagementmock.h" #include "../mocks/projectstoragemock.h" +#include "../mocks/sourcepathcachemock.h" #include #include @@ -70,9 +71,13 @@ protected: protected: NiceMock viewMock; - NiceMock projectStorageMock; + NiceMock pathCacheMock{"/path/foo.qml"}; + NiceMock projectStorageMock{pathCacheMock.sourceId}; QmlDesigner::ModelResourceManagement management; - QmlDesigner::Model model{projectStorageMock, "QtQuick.Item"}; + QmlDesigner::Model model{{projectStorageMock, pathCacheMock}, + "Item", + {QmlDesigner::Import::createLibraryImport("QtQtuick")}, + QUrl::fromLocalFile(pathCacheMock.path.toQString())}; ModelNode rootNode; }; diff --git a/tests/unit/tests/unittests/model/modelutils-test.cpp b/tests/unit/tests/unittests/model/modelutils-test.cpp new file mode 100644 index 00000000000..4418d36ac83 --- /dev/null +++ b/tests/unit/tests/unittests/model/modelutils-test.cpp @@ -0,0 +1,109 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include +#include + +#include +#include + +namespace { + +class ModelUtils : public ::testing::Test +{ +protected: + NiceMock pathCacheMock{"/path/model.qml"}; + QmlDesigner::SourceId sourceId = pathCacheMock.createSourceId("/path/foo.qml"); + NiceMock projectStorageMock{pathCacheMock.sourceId}; + QmlDesigner::ModuleId moduleId = projectStorageMock.moduleId("QtQuick"); + QmlDesigner::Model model{{projectStorageMock, pathCacheMock}, + "Item", + {QmlDesigner::Import::createLibraryImport("QML"), + QmlDesigner::Import::createLibraryImport("QtQuick"), + QmlDesigner::Import::createLibraryImport("QtQml.Models")}, + QUrl::fromLocalFile(pathCacheMock.path.toQString())}; +}; + +TEST_F(ModelUtils, component_file_path) +{ + auto typeId = projectStorageMock.createType(moduleId, + "Foo", + QmlDesigner::Storage::TypeTraits::IsFileComponent, + {}, + sourceId); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto path = QmlDesigner::ModelUtils::componentFilePath(pathCacheMock, metaInfo); + + ASSERT_THAT(path, "/path/foo.qml"); +} + +TEST_F(ModelUtils, empty_component_file_path_for_non_file_component) +{ + auto typeId = projectStorageMock.createType(moduleId, "Foo", {}, {}, sourceId); + QmlDesigner::NodeMetaInfo metaInfo{typeId, &projectStorageMock}; + + auto path = QmlDesigner::ModelUtils::componentFilePath(pathCacheMock, metaInfo); + + ASSERT_THAT(path, IsEmpty()); +} + +TEST_F(ModelUtils, empty_component_file_path_for_invalid_meta_info) +{ + QmlDesigner::NodeMetaInfo metaInfo; + + auto path = QmlDesigner::ModelUtils::componentFilePath(pathCacheMock, metaInfo); + + ASSERT_THAT(path, IsEmpty()); +} + +TEST_F(ModelUtils, component_file_path_for_node) +{ + auto typeId = projectStorageMock.createType(moduleId, + "Foo", + QmlDesigner::Storage::TypeTraits::IsFileComponent, + {}, + sourceId); + projectStorageMock.createImportedTypeNameId(pathCacheMock.sourceId, "Foo", typeId); + auto node = model.createModelNode("Foo"); + + auto path = QmlDesigner::ModelUtils::componentFilePath(node); + + ASSERT_THAT(path, "/path/foo.qml"); +} + +TEST_F(ModelUtils, component_file_path_for_invalid_node_is_empty) +{ + auto path = QmlDesigner::ModelUtils::componentFilePath(QmlDesigner::ModelNode{}); + + ASSERT_THAT(path, IsEmpty()); +} + +TEST_F(ModelUtils, component_file_path_for_node_without_metainfo_is_empty) +{ + projectStorageMock.createType(moduleId, + "Foo", + QmlDesigner::Storage::TypeTraits::IsFileComponent, + {}, + sourceId); + auto node = model.createModelNode("Foo"); + + auto path = QmlDesigner::ModelUtils::componentFilePath(node); + + ASSERT_THAT(path, IsEmpty()); +} + +TEST_F(ModelUtils, component_file_path_for_non_file_component_node_is_empty) +{ + auto typeId = projectStorageMock.createType(moduleId, "Foo", {}, {}, sourceId); + projectStorageMock.createImportedTypeNameId(pathCacheMock.sourceId, "Foo", typeId); + auto node = model.createModelNode("Foo"); + + auto path = QmlDesigner::ModelUtils::componentFilePath(node); + + ASSERT_THAT(path, IsEmpty()); +} + +} // namespace diff --git a/tests/unit/tests/unittests/model/nodelistproperty-test.cpp b/tests/unit/tests/unittests/model/nodelistproperty-test.cpp index 2fa80aa9f3a..6783bde3e75 100644 --- a/tests/unit/tests/unittests/model/nodelistproperty-test.cpp +++ b/tests/unit/tests/unittests/model/nodelistproperty-test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -61,7 +62,7 @@ protected: ON_CALL(projectStorageMock, typeId(Eq(moduleId), Eq(typeName), _)).WillByDefault(Return(typeId)); ON_CALL(projectStorageMock, type(Eq(typeId))) - .WillByDefault(Return(Info::Type{defaultPropertyId, {}})); + .WillByDefault(Return(Info::Type{defaultPropertyId, QmlDesigner::SourceId{}, {}})); ON_CALL(projectStorageMock, propertyName(Eq(defaultPropertyId))) .WillByDefault(Return(defaultPeopertyName)); } @@ -77,9 +78,14 @@ protected: } protected: - NiceMock projectStorageMock; - std::unique_ptr model{ - std::make_unique(projectStorageMock, "QtQuick.Item")}; + NiceMock pathCache{"/path/foo.qml"}; + NiceMock projectStorageMock{pathCache.sourceId}; + QmlDesigner::ModelPointer model{ + QmlDesigner::Model::create(QmlDesigner::ProjectStorageDependencies{projectStorageMock, + pathCache}, + "Item", + {QmlDesigner::Import::createLibraryImport("QtQuick")}, + QUrl::fromLocalFile(pathCache.path.toQString()))}; NiceMock abstractViewMock; QmlDesigner::NodeListProperty nodeListProperty; ModelNode node1; diff --git a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp index 19a60594373..fbf07ed9a54 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp @@ -3,6 +3,8 @@ #include "../utils/googletest.h" +#include + #include #include #include @@ -29,10 +31,10 @@ using QmlDesigner::Storage::Synchronization::SynchronizationPackage; namespace Storage = QmlDesigner::Storage; -Storage::Synchronization::Imports operator+(const Storage::Synchronization::Imports &first, - const Storage::Synchronization::Imports &second) +Storage::Imports operator+(const Storage::Imports &first, + const Storage::Imports &second) { - Storage::Synchronization::Imports imports; + Storage::Imports imports; imports.reserve(first.size() + second.size()); imports.insert(imports.end(), first.begin(), first.end()); @@ -242,15 +244,17 @@ MATCHER(StringsAreSorted, std::string(negation ? "isn't sorted" : "is sorted")) }); } -MATCHER_P2(IsInfoType, +MATCHER_P3(IsInfoType, defaultPropertyId, + sourceId, traits, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::Info::Type{defaultPropertyId, traits})) + + PrintToString(Storage::Info::Type{defaultPropertyId, sourceId, traits})) { const Storage::Info::Type &type = arg; - return type.defaultPropertyId == defaultPropertyId && type.traits == traits; + return type.defaultPropertyId == defaultPropertyId && type.sourceId == sourceId + && type.traits == traits; } class ProjectStorage : public testing::Test @@ -1039,16 +1043,16 @@ protected: ModuleId qtQuick3DModuleId{storage.moduleId("QtQuick3D")}; ModuleId myModuleModuleId{storage.moduleId("MyModule")}; ModuleId QMLModuleId{storage.moduleId("QML")}; - Storage::Synchronization::Imports importsSourceId1; - Storage::Synchronization::Imports importsSourceId2; - Storage::Synchronization::Imports importsSourceId3; - Storage::Synchronization::Imports importsSourceId4; - Storage::Synchronization::Imports importsSourceId5; - Storage::Synchronization::Imports moduleDependenciesSourceId1; - Storage::Synchronization::Imports moduleDependenciesSourceId2; - Storage::Synchronization::Imports moduleDependenciesSourceId3; - Storage::Synchronization::Imports moduleDependenciesSourceId4; - Storage::Synchronization::Imports moduleDependenciesSourceId5; + Storage::Imports importsSourceId1; + Storage::Imports importsSourceId2; + Storage::Imports importsSourceId3; + Storage::Imports importsSourceId4; + Storage::Imports importsSourceId5; + Storage::Imports moduleDependenciesSourceId1; + Storage::Imports moduleDependenciesSourceId2; + Storage::Imports moduleDependenciesSourceId3; + Storage::Imports moduleDependenciesSourceId4; + Storage::Imports moduleDependenciesSourceId5; }; TEST_F(ProjectStorage, fetch_source_context_id_returns_always_the_same_id_for_the_same_path) @@ -1438,7 +1442,7 @@ TEST_F(ProjectStorage, synchronize_types_overwrites_sources) storage.synchronize(package); package.types[0].sourceId = sourceId3; package.types[1].sourceId = sourceId4; - Storage::Synchronization::Imports newImports; + Storage::Imports newImports; newImports.emplace_back(qmlModuleId, Storage::Version{}, sourceId3); newImports.emplace_back(qmlNativeModuleId, Storage::Version{}, sourceId3); newImports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); @@ -1558,10 +1562,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_prototype) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; package.types.push_back(Storage::Synchronization::Type{ "QQuickObject", Storage::Synchronization::ImportedType{"QObject"}, @@ -1604,10 +1605,7 @@ TEST_F(ProjectStorage, synchronize_types_add_qualified_extension) auto package{createSimpleSynchronizationPackage()}; swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; package.types.push_back(Storage::Synchronization::Type{ "QQuickObject", Storage::Synchronization::ImportedType{}, @@ -1826,10 +1824,7 @@ TEST_F(ProjectStorage, synchronize_types_add_property_declaration_qualified_type { auto package{createSimpleSynchronizationPackage()}; package.types[0].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; package.types.push_back( Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{"QObject"}, @@ -2016,10 +2011,7 @@ TEST_F(ProjectStorage, using_non_existing_qualified_exported_property_type_with_ { auto package{createSimpleSynchronizationPackage()}; package.types[0].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "QObject2", - Storage::Synchronization::Import{qmlNativeModuleId, - Storage::Version{}, - sourceId1}}; + "QObject2", Storage::Import{qmlNativeModuleId, Storage::Version{}, sourceId1}}; package.types.pop_back(); package.imports = importsSourceId1; @@ -2030,8 +2022,7 @@ TEST_F(ProjectStorage, using_non_existing_qualified_exported_property_type_with_ { auto package{createSimpleSynchronizationPackage()}; package.types[0].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "QObject", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{}, sourceId1}}; + "QObject", Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}}; package.types.pop_back(); package.imports = importsSourceId1; @@ -3281,8 +3272,7 @@ TEST_F(ProjectStorage, do_not_relink_alias_property_for_qualified_imported_type_ { auto package{createSynchronizationPackageWithAliases()}; package.types[1].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "Object2", - Storage::Synchronization::Import{pathToModuleId, Storage::Version{}, sourceId2}}; + "Object2", Storage::Import{pathToModuleId, Storage::Version{}, sourceId2}}; package.imports.emplace_back(pathToModuleId, Storage::Version{}, sourceId2); storage.synchronize(package); package.types[3].exportedTypes[0].moduleId = qtQuickModuleId; @@ -3298,8 +3288,7 @@ TEST_F(ProjectStorage, { auto package{createSynchronizationPackageWithAliases()}; package.types[1].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "Object2", - Storage::Synchronization::Import{pathToModuleId, Storage::Version{}, sourceId2}}; + "Object2", Storage::Import{pathToModuleId, Storage::Version{}, sourceId2}}; package.imports.emplace_back(pathToModuleId, Storage::Version{}, sourceId2); package.types.push_back(Storage::Synchronization::Type{ "QObject2", @@ -3522,8 +3511,7 @@ TEST_F(ProjectStorage, change_qualified_prototype_type_module_id_throws) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}}; storage.synchronize(package); package.types[1].exportedTypes[0].moduleId = qtQuickModuleId; @@ -3537,8 +3525,7 @@ TEST_F(ProjectStorage, change_qualified_extension_type_module_id_throws) auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}}; storage.synchronize(package); package.types[1].exportedTypes[0].moduleId = qtQuickModuleId; @@ -3551,15 +3538,11 @@ TEST_F(ProjectStorage, change_qualified_prototype_type_module_id) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}}; storage.synchronize(package); package.types[1].exportedTypes[0].moduleId = qtQuickModuleId; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; storage.synchronize(SynchronizationPackage{importsSourceId1 + importsSourceId2, {package.types[0], package.types[1]}, @@ -3578,15 +3561,11 @@ TEST_F(ProjectStorage, change_qualified_extension_type_module_id) auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}}; storage.synchronize(package); package.types[1].exportedTypes[0].moduleId = qtQuickModuleId; package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; storage.synchronize(SynchronizationPackage{importsSourceId1 + importsSourceId2, {package.types[0], package.types[1]}, @@ -3926,8 +3905,7 @@ TEST_F(ProjectStorage, qualified_prototype) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}}; package.types.push_back( Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, @@ -3954,8 +3932,7 @@ TEST_F(ProjectStorage, qualified_extension) auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}}; package.types.push_back( Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, @@ -3981,10 +3958,7 @@ TEST_F(ProjectStorage, qualified_prototype_upper_down_the_module_chain_throws) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); } @@ -3994,10 +3968,7 @@ TEST_F(ProjectStorage, qualified_extension_upper_down_the_module_chain_throws) auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); } @@ -4006,10 +3977,7 @@ TEST_F(ProjectStorage, qualified_prototype_upper_in_the_module_chain) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; package.types.push_back( Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, @@ -4036,10 +4004,7 @@ TEST_F(ProjectStorage, qualified_extension_upper_in_the_module_chain) auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; package.types.push_back( Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, @@ -4065,8 +4030,7 @@ TEST_F(ProjectStorage, qualified_prototype_with_wrong_version_throws) { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{4}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{4}, sourceId1}}; package.types.push_back( Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, @@ -4086,8 +4050,7 @@ TEST_F(ProjectStorage, qualified_extension_with_wrong_version_throws) auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{4}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{4}, sourceId1}}; package.types.push_back( Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, @@ -4220,10 +4183,7 @@ TEST_F(ProjectStorage, qualified_prototype_with_version_down_the_proto_type_chai { auto package{createSimpleSynchronizationPackage()}; package.types[0].prototype = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{2}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{2}, sourceId1}}; ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); } @@ -4233,10 +4193,7 @@ TEST_F(ProjectStorage, qualified_extension_with_version_down_the_proto_type_chai auto package{createSimpleSynchronizationPackage()}; std::swap(package.types.front().extension, package.types.front().prototype); package.types[0].extension = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{2}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{2}, sourceId1}}; ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); } @@ -4245,8 +4202,7 @@ TEST_F(ProjectStorage, qualified_property_declaration_type_name) { auto package{createSimpleSynchronizationPackage()}; package.types[0].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}}; package.types.push_back( Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, @@ -4272,10 +4228,7 @@ TEST_F(ProjectStorage, qualified_property_declaration_type_name_down_the_module_ { auto package{createSimpleSynchronizationPackage()}; package.types[0].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; ASSERT_THROW(storage.synchronize(package), QmlDesigner::TypeNameDoesNotExists); } @@ -4284,10 +4237,7 @@ TEST_F(ProjectStorage, qualified_property_declaration_type_name_in_the_module_ch { auto package{createSimpleSynchronizationPackage()}; package.types[0].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; package.types.push_back( Storage::Synchronization::Type{"QQuickObject", Storage::Synchronization::ImportedType{}, @@ -4313,8 +4263,7 @@ TEST_F(ProjectStorage, qualified_property_declaration_type_name_with_version) { auto package{createSimpleSynchronizationPackage()}; package.types[0].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{2}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{2}, sourceId1}}; package.imports.emplace_back(qmlModuleId, Storage::Version{2}, sourceId1); storage.synchronize(package); @@ -4331,8 +4280,7 @@ TEST_F(ProjectStorage, change_property_type_module_id_with_qualified_type_throws { auto package{createSimpleSynchronizationPackage()}; package.types[0].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}}; storage.synchronize(package); package.types[1].exportedTypes[0].moduleId = qtQuickModuleId; @@ -4345,14 +4293,10 @@ TEST_F(ProjectStorage, change_property_type_module_id_with_qualified_type) { auto package{createSimpleSynchronizationPackage()}; package.types[0].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qmlModuleId, Storage::Version{}, sourceId1}}; + "Object", Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}}; storage.synchronize(package); package.types[0].propertyDeclarations[0].typeName = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuickModuleId, - Storage::Version{}, - sourceId1}}; + "Object", Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}}; package.types[1].exportedTypes[0].moduleId = qtQuickModuleId; package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId2); @@ -4476,9 +4420,7 @@ TEST_F(ProjectStorage, fetch_by_major_version_for_imported_type) {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{1}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{1}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); @@ -4491,9 +4433,7 @@ TEST_F(ProjectStorage, fetch_by_major_version_for_qualified_imported_type) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{1}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{1}, sourceId2}; Storage::Synchronization::Type type{ "Item", Storage::Synchronization::QualifiedImportedType{"Object", import}, @@ -4524,9 +4464,7 @@ TEST_F(ProjectStorage, fetch_by_major_version_and_minor_version_for_imported_typ {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{1, 2}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{1, 2}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); @@ -4539,9 +4477,7 @@ TEST_F(ProjectStorage, fetch_by_major_version_and_minor_version_for_qualified_im { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{1, 2}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{1, 2}, sourceId2}; Storage::Synchronization::Type type{ "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, @@ -4573,9 +4509,7 @@ TEST_F(ProjectStorage, {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{1, 1}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{1, 1}, sourceId2}; ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), QmlDesigner::TypeNameDoesNotExists); @@ -4586,9 +4520,7 @@ TEST_F(ProjectStorage, { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{1, 1}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{1, 1}, sourceId2}; Storage::Synchronization::Type type{ "Item", Storage::Synchronization::QualifiedImportedType{"Object", import}, @@ -4616,9 +4548,7 @@ TEST_F(ProjectStorage, fetch_low_minor_version_for_imported_type_throws) {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{1, 1}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{1, 1}, sourceId2}; ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), QmlDesigner::TypeNameDoesNotExists); @@ -4628,9 +4558,7 @@ TEST_F(ProjectStorage, fetch_low_minor_version_for_qualified_imported_type_throw { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{1, 1}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{1, 1}, sourceId2}; Storage::Synchronization::Type type{ "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, @@ -4658,9 +4586,7 @@ TEST_F(ProjectStorage, fetch_higher_minor_version_for_imported_type) {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{1, 3}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{1, 3}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); @@ -4673,9 +4599,7 @@ TEST_F(ProjectStorage, fetch_higher_minor_version_for_qualified_imported_type) { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{1, 3}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{1, 3}, sourceId2}; Storage::Synchronization::Type type{ "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, @@ -4706,9 +4630,7 @@ TEST_F(ProjectStorage, fetch_different_major_version_for_imported_type_throws) {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{3, 1}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{3, 1}, sourceId2}; ASSERT_THROW(storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}), QmlDesigner::TypeNameDoesNotExists); @@ -4718,9 +4640,7 @@ TEST_F(ProjectStorage, fetch_different_major_version_for_qualified_imported_type { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{3, 1}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{3, 1}, sourceId2}; Storage::Synchronization::Type type{ "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, @@ -4748,9 +4668,7 @@ TEST_F(ProjectStorage, fetch_other_type_by_different_version_for_imported_type) {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{2, 3}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{2, 3}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); @@ -4763,9 +4681,7 @@ TEST_F(ProjectStorage, fetch_other_type_by_different_version_for_qualified_impor { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{2, 3}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{2, 3}, sourceId2}; Storage::Synchronization::Type type{ "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, @@ -4796,7 +4712,7 @@ TEST_F(ProjectStorage, fetch_highest_version_for_import_without_version_for_impo {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - Storage::Synchronization::Import import{qmlModuleId, Storage::Version{}, sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); @@ -4809,7 +4725,7 @@ TEST_F(ProjectStorage, fetch_highest_version_for_import_without_version_for_qual { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Import import{qmlModuleId, Storage::Version{}, sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{}, sourceId2}; Storage::Synchronization::Type type{ "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, @@ -4840,9 +4756,7 @@ TEST_F(ProjectStorage, fetch_highest_version_for_import_with_major_version_for_i {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{2}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{2}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); @@ -4855,9 +4769,7 @@ TEST_F(ProjectStorage, fetch_highest_version_for_import_with_major_version_for_q { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Import import{qmlModuleId, - Storage::Version{2}, - sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{2}, sourceId2}; Storage::Synchronization::Type type{ "Item", Storage::Synchronization::QualifiedImportedType{"Obj", import}, @@ -4888,7 +4800,7 @@ TEST_F(ProjectStorage, fetch_exported_type_without_version_first_for_imported_ty {Storage::Synchronization::ExportedType{qtQuickModuleId, "Item", Storage::Version{}}}}; - Storage::Synchronization::Import import{qmlModuleId, Storage::Version{}, sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{}, sourceId2}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId2}}); @@ -4901,7 +4813,7 @@ TEST_F(ProjectStorage, fetch_exported_type_without_version_first_for_qualified_i { auto package{createSynchronizationPackageWithVersions()}; storage.synchronize(package); - Storage::Synchronization::Import import{qmlModuleId, Storage::Version{}, sourceId2}; + Storage::Import import{qmlModuleId, Storage::Version{}, sourceId2}; Storage::Synchronization::Type type{ "Item", Storage::Synchronization::QualifiedImportedType{"BuiltInObj", import}, @@ -4934,7 +4846,7 @@ TEST_F(ProjectStorage, ensure_that_properties_for_removed_types_are_not_anymore_ Storage::Synchronization::ImportedType{ "Object"}, Storage::PropertyDeclarationTraits::IsList}}}; - Storage::Synchronization::Import import{qmlModuleId, Storage::Version{}, sourceId1}; + Storage::Import import{qmlModuleId, Storage::Version{}, sourceId1}; storage.synchronize(SynchronizationPackage{{import}, {type}, {sourceId1}}); ASSERT_NO_THROW(storage.synchronize(SynchronizationPackage{{sourceId1}})); @@ -5515,10 +5427,7 @@ TEST_F(ProjectStorage, module_exported_import_with_qualified_imported_type) { auto package{createModuleExportedImportSynchronizationPackage()}; package.types.back().prototype = Storage::Synchronization::QualifiedImportedType{ - "Object", - Storage::Synchronization::Import{qtQuick3DModuleId, - Storage::Version{1}, - sourceId4}}; + "Object", Storage::Import{qtQuick3DModuleId, Storage::Version{1}, sourceId4}}; storage.synchronize(std::move(package)); @@ -6615,7 +6524,7 @@ TEST_F(ProjectStorage, get_type) auto type = storage.type(typeId); - ASSERT_THAT(type, Optional(IsInfoType(defaultPropertyId, TypeTraits::Reference))); + ASSERT_THAT(type, Optional(IsInfoType(defaultPropertyId, sourceId1, TypeTraits::Reference))); } TEST_F(ProjectStorage, dont_get_type_for_invalid_id) @@ -6901,4 +6810,201 @@ TEST_F(ProjectStorage, is_not_based_on_if_no_base_type_is_given) ASSERT_FALSE(isBasedOn); } +TEST_F(ProjectStorage, get_imported_type_name_id_for_source_id) +{ + auto sourceId = sourcePathCache.sourceId("/path/foo.qml"); + + auto importedTypeNameId = storage.importedTypeNameId(sourceId, "Item"); + + ASSERT_TRUE(importedTypeNameId); +} + +TEST_F(ProjectStorage, + get_imported_type_name_id_for_source_id_returns_the_same_id_for_the_same_arguments) +{ + auto sourceId = sourcePathCache.sourceId("/path/foo.qml"); + auto expectedImportedTypeNameId = storage.importedTypeNameId(sourceId, "Item"); + + auto importedTypeNameId = storage.importedTypeNameId(sourceId, "Item"); + + ASSERT_THAT(importedTypeNameId, expectedImportedTypeNameId); +} + +TEST_F(ProjectStorage, + get_imported_type_name_id_for_source_id_returns_different_id_for_different_sourceId) +{ + auto sourceId = sourcePathCache.sourceId("/path/foo.qml"); + auto expectedImportedTypeNameId = storage.importedTypeNameId(sourceId, "Item"); + auto sourceId2 = sourcePathCache.sourceId("/path/foo2.qml"); + + auto importedTypeNameId = storage.importedTypeNameId(sourceId2, "Item"); + + ASSERT_THAT(importedTypeNameId, Not(expectedImportedTypeNameId)); +} + +TEST_F(ProjectStorage, get_imported_type_name_id_returns_different_id_for_different_name) +{ + auto sourceId = sourcePathCache.sourceId("/path/foo.qml"); + auto expectedImportedTypeNameId = storage.importedTypeNameId(sourceId, "Item"); + + auto importedTypeNameId = storage.importedTypeNameId(sourceId, "Item2"); + + ASSERT_THAT(importedTypeNameId, Not(expectedImportedTypeNameId)); +} + +TEST_F(ProjectStorage, get_import_id) +{ + auto package{createSimpleSynchronizationPackage()}; + storage.synchronize(package); + + auto importId = storage.importId(Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}); + + ASSERT_TRUE(importId); +} + +TEST_F(ProjectStorage, get_invalid_import_id_if_not_exists) +{ + auto importId = storage.importId(Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}); + + ASSERT_FALSE(importId); +} + +TEST_F(ProjectStorage, get_imported_type_name_id_for_import_id) +{ + auto package{createSimpleSynchronizationPackage()}; + storage.synchronize(package); + auto importId = storage.importId(Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}); + + auto importedTypeNameId = storage.importedTypeNameId(importId, "Item"); + + ASSERT_TRUE(importedTypeNameId); +} + +TEST_F(ProjectStorage, + get_imported_type_name_id_for_import_id_returns_different_id_for_different_importId) +{ + auto package{createSimpleSynchronizationPackage()}; + storage.synchronize(package); + auto importId = storage.importId(Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}); + auto expectedImportedTypeNameId = storage.importedTypeNameId(importId, "Item"); + auto importId2 = storage.importId( + Storage::Import{qtQuickModuleId, Storage::Version{}, sourceId1}); + + auto importedTypeNameId = storage.importedTypeNameId(importId2, "Item"); + + ASSERT_THAT(importedTypeNameId, Not(expectedImportedTypeNameId)); +} + +TEST_F(ProjectStorage, + get_imported_type_name_id_for_import_id_returns_different_id_for_different_name) +{ + auto package{createSimpleSynchronizationPackage()}; + storage.synchronize(package); + auto importId = storage.importId(Storage::Import{qmlModuleId, Storage::Version{}, sourceId1}); + auto expectedImportedTypeNameId = storage.importedTypeNameId(importId, "Item"); + + auto importedTypeNameId = storage.importedTypeNameId(importId, "Item2"); + + ASSERT_THAT(importedTypeNameId, Not(expectedImportedTypeNameId)); +} + +TEST_F(ProjectStorage, synchronize_document_imports) +{ + Storage::Imports imports; + imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); + imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId1); + + storage.synchronizeDocumentImports(imports, sourceId1); + + ASSERT_TRUE(storage.importId(imports.back())); +} + +TEST_F(ProjectStorage, synchronize_document_imports_removes_import) +{ + Storage::Imports imports; + imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); + imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId1); + storage.synchronizeDocumentImports(imports, sourceId1); + auto removedImport = imports.back(); + imports.pop_back(); + + storage.synchronizeDocumentImports(imports, sourceId1); + + ASSERT_FALSE(storage.importId(removedImport)); +} + +TEST_F(ProjectStorage, synchronize_document_imports_adds_import) +{ + Storage::Imports imports; + imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); + storage.synchronizeDocumentImports(imports, sourceId1); + imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId1); + + storage.synchronizeDocumentImports(imports, sourceId1); + + ASSERT_TRUE(storage.importId(imports.back())); +} + +TEST_F(ProjectStorage, get_exported_type_names) +{ + auto package{createSimpleSynchronizationPackage()}; + storage.synchronize(package); + auto typeId = fetchTypeId(sourceId2, "QObject"); + + auto exportedTypeNames = storage.exportedTypeNames(typeId); + + ASSERT_THAT(exportedTypeNames, + UnorderedElementsAre(IsInfoExportTypeNames(qmlModuleId, "Object", 2, -1), + IsInfoExportTypeNames(qmlModuleId, "Obj", 2, -1), + IsInfoExportTypeNames(qmlNativeModuleId, "QObject", -1, -1))); +} + +TEST_F(ProjectStorage, get_no_exported_type_names_if_type_id_is_invalid) +{ + auto package{createSimpleSynchronizationPackage()}; + storage.synchronize(package); + TypeId typeId; + + auto exportedTypeNames = storage.exportedTypeNames(typeId); + + ASSERT_THAT(exportedTypeNames, IsEmpty()); +} + +TEST_F(ProjectStorage, get_exported_type_names_for_source_id) +{ + auto package{createSimpleSynchronizationPackage()}; + package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId3); + storage.synchronize(package); + auto typeId = fetchTypeId(sourceId2, "QObject"); + + auto exportedTypeNames = storage.exportedTypeNames(typeId, sourceId3); + + ASSERT_THAT(exportedTypeNames, + UnorderedElementsAre(IsInfoExportTypeNames(qmlModuleId, "Object", 2, -1), + IsInfoExportTypeNames(qmlModuleId, "Obj", 2, -1))); +} + +TEST_F(ProjectStorage, get_no_exported_type_names_for_source_id_for_invalid_type_id) +{ + auto package{createSimpleSynchronizationPackage()}; + package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId3); + storage.synchronize(package); + TypeId typeId; + + auto exportedTypeNames = storage.exportedTypeNames(typeId, sourceId3); + + ASSERT_THAT(exportedTypeNames, IsEmpty()); +} + +TEST_F(ProjectStorage, get_no_exported_type_names_for_source_id_for_non_matching_import) +{ + auto package{createSimpleSynchronizationPackage()}; + package.imports.emplace_back(qtQuickModuleId, Storage::Version{}, sourceId3); + storage.synchronize(package); + auto typeId = fetchTypeId(sourceId2, "QObject"); + + auto exportedTypeNames = storage.exportedTypeNames(typeId, sourceId3); + + ASSERT_THAT(exportedTypeNames, IsEmpty()); +} } // namespace diff --git a/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp index 80cb7215fce..6a0a294771c 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp @@ -24,14 +24,14 @@ using QmlDesigner::ModuleId; using QmlDesigner::SourceId; namespace Storage = QmlDesigner::Storage; using QmlDesigner::IdPaths; -using QmlDesigner::Storage::TypeTraits; -using QmlDesigner::Storage::Version; +using QmlDesigner::Storage::Import; using QmlDesigner::Storage::Synchronization::FileType; -using QmlDesigner::Storage::Synchronization::Import; using QmlDesigner::Storage::Synchronization::IsAutoVersion; using QmlDesigner::Storage::Synchronization::ModuleExportedImport; using QmlDesigner::Storage::Synchronization::ProjectData; using QmlDesigner::Storage::Synchronization::SynchronizationPackage; +using QmlDesigner::Storage::TypeTraits; +using QmlDesigner::Storage::Version; MATCHER_P5(IsStorageType, typeName, @@ -327,11 +327,11 @@ protected: Storage::Synchronization::Type firstType; Storage::Synchronization::Type secondType; Storage::Synchronization::Type thirdType; - Storage::Synchronization::Import import1{qmlModuleId, Storage::Version{2, 3}, qmlDocumentSourceId1}; - Storage::Synchronization::Import import2{qmlModuleId, Storage::Version{}, qmlDocumentSourceId2}; - Storage::Synchronization::Import import3{qmlModuleId, Storage::Version{2}, qmlDocumentSourceId3}; - Storage::Synchronization::Import import4{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; - Storage::Synchronization::Import import5{qmlModuleId, Storage::Version{2, 3}, qmltypes2PathSourceId}; + Storage::Import import1{qmlModuleId, Storage::Version{2, 3}, qmlDocumentSourceId1}; + Storage::Import import2{qmlModuleId, Storage::Version{}, qmlDocumentSourceId2}; + Storage::Import import3{qmlModuleId, Storage::Version{2}, qmlDocumentSourceId3}; + Storage::Import import4{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; + Storage::Import import5{qmlModuleId, Storage::Version{2, 3}, qmltypes2PathSourceId}; QString qmldirContent{"module Example\ntypeinfo example.qmltypes\n"}; QString qmltypes1{"Module {\ndependencies: [module1]}"}; QString qmltypes2{"Module {\ndependencies: [module2]}"}; @@ -445,7 +445,7 @@ TEST_F(ProjectStorageUpdater, synchronize_is_empty_for_no_change) TEST_F(ProjectStorageUpdater, synchronize_qml_types) { - Storage::Synchronization::Import import{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; + Storage::Import import{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; QString qmltypes{"Module {\ndependencies: []}"}; setQmlFileNames(u"/path", {}); setContent(u"/path/example.qmltypes", qmltypes); @@ -480,7 +480,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_types) TEST_F(ProjectStorageUpdater, synchronize_qml_types_throws_if_qmltpes_does_not_exists) { - Storage::Synchronization::Import import{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; + Storage::Import import{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; setFilesDontExists({qmltypesPathSourceId}); ASSERT_THROW(updater.update(directories, {}), QmlDesigner::CannotParseQmlTypesFile); diff --git a/tests/unit/tests/unittests/projectstorage/qmldocumentparser-test.cpp b/tests/unit/tests/unittests/projectstorage/qmldocumentparser-test.cpp index e823f5f7f44..f39dec121c6 100644 --- a/tests/unit/tests/unittests/projectstorage/qmldocumentparser-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/qmldocumentparser-test.cpp @@ -11,16 +11,17 @@ namespace { -namespace Storage = QmlDesigner::Storage::Synchronization; +namespace Storage = QmlDesigner::Storage; +namespace Synchronization = Storage::Synchronization; using QmlDesigner::ModuleId; using QmlDesigner::SourceContextId; using QmlDesigner::SourceId; MATCHER_P(HasPrototype, prototype, std::string(negation ? "isn't " : "is ") + PrintToString(prototype)) { - const Storage::Type &type = arg; + const Synchronization::Type &type = arg; - return Storage::ImportedTypeName{prototype} == type.prototype; + return Synchronization::ImportedTypeName{prototype} == type.prototype; } MATCHER_P3(IsPropertyDeclaration, @@ -28,12 +29,12 @@ MATCHER_P3(IsPropertyDeclaration, typeName, traits, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::PropertyDeclaration{name, typeName, traits})) + + PrintToString(Synchronization::PropertyDeclaration{name, typeName, traits})) { - const Storage::PropertyDeclaration &propertyDeclaration = arg; + const Synchronization::PropertyDeclaration &propertyDeclaration = arg; return propertyDeclaration.name == name - && Storage::ImportedTypeName{typeName} == propertyDeclaration.typeName + && Synchronization::ImportedTypeName{typeName} == propertyDeclaration.typeName && propertyDeclaration.traits == traits; } @@ -43,12 +44,13 @@ MATCHER_P4(IsAliasPropertyDeclaration, traits, aliasPropertyName, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::PropertyDeclaration{name, typeName, traits, aliasPropertyName})) + + PrintToString( + Synchronization::PropertyDeclaration{name, typeName, traits, aliasPropertyName})) { - const Storage::PropertyDeclaration &propertyDeclaration = arg; + const Synchronization::PropertyDeclaration &propertyDeclaration = arg; return propertyDeclaration.name == name - && Storage::ImportedTypeName{typeName} == propertyDeclaration.typeName + && Synchronization::ImportedTypeName{typeName} == propertyDeclaration.typeName && propertyDeclaration.traits == traits && propertyDeclaration.aliasPropertyName == aliasPropertyName && propertyDeclaration.aliasPropertyNameTail.empty(); @@ -61,12 +63,13 @@ MATCHER_P5(IsAliasPropertyDeclaration, aliasPropertyName, aliasPropertyNameTail, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::PropertyDeclaration{name, typeName, traits, aliasPropertyName})) + + PrintToString( + Synchronization::PropertyDeclaration{name, typeName, traits, aliasPropertyName})) { - const Storage::PropertyDeclaration &propertyDeclaration = arg; + const Synchronization::PropertyDeclaration &propertyDeclaration = arg; return propertyDeclaration.name == name - && Storage::ImportedTypeName{typeName} == propertyDeclaration.typeName + && Synchronization::ImportedTypeName{typeName} == propertyDeclaration.typeName && propertyDeclaration.traits == traits && propertyDeclaration.aliasPropertyName == aliasPropertyName && propertyDeclaration.aliasPropertyNameTail == aliasPropertyNameTail; @@ -76,18 +79,19 @@ MATCHER_P2(IsFunctionDeclaration, name, returnTypeName, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::FunctionDeclaration{name, returnTypeName})) + + PrintToString(Synchronization::FunctionDeclaration{name, returnTypeName})) { - const Storage::FunctionDeclaration &declaration = arg; + const Synchronization::FunctionDeclaration &declaration = arg; return declaration.name == name && declaration.returnTypeName == returnTypeName; } MATCHER_P(IsSignalDeclaration, name, - std::string(negation ? "isn't " : "is ") + PrintToString(Storage::SignalDeclaration{name})) + std::string(negation ? "isn't " : "is ") + + PrintToString(Synchronization::SignalDeclaration{name})) { - const Storage::SignalDeclaration &declaration = arg; + const Synchronization::SignalDeclaration &declaration = arg; return declaration.name == name; } @@ -96,9 +100,9 @@ MATCHER_P2(IsParameter, name, typeName, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::ParameterDeclaration{name, typeName})) + + PrintToString(Synchronization::ParameterDeclaration{name, typeName})) { - const Storage::ParameterDeclaration &declaration = arg; + const Synchronization::ParameterDeclaration &declaration = arg; return declaration.name == name && declaration.typeName == typeName; } @@ -106,9 +110,9 @@ MATCHER_P2(IsParameter, MATCHER_P(IsEnumeration, name, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::EnumerationDeclaration{name, {}})) + + PrintToString(Synchronization::EnumerationDeclaration{name, {}})) { - const Storage::EnumerationDeclaration &declaration = arg; + const Synchronization::EnumerationDeclaration &declaration = arg; return declaration.name == name; } @@ -116,9 +120,9 @@ MATCHER_P(IsEnumeration, MATCHER_P(IsEnumerator, name, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::EnumeratorDeclaration{name})) + + PrintToString(Synchronization::EnumeratorDeclaration{name})) { - const Storage::EnumeratorDeclaration &declaration = arg; + const Synchronization::EnumeratorDeclaration &declaration = arg; return declaration.name == name && !declaration.hasValue; } @@ -127,9 +131,9 @@ MATCHER_P2(IsEnumerator, name, value, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::EnumeratorDeclaration{name, value, true})) + + PrintToString(Synchronization::EnumeratorDeclaration{name, value, true})) { - const Storage::EnumeratorDeclaration &declaration = arg; + const Synchronization::EnumeratorDeclaration &declaration = arg; return declaration.name == name && declaration.value == value && declaration.hasValue; } @@ -154,7 +158,7 @@ TEST_F(QmlDocumentParser, prototype) { auto type = parser.parse("Example{}", imports, qmlFileSourceId, directoryPath); - ASSERT_THAT(type, HasPrototype(Storage::ImportedType("Example"))); + ASSERT_THAT(type, HasPrototype(Synchronization::ImportedType("Example"))); } TEST_F(QmlDocumentParser, qualified_prototype) @@ -166,11 +170,9 @@ TEST_F(QmlDocumentParser, qualified_prototype) auto type = parser.parse(text, imports, qmlFileSourceId, directoryPath); ASSERT_THAT(type, - HasPrototype( - Storage::QualifiedImportedType("Item", - Storage::Import{exampleModuleId, - QmlDesigner::Storage::Version{2, 1}, - qmlFileSourceId}))); + HasPrototype(Synchronization::QualifiedImportedType( + "Item", + Storage::Import{exampleModuleId, Storage::Version{2, 1}, qmlFileSourceId}))); } TEST_F(QmlDocumentParser, properties) @@ -178,10 +180,9 @@ TEST_F(QmlDocumentParser, properties) auto type = parser.parse(R"(Example{ property int foo })", imports, qmlFileSourceId, directoryPath); ASSERT_THAT(type.propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("foo", - Storage::ImportedType{"int"}, - QmlDesigner::Storage::PropertyDeclarationTraits::None))); + UnorderedElementsAre(IsPropertyDeclaration("foo", + Synchronization::ImportedType{"int"}, + Storage::PropertyDeclarationTraits::None))); } TEST_F(QmlDocumentParser, qualified_properties) @@ -197,11 +198,11 @@ TEST_F(QmlDocumentParser, qualified_properties) ASSERT_THAT(type.propertyDeclarations, UnorderedElementsAre(IsPropertyDeclaration( "foo", - Storage::QualifiedImportedType("Foo", - Storage::Import{exampleModuleId, - QmlDesigner::Storage::Version{2, 1}, - qmlFileSourceId}), - QmlDesigner::Storage::PropertyDeclarationTraits::None))); + Synchronization::QualifiedImportedType("Foo", + Storage::Import{exampleModuleId, + Storage::Version{2, 1}, + qmlFileSourceId}), + Storage::PropertyDeclarationTraits::None))); } TEST_F(QmlDocumentParser, enumeration_in_properties) @@ -215,8 +216,8 @@ TEST_F(QmlDocumentParser, enumeration_in_properties) ASSERT_THAT(type.propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("foo", - Storage::ImportedType("Enumeration.Foo"), - QmlDesigner::Storage::PropertyDeclarationTraits::None))); + Synchronization::ImportedType("Enumeration.Foo"), + Storage::PropertyDeclarationTraits::None))); } TEST_F(QmlDocumentParser, qualified_enumeration_in_properties) @@ -232,11 +233,11 @@ TEST_F(QmlDocumentParser, qualified_enumeration_in_properties) ASSERT_THAT(type.propertyDeclarations, UnorderedElementsAre(IsPropertyDeclaration( "foo", - Storage::QualifiedImportedType("Enumeration.Foo", - Storage::Import{exampleModuleId, - QmlDesigner::Storage::Version{2, 1}, - qmlFileSourceId}), - QmlDesigner::Storage::PropertyDeclarationTraits::None))); + Synchronization::QualifiedImportedType("Enumeration.Foo", + Storage::Import{exampleModuleId, + Storage::Version{2, 1}, + qmlFileSourceId}), + Storage::PropertyDeclarationTraits::None))); } TEST_F(QmlDocumentParser, imports) @@ -252,13 +253,12 @@ TEST_F(QmlDocumentParser, imports) qmlFileSourceId, directoryPath); - ASSERT_THAT( - imports, - UnorderedElementsAre( - Storage::Import{directoryModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId}, - Storage::Import{fooDirectoryModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId}, - Storage::Import{qmlModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId}, - Storage::Import{qtQuickModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId})); + ASSERT_THAT(imports, + UnorderedElementsAre( + Storage::Import{directoryModuleId, Storage::Version{}, qmlFileSourceId}, + Storage::Import{fooDirectoryModuleId, Storage::Version{}, qmlFileSourceId}, + Storage::Import{qmlModuleId, Storage::Version{}, qmlFileSourceId}, + Storage::Import{qtQuickModuleId, Storage::Version{}, qmlFileSourceId})); } TEST_F(QmlDocumentParser, imports_with_version) @@ -274,13 +274,12 @@ TEST_F(QmlDocumentParser, imports_with_version) qmlFileSourceId, directoryPath); - ASSERT_THAT( - imports, - UnorderedElementsAre( - Storage::Import{directoryModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId}, - Storage::Import{fooDirectoryModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId}, - Storage::Import{qmlModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId}, - Storage::Import{qtQuickModuleId, QmlDesigner::Storage::Version{2, 1}, qmlFileSourceId})); + ASSERT_THAT(imports, + UnorderedElementsAre( + Storage::Import{directoryModuleId, Storage::Version{}, qmlFileSourceId}, + Storage::Import{fooDirectoryModuleId, Storage::Version{}, qmlFileSourceId}, + Storage::Import{qmlModuleId, Storage::Version{}, qmlFileSourceId}, + Storage::Import{qtQuickModuleId, Storage::Version{2, 1}, qmlFileSourceId})); } TEST_F(QmlDocumentParser, imports_with_explict_directory) @@ -296,11 +295,11 @@ TEST_F(QmlDocumentParser, imports_with_explict_directory) qmlFileSourceId, directoryPath); - ASSERT_THAT(imports, - UnorderedElementsAre( - Storage::Import{directoryModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId}, - Storage::Import{qmlModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId}, - Storage::Import{qtQuickModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId})); + ASSERT_THAT( + imports, + UnorderedElementsAre(Storage::Import{directoryModuleId, Storage::Version{}, qmlFileSourceId}, + Storage::Import{qmlModuleId, Storage::Version{}, qmlFileSourceId}, + Storage::Import{qtQuickModuleId, Storage::Version{}, qmlFileSourceId})); } TEST_F(QmlDocumentParser, functions) @@ -312,12 +311,12 @@ TEST_F(QmlDocumentParser, functions) directoryPath); ASSERT_THAT(type.functionDeclarations, - UnorderedElementsAre(AllOf(IsFunctionDeclaration("otherFunction", ""), - Field(&Storage::FunctionDeclaration::parameters, IsEmpty())), - AllOf(IsFunctionDeclaration("someScript", ""), - Field(&Storage::FunctionDeclaration::parameters, - ElementsAre(IsParameter("x", ""), - IsParameter("y", "")))))); + UnorderedElementsAre( + AllOf(IsFunctionDeclaration("otherFunction", ""), + Field(&Synchronization::FunctionDeclaration::parameters, IsEmpty())), + AllOf(IsFunctionDeclaration("someScript", ""), + Field(&Synchronization::FunctionDeclaration::parameters, + ElementsAre(IsParameter("x", ""), IsParameter("y", "")))))); } TEST_F(QmlDocumentParser, signals) @@ -328,12 +327,12 @@ TEST_F(QmlDocumentParser, signals) directoryPath); ASSERT_THAT(type.signalDeclarations, - UnorderedElementsAre(AllOf(IsSignalDeclaration("someSignal"), - Field(&Storage::SignalDeclaration::parameters, - ElementsAre(IsParameter("x", "int"), - IsParameter("y", "real")))), - AllOf(IsSignalDeclaration("signal2"), - Field(&Storage::SignalDeclaration::parameters, IsEmpty())))); + UnorderedElementsAre( + AllOf(IsSignalDeclaration("someSignal"), + Field(&Synchronization::SignalDeclaration::parameters, + ElementsAre(IsParameter("x", "int"), IsParameter("y", "real")))), + AllOf(IsSignalDeclaration("signal2"), + Field(&Synchronization::SignalDeclaration::parameters, IsEmpty())))); } TEST_F(QmlDocumentParser, enumeration) @@ -347,13 +346,13 @@ TEST_F(QmlDocumentParser, enumeration) ASSERT_THAT(type.enumerationDeclarations, UnorderedElementsAre( AllOf(IsEnumeration("Color"), - Field(&Storage::EnumerationDeclaration::enumeratorDeclarations, + Field(&Synchronization::EnumerationDeclaration::enumeratorDeclarations, ElementsAre(IsEnumerator("red", 0), IsEnumerator("green", 1), IsEnumerator("blue", 10), IsEnumerator("white", 11)))), AllOf(IsEnumeration("State"), - Field(&Storage::EnumerationDeclaration::enumeratorDeclarations, + Field(&Synchronization::EnumerationDeclaration::enumeratorDeclarations, ElementsAre(IsEnumerator("On", 0), IsEnumerator("Off", 1)))))); } @@ -376,14 +375,13 @@ TEST_F(QmlDocumentParser, DISABLED_duplicate_imports_are_removed) qmlFileSourceId, directoryPath); - ASSERT_THAT( - imports, - UnorderedElementsAre( - Storage::Import{directoryModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId}, - Storage::Import{fooDirectoryModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId}, - Storage::Import{qmlModuleId, QmlDesigner::Storage::Version{1, 0}, qmlFileSourceId}, - Storage::Import{qtQmlModuleId, QmlDesigner::Storage::Version{6, 0}, qmlFileSourceId}, - Storage::Import{qtQuickModuleId, QmlDesigner::Storage::Version{}, qmlFileSourceId})); + ASSERT_THAT(imports, + UnorderedElementsAre( + Storage::Import{directoryModuleId, Storage::Version{}, qmlFileSourceId}, + Storage::Import{fooDirectoryModuleId, Storage::Version{}, qmlFileSourceId}, + Storage::Import{qmlModuleId, Storage::Version{1, 0}, qmlFileSourceId}, + Storage::Import{qtQmlModuleId, Storage::Version{6, 0}, qmlFileSourceId}, + Storage::Import{qtQuickModuleId, Storage::Version{}, qmlFileSourceId})); } TEST_F(QmlDocumentParser, alias_item_properties) @@ -399,10 +397,9 @@ TEST_F(QmlDocumentParser, alias_item_properties) directoryPath); ASSERT_THAT(type.propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("delegate", - Storage::ImportedType{"Item"}, - QmlDesigner::Storage::PropertyDeclarationTraits::None))); + UnorderedElementsAre(IsPropertyDeclaration("delegate", + Synchronization::ImportedType{"Item"}, + Storage::PropertyDeclarationTraits::None))); } TEST_F(QmlDocumentParser, alias_properties) @@ -420,8 +417,8 @@ TEST_F(QmlDocumentParser, alias_properties) ASSERT_THAT(type.propertyDeclarations, UnorderedElementsAre( IsAliasPropertyDeclaration("text", - Storage::ImportedType{"Item"}, - QmlDesigner::Storage::PropertyDeclarationTraits::None, + Synchronization::ImportedType{"Item"}, + Storage::PropertyDeclarationTraits::None, "text2"))); } @@ -440,8 +437,8 @@ TEST_F(QmlDocumentParser, indirect_alias_properties) ASSERT_THAT(type.propertyDeclarations, UnorderedElementsAre( IsAliasPropertyDeclaration("textSize", - Storage::ImportedType{"Item"}, - QmlDesigner::Storage::PropertyDeclarationTraits::None, + Synchronization::ImportedType{"Item"}, + Storage::PropertyDeclarationTraits::None, "text", "size"))); } @@ -473,8 +470,8 @@ TEST_F(QmlDocumentParser, list_property) ASSERT_THAT(type.propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("foos", - Storage::ImportedType{"Foo"}, - QmlDesigner::Storage::PropertyDeclarationTraits::IsList))); + Synchronization::ImportedType{"Foo"}, + Storage::PropertyDeclarationTraits::IsList))); } TEST_F(QmlDocumentParser, alias_on_list_property) @@ -494,8 +491,8 @@ TEST_F(QmlDocumentParser, alias_on_list_property) ASSERT_THAT(type.propertyDeclarations, UnorderedElementsAre( IsPropertyDeclaration("foos", - Storage::ImportedType{"Foo"}, - QmlDesigner::Storage::PropertyDeclarationTraits::IsList))); + Synchronization::ImportedType{"Foo"}, + Storage::PropertyDeclarationTraits::IsList))); } TEST_F(QmlDocumentParser, qualified_list_property) @@ -512,11 +509,11 @@ TEST_F(QmlDocumentParser, qualified_list_property) ASSERT_THAT(type.propertyDeclarations, UnorderedElementsAre(IsPropertyDeclaration( "foos", - Storage::QualifiedImportedType{"Foo", - Storage::Import{exampleModuleId, - QmlDesigner::Storage::Version{2, 1}, - qmlFileSourceId}}, - QmlDesigner::Storage::PropertyDeclarationTraits::IsList))); + Synchronization::QualifiedImportedType{"Foo", + Storage::Import{exampleModuleId, + Storage::Version{2, 1}, + qmlFileSourceId}}, + Storage::PropertyDeclarationTraits::IsList))); } } // namespace diff --git a/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp b/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp index 02b4514841d..68138232b20 100644 --- a/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/qmltypesparser-test.cpp @@ -12,7 +12,8 @@ namespace { -namespace Storage = QmlDesigner::Storage::Synchronization; +namespace Storage = QmlDesigner::Storage; +namespace Synchronization = QmlDesigner::Storage::Synchronization; using QmlDesigner::ModuleId; using QmlDesigner::SourceContextId; using QmlDesigner::SourceId; @@ -31,9 +32,9 @@ MATCHER_P3(IsImport, MATCHER_P(HasPrototype, prototype, std::string(negation ? "isn't " : "is ") + PrintToString(prototype)) { - const Storage::Type &type = arg; + const Synchronization::Type &type = arg; - return Storage::ImportedTypeName{prototype} == type.prototype; + return Synchronization::ImportedTypeName{prototype} == type.prototype; } MATCHER_P5(IsType, @@ -43,13 +44,13 @@ MATCHER_P5(IsType, traits, sourceId, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::Type{typeName, prototype, extension, traits, sourceId})) + + PrintToString(Synchronization::Type{typeName, prototype, extension, traits, sourceId})) { - const Storage::Type &type = arg; + const Synchronization::Type &type = arg; - return type.typeName == typeName && type.prototype == Storage::ImportedTypeName{prototype} - && type.extension == Storage::ImportedTypeName{extension} && type.traits == traits - && type.sourceId == sourceId; + return type.typeName == typeName && type.prototype == Synchronization::ImportedTypeName{prototype} + && type.extension == Synchronization::ImportedTypeName{extension} + && type.traits == traits && type.sourceId == sourceId; } MATCHER_P3(IsPropertyDeclaration, @@ -57,32 +58,33 @@ MATCHER_P3(IsPropertyDeclaration, typeName, traits, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::PropertyDeclaration{name, typeName, traits})) + + PrintToString(Synchronization::PropertyDeclaration{name, typeName, traits})) { - const Storage::PropertyDeclaration &propertyDeclaration = arg; + const Synchronization::PropertyDeclaration &propertyDeclaration = arg; return propertyDeclaration.name == name - && Storage::ImportedTypeName{typeName} == propertyDeclaration.typeName + && Synchronization::ImportedTypeName{typeName} == propertyDeclaration.typeName && propertyDeclaration.traits == traits - && propertyDeclaration.kind == Storage::PropertyKind::Property; + && propertyDeclaration.kind == Synchronization::PropertyKind::Property; } MATCHER_P2(IsFunctionDeclaration, name, returnTypeName, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::FunctionDeclaration{name, returnTypeName})) + + PrintToString(Synchronization::FunctionDeclaration{name, returnTypeName})) { - const Storage::FunctionDeclaration &declaration = arg; + const Synchronization::FunctionDeclaration &declaration = arg; return declaration.name == name && declaration.returnTypeName == returnTypeName; } MATCHER_P(IsSignalDeclaration, name, - std::string(negation ? "isn't " : "is ") + PrintToString(Storage::SignalDeclaration{name})) + std::string(negation ? "isn't " : "is ") + + PrintToString(Synchronization::SignalDeclaration{name})) { - const Storage::SignalDeclaration &declaration = arg; + const Synchronization::SignalDeclaration &declaration = arg; return declaration.name == name; } @@ -91,9 +93,9 @@ MATCHER_P2(IsParameter, name, typeName, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::ParameterDeclaration{name, typeName})) + + PrintToString(Synchronization::ParameterDeclaration{name, typeName})) { - const Storage::ParameterDeclaration &declaration = arg; + const Synchronization::ParameterDeclaration &declaration = arg; return declaration.name == name && declaration.typeName == typeName; } @@ -101,9 +103,9 @@ MATCHER_P2(IsParameter, MATCHER_P(IsEnumeration, name, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::EnumerationDeclaration{name, {}})) + + PrintToString(Synchronization::EnumerationDeclaration{name, {}})) { - const Storage::EnumerationDeclaration &declaration = arg; + const Synchronization::EnumerationDeclaration &declaration = arg; return declaration.name == name; } @@ -111,9 +113,9 @@ MATCHER_P(IsEnumeration, MATCHER_P(IsEnumerator, name, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::EnumeratorDeclaration{name})) + + PrintToString(Synchronization::EnumeratorDeclaration{name})) { - const Storage::EnumeratorDeclaration &declaration = arg; + const Synchronization::EnumeratorDeclaration &declaration = arg; return declaration.name == name && !declaration.hasValue; } @@ -122,9 +124,9 @@ MATCHER_P2(IsEnumerator, name, value, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::EnumeratorDeclaration{name, value, true})) + + PrintToString(Synchronization::EnumeratorDeclaration{name, value, true})) { - const Storage::EnumeratorDeclaration &declaration = arg; + const Synchronization::EnumeratorDeclaration &declaration = arg; return declaration.name == name && declaration.value == value && declaration.hasValue; } @@ -134,9 +136,9 @@ MATCHER_P3(IsExportedType, name, version, std::string(negation ? "isn't " : "is ") - + PrintToString(Storage::ExportedType{moduleId, name, version})) + + PrintToString(Synchronization::ExportedType{moduleId, name, version})) { - const Storage::ExportedType &type = arg; + const Synchronization::ExportedType &type = arg; return type.name == name && type.moduleId == moduleId && type.version == version; } @@ -151,13 +153,13 @@ protected: storage}; QmlDesigner::QmlTypesParser parser{storage}; Storage::Imports imports; - Storage::Types types; + Synchronization::Types types; SourceId qmltypesFileSourceId{sourcePathCache.sourceId("path/to/types.qmltypes")}; ModuleId qtQmlNativeModuleId = storage.moduleId("QtQml-cppnative"); - Storage::ProjectData projectData{qmltypesFileSourceId, - qmltypesFileSourceId, - qtQmlNativeModuleId, - Storage::FileType::QmlTypes}; + Synchronization::ProjectData projectData{qmltypesFileSourceId, + qmltypesFileSourceId, + qtQmlNativeModuleId, + Synchronization::FileType::QmlTypes}; SourceContextId qmltypesFileSourceContextId{sourcePathCache.sourceContextId(qmltypesFileSourceId)}; ModuleId directoryModuleId{storage.moduleId("path/to/")}; }; @@ -200,14 +202,14 @@ TEST_F(QmlTypesParser, types) ASSERT_THAT(types, UnorderedElementsAre(IsType("QObject", - Storage::ImportedType{}, - Storage::ImportedType{}, - QmlDesigner::Storage::TypeTraits::Reference, + Synchronization::ImportedType{}, + Synchronization::ImportedType{}, + Storage::TypeTraits::Reference, qmltypesFileSourceId), IsType("QQmlComponent", - Storage::ImportedType{}, - Storage::ImportedType{}, - QmlDesigner::Storage::TypeTraits::Reference, + Synchronization::ImportedType{}, + Synchronization::ImportedType{}, + Storage::TypeTraits::Reference, qmltypesFileSourceId))); } @@ -223,14 +225,14 @@ TEST_F(QmlTypesParser, prototype) ASSERT_THAT(types, UnorderedElementsAre(IsType("QObject", - Storage::ImportedType{}, - Storage::ImportedType{}, - QmlDesigner::Storage::TypeTraits::Reference, + Synchronization::ImportedType{}, + Synchronization::ImportedType{}, + Storage::TypeTraits::Reference, qmltypesFileSourceId), IsType("QQmlComponent", - Storage::ImportedType{"QObject"}, - Storage::ImportedType{}, - QmlDesigner::Storage::TypeTraits::Reference, + Synchronization::ImportedType{"QObject"}, + Synchronization::ImportedType{}, + Storage::TypeTraits::Reference, qmltypesFileSourceId))); } @@ -246,14 +248,14 @@ TEST_F(QmlTypesParser, extension) ASSERT_THAT(types, UnorderedElementsAre(IsType("QObject", - Storage::ImportedType{}, - Storage::ImportedType{}, - QmlDesigner::Storage::TypeTraits::Reference, + Synchronization::ImportedType{}, + Synchronization::ImportedType{}, + Storage::TypeTraits::Reference, qmltypesFileSourceId), IsType("QQmlComponent", - Storage::ImportedType{}, - Storage::ImportedType{"QObject"}, - QmlDesigner::Storage::TypeTraits::Reference, + Synchronization::ImportedType{}, + Synchronization::ImportedType{"QObject"}, + Storage::TypeTraits::Reference, qmltypesFileSourceId))); } @@ -272,7 +274,7 @@ TEST_F(QmlTypesParser, exported_types) ASSERT_THAT( types, ElementsAre(Field( - &Storage::Type::exportedTypes, + &Synchronization::Type::exportedTypes, UnorderedElementsAre( IsExportedType(qmlModuleId, "QtObject", QmlDesigner::Storage::Version{1, 0}), IsExportedType(qtQmlModuleId, "QtObject", QmlDesigner::Storage::Version{2, 1}), @@ -292,25 +294,24 @@ TEST_F(QmlTypesParser, properties) parser.parse(source, imports, types, projectData); - ASSERT_THAT( - types, - ElementsAre(Field( - &Storage::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("objectName", - Storage::ImportedType{"string"}, - QmlDesigner::Storage::PropertyDeclarationTraits::None), - IsPropertyDeclaration("target", - Storage::ImportedType{"QObject"}, - QmlDesigner::Storage::PropertyDeclarationTraits::IsPointer), - IsPropertyDeclaration("progress", - Storage::ImportedType{"double"}, - QmlDesigner::Storage::PropertyDeclarationTraits::IsReadOnly), - IsPropertyDeclaration("targets", - Storage::ImportedType{"QQuickItem"}, - QmlDesigner::Storage::PropertyDeclarationTraits::IsReadOnly - | QmlDesigner::Storage::PropertyDeclarationTraits::IsList - | QmlDesigner::Storage::PropertyDeclarationTraits::IsPointer))))); + ASSERT_THAT(types, + ElementsAre(Field( + &Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("objectName", + Synchronization::ImportedType{"string"}, + Storage::PropertyDeclarationTraits::None), + IsPropertyDeclaration("target", + Synchronization::ImportedType{"QObject"}, + Storage::PropertyDeclarationTraits::IsPointer), + IsPropertyDeclaration("progress", + Synchronization::ImportedType{"double"}, + Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("targets", + Synchronization::ImportedType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsReadOnly + | Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsPointer))))); } TEST_F(QmlTypesParser, properties_with_qualified_types) @@ -327,20 +328,19 @@ TEST_F(QmlTypesParser, properties_with_qualified_types) parser.parse(source, imports, types, projectData); - ASSERT_THAT( - types, - Contains( - Field(&Storage::Type::propertyDeclarations, - UnorderedElementsAre( - IsPropertyDeclaration("values", - Storage::ImportedType{"Qt::Vector"}, - QmlDesigner::Storage::PropertyDeclarationTraits::None), - IsPropertyDeclaration("items", - Storage::ImportedType{"Qt::List"}, - QmlDesigner::Storage::PropertyDeclarationTraits::None), - IsPropertyDeclaration("values2", - Storage::ImportedType{"Qt::Vector"}, - QmlDesigner::Storage::PropertyDeclarationTraits::None))))); + ASSERT_THAT(types, + Contains( + Field(&Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("values", + Synchronization::ImportedType{"Qt::Vector"}, + Storage::PropertyDeclarationTraits::None), + IsPropertyDeclaration("items", + Synchronization::ImportedType{"Qt::List"}, + Storage::PropertyDeclarationTraits::None), + IsPropertyDeclaration("values2", + Synchronization::ImportedType{"Qt::Vector"}, + Storage::PropertyDeclarationTraits::None))))); } TEST_F(QmlTypesParser, properties_without_type) @@ -355,11 +355,12 @@ TEST_F(QmlTypesParser, properties_without_type) parser.parse(source, imports, types, projectData); ASSERT_THAT(types, - ElementsAre(Field(&Storage::Type::propertyDeclarations, - UnorderedElementsAre(IsPropertyDeclaration( - "target", - Storage::ImportedType{"QObject"}, - QmlDesigner::Storage::PropertyDeclarationTraits::IsPointer))))); + ElementsAre( + Field(&Synchronization::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("target", + Synchronization::ImportedType{"QObject"}, + Storage::PropertyDeclarationTraits::IsPointer))))); } TEST_F(QmlTypesParser, functions) @@ -387,21 +388,21 @@ TEST_F(QmlTypesParser, functions) parser.parse(source, imports, types, projectData); ASSERT_THAT(types, - ElementsAre( - Field(&Storage::Type::functionDeclarations, - UnorderedElementsAre( - AllOf(IsFunctionDeclaration("advance", ""), - Field(&Storage::FunctionDeclaration::parameters, - UnorderedElementsAre(IsParameter("frames", "int"), - IsParameter("fps", "double")))), - AllOf(IsFunctionDeclaration("isImageLoading", "bool"), - Field(&Storage::FunctionDeclaration::parameters, - UnorderedElementsAre(IsParameter("url", "QUrl")))), - AllOf(IsFunctionDeclaration("getContext", ""), - Field(&Storage::FunctionDeclaration::parameters, - UnorderedElementsAre(IsParameter("args", "QQmlV4Function")))), - AllOf(IsFunctionDeclaration("movieUpdate", ""), - Field(&Storage::FunctionDeclaration::parameters, IsEmpty())))))); + ElementsAre(Field( + &Synchronization::Type::functionDeclarations, + UnorderedElementsAre( + AllOf(IsFunctionDeclaration("advance", ""), + Field(&Synchronization::FunctionDeclaration::parameters, + UnorderedElementsAre(IsParameter("frames", "int"), + IsParameter("fps", "double")))), + AllOf(IsFunctionDeclaration("isImageLoading", "bool"), + Field(&Synchronization::FunctionDeclaration::parameters, + UnorderedElementsAre(IsParameter("url", "QUrl")))), + AllOf(IsFunctionDeclaration("getContext", ""), + Field(&Synchronization::FunctionDeclaration::parameters, + UnorderedElementsAre(IsParameter("args", "QQmlV4Function")))), + AllOf(IsFunctionDeclaration("movieUpdate", ""), + Field(&Synchronization::FunctionDeclaration::parameters, IsEmpty())))))); } TEST_F(QmlTypesParser, skip_java_script_functions) @@ -417,7 +418,7 @@ TEST_F(QmlTypesParser, skip_java_script_functions) parser.parse(source, imports, types, projectData); - ASSERT_THAT(types, ElementsAre(Field(&Storage::Type::functionDeclarations, IsEmpty()))); + ASSERT_THAT(types, ElementsAre(Field(&Synchronization::Type::functionDeclarations, IsEmpty()))); } TEST_F(QmlTypesParser, functions_with_qualified_types) @@ -439,15 +440,16 @@ TEST_F(QmlTypesParser, functions_with_qualified_types) ASSERT_THAT(types, Contains( - Field(&Storage::Type::functionDeclarations, + Field(&Synchronization::Type::functionDeclarations, UnorderedElementsAre(AllOf( IsFunctionDeclaration("values", ""), - Field(&Storage::FunctionDeclaration::parameters, + Field(&Synchronization::FunctionDeclaration::parameters, UnorderedElementsAre(IsParameter("values", "Qt::Vector"), IsParameter("items", "Qt::List"), IsParameter("values2", "Qt::Vector")))))))); } +#undef signals TEST_F(QmlTypesParser, signals) { QString source{R"(import QtQuick.tooling 1.2 @@ -472,17 +474,17 @@ TEST_F(QmlTypesParser, signals) parser.parse(source, imports, types, projectData); ASSERT_THAT(types, - ElementsAre(Field(&Storage::Type::signalDeclarations, + ElementsAre(Field(&Synchronization::Type::signalDeclarations, UnorderedElementsAre( AllOf(IsSignalDeclaration("advance"), - Field(&Storage::SignalDeclaration::parameters, + Field(&Synchronization::SignalDeclaration::parameters, UnorderedElementsAre(IsParameter("frames", "int"), IsParameter("fps", "double")))), AllOf(IsSignalDeclaration("isImageLoading"), - Field(&Storage::SignalDeclaration::parameters, + Field(&Synchronization::SignalDeclaration::parameters, UnorderedElementsAre(IsParameter("url", "QUrl")))), AllOf(IsSignalDeclaration("getContext"), - Field(&Storage::SignalDeclaration::parameters, + Field(&Synchronization::SignalDeclaration::parameters, UnorderedElementsAre( IsParameter("args", "QQmlV4Function")))))))); } @@ -506,10 +508,10 @@ TEST_F(QmlTypesParser, signals_with_qualified_types) ASSERT_THAT(types, Contains( - Field(&Storage::Type::signalDeclarations, + Field(&Synchronization::Type::signalDeclarations, UnorderedElementsAre(AllOf( IsSignalDeclaration("values"), - Field(&Storage::SignalDeclaration::parameters, + Field(&Synchronization::SignalDeclaration::parameters, UnorderedElementsAre(IsParameter("values", "Qt::Vector"), IsParameter("items", "Qt::List"), IsParameter("values2", "Qt::Vector")))))))); @@ -538,19 +540,19 @@ TEST_F(QmlTypesParser, enumerations) parser.parse(source, imports, types, projectData); ASSERT_THAT(types, - Contains( - Field(&Storage::Type::enumerationDeclarations, - UnorderedElementsAre( - AllOf(IsEnumeration("NamedColorSpace"), - Field(&Storage::EnumerationDeclaration::enumeratorDeclarations, - UnorderedElementsAre(IsEnumerator("Unknown"), - IsEnumerator("SRgb"), - IsEnumerator("AdobeRgb"), - IsEnumerator("DisplayP3")))), - AllOf(IsEnumeration("VerticalLayoutDirection"), - Field(&Storage::EnumerationDeclaration::enumeratorDeclarations, - UnorderedElementsAre(IsEnumerator("TopToBottom"), - IsEnumerator("BottomToTop")))))))); + Contains(Field( + &Synchronization::Type::enumerationDeclarations, + UnorderedElementsAre( + AllOf(IsEnumeration("NamedColorSpace"), + Field(&Synchronization::EnumerationDeclaration::enumeratorDeclarations, + UnorderedElementsAre(IsEnumerator("Unknown"), + IsEnumerator("SRgb"), + IsEnumerator("AdobeRgb"), + IsEnumerator("DisplayP3")))), + AllOf(IsEnumeration("VerticalLayoutDirection"), + Field(&Synchronization::EnumerationDeclaration::enumeratorDeclarations, + UnorderedElementsAre(IsEnumerator("TopToBottom"), + IsEnumerator("BottomToTop")))))))); } TEST_F(QmlTypesParser, enumeration_is_exported_as_type) @@ -580,20 +582,20 @@ TEST_F(QmlTypesParser, enumeration_is_exported_as_type) types, UnorderedElementsAre( AllOf(IsType("QObject::NamedColorSpace", - Storage::ImportedType{}, - Storage::ImportedType{}, - QmlDesigner::Storage::TypeTraits::Value | QmlDesigner::Storage::TypeTraits::IsEnum, + Synchronization::ImportedType{}, + Synchronization::ImportedType{}, + Storage::TypeTraits::Value | Storage::TypeTraits::IsEnum, qmltypesFileSourceId), - Field(&Storage::Type::exportedTypes, + Field(&Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQmlNativeModuleId, "QObject::NamedColorSpace", QmlDesigner::Storage::Version{})))), AllOf(IsType("QObject::VerticalLayoutDirection", - Storage::ImportedType{}, - Storage::ImportedType{}, - QmlDesigner::Storage::TypeTraits::Value | QmlDesigner::Storage::TypeTraits::IsEnum, + Synchronization::ImportedType{}, + Synchronization::ImportedType{}, + Storage::TypeTraits::Value | Storage::TypeTraits::IsEnum, qmltypesFileSourceId), - Field(&Storage::Type::exportedTypes, + Field(&Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQmlNativeModuleId, "QObject::VerticalLayoutDirection", QmlDesigner::Storage::Version{})))), @@ -623,12 +625,11 @@ TEST_F(QmlTypesParser, enumeration_is_exported_as_type_with_alias) ASSERT_THAT(types, UnorderedElementsAre( AllOf(IsType("QObject::NamedColorSpaces", - Storage::ImportedType{}, - Storage::ImportedType{}, - QmlDesigner::Storage::TypeTraits::Value - | QmlDesigner::Storage::TypeTraits::IsEnum, + Synchronization::ImportedType{}, + Synchronization::ImportedType{}, + Storage::TypeTraits::Value | Storage::TypeTraits::IsEnum, qmltypesFileSourceId), - Field(&Storage::Type::exportedTypes, + Field(&Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQmlNativeModuleId, "QObject::NamedColorSpace", QmlDesigner::Storage::Version{}), @@ -670,12 +671,11 @@ TEST_F(QmlTypesParser, enumeration_is_exported_as_type_with_alias_too) ASSERT_THAT(types, UnorderedElementsAre( AllOf(IsType("QObject::NamedColorSpaces", - Storage::ImportedType{}, - Storage::ImportedType{}, - QmlDesigner::Storage::TypeTraits::Value - | QmlDesigner::Storage::TypeTraits::IsEnum, + Synchronization::ImportedType{}, + Synchronization::ImportedType{}, + Storage::TypeTraits::Value | Storage::TypeTraits::IsEnum, qmltypesFileSourceId), - Field(&Storage::Type::exportedTypes, + Field(&Synchronization::Type::exportedTypes, UnorderedElementsAre(IsExportedType(qtQmlNativeModuleId, "QObject::NamedColorSpace", QmlDesigner::Storage::Version{}), @@ -705,10 +705,10 @@ TEST_F(QmlTypesParser, enumeration_is_referenced_by_qualified_name) parser.parse(source, imports, types, projectData); ASSERT_THAT(types, - Contains(Field(&Storage::Type::propertyDeclarations, + Contains(Field(&Synchronization::Type::propertyDeclarations, ElementsAre(IsPropertyDeclaration( "colorSpace", - Storage::ImportedType{"QObject::NamedColorSpace"}, + Synchronization::ImportedType{"QObject::NamedColorSpace"}, QmlDesigner::Storage::PropertyDeclarationTraits::None))))); } @@ -733,10 +733,10 @@ TEST_F(QmlTypesParser, alias_enumeration_is_referenced_by_qualified_name) parser.parse(source, imports, types, projectData); ASSERT_THAT(types, - Contains(Field(&Storage::Type::propertyDeclarations, + Contains(Field(&Synchronization::Type::propertyDeclarations, ElementsAre(IsPropertyDeclaration( "colorSpace", - Storage::ImportedType{"QObject::NamedColorSpaces"}, + Synchronization::ImportedType{"QObject::NamedColorSpaces"}, QmlDesigner::Storage::PropertyDeclarationTraits::None))))); } diff --git a/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp b/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp index 64a9cdd2772..f56de53f041 100644 --- a/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp +++ b/tests/unit/tests/unittests/sqlite/sqlitestatement-test.cpp @@ -887,6 +887,8 @@ public: Sqlite::Value value; + bool isNull() const { return value.isNull(); } + template friend bool operator==(const FooValue &value, const Type &other) { diff --git a/tests/unit/tests/unittests/utils/smallstring-test.cpp b/tests/unit/tests/unittests/utils/smallstring-test.cpp index 98cdc266f9a..134891e5839 100644 --- a/tests/unit/tests/unittests/utils/smallstring-test.cpp +++ b/tests/unit/tests/unittests/utils/smallstring-test.cpp @@ -9,6 +9,8 @@ #include #include +#include + using Utils::PathString; using Utils::SmallString; using Utils::SmallStringLiteral; @@ -613,16 +615,44 @@ TEST(SmallString, to_q_string) ASSERT_THAT(qStringText, QStringLiteral("short string")); } -TEST(SmallString, from_q_string) +class FromQString : public testing::TestWithParam { - QString qStringText = QStringLiteral("short string"); +protected: + QString randomString(qsizetype size) + { + static constexpr char16_t characters[] = u"0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - auto text = SmallString::fromQString(qStringText); + static std::mt19937 generator{std::random_device{}()}; + static std::uniform_int_distribution pick(0, std::size(characters) - 1); - ASSERT_THAT(text, SmallString("short string")); + QString string; + + string.reserve(size); + + std::generate_n(std::back_inserter(string), size, [&]() { + return characters[pick(generator)]; + }); + + return string; + } + +protected: + qsizetype size = GetParam(); +}; + +INSTANTIATE_TEST_SUITE_P(SmallString, FromQString, testing::Range(0, 10000, 300)); + +TEST_P(FromQString, from_qstring) +{ + const QString qStringText = randomString(size); + + auto text = SmallString(qStringText); + + ASSERT_THAT(text, qStringText.toStdString()); } - TEST(SmallString, from_q_byte_array) { QByteArray qByteArray = QByteArrayLiteral("short string"); diff --git a/tests/unit/tests/utils/google-using-declarations.h b/tests/unit/tests/utils/google-using-declarations.h index 19e8df2ef20..0d63479fbe1 100644 --- a/tests/unit/tests/utils/google-using-declarations.h +++ b/tests/unit/tests/utils/google-using-declarations.h @@ -31,7 +31,6 @@ using testing::HasSubstr; using testing::InSequence; using testing::Invoke; using testing::IsFalse; -using testing::IsNull; using testing::IsSubsetOf; using testing::IsSupersetOf; using testing::IsTrue; diff --git a/tests/unit/tests/utils/googletest.h b/tests/unit/tests/utils/googletest.h index a64bdb4435f..f4dfd2f988c 100644 --- a/tests/unit/tests/utils/googletest.h +++ b/tests/unit/tests/utils/googletest.h @@ -7,20 +7,20 @@ # pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" #endif -#include -#include -#include -#include -#include - -#include "../utils/google-using-declarations.h" - #include "conditionally-disabled-tests.h" #include "../printers/gtest-creator-printing.h" #include "../printers/gtest-qt-printing.h" #include "../printers/gtest-std-printing.h" +#include "../utils/google-using-declarations.h" + #include "../matchers/unittest-matchers.h" #include "unittest-utility-functions.h" + +#include +#include +#include +#include +#include